From 77135111a5e1269c9e778df1b6bf927ef64660e9 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sat, 14 Nov 2015 20:49:32 +0100 Subject: [PATCH] Defer creation of hash maps into eval stage Use an intermediate list representation to create the actual hash map in the evaluation phase. --- include/sass/values.h | 3 +- src/ast.cpp | 149 +++++++++++++++++++++++++++++++++++++++++- src/ast.hpp | 46 ++++++++++--- src/debugger.hpp | 2 +- src/eval.cpp | 25 +++++++ src/inspect.cpp | 2 + src/parser.cpp | 11 ++-- 7 files changed, 220 insertions(+), 18 deletions(-) diff --git a/include/sass/values.h b/include/sass/values.h index 394d91108f..d933ccc4df 100644 --- a/include/sass/values.h +++ b/include/sass/values.h @@ -29,7 +29,8 @@ enum Sass_Tag { // Tags for denoting Sass list separators enum Sass_Separator { SASS_COMMA, - SASS_SPACE + SASS_SPACE, + SASS_HASH }; // Value Operators diff --git a/src/ast.cpp b/src/ast.cpp index 38d700b3fd..6121446bb2 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1880,19 +1880,114 @@ namespace Sass { if (empty()) return res; if (is_invisible()) return res; bool items_output = false; - std::string sep = separator() == SASS_COMMA ? "," : " "; + std::string sep = separator() == SASS_SPACE ? " " : ","; if (!compressed && sep == ",") sep += " "; for (size_t i = 0, L = size(); i < L; ++i) { + if (separator_ == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } Expression* item = (*this)[i]; if (item->is_invisible()) continue; if (items_output) res += sep; - if (Value* v_val = dynamic_cast(item)) - { res += v_val->to_string(compressed, precision); } + if (Expression* ex = dynamic_cast(item)) + { res += ex->to_string(compressed, precision); } + // else if (Function_Call* v_fn = dynamic_cast(item)) + // { res += v_fn->to_string(compressed, precision); } + else { res += "[unknown type]"; } items_output = true; } return res; } + std::string Function_Call::to_string(bool compressed, int precision) const + { + std::string str(name()); + str += "("; + str += arguments()->to_string(compressed, precision); + str += ")"; + return str; + } + + std::string Arguments::to_string(bool compressed, int precision) const + { + std::string str(""); + for(auto arg : elements()) { + if (str != "") str += compressed ? "," : ", "; + str += arg->to_string(compressed, precision); + } + return str; + } + + std::string Argument::to_string(bool compressed, int precision) const + { + return value()->to_string(compressed, precision); + } + + std::string Binary_Expression::to_string(bool compressed, int precision) const + { + std::string str(""); + str += left()->to_string(compressed, precision); + if (!compressed) str += " "; + switch (type()) { + case Sass_OP::AND: str += "and"; break; + case Sass_OP::OR: str += "or"; break; + case Sass_OP::EQ: str += "=="; break; + case Sass_OP::NEQ: str += "!="; break; + case Sass_OP::GT: str += ">"; break; + case Sass_OP::GTE: str += ">="; break; + case Sass_OP::LT: str += "<"; break; + case Sass_OP::LTE: str += "<="; break; + case Sass_OP::ADD: str += "+"; break; + case Sass_OP::SUB: str += "-"; break; + case Sass_OP::MUL: str += "*"; break; + case Sass_OP::DIV: str += "/"; break; + case Sass_OP::MOD: str += "%"; break; + default: break; // shouldn't get here + } + if (!compressed) str += " "; + str += right()->to_string(compressed, precision); + return str; + } + std::string Textual::to_string(bool compressed, int precision) const + { + return value(); + } + std::string Variable::to_string(bool compressed, int precision) const + { + return name(); + } + + // For now it seems easiest to just implement these, since we need it to + // ie. report the values as is for error reporting (like duplicate keys). + // We cannot use inspect since we do not always have a context object. + std::string Unary_Expression::to_string(bool compressed, int precision) const + { + return "[Unary_Expression.to_string not implemented]"; + } + std::string Function_Call_Schema::to_string(bool compressed, int precision) const + { + return "[Function_Call_Schema.to_string not implemented]"; + } + std::string Media_Query::to_string(bool compressed, int precision) const + { + return "[Media_Query.to_string not implemented]"; + } + std::string Media_Query_Expression::to_string(bool compressed, int precision) const + { + return "[Media_Query_Expression.to_string not implemented]"; + } + std::string Supports_Condition::to_string(bool compressed, int precision) const + { + return "[Supports_Condition.to_string not implemented]"; + } + std::string At_Root_Expression::to_string(bool compressed, int precision) const + { + return "[At_Root_Expression.to_string not implemented]"; + } + std::string Thunk::to_string(bool compressed, int precision) const + { + return "[Thunk.to_string not implemented]"; + } + std::string String_Schema::to_string(bool compressed, int precision) const { std::string res(""); @@ -1923,6 +2018,54 @@ namespace Sass { else return c; } + std::string Color::to_hex(bool compressed, int precision) const + { + + std::stringstream ss; + + // original color name + // maybe an unknown token + std::string name = disp(); + + // resolved color + std::string res_name = name; + + double r = Sass::round(cap_channel<0xff>(r_)); + double g = Sass::round(cap_channel<0xff>(g_)); + double b = Sass::round(cap_channel<0xff>(b_)); + double a = cap_channel<1> (a_); + + // get color from given name (if one was given at all) + if (name != "" && name_to_color(name)) { + const Color* n = name_to_color(name); + r = Sass::round(cap_channel<0xff>(n->r())); + g = Sass::round(cap_channel<0xff>(n->g())); + b = Sass::round(cap_channel<0xff>(n->b())); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + double numval = r * 0x10000 + g * 0x100 + b; + if (color_to_name(numval)) + res_name = color_to_name(numval); + } + + std::stringstream hexlet; + hexlet << '#' << std::setw(1) << std::setfill('0'); + // create a short color hexlet if there is any need for it + if (compressed && is_color_doublet(r, g, b) && a == 1) { + hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); + } else { + hexlet << std::hex << std::setw(2) << static_cast(r); + hexlet << std::hex << std::setw(2) << static_cast(g); + hexlet << std::hex << std::setw(2) << static_cast(b); + } + + return hexlet.str(); + + } std::string Color::to_string(bool compressed, int precision) const { std::stringstream ss; diff --git a/src/ast.hpp b/src/ast.hpp index 70c2f278d1..6263b7ae17 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -145,9 +145,23 @@ namespace Sass { virtual bool is_false() { return false; } virtual bool operator== (const Expression& rhs) const { return false; } virtual void set_delayed(bool delayed) { is_delayed(delayed); } + virtual std::string to_string(bool compressed = false, int precision = 5) const = 0; virtual size_t hash() { return 0; } }; + ////////////////////////////////////////////////////////////////////// + // Still just an expression, but with a to_string method + ////////////////////////////////////////////////////////////////////// + class PreValue : public Expression { + public: + PreValue(ParserState pstate, + bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) + : Expression(pstate, d, e, i, ct) + { } + virtual std::string to_string(bool compressed = false, int precision = 5) const = 0; + virtual ~PreValue() { } + }; + ////////////////////////////////////////////////////////////////////// // base class for values that support operations ////////////////////////////////////////////////////////////////////// @@ -846,8 +860,8 @@ namespace Sass { std::string type() { return is_arglist_ ? "arglist" : "list"; } static std::string type_name() { return "list"; } const char* sep_string(bool compressed = false) const { - return separator() == SASS_COMMA ? - (compressed ? "," : ", ") : " "; + return separator() == SASS_SPACE ? + " " : (compressed ? "," : ", "); } bool is_invisible() const { return empty(); } Expression* value_at_index(size_t i); @@ -915,7 +929,7 @@ namespace Sass { // operations. Templatized to avoid large switch statements and repetitive // subclassing. ////////////////////////////////////////////////////////////////////////// - class Binary_Expression : public Expression { + class Binary_Expression : public PreValue { private: ADD_HASHED(Operand, op) ADD_HASHED(Expression*, left) @@ -924,7 +938,7 @@ namespace Sass { public: Binary_Expression(ParserState pstate, Operand op, Expression* lhs, Expression* rhs) - : Expression(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) + : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) { } const std::string type_name() { switch (type()) { @@ -997,6 +1011,7 @@ namespace Sass { } return hash_; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; enum Sass_OP type() const { return op_.operand; } ATTACH_OPERATIONS() }; @@ -1046,6 +1061,7 @@ namespace Sass { }; return hash_; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1091,6 +1107,7 @@ namespace Sass { return hash_; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1113,6 +1130,7 @@ namespace Sass { has_rest_argument_(false), has_keyword_argument_(false) { } + virtual std::string to_string(bool compressed = false, int precision = 5) const; Argument* get_rest_argument(); Argument* get_keyword_argument(); @@ -1123,17 +1141,17 @@ namespace Sass { ////////////////// // Function calls. ////////////////// - class Function_Call : public Expression { + class Function_Call : public PreValue { ADD_HASHED(std::string, name) ADD_HASHED(Arguments*, arguments) ADD_PROPERTY(void*, cookie) size_t hash_; public: Function_Call(ParserState pstate, std::string n, Arguments* args, void* cookie) - : Expression(pstate), name_(n), arguments_(args), cookie_(cookie), hash_(0) + : PreValue(pstate), name_(n), arguments_(args), cookie_(cookie), hash_(0) { concrete_type(STRING); } Function_Call(ParserState pstate, std::string n, Arguments* args) - : Expression(pstate), name_(n), arguments_(args), cookie_(0), hash_(0) + : PreValue(pstate), name_(n), arguments_(args), cookie_(0), hash_(0) { concrete_type(STRING); } virtual bool operator==(const Expression& rhs) const @@ -1164,6 +1182,7 @@ namespace Sass { return hash_; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1177,17 +1196,18 @@ namespace Sass { Function_Call_Schema(ParserState pstate, String* n, Arguments* args) : Expression(pstate), name_(n), arguments_(args) { concrete_type(STRING); } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; /////////////////////// // Variable references. /////////////////////// - class Variable : public Expression { + class Variable : public PreValue { ADD_PROPERTY(std::string, name) public: Variable(ParserState pstate, std::string n) - : Expression(pstate), name_(n) + : PreValue(pstate), name_(n) { } virtual bool operator==(const Expression& rhs) const @@ -1208,6 +1228,7 @@ namespace Sass { { return std::hash()(name()); } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1251,6 +1272,7 @@ namespace Sass { } return hash_; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1330,6 +1352,7 @@ namespace Sass { } virtual bool operator== (const Expression& rhs) const; + virtual std::string to_hex(bool compressed = false, int precision = 5) const; virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() @@ -1513,6 +1536,7 @@ namespace Sass { : Expression(pstate), Vectorized(s), media_type_(t), is_negated_(n), is_restricted_(r) { } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1528,6 +1552,7 @@ namespace Sass { Expression* f, Expression* v, bool i = false) : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) { } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1554,6 +1579,7 @@ namespace Sass { : Expression(pstate) { } virtual bool needs_parens(Supports_Condition* cond) const { return false; } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1658,6 +1684,7 @@ namespace Sass { return false; } } + virtual std::string to_string(bool compressed = false, int precision = 5) const; ATTACH_OPERATIONS() }; @@ -1731,6 +1758,7 @@ namespace Sass { Thunk(ParserState pstate, Expression* exp, Env* env = 0) : Expression(pstate), expression_(exp), environment_(env) { } + virtual std::string to_string(bool compressed = false, int precision = 5) const; }; ///////////////////////////////////////////////////////// diff --git a/src/debugger.hpp b/src/debugger.hpp index 0f22eb8764..10cfd6f605 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -539,7 +539,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env) std::cerr << ind << "List " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : "Space ") << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map" : "Space ") << " [delayed: " << expression->is_delayed() << "] " << " [interpolant: " << expression->is_interpolant() << "] " << " [arglist: " << expression->is_arglist() << "] " << diff --git a/src/eval.cpp b/src/eval.cpp index c0e4f2ea90..38f8c4ce9b 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -412,7 +412,32 @@ namespace Sass { Expression* Eval::operator()(List* l) { + // special case for unevaluated map + if (l->separator() == SASS_HASH) { + Map* lm = SASS_MEMORY_NEW(ctx.mem, Map, + l->pstate(), + l->length() / 2); + for (size_t i = 0, L = l->length(); i < L; i += 2) + { + Expression* key = (*l)[i+0]->perform(this); + Expression* val = (*l)[i+1]->perform(this); + // make sure the color key never displays its real name + *lm << std::make_pair(key, val); + } + if (lm->has_duplicate_key()) { + To_String to_string(&ctx); + if (Color* col = dynamic_cast(lm->get_duplicate_key())) { + error("Duplicate key " + col->to_hex() + " in map (" + l->to_string() + ").", lm->pstate()); + } else { + error("Duplicate key \"" + lm->get_duplicate_key()->perform(&to_string) + "\" in map (" + l->to_string() + ").", lm->pstate()); + } + } + + return lm->perform(this); + } + // check if we should expand it if (l->is_expanded()) return l; + // regular case for unevaluated lists List* ll = SASS_MEMORY_NEW(ctx.mem, List, l->pstate(), l->length(), diff --git a/src/inspect.cpp b/src/inspect.cpp index e74e7f73c5..6fbbbb04ef 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -381,6 +381,8 @@ namespace Sass { else if (list->separator() == SASS_COMMA) in_comma_array = true; for (size_t i = 0, L = list->size(); i < L; ++i) { + if (list->separator() == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } Expression* list_item = (*list)[i]; if (list_item->is_invisible()) { continue; diff --git a/src/parser.cpp b/src/parser.cpp index 46cf89d799..b0dd4c5512 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -427,8 +427,11 @@ namespace Sass { bool is_keyword = false; Expression* val = parse_space_list(); val->is_delayed(false); + List* l = dynamic_cast(val); if (lex_css< exactly< ellipsis > >()) { - if (val->concrete_type() == Expression::MAP) is_keyword = true; + if (val->concrete_type() == Expression::MAP || ( + (l != NULL && l->separator() == SASS_HASH) + )) is_keyword = true; else is_arglist = true; } arg = SASS_MEMORY_NEW(ctx.mem, Argument, pstate, val, "", is_arglist, is_keyword); @@ -981,7 +984,7 @@ namespace Sass { Expression* Parser::parse_map() { Expression* key = parse_list(); - Map* map = SASS_MEMORY_NEW(ctx.mem, Map, pstate, 1); + List* map = SASS_MEMORY_NEW(ctx.mem, List, pstate, 0, SASS_HASH); if (String_Quoted* str = dynamic_cast(key)) { if (!str->quote_mark() && !str->is_delayed()) { if (const Color* col = name_to_color(str->value())) { @@ -999,7 +1002,7 @@ namespace Sass { Expression* value = parse_space_list(); - (*map) << std::make_pair(key, value); + (*map) << key << value; while (lex_css< exactly<','> >()) { @@ -1024,7 +1027,7 @@ namespace Sass { Expression* value = parse_space_list(); - (*map) << std::make_pair(key, value); + (*map) << key << value; } ParserState ps = map->pstate();