// sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #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 "operators.hpp" #include "environment.hpp" #include "position.hpp" #include "sass/values.h" #include "to_value.hpp" #include "ast2c.hpp" #include "c2ast.hpp" #include "context.hpp" #include "backtrace.hpp" #include "lexer.hpp" #include "prelexer.hpp" #include "parser.hpp" #include "expand.hpp" #include "color_maps.hpp" #include "sass_functions.hpp" #include "util_string.hpp" namespace Sass { Eval::Eval(Expand& exp) : exp(exp), ctx(exp.ctx), traces(exp.traces), force(false), is_in_comment(false), is_in_selector_schema(false) { bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); } Eval::~Eval() { } Env* Eval::environment() { return exp.environment(); } const std::string Eval::cwd() { return ctx.cwd(); } struct Sass_Inspect_Options& Eval::options() { return ctx.c_options; } struct Sass_Compiler* Eval::compiler() { return ctx.c_compiler; } EnvStack& Eval::env_stack() { return exp.env_stack; } std::vector& Eval::callee_stack() { return ctx.callee_stack; } Expression* Eval::operator()(Block* b) { Expression* val = 0; for (size_t i = 0, L = b->length(); i < L; ++i) { val = b->at(i)->perform(this); if (val) return val; } return val; } Expression* Eval::operator()(Assignment* a) { Env* env = environment(); std::string var(a->variable()); if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { Expression* e = 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_Obj node = cur->get_local(var)) { Expression* e = 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_Obj node = env->get_global(var)) { Expression* e = 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_Obj rv; Env env(environment()); env_stack().push_back(&env); Expression_Obj cond = i->predicate()->perform(this); if (!cond->is_false()) { rv = i->block()->perform(this); } else { Block_Obj alt = i->alternative(); if (alt) rv = alt->perform(this); } env_stack().pop_back(); return rv.detach(); } // 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_Obj low = f->lower_bound()->perform(this); if (low->concrete_type() != Expression::NUMBER) { traces.push_back(Backtrace(low->pstate())); throw Exception::TypeMismatch(traces, *low, "integer"); } Expression_Obj high = f->upper_bound()->perform(this); if (high->concrete_type() != Expression::NUMBER) { traces.push_back(Backtrace(high->pstate())); throw Exception::TypeMismatch(traces, *high, "integer"); } Number_Obj sass_start = Cast(low); Number_Obj sass_end = Cast(high); // check if units are valid for sequence if (sass_start->unit() != sass_end->unit()) { std::stringstream msg; msg << "Incompatible units: '" << sass_end->unit() << "' and '" << sass_start->unit() << "'."; error(msg.str(), low->pstate(), traces); } double start = sass_start->value(); double end = sass_end->value(); // only create iterator once in this environment Env env(environment(), true); env_stack().push_back(&env); Block_Obj body = f->block(); Expression* val = 0; if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; i < end; ++i) { Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); 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) { Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); env.set_local(variable, it); val = body->perform(this); if (val) break; } } 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_Obj expr = e->list()->perform(this); Env env(environment(), true); env_stack().push_back(&env); List_Obj list; Map* map = nullptr; if (expr->concrete_type() == Expression::MAP) { map = Cast(expr); } else if (SelectorList * ls = Cast(expr)) { Expression_Obj rv = Listize::perform(ls); list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); list->append(expr); } else { list = Cast(expr); } Block_Obj body = e->block(); Expression_Obj val; if (map) { for (Expression_Obj key : map->keys()) { Expression_Obj value = map->at(key); if (variables.size() == 1) { List* variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); variable->append(key); variable->append(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 && Cast(list)) { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { Expression* item = list->at(i); // unwrap value if the expression is an argument if (Argument* arg = Cast(item)) item = arg->value(); // check if we got passed a list of args (investigate) if (List* scalars = Cast(item)) { 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(Null, expr->pstate()) : scalars->at(j); env.set_local(variables[j], res); } } } else { if (variables.size() > 0) { env.set_local(variables.at(0), item); for (size_t j = 1, K = variables.size(); j < K; ++j) { // XXX: this is never hit via spec tests Expression* res = SASS_MEMORY_NEW(Null, expr->pstate()); env.set_local(variables[j], res); } } } val = body->perform(this); if (val) break; } } env_stack().pop_back(); return val.detach(); } Expression* Eval::operator()(While* w) { Expression_Obj pred = w->predicate(); Block_Obj body = w->block(); Env env(environment(), true); env_stack().push_back(&env); Expression_Obj cond = pred->perform(this); while (!cond->is_false()) { Expression_Obj val = body->perform(this); if (val) { env_stack().pop_back(); return val.detach(); } cond = pred->perform(this); } 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 = options().output_style; options().output_style = NESTED; Expression_Obj message = w->message()->perform(this); Env* env = environment(); // try to use generic function if (env->has("@warn[f]")) { // add call stack entry callee_stack().push_back({ "@warn", w->pstate().path, w->pstate().line + 1, w->pstate().column + 1, SASS_CALLEE_FUNCTION, { env } }); Definition* def = Cast((*env)["@warn[f]"]); // Block_Obj 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); AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&ast2c)); union Sass_Value* c_val = c_func(c_args, c_function, compiler()); options().output_style = outstyle; callee_stack().pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; } std::string result(unquote(message->to_sass())); std::cerr << "WARNING: " << result << std::endl; traces.push_back(Backtrace(w->pstate())); std::cerr << traces_to_string(traces, " "); std::cerr << std::endl; options().output_style = outstyle; traces.pop_back(); return 0; } Expression* Eval::operator()(Error* e) { Sass_Output_Style outstyle = options().output_style; options().output_style = NESTED; Expression_Obj message = e->message()->perform(this); Env* env = environment(); // try to use generic function if (env->has("@error[f]")) { // add call stack entry callee_stack().push_back({ "@error", e->pstate().path, e->pstate().line + 1, e->pstate().column + 1, SASS_CALLEE_FUNCTION, { env } }); Definition* def = Cast((*env)["@error[f]"]); // Block_Obj 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); AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&ast2c)); union Sass_Value* c_val = c_func(c_args, c_function, compiler()); options().output_style = outstyle; callee_stack().pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; } std::string result(unquote(message->to_sass())); options().output_style = outstyle; error(result, e->pstate(), traces); return 0; } Expression* Eval::operator()(Debug* d) { Sass_Output_Style outstyle = options().output_style; options().output_style = NESTED; Expression_Obj message = d->value()->perform(this); Env* env = environment(); // try to use generic function if (env->has("@debug[f]")) { // add call stack entry callee_stack().push_back({ "@debug", d->pstate().path, d->pstate().line + 1, d->pstate().column + 1, SASS_CALLEE_FUNCTION, { env } }); Definition* def = Cast((*env)["@debug[f]"]); // Block_Obj 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); AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); sass_list_set_value(c_args, 0, message->perform(&ast2c)); union Sass_Value* c_val = c_func(c_args, c_function, compiler()); options().output_style = outstyle; callee_stack().pop_back(); sass_delete_value(c_args); sass_delete_value(c_val); return 0; } 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)); 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_Obj lm = SASS_MEMORY_NEW(Map, l->pstate(), l->length() / 2); for (size_t i = 0, L = l->length(); i < L; i += 2) { Expression_Obj key = (*l)[i+0]->perform(this); Expression_Obj val = (*l)[i+1]->perform(this); // make sure the color key never displays its real name key->is_delayed(true); // verified *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { traces.push_back(Backtrace(l->pstate())); throw Exception::DuplicateKeyError(traces, *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_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), l->length(), l->separator(), l->is_arglist(), l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { ll->append((*l)[i]->perform(this)); } ll->is_interpolant(l->is_interpolant()); ll->from_selector(l->from_selector()); ll->is_expanded(true); return ll.detach(); } 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()) { traces.push_back(Backtrace(m->pstate())); throw Exception::DuplicateKeyError(traces, *m, *m); } Map_Obj mm = SASS_MEMORY_NEW(Map, m->pstate(), m->length()); for (auto key : m->keys()) { Expression* ex_key = key->perform(this); Expression* ex_val = m->at(key); if (ex_val == NULL) continue; ex_val = ex_val->perform(this); *mm << std::make_pair(ex_key, ex_val); } // check the evaluated keys aren't duplicates. if (mm->has_duplicate_key()) { traces.push_back(Backtrace(m->pstate())); throw Exception::DuplicateKeyError(traces, *mm, *m); } mm->is_expanded(true); return mm.detach(); } Expression* Eval::operator()(Binary_Expression* b_in) { Expression_Obj lhs = b_in->left(); Expression_Obj rhs = b_in->right(); enum Sass_OP op_type = b_in->optype(); if (op_type == Sass_OP::AND) { // LOCAL_FLAG(force, true); lhs = lhs->perform(this); if (!*lhs) return lhs.detach(); return rhs->perform(this); } else if (op_type == Sass_OP::OR) { // LOCAL_FLAG(force, true); lhs = lhs->perform(this); if (*lhs) return lhs.detach(); return rhs->perform(this); } // Evaluate variables as early o while (Variable* l_v = Cast(lhs)) { lhs = operator()(l_v); } while (Variable* r_v = Cast(rhs)) { rhs = operator()(r_v); } Binary_Expression_Obj b = b_in; // Evaluate sub-expressions early on while (Binary_Expression* l_b = Cast(lhs)) { if (!force && l_b->is_delayed()) break; lhs = operator()(l_b); } while (Binary_Expression* r_b = Cast(rhs)) { if (!force && r_b->is_delayed()) break; rhs = operator()(r_b); } // don't eval delayed expressions (the '/' when used as a separator) if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { b->right(b->right()->perform(this)); b->left(b->left()->perform(this)); return b.detach(); } // specific types we know are final // handle them early to avoid overhead if (Number* l_n = Cast(lhs)) { // lhs is number and rhs is number if (Number* r_n = Cast(rhs)) { try { switch (op_type) { case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_numbers(op_type, *l_n, *r_n, options(), b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { traces.push_back(Backtrace(b_in->pstate())); throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is number and rhs is color // Todo: allow to work with HSLA colors else if (Color* r_col = Cast(rhs)) { Color_RGBA_Obj r_c = r_col->toRGBA(); try { switch (op_type) { case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_number_color(op_type, *l_n, *r_c, options(), b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { traces.push_back(Backtrace(b_in->pstate())); throw Exception::SassValueError(traces, b_in->pstate(), err); } } } else if (Color* l_col = Cast(lhs)) { Color_RGBA_Obj l_c = l_col->toRGBA(); // lhs is color and rhs is color if (Color* r_col = Cast(rhs)) { Color_RGBA_Obj r_c = r_col->toRGBA(); try { switch (op_type) { case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_colors(op_type, *l_c, *r_c, options(), b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { traces.push_back(Backtrace(b_in->pstate())); throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is color and rhs is number else if (Number* r_n = Cast(rhs)) { try { switch (op_type) { case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: return Operators::op_color_number(op_type, *l_c, *r_n, options(), b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { traces.push_back(Backtrace(b_in->pstate())); throw Exception::SassValueError(traces, b_in->pstate(), err); } } } String_Schema_Obj ret_schema; // only the last item will be used to eval the binary expression if (String_Schema* s_l = Cast(b->left())) { if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), b->op(), s_l->last(), b->right()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified for (size_t i = 0; i < s_l->length() - 1; ++i) { ret_schema->append(s_l->at(i)->perform(this)); } ret_schema->append(bin_ex->perform(this)); return ret_schema->perform(this); } } if (String_Schema* s_r = Cast(b->right())) { if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), b->op(), b->left(), s_r->first()); bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified ret_schema->append(bin_ex->perform(this)); for (size_t i = 1; i < s_r->length(); ++i) { ret_schema->append(s_r->at(i)->perform(this)); } return ret_schema->perform(this); } } // fully evaluate their values 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) { LOCAL_FLAG(force, true); lhs->is_expanded(false); lhs->set_delayed(false); lhs = lhs->perform(this); rhs->is_expanded(false); rhs->set_delayed(false); rhs = rhs->perform(this); } else { lhs = lhs->perform(this); } // not a logical connective, so go ahead and eval the rhs rhs = rhs->perform(this); AST_Node_Obj lu = lhs; AST_Node_Obj ru = rhs; Expression::Type l_type; Expression::Type r_type; // Is one of the operands an interpolant? String_Schema_Obj s1 = Cast(b->left()); String_Schema_Obj s2 = Cast(b->right()); Binary_Expression_Obj b1 = Cast(b->left()); Binary_Expression_Obj b2 = 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 (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 possible upgrade LHS to a number (for number to string compare) if (String_Constant* str = 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 = Parser::lexed_dimension(b->pstate(), str->value()); } } // If possible upgrade RHS to a number (for string to number compare) if (String_Constant* str = Cast(rhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { rhs = Parser::lexed_dimension(b->pstate(), str->value()); } } } To_Value to_value(ctx); Value_Obj v_l = Cast(lhs->perform(&to_value)); Value_Obj v_r = Cast(rhs->perform(&to_value)); if (force_delay) { std::string str(""); str += v_l->to_string(options()); if (b->op().ws_before) str += " "; str += b->separator(); if (b->op().ws_after) str += " "; str += v_r->to_string(options()); String_Constant* val = SASS_MEMORY_NEW(String_Constant, b->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(Boolean, b->pstate(), Operators::eq(lhs, rhs)); case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); default: break; } } catch (Exception::OperationError& err) { traces.push_back(Backtrace(b->pstate())); throw Exception::SassValueError(traces, 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_Obj rv; try { ParserState pstate(b->pstate()); if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { Number* l_n = Cast(lhs); Number* r_n = Cast(rhs); l_n->reduce(); r_n->reduce(); rv = Operators::op_numbers(op_type, *l_n, *r_n, options(), pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { Number* l_n = Cast(lhs); Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); rv = Operators::op_number_color(op_type, *l_n, *r_c, options(), pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); Number* r_n = Cast(rhs); rv = Operators::op_color_number(op_type, *l_c, *r_n, options(), pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); rv = Operators::op_colors(op_type, *l_c, *r_c, options(), pstate); } else { To_Value to_value(ctx); // this will leak if perform does not return a value! Value_Obj v_l = Cast(lhs->perform(&to_value)); Value_Obj v_r = 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; // check for type violations if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { traces.push_back(Backtrace(v_l->pstate())); throw Exception::InvalidValue(traces, *v_l); } if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { traces.push_back(Backtrace(v_r->pstate())); throw Exception::InvalidValue(traces, *v_r); } Value* ex = Operators::op_strings(b->op(), *v_l, *v_r, options(), pstate, !interpolant); // pass true to compress if (String_Constant* str = Cast(ex)) { if (str->concrete_type() == Expression::STRING) { String_Constant* lstr = Cast(lhs); String_Constant* rstr = 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) { traces.push_back(Backtrace(b->pstate())); // throw Exception::Base(b->pstate(), err.what()); throw Exception::SassValueError(traces, 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.detach(); } Expression* Eval::operator()(Unary_Expression* u) { Expression_Obj operand = u->operand()->perform(this); if (u->optype() == Unary_Expression::NOT) { Boolean* result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); result->value(!result->value()); return result; } else if (Number_Obj nr = Cast(operand)) { // negate value for minus unary expression if (u->optype() == Unary_Expression::MINUS) { Number_Obj cpy = SASS_MEMORY_COPY(nr); cpy->value( - cpy->value() ); // negate value return cpy.detach(); // return the copy } else if (u->optype() == Unary_Expression::SLASH) { std::string str = '/' + nr->to_string(options()); return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); } // nothing for positive return nr.detach(); } else { // Special cases: +/- variables which evaluate to null output just +/-, // but +/- null itself outputs the string if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); } // Never apply unary opertions on colors @see #2140 else if (Color* color = Cast(operand)) { // Use the color name if this was eval with one if (color->disp().length() > 0) { Unary_Expression_Obj cpy = SASS_MEMORY_COPY(u); cpy->operand(SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp())); return SASS_MEMORY_NEW(String_Quoted, cpy->pstate(), cpy->inspect()); } } else { Unary_Expression_Obj cpy = SASS_MEMORY_COPY(u); cpy->operand(operand); return SASS_MEMORY_NEW(String_Quoted, cpy->pstate(), cpy->inspect()); } return SASS_MEMORY_NEW(String_Quoted, u->pstate(), u->inspect()); } // unreachable return u; } Expression* Eval::operator()(Function_Call* c) { if (traces.size() > 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(), traces); } if (Cast(c->sname())) { Expression_Obj evaluated_name = c->sname()->perform(this); Expression_Obj evaluated_args = c->arguments()->perform(this); std::string str(evaluated_name->to_string()); str += evaluated_args->to_string(); return SASS_MEMORY_NEW(String_Constant, c->pstate(), str); } std::string name(Util::normalize_underscores(c->name())); std::string full_name(name + "[f]"); // we make a clone here, need to implement that further Arguments_Obj args = c->arguments(); Env* env = environment(); if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { if (!env->has("*[f]")) { for (Argument_Obj arg : args->elements()) { if (List_Obj ls = Cast(arg->value())) { if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); } } args = Cast(args->perform(this)); Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, c->pstate(), c->name(), args); if (args->has_named_arguments()) { error("Plain CSS function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); } String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), lit->to_string(options())); str->is_interpolant(c->is_interpolant()); return str; } else { // call generic function full_name = "*[f]"; } } // further delay for calls if (full_name != "call[f]") { args->set_delayed(false); // verified } if (full_name != "if[f]") { args = Cast(args->perform(this)); } Definition* def = Cast((*env)[full_name]); if (c->func()) def = c->func()->definition(); if (def->is_overload_stub()) { std::stringstream ss; size_t L = args->length(); // account for rest arguments if (args->has_rest_argument() && args->length() > 0) { // get the rest arguments list List* rest = Cast(args->last()->value()); // arguments before rest argument plus rest if (rest) L += rest->length() - 1; } ss << full_name << L; 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(), traces); def = Cast((*env)[resolved_name]); } Expression_Obj result = c; Block_Obj body = def->block(); Native_Function func = def->native_function(); Sass_Function_Entry c_function = def->c_function(); if (c->is_css()) return result.detach(); Parameters_Obj params = def->parameters(); Env fn_env(def->environment()); env_stack().push_back(&fn_env); if (func || body) { bind(std::string("Function"), c->name(), params, args, &fn_env, this, traces); std::string msg(", in function `" + c->name() + "`"); traces.push_back(Backtrace(c->pstate(), msg)); callee_stack().push_back({ c->name().c_str(), c->pstate().path, c->pstate().line + 1, c->pstate().column + 1, SASS_CALLEE_FUNCTION, { env } }); // eval the body if user-defined or special, invoke underlying CPP function if native if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { result = body->perform(this); } else if (func) { result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.getSelectorStack(), exp.originalStack); } if (!result) { error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); } callee_stack().pop_back(); traces.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_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); new_args->concat(args); args = new_args; } // populates env with default values for params std::string ff(c->name()); bind(std::string("Function"), c->name(), params, args, &fn_env, this, traces); std::string msg(", in function `" + c->name() + "`"); traces.push_back(Backtrace(c->pstate(), msg)); callee_stack().push_back({ c->name().c_str(), c->pstate().path, c->pstate().line + 1, c->pstate().column + 1, SASS_CALLEE_C_FUNCTION, { env } }); AST2C ast2c; union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); for(size_t i = 0; i < params->length(); i++) { Parameter_Obj param = params->at(i); std::string key = param->name(); AST_Node_Obj node = fn_env.get_local(key); Expression_Obj arg = Cast(node); sass_list_set_value(c_args, i, arg->perform(&ast2c)); } union Sass_Value* c_val = c_func(c_args, c_function, compiler()); if (sass_value_get_tag(c_val) == SASS_ERROR) { std::string message("error in C function " + c->name() + ": " + sass_error_get_message(c_val)); sass_delete_value(c_val); sass_delete_value(c_args); error(message, c->pstate(), traces); } else if (sass_value_get_tag(c_val) == SASS_WARNING) { std::string message("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val)); sass_delete_value(c_val); sass_delete_value(c_args); error(message, c->pstate(), traces); } result = c2ast(c_val, traces, c->pstate()); callee_stack().pop_back(); traces.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 = result->perform(this); result->is_interpolant(c->is_interpolant()); env_stack().pop_back(); return result.detach(); } Expression* Eval::operator()(Variable* v) { Expression_Obj value; Env* env = environment(); const std::string& name(v->name()); EnvResult rv(env->find(name)); if (rv.found) value = static_cast(rv.it->second.ptr()); else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); if (Argument* arg = Cast(value)) value = arg->value(); if (Number* nr = Cast(value)) nr->zero(true); // force flag value->is_interpolant(v->is_interpolant()); if (force) value->is_expanded(false); value->set_delayed(false); // verified value = value->perform(this); if(!force) rv.it->second = value; return value.detach(); } Expression* Eval::operator()(Color_RGBA* c) { return c; } Expression* Eval::operator()(Color_HSLA* c) { return c; } Expression* Eval::operator()(Number* n) { return n; } Expression* Eval::operator()(Boolean* b) { return b; } void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { bool needs_closing_brace = false; if (Arguments* args = Cast(ex)) { List* ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); for(auto arg : args->elements()) { ll->append(arg->value()); } ll->is_interpolant(args->is_interpolant()); needs_closing_brace = true; res += "("; ex = ll; } if (Number* nr = Cast(ex)) { Number reduced(nr); reduced.reduce(); if (!reduced.is_valid_css_unit()) { traces.push_back(Backtrace(nr->pstate())); throw Exception::InvalidValue(traces, *nr); } } if (Argument* arg = Cast(ex)) { ex = arg->value(); } if (String_Quoted* sq = Cast(ex)) { if (was_itpl) { bool was_interpolant = ex->is_interpolant(); ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); ex->is_interpolant(was_interpolant); } } if (Cast(ex)) { return; } // parent selector needs another go if (Cast(ex)) { // XXX: this is never hit via spec tests ex = ex->perform(this); } if (List* l = Cast(ex)) { List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); // this fixes an issue with bourbon sample, not really sure why // if (l->size() && Cast((*l)[0])) { res += ""; } for(Expression_Obj item : *l) { item->is_interpolant(l->is_interpolant()); std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); bool is_null = Cast(item) != 0; // rl != "" if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); } // Check indicates that we probably should not get a list // here. Normally single list items are already unwrapped. if (l->size() > 1) { // string_to_output would fail "#{'_\a' '_\a'}"; std::string str(ll->to_string(options())); str = read_hex_escapes(str); // read escapes newline_to_space(str); // replace directly res += str; // append to result string } else { res += (ll->to_string(options())); } ll->is_interpolant(l->is_interpolant()); } // Value // Function_Call // Selector_List // String_Quoted // String_Constant // Binary_Expression else { // ex = ex->perform(this); if (into_quotes && ex->is_interpolant()) { res += evacuate_escapes(ex ? ex->to_string(options()) : ""); } else { std::string str(ex ? ex->to_string(options()) : ""); if (into_quotes) str = read_hex_escapes(str); res += str; // append to result string } } if (needs_closing_brace) res += ")"; } Expression* Eval::operator()(String_Schema* s) { size_t L = s->length(); bool into_quotes = false; if (L > 1) { if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { if (String_Constant* l = Cast((*s)[0])) { if (String_Constant* r = Cast((*s)[L - 1])) { if (r->value().size() > 0) { 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; } } } } } bool was_quoted = false; bool was_interpolant = false; std::string res(""); for (size_t i = 0; i < L; ++i) { bool is_quoted = Cast((*s)[i]) != NULL; if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } Expression_Obj ex = (*s)[i]->perform(this); interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); was_quoted = Cast((*s)[i]) != NULL; was_interpolant = (*s)[i]->is_interpolant(); } if (!s->is_interpolant()) { if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); } // string schema seems to have a special unquoting behavior (also handles "nested" quotes) String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); // if (s->is_interpolant()) str->quote_mark(0); // String_Constant* str = SASS_MEMORY_NEW(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.detach(); } Expression* Eval::operator()(String_Constant* s) { return s; } Expression* Eval::operator()(String_Quoted* s) { String_Quoted* str = SASS_MEMORY_NEW(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(Supports_Operator, c->pstate(), Cast(left), Cast(right), c->operand()); return cc; } Expression* Eval::operator()(Supports_Negation* c) { Expression* condition = c->condition()->perform(this); Supports_Negation* cc = SASS_MEMORY_NEW(Supports_Negation, c->pstate(), 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(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(Supports_Interpolation, c->pstate(), value); return cc; } Expression* Eval::operator()(At_Root_Query* e) { Expression_Obj feature = e->feature(); feature = (feature ? feature->perform(this) : 0); Expression_Obj value = e->value(); value = (value ? value->perform(this) : 0); Expression* ee = SASS_MEMORY_NEW(At_Root_Query, e->pstate(), Cast(feature), value); return ee; } Media_Query* Eval::operator()(Media_Query* q) { String_Obj t = q->media_type(); t = static_cast(t.isNull() ? 0 : t->perform(this)); Media_Query_Obj qq = SASS_MEMORY_NEW(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->append(static_cast((*q)[i]->perform(this))); } return qq.detach(); } Expression* Eval::operator()(Media_Query_Expression* e) { Expression_Obj feature = e->feature(); feature = (feature ? feature->perform(this) : 0); if (feature && Cast(feature)) { feature = SASS_MEMORY_NEW(String_Quoted, feature->pstate(), Cast(feature)->value()); } Expression_Obj value = e->value(); value = (value ? value->perform(this) : 0); if (value && Cast(value)) { // XXX: this is never hit via spec tests value = SASS_MEMORY_NEW(String_Quoted, value->pstate(), Cast(value)->value()); } return SASS_MEMORY_NEW(Media_Query_Expression, e->pstate(), feature, value, e->is_interpolated()); } Expression* Eval::operator()(Null* n) { return n; } Expression* Eval::operator()(Argument* a) { Expression_Obj val = a->value()->perform(this); 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_Obj wrapper = SASS_MEMORY_NEW(List, val->pstate(), 0, SASS_COMMA, true); wrapper->append(val); val = wrapper; } } return SASS_MEMORY_NEW(Argument, a->pstate(), val, a->name(), is_rest_argument, is_keyword_argument); } Expression* Eval::operator()(Arguments* a) { Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); if (a->length() == 0) return aa.detach(); for (size_t i = 0, L = a->length(); i < L; ++i) { Expression_Obj rv = (*a)[i]->perform(this); Argument* arg = Cast(rv); if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { aa->append(arg); } } if (a->has_rest_argument()) { Expression_Obj rest = a->get_rest_argument()->perform(this); Expression_Obj splat = Cast(rest)->value()->perform(this); Sass_Separator separator = SASS_COMMA; List* ls = Cast(splat); Map* ms = Cast(splat); List_Obj arglist = SASS_MEMORY_NEW(List, splat->pstate(), 0, ls ? ls->separator() : separator, true); if (ls && ls->is_arglist()) { arglist->concat(ls); } else if (ms) { aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); } else if (ls) { arglist->concat(ls); } else { arglist->append(splat); } if (arglist->length()) { aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); } } if (a->has_keyword_argument()) { Expression_Obj rv = a->get_keyword_argument()->perform(this); Argument* rvarg = Cast(rv); Expression_Obj kwarg = rvarg->value()->perform(this); aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); } return aa.detach(); } Expression* Eval::operator()(Comment* c) { return 0; } SelectorList* Eval::operator()(Selector_Schema* s) { LOCAL_FLAG(is_in_selector_schema, true); // the parser will look for a brace to end the selector Expression_Obj sel = s->contents()->perform(this); std::string result_str(sel->to_string(options())); result_str = unquote(Util::rtrim(result_str)); char* temp_cstr = sass_copy_c_string(result_str.c_str()); ctx.strings.push_back(temp_cstr); // attach to context Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); // If a schema contains a reference to parent it is already // connected to it, so don't connect implicitly anymore SelectorListObj parsed = p.parseSelectorList(true); flag_is_in_selector_schema.reset(); return parsed.detach(); } Expression* Eval::operator()(Parent_Reference* p) { if (SelectorListObj pr = exp.original()) { return operator()(pr); } else { return SASS_MEMORY_NEW(Null, p->pstate()); } } SimpleSelector* Eval::operator()(SimpleSelector* s) { return s; } Pseudo_Selector* Eval::operator()(Pseudo_Selector* pseudo) { // ToDo: should we eval selector? return pseudo; }; }