diff --git a/package.json b/package.json index 9d177e3cc..4f0d45758 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.8.2", - "libsass": "3.5.1", + "libsass": "3.5.2", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -65,7 +65,7 @@ "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.9.2", + "nan": "2.9.2", "node-gyp": "^3.3.1", "npmlog": "^4.0.0", "request": "~2.79.0", diff --git a/src/libsass.gyp b/src/libsass.gyp index 0fd857d81..add96e89a 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -13,6 +13,7 @@ 'sources': [ 'libsass/src/ast.cpp', 'libsass/src/ast_fwd_decl.cpp', + 'libsass/src/backtrace.cpp', 'libsass/src/base64vlq.cpp', 'libsass/src/bind.cpp', 'libsass/src/cencode.c', @@ -35,6 +36,8 @@ 'libsass/src/listize.cpp', 'libsass/src/memory/SharedPtr.cpp', 'libsass/src/node.cpp', + 'libsass/src/operators.cpp', + 'libsass/src/operators.hpp', 'libsass/src/output.cpp', 'libsass/src/parser.cpp', 'libsass/src/plugins.cpp', diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf index a8954edea..5ba968b68 100644 --- a/src/libsass/Makefile.conf +++ b/src/libsass/Makefile.conf @@ -41,6 +41,8 @@ SOURCES = \ sass_context.cpp \ sass_functions.cpp \ sass2scss.cpp \ + backtrace.cpp \ + operators.cpp \ to_c.cpp \ to_value.cpp \ source_map.cpp \ diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp index 269e122b9..c3b38efb9 100644 --- a/src/libsass/src/ast.cpp +++ b/src/libsass/src/ast.cpp @@ -837,7 +837,7 @@ namespace Sass { return lhs_list->is_superselector_of(rhs_list); } } - error("is_superselector expected a Selector_List", sub->pstate()); + coreError("is_superselector expected a Selector_List", sub->pstate()); return false; } @@ -1171,7 +1171,7 @@ namespace Sass { // check if we need to append some headers // then we need to check for the combinator // only then we can safely set the new tail - void Complex_Selector::append(Complex_Selector_Obj ss) + void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) { Complex_Selector_Obj t = ss->tail(); @@ -1185,7 +1185,8 @@ namespace Sass { // append old headers if (h && h->length()) { if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - error("Invalid parent selector", pstate_); + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(this, traces, ss); } else if (last()->head_ && last()->head_->length()) { Compound_Selector_Obj rh = last()->head(); size_t i; @@ -1258,21 +1259,21 @@ namespace Sass { return list; } - Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) { if (!this->has_parent_ref()) return this; Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); Selector_List_Ptr ps = pstack.back(); for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, implicit_parent); + Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); ss->concat(rv); } } return ss; } - Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, bool implicit_parent) + Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) { Complex_Selector_Obj tail = this->tail(); Compound_Selector_Obj head = this->head(); @@ -1285,7 +1286,7 @@ namespace Sass { } // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, implicit_parent) : 0; + Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; if (head && head->length() > 0) { @@ -1331,7 +1332,7 @@ namespace Sass { // keep old parser state s->pstate(pstate()); // append new tail - s->append(ss); + s->append(ss, traces); retval->append(s); } } @@ -1346,7 +1347,8 @@ namespace Sass { // this is only if valid if the parent has no trailing op // otherwise we cannot append more simple selectors to head if (parent->last()->combinator() != ANCESTOR_OF) { - throw Exception::InvalidParent(parent, ss); + traces.push_back(Backtrace(pstate())); + throw Exception::InvalidParent(parent, traces, ss); } ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); @@ -1369,7 +1371,7 @@ namespace Sass { // keep old parser state s->pstate(pstate()); // append new tail - s->append(ss); + s->append(ss, traces); retval->append(s); } } @@ -1406,7 +1408,7 @@ namespace Sass { for (Simple_Selector_Obj ss : head->elements()) { if (Wrapped_Selector_Ptr ws = Cast(ss)) { if (Selector_List_Ptr sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(pstack, implicit_parent)); + if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); } } } @@ -1690,7 +1692,7 @@ namespace Sass { } if (!pIter->head() || pIter->tail()) { - error("nested selectors may not be extended", c->pstate()); + coreError("nested selectors may not be extended", c->pstate()); } compound_sel->is_optional(extendee->is_optional()); @@ -1766,31 +1768,31 @@ namespace Sass { { if (!a->name().empty()) { if (has_keyword_argument()) { - error("named arguments must precede variable-length argument", a->pstate()); + coreError("named arguments must precede variable-length argument", a->pstate()); } has_named_arguments(true); } else if (a->is_rest_argument()) { if (has_rest_argument()) { - error("functions and mixins may only be called with one variable-length argument", a->pstate()); + coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); } if (has_keyword_argument_) { - error("only keyword arguments may follow variable arguments", a->pstate()); + coreError("only keyword arguments may follow variable arguments", a->pstate()); } has_rest_argument(true); } else if (a->is_keyword_argument()) { if (has_keyword_argument()) { - error("functions and mixins may only be called with one keyword argument", a->pstate()); + coreError("functions and mixins may only be called with one keyword argument", a->pstate()); } has_keyword_argument(true); } else { if (has_rest_argument()) { - error("ordinal arguments must precede variable-length arguments", a->pstate()); + coreError("ordinal arguments must precede variable-length arguments", a->pstate()); } if (has_named_arguments()) { - error("ordinal arguments must precede named arguments", a->pstate()); + coreError("ordinal arguments must precede named arguments", a->pstate()); } } } @@ -1907,6 +1909,7 @@ namespace Sass { l.normalize(); r.normalize(); Units &lhs_unit = l, &rhs_unit = r; if (!(lhs_unit == rhs_unit)) { + /* ToDo: do we always get usefull backtraces? */ throw Exception::IncompatibleUnits(rhs, *this); } return lhs_unit < rhs_unit || diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp index 8bd2e6af3..a2be8685c 100644 --- a/src/libsass/src/ast.hpp +++ b/src/libsass/src/ast.hpp @@ -572,13 +572,15 @@ namespace Sass { // Trace. ///////////////// class Trace : public Has_Block { + ADD_CONSTREF(char, type) ADD_CONSTREF(std::string, name) public: - Trace(ParserState pstate, std::string n, Block_Obj b = 0) - : Has_Block(pstate, b), name_(n) + Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm') + : Has_Block(pstate, b), type_(type), name_(n) { } Trace(const Trace* ptr) : Has_Block(ptr), + type_(ptr->type_), name_(ptr->name_) { } ATTACH_AST_OPERATIONS(Trace) @@ -943,7 +945,7 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////////// struct Backtrace; typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); class Definition : public Has_Block { public: enum Type { MIXIN, FUNCTION }; @@ -1184,6 +1186,27 @@ namespace Sass { } } + inline static const std::string sass_op_separator(enum Sass_OP op) { + switch (op) { + case AND: return "&&"; + case OR: return "||"; + case EQ: return "=="; + case NEQ: return "!="; + case GT: return ">"; + case GTE: return ">="; + case LT: return "<"; + case LTE: return "<="; + case ADD: return "+"; + case SUB: return "-"; + case MUL: return "*"; + case DIV: return "/"; + case MOD: return "%"; + // this is only used internally! + case NUM_OPS: return "[OPS]"; + default: return "invalid"; + } + } + ////////////////////////////////////////////////////////////////////////// // Binary expressions. Represents logical, relational, and arithmetic // operations. Templatized to avoid large switch statements and repetitive @@ -1208,44 +1231,10 @@ namespace Sass { hash_(ptr->hash_) { } const std::string type_name() { - switch (optype()) { - case AND: return "and"; - case OR: return "or"; - case EQ: return "eq"; - case NEQ: return "neq"; - case GT: return "gt"; - case GTE: return "gte"; - case LT: return "lt"; - case LTE: return "lte"; - case ADD: return "add"; - case SUB: return "sub"; - case MUL: return "mul"; - case DIV: return "div"; - case MOD: return "mod"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } + return sass_op_to_name(optype()); } const std::string separator() { - switch (optype()) { - case AND: return "&&"; - case OR: return "||"; - case EQ: return "=="; - case NEQ: return "!="; - case GT: return ">"; - case GTE: return ">="; - case LT: return "<"; - case LTE: return "<="; - case ADD: return "+"; - case SUB: return "-"; - case MUL: return "*"; - case DIV: return "/"; - case MOD: return "%"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } + return sass_op_separator(optype()); } bool is_left_interpolant(void) const; bool is_right_interpolant(void) const; @@ -1360,7 +1349,7 @@ namespace Sass { : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) { if (!name_.empty() && is_rest_argument_) { - error("variable-length argument may not be passed by name", pstate_); + coreError("variable-length argument may not be passed by name", pstate_); } } Argument(const Argument* ptr) @@ -1372,7 +1361,7 @@ namespace Sass { hash_(ptr->hash_) { if (!name_.empty() && is_rest_argument_) { - error("variable-length argument may not be passed by name", pstate_); + coreError("variable-length argument may not be passed by name", pstate_); } } @@ -1779,15 +1768,16 @@ namespace Sass { // evaluation phase. /////////////////////////////////////////////////////////////////////// class String_Schema : public String, public Vectorized { - // ADD_PROPERTY(bool, has_interpolants) + ADD_PROPERTY(bool, css) size_t hash_; public: - String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false) - : String(pstate), Vectorized(size), hash_(0) + String_Schema(ParserState pstate, size_t size = 0, bool css = true) + : String(pstate), Vectorized(size), css_(css), hash_(0) { concrete_type(STRING); } String_Schema(const String_Schema* ptr) : String(ptr), Vectorized(*ptr), + css_(ptr->css_), hash_(ptr->hash_) { concrete_type(STRING); } @@ -1840,17 +1830,17 @@ namespace Sass { value_(ptr->value_), hash_(ptr->hash_) { } - String_Constant(ParserState pstate, std::string val) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val)), hash_(0) + String_Constant(ParserState pstate, std::string val, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) { } - String_Constant(ParserState pstate, const char* beg) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg))), hash_(0) + String_Constant(ParserState pstate, const char* beg, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) { } - String_Constant(ParserState pstate, const char* beg, const char* end) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg))), hash_(0) + String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) { } - String_Constant(ParserState pstate, const Token& tok) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0) + String_Constant(ParserState pstate, const Token& tok, bool css = true) + : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) { } std::string type() const { return "string"; } static std::string type_name() { return "string"; } @@ -1883,8 +1873,8 @@ namespace Sass { public: String_Quoted(ParserState pstate, std::string val, char q = 0, bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true) - : String_Constant(pstate, val) + bool strict_unquoting = true, bool css = true) + : String_Constant(pstate, val, css) { if (skip_unquoting == false) { value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); @@ -2217,22 +2207,22 @@ namespace Sass { { if (p->default_value()) { if (has_rest_parameter()) { - error("optional parameters may not be combined with variable-length parameters", p->pstate()); + coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); } has_optional_parameters(true); } else if (p->is_rest_parameter()) { if (has_rest_parameter()) { - error("functions and mixins cannot have more than one variable-length parameter", p->pstate()); + coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); } has_rest_parameter(true); } else { if (has_rest_parameter()) { - error("required parameters must precede variable-length parameters", p->pstate()); + coreError("required parameters must precede variable-length parameters", p->pstate()); } if (has_optional_parameters()) { - error("required parameters must precede optional parameters", p->pstate()); + coreError("required parameters must precede optional parameters", p->pstate()); } } } @@ -2871,13 +2861,13 @@ namespace Sass { Complex_Selector_Obj innermost() { return last(); }; size_t length() const; - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); Combinator clear_innermost(); - void append(Complex_Selector_Obj); + void append(Complex_Selector_Obj, Backtraces& traces); void set_innermost(Complex_Selector_Obj, Combinator); virtual size_t hash() { @@ -2993,7 +2983,7 @@ namespace Sass { virtual bool has_parent_ref() const; virtual bool has_real_parent_ref() const; void remove_parent_selectors(); - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, bool implicit_parent = true); + Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); diff --git a/src/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp index 2d399471c..b3a7f8d16 100644 --- a/src/libsass/src/ast_def_macros.hpp +++ b/src/libsass/src/ast_def_macros.hpp @@ -33,7 +33,7 @@ class LocalOption { #define NESTING_GUARD(name) \ LocalOption cnt_##name(name, name + 1); \ - if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate); \ + if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ #define ATTACH_OPERATIONS()\ virtual void perform(Operation* op) { (*op)(this); }\ diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp index 147b8b559..5145a092b 100644 --- a/src/libsass/src/ast_fwd_decl.hpp +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -11,6 +11,7 @@ #include #include #include "memory/SharedPtr.hpp" +#include "sass/functions.h" ///////////////////////////////////////////// // Forward declarations for the AST visitors. @@ -418,6 +419,8 @@ namespace Sass { typedef std::set CompoundSelectorSet; typedef std::unordered_set SimpleSelectorDict; + typedef std::vector* ImporterStack; + // only to switch implementations for testing #define environment_map std::map diff --git a/src/libsass/src/backtrace.cpp b/src/libsass/src/backtrace.cpp new file mode 100644 index 000000000..8da963a72 --- /dev/null +++ b/src/libsass/src/backtrace.cpp @@ -0,0 +1,46 @@ +#include "backtrace.hpp" + +namespace Sass { + + const std::string traces_to_string(Backtraces traces, std::string indent) { + + std::stringstream ss; + std::string cwd(File::get_cwd()); + + bool first = true; + size_t i_beg = traces.size() - 1; + size_t i_end = std::string::npos; + for (size_t i = i_beg; i != i_end; i --) { + + const Backtrace& trace = traces[i]; + + // make path relative to the current directory + std::string rel_path(File::abs2rel(trace.pstate.path, cwd, cwd)); + + // skip functions on error cases (unsure why ruby sass does this) + // if (trace.caller.substr(0, 6) == ", in f") continue; + + if (first) { + ss << indent; + ss << "on line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + // ss << trace.caller; + first = false; + } else { + ss << trace.caller; + ss << std::endl; + ss << indent; + ss << "from line "; + ss << trace.pstate.line + 1; + ss << " of " << rel_path; + } + + } + + ss << std::endl; + return ss.str(); + + } + +}; diff --git a/src/libsass/src/backtrace.hpp b/src/libsass/src/backtrace.hpp index 57ce1786f..72d5fe517 100644 --- a/src/libsass/src/backtrace.hpp +++ b/src/libsass/src/backtrace.hpp @@ -1,75 +1,28 @@ #ifndef SASS_BACKTRACE_H #define SASS_BACKTRACE_H +#include #include - #include "file.hpp" #include "position.hpp" namespace Sass { - struct Backtrace { - Backtrace* parent; ParserState pstate; - std::string caller; + std::string caller; - Backtrace(Backtrace* prn, ParserState pstate, std::string c) - : parent(prn), - pstate(pstate), + Backtrace(ParserState pstate, std::string c = "") + : pstate(pstate), caller(c) { } - const std::string to_string(bool warning = false) - { - size_t i = -1; - std::stringstream ss; - std::string cwd(Sass::File::get_cwd()); - Backtrace* this_point = this; - - if (!warning) ss << std::endl << "Backtrace:"; - // the first tracepoint (which is parent-less) is an empty placeholder - while (this_point->parent) { - - // make path relative to the current directory - std::string rel_path(Sass::File::abs2rel(this_point->pstate.path, cwd, cwd)); - - if (warning) { - ss << std::endl - << "\t" - << (++i == 0 ? "on" : "from") - << " line " - << this_point->pstate.line + 1 - << " of " - << rel_path; - } else { - ss << std::endl - << "\t" - << rel_path - << ":" - << this_point->pstate.line + 1 - << this_point->parent->caller; - } - - this_point = this_point->parent; - } - - return ss.str(); - } + }; - size_t depth() - { - size_t d = std::string::npos; - Backtrace* p = parent; - while (p) { - ++d; - p = p->parent; - } - return d; - } + typedef std::vector Backtraces; - }; + const std::string traces_to_string(Backtraces traces, std::string indent = "\t"); } diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp index e579deac6..ec20ac838 100644 --- a/src/libsass/src/bind.cpp +++ b/src/libsass/src/bind.cpp @@ -2,6 +2,7 @@ #include "bind.hpp" #include "ast.hpp" #include "context.hpp" +#include "expand.hpp" #include "eval.hpp" #include #include @@ -53,7 +54,7 @@ namespace Sass { std::stringstream msg; msg << "wrong number of arguments (" << LA << " for " << LP << ")"; msg << " for `" << name << "'"; - return error(msg.str(), as->pstate()); + return error(msg.str(), as->pstate(), eval->exp.traces); } Parameter_Obj p = ps->at(ip); @@ -106,7 +107,8 @@ namespace Sass { false, false)); } else { - throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); } } @@ -219,13 +221,16 @@ namespace Sass { for (auto key : argmap->keys()) { String_Constant_Ptr val = Cast(key); - if (val == NULL) throw Exception::InvalidVarKwdType(key->pstate(), key->inspect(), a); + if (val == NULL) { + eval->exp.traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + } std::string param = "$" + unquote(val->value()); if (!param_map.count(param)) { std::stringstream msg; msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } env->local_frame()[param] = argmap->at(key); } @@ -240,7 +245,7 @@ namespace Sass { std::stringstream msg; msg << "parameter " << p->name() << " provided more than once in call to " << callee; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } // ordinal arg -- bind it to the next param env->local_frame()[p->name()] = a->value(); @@ -254,7 +259,7 @@ namespace Sass { } else { std::stringstream msg; msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } } if (param_map[a->name()]) { @@ -262,14 +267,14 @@ namespace Sass { std::stringstream msg; msg << "argument " << a->name() << " of " << callee << "cannot be used as named argument"; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } } if (env->has_local(a->name())) { std::stringstream msg; msg << "parameter " << p->name() << "provided more than once in call to " << callee; - error(msg.str(), a->pstate()); + error(msg.str(), a->pstate(), eval->exp.traces); } env->local_frame()[a->name()] = a->value(); } @@ -294,7 +299,7 @@ namespace Sass { } else { // param is unbound and has no default value -- error - throw Exception::MissingArgument(as->pstate(), name, leftover->name(), type); + throw Exception::MissingArgument(as->pstate(), eval->exp.traces, name, leftover->name(), type); } } } diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp index 19b43e0d8..880bcca37 100644 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -7,10 +7,15 @@ namespace Sass { CheckNesting::CheckNesting() : parents(std::vector()), - parent(0), - current_mixin_definition(0) + traces(std::vector()), + parent(0), current_mixin_definition(0) { } + void error(AST_Node_Ptr node, Backtraces traces, std::string msg) { + traces.push_back(Backtrace(node->pstate())); + throw Exception::InvalidSass(node->pstate(), traces, msg); + } + Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) { Statement_Ptr old_parent = this->parent; @@ -62,6 +67,12 @@ namespace Sass { Block_Ptr b = Cast(parent); + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.push_back(Backtrace(trace->pstate())); + } + } + if (!b) { if (Has_Block_Ptr bb = Cast(parent)) { b = bb->block(); @@ -73,9 +84,16 @@ namespace Sass { n->perform(this); } } + this->parent = old_parent; this->parents.pop_back(); + if (Trace_Ptr trace = Cast(parent)) { + if (trace->type() == 'i') { + this->traces.pop_back(); + } + } + return b; } @@ -126,75 +144,69 @@ namespace Sass { if (!this->parent) return true; if (Cast(node)) - { this->invalid_content_parent(this->parent); } + { this->invalid_content_parent(this->parent, node); } if (is_charset(node)) - { this->invalid_charset_parent(this->parent); } + { this->invalid_charset_parent(this->parent, node); } if (Cast(node)) - { this->invalid_extend_parent(this->parent); } + { this->invalid_extend_parent(this->parent, node); } // if (Cast(node)) // { this->invalid_import_parent(this->parent); } if (this->is_mixin(node)) - { this->invalid_mixin_definition_parent(this->parent); } + { this->invalid_mixin_definition_parent(this->parent, node); } if (this->is_function(node)) - { this->invalid_function_parent(this->parent); } + { this->invalid_function_parent(this->parent, node); } if (this->is_function(this->parent)) { this->invalid_function_child(node); } - if (Cast(node)) - { this->invalid_prop_parent(this->parent); } + if (Declaration_Ptr d = Cast(node)) + { + this->invalid_prop_parent(this->parent, node); + this->invalid_value_child(d->value()); + } if (Cast(this->parent)) { this->invalid_prop_child(node); } if (Cast(node)) - { this->invalid_return_parent(this->parent); } + { this->invalid_return_parent(this->parent, node); } return true; } - void CheckNesting::invalid_content_parent(Statement_Ptr parent) + void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!this->current_mixin_definition) { - throw Exception::InvalidSass( - parent->pstate(), - "@content may only be used within a mixin." - ); + error(node, traces, "@content may only be used within a mixin."); } } - void CheckNesting::invalid_charset_parent(Statement_Ptr parent) + void CheckNesting::invalid_charset_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( is_root_node(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "@charset may only be used at the root of a document." - ); + error(node, traces, "@charset may only be used at the root of a document."); } } - void CheckNesting::invalid_extend_parent(Statement_Ptr parent) + void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( Cast(parent) || Cast(parent) || is_mixin(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "Extend directives may only be used within rules." - ); + error(node, traces, "Extend directives may only be used within rules."); } } - // void CheckNesting::invalid_import_parent(Statement_Ptr parent) + // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node) // { // for (auto pp : this->parents) { // if ( @@ -206,10 +218,7 @@ namespace Sass { // Cast(pp) || // is_mixin(pp) // ) { - // throw Exception::InvalidSass( - // parent->pstate(), - // "Import directives may not be defined within control directives or other mixins." - // ); + // error(node, traces, "Import directives may not be defined within control directives or other mixins."); // } // } @@ -218,14 +227,11 @@ namespace Sass { // } // if (false/*n.css_import?*/) { - // throw Exception::InvalidSass( - // parent->pstate(), - // "CSS import directives may only be used at the root of a document." - // ); + // error(node, traces, "CSS import directives may only be used at the root of a document."); // } // } - void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent) + void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node) { for (Statement_Ptr pp : this->parents) { if ( @@ -237,15 +243,12 @@ namespace Sass { Cast(pp) || is_mixin(pp) ) { - throw Exception::InvalidSass( - parent->pstate(), - "Mixins may not be defined within control directives or other mixins." - ); + error(node, traces, "Mixins may not be defined within control directives or other mixins."); } } } - void CheckNesting::invalid_function_parent(Statement_Ptr parent) + void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node) { for (Statement_Ptr pp : this->parents) { if ( @@ -257,10 +260,7 @@ namespace Sass { Cast(pp) || is_mixin(pp) ) { - throw Exception::InvalidSass( - parent->pstate(), - "Functions may not be defined within control directives or other mixins." - ); + error(node, traces, "Functions may not be defined within control directives or other mixins."); } } } @@ -282,10 +282,7 @@ namespace Sass { Cast(child) || Cast(child) )) { - throw Exception::InvalidSass( - child->pstate(), - "Functions can only contain variable declarations and control directives." - ); + error(child, traces, "Functions can only contain variable declarations and control directives."); } } @@ -301,14 +298,11 @@ namespace Sass { Cast(child) || Cast(child) )) { - throw Exception::InvalidSass( - child->pstate(), - "Illegal nesting: Only properties may be nested beneath properties." - ); + error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); } } - void CheckNesting::invalid_prop_parent(Statement_Ptr parent) + void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!( is_mixin(parent) || @@ -318,20 +312,31 @@ namespace Sass { Cast(parent) || Cast(parent) )) { - throw Exception::InvalidSass( - parent->pstate(), - "Properties are only allowed within rules, directives, mixin includes, or other properties." - ); + error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); + } + } + + void CheckNesting::invalid_value_child(AST_Node_Ptr d) + { + if (Map_Ptr m = Cast(d)) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::InvalidValue(traces, *m); } + if (Number_Ptr n = Cast(d)) { + if (!n->is_valid_css_unit()) { + traces.push_back(Backtrace(n->pstate())); + throw Exception::InvalidValue(traces, *n); + } + } + + // error(dbg + " isn't a valid CSS value.", m->pstate(),); + } - void CheckNesting::invalid_return_parent(Statement_Ptr parent) + void CheckNesting::invalid_return_parent(Statement_Ptr parent, AST_Node_Ptr node) { if (!this->is_function(parent)) { - throw Exception::InvalidSass( - parent->pstate(), - "@return may only be used within a function." - ); + error(node, traces, "@return may only be used within a function."); } } diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp index 5ba303ee1..62c38d9dc 100644 --- a/src/libsass/src/check_nesting.hpp +++ b/src/libsass/src/check_nesting.hpp @@ -9,6 +9,7 @@ namespace Sass { class CheckNesting : public Operation_CRTP { std::vector parents; + Backtraces traces; Statement_Ptr parent; Definition_Ptr current_mixin_definition; @@ -34,17 +35,18 @@ namespace Sass { } private: - void invalid_content_parent(Statement_Ptr); - void invalid_charset_parent(Statement_Ptr); - void invalid_extend_parent(Statement_Ptr); + void invalid_content_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr); // void invalid_import_parent(Statement_Ptr); - void invalid_mixin_definition_parent(Statement_Ptr); - void invalid_function_parent(Statement_Ptr); + void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_function_parent(Statement_Ptr, AST_Node_Ptr); void invalid_function_child(Statement_Ptr); void invalid_prop_child(Statement_Ptr); - void invalid_prop_parent(Statement_Ptr); - void invalid_return_parent(Statement_Ptr); + void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_return_parent(Statement_Ptr, AST_Node_Ptr); + void invalid_value_child(AST_Node_Ptr); bool is_transparent_parent(Statement_Ptr, Statement_Ptr); diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index b64369f63..dae2cbd75 100644 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -74,6 +74,7 @@ namespace Sass { subset_map(), import_stack(), callee_stack(), + traces(), c_compiler(NULL), c_headers (std::vector()), @@ -250,10 +251,9 @@ namespace Sass { return vec; } - // register include with resolved path and its content // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, ParserState* prstate) + void Context::register_resource(const Include& inc, const Resource& res) { // do not parse same resource twice @@ -301,21 +301,22 @@ namespace Sass { for (size_t i = 0; i < import_stack.size() - 2; ++i) { auto parent = import_stack[i]; if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string cwd(File::get_cwd()); + // make path relative to the current directory std::string stack("An @import loop has been found:"); for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + std::string(import_stack[n]->imp_path) + - " imports " + std::string(import_stack[n+1]->imp_path); + stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + + " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); } // implement error throw directly until we // decided how to handle full stack traces - ParserState state = prstate ? *prstate : pstate; - throw Exception::InvalidSyntax(state, stack, &import_stack); + throw Exception::InvalidSyntax(pstate, traces, stack); // error(stack, prstate ? *prstate : pstate, import_stack); } } // create a parser instance from the given c_str buffer - Parser p(Parser::from_c_str(contents, *this, pstate)); + Parser p(Parser::from_c_str(contents, *this, traces, pstate)); // do not yet dispose these buffers sass_import_take_source(import); sass_import_take_srcmap(import); @@ -330,7 +331,15 @@ namespace Sass { ast_pair(inc.abs_path, { res, root }); // register resulting resource sheets.insert(ast_pair); + } + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) + { + traces.push_back(Backtrace(prstate)); + register_resource(inc, res); + traces.pop_back(); } // Add a new import to the context (called from `import_url`) @@ -350,7 +359,7 @@ namespace Sass { for (size_t i = 0, L = resolved.size(); i < L; ++i) { msg_stream << " " << resolved[i].imp_path << "\n"; } msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate); + error(msg_stream.str(), pstate, traces); } // process the resolved entry @@ -362,7 +371,7 @@ namespace Sass { // the memory buffer returned must be freed by us! if (char* contents = read_file(resolved[0].abs_path)) { // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, &pstate); + register_resource(resolved[0], { contents, 0 }, pstate); // return resolved entry return resolved[0]; } @@ -403,7 +412,7 @@ namespace Sass { const Importer importer(imp_path, ctx_path); Include include(load_import(importer, pstate)); if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".\nParent style sheet: " + ctx_path, pstate); + error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); } imp->incs().push_back(include); } @@ -448,9 +457,9 @@ namespace Sass { // handle error message passed back from custom importer // it may (or may not) override the line and column info if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, &pstate); - if (line == std::string::npos && column == std::string::npos) error(err_message, pstate); - else error(err_message, ParserState(ctx_path, source, Position(line, column))); + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); + else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); } // content for import was set else if (source) { @@ -462,7 +471,7 @@ namespace Sass { // attach information to AST node imp->incs().push_back(include); // register the resource buffers - register_resource(include, { source, srcmap }, &pstate); + register_resource(include, { source, srcmap }, pstate); } // only a path was retuned // try to load it like normal @@ -648,10 +657,9 @@ namespace Sass { for (size_t i = 0, S = c_functions.size(); i < S; ++i) { register_c_function(*this, &global, c_functions[i]); } // create initial backtrace entry - Backtrace backtrace(0, ParserState("", 0), ""); // create crtp visitor objects - Expand expand(*this, &global, &backtrace); - Cssize cssize(*this, &backtrace); + Expand expand(*this, &global); + Cssize cssize(*this); CheckNesting check_nesting; // check nesting in all files for (auto sheet : sheets) { diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp index 07a21a5d2..d3caba13e 100644 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -15,6 +15,7 @@ #include "environment.hpp" #include "source_map.hpp" #include "subset_map.hpp" +#include "backtrace.hpp" #include "output.hpp" #include "plugins.hpp" #include "file.hpp" @@ -54,6 +55,7 @@ namespace Sass { Subset_Map subset_map; std::vector import_stack; std::vector callee_stack; + std::vector traces; struct Sass_Compiler* c_compiler; @@ -94,7 +96,8 @@ namespace Sass { virtual char* render(Block_Obj root); virtual char* render_srcmap(); - void register_resource(const Include&, const Resource&, ParserState* = 0); + void register_resource(const Include&, const Resource&); + void register_resource(const Include&, const Resource&, ParserState&); std::vector find_includes(const Importer& import); Include load_import(const Importer&, ParserState pstate); diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp index e1f083c5c..4c062a628 100644 --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -5,15 +5,14 @@ #include "cssize.hpp" #include "context.hpp" -#include "backtrace.hpp" namespace Sass { - Cssize::Cssize(Context& ctx, Backtrace* bt) + Cssize::Cssize(Context& ctx) : ctx(ctx), + traces(ctx.traces), block_stack(std::vector()), - p_stack(std::vector()), - backtrace(bt) + p_stack(std::vector()) { } Statement_Ptr Cssize::parent() @@ -33,7 +32,10 @@ namespace Sass { Statement_Ptr Cssize::operator()(Trace_Ptr t) { - return t->block()->perform(this); + traces.push_back(Backtrace(t->pstate())); + auto result = t->block()->perform(this); + traces.pop_back(); + return result; } Statement_Ptr Cssize::operator()(Declaration_Ptr d) @@ -149,7 +151,7 @@ namespace Sass { // this should protect us (at least a bit) from our mess // fixing this properly is harder that it should be ... if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); } Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, r->pstate(), @@ -161,7 +163,7 @@ namespace Sass { p_stack.pop_back(); if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate()); + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); } Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp index 506b075f7..5a6c704b0 100644 --- a/src/libsass/src/cssize.hpp +++ b/src/libsass/src/cssize.hpp @@ -13,14 +13,14 @@ namespace Sass { class Cssize : public Operation_CRTP { Context& ctx; - std::vector block_stack; - std::vector p_stack; - Backtrace* backtrace; + Backtraces& traces; + std::vector block_stack; + std::vector p_stack; Statement_Ptr fallback_impl(AST_Node_Ptr n); public: - Cssize(Context&, Backtrace*); + Cssize(Context&); ~Cssize() { } Selector_List_Ptr selector(); diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp index e9fc8a279..ee0d6eba7 100644 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -79,7 +79,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) Trace_Ptr trace = Cast(node); std::cerr << ind << "Trace " << trace; std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << "]" + << " [name:" << trace->name() << ", type: " << trace->type() << "]" << std::endl; debug_ast(trace->block(), ind + " ", env); } else if (Cast(node)) { @@ -664,6 +664,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " (" << pstate_source_position(expression) << ")"; std::cerr << " " << expression->concrete_type(); std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->css()) std::cerr << " [css]"; if (expression->is_delayed()) std::cerr << " [delayed]"; if (expression->is_interpolant()) std::cerr << " [is interpolant]"; if (expression->has_interpolant()) std::cerr << " [has interpolant]"; diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp index 67affc81e..745f65508 100644 --- a/src/libsass/src/error_handling.cpp +++ b/src/libsass/src/error_handling.cpp @@ -10,19 +10,18 @@ namespace Sass { namespace Exception { - Base::Base(ParserState pstate, std::string msg, std::vector* import_stack) + Base::Base(ParserState pstate, std::string msg, Backtraces traces) : std::runtime_error(msg), msg(msg), - prefix("Error"), pstate(pstate), - import_stack(import_stack) + prefix("Error"), pstate(pstate), traces(traces) { } - InvalidSass::InvalidSass(ParserState pstate, std::string msg) - : Base(pstate, msg) + InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - InvalidParent::InvalidParent(Selector_Ptr parent, Selector_Ptr selector) - : Base(selector->pstate()), parent(parent), selector(selector) + InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector) + : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) { msg = "Invalid parent selector for \""; msg += selector->to_string(Sass_Inspect_Options()); @@ -31,15 +30,15 @@ namespace Sass { msg += "\""; } - InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg) - : Base(pstate), name(name), arg(arg) + InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg) + : Base(pstate, def_msg, traces), name(name), arg(arg) { msg = "Variable keyword argument map must have string keys.\n"; msg += name + " is not a string in " + arg->to_string() + "."; } - InvalidArgumentType::InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value) - : Base(pstate), fn(fn), arg(arg), type(type), value(value) + InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) { msg = arg + ": \""; if (value) msg += value->to_string(Sass_Inspect_Options()); @@ -47,50 +46,24 @@ namespace Sass { msg += " for `" + fn + "'"; } - MissingArgument::MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype) - : Base(pstate), fn(fn), arg(arg), fntype(fntype) + MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype) + : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) { msg = fntype + " " + fn; msg += " is missing argument "; msg += arg + "."; } - InvalidSyntax::InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack) - : Base(pstate, msg, import_stack) + InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - NestingLimitError::NestingLimitError(ParserState pstate, std::string msg, std::vector* import_stack) - : Base(pstate, msg, import_stack) + NestingLimitError::NestingLimitError(ParserState pstate, Backtraces traces, std::string msg) + : Base(pstate, msg, traces) { } - UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : lhs(lhs), rhs(rhs), op(op) - { - msg = def_op_msg + ": \""; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + op + " "; - msg += rhs->to_string({ TO_SASS, 5 }); - msg += "\"."; - } - - InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : UndefinedOperation(lhs, rhs, op) - { - msg = def_op_null_msg + ": \""; - msg += lhs->inspect(); - msg += " " + op + " "; - msg += rhs->inspect(); - msg += "\"."; - } - - ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) - : lhs(lhs), rhs(rhs) - { - msg = "divided by 0"; - } - - DuplicateKeyError::DuplicateKeyError(const Map& dup, const Expression& org) - : Base(org.pstate()), dup(dup), org(org) + DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) + : Base(org.pstate(), def_msg, traces), dup(dup), org(org) { msg = "Duplicate key "; msg += dup.get_duplicate_key()->inspect(); @@ -99,8 +72,8 @@ namespace Sass { msg += ")."; } - TypeMismatch::TypeMismatch(const Expression& var, const std::string type) - : Base(var.pstate()), var(var), type(type) + TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type) + : Base(var.pstate(), def_msg, traces), var(var), type(type) { msg = var.to_string(); msg += " is not an "; @@ -108,15 +81,15 @@ namespace Sass { msg += "."; } - InvalidValue::InvalidValue(const Expression& val) - : Base(val.pstate()), val(val) + InvalidValue::InvalidValue(Backtraces traces, const Expression& val) + : Base(val.pstate(), def_msg, traces), val(val) { msg = val.to_string(); msg += " isn't a valid CSS value."; } - StackError::StackError(const AST_Node& node) - : Base(node.pstate()), node(node) + StackError::StackError(Backtraces traces, const AST_Node& node) + : Base(node.pstate(), def_msg, traces), node(node) { msg = "stack level too deep"; } @@ -139,19 +112,44 @@ namespace Sass { msg += "'."; } - AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) - : lhs(lhs), rhs(rhs), op(op) + AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) { msg = "Alpha channels must be equal: "; msg += lhs->to_string({ NESTED, 5 }); - msg += " " + op + " "; + msg += " " + sass_op_to_name(op) + " "; msg += rhs->to_string({ NESTED, 5 }); msg += "."; } + ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) + : OperationError(), lhs(lhs), rhs(rhs) + { + msg = "divided by 0"; + } + + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : OperationError(), lhs(lhs), rhs(rhs), op(op) + { + msg = def_op_msg + ": \""; + msg += lhs->to_string({ NESTED, 5 }); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->to_string({ TO_SASS, 5 }); + msg += "\"."; + } + + InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + : UndefinedOperation(lhs, rhs, op) + { + msg = def_op_null_msg + ": \""; + msg += lhs->inspect(); + msg += " " + sass_op_to_name(op) + " "; + msg += rhs->inspect(); + msg += "\"."; + } - SassValueError::SassValueError(ParserState pstate, OperationError& err) - : Base(pstate, err.what()) + SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err) + : Base(pstate, err.what(), traces) { msg = err.what(); prefix = err.errtype(); @@ -162,7 +160,7 @@ namespace Sass { void warn(std::string msg, ParserState pstate) { - std::cerr << "Warning: " << msg<< std::endl; + std::cerr << "Warning: " << msg << std::endl; } void warning(std::string msg, ParserState pstate) @@ -178,8 +176,6 @@ namespace Sass { void warn(std::string msg, ParserState pstate, Backtrace* bt) { - Backtrace top(bt, pstate, ""); - msg += top.to_string(); warn(msg, pstate); } @@ -223,16 +219,17 @@ namespace Sass { std::cerr << "This will be an error in future versions of Sass." << std::endl; } - void error(std::string msg, ParserState pstate) + // should be replaced with error with backtraces + void coreError(std::string msg, ParserState pstate) { - throw Exception::InvalidSyntax(pstate, msg); + Backtraces traces; + throw Exception::InvalidSyntax(pstate, traces, msg); } - void error(std::string msg, ParserState pstate, Backtrace* bt) + void error(std::string msg, ParserState pstate, Backtraces& traces) { - Backtrace top(bt, pstate, ""); - msg += "\n" + top.to_string(); - error(msg, pstate); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSyntax(pstate, traces, msg); } } diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp index e7890f6ed..f863792ea 100644 --- a/src/libsass/src/error_handling.hpp +++ b/src/libsass/src/error_handling.hpp @@ -5,6 +5,7 @@ #include #include #include "position.hpp" +#include "backtrace.hpp" #include "ast_fwd_decl.hpp" #include "sass/functions.h" @@ -25,9 +26,9 @@ namespace Sass { std::string prefix; public: ParserState pstate; - std::vector* import_stack; + Backtraces traces; public: - Base(ParserState pstate, std::string msg = def_msg, std::vector* import_stack = 0); + Base(ParserState pstate, std::string msg, Backtraces traces); virtual const char* errtype() const { return prefix.c_str(); } virtual const char* what() const throw() { return msg.c_str(); } virtual ~Base() throw() {}; @@ -35,7 +36,7 @@ namespace Sass { class InvalidSass : public Base { public: - InvalidSass(ParserState pstate, std::string msg); + InvalidSass(ParserState pstate, Backtraces traces, std::string msg); virtual ~InvalidSass() throw() {}; }; @@ -44,7 +45,7 @@ namespace Sass { Selector_Ptr parent; Selector_Ptr selector; public: - InvalidParent(Selector_Ptr parent, Selector_Ptr selector); + InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector); virtual ~InvalidParent() throw() {}; }; @@ -54,7 +55,7 @@ namespace Sass { std::string arg; std::string fntype; public: - MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype); + MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype); virtual ~MissingArgument() throw() {}; }; @@ -65,7 +66,7 @@ namespace Sass { std::string type; const Value_Ptr value; public: - InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); + InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); virtual ~InvalidArgumentType() throw() {}; }; @@ -74,52 +75,28 @@ namespace Sass { std::string name; const Argument_Ptr arg; public: - InvalidVarKwdType(ParserState pstate, std::string name, const Argument_Ptr arg = 0); + InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0); virtual ~InvalidVarKwdType() throw() {}; }; class InvalidSyntax : public Base { public: - InvalidSyntax(ParserState pstate, std::string msg, std::vector* import_stack = 0); + InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg); virtual ~InvalidSyntax() throw() {}; }; class NestingLimitError : public Base { public: - NestingLimitError(ParserState pstate, std::string msg = def_nesting_limit, std::vector* import_stack = 0); + NestingLimitError(ParserState pstate, Backtraces traces, std::string msg = def_nesting_limit); virtual ~NestingLimitError() 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 DuplicateKeyError : public Base { protected: const Map& dup; const Expression& org; public: - DuplicateKeyError(const Map& dup, const Expression& org); + DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); virtual const char* errtype() const { return "Error"; } virtual ~DuplicateKeyError() throw() {}; }; @@ -129,7 +106,7 @@ namespace Sass { const Expression& var; const std::string type; public: - TypeMismatch(const Expression& var, const std::string type); + TypeMismatch(Backtraces traces, const Expression& var, const std::string type); virtual const char* errtype() const { return "Error"; } virtual ~TypeMismatch() throw() {}; }; @@ -138,7 +115,7 @@ namespace Sass { protected: const Expression& val; public: - InvalidValue(const Expression& val); + InvalidValue(Backtraces traces, const Expression& val); virtual const char* errtype() const { return "Error"; } virtual ~InvalidValue() throw() {}; }; @@ -147,11 +124,35 @@ namespace Sass { protected: const AST_Node& node; public: - StackError(const AST_Node& node); + StackError(Backtraces traces, const AST_Node& node); virtual const char* errtype() const { return "SystemStackError"; } virtual ~StackError() throw() {}; }; + /* common virtual base class (has no pstate or trace) */ + 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 Sass::UnitType lhs; @@ -166,16 +167,16 @@ namespace Sass { protected: Expression_Ptr_Const lhs; Expression_Ptr_Const rhs; - const std::string op; + const Sass_OP op; public: - UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); // virtual const char* errtype() const { return "Error"; } virtual ~UndefinedOperation() throw() {}; }; class InvalidNullOperation : public UndefinedOperation { public: - InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); virtual ~InvalidNullOperation() throw() {}; }; @@ -183,16 +184,16 @@ namespace Sass { protected: Expression_Ptr_Const lhs; Expression_Ptr_Const rhs; - const std::string op; + const Sass_OP op; public: - AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op); + AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); // virtual const char* errtype() const { return "Error"; } virtual ~AlphaChannelsNotEqual() throw() {}; }; class SassValueError : public Base { public: - SassValueError(ParserState pstate, OperationError& err); + SassValueError(Backtraces traces, ParserState pstate, OperationError& err); virtual ~SassValueError() throw() {}; }; @@ -207,8 +208,8 @@ namespace Sass { void deprecated_bind(std::string msg, ParserState pstate); // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); - void error(std::string msg, ParserState pstate); - void error(std::string msg, ParserState pstate, Backtrace* bt); + void coreError(std::string msg, ParserState pstate); + void error(std::string msg, ParserState pstate, Backtraces& traces); } diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 67fa628a3..2ddfa93ea 100644 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -12,6 +12,7 @@ #include "bind.hpp" #include "util.hpp" #include "inspect.hpp" +#include "operators.hpp" #include "environment.hpp" #include "position.hpp" #include "sass/values.h" @@ -28,28 +29,10 @@ namespace Sass { - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - Eval::Eval(Expand& exp) : exp(exp), ctx(exp.ctx), + traces(exp.traces), force(false), is_in_comment(false), is_in_selector_schema(false) @@ -69,11 +52,6 @@ namespace Sass { return exp.selector(); } - Backtrace* Eval::backtrace() - { - return exp.backtrace(); - } - Expression_Ptr Eval::operator()(Block_Ptr b) { Expression_Ptr val = 0; @@ -169,11 +147,13 @@ namespace Sass { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(this); if (low->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*low, "integer"); + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); } Expression_Obj high = f->upper_bound()->perform(this); if (high->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*high, "integer"); + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); } Number_Obj sass_start = Cast(low); Number_Obj sass_end = Cast(high); @@ -182,7 +162,7 @@ namespace Sass { std::stringstream msg; msg << "Incompatible units: '" << sass_end->unit() << "' and '" << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), backtrace()); + error(msg.str(), low->pstate(), traces); } double start = sass_start->value(); double end = sass_end->value(); @@ -366,11 +346,12 @@ namespace Sass { } std::string result(unquote(message->to_sass())); - Backtrace top(backtrace(), w->pstate(), ""); - std::cerr << "WARNING: " << result; - std::cerr << top.to_string(); - std::cerr << std::endl << std::endl; + std::cerr << "WARNING: " << result << std::endl; + traces.push_back(Backtrace(w->pstate())); + std::cerr << traces_to_string(traces, " "); + std::cerr << std::endl; ctx.c_options.output_style = outstyle; + traces.pop_back(); return 0; } @@ -414,7 +395,7 @@ namespace Sass { std::string result(unquote(message->to_sass())); ctx.c_options.output_style = outstyle; - error(result, e->pstate()); + error(result, e->pstate(), traces); return 0; } @@ -484,7 +465,8 @@ namespace Sass { *lm << std::make_pair(key, val); } if (lm->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*lm, *l); + traces.push_back(Backtrace(l->pstate())); + throw Exception::DuplicateKeyError(traces, *lm, *l); } lm->is_interpolant(l->is_interpolant()); @@ -515,7 +497,8 @@ namespace Sass { // make sure we're not starting with duplicate keys. // the duplicate key state will have been set in the parser phase. if (m->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*m, *m); + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *m, *m); } Map_Obj mm = SASS_MEMORY_NEW(Map, @@ -531,7 +514,8 @@ namespace Sass { // check the evaluated keys aren't duplicates. if (mm->has_duplicate_key()) { - throw Exception::DuplicateKeyError(*mm, *m); + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *mm, *m); } mm->is_expanded(true); @@ -599,13 +583,14 @@ namespace Sass { case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is number and rhs is color @@ -619,13 +604,14 @@ namespace Sass { case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false; case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } } @@ -641,13 +627,14 @@ namespace Sass { case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } // lhs is color and rhs is number @@ -661,13 +648,14 @@ namespace Sass { case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false; case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true; case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); default: break; } } catch (Exception::OperationError& err) { - throw Exception::SassValueError(b_in->pstate(), err); + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); } } } @@ -786,19 +774,20 @@ namespace Sass { // see if it's a relational expression try { switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), !eq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gt") && !eq(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gte")); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lt")); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lte") || eq(lhs, rhs)); - default: break; + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); + default: break; } } catch (Exception::OperationError& err) { // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(b->pstate(), err); + traces.push_back(Backtrace(b->pstate())); + throw Exception::SassValueError(traces, b->pstate(), err); } l_type = lhs->concrete_type(); @@ -813,22 +802,22 @@ namespace Sass { Number_Ptr l_n = Cast(lhs); Number_Ptr r_n = Cast(rhs); l_n->reduce(); r_n->reduce(); - rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); + rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { Number_Ptr l_n = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { Color_Ptr l_c = Cast(lhs); Number_Ptr r_n = Cast(rhs); - rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); } else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { Color_Ptr l_c = Cast(lhs); Color_Ptr r_c = Cast(rhs); - rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); } else { To_Value to_value(ctx); @@ -842,12 +831,14 @@ namespace Sass { // if (op_type == Sass_OP::DIV) interpolant = true; // check for type violations if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - throw Exception::InvalidValue(*v_l); + traces.push_back(Backtrace(v_l->pstate())); + throw Exception::InvalidValue(traces, *v_l); } if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - throw Exception::InvalidValue(*v_r); + traces.push_back(Backtrace(v_r->pstate())); + throw Exception::InvalidValue(traces, *v_r); } - Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress + Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress if (String_Constant_Ptr str = Cast(ex)) { if (str->concrete_type() == Expression::STRING) @@ -866,8 +857,9 @@ namespace Sass { } catch (Exception::OperationError& err) { + traces.push_back(Backtrace(b->pstate())); // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(b->pstate(), err); + throw Exception::SassValueError(traces, b->pstate(), err); } if (rv) { @@ -932,11 +924,11 @@ namespace Sass { Expression_Ptr Eval::operator()(Function_Call_Ptr c) { - if (backtrace()->parent != NULL && backtrace()->depth() > Constants::MaxCallStack) { + if (traces.size() > Constants::MaxCallStack) { // XXX: this is never hit via spec tests std::ostringstream stm; stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), backtrace()); + error(stm.str(), c->pstate(), traces); } std::string name(Util::normalize_underscores(c->name())); std::string full_name(name + "[f]"); @@ -948,7 +940,7 @@ namespace Sass { if (!env->has("*[f]")) { for (Argument_Obj arg : args->elements()) { if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate()); + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); } } args = Cast(args->perform(this)); @@ -957,7 +949,7 @@ namespace Sass { c->name(), args); if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate()); + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); } String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), @@ -994,7 +986,7 @@ namespace Sass { ss << full_name << L; full_name = ss.str(); std::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate()); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); def = Cast((*env)[resolved_name]); } @@ -1011,8 +1003,8 @@ namespace Sass { if (func || body) { bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); - exp.backtrace_stack.push_back(&here); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -1027,13 +1019,13 @@ namespace Sass { result = body->perform(this); } else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), backtrace(), exp.selector_stack); + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); } if (!result) { - error(std::string("Function ") + c->name() + " did not return a value", c->pstate()); + error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); } - exp.backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + traces.pop_back(); } // else if it's a user-defined c function @@ -1051,9 +1043,8 @@ namespace Sass { // populates env with default values for params std::string ff(c->name()); bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); - - Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`"); - exp.backtrace_stack.push_back(&here); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -1074,14 +1065,14 @@ namespace Sass { } union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); if (sass_value_get_tag(c_val) == SASS_ERROR) { - error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), backtrace()); + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), backtrace()); + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); } - result = cval_to_astnode(c_val, backtrace(), c->pstate()); + result = cval_to_astnode(c_val, traces, c->pstate()); - exp.backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + traces.pop_back(); sass_delete_value(c_args); if (c_val != c_args) sass_delete_value(c_val); @@ -1115,7 +1106,7 @@ namespace Sass { const std::string& name(v->name()); EnvResult rv(env->find(name)); if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); if (Argument_Ptr arg = Cast(value)) value = arg->value(); if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag value->is_interpolant(v->is_interpolant()); @@ -1159,7 +1150,8 @@ namespace Sass { Number reduced(nr); reduced.reduce(); if (!reduced.is_valid_css_unit()) { - throw Exception::InvalidValue(*nr); + traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue(traces, *nr); } } if (Argument_Ptr arg = Cast(ex)) { @@ -1258,10 +1250,10 @@ namespace Sass { } if (!s->is_interpolant()) { if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - return SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); } // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false); + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); // if (s->is_interpolant()) str->quote_mark(0); // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); if (str->quote_mark()) str->quote_mark('*'); @@ -1482,206 +1474,7 @@ namespace Sass { // All the binary helpers. - bool Eval::eq(Expression_Obj lhs, Expression_Obj rhs) - { - // use compare operator from ast node - return lhs && rhs && *lhs == *rhs; - } - - bool Eval::lt(Expression_Obj lhs, Expression_Obj rhs, std::string op) - { - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // use compare operator from ast node - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double lv = l.value(); - double rv = r.value(); - - if (op == Sass_OP::DIV && rv == 0) { - // XXX: this is never hit via spec tests - return SASS_MEMORY_NEW(String_Quoted, pstate, lv ? "Infinity" : "NaN"); - } - - if (op == Sass_OP::MOD && !rv) { - // XXX: this is never hit via spec tests - throw Exception::ZeroDivisionError(l, r); - } - - size_t l_n_units = l.numerators.size(); - size_t l_d_units = l.numerators.size(); - size_t r_n_units = r.denominators.size(); - size_t r_d_units = r.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (l.numerators == r.numerators) { - if (l.denominators == r.denominators) { - Number_Ptr v = SASS_MEMORY_COPY(&l); - v->value(ops[op](lv, rv)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&l); - - if (l.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = r.numerators; - v->denominators = r.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lv, rv)); - v->numerators.insert(v->numerators.end(), - r.numerators.begin(), r.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - r.denominators.begin(), r.denominators.end() - ); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lv, rv)); - v->numerators.insert(v->numerators.end(), - r.denominators.begin(), r.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - r.numerators.begin(), r.numerators.end() - ); - } - else { - Number ln(l), rn(r); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lv, rn.value() * f)); - } - - v->reduce(); - v->pstate(pstate); - return v.detach(); - } - - Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double lv = l.value(); - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](lv, r.r()), - ops[op](lv, r.g()), - ops[op](lv, r.b()), - r.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - std::string sep(op == Sass_OP::SUB ? "-" : "/"); - std::string color(r.to_string(opt)); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l.to_string(opt) - + sep - + color); - } - case Sass_OP::MOD: { - throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op)); - } - default: break; // caller should ensure that we don't get here - } - // unreachable - return NULL; - } - - Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - double rv = r.value(); - if (op == Sass_OP::DIV && !rv) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(l, r); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](l.r(), rv), - ops[op](l.g(), rv), - ops[op](l.b(), rv), - l.a()); - } - - Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, const ParserState& pstate) - { - if (l.a() != r.a()) { - throw Exception::AlphaChannelsNotEqual(&l, &r, "+"); - } - if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(l, r); - } - return SASS_MEMORY_NEW(Color, - pstate, - ops[op](l.r(), r.r()), - ops[op](l.g(), r.g()), - ops[op](l.b(), r.b()), - l.a()); - } - - Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) - { - Expression::Concrete_Type ltype = lhs.concrete_type(); - Expression::Concrete_Type rtype = rhs.concrete_type(); - enum Sass_OP op = operand.operand; - - String_Quoted_Ptr lqstr = Cast(&lhs); - String_Quoted_Ptr rqstr = Cast(&rhs); - - std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); - if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op)); - std::string sep; - switch (op) { - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - // cases are already handled above - 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; - case Sass_OP::MUL: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); - case Sass_OP::MOD: throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op)); - default: break; - } - - if ( (sep == "") /* && - (sep != "/" || !rqstr || !rqstr->quote_mark()) */ - ) { - // create a new string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - 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(String_Constant, pstate, lstr + sep + rstr); - } - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate) + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) { using std::strlen; using std::strcpy; @@ -1706,7 +1499,7 @@ namespace Sass { case SASS_LIST: { List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(cval_to_astnode(sass_list_get_value(v, i), backtrace, pstate)); + l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); } l->is_bracketed(sass_list_get_is_bracketed(v)); e = l; @@ -1715,8 +1508,8 @@ namespace Sass { Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { *m << std::make_pair( - cval_to_astnode(sass_map_get_key(v, i), backtrace, pstate), - cval_to_astnode(sass_map_get_value(v, i), backtrace, pstate)); + cval_to_astnode(sass_map_get_key(v, i), traces, pstate), + cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); } e = m; } break; @@ -1724,10 +1517,10 @@ namespace Sass { e = SASS_MEMORY_NEW(Null, pstate); } break; case SASS_ERROR: { - error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, backtrace); + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); } break; case SASS_WARNING: { - error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace); + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); } break; default: break; } @@ -1771,7 +1564,7 @@ namespace Sass { { bool implicit_parent = !exp.old_at_root_without_rule; if (is_in_selector_schema) exp.selector_stack.push_back(0); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, implicit_parent); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); if (is_in_selector_schema) exp.selector_stack.pop_back(); for (size_t i = 0; i < resolved->length(); i++) { Complex_Selector_Ptr is = resolved->at(i)->first(); @@ -1807,7 +1600,7 @@ namespace Sass { result_str = unquote(Util::rtrim(result_str)); char* temp_cstr = sass_copy_c_string(result_str.c_str()); ctx.strings.push_back(temp_cstr); // attach to context - Parser p = Parser::from_c_str(temp_cstr, ctx, s->pstate()); + Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); p.last_media_block = s->media_block(); // a selector schema may or may not connect to parent? bool chroot = s->connect_parent() == false; diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp index 16a1b9210..aeaada87e 100644 --- a/src/libsass/src/eval.hpp +++ b/src/libsass/src/eval.hpp @@ -18,8 +18,9 @@ namespace Sass { Expression_Ptr fallback_impl(AST_Node_Ptr n); public: - Expand& exp; + Expand& exp; Context& ctx; + Backtraces& traces; Eval(Expand& exp); ~Eval(); @@ -31,7 +32,6 @@ namespace Sass { Boolean_Obj bool_false; Env* environment(); - Backtrace* backtrace(); Selector_List_Obj selector(); // for evaluating function bodies @@ -91,22 +91,12 @@ namespace Sass { template Expression_Ptr fallback(U x) { return fallback_impl(x); } - // -- only need to define two comparisons, and the rest can be implemented in terms of them - static bool eq(Expression_Obj, Expression_Obj); - static bool lt(Expression_Obj, Expression_Obj, std::string op); - // -- arithmetic on the combinations that matter - static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate); - static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool interpolant = false); - private: void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); }; - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate = ParserState("[AST]")); + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]")); } diff --git a/src/libsass/src/expand.cpp b/src/libsass/src/expand.cpp index 8c85024ae..ccd2822df 100644 --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -16,8 +16,9 @@ namespace Sass { // simple endless recursion protection const size_t maxRecursion = 500; - Expand::Expand(Context& ctx, Env* env, Backtrace* bt, std::vector* stack) + Expand::Expand(Context& ctx, Env* env, std::vector* stack) : ctx(ctx), + traces(ctx.traces), eval(Eval(*this)), recursions(0), in_keyframes(false), @@ -27,8 +28,7 @@ namespace Sass { block_stack(std::vector()), call_stack(std::vector()), selector_stack(std::vector()), - media_block_stack(std::vector()), - backtrace_stack(std::vector()) + media_block_stack(std::vector()) { env_stack.push_back(0); env_stack.push_back(env); @@ -37,8 +37,6 @@ namespace Sass { if (stack == NULL) { selector_stack.push_back(0); } else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } media_block_stack.push_back(0); - backtrace_stack.push_back(0); - backtrace_stack.push_back(bt); } Env* Expand::environment() @@ -55,13 +53,6 @@ namespace Sass { return 0; } - Backtrace* Expand::backtrace() - { - if (backtrace_stack.size() > 0) - return backtrace_stack.back(); - return 0; - } - // blocks create new variable scopes Block_Ptr Expand::operator()(Block_Ptr b) { @@ -126,7 +117,7 @@ namespace Sass { Parent_Selector_Ptr ptr = Cast(header); if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace()); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); } tail = tail->tail(); } @@ -136,7 +127,7 @@ namespace Sass { else { if (sel->length() == 0 || sel->has_parent_ref()) { if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace()); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); } } } @@ -189,7 +180,7 @@ namespace Sass { std::string str_mq(mq->to_string(ctx.c_options)); char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, mq->pstate())); + Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); mq = p.parse_media_queries(); // re-assign now cpy->media_queries(mq); media_block_stack.push_back(cpy); @@ -349,10 +340,11 @@ namespace Sass { Statement_Ptr Expand::operator()(Import_Stub_Ptr i) { + traces.push_back(Backtrace(i->pstate())); // get parent node from call stack AST_Node_Obj parent = call_stack.back(); if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate()); + error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); } // we don't seem to need that actually afterall Sass_Import_Entry import = sass_make_import( @@ -361,10 +353,18 @@ namespace Sass { 0, 0 ); ctx.import_stack.push_back(import); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); + block_stack.back()->append(trace); + block_stack.push_back(trace_block); + const std::string& abs_path(i->resource().abs_path); append_block(ctx.sheets.at(abs_path).root); sass_delete_import(ctx.import_stack.back()); ctx.import_stack.pop_back(); + block_stack.pop_back(); + traces.pop_back(); return 0; } @@ -428,11 +428,13 @@ namespace Sass { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(&eval); if (low->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*low, "integer"); + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); } Expression_Obj high = f->upper_bound()->perform(&eval); if (high->concrete_type() != Expression::NUMBER) { - throw Exception::TypeMismatch(*high, "integer"); + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); } Number_Obj sass_start = Cast(low); Number_Obj sass_end = Cast(high); @@ -441,7 +443,7 @@ namespace Sass { std::stringstream msg; msg << "Incompatible units: '" << sass_start->unit() << "' and '" << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), backtrace()); + error(msg.str(), low->pstate(), traces); } double start = sass_start->value(); double end = sass_end->value(); @@ -579,7 +581,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Return_Ptr r) { - error("@return may only be used within a function", r->pstate(), backtrace()); + error("@return may only be used within a function", r->pstate(), traces); return 0; } @@ -593,7 +595,7 @@ namespace Sass { if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { if (Cast(header) == NULL) continue; // skip all others std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace()); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); } tail = tail->tail(); } @@ -607,7 +609,7 @@ namespace Sass { Complex_Selector_Obj c = complex_sel; if (!c->head() || c->tail()) { std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace()); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); } Compound_Selector_Obj target = c->head(); if (contextualized->is_optional()) target->is_optional(true); @@ -694,7 +696,7 @@ namespace Sass { Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) { if (recursions > maxRecursion) { - throw Exception::StackError(*c); + throw Exception::StackError(traces, *c); } recursions ++; @@ -702,19 +704,19 @@ namespace Sass { Env* env = environment(); std::string full_name(c->name() + "[m]"); if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), backtrace()); + error("no mixin named " + c->name(), c->pstate(), traces); } Definition_Obj def = Cast((*env)[full_name]); Block_Obj body = def->block(); Parameters_Obj params = def->parameters(); if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), backtrace()); + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); } Expression_Obj rv = c->arguments()->perform(&eval); Arguments_Obj args = Cast(rv); - Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`"); - backtrace_stack.push_back(&new_bt); + std::string msg(", in mixin `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); ctx.callee_stack.push_back({ c->name().c_str(), c->pstate().path, @@ -758,9 +760,9 @@ namespace Sass { block_stack.pop_back(); env->del_global("is_in_mixin"); - env_stack.pop_back(); - backtrace_stack.pop_back(); ctx.callee_stack.pop_back(); + env_stack.pop_back(); + traces.pop_back(); recursions --; return trace.detach(); @@ -795,7 +797,7 @@ namespace Sass { { std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); - error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace()); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); } diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp index 348503b2b..3464c98f6 100644 --- a/src/libsass/src/expand.hpp +++ b/src/libsass/src/expand.hpp @@ -20,9 +20,9 @@ namespace Sass { Env* environment(); Selector_List_Obj selector(); - Backtrace* backtrace(); Context& ctx; + Backtraces& traces; Eval eval; size_t recursions; bool in_keyframes; @@ -35,7 +35,6 @@ namespace Sass { std::vector call_stack; std::vector selector_stack; std::vector media_block_stack; - std::vector backtrace_stack; Boolean_Obj bool_true; @@ -45,7 +44,7 @@ namespace Sass { void expand_selector_list(Selector_Obj, Selector_List_Obj extender); public: - Expand(Context&, Env*, Backtrace*, std::vector* stack = NULL); + Expand(Context&, Env*, std::vector* stack = NULL); ~Expand() { } Block_Ptr operator()(Block_Ptr); diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp index 5348e5dcf..602269880 100644 --- a/src/libsass/src/extend.cpp +++ b/src/libsass/src/extend.cpp @@ -1718,7 +1718,7 @@ namespace Sass { err << "You may only @extend selectors within the same directive.\n"; err << "From \"@extend " << ext.second->to_string() << "\""; err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), selector->pstate()); + error(err.str(), selector->pstate(), eval->exp.traces); } if (entries.size() > 0) hasExtension = true; } @@ -2098,7 +2098,7 @@ namespace Sass { error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" "The selector \"" + str_ext + "\" was not found.\n" "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", (ext ? ext->pstate() : NULL)); + " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); } } diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp index aa3c55ec5..32d4a7c63 100644 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -56,6 +56,8 @@ namespace Sass { #ifndef _WIN32 char wd[wd_len]; char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); std::string cwd = pwd; #else @@ -74,11 +76,15 @@ namespace Sass { bool file_exists(const std::string& path) { #ifdef _WIN32 + wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); - DWORD dwAttrib = GetFileAttributesW(wpath.c_str()); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + DWORD dwAttrib = GetFileAttributesW(resolved); return (dwAttrib != INVALID_FILE_ATTRIBUTES && (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); #else @@ -200,6 +206,13 @@ namespace Sass { if (is_absolute_path(r)) return r; if (l[l.length()-1] != '/') l += '/'; + // this does a logical cleanup of the right hand path + // Note that this does collapse x/../y sections into y. + // This is by design. If /foo on your system is a symlink + // to /bar/baz, then /foo/../cd is actually /bar/cd, + // not /cd as a naive ../ removal would give you. + // will only work on leading double dot dirs on rhs + // therefore it is safe if lhs is already resolved cwd while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); @@ -395,11 +408,15 @@ namespace Sass { #ifdef _WIN32 BYTE* pBuffer; DWORD dwBytes; + wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); - HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return 0; DWORD dwFileLength = GetFileSize(hFile, NULL); if (dwFileLength == INVALID_FILE_SIZE) return 0; diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp index aaa3394fb..9b118d53a 100644 --- a/src/libsass/src/functions.cpp +++ b/src/libsass/src/functions.cpp @@ -10,6 +10,7 @@ #include "eval.hpp" #include "util.hpp" #include "expand.hpp" +#include "operators.hpp" #include "utf8_string.hpp" #include "sass/base.h" #include "utf8.h" @@ -30,27 +31,26 @@ #include "wincrypt.h" #endif -#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, backtrace) -#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, backtrace, ctx) -#define ARGNR(argname) get_arg_nr(argname, env, sig, pstate, backtrace) +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) // return a number object (copied since we want to have reduced units) -#define ARGN(argname) get_arg_n(argname, env, sig, pstate, backtrace) // Number copy +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy // special function for weird hsla percent (10px == 10% == 10 != 0.1) -#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, backtrace) // double +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double // macros for common ranges (u mean unsigned or upper, r for full range) -#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 1.0) // double -#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 1.0, 1.0) // double -#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 255.0) // double -#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 255.0, 255.0) // double -#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 0.0, 100.0) // double -#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, backtrace, - 100.0, 100.0) // double +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double // macros for color related inputs (rbg and alpha/opacity values) -#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, backtrace) // double -#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, backtrace) // double +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double namespace Sass { using std::stringstream; @@ -58,7 +58,7 @@ namespace Sass { Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) { - Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[built-in function]")); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); sig_parser.lex(); std::string name(Util::normalize_underscores(sig_parser.lexed)); Parameters_Obj params = sig_parser.parse_parameters(); @@ -76,7 +76,7 @@ namespace Sass { using namespace Prelexer; const char* sig = sass_function_get_signature(c_func); - Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[c function]")); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); // allow to overload generic callback plus @warn, @error and @debug with custom functions sig_parser.lex < alternatives < identifier, exactly <'*'>, exactly < Constants::warn_kwd >, @@ -102,28 +102,28 @@ namespace Sass { namespace Functions { - inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace) + inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) { try { throw; } catch (utf8::invalid_code_point) { std::string msg("utf8::invalid_code_point"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (utf8::not_enough_room) { std::string msg("utf8::not_enough_room"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (utf8::invalid_utf8) { std::string msg("utf8::invalid_utf8"); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } catch (...) { throw; } } template - T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! T* val = Cast(env[argname]); @@ -134,12 +134,12 @@ namespace Sass { msg += sig; msg += "` must be a "; msg += T::type_name(); - error(msg, pstate, backtrace); + error(msg, pstate, traces); } return val; } - Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { // Minimal error handling -- the expectation is that built-ins will be written correctly! Map_Ptr val = Cast(env[argname]); @@ -149,14 +149,14 @@ namespace Sass { if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); // fallback on get_arg for error handling - val = get_arg(argname, env, sig, pstate, backtrace); + val = get_arg(argname, env, sig, pstate, traces); return val; } - double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, double lo, double hi) + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); double v = tmpnr.value(); @@ -164,33 +164,24 @@ namespace Sass { std::stringstream msg; msg << "argument `" << argname << "` of `" << sig << "` must be between "; msg << lo << " and " << hi; - error(msg.str(), pstate, backtrace); + error(msg.str(), pstate, traces); } return v; } - const Number& get_arg_nr(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr; - } - - Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); val = SASS_MEMORY_COPY(val); val->reduce(); return val; } - double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); /* @@ -204,18 +195,18 @@ namespace Sass { return tmpnr.value(); } - double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); return tmpnr.value(); } - double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); if (tmpnr.unit() == "%") { @@ -226,8 +217,8 @@ namespace Sass { } - inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace) { - Number_Ptr val = get_arg(argname, env, sig, pstate, backtrace); + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); Number tmpnr(val); tmpnr.reduce(); if (tmpnr.unit() == "%") { @@ -237,40 +228,40 @@ namespace Sass { } } - #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, backtrace, ctx) + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) template - T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx); + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); template <> - Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << argname << ": null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx); + return Parser::parse_selector(exp_src.c_str(), ctx, traces); } template <> - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { Expression_Obj exp = ARG(argname, Expression); if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); if (sel_list->length() == 0) return NULL; Complex_Selector_Obj first = sel_list->first(); if (!first->tail()) return first->head(); @@ -870,7 +861,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); } if (rgb) { double rr = r ? DARG_R_BYTE("$red") : 0; @@ -904,7 +895,7 @@ namespace Sass { color->b(), color->a() + (a ? a->value() : 0)); } - error("not enough arguments for `adjust-color'", pstate); + error("not enough arguments for `adjust-color'", pstate, traces); // unreachable return color; } @@ -925,7 +916,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); } if (rgb) { double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; @@ -960,7 +951,7 @@ namespace Sass { color->b(), color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); } - error("not enough arguments for `scale-color'", pstate); + error("not enough arguments for `scale-color'", pstate, traces); // unreachable return color; } @@ -981,7 +972,7 @@ namespace Sass { bool hsl = h || s || l; if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate); + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); } if (rgb) { return SASS_MEMORY_NEW(Color, @@ -1008,7 +999,7 @@ namespace Sass { color->b(), alpha); } - error("not enough arguments for `change-color'", pstate); + error("not enough arguments for `change-color'", pstate, traces); // unreachable return color; } @@ -1101,7 +1092,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } // return something even if we had an error (-1) return SASS_MEMORY_NEW(Number, pstate, (double)len); } @@ -1147,7 +1138,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } return SASS_MEMORY_NEW(String_Quoted, pstate, str); } @@ -1171,7 +1162,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } // return something even if we had an error (-1) return SASS_MEMORY_NEW(Number, pstate, (double)index); } @@ -1224,7 +1215,7 @@ namespace Sass { } // handle any invalid utf8 errors // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, backtrace); } + catch (...) { handle_utf8_error(pstate, traces); } return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); } @@ -1278,7 +1269,7 @@ namespace Sass { BUILT_IN(percentage) { Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); } @@ -1327,7 +1318,7 @@ namespace Sass { Expression_Obj val = arglist->value_at_index(i); Number_Obj xi = Cast(val); if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate); + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); } if (least) { if (*xi < *least) least = xi; @@ -1345,7 +1336,7 @@ namespace Sass { Expression_Obj val = arglist->value_at_index(i); Number_Obj xi = Cast(val); if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate); + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); } if (greatest) { if (*greatest < *xi) greatest = xi; @@ -1366,13 +1357,13 @@ namespace Sass { if (lv < 1) { stringstream err; err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate); + error(err.str(), pstate, traces); } bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; if (!eq_int) { stringstream err; err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate); + error(err.str(), pstate, traces); } std::uniform_real_distribution<> distributor(1, lv + 1); uint_fast32_t distributed = static_cast(distributor(rand)); @@ -1383,9 +1374,11 @@ namespace Sass { double distributed = static_cast(distributor(rand)); return SASS_MEMORY_NEW(Number, pstate, distributed); } else if (v) { - throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number", v); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); } else { - throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number"); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); } } @@ -1428,15 +1421,15 @@ namespace Sass { if (Selector_List_Ptr sl = Cast(env["$list"])) { size_t len = m ? m->length() : sl->length(); bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); // return (*sl)[static_cast(index)]; Listize listize; return (*sl)[static_cast(index)]->perform(&listize); } List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); // if the argument isn't a list, then wrap it in a singleton list if (!m && !l) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1444,9 +1437,9 @@ namespace Sass { } size_t len = m ? m->length() : l->length(); bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); if (m) { l = SASS_MEMORY_NEW(List, pstate, 1); @@ -1475,9 +1468,9 @@ namespace Sass { if (m) { l = m->to_list(pstate); } - if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate); + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); for (size_t i = 0, L = l->length(); i < L; ++i) { result->append(((i == index) ? v : (*l)[i])); @@ -1499,7 +1492,7 @@ namespace Sass { l = m->to_list(pstate); } for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Eval::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); } return SASS_MEMORY_NEW(Null, pstate); } @@ -1536,7 +1529,7 @@ namespace Sass { std::string sep_str = unquote(sep->value()); if (sep_str == "space") sep_val = SASS_SPACE; else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); String_Constant_Obj bracketed_as_str = Cast(bracketed); bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; if (!bracketed_is_auto) { @@ -1571,7 +1564,7 @@ namespace Sass { if (sep_str != "auto") { // check default first if (sep_str == "space") result->separator(SASS_SPACE); else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); } if (l->is_arglist()) { result->append(SASS_MEMORY_NEW(Argument, @@ -1712,7 +1705,7 @@ namespace Sass { for (auto key : m->keys()) { remove = false; for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { - remove = Eval::eq(key, arglist->value_at_index(j)); + remove = Operators::eq(key, arglist->value_at_index(j)); } if (!remove) *result << std::make_pair(key, m->at(key)); } @@ -1809,7 +1802,7 @@ namespace Sass { { String_Constant_Ptr ss = Cast(env["$name"]); if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, backtrace); + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); } std::string name = Util::normalize_underscores(unquote(ss->value())); @@ -1893,7 +1886,7 @@ namespace Sass { } } Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); - Expand expand(ctx, &d_env, backtrace, &selector_stack); + Expand expand(ctx, &d_env, &selector_stack); func->via_call(true); // calc invoke is allowed if (ff) func->func(ff); return func->perform(&expand.eval); @@ -1914,7 +1907,7 @@ namespace Sass { // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } BUILT_IN(sass_if) { - Expand expand(ctx, &d_env, backtrace, &selector_stack); + Expand expand(ctx, &d_env, &selector_stack); Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); bool is_true = !cond->is_false(); Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); @@ -1961,7 +1954,7 @@ namespace Sass { // Not enough parameters if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-nest'", pstate); + error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); // Parse args into vector of selectors std::vector parsedSelectors; @@ -1971,13 +1964,13 @@ namespace Sass { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Obj str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); parsedSelectors.push_back(sel); } @@ -1995,7 +1988,7 @@ namespace Sass { Selector_List_Obj child = *itr; std::vector exploded; selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); selector_stack.pop_back(); for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { exploded.push_back((*rv)[m]); @@ -2014,7 +2007,7 @@ namespace Sass { // Not enough parameters if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate); + error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); // Parse args into vector of selectors std::vector parsedSelectors; @@ -2024,13 +2017,13 @@ namespace Sass { std::stringstream msg; msg << "$selectors: null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate); + error(msg.str(), pstate, traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); parsedSelectors.push_back(sel); } @@ -2068,7 +2061,7 @@ namespace Sass { msg += "\" to \""; msg += parentSeqClone->to_string(); msg += "\" for `selector-append'"; - error(msg, pstate, backtrace); + error(msg, pstate, traces); } // Cannot be a Universal selector @@ -2079,7 +2072,7 @@ namespace Sass { msg += "\" to \""; msg += parentSeqClone->to_string(); msg += "\" for `selector-append'"; - error(msg, pstate, backtrace); + error(msg, pstate, traces); } // TODO: Add check for namespace stuff @@ -2202,7 +2195,7 @@ namespace Sass { BUILT_IN(content_exists) { if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, backtrace); + error("Cannot call content-exists() except within a mixin.", pstate, traces); } return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); } @@ -2212,7 +2205,7 @@ namespace Sass { { String_Constant_Ptr ss = Cast(env["$name"]); if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, backtrace); + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); } std::string name = Util::normalize_underscores(unquote(ss->value())); @@ -2231,7 +2224,7 @@ namespace Sass { if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, backtrace); + error("Function not found: " + name, pstate, traces); } Definition_Ptr def = Cast(d_env[full_name]); diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp index 131160d4c..7019be934 100644 --- a/src/libsass/src/functions.hpp +++ b/src/libsass/src/functions.hpp @@ -8,12 +8,12 @@ #include "sass/functions.h" #define BUILT_IN(name) Expression_Ptr \ -name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtrace* backtrace, std::vector selector_stack) +name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector selector_stack) namespace Sass { struct Backtrace; typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector); + typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); diff --git a/src/libsass/src/operators.cpp b/src/libsass/src/operators.cpp new file mode 100644 index 000000000..65885bf19 --- /dev/null +++ b/src/libsass/src/operators.cpp @@ -0,0 +1,240 @@ +#include "sass.hpp" +#include "operators.hpp" + +namespace Sass { + + namespace Operators { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + /* static function, has no pstate or traces */ + bool eq(Expression_Obj lhs, Expression_Obj rhs) + { + // operation is undefined if one is not a number + if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); + // use compare operator from ast node + return *lhs == *rhs; + } + + /* static function, throws OperationError, has no pstate or traces */ + bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) + { + // can only compare numbers!? + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); + // operation is undefined if one is not a number + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); + // use compare operator from ast node + return *l < *r; + } + + /* static functions, throws OperationError, has no pstate or traces */ + bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } + bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } + bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } + bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } + bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + + std::string sep; + switch (op) { + case Sass_OP::ADD: sep = ""; break; + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: 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: + throw Exception::UndefinedOperation(&lhs, &rhs, op); + break; + } + + if (op == Sass_OP::ADD) { + // create string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); + } + + // add whitespace around operator + // but only if result is not delayed + if (sep != "" && delayed == false) { + 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(String_Constant, pstate, lstr + sep + rstr); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + if (lhs.a() != rhs.a()) { + throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); + } + if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rhs.r()), + ops[op](lhs.g(), rhs.g()), + ops[op](lhs.b(), rhs.b()), + lhs.a()); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + double rval = rhs.value(); + + if (op == Sass_OP::DIV && rval == 0) { + std::string result(lval ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + if (op == Sass_OP::MOD && rval == 0) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + + size_t l_n_units = lhs.numerators.size(); + size_t l_d_units = lhs.numerators.size(); + size_t r_n_units = rhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (lhs.numerators == rhs.numerators) { + if (lhs.denominators == rhs.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&lhs); + v->value(ops[op](lval, rval)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&lhs); + + if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = rhs.numerators; + v->denominators = rhs.denominators; + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->reduce(); + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->reduce(); + } + else { + Number ln(lhs), rn(rhs); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lval, rn.value() * f)); + } + + v->pstate(pstate); + return v.detach(); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lval, rhs.r()), + ops[op](lval, rhs.g()), + ops[op](lval, rhs.b()), + rhs.a()); + } + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string color(rhs.to_string(opt)); + return SASS_MEMORY_NEW(String_Quoted, + pstate, + lhs.to_string(opt) + + sass_op_separator(op) + + color); + } + default: break; + } + throw Exception::UndefinedOperation(&lhs, &rhs, op); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double rval = rhs.value(); + if (op == Sass_OP::DIV && rval == 0) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rval), + ops[op](lhs.g(), rval), + ops[op](lhs.b(), rval), + lhs.a()); + } + + } + +} diff --git a/src/libsass/src/operators.hpp b/src/libsass/src/operators.hpp new file mode 100644 index 000000000..f89eb4ee2 --- /dev/null +++ b/src/libsass/src/operators.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_OPERATORS_H +#define SASS_OPERATORS_H + +#include "values.hpp" +#include "sass/values.h" + +namespace Sass { + + namespace Operators { + + // equality operator using AST Node operator== + bool eq(Expression_Obj, Expression_Obj); + bool neq(Expression_Obj, Expression_Obj); + // specific operators based on cmp and eq + bool lt(Expression_Obj, Expression_Obj); + bool gt(Expression_Obj, Expression_Obj); + bool lte(Expression_Obj, Expression_Obj); + bool gte(Expression_Obj, Expression_Obj); + // arithmetic for all the combinations that matter + Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + + }; + +} + +#endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp index cc847ee08..b2ca65e7e 100644 --- a/src/libsass/src/output.cpp +++ b/src/libsass/src/output.cpp @@ -19,13 +19,14 @@ namespace Sass { void Output::operator()(Number_Ptr n) { - // use values to_string facility - std::string res = n->to_string(opt); // check for a valid unit here // includes result for reporting if (!n->is_valid_css_unit()) { - throw Exception::InvalidValue(*n); + // should be handle in check_expression + throw Exception::InvalidValue({}, *n); } + // use values to_string facility + std::string res = n->to_string(opt); // output the final token append_token(res, n); } @@ -37,8 +38,8 @@ namespace Sass { void Output::operator()(Map_Ptr m) { - std::string dbg(m->to_string(opt)); - error(dbg + " isn't a valid CSS value.", m->pstate()); + // should be handle in check_expression + throw Exception::InvalidValue({}, *m); } OutputBuffer Output::get_buffer(void) diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp index 71858de10..525f199d1 100644 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -30,11 +30,11 @@ namespace Sass { using namespace Constants; using namespace Prelexer; - Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = p.position + strlen(p.position); @@ -44,11 +44,11 @@ namespace Sass { return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = end ? end : p.position + strlen(p.position); @@ -66,9 +66,9 @@ namespace Sass { pstate.offset.line = 0; } - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, ParserState pstate, const char* source) + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { - Parser p = Parser::from_c_str(beg, ctx, pstate, source); + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); // ToDo: ruby sass errors on parent references // ToDo: remap the source-map entries somehow return p.parse_selector_list(false); @@ -80,9 +80,9 @@ namespace Sass { && ! peek_css>(start); } - Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source) + Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) { - Parser p(ctx, pstate); + Parser p(ctx, pstate, traces); 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); @@ -105,7 +105,8 @@ namespace Sass { // report invalid utf8 if (it != end) { pstate += Offset::init(position, it); - throw Exception::InvalidSass(pstate, "Invalid UTF-8 sequence"); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); } // create a block AST node to hold children @@ -240,7 +241,7 @@ namespace Sass { Scope parent = stack.empty() ? Scope::Rules : stack.back(); if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins.", pstate); + error("Import directives may not be used within control directives or mixins."); } } // this puts the parsed doc into sheets @@ -350,14 +351,14 @@ namespace Sass { args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); } else { - error("malformed URL", pstate); + error("malformed URL"); } - if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate); + if (!lex< exactly<')'> >()) error("URI is missing ')'"); to_import.push_back(std::pair("", result)); } else { - if (first) error("@import directive requires a url or quoted path", pstate); - else error("expecting another url or quoted path in @import list", pstate); + if (first) error("@import directive requires a url or quoted path"); + else error("expecting another url or quoted path in @import list"); } first = false; } while (lex_css< exactly<','> >()); @@ -384,10 +385,10 @@ namespace Sass { Definition_Obj Parser::parse_definition(Definition::Type which_type) { std::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition", pstate); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); std::string name(Util::normalize_underscores(lexed)); if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\".", pstate); } + { error("Invalid function name \"" + name + "\"."); } ParserState source_position_of_def = pstate; Parameters_Obj params = parse_parameters(); if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); @@ -494,7 +495,7 @@ namespace Sass { { std::string name(Util::normalize_underscores(lexed)); ParserState var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate); + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } @@ -583,7 +584,7 @@ namespace Sass { } // pass inner expression to the parser to resolve nested interpolations pstate.add(p, p+2); - Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list(); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); // set status on the list expression interpolant->is_interpolant(true); // schema->has_interpolants(true); @@ -904,7 +905,7 @@ namespace Sass { ParserState nsource_position = pstate; Selector_List_Obj negated = parse_selector_list(true); if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'", pstate); + error("negated selector is missing ')'"); } name.erase(name.size() - 1); return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); @@ -981,7 +982,7 @@ namespace Sass { Attribute_Selector_Obj Parser::parse_attribute_selector() { ParserState p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate); + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); std::string name(lexed); if (lex_css< re_attr_sensitive_close >()) { return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); @@ -992,7 +993,7 @@ namespace Sass { } if (!lex_css< alternatives< exact_match, class_match, dash_match, prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name, pstate); + error("invalid operator in attribute selector for " + name); } std::string matcher(lexed); @@ -1004,7 +1005,7 @@ namespace Sass { value = parse_interpolated_chunk(lexed, true); // needed! } else { - error("expected a string constant or identifier in attribute selector for " + name, pstate); + error("expected a string constant or identifier in attribute selector for " + name); } if (lex_css< re_attr_sensitive_close >()) { @@ -1014,7 +1015,7 @@ namespace Sass { char modifier = lexed.begin[0]; return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); } - error("unterminated attribute selector for " + name, pstate); + error("unterminated attribute selector for " + name); return NULL; // to satisfy compilers (error must not return) } @@ -1026,7 +1027,7 @@ namespace Sass { while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true); + String_Obj contents = parse_interpolated_chunk(lexed, true, false); block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); } } @@ -1049,8 +1050,8 @@ namespace Sass { } bool is_indented = true; const std::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value", pstate); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty if (is_custom_property) { return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); @@ -1448,7 +1449,7 @@ namespace Sass { // parse_map may return a list Expression_Obj value = parse_map(); // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); // expression can be evaluated return value; } @@ -1456,7 +1457,7 @@ namespace Sass { // explicit bracketed Expression_Obj value = parse_bracket_list(); // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket", pstate); + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); return value; } // string may be interpolated @@ -1721,7 +1722,7 @@ namespace Sass { // this parses interpolation inside other strings // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant) + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) { const char* i = chunk.begin; // see if there any interpolants @@ -1729,12 +1730,12 @@ namespace Sass { find_first_in_interval< exactly, block_comment >(i, chunk.end); if (!p) { - String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end)); + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); return str_quoted; } - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); schema->is_interpolant(true); while (i < chunk.end) { p = constant ? find_first_in_interval< exactly >(i, chunk.end) : @@ -1742,7 +1743,7 @@ namespace Sass { if (p) { if (i < p) { // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); } // we need to skip anything inside strings // create a new target in parser/prelexer @@ -1752,19 +1753,19 @@ 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_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string(), pstate); + error("unterminated interpolant inside string constant " + chunk.to_string()); } } else { // no interpolants left; add the last segment if nonempty // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end))); + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); break; } ++ i; @@ -1884,14 +1885,14 @@ 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_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string(), pstate); + error("unterminated interpolant inside IE function " + str.to_string()); } } else { // no interpolants left; add the last segment if nonempty @@ -2062,7 +2063,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_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).parse_list(DELAYED); + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); interp_node->is_interpolant(true); schema->append(interp_node); // schema->has_interpolants(true); @@ -2070,7 +2071,7 @@ namespace Sass { } else { // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string(), pstate); + error("unterminated interpolant inside interpolated identifier " + id.to_string()); } } else { // no interpolants left; add the last segment if nonempty @@ -2171,7 +2172,7 @@ namespace Sass { std::string name(lexed); if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin.", pstate); } + { error("Cannot call content-exists() except within a mixin."); } ParserState call_pos = pstate; Arguments_Obj args = parse_arguments(); @@ -2221,12 +2222,12 @@ namespace Sass { bool root = block_stack.back()->is_root(); lex_variable(); std::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); Expression_Obj lower_bound = parse_expression(); bool inclusive = false; if (lex< kwd_through >()) inclusive = true; else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive", pstate); + else error("expected 'through' or 'to' keyword in @for directive"); Expression_Obj upper_bound = parse_expression(); Block_Obj body = parse_block(root); stack.pop_back(); @@ -2268,10 +2269,10 @@ namespace Sass { lex_variable(); vars.push_back(Util::normalize_underscores(lexed)); while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable", pstate); + if (!lex< variable >()) error("@each directive requires an iteration variable"); vars.push_back(Util::normalize_underscores(lexed)); } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate); + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); Expression_Obj list = parse_list(); Block_Obj body = parse_block(root); stack.pop_back(); @@ -2360,11 +2361,11 @@ namespace Sass { return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); } if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('", pstate); + error("media query expression must begin with '('"); } Expression_Obj feature; if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression", pstate); + error("media feature required in media query expression"); } feature = parse_expression(); Expression_Obj expression = 0; @@ -2372,7 +2373,7 @@ namespace Sass { expression = parse_list(DELAYED); } if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression", pstate); + error("unclosed parenthesis in media query expression"); } return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); } @@ -2453,7 +2454,7 @@ namespace Sass { if (lex_css< exactly<':'> >()) { expression = parse_list(DELAYED); } - if (!feature || !expression) error("@supports condition expected declaration", pstate); + if (!feature || !expression) error("@supports condition expected declaration"); cond = SASS_MEMORY_NEW(Supports_Declaration, feature->pstate(), feature, @@ -2472,10 +2473,10 @@ namespace Sass { Supports_Condition_Obj cond = parse_supports_condition(); if (cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); } else { cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); } lex < css_whitespace >(); return cond; @@ -2508,14 +2509,14 @@ namespace Sass { At_Root_Query_Obj Parser::parse_at_root_query() { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression", pstate); + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); } Expression_Obj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value", pstate); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); Expression_Obj expression = parse_list(); List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); @@ -2528,7 +2529,7 @@ namespace Sass { value->pstate(), feature, value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); return cond; } @@ -2536,7 +2537,7 @@ namespace Sass { { std::string kwd(lexed); - if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); // this whole branch is never hit via spec tests @@ -2568,7 +2569,7 @@ namespace Sass { { std::string kwd(lexed); - if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); Lookahead lookahead = lookahead_for_include(position); @@ -2725,7 +2726,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); } @@ -2737,7 +2738,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); } @@ -2749,7 +2750,7 @@ namespace Sass { stack.back() != Scope::Mixin && stack.back() != Scope::Control && stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties.", pstate); + error("Illegal nesting: Only properties may be nested beneath properties."); } return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); } @@ -2955,7 +2956,7 @@ namespace Sass { break; default: break; } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding, pstate); + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); position += skip; } @@ -3035,7 +3036,15 @@ namespace Sass { void Parser::error(std::string msg, Position pos) { - throw Exception::InvalidSass(ParserState(path, source, pos.line ? pos : before_token, Offset(0, 0)), msg); + Position p(pos.line ? pos : before_token); + ParserState pstate(path, source, p, Offset(0, 0)); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, msg); + } + + void Parser::error(std::string msg) + { + error(msg, pstate); } // print a css parsing error with actual context information from parsed source @@ -3106,7 +3115,7 @@ namespace Sass { // Hotfix when source is null, probably due to interpolation parsing!? if (source == NULL || *source == 0) source = pstate.src; // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right), pstate); + error(msg + prefix + quote(left) + middle + quote(right)); } } diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp index 71b00a854..83c7f34ba 100644 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -45,22 +45,26 @@ namespace Sass { Position before_token; Position after_token; ParserState pstate; + Backtraces traces; size_t indentation; size_t nestings; Token lexed; - Parser(Context& ctx, const ParserState& pstate) + Parser(Context& ctx, const ParserState& pstate, Backtraces traces) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0), nestings(0) - { stack.push_back(Scope::Root); } + source(0), position(0), end(0), before_token(pstate), after_token(pstate), + pstate(pstate), traces(traces), indentation(0), nestings(0) + { + 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]"), 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); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); #ifdef __clang__ @@ -232,6 +236,7 @@ namespace Sass { #endif + void error(std::string msg); void error(std::string msg, Position pos); // generate message with given and expected sample // text before and in the middle are configurable @@ -283,7 +288,7 @@ namespace Sass { Function_Call_Schema_Obj parse_function_call_schema(); String_Obj parse_url_function_string(); String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false); + String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); String_Obj parse_string(); String_Constant_Obj parse_static_value(); String_Schema_Obj parse_css_variable_value(bool top_level = true); diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp index 03a2f12fb..afadc66e1 100644 --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -35,7 +35,6 @@ namespace Sass { catch (Exception::Base& e) { std::stringstream msg_stream; std::string cwd(Sass::File::get_cwd()); - std::string msg_prefix(e.errtype()); bool got_newline = false; msg_stream << msg_prefix << ": "; @@ -55,20 +54,17 @@ namespace Sass { ++msg; } if (!got_newline) msg_stream << "\n"; - if (e.import_stack) { - 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() + 2, ' '); - msg_stream << (i == 1 ? " on line " : " from line "); - msg_stream << e.pstate.line + 1 << " of " << rel_path << "\n"; - } - } - else { + + if (e.traces.empty()) { + // we normally should have some traces, still here as a fallback std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); msg_stream << std::string(msg_prefix.size() + 2, ' '); msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << traces_to_string(e.traces, " "); + } // now create the code trace (ToDo: maybe have util functions?) if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp index 25abd49ba..34c591a24 100644 --- a/src/libsass/src/sass_values.cpp +++ b/src/libsass/src/sass_values.cpp @@ -4,6 +4,7 @@ #include "util.hpp" #include "eval.hpp" #include "values.hpp" +#include "operators.hpp" #include "sass/values.h" #include "sass_values.hpp" @@ -299,41 +300,41 @@ extern "C" { // see if it's a relational expression 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, "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)); + case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); + case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); + case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); - default: break; + default: break; } if (sass_value_is_number(a) && sass_value_is_number(b)) { Number_Ptr_Const l_n = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); + rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); } else if (sass_value_is_number(a) && sass_value_is_color(a)) { Number_Ptr_Const l_n = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); + rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); } else if (sass_value_is_color(a) && sass_value_is_number(b)) { Color_Ptr_Const l_c = Cast(lhs); Number_Ptr_Const r_n = Cast(rhs); - rv = Eval::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); + rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); } else if (sass_value_is_color(a) && sass_value_is_color(b)) { Color_Ptr_Const l_c = Cast(lhs); Color_Ptr_Const r_c = Cast(rhs); - rv = Eval::op_colors(op, *l_c, *r_c, options, l_c->pstate()); + rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); } else /* convert other stuff to string and apply operation */ { Value_Ptr l_v = Cast(lhs); Value_Ptr r_v = Cast(rhs); - rv = Eval::op_strings(op, *l_v, *r_v, options, l_v->pstate()); + rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate()); } // ToDo: maybe we should should return null value? diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp index 49187d95d..60f69ab76 100644 --- a/src/libsass/src/util.cpp +++ b/src/libsass/src/util.cpp @@ -96,8 +96,9 @@ namespace Sass { } // read css string (handle multiline DELIM) - std::string read_css_string(const std::string& str) + std::string read_css_string(const std::string& str, bool css) { + if (!css) return str; std::string out(""); bool esc = false; for (auto i : str) { @@ -180,6 +181,23 @@ namespace Sass { return out; } + std::string escape_string(const std::string& str) + { + std::string out(""); + for (auto i : str) { + if (i == '\n') { + out += "\\n"; + } else if (i == '\r') { + out += "\\r"; + } else if (i == '\t') { + out += "\\t"; + } else { + out += i; + } + } + return out; + } + std::string comment_to_string(const std::string& text) { std::string str = ""; diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp index ee263711b..4313d502d 100644 --- a/src/libsass/src/util.hpp +++ b/src/libsass/src/util.hpp @@ -22,11 +22,12 @@ 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 read_css_string(const std::string& str); + std::string read_css_string(const std::string& str, bool css = true); 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 read_hex_escapes(const std::string& str); + std::string escape_string(const std::string& str); void newline_to_space(std::string& str); std::string quote(const std::string&, char q = 0); diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets index 2f079b9d5..c1c7d45f3 100644 --- a/src/libsass/win/libsass.targets +++ b/src/libsass/win/libsass.targets @@ -97,6 +97,8 @@ + + diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters index 36d85b389..980f00f3f 100644 --- a/src/libsass/win/libsass.vcxproj.filters +++ b/src/libsass/win/libsass.vcxproj.filters @@ -302,6 +302,12 @@ Sources + + Sources + + + Sources + Sources