From 734668f1803bc7ad5315f05bc026a1a2d593349a Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Thu, 28 Apr 2016 03:01:41 +0200 Subject: [PATCH] Fix and refactor delayed evaluation flag --- src/ast.cpp | 38 +++++------- src/ast.hpp | 30 +++++----- src/bind.cpp | 1 - src/debugger.hpp | 5 +- src/error_handling.cpp | 1 - src/eval.cpp | 129 +++++++++-------------------------------- src/eval.hpp | 2 + src/functions.cpp | 17 +++--- src/inspect.cpp | 20 ++----- src/parser.cpp | 104 ++++++++++++++------------------- src/parser.hpp | 4 +- src/util.cpp | 6 +- 12 files changed, 121 insertions(+), 236 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index b0f05ab9d8..efb8835291 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -74,6 +74,21 @@ namespace Sass { ltrim(); } + void Argument::set_delayed(bool delayed) + { + if (value_) value_->set_delayed(delayed); + is_delayed(delayed); + } + + void Arguments::set_delayed(bool delayed) + { + for (Argument* arg : elements()) { + if (arg) arg->set_delayed(delayed); + } + is_delayed(delayed); + } + + bool At_Root_Query::exclude(std::string str) { bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; @@ -2149,29 +2164,6 @@ namespace Sass { return is_interpolant() || (right() && right()->is_right_interpolant()); } - // delay binary expressions in function arguments - // https://github.com/sass/libsass/issues/1417 - bool Binary_Expression::can_delay(void) const - { - bool l_delay = false; - bool r_delay = false; - if (op().operand == Sass_OP::DIV) { - if (Textual* tl = dynamic_cast(left())) { - l_delay = tl->type() == Textual::NUMBER || - tl->type() == Textual::DIMENSION; - } else { - l_delay = dynamic_cast(left()) != NULL; - } - if (Textual* tr = dynamic_cast(right())) { - r_delay = tr->type() == Textual::NUMBER || - tr->type() == Textual::DIMENSION; - } else { - r_delay = dynamic_cast(right()) != NULL; - } - } - return l_delay && r_delay; - } - std::string AST_Node::to_string(Sass_Inspect_Options opt) const { Sass_Output_Options out(opt); diff --git a/src/ast.hpp b/src/ast.hpp index 86cb06830b..3ed927bff4 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -50,6 +50,9 @@ namespace Sass { + // easier to search with name + const bool DELAYED = true; + // ToDo: should this really be hardcoded // Note: most methods follow precision option const double NUMBER_EPSILON = 0.00000000000001; @@ -67,7 +70,8 @@ namespace Sass { bool ws_after; }; - // from boost (functional/hash): + ////////////////////////////////////////////////////////// + // `hash_combine` comes from boost (functional/hash): // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html // Boost Software License - Version 1.0 // http://www.boost.org/users/license.html @@ -77,6 +81,7 @@ namespace Sass { seed ^= std::hash()(val) + 0x9e3779b9 + (seed<<6) + (seed>>2); } + ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // Abstract base class for all abstract syntax tree nodes. @@ -905,9 +910,8 @@ namespace Sass { virtual void set_delayed(bool delayed) { - for (size_t i = 0, L = length(); i < L; ++i) - (elements()[i])->set_delayed(delayed); is_delayed(delayed); + // don't set children } virtual bool operator== (const Expression& rhs) const; @@ -1031,12 +1035,6 @@ namespace Sass { return is_left_interpolant() || is_right_interpolant(); } - virtual bool can_delay() const; - void reset_whitespace() - { - op_.ws_before = false; - op_.ws_after = false; - } virtual void set_delayed(bool delayed) { right()->set_delayed(delayed); @@ -1138,6 +1136,7 @@ namespace Sass { } } + virtual void set_delayed(bool delayed); virtual bool operator==(const Expression& rhs) const { try @@ -1185,6 +1184,8 @@ namespace Sass { has_keyword_argument_(false) { } + virtual void set_delayed(bool delayed); + Argument* get_rest_argument(); Argument* get_keyword_argument(); @@ -1296,7 +1297,7 @@ namespace Sass { size_t hash_; public: Textual(ParserState pstate, Type t, std::string val) - : Expression(pstate, true), type_(t), value_(val), + : Expression(pstate, DELAYED), type_(t), value_(val), hash_(0) { } @@ -1466,10 +1467,9 @@ namespace Sass { // "flat" strings. //////////////////////////////////////////////////////////////////////// class String : public Value { - ADD_PROPERTY(bool, sass_fix_1291) public: - String(ParserState pstate, bool delayed = false, bool sass_fix_1291 = false) - : Value(pstate, delayed), sass_fix_1291_(sass_fix_1291) + String(ParserState pstate, bool delayed = false) + : Value(pstate, delayed) { concrete_type(STRING); } static std::string type_name() { return "string"; } virtual ~String() = 0; @@ -1517,6 +1517,10 @@ namespace Sass { return hash_; } + virtual void set_delayed(bool delayed) { + is_delayed(delayed); + } + virtual bool operator==(const Expression& rhs) const; ATTACH_OPERATIONS() diff --git a/src/bind.cpp b/src/bind.cpp index 84c1f66667..5f77fe8e19 100644 --- a/src/bind.cpp +++ b/src/bind.cpp @@ -21,7 +21,6 @@ namespace Sass { // force optional quotes (only if needed) if (str->quote_mark()) { str->quote_mark('*'); - str->is_delayed(true); } } } diff --git a/src/debugger.hpp b/src/debugger.hpp index 15170f4561..fe1cece100 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -311,6 +311,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "Warning " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->message(), ind + " : "); } else if (dynamic_cast(node)) { Error* block = dynamic_cast(node); std::cerr << ind << "Error " << block; @@ -578,6 +579,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) Color* expression = dynamic_cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; } else if (dynamic_cast(node)) { @@ -594,7 +596,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << prettyprint(expression->value()) << "]"; if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->sass_fix_1291()) std::cerr << " [sass_fix_1291]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; @@ -607,7 +608,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << prettyprint(expression->value()) << "]"; if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->sass_fix_1291()) std::cerr << " [sass_fix_1291]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; } else if (dynamic_cast(node)) { @@ -626,7 +626,6 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "String " << expression; std::cerr << " " << expression->concrete_type(); std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->sass_fix_1291()) std::cerr << " [sass_fix_1291]"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; } else if (dynamic_cast(node)) { diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 2e36c7bc8c..05f35b2f78 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -74,7 +74,6 @@ namespace Sass { : Base(org.pstate()), dup(dup), org(org) { msg = "Duplicate key "; - dup.get_duplicate_key()->is_delayed(false); msg += dup.get_duplicate_key()->inspect(); msg += " in map ("; msg += org.inspect(); diff --git a/src/eval.cpp b/src/eval.cpp index 5e1f11c19c..573baef23d 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -41,7 +41,9 @@ namespace Sass { Eval::Eval(Expand& exp) : exp(exp), - ctx(exp.ctx) + ctx(exp.ctx), + force(false), + is_in_comment(false) { } Eval::~Eval() { } @@ -436,7 +438,7 @@ namespace Sass { 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); + key->is_delayed(true); // verified *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { @@ -497,21 +499,13 @@ namespace Sass { 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_l = dynamic_cast(b->left())) { if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { ret_schema = SASS_MEMORY_NEW(ctx.mem, String_Schema, s_l->pstate()); Binary_Expression* bin_ex = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, b->pstate(), b->op(), s_l->last(), b->right()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); - // bin_ex->is_interpolant(b->left()->is_interpolant()); + 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 << s_l->at(i)->perform(this); } @@ -524,8 +518,7 @@ namespace Sass { 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); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified *ret_schema << bin_ex->perform(this); for (size_t i = 1; i < s_r->length(); ++i) { *ret_schema << s_r->at(i)->perform(this); @@ -536,33 +529,16 @@ namespace Sass { // don't eval delayed expressions (the '/' when used as a separator) - if (op_type == Sass_OP::DIV && b->is_delayed()) { + if (!force && 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 + // fully evaluate their values if (op_type == Sass_OP::EQ || op_type == Sass_OP::NEQ || op_type == Sass_OP::GT || @@ -570,43 +546,16 @@ namespace Sass { 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); - } - } + LOCAL_FLAG(force, 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); + else { + lhs = lhs->perform(this); } switch (op_type) { @@ -624,19 +573,6 @@ namespace Sass { // 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(); @@ -663,7 +599,7 @@ namespace Sass { 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); + lhs = lhs->perform(this); } } if (String_Constant* str = dynamic_cast(rhs)) { @@ -671,7 +607,7 @@ namespace Sass { 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); + rhs = rhs->perform(this); } } } @@ -837,28 +773,9 @@ namespace Sass { std::string full_name(name + "[f]"); Arguments* args = SASS_MEMORY_NEW(ctx.mem, Arguments, *c->arguments()); - // handle call here if valid arg - // otherwise we eval arguments to early - if (name == "call" && args->length() > 0) { - Expression* redirect = args->at(0)->perform(this); - args->erase(args->begin()); - Function_Call* lit = SASS_MEMORY_NEW(ctx.mem, Function_Call, - c->pstate(), - unquote(redirect->to_string()), - args); - return operator()(lit); - } - Env* env = environment(); if (!env->has(full_name)) { if (!env->has("*[f]")) { - // just pass it through as a literal - for (Argument* arg : *args) { - if (Binary_Expression* b = dynamic_cast(arg->value())) { - b->reset_whitespace(); - arg->is_delayed(b->can_delay()); // delay - } - } args = static_cast(args->perform(this)); Function_Call* lit = SASS_MEMORY_NEW(ctx.mem, Function_Call, c->pstate(), @@ -878,10 +795,13 @@ namespace Sass { } } + // further delay for calls + if (full_name != "call[f]") { + args->set_delayed(false); // verified + } if (full_name != "if[f]") { args = static_cast(args->perform(this)); } - Definition* def = static_cast((*env)[full_name]); if (def->is_overload_stub()) { @@ -960,8 +880,7 @@ namespace Sass { 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 = result->perform(this); result->is_interpolant(c->is_interpolant()); exp.env_stack.pop_back(); return result; @@ -1018,6 +937,7 @@ namespace Sass { value->is_interpolant(v->is_interpolant()); value->is_expanded(false); + value->set_delayed(false); // verified return value->perform(this); } @@ -1095,6 +1015,11 @@ namespace Sass { return result; } + Expression* Eval::operator()(Color* c) + { + return c; + } + Expression* Eval::operator()(Number* n) { return n; @@ -1201,7 +1126,7 @@ namespace Sass { bool is_quoted = dynamic_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* ex = (*s)[i]->is_delayed() ? (*s)[i] : (*s)[i]->perform(this); + Expression* ex = (*s)[i]->perform(this); interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); was_quoted = dynamic_cast((*s)[i]) != NULL; was_interpolant = (*s)[i]->is_interpolant(); @@ -1227,6 +1152,7 @@ namespace Sass { Color* c = SASS_MEMORY_NEW(ctx.mem, Color, *name_to_color(s->value())); c->pstate(s->pstate()); c->disp(s->value()); + c->is_delayed(true); return c; } return s; @@ -1343,10 +1269,7 @@ namespace Sass { Expression* Eval::operator()(Argument* a) { Expression* val = a->value(); - // delay missin function arguments? - val->is_delayed(a->is_delayed()); val = val->perform(this); - val->is_delayed(false); bool is_rest_argument = a->is_rest_argument(); bool is_keyword_argument = a->is_keyword_argument(); diff --git a/src/eval.hpp b/src/eval.hpp index 5ba9f8a34a..1ac2c8d71d 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -22,6 +22,7 @@ namespace Sass { Eval(Expand& exp); ~Eval(); + bool force; bool is_in_comment; Env* environment(); @@ -50,6 +51,7 @@ namespace Sass { Expression* operator()(Variable*); Expression* operator()(Textual*); Expression* operator()(Number*); + Expression* operator()(Color*); Expression* operator()(Boolean*); Expression* operator()(String_Schema*); Expression* operator()(String_Quoted*); diff --git a/src/functions.cpp b/src/functions.cpp index 2332c7978a..c83b1ad4dd 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -880,7 +880,7 @@ namespace Sass { if (String_Quoted* string_quoted = dynamic_cast(arg)) { String_Constant* result = SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, string_quoted->value()); // remember if the string was quoted (color tokens) - result->sass_fix_1291(string_quoted->quote_mark() != 0); + result->is_delayed(true); // delay colors return result; } else if (dynamic_cast(arg)) { @@ -910,7 +910,6 @@ namespace Sass { // all other nodes must be converted to a string node std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); String_Quoted* result = SASS_MEMORY_NEW(ctx.mem, String_Quoted, pstate, str); - result->is_delayed(true); result->quote_mark('*'); return result; } @@ -1271,7 +1270,9 @@ namespace Sass { return l; } else { - return l->value_at_index(static_cast(index)); + Expression* rv = l->value_at_index(static_cast(index)); + rv->set_delayed(false); + return rv; } } @@ -1656,12 +1657,10 @@ namespace Sass { { Expand expand(ctx, &d_env, backtrace); bool is_true = !ARG("$condition", Expression)->perform(&expand.eval)->is_false(); - if (is_true) { - return ARG("$if-true", Expression)->perform(&expand.eval); - } - else { - return ARG("$if-false", Expression)->perform(&expand.eval); - } + Expression* res = ARG(is_true ? "$if-true" : "$if-false", Expression); + res = res->perform(&expand.eval); + res->set_delayed(false); // clone? + return res; } //////////////// diff --git a/src/inspect.cpp b/src/inspect.cpp index 55a3b12cc2..ed14e2cf48 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -166,10 +166,6 @@ namespace Sass { append_token("@import", import); append_mandatory_space(); - if (String_Quoted* strq = dynamic_cast(import->urls().front())) { - strq->is_delayed(false); - } - import->urls().front()->perform(this); if (import->urls().size() == 1) { if (import->media_queries()) { @@ -183,10 +179,6 @@ namespace Sass { append_token("@import", import); append_mandatory_space(); - if (String_Quoted* strq = dynamic_cast(import->urls()[i])) { - strq->is_delayed(false); - } - import->urls()[i]->perform(this); if (import->urls().size() - 1 == i) { if (import->media_queries()) { @@ -451,10 +443,8 @@ namespace Sass { (output_style() == INSPECT) || ( expr->op().ws_before && (!expr->is_interpolant()) - && (!expr->is_delayed() || - expr->is_left_interpolant() || - expr->is_right_interpolant() - ) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) )) append_string(" "); switch (expr->type()) { @@ -477,10 +467,8 @@ namespace Sass { (output_style() == INSPECT) || ( expr->op().ws_after && (!expr->is_interpolant()) - && (!expr->is_delayed() - || expr->is_left_interpolant() - || expr->is_right_interpolant() - ) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) )) append_string(" "); expr->right()->perform(this); } diff --git a/src/parser.cpp b/src/parser.cpp index 07d5a3cf8c..3b448f899b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -12,6 +12,18 @@ #include "sass/functions.h" #include "error_handling.hpp" +// Notes about delayed: some ast nodes can have delayed evaluation so +// they can preserve their original semantics if needed. This is most +// prominently exhibited by the division operation, since it is not +// only a valid operation, but also a valid css statement (i.e. for +// fonts, as in `16px/24px`). When parsing lists and expression we +// unwrap single items from lists and other operations. A nested list +// must not be delayed, only the items of the first level sometimes +// are delayed (as with argument lists). To achieve this we need to +// pass status to the list parser, so this can be set correctly. +// Another case with delayed values are colors. In compressed mode +// only processed values get compressed (other are left as written). + #include #include @@ -391,7 +403,6 @@ namespace Sass { if (lex< exactly<':'> >()) { // there's a default value while (lex< block_comment >()); val = parse_space_list(); - val->is_delayed(false); } else if (lex< exactly< ellipsis > >()) { is_rest = true; @@ -430,14 +441,12 @@ namespace Sass { ParserState p = pstate; lex_css< exactly<':'> >(); Expression* val = parse_space_list(); - val->is_delayed(false); arg = SASS_MEMORY_NEW(ctx.mem, Argument, p, val, name); } else { bool is_arglist = false; bool is_keyword = false; Expression* val = parse_space_list(); - val->is_delayed(false); List* l = dynamic_cast(val); if (lex_css< exactly< ellipsis > >()) { if (val->concrete_type() == Expression::MAP || ( @@ -462,7 +471,6 @@ namespace Sass { } else { val = parse_list(); } - val->is_delayed(false); bool is_default = false; bool is_global = false; while (peek< alternatives < default_flag, global_flag > >()) { @@ -938,7 +946,6 @@ namespace Sass { } else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { prop = SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); - prop->is_delayed(true); } else { css_error("Invalid CSS", " after ", ": expected \"}\", was "); @@ -959,11 +966,11 @@ namespace Sass { if (lookahead.has_interpolants) { value = parse_value_schema(lookahead.found); } else { - value = parse_list(); + value = parse_list(DELAYED); } } else { - value = parse_list(); + value = parse_list(DELAYED); if (List* list = dynamic_cast(value)) { if (list->length() == 0 && !peek< exactly <'{'> >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); @@ -1052,14 +1059,13 @@ namespace Sass { // parse list returns either a space separated list, // a comma separated list or any bare expression found. // so to speak: we unwrap items from lists if possible here! - Expression* Parser::parse_list() + Expression* Parser::parse_list(bool delayed) { - // parse list is relly just an alias - return parse_comma_list(); + return parse_comma_list(delayed); } // will return singletons unwrapped - Expression* Parser::parse_comma_list() + Expression* Parser::parse_comma_list(bool delayed) { // check if we have an empty list // return the empty list as such @@ -1075,12 +1081,20 @@ namespace Sass { default_flag, global_flag > >(position)) - { return SASS_MEMORY_NEW(ctx.mem, List, pstate, 0); } + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(ctx.mem, List, pstate, 0); + } // now try to parse a space list Expression* list = parse_space_list(); // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) return list; + if (!peek_css< exactly<','> >(position)) { + // set_delay doesn't apply to list children + // so this will only undelay single values + if (!delayed) list->set_delayed(false); + return list; + } // if we got so far, we actually do have a comma list List* comma_list = SASS_MEMORY_NEW(ctx.mem, List, pstate, 2, SASS_COMMA); @@ -1222,7 +1236,11 @@ namespace Sass { operands.push_back(parse_expression()); left_ws = peek < css_comments >() != NULL; } - // parse the operator + // we are called recursively for list, so we first + // fold inner binary expression which has delayed + // correctly set to zero. After folding we also unwrap + // single nested items. So we cannot set delay on the + // returned result here, as we have lost nestings ... return fold_operands(lhs, operands, operators); } // parse_relation @@ -1259,7 +1277,6 @@ namespace Sass { ) { - bool right_ws = peek < css_comments >() != NULL; operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); operands.push_back(parse_operators()); @@ -1307,14 +1324,6 @@ namespace Sass { // lex the expected closing parenthesis if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); // expression can be evaluated - // make sure wrapped lists and division expressions are non-delayed within parentheses - if (value->concrete_type() == Expression::LIST) { - // List* l = static_cast(value); - // if (!l->empty()) (*l)[0]->is_delayed(false); - } else if (typeid(*value) == typeid(Binary_Expression)) { - Binary_Expression* b = static_cast(value); - if (b && b->type() == Sass_OP::DIV) b->set_delayed(false); - } return value; } // string may be interpolated @@ -1455,7 +1464,6 @@ namespace Sass { if (!p) { String_Quoted* str_quoted = SASS_MEMORY_NEW(ctx.mem, String_Quoted, pstate, std::string(i, chunk.end)); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); - str_quoted->is_delayed(true); return str_quoted; } @@ -1514,7 +1522,6 @@ namespace Sass { --position; String_Constant* str_node = SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, str.time_wspace()); - str_node->is_delayed(true); return str_node; } @@ -1720,7 +1727,7 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace if (j) { // parse the interpolant and accumulate it - Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(DELAYED); interp_node->is_interpolant(true); (*schema) << interp_node; // schema->has_interpolants(true); @@ -1862,7 +1869,6 @@ namespace Sass { stack.push_back(Scope::Control); ParserState if_source_position = pstate; Expression* predicate = parse_list(); - predicate->is_delayed(false); Block* block = parse_block(); Block* alternative = 0; @@ -1887,13 +1893,11 @@ namespace Sass { std::string var(Util::normalize_underscores(lexed)); if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate); Expression* lower_bound = parse_expression(); - lower_bound->is_delayed(false); bool inclusive = false; if (lex< kwd_through >()) inclusive = true; else if (lex< kwd_to >()) inclusive = false; else error("expected 'through' or 'to' keyword in @for directive", pstate); Expression* upper_bound = parse_expression(); - upper_bound->is_delayed(false); Block* body = parse_block(); stack.pop_back(); return SASS_MEMORY_NEW(ctx.mem, For, for_source_position, var, lower_bound, upper_bound, body, inclusive); @@ -1938,13 +1942,6 @@ namespace Sass { } if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate); Expression* list = parse_list(); - list->is_delayed(false); - if (list->concrete_type() == Expression::LIST) { - List* l = static_cast(list); - for (size_t i = 0, L = l->length(); i < L; ++i) { - (*l)[i]->is_delayed(false); - } - } Block* body = parse_block(); stack.pop_back(); return SASS_MEMORY_NEW(ctx.mem, Each, each_source_position, vars, list, body); @@ -1958,7 +1955,6 @@ namespace Sass { While* call = SASS_MEMORY_NEW(ctx.mem, While, pstate, 0, 0); // parse mandatory predicate Expression* predicate = parse_list(); - predicate->is_delayed(false); call->predicate(predicate); // parse mandatory block call->block(parse_block()); @@ -2033,7 +2029,7 @@ namespace Sass { feature = parse_expression(); Expression* expression = 0; if (lex_css< exactly<':'> >()) { - expression = parse_list(); + expression = parse_list(DELAYED); } if (!lex_css< exactly<')'> >()) { error("unclosed parenthesis in media query expression", pstate); @@ -2252,8 +2248,6 @@ namespace Sass { Directive* directive = SASS_MEMORY_NEW(ctx.mem, Directive, pstate, lexed); Expression* val = parse_almost_any_value(); // strip left and right if they are of type string - // debug_ast(val); - // std::cerr << "HAASDASD\n"; directive->value(val); if (peek< exactly<'{'> >()) { directive->block(parse_block()); @@ -2386,7 +2380,7 @@ namespace Sass { stack.back() != Scope::Rules) { error("Illegal nesting: Only properties may be nested beneath properties.", pstate); } - return SASS_MEMORY_NEW(ctx.mem, Warning, pstate, parse_list()); + return SASS_MEMORY_NEW(ctx.mem, Warning, pstate, parse_list(DELAYED)); } Error* Parser::parse_error() @@ -2398,7 +2392,7 @@ namespace Sass { stack.back() != Scope::Rules) { error("Illegal nesting: Only properties may be nested beneath properties.", pstate); } - return SASS_MEMORY_NEW(ctx.mem, Error, pstate, parse_list()); + return SASS_MEMORY_NEW(ctx.mem, Error, pstate, parse_list(DELAYED)); } Debug* Parser::parse_debug() @@ -2410,7 +2404,7 @@ namespace Sass { stack.back() != Scope::Rules) { error("Illegal nesting: Only properties may be nested beneath properties.", pstate); } - return SASS_MEMORY_NEW(ctx.mem, Debug, pstate, parse_list()); + return SASS_MEMORY_NEW(ctx.mem, Debug, pstate, parse_list(DELAYED)); } Return* Parser::parse_return_directive() @@ -2624,21 +2618,12 @@ namespace Sass { { for (size_t i = 0, S = operands.size(); i < S; ++i) { base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, pstate, op, base, operands[i]); - Binary_Expression* b = static_cast(base); - if (op.operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { - base->is_delayed(true); - } - else if (b && b->op().operand != Sass_OP::DIV) { - b->left()->is_delayed(false); - b->right()->is_delayed(false); - } } return base; } Expression* Parser::fold_operands(Expression* base, std::vector& operands, std::vector& ops, size_t i) { - if (String_Schema* schema = dynamic_cast(base)) { // return schema; if (schema->has_interpolants()) { @@ -2655,8 +2640,6 @@ namespace Sass { )) { Expression* rhs = fold_operands(operands[0], operands, ops, 1); rhs = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[0], schema, rhs); - rhs->set_delayed(false); - rhs->is_delayed(true); return rhs; } // return schema; @@ -2670,12 +2653,9 @@ namespace Sass { Expression* rhs = fold_operands(operands[i+1], operands, ops, i + 2); rhs = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], schema, rhs); base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, rhs); - rhs->is_delayed(true); - base->is_delayed(true); return base; } base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); - if (ops[i].operand != Sass_OP::DIV) base->is_delayed(true); return base; } else { base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); @@ -2687,11 +2667,11 @@ namespace Sass { if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { base->is_delayed(true); } - else if (b) { - b->left()->is_delayed(false); - b->right()->is_delayed(false); - } - + } + // nested binary expression are never to be delayed + if (Binary_Expression* b = dynamic_cast(base)) { + if (dynamic_cast(b->left())) base->set_delayed(false); + if (dynamic_cast(b->right())) base->set_delayed(false); } return base; } diff --git a/src/parser.hpp b/src/parser.hpp index e9284978d4..215c8f8ecc 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -255,8 +255,8 @@ namespace Sass { Declaration* parse_declaration(); Expression* parse_map_value(); Expression* parse_map(); - Expression* parse_list(); - Expression* parse_comma_list(); + Expression* parse_list(bool delayed = false); + Expression* parse_comma_list(bool delayed = false); Expression* parse_space_list(); Expression* parse_disjunction(); Expression* parse_conjunction(); diff --git a/src/util.cpp b/src/util.cpp index 426e10d079..491c6421d2 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -151,7 +151,7 @@ namespace Sass { std::string out(""); bool lf = false; for (auto i : str) { - if (i == 10) { + if (i == '\n') { out += ' '; lf = true; } else if (!(lf && isspace(i))) { @@ -267,7 +267,7 @@ namespace Sass { // assert invalid code points if (cp == 0) cp = 0xFFFD; // replace bell character - // if (cp == 10) cp = 32; + // if (cp == '\n') cp = 32; // use a very simple approach to convert via utf8 lib // maybe there is a more elegant way; maybe we shoud @@ -330,7 +330,7 @@ namespace Sass { int cp = utf8::next(it, end); // in case of \r, check if the next in sequence - // is \n and then advance the iterator. + // is \n and then advance the iterator and skip \r if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { cp = utf8::next(it, end); }