#include "sass.hpp" #include #include #include #include #include #include #include "file.hpp" #include "eval.hpp" #include "ast.hpp" #include "bind.hpp" #include "util.hpp" #include "inspect.hpp" #include "environment.hpp" #include "position.hpp" #include "sass/values.h" #include "to_value.hpp" #include "to_c.hpp" #include "context.hpp" #include "backtrace.hpp" #include "lexer.hpp" #include "prelexer.hpp" #include "parser.hpp" #include "expand.hpp" #include "color_maps.hpp" namespace Sass { inline double add(double x, double y) { return x + y; } inline double sub(double x, double y) { return x - y; } inline double mul(double x, double y) { return x * y; } inline double div(double x, double y) { return x / y; } // x/0 checked by caller inline double mod(double x, double y) { return std::abs(std::fmod(x, y)); } // x/0 checked by caller typedef double (*bop)(double, double); bop ops[Sass_OP::NUM_OPS] = { 0, 0, // and, or 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte add, sub, mul, div, mod }; Eval::Eval(Expand& exp) : exp(exp), ctx(exp.ctx) { } Eval::~Eval() { } Context& Eval::context() { return ctx; } Env* Eval::environment() { return exp.environment(); } Selector_List* Eval::selector() { return exp.selector(); } Backtrace* Eval::backtrace() { return exp.backtrace(); } Expression* Eval::operator()(Block* b) { Expression* val = 0; for (size_t i = 0, L = b->length(); i < L; ++i) { val = (*b)[i]->perform(this); if (val) return val; } return val; } Expression* Eval::operator()(Assignment* a) { Env* env = exp.environment(); std::string var(a->variable()); if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { Expression* e = dynamic_cast(env->get_global(var)); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(this)); } } else { env->set_global(var, a->value()->perform(this)); } } else { env->set_global(var, a->value()->perform(this)); } } else if (a->is_default()) { if (env->has_lexical(var)) { auto cur = env; while (cur && cur->is_lexical()) { if (cur->has_local(var)) { if (AST_Node* node = cur->get_local(var)) { Expression* e = dynamic_cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { cur->set_local(var, a->value()->perform(this)); } } else { throw std::runtime_error("Env not in sync"); } return 0; } cur = cur->parent(); } throw std::runtime_error("Env not in sync"); } else if (env->has_global(var)) { if (AST_Node* node = env->get_global(var)) { Expression* e = dynamic_cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(this)); } } } else if (env->is_lexical()) { env->set_local(var, a->value()->perform(this)); } else { env->set_local(var, a->value()->perform(this)); } } else { env->set_lexical(var, a->value()->perform(this)); } return 0; } Expression* Eval::operator()(If* i) { Expression* rv = 0; Env env(exp.environment()); exp.env_stack.push_back(&env); if (*i->predicate()->perform(this)) { rv = i->block()->perform(this); } else { Block* alt = i->alternative(); if (alt) rv = alt->perform(this); } exp.env_stack.pop_back(); return rv; } // For does not create a new env scope // But iteration vars are reset afterwards Expression* Eval::operator()(For* f) { std::string variable(f->variable()); Expression* low = f->lower_bound()->perform(this); if (low->concrete_type() != Expression::NUMBER) { throw Exception::TypeMismatch(*low, "integer"); } Expression* high = f->upper_bound()->perform(this); if (high->concrete_type() != Expression::NUMBER) { throw Exception::TypeMismatch(*high, "integer"); } Number* sass_start = static_cast(low); Number* sass_end = static_cast(high); // check if units are valid for sequence if (sass_start->unit() != sass_end->unit()) { std::stringstream msg; msg << "Incompatible units: '" << sass_start->unit() << "' and '" << sass_end->unit() << "'."; error(msg.str(), low->pstate(), backtrace()); } double start = sass_start->value(); double end = sass_end->value(); // only create iterator once in this environment Env env(environment(), true); exp.env_stack.push_back(&env); Number* it = SASS_MEMORY_NEW(env.mem, Number, low->pstate(), start, sass_end->unit()); env.set_local(variable, it); Block* body = f->block(); Expression* val = 0; if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; i < end; ++i) { it->value(i); env.set_local(variable, it); val = body->perform(this); if (val) break; } } else { if (f->is_inclusive()) --end; for (double i = start; i > end; --i) { it->value(i); env.set_local(variable, it); val = body->perform(this); if (val) break; } } exp.env_stack.pop_back(); return val; } // Eval does not create a new env scope // But iteration vars are reset afterwards Expression* Eval::operator()(Each* e) { std::vector variables(e->variables()); Expression* expr = e->list()->perform(this); Env env(environment(), true); exp.env_stack.push_back(&env); Vectorized* list = 0; Map* map = 0; if (expr->concrete_type() == Expression::MAP) { map = static_cast(expr); } else if (Selector_List* ls = dynamic_cast(expr)) { Listize listize(ctx.mem); list = dynamic_cast(ls->perform(&listize)); } else if (expr->concrete_type() != Expression::LIST) { list = SASS_MEMORY_NEW(ctx.mem, List, expr->pstate(), 1, SASS_COMMA); *list << expr; } else { list = static_cast(expr); } Block* body = e->block(); Expression* val = 0; if (map) { for (auto key : map->keys()) { Expression* value = map->at(key); if (variables.size() == 1) { List* variable = SASS_MEMORY_NEW(ctx.mem, List, map->pstate(), 2, SASS_SPACE); *variable << key; *variable << value; env.set_local(variables[0], variable); } else { env.set_local(variables[0], key); env.set_local(variables[1], value); } val = body->perform(this); if (val) break; } } else { if (list->length() == 1 && dynamic_cast(list)) { list = dynamic_cast*>(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { Expression* e = (*list)[i]; // unwrap value if the expression is an argument if (Argument* arg = dynamic_cast(e)) e = arg->value(); // check if we got passed a list of args (investigate) if (List* scalars = dynamic_cast(e)) { if (variables.size() == 1) { Expression* var = scalars; env.set_local(variables[0], var); } else { // XXX: this is never hit via spec tests for (size_t j = 0, K = variables.size(); j < K; ++j) { Expression* res = j >= scalars->length() ? SASS_MEMORY_NEW(ctx.mem, Null, expr->pstate()) : (*scalars)[j]; env.set_local(variables[j], res); } } } else { if (variables.size() > 0) { env.set_local(variables[0], e); for (size_t j = 1, K = variables.size(); j < K; ++j) { // XXX: this is never hit via spec tests Expression* res = SASS_MEMORY_NEW(ctx.mem, Null, expr->pstate()); env.set_local(variables[j], res); } } } val = body->perform(this); if (val) break; } } exp.env_stack.pop_back(); return val; } Expression* Eval::operator()(While* w) { Expression* pred = w->predicate(); Block* body = w->block(); Env env(environment(), true); exp.env_stack.push_back(&env); while (*pred->perform(this)) { Expression* val = body->perform(this); if (val) { exp.env_stack.pop_back(); return val; } } exp.env_stack.pop_back(); return 0; } Expression* Eval::operator()(Return* r) { return r->value()->perform(this); } Expression* Eval::operator()(Warning* w) { Sass_Output_Style outstyle = ctx.c_options.output_style; ctx.c_options.output_style = NESTED; Expression* message = w->message()->perform(this); Env* env = exp.environment(); // try to use generic function if (env->has("@warn[f]")) { Definition* def = static_cast((*env)["@warn[f]"]); // Block* body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; sass_delete_value(c_args); sass_delete_value(c_val); return 0; } std::string result(unquote(message->to_sass())); Backtrace top(backtrace(), w->pstate(), ""); std::cerr << "WARNING: " << result; std::cerr << top.to_string(); std::cerr << std::endl << std::endl; ctx.c_options.output_style = outstyle; return 0; } Expression* Eval::operator()(Error* e) { Sass_Output_Style outstyle = ctx.c_options.output_style; ctx.c_options.output_style = NESTED; Expression* message = e->message()->perform(this); Env* env = exp.environment(); // try to use generic function if (env->has("@error[f]")) { Definition* def = static_cast((*env)["@error[f]"]); // Block* body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; sass_delete_value(c_args); sass_delete_value(c_val); return 0; } std::string result(unquote(message->to_sass())); ctx.c_options.output_style = outstyle; error(result, e->pstate()); return 0; } Expression* Eval::operator()(Debug* d) { Sass_Output_Style outstyle = ctx.c_options.output_style; ctx.c_options.output_style = NESTED; Expression* message = d->value()->perform(this); Env* env = exp.environment(); // try to use generic function if (env->has("@debug[f]")) { Definition* def = static_cast((*env)["@debug[f]"]); // Block* body = def->block(); // Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Sass_Function_Fn c_func = sass_function_get_function(c_function); To_C to_c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA); sass_list_set_value(c_args, 0, message->perform(&to_c)); union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); ctx.c_options.output_style = outstyle; sass_delete_value(c_args); sass_delete_value(c_val); return 0; } std::string cwd(ctx.cwd()); std::string result(unquote(message->to_sass())); std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); ctx.c_options.output_style = outstyle; std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; std::cerr << std::endl; return 0; } Expression* Eval::operator()(List* l) { // special case for unevaluated map if (l->separator() == SASS_HASH) { Map* lm = SASS_MEMORY_NEW(ctx.mem, Map, l->pstate(), l->length() / 2); for (size_t i = 0, L = l->length(); i < L; i += 2) { Expression* key = (*l)[i+0]->perform(this); Expression* val = (*l)[i+1]->perform(this); // make sure the color key never displays its real name key->is_delayed(true); *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { throw Exception::DuplicateKeyError(*lm, *l); } lm->is_interpolant(l->is_interpolant()); return lm->perform(this); } // check if we should expand it if (l->is_expanded()) return l; // regular case for unevaluated lists List* ll = SASS_MEMORY_NEW(ctx.mem, List, l->pstate(), l->length(), l->separator(), l->is_arglist()); for (size_t i = 0, L = l->length(); i < L; ++i) { *ll << (*l)[i]->perform(this); } ll->is_interpolant(l->is_interpolant()); ll->is_expanded(true); return ll; } Expression* Eval::operator()(Map* m) { if (m->is_expanded()) return m; // make sure we're not starting with duplicate keys. // the duplicate key state will have been set in the parser phase. if (m->has_duplicate_key()) { throw Exception::DuplicateKeyError(*m, *m); } Map* mm = SASS_MEMORY_NEW(ctx.mem, Map, m->pstate(), m->length()); for (auto key : m->keys()) { Expression* ex_key = key->perform(this); Expression* ex_val = m->at(key)->perform(this); *mm << std::make_pair(ex_key, ex_val); } // check the evaluated keys aren't duplicates. if (mm->has_duplicate_key()) { throw Exception::DuplicateKeyError(*mm, *m); } mm->is_expanded(true); return mm; } Expression* Eval::operator()(Binary_Expression* b) { String_Schema* ret_schema = 0; enum Sass_OP op_type = b->type(); // don't eval delayed expressions (the '/' when used as a separator) if (op_type == Sass_OP::DIV && b->is_delayed()) { b->right(b->right()->perform(this)); b->left(b->left()->perform(this)); return b; } // only the last item will be used to eval the binary expression if (String_Schema* s_1 = dynamic_cast(b->left())) { if (!s_1->is_right_interpolant()) { ret_schema = SASS_MEMORY_NEW(ctx.mem, String_Schema, s_1->pstate()); Binary_Expression* bin_ex = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, b->pstate(), b->op(), s_1->last(), b->right()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // bin_ex->is_interpolant(b->left()->is_interpolant()); for (size_t i = 0; i < s_1->length() - 1; ++i) { *ret_schema << s_1->at(i)->perform(this); } *ret_schema << bin_ex->perform(this); return ret_schema->perform(this); } } if (String_Schema* s_r = dynamic_cast(b->right())) { if (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV) { ret_schema = SASS_MEMORY_NEW(ctx.mem, String_Schema, s_r->pstate()); Binary_Expression* bin_ex = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, b->pstate(), b->op(), b->left(), s_r->first()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // if (op_type == Sass_OP::SUB && b->is_right_interpolant()) bin_ex->is_interpolant(true); *ret_schema << bin_ex->perform(this); for (size_t i = 1; i < s_r->length(); ++i) { *ret_schema << s_r->at(i)->perform(this); } return ret_schema->perform(this); } } // don't eval delayed expressions (the '/' when used as a separator) if (op_type == Sass_OP::DIV && b->is_delayed()) { b->right(b->right()->perform(this)); b->left(b->left()->perform(this)); return b; } // b->is_delayed(false); Expression* lhs = b->left(); Expression* rhs = b->right(); // bool delay_lhs = false; // bool delay_rhs = false; if (String_Schema* schema = dynamic_cast(lhs)) { if (schema->is_right_interpolant()) { b->is_delayed(true); // delay_lhs = true; } } if (String_Schema* schema = dynamic_cast(rhs)) { if (schema->is_left_interpolant()) { b->is_delayed(true); // delay_rhs = true; } } // maybe fully evaluate structure if (op_type == Sass_OP::EQ || op_type == Sass_OP::NEQ || op_type == Sass_OP::GT || op_type == Sass_OP::GTE || op_type == Sass_OP::LT || op_type == Sass_OP::LTE) { if (String_Schema* schema = dynamic_cast(lhs)) { if (schema->has_interpolants()) { b->is_delayed(true); } } if (String_Schema* schema = dynamic_cast(rhs)) { if (schema->has_interpolants()) { b->is_delayed(true); } } lhs->is_expanded(false); lhs->set_delayed(false); lhs = lhs->perform(this); lhs->is_expanded(false); lhs->set_delayed(false); lhs = lhs->perform(this); rhs->is_expanded(false); rhs->set_delayed(false); rhs = rhs->perform(this); rhs->is_expanded(false); rhs->set_delayed(false); rhs = rhs->perform(this); } else { // rhs->set_delayed(false); // rhs = rhs->perform(this); } // if one of the operands is a '/' then make sure it's evaluated lhs = lhs->perform(this); lhs->is_delayed(false); while (typeid(*lhs) == typeid(Binary_Expression)) { Binary_Expression* lhs_ex = static_cast(lhs); if (lhs_ex->type() == Sass_OP::DIV && lhs_ex->is_delayed()) break; lhs = Eval::operator()(lhs_ex); } switch (op_type) { case Sass_OP::AND: return *lhs ? b->right()->perform(this) : lhs; break; case Sass_OP::OR: return *lhs ? lhs : b->right()->perform(this); break; default: break; } // not a logical connective, so go ahead and eval the rhs rhs = rhs->perform(this); // upgrade string to number if possible (issue #948) if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL) { if (String_Constant* str = dynamic_cast(rhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::number >(start) != 0) { rhs = SASS_MEMORY_NEW(ctx.mem, Textual, rhs->pstate(), Textual::DIMENSION, str->value()); rhs->is_delayed(false); rhs = rhs->perform(this); } } } Expression::Concrete_Type l_type = lhs->concrete_type(); Expression::Concrete_Type r_type = rhs->concrete_type(); // Is one of the operands an interpolant? String_Schema* s1 = dynamic_cast(b->left()); String_Schema* s2 = dynamic_cast(b->right()); Binary_Expression* b1 = dynamic_cast(b->left()); Binary_Expression* b2 = dynamic_cast(b->right()); bool schema_op = false; bool force_delay = (s2 && s2->is_left_interpolant()) || (s1 && s1->is_right_interpolant()) || (b1 && b1->is_right_interpolant()) || (b2 && b2->is_left_interpolant()); if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) { // If possible upgrade LHS to a number if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || op_type == Sass_OP::EQ) { if (String_Constant* str = dynamic_cast(lhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { lhs = SASS_MEMORY_NEW(ctx.mem, Textual, lhs->pstate(), Textual::DIMENSION, str->value()); lhs->is_delayed(false); lhs = lhs->perform(this); } } if (String_Constant* str = dynamic_cast(rhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::number >(start) != 0) { rhs = SASS_MEMORY_NEW(ctx.mem, Textual, rhs->pstate(), Textual::DIMENSION, str->value()); rhs->is_delayed(false); rhs = rhs->perform(this); } } } To_Value to_value(ctx, ctx.mem); Value* v_l = dynamic_cast(lhs->perform(&to_value)); Value* v_r = dynamic_cast(rhs->perform(&to_value)); l_type = lhs->concrete_type(); r_type = rhs->concrete_type(); if (s2 && s2->has_interpolants() && s2->length()) { Textual* front = dynamic_cast(s2->elements().front()); if (front && !front->is_interpolant()) { // XXX: this is never hit via spec tests schema_op = true; rhs = front->perform(this); } } if (force_delay) { std::string str(""); str += v_l->to_string(ctx.c_options); if (b->op().ws_before) str += " "; str += b->separator(); if (b->op().ws_after) str += " "; str += v_r->to_string(ctx.c_options); String_Constant* val = SASS_MEMORY_NEW(ctx.mem, String_Constant, lhs->pstate(), str); val->is_interpolant(b->left()->has_interpolant()); return val; } } // see if it's a relational expression try { switch(op_type) { case Sass_OP::EQ: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), eq(lhs, rhs)); case Sass_OP::NEQ: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !eq(lhs, rhs)); case Sass_OP::GT: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !lt(lhs, rhs, "gt") && !eq(lhs, rhs)); case Sass_OP::GTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !lt(lhs, rhs, "gte")); case Sass_OP::LT: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs, "lt")); case Sass_OP::LTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs, "lte") || eq(lhs, rhs)); default: break; } } catch (Exception::OperationError& err) { // throw Exception::Base(b->pstate(), err.what()); throw Exception::SassValueError(b->pstate(), err); } l_type = lhs->concrete_type(); r_type = rhs->concrete_type(); // ToDo: throw error in op functions // ToDo: then catch and re-throw them Expression* rv = 0; try { ParserState pstate(b->pstate()); if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { const Number* l_n = dynamic_cast(lhs); const Number* r_n = dynamic_cast(rhs); rv = op_numbers(ctx.mem, op_type, *l_n, *r_n, ctx.c_options, &pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { const Number* l_n = dynamic_cast(lhs); const Color* r_c = dynamic_cast(rhs); rv = op_number_color(ctx.mem, op_type, *l_n, *r_c, ctx.c_options, &pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { const Color* l_c = dynamic_cast(lhs); const Number* r_n = dynamic_cast(rhs); rv = op_color_number(ctx.mem, op_type, *l_c, *r_n, ctx.c_options, &pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { const Color* l_c = dynamic_cast(lhs); const Color* r_c = dynamic_cast(rhs); rv = op_colors(ctx.mem, op_type, *l_c, *r_c, ctx.c_options, &pstate); } else { To_Value to_value(ctx, ctx.mem); Value* v_l = dynamic_cast(lhs->perform(&to_value)); Value* v_r = dynamic_cast(rhs->perform(&to_value)); bool interpolant = b->is_right_interpolant() || b->is_left_interpolant() || b->is_interpolant(); if (op_type == Sass_OP::SUB) interpolant = false; // if (op_type == Sass_OP::DIV) interpolant = true; Value* ex = op_strings(ctx.mem, b->op(), *v_l, *v_r, ctx.c_options, &pstate, !interpolant); // pass true to compress if (String_Constant* str = dynamic_cast(ex)) { if (str->concrete_type() == Expression::STRING) { String_Constant* lstr = dynamic_cast(lhs); String_Constant* rstr = dynamic_cast(rhs); if (op_type != Sass_OP::SUB) { if (String_Constant* org = lstr ? lstr : rstr) { str->quote_mark(org->quote_mark()); } } } } ex->is_interpolant(b->is_interpolant()); rv = ex; } } catch (Exception::OperationError& err) { // throw Exception::Base(b->pstate(), err.what()); throw Exception::SassValueError(b->pstate(), err); } if (rv) { if (schema_op) { // XXX: this is never hit via spec tests (*s2)[0] = rv; rv = s2->perform(this); } } return rv; } Expression* Eval::operator()(Unary_Expression* u) { Expression* operand = u->operand()->perform(this); if (u->type() == Unary_Expression::NOT) { Boolean* result = SASS_MEMORY_NEW(ctx.mem, Boolean, u->pstate(), (bool)*operand); result->value(!result->value()); return result; } else if (operand->concrete_type() == Expression::NUMBER) { Number* result = SASS_MEMORY_NEW(ctx.mem, Number, *static_cast(operand)); result->value(u->type() == Unary_Expression::MINUS ? -result->value() : result->value()); return result; } else { // Special cases: +/- variables which evaluate to null ouput just +/-, // but +/- null itself outputs the string if (operand->concrete_type() == Expression::NULL_VAL && dynamic_cast(u->operand())) { u->operand(SASS_MEMORY_NEW(ctx.mem, String_Quoted, u->pstate(), "")); } else u->operand(operand); String_Constant* result = SASS_MEMORY_NEW(ctx.mem, String_Quoted, u->pstate(), u->inspect()); return result; } // unreachable return u; } Expression* Eval::operator()(Function_Call* c) { if (backtrace()->parent != NULL && backtrace()->depth() > Constants::MaxCallStack) { // XXX: this is never hit via spec tests std::ostringstream stm; stm << "Stack depth exceeded max of " << Constants::MaxCallStack; error(stm.str(), c->pstate(), backtrace()); } std::string name(Util::normalize_underscores(c->name())); std::string full_name(name + "[f]"); Arguments* args = c->arguments(); if (full_name != "if[f]") { args = static_cast(args->perform(this)); } Env* env = environment(); if (!env->has(full_name)) { if (!env->has("*[f]")) { // just pass it through as a literal Function_Call* lit = SASS_MEMORY_NEW(ctx.mem, Function_Call, c->pstate(), c->name(), args); if (args->has_named_arguments()) { error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); } String_Quoted* str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, c->pstate(), lit->to_string(ctx.c_options)); str->is_interpolant(c->is_interpolant()); return str; } else { // call generic function full_name = "*[f]"; } } Definition* def = static_cast((*env)[full_name]); if (def->is_overload_stub()) { std::stringstream ss; ss << full_name << args->length(); full_name = ss.str(); std::string resolved_name(full_name); if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate()); def = static_cast((*env)[resolved_name]); } Expression* result = c; Block* body = def->block(); Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); Parameters* params = def->parameters(); Env fn_env(def->environment()); exp.env_stack.push_back(&fn_env); if (func || body) { bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); exp.backtrace_stack.push_back(&here); // eval the body if user-defined or special, invoke underlying CPP function if native if (body && !Prelexer::re_special_fun(c->name().c_str())) { result = body->perform(this); } else if (func) { result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace()); } if (!result) error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); exp.backtrace_stack.pop_back(); } // else if it's a user-defined c function // convert call into C-API compatible form else if (c_function) { Sass_Function_Fn c_func = sass_function_get_function(c_function); if (full_name == "*[f]") { String_Quoted *str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, c->pstate(), c->name()); Arguments* new_args = SASS_MEMORY_NEW(ctx.mem, Arguments, c->pstate()); *new_args << SASS_MEMORY_NEW(ctx.mem, Argument, c->pstate(), str); *new_args += args; args = new_args; } // populates env with default values for params std::string ff(c->name()); bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); exp.backtrace_stack.push_back(&here); To_C to_c; union Sass_Value* c_args = sass_make_list(params[0].length(), SASS_COMMA); for(size_t i = 0; i < params[0].length(); i++) { std::string key = params[0][i]->name(); AST_Node* node = fn_env.get_local(key); Expression* arg = static_cast(node); sass_list_set_value(c_args, i, arg->perform(&to_c)); } union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); if (sass_value_get_tag(c_val) == SASS_ERROR) { error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), backtrace()); } else if (sass_value_get_tag(c_val) == SASS_WARNING) { error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), backtrace()); } result = cval_to_astnode(ctx.mem, c_val, ctx, backtrace(), c->pstate()); exp.backtrace_stack.pop_back(); sass_delete_value(c_args); if (c_val != c_args) sass_delete_value(c_val); } // link back to function definition // only do this for custom functions if (result->pstate().file == std::string::npos) result->pstate(c->pstate()); result->is_delayed(result->concrete_type() == Expression::STRING); if (!result->is_delayed()) result = result->perform(this); result->is_interpolant(c->is_interpolant()); exp.env_stack.pop_back(); return result; } Expression* Eval::operator()(Function_Call_Schema* s) { Expression* evaluated_name = s->name()->perform(this); Expression* evaluated_args = s->arguments()->perform(this); String_Schema* ss = SASS_MEMORY_NEW(ctx.mem, String_Schema, s->pstate(), 2); (*ss) << evaluated_name << evaluated_args; return ss->perform(this); } Expression* Eval::operator()(Variable* v) { std::string name(v->name()); Expression* value = 0; Env* env = environment(); if (env->has(name)) value = static_cast((*env)[name]); else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); if (typeid(*value) == typeid(Argument)) value = static_cast(value)->value(); // behave according to as ruby sass (add leading zero) if (value->concrete_type() == Expression::NUMBER) { value = SASS_MEMORY_NEW(ctx.mem, Number, *static_cast(value)); static_cast(value)->zero(true); } else if (value->concrete_type() == Expression::STRING) { if (auto str = dynamic_cast(value)) { value = SASS_MEMORY_NEW(ctx.mem, String_Quoted, *str); } else if (auto str = dynamic_cast(value)) { value = SASS_MEMORY_NEW(ctx.mem, String_Quoted, str->pstate(), str->value()); } } else if (value->concrete_type() == Expression::LIST) { value = SASS_MEMORY_NEW(ctx.mem, List, *static_cast(value)); } else if (value->concrete_type() == Expression::MAP) { value = SASS_MEMORY_NEW(ctx.mem, Map, *static_cast(value)); } else if (value->concrete_type() == Expression::BOOLEAN) { value = SASS_MEMORY_NEW(ctx.mem, Boolean, *static_cast(value)); } else if (value->concrete_type() == Expression::COLOR) { value = SASS_MEMORY_NEW(ctx.mem, Color, *static_cast(value)); } else if (value->concrete_type() == Expression::NULL_VAL) { value = SASS_MEMORY_NEW(ctx.mem, Null, value->pstate()); } else if (value->concrete_type() == Expression::SELECTOR) { value = value->perform(this); // ->perform(&listize); } value->is_interpolant(v->is_interpolant()); value->is_expanded(false); return value->perform(this); } Expression* Eval::operator()(Textual* t) { using Prelexer::number; Expression* result = 0; size_t L = t->value().length(); bool zero = !( (L > 0 && t->value().substr(0, 1) == ".") || (L > 1 && t->value().substr(0, 2) == "0.") || (L > 1 && t->value().substr(0, 2) == "-.") || (L > 2 && t->value().substr(0, 3) == "-0.") ); const std::string& text = t->value(); size_t num_pos = text.find_first_not_of(" \n\r\t"); if (num_pos == std::string::npos) num_pos = text.length(); size_t unit_pos = text.find_first_not_of("-+0123456789.", num_pos); if (unit_pos == std::string::npos) unit_pos = text.length(); const std::string& num = text.substr(num_pos, unit_pos - num_pos); switch (t->type()) { case Textual::NUMBER: result = SASS_MEMORY_NEW(ctx.mem, Number, t->pstate(), sass_atof(num.c_str()), "", zero); break; case Textual::PERCENTAGE: result = SASS_MEMORY_NEW(ctx.mem, Number, t->pstate(), sass_atof(num.c_str()), "%", true); break; case Textual::DIMENSION: result = SASS_MEMORY_NEW(ctx.mem, Number, t->pstate(), sass_atof(num.c_str()), Token(number(text.c_str())), zero); break; case Textual::HEX: { if (t->value().substr(0, 1) != "#") { result = SASS_MEMORY_NEW(ctx.mem, String_Quoted, t->pstate(), t->value()); break; } std::string hext(t->value().substr(1)); // chop off the '#' if (hext.length() == 6) { std::string r(hext.substr(0,2)); std::string g(hext.substr(2,2)); std::string b(hext.substr(4,2)); result = SASS_MEMORY_NEW(ctx.mem, Color, t->pstate(), static_cast(strtol(r.c_str(), NULL, 16)), static_cast(strtol(g.c_str(), NULL, 16)), static_cast(strtol(b.c_str(), NULL, 16)), 1, // alpha channel t->value()); } else { result = SASS_MEMORY_NEW(ctx.mem, Color, t->pstate(), static_cast(strtol(std::string(2,hext[0]).c_str(), NULL, 16)), static_cast(strtol(std::string(2,hext[1]).c_str(), NULL, 16)), static_cast(strtol(std::string(2,hext[2]).c_str(), NULL, 16)), 1, // alpha channel t->value()); } } break; } result->is_interpolant(t->is_interpolant()); return result; } Expression* Eval::operator()(Number* n) { return n; } Expression* Eval::operator()(Boolean* b) { return b; } void Eval::interpolation(Context& ctx, std::string& res, Expression* ex, bool into_quotes, bool was_itpl) { bool needs_closing_brace = false; //debug_ast(ex); if (Arguments* args = dynamic_cast(ex)) { List* ll = SASS_MEMORY_NEW(ctx.mem, List, args->pstate(), 0, SASS_COMMA); for(auto arg : *args) { *ll << arg->value(); } ll->is_interpolant(args->is_interpolant()); needs_closing_brace = true; res += "("; ex = ll; } if (Number* nr = dynamic_cast(ex)) { if (!nr->is_valid_css_unit()) { throw Exception::InvalidValue(*nr); } } if (Argument* arg = dynamic_cast(ex)) { ex = arg->value(); } if (String_Quoted* sq = dynamic_cast(ex)) { if (was_itpl) { bool was_interpolant = ex->is_interpolant(); ex = SASS_MEMORY_NEW(ctx.mem, String_Constant, sq->pstate(), sq->value()); ex->is_interpolant(was_interpolant); } } if (dynamic_cast(ex)) { return; } // parent selector needs another go if (dynamic_cast(ex)) { // XXX: this is never hit via spec tests ex = ex->perform(this); } if (List* l = dynamic_cast(ex)) { List* ll = SASS_MEMORY_NEW(ctx.mem, List, l->pstate(), 0, l->separator()); // this fixes an issue with bourbon sample, not really sure why if (l->size() && dynamic_cast((*l)[0])) { res += " "; } for(auto item : *l) { item->is_interpolant(l->is_interpolant()); std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); if (rl != "") *ll << SASS_MEMORY_NEW(ctx.mem, String_Quoted, item->pstate(), rl); } res += (ll->to_string(ctx.c_options)); ll->is_interpolant(l->is_interpolant()); } // Value // Textual // Function_Call // Selector_List // String_Quoted // String_Constant // Parent_Selector // Binary_Expression else { // ex = ex->perform(this); if (into_quotes && ex->is_interpolant()) { res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); } else { res += ex ? ex->to_string(ctx.c_options) : ""; } } if (needs_closing_brace) res += ")"; } Expression* Eval::operator()(String_Schema* s) { size_t L = s->length(); bool into_quotes = false; if (L > 1) { if (!dynamic_cast((*s)[0]) && !dynamic_cast((*s)[L - 1])) { if (String_Constant* l = dynamic_cast((*s)[0])) { if (String_Constant* r = dynamic_cast((*s)[L - 1])) { if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; } } } } std::string res(""); for (size_t i = 0; i < L; ++i) { (*s)[i]->perform(this); Expression* ex = (*s)[i]->is_delayed() ? (*s)[i] : (*s)[i]->perform(this); interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); } if (!s->is_interpolant()) { if (res == "") return SASS_MEMORY_NEW(ctx.mem, Null, s->pstate()); return SASS_MEMORY_NEW(ctx.mem, String_Constant, s->pstate(), res); } String_Quoted* str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, s->pstate(), res); // if (s->is_interpolant()) str->quote_mark(0); // String_Constant* str = SASS_MEMORY_NEW(ctx.mem, String_Constant, s->pstate(), res); if (str->quote_mark()) str->quote_mark('*'); else if (!is_in_comment) str->value(string_to_output(str->value())); str->is_interpolant(s->is_interpolant()); return str; } Expression* Eval::operator()(String_Constant* s) { if (!s->is_delayed() && name_to_color(s->value())) { Color* c = SASS_MEMORY_NEW(ctx.mem, Color, *name_to_color(s->value())); c->pstate(s->pstate()); c->disp(s->value()); return c; } return s; } Expression* Eval::operator()(String_Quoted* s) { String_Quoted* str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, s->pstate(), ""); str->value(s->value()); str->quote_mark(s->quote_mark()); str->is_interpolant(s->is_interpolant()); return str; } Expression* Eval::operator()(Supports_Operator* c) { Expression* left = c->left()->perform(this); Expression* right = c->right()->perform(this); Supports_Operator* cc = SASS_MEMORY_NEW(ctx.mem, Supports_Operator, c->pstate(), static_cast(left), static_cast(right), c->operand()); return cc; } Expression* Eval::operator()(Supports_Negation* c) { Expression* condition = c->condition()->perform(this); Supports_Negation* cc = SASS_MEMORY_NEW(ctx.mem, Supports_Negation, c->pstate(), static_cast(condition)); return cc; } Expression* Eval::operator()(Supports_Declaration* c) { Expression* feature = c->feature()->perform(this); Expression* value = c->value()->perform(this); Supports_Declaration* cc = SASS_MEMORY_NEW(ctx.mem, Supports_Declaration, c->pstate(), feature, value); return cc; } Expression* Eval::operator()(Supports_Interpolation* c) { Expression* value = c->value()->perform(this); Supports_Interpolation* cc = SASS_MEMORY_NEW(ctx.mem, Supports_Interpolation, c->pstate(), value); return cc; } Expression* Eval::operator()(At_Root_Expression* e) { Expression* feature = e->feature(); feature = (feature ? feature->perform(this) : 0); Expression* value = e->value(); value = (value ? value->perform(this) : 0); Expression* ee = SASS_MEMORY_NEW(ctx.mem, At_Root_Expression, e->pstate(), static_cast(feature), value, e->is_interpolated()); return ee; } Expression* Eval::operator()(Media_Query* q) { String* t = q->media_type(); t = static_cast(t ? t->perform(this) : 0); Media_Query* qq = SASS_MEMORY_NEW(ctx.mem, Media_Query, q->pstate(), t, q->length(), q->is_negated(), q->is_restricted()); for (size_t i = 0, L = q->length(); i < L; ++i) { *qq << static_cast((*q)[i]->perform(this)); } return qq; } Expression* Eval::operator()(Media_Query_Expression* e) { Expression* feature = e->feature(); feature = (feature ? feature->perform(this) : 0); if (feature && dynamic_cast(feature)) { feature = SASS_MEMORY_NEW(ctx.mem, String_Quoted, feature->pstate(), dynamic_cast(feature)->value()); } Expression* value = e->value(); value = (value ? value->perform(this) : 0); if (value && dynamic_cast(value)) { // XXX: this is never hit via spec tests value = SASS_MEMORY_NEW(ctx.mem, String_Quoted, value->pstate(), dynamic_cast(value)->value()); } return SASS_MEMORY_NEW(ctx.mem, Media_Query_Expression, e->pstate(), feature, value, e->is_interpolated()); } Expression* Eval::operator()(Null* n) { return n; } Expression* Eval::operator()(Argument* a) { Expression* val = a->value(); val->is_delayed(false); val = val->perform(this); val->is_delayed(false); bool is_rest_argument = a->is_rest_argument(); bool is_keyword_argument = a->is_keyword_argument(); if (a->is_rest_argument()) { if (val->concrete_type() == Expression::MAP) { is_rest_argument = false; is_keyword_argument = true; } else if(val->concrete_type() != Expression::LIST) { List* wrapper = SASS_MEMORY_NEW(ctx.mem, List, val->pstate(), 0, SASS_COMMA, true); *wrapper << val; val = wrapper; } } return SASS_MEMORY_NEW(ctx.mem, Argument, a->pstate(), val, a->name(), is_rest_argument, is_keyword_argument); } Expression* Eval::operator()(Arguments* a) { Arguments* aa = SASS_MEMORY_NEW(ctx.mem, Arguments, a->pstate()); for (size_t i = 0, L = a->length(); i < L; ++i) { Argument* arg = static_cast((*a)[i]->perform(this)); if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { *aa << arg; } } if (a->has_rest_argument()) { Expression* splat = static_cast( a->get_rest_argument()->perform(this) )->value()->perform(this); Sass_Separator separator = SASS_COMMA; List* ls = dynamic_cast(splat); Map* ms = dynamic_cast(splat); List* arglist = SASS_MEMORY_NEW(ctx.mem, List, splat->pstate(), 0, ls ? ls->separator() : separator, true); if (ls && ls->is_arglist()) { for (auto as : *ls) *arglist << as; } else if (ms) { *aa << SASS_MEMORY_NEW(ctx.mem, Argument, splat->pstate(), ms, "", false, true); } else if (ls) { for (auto as : *ls) *arglist << as; } else { *arglist << splat; } if (arglist->length()) { *aa << SASS_MEMORY_NEW(ctx.mem, Argument, splat->pstate(), arglist, "", true); } } if (a->has_keyword_argument()) { Expression* kwarg = static_cast( a->get_keyword_argument()->perform(this) )->value()->perform(this); *aa << SASS_MEMORY_NEW(ctx.mem, Argument, kwarg->pstate(), kwarg, "", false, true); } return aa; } Expression* Eval::operator()(Comment* c) { return 0; } inline Expression* Eval::fallback_impl(AST_Node* n) { return static_cast(n); } // All the binary helpers. bool Eval::eq(Expression* lhs, Expression* rhs) { // use compare operator from ast node return lhs && rhs && *lhs == *rhs; } bool Eval::lt(Expression* lhs, Expression* rhs, std::string op) { Number* l = dynamic_cast(lhs); Number* r = dynamic_cast(rhs); // use compare operator from ast node if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); // use compare operator from ast node return *l < *r; } Value* Eval::op_numbers(Memory_Manager& mem, enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) { double lv = l.value(); double rv = r.value(); if (op == Sass_OP::DIV && rv == 0) { // XXX: this is never hit via spec tests return SASS_MEMORY_NEW(mem, String_Quoted, pstate ? *pstate : l.pstate(), lv ? "Infinity" : "NaN"); } if (op == Sass_OP::MOD && !rv) { // XXX: this is never hit via spec tests throw Exception::ZeroDivisionError(l, r); } Number tmp(r); bool strict = op != Sass_OP::MUL && op != Sass_OP::DIV; tmp.normalize(l.find_convertible_unit(), strict); std::string l_unit(l.unit()); std::string r_unit(tmp.unit()); Number* v = SASS_MEMORY_NEW(mem, Number, l); v->pstate(pstate ? *pstate : l.pstate()); if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { v->numerator_units() = r.numerator_units(); v->denominator_units() = r.denominator_units(); } if (op == Sass_OP::MUL) { v->value(ops[op](lv, rv)); for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { v->numerator_units().push_back(r.numerator_units()[i]); } for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { v->denominator_units().push_back(r.denominator_units()[i]); } } else if (op == Sass_OP::DIV) { v->value(ops[op](lv, rv)); for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) { v->denominator_units().push_back(r.numerator_units()[i]); } for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) { v->numerator_units().push_back(r.denominator_units()[i]); } } else { Number rh(r); v->value(ops[op](lv, rh.value() * r.convert_factor(l))); // v->normalize(); return v; v->value(ops[op](lv, tmp.value())); } v->normalize(); return v; } Value* Eval::op_number_color(Memory_Manager& mem, enum Sass_OP op, const Number& l, const Color& rh, struct Sass_Inspect_Options opt, ParserState* pstate) { Color r(rh); double lv = l.value(); switch (op) { case Sass_OP::ADD: case Sass_OP::MUL: { return SASS_MEMORY_NEW(mem, Color, pstate ? *pstate : l.pstate(), ops[op](lv, r.r()), ops[op](lv, r.g()), ops[op](lv, r.b()), r.a()); } break; case Sass_OP::SUB: case Sass_OP::DIV: { std::string sep(op == Sass_OP::SUB ? "-" : "/"); std::string color(r.to_string(opt)); return SASS_MEMORY_NEW(mem, String_Quoted, pstate ? *pstate : l.pstate(), l.to_string(opt) + sep + color); } break; case Sass_OP::MOD: { throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); } break; default: break; // caller should ensure that we don't get here } // unreachable return SASS_MEMORY_NEW(mem, Color, rh); } Value* Eval::op_color_number(Memory_Manager& mem, enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate) { double rv = r.value(); if (op == Sass_OP::DIV && !rv) { // comparison of Fixnum with Float failed? throw Exception::ZeroDivisionError(l, r); } return SASS_MEMORY_NEW(mem, Color, pstate ? *pstate : l.pstate(), ops[op](l.r(), rv), ops[op](l.g(), rv), ops[op](l.b(), rv), l.a()); } Value* Eval::op_colors(Memory_Manager& mem, enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate) { if (l.a() != r.a()) { throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); } if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) { // comparison of Fixnum with Float failed? throw Exception::ZeroDivisionError(l, r); } return SASS_MEMORY_NEW(mem, Color, pstate ? *pstate : l.pstate(), ops[op](l.r(), r.r()), ops[op](l.g(), r.g()), ops[op](l.b(), r.b()), l.a()); } Value* Eval::op_strings(Memory_Manager& mem, Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, ParserState* pstate, bool delayed) { Expression::Concrete_Type ltype = lhs.concrete_type(); Expression::Concrete_Type rtype = rhs.concrete_type(); enum Sass_OP op = operand.operand; String_Quoted* lqstr = dynamic_cast(&lhs); String_Quoted* rqstr = dynamic_cast(&rhs); std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); if (op == Sass_OP::MOD) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); if (op == Sass_OP::MUL) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); std::string sep; switch (op) { case Sass_OP::SUB: sep = "-"; break; case Sass_OP::DIV: sep = "/"; break; case Sass_OP::MUL: sep = "*"; break; case Sass_OP::MOD: sep = "%"; break; case Sass_OP::EQ: sep = "=="; break; case Sass_OP::NEQ: sep = "!="; break; case Sass_OP::LT: sep = "<"; break; case Sass_OP::GT: sep = ">"; break; case Sass_OP::LTE: sep = "<="; break; case Sass_OP::GTE: sep = ">="; break; default: break; } if ( (sep == "") /* && (sep != "/" || !rqstr || !rqstr->quote_mark()) */ ) { char quote_mark = 0; std::string unq(unquote(lstr + sep + rstr, "e_mark, true)); if (quote_mark && quote_mark != '*') { return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), quote_mark + unq + quote_mark); } return SASS_MEMORY_NEW(mem, String_Quoted, lhs.pstate(), lstr + sep + rstr); } if (sep != "" && !delayed) { if (operand.ws_before) sep = " " + sep; if (operand.ws_after) sep = sep + " "; } if (op == Sass_OP::SUB || op == Sass_OP::DIV) { if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), lstr + sep + rstr); } return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), (lstr) + sep + (rstr)); } Expression* cval_to_astnode(Memory_Manager& mem, union Sass_Value* v, Context& ctx, Backtrace* backtrace, ParserState pstate) { using std::strlen; using std::strcpy; Expression* e = 0; switch (sass_value_get_tag(v)) { case SASS_BOOLEAN: { e = SASS_MEMORY_NEW(mem, Boolean, pstate, !!sass_boolean_get_value(v)); } break; case SASS_NUMBER: { e = SASS_MEMORY_NEW(mem, Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); } break; case SASS_COLOR: { e = SASS_MEMORY_NEW(mem, Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); } break; case SASS_STRING: { if (sass_string_is_quoted(v)) e = SASS_MEMORY_NEW(mem, String_Quoted, pstate, sass_string_get_value(v)); else { e = SASS_MEMORY_NEW(mem, String_Constant, pstate, sass_string_get_value(v)); } } break; case SASS_LIST: { List* l = SASS_MEMORY_NEW(mem, List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { *l << cval_to_astnode(mem, sass_list_get_value(v, i), ctx, backtrace, pstate); } e = l; } break; case SASS_MAP: { Map* m = SASS_MEMORY_NEW(mem, Map, pstate); for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { *m << std::make_pair( cval_to_astnode(mem, sass_map_get_key(v, i), ctx, backtrace, pstate), cval_to_astnode(mem, sass_map_get_value(v, i), ctx, backtrace, pstate)); } e = m; } break; case SASS_NULL: { e = SASS_MEMORY_NEW(mem, Null, pstate); } break; case SASS_ERROR: { error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, backtrace); } break; case SASS_WARNING: { error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace); } break; } return e; } Selector_List* Eval::operator()(Selector_List* s) { std::vector rv; Selector_List* sl = SASS_MEMORY_NEW(ctx.mem, Selector_List, s->pstate()); sl->media_block(s->media_block()); for (size_t i = 0, iL = s->length(); i < iL; ++i) { rv.push_back(operator()((*s)[i])); } // we should actually permutate parent first // but here we have permutated the selector first size_t round = 0; while (round != std::string::npos) { bool abort = true; for (size_t i = 0, iL = rv.size(); i < iL; ++i) { if (rv[i]->length() > round) { *sl << (*rv[i])[round]; abort = false; } } if (abort) { round = std::string::npos; } else { ++ round; } } return sl; } Selector_List* Eval::operator()(Complex_Selector* s) { return s->parentize(selector(), ctx); } // XXX: this is never hit via spec tests Attribute_Selector* Eval::operator()(Attribute_Selector* s) { String* attr = s->value(); if (attr) { attr = static_cast(attr->perform(this)); } Attribute_Selector* ss = SASS_MEMORY_NEW(ctx.mem, Attribute_Selector, *s); ss->value(attr); return ss; } Selector_List* Eval::operator()(Selector_Schema* s) { // the parser will look for a brace to end the selector std::string result_str(s->contents()->perform(this)->to_string(ctx.c_options)); result_str = unquote(Util::rtrim(result_str)) + "{"; Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()); return operator()(p.parse_selector_list(exp.block_stack.back()->is_root())); } Expression* Eval::operator()(Parent_Selector* p) { Selector_List* pr = selector(); if (pr) { exp.selector_stack.pop_back(); pr = operator()(pr); exp.selector_stack.push_back(pr); return pr; } else { return SASS_MEMORY_NEW(ctx.mem, Null, p->pstate()); } } }