From db979ca2ac9086b6cd51b1db391a799a6f240ae1 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 12 Dec 2015 18:22:36 +0100 Subject: [PATCH 01/17] Remove obsolete stuff from CRTP implementation --- src/cssize.hpp | 4 +- src/eval.hpp | 4 +- src/expand.hpp | 4 +- src/extend.hpp | 4 +- src/listize.hpp | 4 +- src/operation.hpp | 139 ++++++++++++++++++------------------ src/output.cpp | 3 +- src/remove_placeholders.hpp | 4 +- src/to_c.hpp | 4 +- src/to_string.hpp | 5 +- src/to_value.hpp | 2 +- 11 files changed, 81 insertions(+), 96 deletions(-) diff --git a/src/cssize.hpp b/src/cssize.hpp index 104e80548e..9d5a34ad5d 100644 --- a/src/cssize.hpp +++ b/src/cssize.hpp @@ -22,9 +22,7 @@ namespace Sass { public: Cssize(Context&, Backtrace*); - virtual ~Cssize() { } - - using Operation::operator(); + ~Cssize() { } Statement* operator()(Block*); Statement* operator()(Ruleset*); diff --git a/src/eval.hpp b/src/eval.hpp index 15334b3d52..4634eadff5 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -21,15 +21,13 @@ namespace Sass { Context& ctx; Listize listize; Eval(Expand& exp); - virtual ~Eval(); + ~Eval(); Env* environment(); Context& context(); Selector_List* selector(); Backtrace* backtrace(); - using Operation::operator(); - // for evaluating function bodies Expression* operator()(Block*); Expression* operator()(Assignment*); diff --git a/src/expand.hpp b/src/expand.hpp index 2c4d846952..d23e5280a2 100644 --- a/src/expand.hpp +++ b/src/expand.hpp @@ -43,9 +43,7 @@ namespace Sass { public: Expand(Context&, Env*, Backtrace*); - virtual ~Expand() { } - - using Operation::operator(); + ~Expand() { } Statement* operator()(Block*); Statement* operator()(Ruleset*); diff --git a/src/extend.hpp b/src/extend.hpp index 48514d6181..7061b3fada 100644 --- a/src/extend.hpp +++ b/src/extend.hpp @@ -25,9 +25,7 @@ namespace Sass { static Node subweave(Node& one, Node& two, Context& ctx); static Selector_List* extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subset_map, bool isReplace, bool& extendedSomething); Extend(Context&, ExtensionSubsetMap&); - virtual ~Extend() { } - - using Operation::operator(); + ~Extend() { } void operator()(Block*); void operator()(Ruleset*); diff --git a/src/listize.hpp b/src/listize.hpp index e0657dcdef..4a34e9b306 100644 --- a/src/listize.hpp +++ b/src/listize.hpp @@ -22,9 +22,7 @@ namespace Sass { public: Listize(Context&); - virtual ~Listize() { } - - using Operation::operator(); + ~Listize() { } Expression* operator()(Selector_List*); Expression* operator()(Complex_Selector*); diff --git a/src/operation.hpp b/src/operation.hpp index 47adf6bafc..86d61f9928 100644 --- a/src/operation.hpp +++ b/src/operation.hpp @@ -88,84 +88,83 @@ namespace Sass { template class Operation_CRTP : public Operation { public: - virtual T operator()(AST_Node* x) { return static_cast(this)->fallback(x); } - virtual ~Operation_CRTP() = 0; + D& impl() { return static_cast(*this); } + public: + T operator()(AST_Node* x) { return static_cast(this)->fallback(x); } // statements - virtual T operator()(Block* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Ruleset* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Propset* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Bubble* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Block* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Media_Block* x) { return static_cast(this)->fallback(x); } - virtual T operator()(At_Root_Block* x) { return static_cast(this)->fallback(x); } - virtual T operator()(At_Rule* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Declaration* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Assignment* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Import* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Import_Stub* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Warning* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Error* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Debug* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Comment* x) { return static_cast(this)->fallback(x); } - virtual T operator()(If* x) { return static_cast(this)->fallback(x); } - virtual T operator()(For* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Each* x) { return static_cast(this)->fallback(x); } - virtual T operator()(While* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Return* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Content* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Extension* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Definition* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Mixin_Call* x) { return static_cast(this)->fallback(x); } + T operator()(Block* x) { return static_cast(this)->fallback(x); } + T operator()(Ruleset* x) { return static_cast(this)->fallback(x); } + T operator()(Propset* x) { return static_cast(this)->fallback(x); } + T operator()(Bubble* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Block* x) { return static_cast(this)->fallback(x); } + T operator()(Media_Block* x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Block* x) { return static_cast(this)->fallback(x); } + T operator()(At_Rule* x) { return static_cast(this)->fallback(x); } + T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } + T operator()(Declaration* x) { return static_cast(this)->fallback(x); } + T operator()(Assignment* x) { return static_cast(this)->fallback(x); } + T operator()(Import* x) { return static_cast(this)->fallback(x); } + T operator()(Import_Stub* x) { return static_cast(this)->fallback(x); } + T operator()(Warning* x) { return static_cast(this)->fallback(x); } + T operator()(Error* x) { return static_cast(this)->fallback(x); } + T operator()(Debug* x) { return static_cast(this)->fallback(x); } + T operator()(Comment* x) { return static_cast(this)->fallback(x); } + T operator()(If* x) { return static_cast(this)->fallback(x); } + T operator()(For* x) { return static_cast(this)->fallback(x); } + T operator()(Each* x) { return static_cast(this)->fallback(x); } + T operator()(While* x) { return static_cast(this)->fallback(x); } + T operator()(Return* x) { return static_cast(this)->fallback(x); } + T operator()(Content* x) { return static_cast(this)->fallback(x); } + T operator()(Extension* x) { return static_cast(this)->fallback(x); } + T operator()(Definition* x) { return static_cast(this)->fallback(x); } + T operator()(Mixin_Call* x) { return static_cast(this)->fallback(x); } // expressions - virtual T operator()(List* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Map* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Binary_Expression* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Unary_Expression* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Function_Call* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Function_Call_Schema* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Custom_Warning* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Custom_Error* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Variable* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Textual* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Number* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Color* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Boolean* x) { return static_cast(this)->fallback(x); } - virtual T operator()(String_Schema* x) { return static_cast(this)->fallback(x); } - virtual T operator()(String_Constant* x) { return static_cast(this)->fallback(x); } - virtual T operator()(String_Quoted* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Condition* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Operator* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Negation* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Declaration* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Supports_Interpolation* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Media_Query* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Media_Query_Expression* x) { return static_cast(this)->fallback(x); } - virtual T operator()(At_Root_Expression* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Null* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Parent_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(List* x) { return static_cast(this)->fallback(x); } + T operator()(Map* x) { return static_cast(this)->fallback(x); } + T operator()(Binary_Expression* x) { return static_cast(this)->fallback(x); } + T operator()(Unary_Expression* x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call* x) { return static_cast(this)->fallback(x); } + T operator()(Function_Call_Schema* x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Warning* x) { return static_cast(this)->fallback(x); } + T operator()(Custom_Error* x) { return static_cast(this)->fallback(x); } + T operator()(Variable* x) { return static_cast(this)->fallback(x); } + T operator()(Textual* x) { return static_cast(this)->fallback(x); } + T operator()(Number* x) { return static_cast(this)->fallback(x); } + T operator()(Color* x) { return static_cast(this)->fallback(x); } + T operator()(Boolean* x) { return static_cast(this)->fallback(x); } + T operator()(String_Schema* x) { return static_cast(this)->fallback(x); } + T operator()(String_Constant* x) { return static_cast(this)->fallback(x); } + T operator()(String_Quoted* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Condition* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Operator* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Negation* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Declaration* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Interpolation* x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query* x) { return static_cast(this)->fallback(x); } + T operator()(Media_Query_Expression* x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Expression* x) { return static_cast(this)->fallback(x); } + T operator()(Null* x) { return static_cast(this)->fallback(x); } + T operator()(Parent_Selector* x) { return static_cast(this)->fallback(x); } // parameters and arguments - virtual T operator()(Parameter* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Parameters* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Argument* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Arguments* x) { return static_cast(this)->fallback(x); } + T operator()(Parameter* x) { return static_cast(this)->fallback(x); } + T operator()(Parameters* x) { return static_cast(this)->fallback(x); } + T operator()(Argument* x) { return static_cast(this)->fallback(x); } + T operator()(Arguments* x) { return static_cast(this)->fallback(x); } // selectors - virtual T operator()(Selector_Schema* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Selector_Placeholder* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Type_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Selector_Qualifier* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Attribute_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Pseudo_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Wrapped_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Compound_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Complex_Selector* x) { return static_cast(this)->fallback(x); } - virtual T operator()(Selector_List* x) { return static_cast(this)->fallback(x); } + T operator()(Selector_Schema* x) { return static_cast(this)->fallback(x); } + T operator()(Selector_Placeholder* x) { return static_cast(this)->fallback(x); } + T operator()(Type_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Selector_Qualifier* x) { return static_cast(this)->fallback(x); } + T operator()(Attribute_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Pseudo_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Wrapped_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Compound_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Complex_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Selector_List* x) { return static_cast(this)->fallback(x); } template T fallback(U x) { return T(); } }; - template - inline Operation_CRTP::~Operation_CRTP() { } } diff --git a/src/output.cpp b/src/output.cpp index 0b25529e4f..eae11c4920 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -341,7 +341,8 @@ namespace Sass { } if (b->is_invisible() || b->length() == 0) { - return append_string(" {}"); + append_optional_space(); + return append_string("{}"); } append_scope_opener(); diff --git a/src/remove_placeholders.hpp b/src/remove_placeholders.hpp index b39fba8b3c..de7e5645ef 100644 --- a/src/remove_placeholders.hpp +++ b/src/remove_placeholders.hpp @@ -22,9 +22,7 @@ namespace Sass { public: Remove_Placeholders(Context&); - virtual ~Remove_Placeholders() { } - - using Operation::operator(); + ~Remove_Placeholders() { } void operator()(Block*); void operator()(Ruleset*); diff --git a/src/to_c.hpp b/src/to_c.hpp index 6b00dcf926..cdc5546443 100644 --- a/src/to_c.hpp +++ b/src/to_c.hpp @@ -8,15 +8,13 @@ namespace Sass { class To_C : public Operation_CRTP { - // import all the class-specific methods and override as desired - using Operation::operator(); // override this to define a catch-all union Sass_Value* fallback_impl(AST_Node* n); public: To_C() { } - virtual ~To_C() { } + ~To_C() { } union Sass_Value* operator()(Boolean*); union Sass_Value* operator()(Number*); diff --git a/src/to_string.hpp b/src/to_string.hpp index b63f2a0931..3ee45e8b49 100644 --- a/src/to_string.hpp +++ b/src/to_string.hpp @@ -11,8 +11,7 @@ namespace Sass { class Null; class To_String : public Operation_CRTP { - // import all the class-specific methods and override as desired - using Operation::operator(); + // override this to define a catch-all std::string fallback_impl(AST_Node* n); @@ -23,7 +22,7 @@ namespace Sass { public: To_String(Context* ctx = 0, bool in_declaration = true, bool in_debug = false); - virtual ~To_String(); + ~To_String(); std::string operator()(Null* n); std::string operator()(String_Schema*); diff --git a/src/to_value.hpp b/src/to_value.hpp index c1a45069af..63af398e5a 100644 --- a/src/to_value.hpp +++ b/src/to_value.hpp @@ -21,7 +21,7 @@ namespace Sass { To_Value(Context& ctx, Memory_Manager& mem) : ctx(ctx), mem(mem) { } - virtual ~To_Value() { } + ~To_Value() { } using Operation::operator(); Value* operator()(Argument*); From 9c592e278b0a1171f1017e99e983133b8ee7f775 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 12 Dec 2015 18:24:40 +0100 Subject: [PATCH 02/17] Update behavior to match ruby sass 3.4.20 - omit last delimiter semicolon in compressed mode - shorten floats with leading zero in compressed mode --- src/ast.cpp | 13 +++++++++++-- src/emitter.cpp | 4 +++- src/emitter.hpp | 2 +- src/output.cpp | 3 ++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 82c769b49e..08db7dcb2a 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -2215,9 +2215,18 @@ namespace Sass { } // some final cosmetics - if (res == "-0.0") res.erase(0, 1); - else if (res == "-0") res.erase(0, 1); + if (res == "0.0") res = "0"; else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (compressed) + { + if (res == "-0.0") res = ".0"; + // check if handling negative nr + size_t off = res[0] == '-' ? 1 : 0; + // remove leading zero from floating point in compressed mode + if (res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + } + else if (res == "-0.0") res = "0.0"; // add unit now res += unit(); diff --git a/src/emitter.cpp b/src/emitter.cpp index 86b135aaa4..8b2e2a5897 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -58,9 +58,11 @@ namespace Sass { // MAIN BUFFER MANIPULATION // add outstanding delimiter - void Emitter::finalize(void) + void Emitter::finalize(bool final) { scheduled_space = 0; + if (output_style() == SASS_STYLE_COMPRESSED) + if (final) scheduled_delimiter = false; if (scheduled_linefeed) scheduled_linefeed = 1; flush_schedules(); diff --git a/src/emitter.hpp b/src/emitter.hpp index 5efa00509a..764044b181 100644 --- a/src/emitter.hpp +++ b/src/emitter.hpp @@ -59,7 +59,7 @@ namespace Sass { // flush scheduled space/linefeed Sass_Output_Style output_style(void); // add outstanding linefeed - void finalize(void); + void finalize(bool final = true); // flush scheduled space/linefeed void flush_schedules(void); // prepend some text or token to the buffer diff --git a/src/output.cpp b/src/output.cpp index eae11c4920..1c071d0e3d 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -61,7 +61,8 @@ namespace Sass { } // flush scheduled outputs - inspect.finalize(); + // maybe omit semicolon if possible + inspect.finalize(wbuf.buffer.size() == 0); // prepend buffer on top prepend_output(inspect.output()); // make sure we end with a linefeed From 47d1dc88ff7794d5c2bc36be8a820120c74fea38 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 14 Nov 2015 14:55:42 +0100 Subject: [PATCH 03/17] Refactor and streamline interpolation Probably needs some further code clean ups. --- src/ast.hpp | 12 ++- src/debugger.hpp | 6 ++ src/eval.cpp | 238 ++++++++++++++++++++++++++++------------------- src/eval.hpp | 4 +- src/expand.cpp | 5 +- src/parser.cpp | 8 +- src/util.cpp | 142 ---------------------------- src/util.hpp | 5 - 8 files changed, 171 insertions(+), 249 deletions(-) diff --git a/src/ast.hpp b/src/ast.hpp index 453adb5b92..ee6bec15d9 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -1439,15 +1439,23 @@ namespace Sass { // evaluation phase. /////////////////////////////////////////////////////////////////////// class String_Schema : public String, public Vectorized { - ADD_PROPERTY(bool, has_interpolants) + // ADD_PROPERTY(bool, has_interpolants) size_t hash_; public: String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false) - : String(pstate), Vectorized(size), has_interpolants_(has_interpolants), hash_(0) + : String(pstate), Vectorized(size), hash_(0) { concrete_type(STRING); } std::string type() { return "string"; } static std::string type_name() { return "string"; } + void has_interpolants(bool tc) { } + bool has_interpolants() { + for (auto el : elements()) { + if (el->is_interpolant()) return true; + } + return false; + } + virtual size_t hash() { if (hash_ == 0) { diff --git a/src/debugger.hpp b/src/debugger.hpp index 4b16b86562..64418a3a77 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -458,6 +458,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Variable* expression = dynamic_cast(node); std::cerr << ind << "Variable " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->name() << "]" << std::endl; std::string name(expression->name()); @@ -465,6 +466,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Function_Call_Schema* expression = dynamic_cast(node); std::cerr << ind << "Function_Call_Schema " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << "" << std::endl; debug_ast(expression->name(), ind + "name: ", env); @@ -472,6 +474,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Function_Call* expression = dynamic_cast(node); std::cerr << ind << "Function_Call " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->name() << "]"; if (expression->is_delayed()) std::cerr << " [delayed]"; @@ -515,12 +518,14 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Unary_Expression* expression = dynamic_cast(node); std::cerr << ind << "Unary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->type() << "]" << std::endl; debug_ast(expression->operand(), ind + " operand: ", env); } else if (dynamic_cast(node)) { Binary_Expression* expression = dynamic_cast(node); std::cerr << ind << "Binary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->type_name() << "]" << std::endl; @@ -529,6 +534,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Map* expression = dynamic_cast(node); std::cerr << ind << "Map " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [Hashed]" << std::endl; for (auto i : expression->elements()) { diff --git a/src/eval.cpp b/src/eval.cpp index 41ca4d805b..a47233300f 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -423,6 +423,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); *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { @@ -434,6 +435,7 @@ namespace Sass { } } + lm->is_interpolant(l->is_interpolant()); return lm->perform(this); } // check if we should expand it @@ -447,6 +449,7 @@ namespace Sass { 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; } @@ -638,6 +641,7 @@ namespace Sass { if (String_Constant* org = lstr ? lstr : rstr) { str->quote_mark(org->quote_mark()); } } + ex->is_interpolant(b->is_interpolant()); return ex; } @@ -700,9 +704,11 @@ namespace Sass { if (args->has_named_arguments()) { error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); } - return SASS_MEMORY_NEW(ctx.mem, String_Quoted, - c->pstate(), - lit->perform(&to_string)); + String_Quoted* str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, + c->pstate(), + lit->perform(&to_string)); + str->is_interpolant(c->is_interpolant()); + return str; } else { // call generic function full_name = "*[f]"; @@ -790,6 +796,7 @@ namespace Sass { 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; } @@ -846,6 +853,7 @@ namespace Sass { } // std::cerr << "\ttype is now: " << typeid(*value).name() << std::endl << std::endl; + value->is_interpolant(v->is_interpolant()); return value; } @@ -915,6 +923,7 @@ namespace Sass { } } break; } + result->is_interpolant(t->is_interpolant()); return result; } @@ -940,127 +949,160 @@ namespace Sass { } } - std::string Eval::interpolation(Expression* s, bool into_quotes) { - Env* env = environment(); - if (String_Quoted* str_quoted = dynamic_cast(s)) { - if (str_quoted->quote_mark()) { - if (str_quoted->quote_mark() == '*' || str_quoted->is_delayed()) { - return evacuate_escapes(str_quoted->value()); - } else { - return string_escape(quote(str_quoted->value(), str_quoted->quote_mark())); - } + void Eval::interpolation(Context& ctx, std::string& res, Expression* ex, bool into_quotes, bool was_itpl) { + int precision = (int)ctx.c_options->precision; + bool compressed = ctx.output_style() == SASS_STYLE_COMPRESSED; + bool needs_closing_brace = false; + +// std::cerr << "IN\n"; + 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 (Argument* arg = dynamic_cast(ex)) { + ex = arg->value(); + } + if (String_Constant* 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; } + + if (List* l = dynamic_cast(ex)) { + List* ll = SASS_MEMORY_NEW(ctx.mem, List, l->pstate(), 0, l->separator()); + 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); + } + To_String to_string(&ctx); + res += (ll->to_string(compressed, precision)); + ll->is_interpolant(l->is_interpolant()); + } + + else if (String_Quoted* val = dynamic_cast(ex)) { + To_String to_string(&ctx); + if (into_quotes && val->is_interpolant()) { + res += evacuate_escapes(val->to_string(compressed, precision)); + // res += evacuate_escapes(val ? val->perform(&to_string) : ""); } else { - return evacuate_escapes(str_quoted->value()); + res += val->to_string(compressed, precision); + // res += val ? val->perform(&to_string) : ""; } - } else if (String_Constant* str_constant = dynamic_cast(s)) { - if (into_quotes && !str_constant->is_interpolant()) return str_constant->value(); - return evacuate_escapes(str_constant->value()); - } else if (dynamic_cast(s)) { + } + else if (String_Constant* val = dynamic_cast(ex)) { To_String to_string(&ctx); - Expression* sel = s->perform(this); - return evacuate_quotes(sel ? sel->perform(&to_string) : ""); - - } else if (String_Schema* str_schema = dynamic_cast(s)) { - // To_String to_string(&ctx); - // return evacuate_quotes(str_schema->perform(&to_string)); - - std::string res = ""; - for(auto i : str_schema->elements()) - res += (interpolation(i)); - //ToDo: do this in one step - auto esc = evacuate_escapes(res); - auto unq = unquote(esc); - if (unq == esc) { - return string_to_output(res); + if (into_quotes && val->is_interpolant()) { + res += evacuate_escapes(val->to_string(compressed, precision)); + // res += evacuate_escapes(val ? val->perform(&to_string) : ""); } else { - return evacuate_quotes(unq); + val->quote_mark(0); + res += val->to_string(compressed, precision); + // res += val ? val->perform(&to_string) : ""; } - } else if (List* list = dynamic_cast(s)) { - std::string acc = ""; // ToDo: different output styles - std::string sep = list->separator() == SASS_COMMA ? "," : " "; - if (ctx.output_style() != SASS_STYLE_COMPRESSED && sep == ",") sep += " "; - bool initial = false; - for(auto item : list->elements()) { - if (item->concrete_type() != Expression::NULL_VAL) { - if (initial) acc += sep; - acc += interpolation(item); - initial = true; - } + } + else if (Value* val = dynamic_cast(ex)) { + To_String to_string(&ctx); + if (into_quotes && val->is_interpolant()) { + res += evacuate_escapes(val->to_string(compressed, precision)); + // res += evacuate_escapes(val ? val->perform(&to_string) : ""); + } else { + res += val->to_string(compressed, precision); + // res += val ? val->perform(&to_string) : ""; } - return evacuate_quotes(acc); - } else if (Variable* var = dynamic_cast(s)) { - std::string name(var->name()); - if (!env->has(name)) error("Undefined variable: \"" + var->name() + "\".", var->pstate()); - Expression* value = static_cast((*env)[name]); - return evacuate_quotes(interpolation(value)); - } else if (dynamic_cast(s)) { - Expression* ex = s->perform(this); - // avoid recursive calls if same object gets returned - // since we will call interpolate again for the result - if (ex == s) { - To_String to_string(&ctx); - return evacuate_quotes(s->perform(&to_string)); + } + else if (Textual* val = dynamic_cast(ex)) { + To_String to_string(&ctx); + if (into_quotes && val->is_interpolant()) { + + res += evacuate_escapes(val ? val->perform(&to_string) : ""); + } else { + res += val ? val->perform(&to_string) : ""; } - return evacuate_quotes(interpolation(ex)); - } else if (dynamic_cast(s)) { - Expression* ex = s->perform(this); - return evacuate_quotes(unquote(interpolation(ex))); - } else if (dynamic_cast(s)) { - Expression* ex = s->perform(this); - return evacuate_quotes(interpolation(ex)); - } else if (dynamic_cast(s)) { + } + else if (Binary_Expression* val = dynamic_cast(ex)) { To_String to_string(&ctx); - std::string dbg(s->perform(&to_string)); - error(dbg + " isn't a valid CSS value.", s->pstate()); - return dbg; - } else { + if (into_quotes && val->is_interpolant()) { + + res += evacuate_escapes(val ? val->perform(&to_string) : ""); + } else { + res += val ? val->perform(&to_string) : ""; + } + } + + else if (Parent_Selector* pr = dynamic_cast(ex)) { To_String to_string(&ctx); - return evacuate_quotes(s->perform(&to_string)); + Expression* sel = pr->perform(this); + if (into_quotes && sel->is_interpolant()) { + res += evacuate_escapes(sel ? sel->perform(&to_string) : ""); + } else { + res += sel ? sel->perform(&to_string) : ""; + } + } + else if (Selector_List* sl = dynamic_cast(ex)) { + + if (into_quotes) { + res += evacuate_escapes(sl->to_string(compressed, precision)); + } else { + res += sl->to_string(compressed, precision); + } + } + else if (dynamic_cast(ex)) { + throw std::runtime_error("fn not handlerd"); + } + else { + throw std::runtime_error(ex->type()); } + + if (needs_closing_brace) res += ")"; + +// std::cerr << "OUT\n"; } Expression* Eval::operator()(String_Schema* s) { - std::string acc; - bool into_quotes = false; 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) { - // really a very special fix, but this is the logic I got from - // analyzing the ruby sass behavior and it actually seems to work - // https://github.com/sass/libsass/issues/1333 - if (i == 0 && L > 1 && dynamic_cast((*s)[i])) { - Expression* ex = (*s)[i]->perform(this); - if (auto sq = dynamic_cast(ex)) { - if (sq->is_delayed() && ! s->has_interpolants()) { - acc += string_escape(quote(sq->value(), sq->quote_mark())); - } else { - acc += interpolation((*s)[i], into_quotes); - } - } else if (ex) { - acc += interpolation((*s)[i], into_quotes); - } - } else if ((*s)[i]) { - acc += interpolation((*s)[i], into_quotes); - } + (*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()); + } - String_Quoted* str = SASS_MEMORY_NEW(ctx.mem, String_Quoted, s->pstate(), acc); - if (!str->quote_mark()) { - str->value(string_unescape(str->value())); - } else if (str->quote_mark()) { - str->quote_mark('*'); + 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); } - str->is_delayed(true); + 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())) { @@ -1074,7 +1116,11 @@ namespace Sass { Expression* Eval::operator()(String_Quoted* s) { - return 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) diff --git a/src/eval.hpp b/src/eval.hpp index 4634eadff5..2065445af5 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -23,6 +23,8 @@ namespace Sass { Eval(Expand& exp); ~Eval(); + bool is_in_comment; + Env* environment(); Context& context(); Selector_List* selector(); @@ -94,7 +96,7 @@ namespace Sass { static Value* op_strings(Memory_Manager&, enum Sass_OP, Value&, Value&, bool compressed = false, int precision = 5, ParserState* pstate = 0); private: - std::string interpolation(Expression* s, bool into_quotes = false); + void interpolation(Context& ctx, std::string& res, Expression* ex, bool into_quotes, bool was_itpl = false); }; diff --git a/src/expand.cpp b/src/expand.cpp index cf3bfcbe5e..e87dbaabec 100644 --- a/src/expand.cpp +++ b/src/expand.cpp @@ -368,8 +368,11 @@ namespace Sass { Statement* Expand::operator()(Comment* c) { + eval.is_in_comment = true; + auto rv = SASS_MEMORY_NEW(ctx.mem, Comment, c->pstate(), static_cast(c->text()->perform(&eval)), c->is_important()); + eval.is_in_comment = false; // TODO: eval the text, once we're parsing/storing it as a String_Schema - return SASS_MEMORY_NEW(ctx.mem, Comment, c->pstate(), static_cast(c->text()->perform(&eval)), c->is_important()); + return rv; } Statement* Expand::operator()(If* i) diff --git a/src/parser.cpp b/src/parser.cpp index 815f84ae30..216b60b919 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1430,6 +1430,7 @@ namespace Sass { } String_Schema* schema = SASS_MEMORY_NEW(ctx.mem, String_Schema, pstate); + schema->is_interpolant(true); while (i < chunk.end) { p = constant ? find_first_in_interval< exactly >(i, chunk.end) : find_first_in_interval< exactly, block_comment >(i, chunk.end); @@ -1578,11 +1579,14 @@ namespace Sass { if (peek< exactly< rbrace > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } + Expression* ex = 0; if (lex< re_static_expression >()) { - (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); + ex = SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); } else { - (*schema) << parse_list(); + ex = parse_list(); } + ex->is_interpolant(true); + (*schema) << ex; // ToDo: no error check here? lex < exactly < rbrace > >(); } diff --git a/src/util.cpp b/src/util.cpp index f32c324590..0160fd910a 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -105,94 +105,6 @@ namespace Sass { return *array = arr; } - std::string string_eval_escapes(const std::string& s) - { - - std::string out(""); - bool esc = false; - for (size_t i = 0, L = s.length(); i < L; ++i) { - if(s[i] == '\\' && esc == false) { - esc = true; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; - - // hex string? - if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (cp == 0) cp = 0xFFFD; - - // assert invalid code points - if (cp >= 1) { - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we shoud - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; u[m] && m < 5; m++) out.push_back(u[m]); - - // skip some more chars? - i += len - 1; esc = false; - if (cp == 10) out += ' '; - - } - - } - - } - else { - out += s[i]; - esc = false; - } - } - return out; - - } - - // double escape every escape sequences - // escape unescaped quotes and backslashes - std::string string_escape(const std::string& str) - { - std::string out(""); - for (auto i : str) { - // escape some characters - if (i == '"') out += '\\'; - if (i == '\'') out += '\\'; - if (i == '\\') out += '\\'; - out += i; - } - return out; - } - - // unescape every escape sequence - // only removes unescaped backslashes - std::string string_unescape(const std::string& str) - { - std::string out(""); - bool esc = false; - for (auto i : str) { - if (esc || i != '\\') { - esc = false; - out += i; - } else { - esc = true; - } - } - // open escape sequence at end - // maybe it should thow an error - if (esc) { out += '\\'; } - return out; - } - // read css string (handle multiline DELIM) std::string read_css_string(const std::string& str) { @@ -216,28 +128,6 @@ namespace Sass { return out; } - // evacuate unescaped quoted - // leave everything else untouched - std::string evacuate_quotes(const std::string& str) - { - std::string out(""); - bool esc = false; - for (auto i : str) { - if (!esc) { - // ignore next character - if (i == '\\') esc = true; - // evacuate unescaped quotes - else if (i == '"') out += '\\'; - else if (i == '\'') out += '\\'; - } - // get escaped char now - else { esc = false; } - // remove nothing - out += i; - } - return out; - } - // double escape all escape sequences // keep unescaped quotes and backslashes std::string evacuate_escapes(const std::string& str) @@ -322,38 +212,6 @@ namespace Sass { else return text; } - std::string normalize_wspace(const std::string& str) - { - bool ws = false; - bool esc = false; - std::string text = ""; - for(const char& i : str) { - if (!esc && i == '\\') { - esc = true; - ws = false; - text += i; - } else if (esc) { - esc = false; - ws = false; - text += i; - } else if ( - i == ' ' || - i == '\r' || - i == '\n' || - i == ' ' - ) { - // only add one space - if (!ws) text += ' '; - ws = true; - } else { - ws = false; - text += i; - } - } - if (esc) text += '\\'; - return text; - } - // find best quote_mark by detecting if the string contains any single // or double quotes. When a single quote is found, we not we want a double // quote as quote_mark. Otherwise we check if the string cotains any double diff --git a/src/util.hpp b/src/util.hpp index 4325878201..5d67a7bf2b 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -17,15 +17,10 @@ namespace Sass { const char* safe_str(const char *, const char* = ""); void free_string_array(char **); char **copy_strings(const std::vector&, char ***, int = 0); - std::string string_escape(const std::string& str); - std::string string_unescape(const std::string& str); - std::string string_eval_escapes(const std::string& str); std::string read_css_string(const std::string& str); - std::string evacuate_quotes(const std::string& str); std::string evacuate_escapes(const std::string& str); std::string string_to_output(const std::string& str); std::string comment_to_string(const std::string& text); - std::string normalize_wspace(const std::string& str); std::string quote(const std::string&, char q = 0, bool keep_linefeed_whitespace = false); std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false); From 97d234139a92f6dd5d72b4592c0c4d8fca56c57a Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 12 Dec 2015 19:20:49 +0100 Subject: [PATCH 04/17] Fix issue with to_string and selector lists --- src/ast.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ast.cpp b/src/ast.cpp index 08db7dcb2a..0f15ccf0da 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -2260,9 +2260,10 @@ namespace Sass { std::string str(""); auto end = this->end(); auto start = this->begin(); + std::string sep(compressed ? "," : ", "); while (start < end && *start) { Complex_Selector* sel = *start; - if (!str.empty()) str += ", "; + if (!str.empty()) str += sep; str += sel->to_string(compressed, precision); ++ start; } From 9611017413de85ffc4b84c84f69d7ebca9ca9d32 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 15 Nov 2015 02:56:05 +0100 Subject: [PATCH 05/17] Fix Binary_Expression evaluation --- src/debugger.hpp | 4 +++ src/eval.cpp | 77 +++++++++++++++++++++++++++++++++--------------- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/debugger.hpp b/src/debugger.hpp index 64418a3a77..a4bdd68764 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -453,6 +453,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) else if (expression->type() == Textual::DIMENSION) std::cerr << " [DIMENSION]"; else if (expression->type() == Textual::HEX) std::cerr << " [HEX]"; std::cerr << expression << " [" << expression->value() << "]"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; if (expression->is_delayed()) std::cerr << " [delayed]"; std::cerr << std::endl; } else if (dynamic_cast(node)) { @@ -562,16 +563,19 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) Boolean* expression = dynamic_cast(node); std::cerr << ind << "Boolean " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << "]" << std::endl; } else if (dynamic_cast(node)) { Color* expression = dynamic_cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; } else if (dynamic_cast(node)) { Number* expression = dynamic_cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << expression->unit() << "]" << " [hash: " << expression->hash() << "] " << std::endl; diff --git a/src/eval.cpp b/src/eval.cpp index a47233300f..736ed19dbc 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -566,6 +566,8 @@ namespace Sass { int precision = (int)ctx.c_options->precision; bool compressed = ctx.output_style() == SASS_STYLE_COMPRESSED; + bool schema_op = false; + if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants())) { // If possible upgrade LHS to a number @@ -591,10 +593,22 @@ namespace Sass { 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)); - Expression::Concrete_Type l_type = lhs->concrete_type(); - Expression::Concrete_Type r_type = rhs->concrete_type(); + 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()) + { + schema_op = true; + if (op_type == Sass_OP::DIV) { + delay_anyway = true; + } + rhs = front->perform(this); + } + } - if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + if ((!schema_op || delay_anyway) && l_type == Expression::NUMBER && r_type == Expression::NUMBER) { std::string str(""); str += v_l->to_string(compressed, precision); if (b->op().ws_before) str += " "; @@ -605,44 +619,59 @@ namespace Sass { } } + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + // ToDo: throw error in op functions // ToDo: then catch and re-throw them ParserState pstate(b->pstate()); + Expression* rv = 0; if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { const Number* l_n = dynamic_cast(lhs); const Number* r_n = dynamic_cast(rhs); - return op_numbers(ctx.mem, op_type, *l_n, *r_n, compressed, precision, &pstate); + rv = op_numbers(ctx.mem, op_type, *l_n, *r_n, compressed, precision, &pstate); } - if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { + else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { const Number* l_n = dynamic_cast(lhs); const Color* r_c = dynamic_cast(rhs); - return op_number_color(ctx.mem, op_type, *l_n, *r_c, compressed, precision, &pstate); + rv = op_number_color(ctx.mem, op_type, *l_n, *r_c, compressed, precision, &pstate); } - if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { + else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { const Color* l_c = dynamic_cast(lhs); const Number* r_n = dynamic_cast(rhs); - return op_color_number(ctx.mem, op_type, *l_c, *r_n, compressed, precision, &pstate); + rv = op_color_number(ctx.mem, op_type, *l_c, *r_n, compressed, precision, &pstate); } - if (l_type == Expression::COLOR && r_type == Expression::COLOR) { + else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { const Color* l_c = dynamic_cast(lhs); const Color* r_c = dynamic_cast(rhs); - return op_colors(ctx.mem, op_type, *l_c, *r_c, compressed, precision, &pstate); + rv = op_colors(ctx.mem, op_type, *l_c, *r_c, compressed, precision, &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)); + Value* ex = op_strings(ctx.mem, op_type, *v_l, *v_r, compressed, precision, &pstate); + 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 (String_Constant* org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + } + ex->is_interpolant(b->is_interpolant()); + rv = ex; } - 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)); - Value* ex = op_strings(ctx.mem, op_type, *v_l, *v_r, compressed, precision, &pstate); - if (String_Constant* str = dynamic_cast(ex)) - { - if (str->concrete_type() != Expression::STRING) return ex; - String_Constant* lstr = dynamic_cast(lhs); - String_Constant* rstr = dynamic_cast(rhs); - if (String_Constant* org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } - } - ex->is_interpolant(b->is_interpolant()); - return ex; + if (rv) { + if (schema_op) { + (*s2)[0] = rv; + rv = s2->perform(this); + } + } + return rv; } From b184f91ceb6529b3bfe16cb30cbcaa05cb7e2543 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 12 Dec 2015 21:58:29 +0100 Subject: [PATCH 06/17] Fix the last discrepancies to sass 3.4.20 --- src/ast.cpp | 10 ++++++++-- src/constants.cpp | 1 + src/constants.hpp | 1 + src/emitter.cpp | 1 + src/eval.cpp | 5 +++++ src/parser.cpp | 7 +++++-- src/prelexer.cpp | 3 +++ src/prelexer.hpp | 1 + 8 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 0f15ccf0da..3573695c31 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -2218,15 +2218,14 @@ namespace Sass { if (res == "0.0") res = "0"; else if (res == "") res = "0"; else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; else if (compressed) { - if (res == "-0.0") res = ".0"; // check if handling negative nr size_t off = res[0] == '-' ? 1 : 0; // remove leading zero from floating point in compressed mode if (res[off] == '0' && res[off+1] == '.') res.erase(off, 1); } - else if (res == "-0.0") res = "0.0"; // add unit now res += unit(); @@ -2299,6 +2298,7 @@ namespace Sass { case ADJACENT_TO: str_op = "+"; break; case REFERENCE: str_op = "/" + str_ref + "/"; break; } + // prettify for non ancestors if (combinator() != ANCESTOR_OF) { // no spaces needed for compressed @@ -2312,6 +2312,12 @@ namespace Sass { else if (str_tail == "") { str_op = ""; // superflous } + else if (compressed) + { + if (str_tail[0] == '-') { + str_op = ""; // superflous + } + } // now build the final result return str_head + str_op + str_tail; } diff --git a/src/constants.cpp b/src/constants.cpp index 518324b4cb..ff03f24228 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -125,6 +125,7 @@ namespace Sass { extern const char rbrace[] = "}"; extern const char rparen[] = ")"; extern const char sign_chars[] = "-+"; + extern const char op_chars[] = "-+*/%"; extern const char hyphen[] = "-"; extern const char ellipsis[] = "..."; // extern const char url_space_chars[] = " \t\r\n\f"; diff --git a/src/constants.hpp b/src/constants.hpp index 55bc5bf2ee..21d416025d 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -126,6 +126,7 @@ namespace Sass { extern const char rbrace[]; extern const char rparen[]; extern const char sign_chars[]; + extern const char op_chars[]; extern const char hyphen[]; extern const char ellipsis[]; // extern const char url_space_chars[]; diff --git a/src/emitter.cpp b/src/emitter.cpp index 8b2e2a5897..4c8b70aaf9 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -109,6 +109,7 @@ namespace Sass { // append some text or token to the buffer void Emitter::append_string(const std::string& text) { + // write space/lf flush_schedules(); diff --git a/src/eval.cpp b/src/eval.cpp index 736ed19dbc..66ba0e43d4 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -521,6 +521,9 @@ namespace Sass { op_type == Sass_OP::LT || op_type == Sass_OP::LTE) { + lhs->is_expanded(false); + lhs->set_delayed(false); + lhs = lhs->perform(this); rhs->is_expanded(false); rhs->set_delayed(false); rhs = rhs->perform(this); @@ -1008,6 +1011,8 @@ namespace Sass { 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()); diff --git a/src/parser.cpp b/src/parser.cpp index 216b60b919..9b1441bcaa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1291,8 +1291,8 @@ namespace Sass { value->is_delayed(false); // 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); + // 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); Binary_Expression* lhs = static_cast(b->left()); @@ -1360,6 +1360,9 @@ namespace Sass { if (lex< kwd_important >()) { return SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, "!important"); } + if (lex< sequence < number, lookahead< exactly<'+'> > > >()) + { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::NUMBER, lexed); } + if (const char* stop = peek< value_schema >()) { return parse_value_schema(stop); } diff --git a/src/prelexer.cpp b/src/prelexer.cpp index d86df3f3d8..902d1b4a1f 100644 --- a/src/prelexer.cpp +++ b/src/prelexer.cpp @@ -540,6 +540,9 @@ namespace Sass { } // Match CSS numeric constants. + const char* op(const char* src) { + return class_char(src); + } const char* sign(const char* src) { return class_char(src); } diff --git a/src/prelexer.hpp b/src/prelexer.hpp index ae9af6e540..4db78ed05c 100644 --- a/src/prelexer.hpp +++ b/src/prelexer.hpp @@ -282,6 +282,7 @@ namespace Sass { // Match placeholder selectors. const char* placeholder(const char* src); // Match CSS numeric constants. + const char* op(const char* src); const char* sign(const char* src); const char* unsigned_number(const char* src); const char* number(const char* src); From b30d7d755f75a37edbf184f6e830c9fc3efcc6af Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 13 Dec 2015 12:29:09 +0100 Subject: [PATCH 07/17] Fix parser to split percentage from numbers --- src/constants.cpp | 2 +- src/parser.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/constants.cpp b/src/constants.cpp index ff03f24228..cffc519773 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -125,7 +125,7 @@ namespace Sass { extern const char rbrace[] = "}"; extern const char rparen[] = ")"; extern const char sign_chars[] = "-+"; - extern const char op_chars[] = "-+*/%"; + extern const char op_chars[] = "-+"; extern const char hyphen[] = "-"; extern const char ellipsis[] = "..."; // extern const char url_space_chars[] = " \t\r\n\f"; diff --git a/src/parser.cpp b/src/parser.cpp index 9b1441bcaa..33463f5867 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1360,7 +1360,11 @@ namespace Sass { if (lex< kwd_important >()) { return SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, "!important"); } - if (lex< sequence < number, lookahead< exactly<'+'> > > >()) + // parse `10%4px` into separated items and not a schema + if (lex< sequence < percentage, lookahead < number > > >()) + { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::PERCENTAGE, lexed); } + + if (lex< sequence < number, lookahead< sequence < op, number > > > >()) { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::NUMBER, lexed); } if (const char* stop = peek< value_schema >()) From e228a53c44b4ef0961123281cf328b32a81d2468 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 3 Jan 2016 20:50:51 +0100 Subject: [PATCH 08/17] Implement handling of operator precedences correctly Starting point to improve and refactor interpolations and binary expressions to the correct implementations.x --- src/debugger.hpp | 2 + src/eval.cpp | 137 ++++++++++++++++++++++++++++++++--------------- src/eval.hpp | 2 +- src/parser.cpp | 112 +++++++++++++++++++++++++++----------- src/parser.hpp | 2 +- src/prelexer.cpp | 1 - 6 files changed, 180 insertions(+), 76 deletions(-) diff --git a/src/debugger.hpp b/src/debugger.hpp index a4bdd68764..31a5d8fe86 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -528,6 +528,8 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "Binary_Expression " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [ws_before: " << expression->op().ws_before << "] "; + std::cerr << " [ws_after: " << expression->op().ws_after << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->type_name() << "]" << std::endl; debug_ast(expression->left(), ind + " left: ", env); diff --git a/src/eval.cpp b/src/eval.cpp index 66ba0e43d4..3c34408f36 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -488,31 +488,15 @@ namespace Sass { { 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()) return b; - b->is_delayed(false); - // if one of the operands is a '/' then make sure it's evaluated - Expression* lhs = b->left()->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); + if (op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b; } - switch (op_type) { - case Sass_OP::AND: - return *lhs ? b->right()->perform(this) : lhs; - break; + Expression* lhs = b->left(); + Expression* rhs = b->right(); - 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 - Expression* rhs = b->right()->perform(this); // maybe fully evaluate structure if (op_type == Sass_OP::EQ || op_type == Sass_OP::NEQ || @@ -521,12 +505,28 @@ 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); + } + } + 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 { @@ -534,6 +534,30 @@ namespace Sass { // 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)) { @@ -547,15 +571,16 @@ namespace Sass { } // see if it's a relational expression - 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) && !eq(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !lt(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs) || eq(lhs, rhs)); - - default: break; + if (!b->is_delayed()) { + 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) && !eq(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !lt(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs) || eq(lhs, rhs)); + default: break; + } } @@ -574,7 +599,8 @@ namespace Sass { if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants())) { // 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) { + 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(); @@ -611,7 +637,7 @@ namespace Sass { } } - if ((!schema_op || delay_anyway) && l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + if ((!schema_op || delay_anyway) && (l_type == Expression::NUMBER || r_type == Expression::NUMBER)) { std::string str(""); str += v_l->to_string(compressed, precision); if (b->op().ws_before) str += " "; @@ -653,7 +679,7 @@ namespace Sass { 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)); - Value* ex = op_strings(ctx.mem, op_type, *v_l, *v_r, compressed, precision, &pstate); + Value* ex = op_strings(ctx.mem, b->op(), *v_l, *v_r, compressed, precision, &pstate); if (String_Constant* str = dynamic_cast(ex)) { if (str->concrete_type() == Expression::STRING) @@ -775,7 +801,12 @@ namespace Sass { // if it's user-defined, eval the body if (body) result = body->perform(this); // if it's native, invoke the underlying CPP function - else result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace()); + else { + Sass_Output_Style style = ctx.c_options->output_style; + ctx.c_options->output_style = SASS_STYLE_COMPACT; + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace()); + ctx.c_options->output_style = style; + } if (!result) error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); exp.backtrace_stack.pop_back(); } @@ -886,7 +917,8 @@ namespace Sass { // std::cerr << "\ttype is now: " << typeid(*value).name() << std::endl << std::endl; value->is_interpolant(v->is_interpolant()); - return value; + value->is_expanded(false); + return value->perform(this); } Expression* Eval::operator()(Textual* t) @@ -1480,10 +1512,11 @@ namespace Sass { l.a()); } - Value* Eval::op_strings(Memory_Manager& mem, enum Sass_OP op, Value& lhs, Value& rhs, bool compressed, int precision, ParserState* pstate) + Value* Eval::op_strings(Memory_Manager& mem, Sass::Operand operand, Value& lhs, Value& rhs, bool compressed, int precision, ParserState* pstate) { 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); @@ -1521,12 +1554,21 @@ namespace Sass { const Color* c_r = name_to_color(rstr); return op_number_color(mem, op, *n_l, *c_r, compressed, precision); } - if (op == Sass_OP::MUL) error("invalid operands for multiplication", lhs.pstate()); - if (op == Sass_OP::MOD) error("invalid operands for modulo", lhs.pstate()); + + // if (op == Sass_OP::MUL) error("invalid operands for multiplication", lhs.pstate()); + // if (op == Sass_OP::MOD) error("invalid operands for modulo", lhs.pstate()); 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 (ltype == Expression::NULL_VAL) error("Invalid null operation: \"null plus "+quote(unquote(rstr), '"')+"\".", lhs.pstate()); @@ -1538,8 +1580,8 @@ namespace Sass { lhs.to_string() + sep + rhs.to_string()); } - if ( (ltype == Expression::STRING || sep == "") && - (sep != "/" || !rqstr || !rqstr->quote_mark()) + if ( (sep == "") /* && + (sep != "/" || !rqstr || !rqstr->quote_mark()) */ ) { char quote_mark = 0; std::string unq(unquote(lstr + sep + rstr, "e_mark, true)); @@ -1548,7 +1590,18 @@ namespace Sass { } return SASS_MEMORY_NEW(mem, String_Quoted, lhs.pstate(), lstr + sep + rstr); } - return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), (lstr) + sep + quote(rstr)); + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), lstr + sep + rstr); + } + + if (sep != "") { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + 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) diff --git a/src/eval.hpp b/src/eval.hpp index 2065445af5..08b09e36fc 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -93,7 +93,7 @@ namespace Sass { static Value* op_number_color(Memory_Manager&, enum Sass_OP, const Number&, const Color&, bool compressed = false, int precision = 5, ParserState* pstate = 0); static Value* op_color_number(Memory_Manager&, enum Sass_OP, const Color&, const Number&, bool compressed = false, int precision = 5, ParserState* pstate = 0); static Value* op_colors(Memory_Manager&, enum Sass_OP, const Color&, const Color&, bool compressed = false, int precision = 5, ParserState* pstate = 0); - static Value* op_strings(Memory_Manager&, enum Sass_OP, Value&, Value&, bool compressed = false, int precision = 5, ParserState* pstate = 0); + static Value* op_strings(Memory_Manager&, Sass::Operand, Value&, Value&, bool compressed = false, int precision = 5, ParserState* pstate = 0); private: void interpolation(Context& ctx, std::string& res, Expression* ex, bool into_quotes, bool was_itpl = false); diff --git a/src/parser.cpp b/src/parser.cpp index 33463f5867..7a47569170 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1188,34 +1188,38 @@ namespace Sass { { // parse the left hand side expression Expression* lhs = parse_expression(); + std::vector operands; + std::vector operators; // if it's a singleton, return it (don't wrap it) - if (!(peek< alternatives < + while (peek< alternatives < kwd_eq, kwd_neq, kwd_gte, kwd_gt, kwd_lte, kwd_lt - > >(position))) - { return lhs; } - // parse the operator - const char* left_ws = peek < css_comments >(); + > >(position)) + { + // is directly adjancent to expression? + bool left_ws = peek < css_comments >(); + // parse the operator + enum Sass_OP op + = lex() ? Sass_OP::EQ + : lex() ? Sass_OP::NEQ + : lex() ? Sass_OP::GTE + : lex() ? Sass_OP::LTE + : lex() ? Sass_OP::GT + : lex() ? Sass_OP::LT + // we checked the possibilites on top of fn + : Sass_OP::EQ; + // is directly adjancent to expression? + bool right_ws = peek < css_comments >(); + operators.push_back({ op, left_ws, right_ws }); + operands.push_back(parse_expression()); + left_ws = peek < css_comments >(); + } // parse the operator - enum Sass_OP op - = lex() ? Sass_OP::EQ - : lex() ? Sass_OP::NEQ - : lex() ? Sass_OP::GTE - : lex() ? Sass_OP::LTE - : lex() ? Sass_OP::GT - : lex() ? Sass_OP::LT - // we checked the possibilites on top of fn - : Sass_OP::EQ; - // parse the right hand side expression - const char* right_ws = peek < css_comments >(); - // parse the right hand side expression - Expression* rhs = parse_expression(); - // return binary expression with a left and a right hand side - return SASS_MEMORY_NEW(ctx.mem, Binary_Expression, lhs->pstate(), { op, left_ws != 0, right_ws != 0 }, lhs, rhs); + return fold_operands(lhs, operands, operators); } // parse_relation @@ -1254,8 +1258,6 @@ namespace Sass { { Expression* factor = parse_factor(); // if it's a singleton, return it (don't wrap it) - if (!peek_css< class_char< static_ops > >()) return factor; - // parse more factors and operators std::vector operands; // factors std::vector operators; // ops // lex operations to apply to lhs @@ -1288,15 +1290,14 @@ namespace Sass { // lex the expected closing parenthesis if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); // expression can be evaluated - value->is_delayed(false); + // make sure wrapped lists and division expressions are non-delayed within parentheses // 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); - Binary_Expression* lhs = static_cast(b->left()); - if (lhs && lhs->type() == Sass_OP::DIV) lhs->is_delayed(false); + if (b && b->type() == Sass_OP::DIV) b->is_delayed(false); } return value; } @@ -2480,19 +2481,68 @@ namespace Sass { return base; } - Expression* Parser::fold_operands(Expression* base, std::vector& operands, std::vector& ops) - { - for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); + Expression* Parser::fold_operands(Expression* base, std::vector& operands, std::vector& ops, size_t i) + { +// std::cerr << "in\n"; + + if (String_Schema* schema = dynamic_cast(base)) { + // return schema; + if (schema->has_interpolants()) { + if (i + 1 < operands.size() && ( + (ops[0].operand == Sass_OP::EQ) + || (ops[0].operand == Sass_OP::ADD) + || (ops[0].operand == Sass_OP::DIV) + || (ops[0].operand == Sass_OP::MUL) + || (ops[0].operand == Sass_OP::NEQ) + || (ops[0].operand == Sass_OP::LT) + || (ops[0].operand == Sass_OP::GT) + || (ops[0].operand == Sass_OP::LTE) + || (ops[0].operand == Sass_OP::GTE) + )) { + 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; + } + } + + for (size_t S = operands.size(); i < S; ++i) { + if (String_Schema* schema = dynamic_cast(operands[i])) { + if (schema->has_interpolants()) { + if (i + 1 < S) { +// std::cerr << "fold\n"; + 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); + // std::cerr << "out\n"; + 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); + // std::cerr << "out\n"; + return base; + } else { + base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + } else { + base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } Binary_Expression* b = static_cast(base); - if (ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { + if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { base->is_delayed(true); } - else { + else if (b) { b->left()->is_delayed(false); b->right()->is_delayed(false); } + } +// std::cerr << "out\n"; return base; } diff --git a/src/parser.hpp b/src/parser.hpp index 8bf573c9a2..dc2b9a2e61 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -300,7 +300,7 @@ namespace Sass { Lookahead lookahead_for_include(const char* start = 0); Expression* fold_operands(Expression* base, std::vector& operands, Operand op); - Expression* fold_operands(Expression* base, std::vector& operands, std::vector& ops); + Expression* fold_operands(Expression* base, std::vector& operands, std::vector& ops, size_t i = 0); void throw_syntax_error(std::string message, size_t ln = 0); void throw_read_error(std::string message, size_t ln = 0); diff --git a/src/prelexer.cpp b/src/prelexer.cpp index 902d1b4a1f..4844b582ad 100644 --- a/src/prelexer.cpp +++ b/src/prelexer.cpp @@ -205,7 +205,6 @@ namespace Sass { digits, identifier, quoted_string, - exactly<'+'>, exactly<'-'> > > From 5c8732181b79171f8fad5d03d37ad39747b253d7 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Thu, 7 Jan 2016 22:51:22 +0100 Subject: [PATCH 09/17] Add `inspect` function for Expressions Ruby sass often uses inspect for error reporting etc. We do have a inspect CRTP visitor class, but it requires a valid context which is not always available. At some point we should consolidate those two implementations. --- src/ast.cpp | 19 ++++++++++ src/ast.hpp | 28 ++++++++++++++- src/error_handling.cpp | 78 ++++++++++++++++++++++++++++++++++++++++++ src/parser.cpp | 4 +-- 4 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 3573695c31..0e10a40e0b 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1773,6 +1773,15 @@ namespace Sass { return false; } + bool String_Schema::has_left_interpolant(void) const + { + return length() && first()->is_interpolant(); + } + bool String_Schema::has_right_interpolant(void) const + { + return length() && last()->is_interpolant(); + } + bool String_Schema::operator== (const Expression& rhs) const { if (const String_Schema* r = dynamic_cast(&rhs)) { @@ -2235,11 +2244,21 @@ namespace Sass { } + std::string String_Quoted::inspect() const + { + return quote(value_, '*', true); + } + std::string String_Quoted::to_string(bool compressed, int precision) const { return quote_mark_ ? quote(value_, quote_mark_, true) : value_; } + std::string String_Constant::inspect() const + { + return quote(value_, '*', true); + } + std::string String_Constant::to_string(bool compressed, int precision) const { return quote_mark_ ? quote(value_, quote_mark_, true) : value_; diff --git a/src/ast.hpp b/src/ast.hpp index ee6bec15d9..fe03fa641a 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -145,6 +145,7 @@ namespace Sass { virtual bool is_false() { return false; } virtual bool operator== (const Expression& rhs) const { return false; } virtual void set_delayed(bool delayed) { is_delayed(delayed); } + virtual std::string inspect() const { return to_string(); } // defaults to to_string virtual std::string to_string(bool compressed = false, int precision = 5) const = 0; virtual size_t hash() { return 0; } }; @@ -924,6 +925,27 @@ namespace Sass { ATTACH_OPERATIONS() }; + inline static const std::string sass_op_to_name(enum Sass_OP op) { + switch (op) { + case AND: return "and"; break; + case OR: return "or"; break; + case EQ: return "eq"; break; + case NEQ: return "neq"; break; + case GT: return "gt"; break; + case GTE: return "gte"; break; + case LT: return "lt"; break; + case LTE: return "lte"; break; + case ADD: return "plus"; break; + case SUB: return "sub"; break; + case MUL: return "times"; break; + case DIV: return "div"; break; + case MOD: return "mod"; break; + // this is only used internally! + case NUM_OPS: return "[OPS]"; break; + default: return "invalid"; break; + } + } + ////////////////////////////////////////////////////////////////////////// // Binary expressions. Represents logical, relational, and arithmetic // operations. Templatized to avoid large switch statements and repetitive @@ -1448,7 +1470,9 @@ namespace Sass { std::string type() { return "string"; } static std::string type_name() { return "string"; } - void has_interpolants(bool tc) { } + bool has_left_interpolant(void) const; + bool has_right_interpolant(void) const; + // void has_interpolants(bool tc) { } bool has_interpolants() { for (auto el : elements()) { if (el->is_interpolant()) return true; @@ -1505,6 +1529,7 @@ namespace Sass { } virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection virtual std::string to_string(bool compressed = false, int precision = 5) const; // static char auto_quote() { return '*'; } @@ -1526,6 +1551,7 @@ namespace Sass { if (q && quote_mark_) quote_mark_ = q; } virtual bool operator==(const Expression& rhs) const; + virtual std::string inspect() const; // quotes are forced on inspection virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 01171c2834..58751b5471 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -50,6 +50,84 @@ namespace Sass { : Base(pstate, msg, import_stack) { } + UndefinedOperation::UndefinedOperation(const Expression* lhs, const Expression* rhs, const std::string& op) + : lhs(lhs), rhs(rhs), op(op) + { + msg = def_op_msg + ": \""; + if (const Value* l = dynamic_cast(lhs)) { + msg += l->inspect(); + } else if (lhs) { + msg += "[EXPRESSION]"; + } + msg += " " + op + " "; + if (const Value* r = dynamic_cast(rhs)) { + msg += r->inspect(); + } else if (rhs) { + msg += "[EXPRESSION]"; + } + msg += "\"."; + } + + InvalidNullOperation::InvalidNullOperation(const Expression* lhs, const Expression* rhs, const std::string& op) + : UndefinedOperation(lhs, rhs, op) + { + msg = def_op_null_msg + ": \""; + if (const Value* l = dynamic_cast(lhs)) { + msg += l->inspect(); + } else if (lhs) { + msg += "[EXPRESSION]"; + } + msg += " " + op + " "; + if (const Value* r = dynamic_cast(rhs)) { + msg += r->inspect(); + } else if (rhs) { + msg += "[EXPRESSION]"; + } + msg += "\"."; + } + + ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) + : lhs(lhs), rhs(rhs) + { + msg = "divided by 0"; + } + + IncompatibleUnits::IncompatibleUnits(const Number& lhs, const Number& rhs) + : lhs(lhs), rhs(rhs) + { + msg = "Incompatible units: '"; + msg += rhs.unit(); + msg += "' and '"; + msg += lhs.unit(); + msg += "'."; + } + + AlphaChannelsNotEqual::AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, const std::string& op) + : lhs(lhs), rhs(rhs), op(op) + { + msg = "Alpha channels must be equal: "; + if (const Value* l = dynamic_cast(lhs)) { + msg += l->to_string(); + } else if (lhs) { + msg += "[EXPRESSION]"; + } + msg += " " + op + " "; + if (const Value* r = dynamic_cast(rhs)) { + msg += r->to_string(); + } else if (rhs) { + msg += "[EXPRESSION]"; + } + msg += "."; + } + + + SassValueError::SassValueError(ParserState pstate, OperationError& err) + : Base(pstate, err.what()) + { + msg = err.what(); + prefix = err.errtype(); + } + } diff --git a/src/parser.cpp b/src/parser.cpp index 7a47569170..1a739946b5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -525,7 +525,7 @@ namespace Sass { Expression* interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); // set status on the list expression interpolant->is_interpolant(true); - schema->has_interpolants(true); + // schema->has_interpolants(true); // add to the string schema (*schema) << interpolant; // advance position @@ -1682,7 +1682,7 @@ namespace Sass { Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; - schema->has_interpolants(true); + // schema->has_interpolants(true); i = j; } else { From d94bea9d345548ae18419b059c417b10d88c7ba3 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Wed, 25 Nov 2015 03:08:08 +0100 Subject: [PATCH 10/17] Refactor evaluation of binary expressions Throw native C++ errors inside value operations Fix delayed binary evaluations (with interpolations) --- src/error_handling.cpp | 9 +-- src/error_handling.hpp | 75 +++++++++++++++++- src/eval.cpp | 167 +++++++++++++++++++++++++---------------- src/eval.hpp | 2 +- src/functions.cpp | 4 +- src/sass_context.cpp | 10 +-- src/sass_values.cpp | 8 +- 7 files changed, 188 insertions(+), 87 deletions(-) diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 58751b5471..0a44a84742 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -12,16 +12,11 @@ namespace Sass { namespace Exception { Base::Base(ParserState pstate, std::string msg, std::vector* import_stack) - : std::runtime_error(msg), - msg(msg), pstate(pstate), + : std::runtime_error(msg), msg(msg), + prefix("Error"), pstate(pstate), import_stack(import_stack) { } - const char* Base::what() const throw() - { - return msg.c_str(); - } - InvalidSass::InvalidSass(ParserState pstate, std::string msg) : Base(pstate, msg) { } diff --git a/src/error_handling.hpp b/src/error_handling.hpp index 0c2b1182db..0bb2fded03 100644 --- a/src/error_handling.hpp +++ b/src/error_handling.hpp @@ -12,17 +12,21 @@ namespace Sass { namespace Exception { - const std::string def_msg = "Invalid sass"; + const std::string def_msg = "Invalid sass detected"; + const std::string def_op_msg = "Undefined operation"; + const std::string def_op_null_msg = "Invalid null operation"; class Base : public std::runtime_error { protected: std::string msg; + std::string prefix; public: ParserState pstate; std::vector* import_stack; public: Base(ParserState pstate, std::string msg = def_msg, std::vector* import_stack = 0); - virtual const char* what() const throw(); + virtual const char* errtype() const { return prefix.c_str(); } + virtual const char* what() const throw() { return msg.c_str(); } virtual ~Base() throw() {}; }; @@ -58,6 +62,73 @@ namespace Sass { virtual ~InvalidSyntax() throw() {}; }; + /* common virtual base class (has no pstate) */ + class OperationError : public std::runtime_error { + protected: + std::string msg; + public: + OperationError(std::string msg = def_op_msg) + : std::runtime_error(msg), msg(msg) + {}; + public: + virtual const char* errtype() const { return "Error"; } + virtual const char* what() const throw() { return msg.c_str(); } + virtual ~OperationError() throw() {}; + }; + + class ZeroDivisionError : public OperationError { + protected: + const Expression& lhs; + const Expression& rhs; + public: + ZeroDivisionError(const Expression& lhs, const Expression& rhs); + virtual const char* errtype() const { return "ZeroDivisionError"; } + virtual ~ZeroDivisionError() throw() {}; + }; + + class IncompatibleUnits : public OperationError { + protected: + const Number& lhs; + const Number& rhs; + public: + IncompatibleUnits(const Number& lhs, const Number& rhs); + virtual ~IncompatibleUnits() throw() {}; + }; + + class UndefinedOperation : public OperationError { + protected: + const Expression* lhs; + const Expression* rhs; + const std::string op; + public: + UndefinedOperation(const Expression* lhs, const Expression* rhs, const std::string& op); + // virtual const char* errtype() const { return "Error"; } + virtual ~UndefinedOperation() throw() {}; + }; + + class InvalidNullOperation : public UndefinedOperation { + public: + InvalidNullOperation(const Expression* lhs, const Expression* rhs, const std::string& op); + virtual ~InvalidNullOperation() throw() {}; + }; + + class AlphaChannelsNotEqual : public OperationError { + protected: + const Expression* lhs; + const Expression* rhs; + const std::string op; + public: + AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, const std::string& op); + // virtual const char* errtype() const { return "Error"; } + virtual ~AlphaChannelsNotEqual() throw() {}; + }; + + class SassValueError : public Base { + public: + SassValueError(ParserState pstate, OperationError& err); + virtual ~SassValueError() throw() {}; + }; + } void warn(std::string msg, ParserState pstate); diff --git a/src/eval.cpp b/src/eval.cpp index 3c34408f36..e6e9dcce6b 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -497,6 +497,22 @@ namespace Sass { 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->has_right_interpolant()) { + b->is_delayed(true); + delay_lhs = true; + } + } + if (String_Schema* schema = dynamic_cast(rhs)) { + if (schema->has_left_interpolant()) { + b->is_delayed(true); + delay_rhs = true; + } + } + // maybe fully evaluate structure if (op_type == Sass_OP::EQ || op_type == Sass_OP::NEQ || @@ -505,6 +521,7 @@ 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); @@ -534,6 +551,8 @@ namespace Sass { // rhs = rhs->perform(this); } + // debug_ast(b); + // if one of the operands is a '/' then make sure it's evaluated lhs = lhs->perform(this); lhs->is_delayed(false); @@ -570,19 +589,6 @@ namespace Sass { } } - // see if it's a relational expression - if (!b->is_delayed()) { - 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) && !eq(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), !lt(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(ctx.mem, Boolean, b->pstate(), lt(lhs, rhs) || eq(lhs, rhs)); - default: break; - } - } - Expression::Concrete_Type l_type = lhs->concrete_type(); Expression::Concrete_Type r_type = rhs->concrete_type(); @@ -637,7 +643,9 @@ namespace Sass { } } - if ((!schema_op || delay_anyway) && (l_type == Expression::NUMBER || r_type == Expression::NUMBER)) { + // if (b->is_delayed()) delay_anyway = true; + + if ((!schema_op || delay_anyway)) { std::string str(""); str += v_l->to_string(compressed, precision); if (b->op().ws_before) str += " "; @@ -648,50 +656,75 @@ namespace Sass { } } + // 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 - ParserState pstate(b->pstate()); Expression* rv = 0; - 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, compressed, precision, &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, compressed, precision, &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, compressed, precision, &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, compressed, precision, &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)); - Value* ex = op_strings(ctx.mem, b->op(), *v_l, *v_r, compressed, precision, &pstate); - if (String_Constant* str = dynamic_cast(ex)) - { - if (str->concrete_type() == Expression::STRING) + 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, compressed, precision, &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, compressed, precision, &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, compressed, precision, &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, compressed, precision, &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)); + Value* ex = op_strings(ctx.mem, b->op(), *v_l, *v_r, compressed, precision, &pstate); + if (String_Constant* str = dynamic_cast(ex)) { - String_Constant* lstr = dynamic_cast(lhs); - String_Constant* rstr = dynamic_cast(rhs); - if (String_Constant* org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } + if (str->concrete_type() == Expression::STRING) + { + String_Constant* lstr = dynamic_cast(lhs); + String_Constant* rstr = dynamic_cast(rhs); + if (String_Constant* org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } } + ex->is_interpolant(b->is_interpolant()); + rv = ex; } - 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) { @@ -1390,12 +1423,12 @@ namespace Sass { return lhs && rhs && *lhs == *rhs; } - bool Eval::lt(Expression* lhs, Expression* rhs) + bool Eval::lt(Expression* lhs, Expression* rhs, std::string op) { Number* l = dynamic_cast(lhs); Number* r = dynamic_cast(rhs); - if (!l) error("may only compare numbers", lhs->pstate()); - if (!r) error("may only compare numbers", rhs->pstate()); + // use compare operator from ast node + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); // use compare operator from ast node return *l < *r; } @@ -1404,11 +1437,11 @@ namespace Sass { { double lv = l.value(); double rv = r.value(); - if (op == Sass_OP::DIV && !rv) { - return SASS_MEMORY_NEW(mem, String_Quoted, pstate ? *pstate : l.pstate(), "Infinity"); + if (op == Sass_OP::DIV && rv == 0) { + return SASS_MEMORY_NEW(mem, String_Quoted, pstate ? *pstate : l.pstate(), lv ? "Infinity" : "NaN"); } if (op == Sass_OP::MOD && !rv) { - error("division by zero", pstate ? *pstate : r.pstate()); + throw Exception::ZeroDivisionError(l, r); } Number tmp(r); @@ -1418,7 +1451,7 @@ namespace Sass { std::string r_unit(tmp.unit()); if (l_unit != r_unit && !l_unit.empty() && !r_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB)) { - error("Incompatible units: '"+r_unit+"' and '"+l_unit+"'.", pstate ? *pstate : r.pstate()); + throw Exception::IncompatibleUnits(l, r); } Number* v = SASS_MEMORY_NEW(mem, Number, l); v->pstate(pstate ? *pstate : l.pstate()); @@ -1476,7 +1509,7 @@ namespace Sass { + color); } break; case Sass_OP::MOD: { - error("cannot divide a number by a color", pstate ? *pstate : r.pstate()); + throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); } break; default: break; // caller should ensure that we don't get here } @@ -1487,7 +1520,10 @@ namespace Sass { Value* Eval::op_color_number(Memory_Manager& mem, enum Sass_OP op, const Color& l, const Number& r, bool compressed, int precision, ParserState* pstate) { double rv = r.value(); - if (op == Sass_OP::DIV && !rv) error("division by zero", pstate ? *pstate : r.pstate()); + 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), @@ -1499,10 +1535,11 @@ namespace Sass { Value* Eval::op_colors(Memory_Manager& mem, enum Sass_OP op, const Color& l, const Color& r, bool compressed, int precision, ParserState* pstate) { if (l.a() != r.a()) { - error("alpha channels must be equal when combining colors", pstate ? *pstate : r.pstate()); + throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); } if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) { - error("division by zero", pstate ? *pstate : r.pstate()); + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(l, r); } return SASS_MEMORY_NEW(mem, Color, pstate ? *pstate : l.pstate(), @@ -1554,9 +1591,10 @@ namespace Sass { const Color* c_r = name_to_color(rstr); return op_number_color(mem, op, *n_l, *c_r, compressed, precision); } - - // if (op == Sass_OP::MUL) error("invalid operands for multiplication", lhs.pstate()); - // if (op == Sass_OP::MOD) error("invalid operands for modulo", lhs.pstate()); + 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; @@ -1571,9 +1609,6 @@ namespace Sass { case Sass_OP::GTE: sep = ">="; break; default: break; } - if (ltype == Expression::NULL_VAL) error("Invalid null operation: \"null plus "+quote(unquote(rstr), '"')+"\".", lhs.pstate()); - if (rtype == Expression::NULL_VAL) error("Invalid null operation: \""+quote(unquote(lstr), '"')+" plus null\".", rhs.pstate()); - if (ltype == Expression::NUMBER && sep == "/" && rtype == Expression::STRING) { return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), diff --git a/src/eval.hpp b/src/eval.hpp index 08b09e36fc..1031ed72fc 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -87,7 +87,7 @@ namespace Sass { // -- only need to define two comparisons, and the rest can be implemented in terms of them static bool eq(Expression*, Expression*); - static bool lt(Expression*, Expression*); + static bool lt(Expression*, Expression*, std::string op); // -- arithmetic on the combinations that matter static Value* op_numbers(Memory_Manager&, enum Sass_OP, const Number&, const Number&, bool compressed = false, int precision = 5, ParserState* pstate = 0); static Value* op_number_color(Memory_Manager&, enum Sass_OP, const Number&, const Color&, bool compressed = false, int precision = 5, ParserState* pstate = 0); diff --git a/src/functions.cpp b/src/functions.cpp index eb82b86644..64a2f1cea0 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -1137,7 +1137,7 @@ namespace Sass { error("\"" + val->perform(&to_string) + "\" is not a number for `min'", pstate); } if (least) { - if (Eval::lt(xi, least)) least = xi; + if (*xi < *least) least = xi; } else least = xi; } return least; @@ -1156,7 +1156,7 @@ namespace Sass { error("\"" + val->perform(&to_string) + "\" is not a number for `max'", pstate); } if (greatest) { - if (Eval::lt(greatest, xi)) greatest = xi; + if (*greatest < *xi) greatest = xi; } else greatest = xi; } return greatest; diff --git a/src/sass_context.cpp b/src/sass_context.cpp index 6befdbe9a7..26527fb932 100644 --- a/src/sass_context.cpp +++ b/src/sass_context.cpp @@ -44,9 +44,9 @@ extern "C" { std::stringstream msg_stream; std::string cwd(Sass::File::get_cwd()); - std::string msg_prefix("Error: "); + std::string msg_prefix(e.errtype()); bool got_newline = false; - msg_stream << msg_prefix; + msg_stream << msg_prefix << ": "; const char* msg = e.what(); while(msg && *msg) { if (*msg == '\r') { @@ -54,7 +54,7 @@ extern "C" { } else if (*msg == '\n') { got_newline = true; } else if (got_newline) { - msg_stream << std::string(msg_prefix.size(), ' '); + msg_stream << std::string(msg_prefix.size() + 2, ' '); got_newline = false; } msg_stream << *msg; @@ -65,13 +65,13 @@ extern "C" { for (size_t i = 1; i < e.import_stack->size() - 1; ++i) { std::string path((*e.import_stack)[i]->imp_path); std::string rel_path(Sass::File::abs2rel(path, cwd, cwd)); - msg_stream << std::string(msg_prefix.size(), ' '); + msg_stream << std::string(msg_prefix.size() + 2, ' '); msg_stream << (i == 1 ? " on line " : " from line "); msg_stream << e.pstate.line+1 << " of " << rel_path << "\n"; } } else { std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); - msg_stream << std::string(msg_prefix.size(), ' '); + msg_stream << std::string(msg_prefix.size() + 2, ' '); msg_stream << " on line " << e.pstate.line+1 << " of " << rel_path << "\n"; } diff --git a/src/sass_values.cpp b/src/sass_values.cpp index 4ce2d601e6..be30615b37 100644 --- a/src/sass_values.cpp +++ b/src/sass_values.cpp @@ -296,10 +296,10 @@ extern "C" { switch(op) { case Sass_OP::EQ: return sass_make_boolean(Eval::eq(lhs, rhs)); case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(lhs, rhs)); - case Sass_OP::GT: return sass_make_boolean(!Eval::lt(lhs, rhs) && !Eval::eq(lhs, rhs)); - case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(lhs, rhs)); - case Sass_OP::LT: return sass_make_boolean(Eval::lt(lhs, rhs)); - case Sass_OP::LTE: return sass_make_boolean(Eval::lt(lhs, rhs) || Eval::eq(lhs, rhs)); + case Sass_OP::GT: return sass_make_boolean(!Eval::lt(lhs, rhs, "gt") && !Eval::eq(lhs, rhs)); + case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(lhs, rhs, "gte")); + case Sass_OP::LT: return sass_make_boolean(Eval::lt(lhs, rhs, "lt")); + case Sass_OP::LTE: return sass_make_boolean(Eval::lt(lhs, rhs, "lte") || Eval::eq(lhs, rhs)); default: break; } From d7bdd186bef00d3fb1e47e2075d4755aafc71f93 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Thu, 7 Jan 2016 23:08:43 +0100 Subject: [PATCH 11/17] Improve evaluation of binary expressions Also fixes a bunch of little internal issues --- src/ast.cpp | 17 ++++-- src/ast.hpp | 14 ++++- src/context.cpp | 1 - src/debugger.hpp | 10 +++- src/error_handling.cpp | 4 +- src/eval.cpp | 122 +++++++++++++++++++++++++++-------------- src/eval.hpp | 2 +- src/inspect.cpp | 13 ++++- src/parser.cpp | 37 ++++++++++--- 9 files changed, 156 insertions(+), 64 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 0e10a40e0b..a7b2ad4fff 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1773,13 +1773,13 @@ namespace Sass { return false; } - bool String_Schema::has_left_interpolant(void) const + bool String_Schema::is_left_interpolant(void) const { - return length() && first()->is_interpolant(); + return length() && first()->is_left_interpolant(); } - bool String_Schema::has_right_interpolant(void) const + bool String_Schema::is_right_interpolant(void) const { - return length() && last()->is_interpolant(); + return length() && last()->is_right_interpolant(); } bool String_Schema::operator== (const Expression& rhs) const @@ -1939,6 +1939,15 @@ namespace Sass { return value()->to_string(compressed, precision); } + bool Binary_Expression::is_left_interpolant(void) const + { + return is_interpolant() || (left() && left()->is_left_interpolant()); + } + bool Binary_Expression::is_right_interpolant(void) const + { + return is_interpolant() || (right() && right()->is_right_interpolant()); + } + std::string Binary_Expression::to_string(bool compressed, int precision) const { std::string str(""); diff --git a/src/ast.hpp b/src/ast.hpp index fe03fa641a..261f1b3825 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -145,6 +145,9 @@ namespace Sass { virtual bool is_false() { return false; } virtual bool operator== (const Expression& rhs) const { return false; } virtual void set_delayed(bool delayed) { is_delayed(delayed); } + virtual bool has_interpolant() const { return is_interpolant(); } + virtual bool is_left_interpolant() const { return is_interpolant(); } + virtual bool is_right_interpolant() const { return is_interpolant(); } virtual std::string inspect() const { return to_string(); } // defaults to to_string virtual std::string to_string(bool compressed = false, int precision = 5) const = 0; virtual size_t hash() { return 0; } @@ -1002,6 +1005,13 @@ namespace Sass { default: return "invalid"; break; } } + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; + bool has_interpolant() const + { + return is_left_interpolant() || + is_right_interpolant(); + } virtual void set_delayed(bool delayed) { right()->set_delayed(delayed); @@ -1470,8 +1480,8 @@ namespace Sass { std::string type() { return "string"; } static std::string type_name() { return "string"; } - bool has_left_interpolant(void) const; - bool has_right_interpolant(void) const; + bool is_left_interpolant(void) const; + bool is_right_interpolant(void) const; // void has_interpolants(bool tc) { } bool has_interpolants() { for (auto el : elements()) { diff --git a/src/context.cpp b/src/context.cpp index d9600ecdcc..15e59df210 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -650,7 +650,6 @@ namespace Sass { Expand expand(*this, &global, &backtrace); Cssize cssize(*this, &backtrace); // expand and eval the tree - // debug_ast(root); root = root->perform(&expand)->block(); // merge and bubble certain rules root = root->perform(&cssize)->block(); diff --git a/src/debugger.hpp b/src/debugger.hpp index 31a5d8fe86..50ccfd2557 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -526,7 +526,9 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) } else if (dynamic_cast(node)) { Binary_Expression* expression = dynamic_cast(node); std::cerr << ind << "Binary_Expression " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + if (expression->is_interpolant()) std::cerr << " [is interpolant] "; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; std::cerr << " [delayed: " << expression->is_delayed() << "] "; std::cerr << " [ws_before: " << expression->op().ws_before << "] "; std::cerr << " [ws_after: " << expression->op().ws_after << "] "; @@ -608,8 +610,10 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "String_Schema " << expression; std::cerr << " " << expression->concrete_type(); if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->has_interpolants()) std::cerr << " [has_interpolants]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->is_interpolant()) std::cerr << " [is interpolant]"; + if (expression->has_interpolant()) std::cerr << " [has interpolant]"; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); } } else if (dynamic_cast(node)) { diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 0a44a84742..ec222db68f 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -50,13 +50,13 @@ namespace Sass { { msg = def_op_msg + ": \""; if (const Value* l = dynamic_cast(lhs)) { - msg += l->inspect(); + msg += l->to_string(); } else if (lhs) { msg += "[EXPRESSION]"; } msg += " " + op + " "; if (const Value* r = dynamic_cast(rhs)) { - msg += r->inspect(); + msg += r->to_string(); } else if (rhs) { msg += "[EXPRESSION]"; } diff --git a/src/eval.cpp b/src/eval.cpp index e6e9dcce6b..88ce51e0a5 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -486,7 +486,48 @@ namespace Sass { 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; + } + + // if we have a schema on the left side, we also return a string + 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)); @@ -494,22 +535,23 @@ namespace Sass { return b; } + // b->is_delayed(false); Expression* lhs = b->left(); Expression* rhs = b->right(); - bool delay_lhs = false; - bool delay_rhs = false; + // bool delay_lhs = false; + // bool delay_rhs = false; if (String_Schema* schema = dynamic_cast(lhs)) { - if (schema->has_right_interpolant()) { + if (schema->is_right_interpolant()) { b->is_delayed(true); - delay_lhs = true; + // delay_lhs = true; } } if (String_Schema* schema = dynamic_cast(rhs)) { - if (schema->has_left_interpolant()) { + if (schema->is_left_interpolant()) { b->is_delayed(true); - delay_rhs = true; + // delay_rhs = true; } } @@ -551,8 +593,6 @@ namespace Sass { // rhs = rhs->perform(this); } - // debug_ast(b); - // if one of the operands is a '/' then make sure it's evaluated lhs = lhs->perform(this); lhs->is_delayed(false); @@ -596,13 +636,20 @@ namespace Sass { // 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()); int precision = (int)ctx.c_options->precision; bool compressed = ctx.output_style() == SASS_STYLE_COMPRESSED; bool schema_op = false; - if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants())) + 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 || @@ -610,7 +657,7 @@ namespace Sass { if (String_Constant* str = dynamic_cast(lhs)) { std::string value(str->value()); const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::number >(start) != 0) { + 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); } @@ -636,23 +683,20 @@ namespace Sass { if (front && !front->is_interpolant()) { schema_op = true; - if (op_type == Sass_OP::DIV) { - delay_anyway = true; - } rhs = front->perform(this); } } - // if (b->is_delayed()) delay_anyway = true; - - if ((!schema_op || delay_anyway)) { + if (force_delay) { std::string str(""); str += v_l->to_string(compressed, precision); if (b->op().ws_before) str += " "; str += b->separator(); if (b->op().ws_after) str += " "; str += v_r->to_string(compressed, precision); - return SASS_MEMORY_NEW(ctx.mem, String_Constant, lhs->pstate(), str); + String_Constant* val = SASS_MEMORY_NEW(ctx.mem, String_Constant, lhs->pstate(), str); + val->is_interpolant(b->left()->has_interpolant()); + return val; } } @@ -706,15 +750,22 @@ namespace Sass { 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)); - Value* ex = op_strings(ctx.mem, b->op(), *v_l, *v_r, compressed, precision, &pstate); + 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, compressed, precision, &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 (String_Constant* org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } + 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()); @@ -832,14 +883,9 @@ namespace Sass { Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); exp.backtrace_stack.push_back(&here); // if it's user-defined, eval the body - if (body) result = body->perform(this); + if (body) { result = body->perform(this); } // if it's native, invoke the underlying CPP function - else { - Sass_Output_Style style = ctx.c_options->output_style; - ctx.c_options->output_style = SASS_STYLE_COMPACT; - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace()); - ctx.c_options->output_style = style; - } + else { 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(); } @@ -914,7 +960,6 @@ namespace Sass { Env* env = environment(); if (env->has(name)) value = static_cast((*env)[name]); else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); - // std::cerr << "name: " << v->name() << "; type: " << typeid(*value).name() << "; value: " << value->perform(&to_string) << std::endl; if (typeid(*value) == typeid(Argument)) value = static_cast(value)->value(); // behave according to as ruby sass (add leading zero) @@ -948,7 +993,6 @@ namespace Sass { value = value->perform(this); // ->perform(&listize); } - // std::cerr << "\ttype is now: " << typeid(*value).name() << std::endl << std::endl; value->is_interpolant(v->is_interpolant()); value->is_expanded(false); return value->perform(this); @@ -1051,7 +1095,6 @@ namespace Sass { bool compressed = ctx.output_style() == SASS_STYLE_COMPRESSED; bool needs_closing_brace = false; -// std::cerr << "IN\n"; if (Arguments* args = dynamic_cast(ex)) { List* ll = SASS_MEMORY_NEW(ctx.mem, List, args->pstate(), 0, SASS_COMMA); for(auto arg : *args) { @@ -1164,7 +1207,6 @@ namespace Sass { if (needs_closing_brace) res += ")"; -// std::cerr << "OUT\n"; } Expression* Eval::operator()(String_Schema* s) @@ -1549,7 +1591,7 @@ namespace Sass { l.a()); } - Value* Eval::op_strings(Memory_Manager& mem, Sass::Operand operand, Value& lhs, Value& rhs, bool compressed, int precision, ParserState* pstate) + Value* Eval::op_strings(Memory_Manager& mem, Sass::Operand operand, Value& lhs, Value& rhs, bool compressed, int precision, ParserState* pstate, bool delayed) { Expression::Concrete_Type ltype = lhs.concrete_type(); Expression::Concrete_Type rtype = rhs.concrete_type(); @@ -1609,11 +1651,6 @@ namespace Sass { case Sass_OP::GTE: sep = ">="; break; default: break; } - if (ltype == Expression::NUMBER && sep == "/" && rtype == Expression::STRING) - { - return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), - lhs.to_string() + sep + rhs.to_string()); - } if ( (sep == "") /* && (sep != "/" || !rqstr || !rqstr->quote_mark()) */ @@ -1626,16 +1663,17 @@ namespace Sass { 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); } - if (sep != "") { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - return SASS_MEMORY_NEW(mem, String_Constant, lhs.pstate(), (lstr) + sep + (rstr)); } diff --git a/src/eval.hpp b/src/eval.hpp index 1031ed72fc..614bac99df 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -93,7 +93,7 @@ namespace Sass { static Value* op_number_color(Memory_Manager&, enum Sass_OP, const Number&, const Color&, bool compressed = false, int precision = 5, ParserState* pstate = 0); static Value* op_color_number(Memory_Manager&, enum Sass_OP, const Color&, const Number&, bool compressed = false, int precision = 5, ParserState* pstate = 0); static Value* op_colors(Memory_Manager&, enum Sass_OP, const Color&, const Color&, bool compressed = false, int precision = 5, ParserState* pstate = 0); - static Value* op_strings(Memory_Manager&, Sass::Operand, Value&, Value&, bool compressed = false, int precision = 5, ParserState* pstate = 0); + static Value* op_strings(Memory_Manager&, Sass::Operand, Value&, Value&, bool compressed = false, int precision = 5, ParserState* pstate = 0, bool interpolant = false); private: void interpolation(Context& ctx, std::string& res, Expression* ex, bool into_quotes, bool was_itpl = false); diff --git a/src/inspect.cpp b/src/inspect.cpp index 0b183a0202..628731468f 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -413,7 +413,12 @@ namespace Sass { expr->left()->perform(this); if ( in_media_block || ( expr->op().ws_before - && !expr->is_delayed() + && (!expr->is_interpolant()) + && (!expr->is_delayed() || + expr->is_left_interpolant() || + expr->is_right_interpolant() + ) + )) append_string(" "); switch (expr->type()) { case Sass_OP::AND: append_string("&&"); break; @@ -433,7 +438,11 @@ namespace Sass { } if ( in_media_block || ( expr->op().ws_after - && !expr->is_delayed() + && (!expr->is_interpolant()) + && (!expr->is_delayed() + || 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 1a739946b5..7821b63359 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1230,21 +1230,35 @@ namespace Sass { // parse addition and subtraction operations Expression* Parser::parse_expression() { + // parses multiple add and subtract operations + // NOTE: make sure that identifiers starting with + // NOTE: dashes do NOT count as subtract operation Expression* lhs = parse_operators(); // if it's a singleton, return it (don't wrap it) + // if it's a singleton, return it (don't wrap it) if (!(peek_css< exactly<'+'> >(position) || // condition is a bit misterious, but some combinations should not be counted as operations (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || - peek< identifier >(position)) + peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) { return lhs; } std::vector operands; std::vector operators; - const char* left_ws = peek < css_comments >(); - while (lex_css< exactly<'+'> >() || lex< sequence< negate< digit >, exactly<'-'> > >()) { - const char* right_ws = peek < css_comments >(); - operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws != 0, right_ws != 0 }); + bool left_ws = peek < css_comments >(); + while ( + lex_css< exactly<'+'> >() || + + ( + ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) + && lex_css< sequence< negate< digit >, exactly<'-'> > >() + ) + + ) { + + + bool right_ws = peek < css_comments >(); + operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); operands.push_back(parse_operators()); left_ws = peek < css_comments >(); } @@ -1297,10 +1311,14 @@ namespace Sass { // 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->is_delayed(false); + if (b && b->type() == Sass_OP::DIV) b->set_delayed(false); } return value; } + // string may be interpolated + // if (lex< quoted_string >()) { + // return parse_string(); + // } else if (peek< ie_property >()) { return parse_ie_property(); } @@ -1368,6 +1386,10 @@ namespace Sass { if (lex< sequence < number, lookahead< sequence < op, number > > > >()) { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::NUMBER, lexed); } + // string may be interpolated + if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) + { return parse_string(); } + if (const char* stop = peek< value_schema >()) { return parse_value_schema(stop); } @@ -1612,6 +1634,7 @@ namespace Sass { if (*position == '"' || *position == '\'' || alpha(position)) { (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, " "); } + if (peek < exactly < '-' > >()) return schema; } // lex (normalized) variable else if (lex< variable >()) { @@ -2473,7 +2496,7 @@ namespace Sass { if (op.operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { base->is_delayed(true); } - else { + else if (b && b->op().operand != Sass_OP::DIV) { b->left()->is_delayed(false); b->right()->is_delayed(false); } From 0c8f94cd0d4d0613e7a377a47f5d9896d9d71f77 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Fri, 8 Jan 2016 18:26:13 +0100 Subject: [PATCH 12/17] Fix multiple parser issues --- src/ast.cpp | 15 +++++++++++++- src/parser.cpp | 21 +++++++++++++------ src/prelexer.cpp | 54 ++++++++++++++++++++++++++++++++++++++++-------- src/prelexer.hpp | 1 + 4 files changed, 75 insertions(+), 16 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index a7b2ad4fff..7afda89f30 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1734,6 +1734,12 @@ namespace Sass { bool Number::operator== (const Expression& rhs) const { if (const Number* r = dynamic_cast(&rhs)) { + size_t lhs_units = numerator_units_.size() + denominator_units_.size(); + size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return std::fabs(value() - r->value()) < NUMBER_EPSILON; + } return (numerator_units_ == r->numerator_units_) && (denominator_units_ == r->denominator_units_) && std::fabs(value() - r->value()) < NUMBER_EPSILON; @@ -1743,11 +1749,18 @@ namespace Sass { bool Number::operator< (const Number& rhs) const { + size_t lhs_units = numerator_units_.size() + denominator_units_.size(); + size_t rhs_units = rhs.numerator_units_.size() + rhs.denominator_units_.size(); + // unitless and only having one unit seems equivalent (will change in future) + if (!lhs_units || !rhs_units) { + return value() < rhs.value(); + } + Number tmp_r(rhs); tmp_r.normalize(find_convertible_unit()); std::string l_unit(unit()); std::string r_unit(tmp_r.unit()); - if (!l_unit.empty() && !r_unit.empty() && unit() != tmp_r.unit()) { + if (unit() != tmp_r.unit()) { error("cannot compare numbers with incompatible units", pstate()); } return value() < tmp_r.value(); diff --git a/src/parser.cpp b/src/parser.cpp index 7821b63359..ae4fb9431a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1421,7 +1421,8 @@ namespace Sass { { return SASS_MEMORY_NEW(ctx.mem, String_Quoted, pstate, lexed); } // also handle the 10em- foo special case - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, negate< digit > > > > >()) + // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < exactly < '.' >, space > > > > > >()) { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::DIMENSION, lexed); } if (lex< sequence< static_component, one_plus< strict_identifier > > >()) @@ -1595,9 +1596,14 @@ namespace Sass { const char* e = 0; size_t num_items = 0; + bool need_space = false; while (position < stop) { // parse space between tokens if (lex< spaces >() && num_items) { + need_space = true; + } + if (need_space) { + need_space = false; (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, " "); } if ((e = peek< re_functional >()) && e < stop) { @@ -1622,17 +1628,20 @@ namespace Sass { } // lex some string constants or other valid token // Note: [-+] chars are left over from ie. `#{3}+3` - else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' >, identifier > >()) { + else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { + (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); + } + else if (lex< sequence < identifier > >()) { (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); - if (*position == '"' || *position == '\'') { - (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, " "); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + need_space = true; } } // lex a quoted string else if (lex< quoted_string >()) { (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Quoted, pstate, lexed, '"'); - if (*position == '"' || *position == '\'' || alpha(position)) { - (*schema) << SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, " "); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + need_space = true; } if (peek < exactly < '-' > >()) return schema; } diff --git a/src/prelexer.cpp b/src/prelexer.cpp index 4844b582ad..f50519ddd8 100644 --- a/src/prelexer.cpp +++ b/src/prelexer.cpp @@ -278,18 +278,54 @@ namespace Sass { >(src); } - const char* value_schema(const char* src) { - // follows this pattern: ([xyz]*i[xyz]*)+ - return one_plus< sequence< zero_plus< alternatives< identifier, percentage, dimension, hex, number, quoted_string > >, - interpolant, - zero_plus< alternatives< identifier, percentage, dimension, hex, number, quoted_string, exactly<'%'> > > > >(src); + const char* sass_value(const char* src) { + return alternatives < + quoted_string, + identifier, + percentage, + hex, + dimension, + number + >(src); } - /* not used anymore - remove? - const char* filename(const char* src) { - return one_plus< alternatives< identifier, number, exactly<'.'> > >(src); + // this is basically `one_plus < sass_value >` + // takes care to not parse invalid combinations + const char* value_combinations(const char* src) { + // `2px-2px` is invalid combo + bool was_number = false; + const char* pos = src; + while (src) { + if (pos = alternatives < quoted_string, identifier, percentage, hex >(src)) { + was_number = false; + src = pos; + } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { + was_number = true; + src = pos; + } else { + break; + } + } + return src; + } + + // must be at least one interpolant + // can be surrounded by sass values + // make sure to never parse (dim)(dim) + // since this wrongly consumes `2px-1px` + // `2px1px` is valid number (unit `px1px`) + const char* value_schema(const char* src) + { + return sequence < + one_plus < + sequence < + optional < value_combinations >, + interpolant, + optional < value_combinations > + > + > + >(src); } - */ // Match CSS '@' keywords. const char* at_keyword(const char* src) { diff --git a/src/prelexer.hpp b/src/prelexer.hpp index 4db78ed05c..e7156f3723 100644 --- a/src/prelexer.hpp +++ b/src/prelexer.hpp @@ -210,6 +210,7 @@ namespace Sass { // Match interpolant schemas const char* identifier_schema(const char* src); const char* value_schema(const char* src); + const char* sass_value(const char* src); // const char* filename(const char* src); // const char* filename_schema(const char* src); // const char* url_schema(const char* src); From 25f43c57e98bd6d51866292fdc418f0445c10415 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Fri, 8 Jan 2016 23:49:57 +0100 Subject: [PATCH 13/17] Fix error reporting within token parsing --- src/parser.cpp | 51 ++++++++++++++++++++++++-------------------------- src/parser.hpp | 8 ++++---- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index ae4fb9431a..c402d6d077 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -20,33 +20,33 @@ namespace Sass { using namespace Constants; using namespace Prelexer; - Parser Parser::from_c_str(const char* str, Context& ctx, ParserState pstate) + Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source) { Parser p(ctx, pstate); - p.source = str; - p.position = p.source; - p.end = str + strlen(str); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = p.position + strlen(p.position); Block* root = SASS_MEMORY_NEW(ctx.mem, Block, pstate); p.block_stack.push_back(root); root->is_root(true); return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate) + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, const char* source) { Parser p(ctx, pstate); - p.source = beg; - p.position = p.source; - p.end = end; + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = end ? end : p.position + strlen(p.position); Block* root = SASS_MEMORY_NEW(ctx.mem, Block, pstate); p.block_stack.push_back(root); root->is_root(true); return p; } - Selector_List* Parser::parse_selector(const char* src, Context& ctx, ParserState pstate) + Selector_List* Parser::parse_selector(const char* beg, Context& ctx, ParserState pstate, const char* source) { - Parser p = Parser::from_c_str(src, ctx, pstate); + Parser p = Parser::from_c_str(beg, ctx, pstate, source); // ToDo: ruby sass errors on parent references // ToDo: remap the source-map entries somehow return p.parse_selector_list(false); @@ -58,12 +58,12 @@ namespace Sass { && ! peek_css>(start); } - Parser Parser::from_token(Token t, Context& ctx, ParserState pstate) + Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source) { Parser p(ctx, pstate); - p.source = t.begin; - p.position = p.source; - p.end = t.end; + p.source = source ? source : t.begin; + p.position = t.begin ? t.begin : p.source; + p.end = t.end ? t.end : p.position + strlen(p.position); Block* root = SASS_MEMORY_NEW(ctx.mem, Block, pstate); p.block_stack.push_back(root); root->is_root(true); @@ -1438,7 +1438,7 @@ namespace Sass { if (lex< sequence< exactly<'%'>, optional< percentage > > >()) { return SASS_MEMORY_NEW(ctx.mem, String_Constant, pstate, lexed); } - error("error reading values after " + lexed.to_string(), pstate); + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); // unreachable statement return 0; @@ -1478,7 +1478,7 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace if (j) { --j; // parse the interpolant and accumulate it - Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list(); + Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; i = j; @@ -1548,7 +1548,7 @@ namespace Sass { const char* j = skip_over_scopes< exactly, exactly >(p+2, str.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).parse_list(); + Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; i = j; @@ -1711,7 +1711,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).parse_list(); + Expression* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); interp_node->is_interpolant(true); (*schema) << interp_node; // schema->has_interpolants(true); @@ -2515,7 +2515,6 @@ namespace Sass { Expression* Parser::fold_operands(Expression* base, std::vector& operands, std::vector& ops, size_t i) { -// std::cerr << "in\n"; if (String_Schema* schema = dynamic_cast(base)) { // return schema; @@ -2545,18 +2544,15 @@ namespace Sass { if (String_Schema* schema = dynamic_cast(operands[i])) { if (schema->has_interpolants()) { if (i + 1 < S) { -// std::cerr << "fold\n"; 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); - // std::cerr << "out\n"; 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); - // std::cerr << "out\n"; return base; } else { base = SASS_MEMORY_NEW(ctx.mem, Binary_Expression, base->pstate(), ops[i], base, operands[i]); @@ -2574,7 +2570,6 @@ namespace Sass { } } -// std::cerr << "out\n"; return base; } @@ -2598,7 +2593,8 @@ namespace Sass { const char* end_left(last_pos + 1); while (pos_left > source) { if (end_left - pos_left >= max_len) { - ellipsis_left = true; + ellipsis_left = *(pos_left-1) != '\n' && + *(pos_left-1) != '\r'; break; } @@ -2614,16 +2610,17 @@ namespace Sass { bool ellipsis_right = false; const char* end_right(pos); const char* pos_right(pos); - while (end_right <= end) { + while (*end_right != 0) { if (end_right - pos_right > max_len) { - ellipsis_right = true; + ellipsis_left = *(pos_right) != '\n' && + *(pos_right) != '\r'; break; } if (*end_right == '\r') break; if (*end_right == '\n') break; ++ end_right; } - if (end_right > end) end_right = end; + // if (*end_right == 0) end_right ++; std::string left(pos_left, end_left); std::string right(pos_right, end_right); diff --git a/src/parser.hpp b/src/parser.hpp index dc2b9a2e61..f99f717652 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -47,11 +47,11 @@ namespace Sass { { in_at_root = false; stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]")); - static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]")); + static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); // special static parsers to convert strings into certain selectors - static Selector_List* parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]")); + static Selector_List* parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); #ifdef __clang__ From 907f482b83cdbf614a5dd26c1fdbf9e37dfd343b Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 9 Jan 2016 02:57:43 +0100 Subject: [PATCH 14/17] Change leading zero fraction compression logic --- src/ast.cpp | 2 +- src/eval.cpp | 12 ++++++++---- src/parser.cpp | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ast.cpp b/src/ast.cpp index 7afda89f30..117ce60018 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -2255,7 +2255,7 @@ namespace Sass { // check if handling negative nr size_t off = res[0] == '-' ? 1 : 0; // remove leading zero from floating point in compressed mode - if (res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + if (zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); } // add unit now diff --git a/src/eval.cpp b/src/eval.cpp index 88ce51e0a5..0d8a2c42fe 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -497,7 +497,7 @@ namespace Sass { return b; } - // if we have a schema on the left side, we also return a string + // 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()); @@ -1002,8 +1002,12 @@ namespace Sass { { using Prelexer::number; Expression* result = 0; - bool zero = !( t->value().substr(0, 1) == "." || - t->value().substr(0, 2) == "-." ); + 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"); @@ -1026,7 +1030,7 @@ namespace Sass { t->pstate(), sass_atof(num.c_str()), "%", - zero); + true); break; case Textual::DIMENSION: result = SASS_MEMORY_NEW(ctx.mem, Number, diff --git a/src/parser.cpp b/src/parser.cpp index c402d6d077..a6f88baada 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1422,7 +1422,7 @@ namespace Sass { // also handle the 10em- foo special case // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < exactly < '.' >, space > > > > > >()) + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) { return SASS_MEMORY_NEW(ctx.mem, Textual, pstate, Textual::DIMENSION, lexed); } if (lex< sequence< static_component, one_plus< strict_identifier > > >()) From b8da8c2f85629d06e6525077ff587c49ac25919f Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 9 Jan 2016 18:22:34 +0100 Subject: [PATCH 15/17] Fix potential out of bound substr access --- src/parser.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index a6f88baada..46c8c4e911 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2624,8 +2624,10 @@ namespace Sass { std::string left(pos_left, end_left); std::string right(pos_right, end_right); - if (ellipsis_left) left = ellipsis + left.substr(left.size() - 15); - if (ellipsis_right) right = right.substr(right.size() - 15) + ellipsis; + size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; + size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; + if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); + if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; // now pass new message to the more generic error function error(msg + prefix + quote(left) + middle + quote(right), pstate); } From 67641d0a0e4802f3092cd0212e54299ac341008e Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 9 Jan 2016 18:33:03 +0100 Subject: [PATCH 16/17] Fix binomial parsing in wrapped selectors --- src/parser.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index 46c8c4e911..d918ae92c4 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2306,9 +2306,10 @@ namespace Sass { quoted_string, interpolant, identifier, + variable, percentage, + binomial, dimension, - variable, alnum > > >, From 6d4f9a216c68ed9d1c93855a3864206936cfd937 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 9 Jan 2016 19:31:26 +0100 Subject: [PATCH 17/17] improve parsing of special functions --- src/constants.cpp | 1 + src/constants.hpp | 1 + src/eval.cpp | 7 +++---- src/prelexer.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++++- src/prelexer.hpp | 4 ++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/constants.cpp b/src/constants.cpp index cffc519773..f52eda44f7 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -87,6 +87,7 @@ namespace Sass { extern const char odd_kwd[] = "odd"; extern const char progid_kwd[] = "progid"; extern const char expression_kwd[] = "expression"; + extern const char calc_fn_kwd[] = "calc"; extern const char calc_kwd[] = "calc("; extern const char moz_calc_kwd[] = "-moz-calc("; extern const char webkit_calc_kwd[] = "-webkit-calc("; diff --git a/src/constants.hpp b/src/constants.hpp index 21d416025d..99a410de86 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -89,6 +89,7 @@ namespace Sass { extern const char progid_kwd[]; extern const char expression_kwd[]; extern const char calc_kwd[]; + extern const char calc_fn_kwd[]; extern const char moz_calc_kwd[]; extern const char webkit_calc_kwd[]; extern const char ms_calc_kwd[]; diff --git a/src/eval.cpp b/src/eval.cpp index 0d8a2c42fe..fa42c42650 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -882,10 +882,9 @@ namespace Sass { 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); - // if it's user-defined, eval the body - if (body) { result = body->perform(this); } - // if it's native, invoke the underlying CPP function - else { result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace()); } + // 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(); } diff --git a/src/prelexer.cpp b/src/prelexer.cpp index f50519ddd8..79c16519be 100644 --- a/src/prelexer.cpp +++ b/src/prelexer.cpp @@ -296,7 +296,7 @@ namespace Sass { bool was_number = false; const char* pos = src; while (src) { - if (pos = alternatives < quoted_string, identifier, percentage, hex >(src)) { + if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { was_number = false; src = pos; } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { @@ -1129,6 +1129,41 @@ namespace Sass { return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); } + // lexer special_fn: these functions cannot be overloaded + // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) + const char* re_special_fun(const char* src) { + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < + alternatives < + alpha, + exactly <'+'>, + exactly <'-'> + > + > + > + >, + alternatives < + exactly < calc_fn_kwd >, + exactly < expression_kwd >, + sequence < + sequence < + exactly < progid_kwd >, + exactly <':'> + >, + zero_plus < + alternatives < + char_range <'a', 'z'>, + exactly <'.'> + > + > + > + > + >(src); + } + template const char* padded_token(const char* src) { @@ -1159,5 +1194,13 @@ namespace Sass { return pos; } + template + const char* char_range(const char* src) + { + if (*src < min) return 0; + if (*src > max) return 0; + return src + 1; + } + } } diff --git a/src/prelexer.hpp b/src/prelexer.hpp index e7156f3723..d22ccac170 100644 --- a/src/prelexer.hpp +++ b/src/prelexer.hpp @@ -253,6 +253,7 @@ namespace Sass { const char* re_nothing(const char* src); const char* re_type_selector2(const char* src); + const char* re_special_fun(const char* src); const char* kwd_warn(const char* src); const char* kwd_err(const char* src); @@ -423,6 +424,9 @@ namespace Sass { template const char* minmax_range(const char* src); + template + const char* char_range(const char* src); + } }