From f0b5f2270bb24a3b6c529743e45f394ff7a546d2 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 00:03:22 +0100 Subject: [PATCH 01/11] More improvements --- context.cpp | 3 ++- cssize.cpp | 7 +------ emitter.cpp | 7 +++++++ emitter.hpp | 2 ++ extend.cpp | 1 + output.cpp | 16 +++++++++------- output.hpp | 2 +- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/context.cpp b/context.cpp index 6ab06ce572..77729a27c9 100644 --- a/context.cpp +++ b/context.cpp @@ -279,7 +279,8 @@ namespace Sass { if (!root) return 0; root->perform(&emitter); emitter.finalize(); - string output = emitter.get_buffer(); + OutputBuffer emitted = emitter.get_buffer(); + string output = emitted.buffer; if (source_map_file != "" && !omit_source_map_url) { output += linefeed + format_source_mapping_url(source_map_file); } diff --git a/cssize.cpp b/cssize.cpp index ac2ec38e0b..b00225a370 100644 --- a/cssize.cpp +++ b/cssize.cpp @@ -102,17 +102,12 @@ namespace Sass { p_stack.pop_back(); Block* props = new (ctx.mem) Block(rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement* s = (*rr->block())[i]; - if (!bubblable(s)) *props << s; - } - Block* rules = new (ctx.mem) Block(rr->block()->pstate()); for (size_t i = 0, L = rr->block()->length(); i < L; i++) { Statement* s = (*rr->block())[i]; if (bubblable(s)) *rules << s; + if (!bubblable(s)) *props << s; } if (props->length()) diff --git a/emitter.cpp b/emitter.cpp index ea866af1ed..4aa385d3f4 100644 --- a/emitter.cpp +++ b/emitter.cpp @@ -85,6 +85,13 @@ namespace Sass { } } + // prepend some text or token to the buffer + void Emitter::prepend_string(const string& text) + { + // update source-map for new text + wbuf.buffer = text + wbuf.buffer; + } + // append some text or token to the buffer void Emitter::append_string(const string& text) { diff --git a/emitter.hpp b/emitter.hpp index 400e084232..2b96bc4525 100644 --- a/emitter.hpp +++ b/emitter.hpp @@ -62,6 +62,8 @@ namespace Sass { void finalize(void); // flush scheduled space/linefeed void flush_schedules(void); + // prepend some text or token to the buffer + void prepend_string(const string& text); // append some text or token to the buffer void append_string(const string& text); // append some white-space only text diff --git a/extend.cpp b/extend.cpp index 812d5e4c60..ce365560e4 100644 --- a/extend.cpp +++ b/extend.cpp @@ -235,6 +235,7 @@ namespace Sass { return os; } #endif + static bool parentSuperselector(Complex_Selector* pOne, Complex_Selector* pTwo, Context& ctx) { // TODO: figure out a better way to create a Complex_Selector from scratch // TODO: There's got to be a better way. This got ugly quick... diff --git a/output.cpp b/output.cpp index 4256bbd7de..fd5b670942 100644 --- a/output.cpp +++ b/output.cpp @@ -24,7 +24,7 @@ namespace Sass { top_imports.push_back(imp); } - string Output::get_buffer(void) + OutputBuffer Output::get_buffer(void) { Emitter emitter(ctx); @@ -45,16 +45,16 @@ namespace Sass { // flush scheduled outputs inspect.finalize(); // create combined buffer string - string buffer = inspect.buffer() - + this->buffer(); + wbuf.buffer = inspect.buffer() + + this->buffer(); // make sure we end with a linefeed - if (!ends_with(buffer, ctx->linefeed)) { + if (!ends_with(wbuf.buffer, ctx->linefeed)) { // if the output is not completely empty - if (!buffer.empty()) buffer += ctx->linefeed; + if (!wbuf.buffer.empty()) append_string(ctx->linefeed); } // search for unicode char - for(const char& chr : buffer) { + for(const char& chr : wbuf.buffer) { // skip all ascii chars if (chr >= 0) continue; // declare the charset @@ -67,7 +67,9 @@ namespace Sass { } // add charset as first line, before comments and imports - return (charset.empty() ? "" : charset) + buffer; + if (!charset.empty()) prepend_string(charset); + + return wbuf; } diff --git a/output.hpp b/output.hpp index f2cdd2c633..9417a05613 100644 --- a/output.hpp +++ b/output.hpp @@ -34,7 +34,7 @@ namespace Sass { vector top_comments; public: - string get_buffer(void); + OutputBuffer get_buffer(void); virtual void operator()(Ruleset*); // virtual void operator()(Propset*); From d68cc86549989b876d713deb8f0ec46ada927b78 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 04:12:22 +0100 Subject: [PATCH 02/11] Fix illegal extending accross media blocks Prints the exact error message as ruby sass. --- ast.hpp | 11 +++++++---- backtrace.hpp | 5 +---- debugger.hpp | 26 ++++++++++++++++++++++---- expand.cpp | 11 ++++++----- extend.cpp | 19 +++++++++++++++++-- parser.cpp | 17 ++++++++++++++--- parser.hpp | 5 +++-- sass_context.cpp | 20 +++++++++++++++++++- 8 files changed, 89 insertions(+), 25 deletions(-) diff --git a/ast.hpp b/ast.hpp index 46b20cb049..9db7f2fa20 100644 --- a/ast.hpp +++ b/ast.hpp @@ -1704,13 +1704,16 @@ namespace Sass { ADD_PROPERTY(bool, has_line_feed); // line break after list separator ADD_PROPERTY(bool, has_line_break); + // parent media block (for extend check) + ADD_PROPERTY(Media_Block*, media_block); public: Selector(ParserState pstate, bool r = false, bool h = false) : AST_Node(pstate), has_reference_(r), has_placeholder_(h), has_line_feed_(false), - has_line_break_(false) + has_line_break_(false), + media_block_(0) { } virtual ~Selector() = 0; // virtual Selector_Placeholder* find_placeholder(); @@ -1962,9 +1965,9 @@ namespace Sass { ADD_PROPERTY(Complex_Selector*, tail); public: Complex_Selector(ParserState pstate, - Combinator c, - Compound_Selector* h, - Complex_Selector* t) + Combinator c, + Compound_Selector* h, + Complex_Selector* t) : Selector(pstate), combinator_(c), head_(h), tail_(t) { if ((h && h->has_reference()) || (t && t->has_reference())) has_reference(true); diff --git a/backtrace.hpp b/backtrace.hpp index e59b5a9892..2c8eed2607 100644 --- a/backtrace.hpp +++ b/backtrace.hpp @@ -3,11 +3,8 @@ #include -#include "position.hpp" - -#ifndef SASS_FILE #include "file.hpp" -#endif +#include "position.hpp" namespace Sass { diff --git a/debugger.hpp b/debugger.hpp index a55212d4bf..06a0b63376 100644 --- a/debugger.hpp +++ b/debugger.hpp @@ -37,7 +37,13 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) if (root_block->block()) for(auto i : root_block->block()->elements()) { debug_ast(i, ind + " ", env); } } else if (dynamic_cast(node)) { Selector_List* selector = dynamic_cast(node); - cerr << ind << "Selector_List " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + + cerr << ind << "Selector_List " << selector + << " [mq:" << selector->media_block() << "]" + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << endl; + for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } // } else if (dynamic_cast(node)) { @@ -46,7 +52,10 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) } else if (dynamic_cast(node)) { Complex_Selector* selector = dynamic_cast(node); - cerr << ind << "Complex_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << " -> "; + cerr << ind << "Complex_Selector " << selector + << " [mq:" << selector->media_block() << "]" + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") << " -> "; switch (selector->combinator()) { case Complex_Selector::PARENT_OF: cerr << "{>}"; break; case Complex_Selector::PRECEDES: cerr << "{~}"; break; @@ -58,7 +67,10 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) debug_ast(selector->tail(), ind + "-", env); } else if (dynamic_cast(node)) { Compound_Selector* selector = dynamic_cast(node); - cerr << ind << "Compound_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << + cerr << ind << "Compound_Selector " << selector + << " [mq:" << selector->media_block() << "]" + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") << " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } } else if (dynamic_cast(node)) { @@ -85,8 +97,14 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) cerr << ind << "Type_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; } else if (dynamic_cast(node)) { + Selector_Placeholder* selector = dynamic_cast(node); - cerr << ind << "Selector_Placeholder [" << selector->name() << "] " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + cerr << ind << "Selector_Placeholder [" << selector->name() << "] " << selector + << " [mq:" << selector->media_block() << "]" + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << endl; + } else if (dynamic_cast(node)) { Selector_Reference* selector = dynamic_cast(node); cerr << ind << "Selector_Reference " << selector << " @ref " << selector->selector() << endl; diff --git a/expand.cpp b/expand.cpp index a8a30de67e..88386b08f0 100644 --- a/expand.cpp +++ b/expand.cpp @@ -70,22 +70,23 @@ namespace Sass { string str = isp.get_buffer(); str += ";"; - Parser p(ctx, ParserState("[REPARSE]", 0)); + Parser p(ctx, r->pstate()); + p.last_media_block = r->selector() ? r->selector()->media_block() : 0; p.source = str.c_str(); p.position = str.c_str(); p.end = str.c_str() + strlen(str.c_str()); Selector_List* sel_lst = p.parse_selector_group(); - sel_lst->pstate(isp.remap(sel_lst->pstate())); + // sel_lst->pstate(isp.remap(sel_lst->pstate())); for(size_t i = 0; i < sel_lst->length(); i++) { Complex_Selector* pIter = (*sel_lst)[i]; while (pIter) { Compound_Selector* pHead = pIter->head(); - pIter->pstate(isp.remap(pIter->pstate())); + // pIter->pstate(isp.remap(pIter->pstate())); if (pHead) { - pHead->pstate(isp.remap(pHead->pstate())); - (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate())); + // pHead->pstate(isp.remap(pHead->pstate())); + // (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate())); } pIter = pIter->tail(); } diff --git a/extend.cpp b/extend.cpp index ce365560e4..7c2bdc38c1 100644 --- a/extend.cpp +++ b/extend.cpp @@ -1675,7 +1675,21 @@ namespace Sass { if (pHead) { SubsetMapEntries entries = subsetMap.get_v(pHead->to_str_vec()); - + for (ExtensionPair ext : entries) { + // check if both selectors have the same media block parent + if (ext.first->media_block() == pComplexSelector->media_block()) continue; + stringstream err; + To_String to_string(&ctx); + string cwd(Sass::File::get_cwd()); + // make path relative to the current directory + ParserState pstate(ext.second->pstate()); + string rel_path(Sass::File::resolve_relative_path(pstate.path, cwd, cwd)); + err << "You may not @extend an outer selector from within @media.\n"; + err << "You may only @extend selectors within the same directive.\n"; + err << "From \"@extend " << ext.second->perform(&to_string) << "\""; + err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; + error(err.str(), pComplexSelector->pstate()); + } hasExtension = entries.size() > 0; } @@ -1706,10 +1720,11 @@ namespace Sass { pComplexSelector->tail()->has_line_feed(pComplexSelector->has_line_feed()); Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); - +// debug_ast(pComplexSelector->parent()); DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) Node extendedNotExpanded = Node::createCollection(); + // extendedNotExpanded.media_block = pComplexSelector->media_block(); for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(), complexSelIterEnd = complexSelector.collection()->end(); complexSelIter != complexSelIterEnd; ++complexSelIter) { Node& sseqOrOp = *complexSelIter; diff --git a/parser.cpp b/parser.cpp index 6218a45533..5d32ad78ef 100644 --- a/parser.cpp +++ b/parser.cpp @@ -458,6 +458,7 @@ namespace Sass { To_String to_string(&ctx); lex< optional_spaces_and_comments >(); Selector_List* group = new (ctx.mem) Selector_List(pstate); + group->media_block(last_media_block); do { reloop = false; if (peek< exactly<'{'> >() || @@ -470,6 +471,7 @@ namespace Sass { ParserState sel_source_position = pstate; Selector_Reference* ref = new (ctx.mem) Selector_Reference(sel_source_position); Compound_Selector* ref_wrap = new (ctx.mem) Compound_Selector(sel_source_position); + ref_wrap->media_block(last_media_block); (*ref_wrap) << ref; if (!comb->head()) { comb->head(ref_wrap); @@ -477,6 +479,7 @@ namespace Sass { } else { comb = new (ctx.mem) Complex_Selector(sel_source_position, Complex_Selector::ANCESTOR_OF, ref_wrap, comb); + comb->media_block(last_media_block); comb->has_reference(true); } if (peek_newline()) ref_wrap->has_line_break(true); @@ -538,6 +541,7 @@ namespace Sass { } if (!sel_source_position.line) sel_source_position = before_token; Complex_Selector* cpx = new (ctx.mem) Complex_Selector(ParserState(path, sel_source_position, Offset(0, 0)), cmb, lhs, rhs); + cpx->media_block(last_media_block); if (cpx_lf) cpx->has_line_break(cpx_lf); return cpx; } @@ -545,6 +549,7 @@ namespace Sass { Compound_Selector* Parser::parse_simple_selector_sequence() { Compound_Selector* seq = new (ctx.mem) Compound_Selector(pstate); + seq->media_block(last_media_block); bool sawsomething = false; if (lex< exactly<'&'> >()) { // if you see a & @@ -600,7 +605,9 @@ namespace Sass { return parse_attribute_selector(); } else if (lex< placeholder >()) { - return new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed)); + Selector_Placeholder* sel = new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed)); + sel->media_block(last_media_block); + return sel; } else { error("invalid selector after " + lexed.to_string(), pstate); @@ -1643,9 +1650,13 @@ namespace Sass { if (!peek< exactly<'{'> >()) { error("expected '{' in media query", pstate); } - Block* block = parse_block(); + Media_Block* media_block = new (ctx.mem) Media_Block(media_source_position, media_queries, 0); + Media_Block* prev_media_block = last_media_block; + last_media_block = media_block; + media_block->block(parse_block()); + last_media_block = prev_media_block; - return new (ctx.mem) Media_Block(media_source_position, media_queries, block); + return media_block; } List* Parser::parse_media_queries() diff --git a/parser.hpp b/parser.hpp index fbefbb5c32..a0b3404536 100644 --- a/parser.hpp +++ b/parser.hpp @@ -32,6 +32,7 @@ namespace Sass { Context& ctx; vector stack; + Media_Block* last_media_block; const char* source; const char* position; const char* end; @@ -44,8 +45,8 @@ namespace Sass { Token lexed; bool in_at_root; - Parser(Context& ctx, ParserState pstate) - : ParserState(pstate), ctx(ctx), stack(vector()), + Parser(Context& ctx, const ParserState& pstate) + : ParserState(pstate), ctx(ctx), stack(0), last_media_block(0), source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate("[NULL]"), indentation(0) { in_at_root = false; stack.push_back(nothing); } diff --git a/sass_context.cpp b/sass_context.cpp index 02f2efbb58..8f2badb175 100644 --- a/sass_context.cpp +++ b/sass_context.cpp @@ -8,6 +8,7 @@ #include #include +#include "file.hpp" #include "json.hpp" #include "util.hpp" #include "context.hpp" @@ -224,13 +225,30 @@ extern "C" { } catch (Sass_Error& e) { stringstream msg_stream; + string cwd(Sass::File::get_cwd()); JsonNode* json_err = json_mkobject(); json_append_member(json_err, "status", json_mknumber(1)); json_append_member(json_err, "file", json_mkstring(e.pstate.path.c_str())); json_append_member(json_err, "line", json_mknumber(e.pstate.line+1)); json_append_member(json_err, "column", json_mknumber(e.pstate.column+1)); json_append_member(json_err, "message", json_mkstring(e.message.c_str())); - msg_stream << e.pstate.path << ":" << e.pstate.line+1 << ": " << e.message << endl; + string rel_path(Sass::File::resolve_relative_path(e.pstate.path, cwd, cwd)); + + string msg_prefix("Error: "); + bool got_newline = false; + msg_stream << msg_prefix; + for (char chr : e.message) { + if (chr == '\n') { + got_newline = true; + } else if (got_newline) { + msg_stream << string(msg_prefix.size(), ' '); + got_newline = false; + } + msg_stream << chr; + } + if (!got_newline) msg_stream << "\n"; + msg_stream << string(msg_prefix.size(), ' '); + msg_stream << " on line " << e.pstate.line+1 << " of " << rel_path << "\n"; c_ctx->error_json = json_stringify(json_err, " ");; c_ctx->error_message = sass_strdup(msg_stream.str().c_str()); c_ctx->error_text = strdup(e.message.c_str()); From a61ec99d7446acc77d0687f167b42f85422e7f4d Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 05:07:33 +0100 Subject: [PATCH 03/11] Fix illegal extending when no blocks are found Prints the exact error message as ruby sass. Implements optional flag to suppress message. --- ast.hpp | 3 +++ debugger.hpp | 4 ++++ expand.cpp | 5 +++-- extend.cpp | 26 ++++++++++++++++++++++++-- parser.cpp | 4 +++- subset_map.hpp | 1 + 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/ast.hpp b/ast.hpp index 9db7f2fa20..ce2a8f82b6 100644 --- a/ast.hpp +++ b/ast.hpp @@ -1704,6 +1704,8 @@ namespace Sass { ADD_PROPERTY(bool, has_line_feed); // line break after list separator ADD_PROPERTY(bool, has_line_break); + // maybe we have optional flag + ADD_PROPERTY(bool, is_optional); // parent media block (for extend check) ADD_PROPERTY(Media_Block*, media_block); public: @@ -1713,6 +1715,7 @@ namespace Sass { has_placeholder_(h), has_line_feed_(false), has_line_break_(false), + is_optional_(false), media_block_(0) { } virtual ~Selector() = 0; diff --git a/debugger.hpp b/debugger.hpp index 06a0b63376..e0443e4bcd 100644 --- a/debugger.hpp +++ b/debugger.hpp @@ -40,6 +40,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) cerr << ind << "Selector_List " << selector << " [mq:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; @@ -54,6 +55,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) Complex_Selector* selector = dynamic_cast(node); cerr << ind << "Complex_Selector " << selector << " [mq:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << " -> "; switch (selector->combinator()) { @@ -69,6 +71,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) Compound_Selector* selector = dynamic_cast(node); cerr << ind << "Compound_Selector " << selector << " [mq:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << " <" << prettyprint(selector->pstate().token.ws_before()) << "> X <" << prettyprint(selector->pstate().token.ws_after()) << ">" << endl; @@ -101,6 +104,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) Selector_Placeholder* selector = dynamic_cast(node); cerr << ind << "Selector_Placeholder [" << selector->name() << "] " << selector << " [mq:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; diff --git a/expand.cpp b/expand.cpp index 88386b08f0..ef4f6dd744 100644 --- a/expand.cpp +++ b/expand.cpp @@ -404,7 +404,8 @@ namespace Sass { To_String to_string(&ctx); Selector_List* extender = static_cast(selector_stack.back()); if (!extender) return 0; - Selector_List* extendee = static_cast(e->selector()->perform(contextualize->with(0, env, backtrace))); + Selector_List* org_extendee = static_cast(e->selector()); + Selector_List* extendee = static_cast(org_extendee->perform(contextualize->with(0, env, backtrace))); if (extendee->length() != 1) { error("selector groups may not be extended", extendee->pstate(), backtrace); } @@ -413,7 +414,7 @@ namespace Sass { error("nested selectors may not be extended", c->pstate(), backtrace); } Compound_Selector* s = c->head(); - + s->is_optional(org_extendee->is_optional()); // // need to convert the compound selector into a by-value data structure // vector target_vec; // for (size_t i = 0, L = s->length(); i < L; ++i) diff --git a/extend.cpp b/extend.cpp index 7c2bdc38c1..6fb07c714d 100644 --- a/extend.cpp +++ b/extend.cpp @@ -5,6 +5,7 @@ #include "backtrace.hpp" #include "paths.hpp" #include "parser.hpp" +#include "debugger.hpp" #include "node.hpp" #include "sass_util.hpp" #include "debug.hpp" @@ -1678,10 +1679,10 @@ namespace Sass { for (ExtensionPair ext : entries) { // check if both selectors have the same media block parent if (ext.first->media_block() == pComplexSelector->media_block()) continue; + // fail if one goes across media block boundaries stringstream err; To_String to_string(&ctx); string cwd(Sass::File::get_cwd()); - // make path relative to the current directory ParserState pstate(ext.second->pstate()); string rel_path(Sass::File::resolve_relative_path(pstate.path, cwd, cwd)); err << "You may not @extend an outer selector from within @media.\n"; @@ -1690,12 +1691,33 @@ namespace Sass { err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; error(err.str(), pComplexSelector->pstate()); } - hasExtension = entries.size() > 0; + if (entries.size() > 0) hasExtension = true; } pIter = pIter->tail(); } + if (!hasExtension) { + stringstream err; + To_String to_string(&ctx); + string cwd(Sass::File::get_cwd()); + string sel1(pComplexSelector->perform(&to_string)); + Compound_Selector* pExtendSelector = 0; + for (auto i : subsetMap.values()) { + if (i.first == pComplexSelector) { + pExtendSelector = i.second; + break; + } + } + if (!pExtendSelector->is_optional()) { + string sel2(pExtendSelector ? pExtendSelector->perform(&to_string) : "[unknown]"); + err << "\"" << sel1 << "\" failed to @extend \"" << sel2 << "\"\n"; + err << "The selector \"" << sel2 << "\" was not found.\n"; + err << "Use \"@extend " << sel2 << " !optional\" if the extend should be able to fail."; + error(err.str(), pExtendSelector ? pExtendSelector->pstate() : pComplexSelector->pstate()); + } + } + return hasExtension; } diff --git a/parser.cpp b/parser.cpp index 5d32ad78ef..8debeb7b4c 100644 --- a/parser.cpp +++ b/parser.cpp @@ -497,7 +497,9 @@ namespace Sass { (*group) << comb; } while (reloop); - while (lex< optional >()); // JMA - ignore optional flag if it follows the selector group + while (lex< optional >()) { + group->is_optional(true); + } return group; } diff --git a/subset_map.hpp b/subset_map.hpp index b9ce01a23b..580c0cc145 100644 --- a/subset_map.hpp +++ b/subset_map.hpp @@ -82,6 +82,7 @@ namespace Sass { vector get_v(const vector& s); bool empty() { return values_.empty(); } void clear() { values_.clear(); hash_.clear(); } + const vector values(void) { return values_; } }; template From 49862a68ee74caa332373c21f454f402661967e2 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 06:13:45 +0100 Subject: [PATCH 04/11] Parse line comments in blocks Fixes https://github.com/sass/libsass/issues/533 --- eval.cpp | 4 ++-- parser.cpp | 5 ++++- parser.hpp | 3 +++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/eval.cpp b/eval.cpp index 44e6c1e2f8..54168a5f62 100644 --- a/eval.cpp +++ b/eval.cpp @@ -604,7 +604,7 @@ namespace Sass { string name(v->name()); Expression* value = 0; if (env->has(name)) value = static_cast((*env)[name]); - else error("unbound variable " + v->name(), v->pstate()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate()); // cerr << "name: " << v->name() << "; type: " << typeid(*value).name() << "; value: " << value->perform(&to_string) << endl; if (typeid(*value) == typeid(Argument)) value = static_cast(value)->value(); @@ -770,7 +770,7 @@ namespace Sass { } else if (Variable* var = dynamic_cast(s)) { string name(var->name()); - if (!env->has(name)) return name; + if (!env->has(name)) error("Undefined variable: \"" + var->name() + "\".", var->pstate()); Expression* value = static_cast((*env)[name]); return evacuate_quotes(interpolation(value)); diff --git a/parser.cpp b/parser.cpp index 8debeb7b4c..8331470ee0 100644 --- a/parser.cpp +++ b/parser.cpp @@ -728,7 +728,7 @@ namespace Sass { bool semicolon = false; Selector_Lookahead lookahead_result; Block* block = new (ctx.mem) Block(pstate); - + lex< spaces_and_comments >(); // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; @@ -775,6 +775,9 @@ namespace Sass { (*block) << parse_assignment(); semicolon = true; } + else if (lex< line_comment >()) { + // throw line comments away + } else if (peek< if_directive >()) { (*block) << parse_if_directive(); } diff --git a/parser.hpp b/parser.hpp index a0b3404536..f45baa2447 100644 --- a/parser.hpp +++ b/parser.hpp @@ -147,6 +147,9 @@ namespace Sass { else if (mx == optional_spaces_and_comments) { it_before_token = position; } + else if (mx == spaces_and_comments) { + it_before_token = position; + } else if (mx == optional_spaces) { // ToDo: what are optiona_spaces ??? From f65aa5f46e58f6a798c7b61c1def796592dd5ee0 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 06:43:54 +0100 Subject: [PATCH 05/11] Add another fix for media query boundaries Evaluate the actual queries against each other. We probably need to implement a better test here! --- ast.cpp | 4 +++- contextualize.cpp | 3 +++ debugger.hpp | 15 ++++++++++++++- extend.cpp | 15 +++++++++++++-- parser.cpp | 2 +- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/ast.cpp b/ast.cpp index 51801f8ba0..064a6c6a94 100644 --- a/ast.cpp +++ b/ast.cpp @@ -414,7 +414,9 @@ namespace Sass { { if (!tail()) return 0; if (!head()) return tail()->context(ctx); - return new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx)); + Complex_Selector* cpy = new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx)); + cpy->media_block(media_block()); + return cpy; } Complex_Selector* Complex_Selector::innermost() diff --git a/contextualize.cpp b/contextualize.cpp index 9e7d8a4985..c0ac5fa12a 100644 --- a/contextualize.cpp +++ b/contextualize.cpp @@ -64,6 +64,7 @@ namespace Sass { { To_String to_string(&ctx); Complex_Selector* ss = new (ctx.mem) Complex_Selector(*s); + // ss->media_block(s->media_block()); Compound_Selector* new_head = 0; Complex_Selector* new_tail = 0; if (ss->head()) { @@ -72,6 +73,7 @@ namespace Sass { } if (ss->tail()) { new_tail = static_cast(s->tail()->perform(this)); + // new_tail->media_block(s->media_block()); ss->tail(new_tail); } if ((new_head && new_head->has_placeholder()) || (new_tail && new_tail->has_placeholder())) { @@ -95,6 +97,7 @@ namespace Sass { return extender; } Compound_Selector* ss = new (ctx.mem) Compound_Selector(s->pstate(), s->length()); + ss->media_block(s->media_block()); ss->has_line_break(s->has_line_break()); for (size_t i = 0, L = s->length(); i < L; ++i) { Simple_Selector* simp = static_cast((*s)[i]->perform(this)); diff --git a/debugger.hpp b/debugger.hpp index e0443e4bcd..6843626090 100644 --- a/debugger.hpp +++ b/debugger.hpp @@ -124,10 +124,23 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) } else if (dynamic_cast(node)) { Selector* selector = dynamic_cast(node); - cerr << ind << "Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + cerr << ind << "Selector " << selector + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << endl; + } else if (dynamic_cast(node)) { + Media_Query* block = dynamic_cast(node); + cerr << ind << "Media_Query " << block + << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << endl; + debug_ast(block->media_type(), ind + " "); + } else if (dynamic_cast(node)) { Media_Block* block = dynamic_cast(node); cerr << ind << "Media_Block " << block << " " << block->tabs() << endl; + debug_ast(block->media_queries(), ind + " =@ "); + debug_ast(block->selector(), ind + " -@ "); if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); } } else if (dynamic_cast(node)) { Feature_Block* block = dynamic_cast(node); diff --git a/extend.cpp b/extend.cpp index 6fb07c714d..60aa95cb3f 100644 --- a/extend.cpp +++ b/extend.cpp @@ -1580,6 +1580,7 @@ namespace Sass { // out and aren't operated on. Complex_Selector* pNewSelector = pExtComplexSelector->cloneFully(ctx); Complex_Selector* pNewInnerMost = new (ctx.mem) Complex_Selector(pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); + // pNewInnerMost->media_block(pSelector->media_block()); Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); pNewSelector->set_innermost(pNewInnerMost, combinator); @@ -1679,9 +1680,17 @@ namespace Sass { for (ExtensionPair ext : entries) { // check if both selectors have the same media block parent if (ext.first->media_block() == pComplexSelector->media_block()) continue; + To_String to_string(&ctx); + if (ext.second->media_block() && ext.second->media_block()->media_queries() && + pComplexSelector->media_block() && pComplexSelector->media_block()->media_queries()) + { + string query_left(ext.second->media_block()->media_queries()->perform(&to_string)); + string query_right(pComplexSelector->media_block()->media_queries()->perform(&to_string)); + if (query_left == query_right) continue; + } + // fail if one goes across media block boundaries stringstream err; - To_String to_string(&ctx); string cwd(Sass::File::get_cwd()); ParserState pstate(ext.second->pstate()); string rel_path(Sass::File::resolve_relative_path(pstate.path, cwd, cwd)); @@ -1698,6 +1707,7 @@ namespace Sass { } if (!hasExtension) { + /* ToDo: don't break stuff stringstream err; To_String to_string(&ctx); string cwd(Sass::File::get_cwd()); @@ -1709,13 +1719,14 @@ namespace Sass { break; } } - if (!pExtendSelector->is_optional()) { + if (!pExtendSelector || !pExtendSelector->is_optional()) { string sel2(pExtendSelector ? pExtendSelector->perform(&to_string) : "[unknown]"); err << "\"" << sel1 << "\" failed to @extend \"" << sel2 << "\"\n"; err << "The selector \"" << sel2 << "\" was not found.\n"; err << "Use \"@extend " << sel2 << " !optional\" if the extend should be able to fail."; error(err.str(), pExtendSelector ? pExtendSelector->pstate() : pComplexSelector->pstate()); } + */ } return hasExtension; diff --git a/parser.cpp b/parser.cpp index 8331470ee0..ba66a12af3 100644 --- a/parser.cpp +++ b/parser.cpp @@ -728,7 +728,7 @@ namespace Sass { bool semicolon = false; Selector_Lookahead lookahead_result; Block* block = new (ctx.mem) Block(pstate); - lex< spaces_and_comments >(); + // lex< zero_plus < alternatives < space, line_comment > > >(); // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; From b5235ec54021b49411bd2c8db6584c9a5e97659a Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 08:19:14 +0100 Subject: [PATCH 06/11] Improve comment parsing --- parser.cpp | 23 +++++++++++++++++++---- prelexer.cpp | 6 +++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/parser.cpp b/parser.cpp index ba66a12af3..4d5093df74 100644 --- a/parser.cpp +++ b/parser.cpp @@ -178,6 +178,7 @@ namespace Sass { Import* imp = new (ctx.mem) Import(pstate); bool first = true; do { + while (lex< block_comment >()); if (lex< quoted_string >()) { string import_path(lexed); @@ -279,8 +280,10 @@ namespace Sass { // if there's anything there at all if (!peek< exactly<')'> >()) { do (*params) << parse_parameter(); - while (lex< exactly<','> >()); + while (lex< alternatives < spaces,block_comment, exactly<','> > >()); } + while (lex< alternatives < spaces, block_comment > >()); + if (!peek< exactly<')'> >()) cerr << "BALAB [" << position << "]\n"; if (!lex< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, pstate); } return params; @@ -288,12 +291,15 @@ namespace Sass { Parameter* Parser::parse_parameter() { + while (lex< alternatives < spaces, block_comment > >()); lex< variable >(); string name(Util::normalize_underscores(lexed)); ParserState pos = pstate; Expression* val = 0; bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); val = parse_space_list(); val->is_delayed(false); } @@ -328,8 +334,10 @@ namespace Sass { // if there's anything there at all if (!peek< exactly<')'> >()) { do (*args) << parse_argument(); - while (lex< exactly<','> >()); + while (lex< alternatives < block_comment, exactly<','> > >()); } + while (lex< block_comment >()); + if (!peek< exactly<')'> >()) cerr << "BALAB [" << position << "]\n"; if (!lex< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, pstate); } @@ -339,10 +347,12 @@ namespace Sass { Argument* Parser::parse_argument() { Argument* arg; - if (peek< sequence < variable, optional_spaces_and_comments, exactly<':'> > >()) { + while (lex< alternatives < spaces, block_comment > >()); + if (peek< sequence < variable, zero_plus < alternatives < spaces, line_comment, block_comment > >, exactly<':'> > >()) { lex< variable >(); string name(Util::normalize_underscores(lexed)); ParserState p = pstate; + while (lex< alternatives < spaces, block_comment > >()); lex< exactly<':'> >(); Expression* val = parse_space_list(); val->is_delayed(false); @@ -728,7 +738,7 @@ namespace Sass { bool semicolon = false; Selector_Lookahead lookahead_result; Block* block = new (ctx.mem) Block(pstate); - // lex< zero_plus < alternatives < space, line_comment > > >(); + lex< zero_plus < alternatives < space, line_comment > > >(); // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { bool is_important = lexed.begin[2] == '!'; @@ -1128,6 +1138,7 @@ namespace Sass { peek< exactly<'%'> >(position))) { return fact1; } + while (lex< block_comment >()); vector operands; vector operators; while (lex< exactly<'*'> >() || lex< exactly<'/'> >() || lex< exactly<'%'> >()) { @@ -1194,6 +1205,10 @@ namespace Sass { Expression* Parser::parse_value() { + + // ToDo: Move where + while (lex< block_comment >()); + if (lex< uri_prefix >()) { Arguments* args = new (ctx.mem) Arguments(pstate); Function_Call* result = new (ctx.mem) Function_Call(pstate, "url", args); diff --git a/prelexer.cpp b/prelexer.cpp index 64dbdb0899..189987e308 100644 --- a/prelexer.cpp +++ b/prelexer.cpp @@ -59,7 +59,7 @@ namespace Sass { } // Match either comment. const char* comment(const char* src) { - return alternatives(src); + return line_comment(src); } const char* wspaces(const char* src) { @@ -100,10 +100,10 @@ namespace Sass { const char* optional_spaces(const char* src) { return optional(src); } // const char* optional_comment(const char* src) { return optional(src); } const char* optional_spaces_and_comments(const char* src) { - return zero_plus< alternatives >(src); + return zero_plus< alternatives >(src); } const char* spaces_and_comments(const char* src) { - return one_plus< alternatives >(src); + return one_plus< alternatives >(src); } const char* no_spaces(const char* src) { return negate< spaces >(src); From 0e52c1c167eb81ba4a502224e1e3d6c28ef57d9f Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Sun, 8 Mar 2015 09:41:45 +0100 Subject: [PATCH 07/11] Fix interpolation in media queries Fixes https://github.com/sass/libsass/issues/346 --- debugger.hpp | 11 +++++++++++ parser.cpp | 11 +++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/debugger.hpp b/debugger.hpp index 6843626090..7f1194be95 100644 --- a/debugger.hpp +++ b/debugger.hpp @@ -1,6 +1,7 @@ #ifndef SASS_DEBUGGER_H #define SASS_DEBUGGER_H +#include #include "ast_fwd_decl.hpp" using namespace std; @@ -128,6 +129,15 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl; + + } else if (dynamic_cast(node)) { + Media_Query_Expression* block = dynamic_cast(node); + cerr << ind << "Media_Query_Expression " << block + << (block->is_interpolated() ? " [is_interpolated]": " -") + << endl; + debug_ast(block->feature(), ind + " f) "); + debug_ast(block->value(), ind + " v) "); + } else if (dynamic_cast(node)) { Media_Query* block = dynamic_cast(node); cerr << ind << "Media_Query " << block @@ -135,6 +145,7 @@ inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0) << (block->is_restricted() ? " [is_restricted]": " -") << endl; debug_ast(block->media_type(), ind + " "); + for(auto i : block->elements()) { debug_ast(i, ind + " ", env); } } else if (dynamic_cast(node)) { Media_Block* block = dynamic_cast(node); diff --git a/parser.cpp b/parser.cpp index 4d5093df74..c3e9c0f01c 100644 --- a/parser.cpp +++ b/parser.cpp @@ -1696,11 +1696,18 @@ namespace Sass { else if (lex< exactly< only_kwd > >()) media_query->is_restricted(true); if (peek< identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex< identifier >()) media_query->media_type(new (ctx.mem) String_Quoted(pstate, lexed)); + else if (lex< identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); else (*media_query) << parse_media_expression(); while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression(); - + if (peek< identifier_schema >()) { + String_Schema* schema = new (ctx.mem) String_Schema(pstate); + *schema << media_query->media_type(); + *schema << new (ctx.mem) String_Constant(pstate, " "); + *schema << parse_identifier_schema(); + media_query->media_type(schema); + } + while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression(); return media_query; } From c64f22804b6d6bf95d18543b8664980fb9270e31 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Mon, 9 Mar 2015 01:04:18 +0100 Subject: [PATCH 08/11] Add error for parent selectors in root blocks Adds block stack for parser to do this check! Fixes https://github.com/sass/libsass/issues/325 --- parser.cpp | 5 +++++ parser.hpp | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/parser.cpp b/parser.cpp index c3e9c0f01c..1156985e24 100644 --- a/parser.cpp +++ b/parser.cpp @@ -565,6 +565,9 @@ namespace Sass { bool sawsomething = false; if (lex< exactly<'&'> >()) { // if you see a & + if (block_stack.size() == 0) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", pstate); + } (*seq) << new (ctx.mem) Selector_Reference(pstate); sawsomething = true; // if you see a space after a &, then you're done @@ -738,6 +741,7 @@ namespace Sass { bool semicolon = false; Selector_Lookahead lookahead_result; Block* block = new (ctx.mem) Block(pstate); + block_stack.push_back(block); lex< zero_plus < alternatives < space, line_comment > > >(); // JMA - ensure that a block containing only block_comments is parsed while (lex< block_comment >()) { @@ -909,6 +913,7 @@ namespace Sass { (*block) << comment; } } + block_stack.pop_back(); return block; } diff --git a/parser.hpp b/parser.hpp index f45baa2447..757b7b855f 100644 --- a/parser.hpp +++ b/parser.hpp @@ -31,6 +31,7 @@ namespace Sass { enum Syntactic_Context { nothing, mixin_def, function_def }; Context& ctx; + vector block_stack; vector stack; Media_Block* last_media_block; const char* source; @@ -46,7 +47,7 @@ namespace Sass { bool in_at_root; Parser(Context& ctx, const ParserState& pstate) - : ParserState(pstate), ctx(ctx), stack(0), last_media_block(0), + : ParserState(pstate), ctx(ctx), block_stack(0), stack(0), last_media_block(0), source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate("[NULL]"), indentation(0) { in_at_root = false; stack.push_back(nothing); } From 5606d2628ef39b77cda022b833b17aa449f835f8 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Mon, 9 Mar 2015 02:50:46 +0100 Subject: [PATCH 09/11] Implement number prefix parsing (/[\+\-]+/) Fixes https://github.com/sass/libsass/issues/535 --- extend.cpp | 4 ---- parser.cpp | 24 ++++++++++++++++++++---- parser.hpp | 1 + prelexer.cpp | 12 ++++++++++++ prelexer.hpp | 2 ++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/extend.cpp b/extend.cpp index 60aa95cb3f..0e1a7465a6 100644 --- a/extend.cpp +++ b/extend.cpp @@ -5,7 +5,6 @@ #include "backtrace.hpp" #include "paths.hpp" #include "parser.hpp" -#include "debugger.hpp" #include "node.hpp" #include "sass_util.hpp" #include "debug.hpp" @@ -1580,7 +1579,6 @@ namespace Sass { // out and aren't operated on. Complex_Selector* pNewSelector = pExtComplexSelector->cloneFully(ctx); Complex_Selector* pNewInnerMost = new (ctx.mem) Complex_Selector(pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); - // pNewInnerMost->media_block(pSelector->media_block()); Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); pNewSelector->set_innermost(pNewInnerMost, combinator); @@ -1753,11 +1751,9 @@ namespace Sass { pComplexSelector->tail()->has_line_feed(pComplexSelector->has_line_feed()); Node complexSelector = complexSelectorToNode(pComplexSelector, ctx); -// debug_ast(pComplexSelector->parent()); DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) Node extendedNotExpanded = Node::createCollection(); - // extendedNotExpanded.media_block = pComplexSelector->media_block(); for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(), complexSelIterEnd = complexSelector.collection()->end(); complexSelIter != complexSelIterEnd; ++complexSelIter) { Node& sseqOrOp = *complexSelIter; diff --git a/parser.cpp b/parser.cpp index 1156985e24..5eb1e94de3 100644 --- a/parser.cpp +++ b/parser.cpp @@ -402,7 +402,6 @@ namespace Sass { property_segment = new (ctx.mem) String_Quoted(pstate, lexed); } Propset* propset = new (ctx.mem) Propset(pstate, property_segment); - // debug_ast(property_segment); lex< exactly<':'> >(); if (!peek< exactly<'{'> >()) error("expected a '{' after namespaced property", pstate); @@ -941,6 +940,22 @@ namespace Sass { } } + // parse +/- and return false if negative + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + Expression* Parser::parse_map() { To_String to_string(&ctx); @@ -1203,6 +1218,10 @@ namespace Sass { else if (lex< sequence< not_op, spaces_and_comments > >()) { return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::NOT, parse_factor()); } + else if (peek < sequence < one_plus < alternatives < spaces_and_comments, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return parse_value(); // prefix is positive + return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::MINUS, parse_value()); + } else { return parse_value(); } @@ -1210,10 +1229,7 @@ namespace Sass { Expression* Parser::parse_value() { - - // ToDo: Move where while (lex< block_comment >()); - if (lex< uri_prefix >()) { Arguments* args = new (ctx.mem) Arguments(pstate); Function_Call* result = new (ctx.mem) Function_Call(pstate, "url", args); diff --git a/parser.hpp b/parser.hpp index 757b7b855f..8f6a061adf 100644 --- a/parser.hpp +++ b/parser.hpp @@ -234,6 +234,7 @@ namespace Sass { Simple_Selector* parse_pseudo_selector(); Attribute_Selector* parse_attribute_selector(); Block* parse_block(); + bool parse_number_prefix(); Declaration* parse_declaration(); Expression* parse_map_value(); Expression* parse_map(); diff --git a/prelexer.cpp b/prelexer.cpp index 189987e308..d1f6065348 100644 --- a/prelexer.cpp +++ b/prelexer.cpp @@ -147,6 +147,18 @@ namespace Sass { return sequence< exactly<'-'>, exactly<'-'>, identifier >(src); } + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_spaces_and_comments, + exactly< '-' > + > + >(src); + } + // Match interpolant schemas const char* identifier_schema(const char* src) { // follows this pattern: (x*ix*)+ ... well, not quite diff --git a/prelexer.hpp b/prelexer.hpp index ba18b131c4..0a2324f49e 100644 --- a/prelexer.hpp +++ b/prelexer.hpp @@ -396,6 +396,8 @@ namespace Sass { const char* quoted_string(const char* src); // Match interpolants. const char* interpolant(const char* src); + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src); // Whitespace handling. const char* optional_spaces(const char* src); From 21ce837c5b068a8f926b2091f1db047786f9003e Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Mon, 9 Mar 2015 04:33:25 +0100 Subject: [PATCH 10/11] Improve sourcemap output for prepended texts Fixes https://github.com/sass/libsass/issues/879 Should work for all prepended texts (like top-comments) --- ast.hpp | 1 + emitter.cpp | 13 ++++++++++--- emitter.hpp | 13 ++----------- output.cpp | 5 ++--- position.cpp | 26 ++++++++++++++++++++++++-- position.hpp | 6 +++++- source_map.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++------- source_map.hpp | 21 ++++++++++++++++++++- 8 files changed, 107 insertions(+), 28 deletions(-) diff --git a/ast.hpp b/ast.hpp index ce2a8f82b6..44ec6ae981 100644 --- a/ast.hpp +++ b/ast.hpp @@ -44,6 +44,7 @@ #include "ast_def_macros.hpp" #include "ast_fwd_decl.hpp" #include "to_string.hpp" +#include "source_map.hpp" #include "sass.h" #include "sass_values.h" diff --git a/emitter.cpp b/emitter.cpp index 4aa385d3f4..91e30aecf5 100644 --- a/emitter.cpp +++ b/emitter.cpp @@ -85,10 +85,17 @@ namespace Sass { } } + // prepend some text or token to the buffer + void Emitter::prepend_output(const OutputBuffer& output) + { + wbuf.smap.prepend(output); + wbuf.buffer = output.buffer + wbuf.buffer; + } + // prepend some text or token to the buffer void Emitter::prepend_string(const string& text) { - // update source-map for new text + wbuf.smap.prepend(Offset(text)); wbuf.buffer = text + wbuf.buffer; } @@ -104,12 +111,12 @@ namespace Sass { // add to buffer wbuf.buffer += out; // account for data in source-maps - wbuf.smap.update_column(out); + wbuf.smap.append(Offset(out)); } else { // add to buffer wbuf.buffer += text; // account for data in source-maps - wbuf.smap.update_column(text); + wbuf.smap.append(Offset(text)); } } diff --git a/emitter.hpp b/emitter.hpp index 2b96bc4525..8dded8baf4 100644 --- a/emitter.hpp +++ b/emitter.hpp @@ -9,17 +9,6 @@ namespace Sass { class Context; using namespace std; - class OutputBuffer { - public: - OutputBuffer(void) - : buffer(""), - smap() - { } - public: - string buffer; - SourceMap smap; - }; - class Emitter { public: @@ -31,6 +20,7 @@ namespace Sass { public: const string buffer(void) { return wbuf.buffer; } const SourceMap smap(void) { return wbuf.smap; } + const OutputBuffer output(void) { return wbuf; } // proxy methods for source maps void add_source_index(size_t idx); void set_filename(const string& str); @@ -64,6 +54,7 @@ namespace Sass { void flush_schedules(void); // prepend some text or token to the buffer void prepend_string(const string& text); + void prepend_output(const OutputBuffer& out); // append some text or token to the buffer void append_string(const string& text); // append some white-space only text diff --git a/output.cpp b/output.cpp index fd5b670942..9310d74041 100644 --- a/output.cpp +++ b/output.cpp @@ -44,9 +44,8 @@ namespace Sass { // flush scheduled outputs inspect.finalize(); - // create combined buffer string - wbuf.buffer = inspect.buffer() - + this->buffer(); + // prepend buffer on top + prepend_output(inspect.output()); // make sure we end with a linefeed if (!ends_with(wbuf.buffer, ctx->linefeed)) { // if the output is not completely empty diff --git a/position.cpp b/position.cpp index 0217faf4f6..3f7d3013e8 100644 --- a/position.cpp +++ b/position.cpp @@ -4,6 +4,18 @@ namespace Sass { using namespace std; + Offset::Offset(const char* string) + : line(0), column(0) + { + *this = inc(string, string + strlen(string)); + } + + Offset::Offset(const string& text) + : line(0), column(0) + { + *this = inc(text.c_str(), text.c_str() + text.size()); + } + Offset::Offset(const size_t line, const size_t column) : line(line), column(column) { } @@ -34,7 +46,12 @@ namespace Sass { return line != pos.line || column != pos.column; } - const Offset Offset::operator+ (const Offset &off) const + void Offset::operator+= (const Offset &off) + { + *this = Offset(line + off.line, off.line > 0 ? off.column : off.column + column); + } + + Offset Offset::operator+ (const Offset &off) const { return Offset(line + off.line, off.line > 0 ? off.column : off.column + column); } @@ -67,7 +84,7 @@ namespace Sass { Position Position::inc(const char* begin, const char* end) const { Offset offset(line, column); - offset.inc(begin, end); + offset = offset.inc(begin, end); return Position(file, offset); } @@ -81,6 +98,11 @@ namespace Sass { return file == pos.file || line != pos.line || column != pos.column; } + void Position::operator+= (const Offset &off) + { + *this = Position(file, line + off.line, off.line > 0 ? off.column : off.column + column); + } + const Position Position::operator+ (const Offset &off) const { return Position(file, line + off.line, off.line > 0 ? off.column : off.column + column); diff --git a/position.hpp b/position.hpp index b7eef7e564..55734119eb 100644 --- a/position.hpp +++ b/position.hpp @@ -14,15 +14,18 @@ namespace Sass { class Offset { public: // c-tor + Offset(const char* string); + Offset(const string& text); Offset(const size_t line, const size_t column); // return new position, incremented by the given string Offset inc(const char* begin, const char* end) const; public: // overload operators for position + void operator+= (const Offset &pos); bool operator== (const Offset &pos) const; bool operator!= (const Offset &pos) const; - const Offset operator+ (const Offset &off) const; + Offset operator+ (const Offset &off) const; public: // overload output stream operator // friend ostream& operator<<(ostream& strm, const Offset& off); @@ -45,6 +48,7 @@ namespace Sass { Position(const size_t file, const size_t line, const size_t column); public: // overload operators for position + void operator+= (const Offset &off); bool operator== (const Position &pos) const; bool operator!= (const Position &pos) const; const Position operator+ (const Offset &off) const; diff --git a/source_map.cpp b/source_map.cpp index 587ea87e1a..2cfdc32065 100644 --- a/source_map.cpp +++ b/source_map.cpp @@ -112,15 +112,51 @@ namespace Sass { return result; } - void SourceMap::update_column(const string& str) + void SourceMap::prepend(const OutputBuffer& out) { - const ptrdiff_t new_line_count = std::count(str.begin(), str.end(), '\n'); - current_position.line += new_line_count; - if (new_line_count > 0) { - current_position.column = str.size() - str.find_last_of('\n') - 1; - } else { - current_position.column += str.size(); + Offset size(out.smap.current_position); + for (Mapping mapping : out.smap.mappings) { + if (mapping.generated_position.line > size.line) { + throw(runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.generated_position.line == size.line) { + if (mapping.generated_position.column > size.column) { + throw(runtime_error("prepend sourcemap has illegal column")); + } + } + } + // will adjust the offset + prepend(Offset(out.buffer)); + // now add the new mappings + VECTOR_UNSHIFT(mappings, out.smap.mappings); + } + + void SourceMap::append(const OutputBuffer& out) + { + append(Offset(out.buffer)); + } + + void SourceMap::prepend(const Offset& offset) + { + if (offset.line != 0 || offset.column != 0) { + for (Mapping& mapping : mappings) { + // move stuff on the first old line + if (mapping.generated_position.line == 0) { + mapping.generated_position.column += offset.column; + } + // make place for the new lines + mapping.generated_position.line += offset.line; + } } + if (current_position.line == 0) { + current_position.column += offset.column; + } + current_position.line += offset.line; + } + + void SourceMap::append(const Offset& offset) + { + current_position += offset; } void SourceMap::add_open_mapping(AST_Node* node) diff --git a/source_map.hpp b/source_map.hpp index 0830f9fd82..cdb2792b38 100644 --- a/source_map.hpp +++ b/source_map.hpp @@ -5,12 +5,17 @@ #include "ast_fwd_decl.hpp" #include "base64vlq.hpp" +#include "position.hpp" #include "mapping.hpp" +#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) +#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) + namespace Sass { using std::vector; class Context; + class OutputBuffer; class SourceMap { @@ -22,7 +27,10 @@ namespace Sass { void setFile(const string& str) { file = str; } - void update_column(const string& str); + void append(const Offset& offset); + void prepend(const Offset& offset); + void append(const OutputBuffer& out); + void prepend(const OutputBuffer& out); void add_open_mapping(AST_Node* node); void add_close_mapping(AST_Node* node); @@ -41,6 +49,17 @@ namespace Sass { Base64VLQ base64vlq; }; + class OutputBuffer { + public: + OutputBuffer(void) + : buffer(""), + smap() + { } + public: + string buffer; + SourceMap smap; + }; + } #endif From 14bfd99ad7637dcd7ddc9832edda559a1d677af8 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Mon, 9 Mar 2015 23:20:49 +0100 Subject: [PATCH 11/11] Add local unaware atof function Fixes https://github.com/sass/libsass/issues/923 --- eval.cpp | 6 +++--- util.cpp | 10 ++++++++++ util.hpp | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/eval.cpp b/eval.cpp index 54168a5f62..a25ee8100a 100644 --- a/eval.cpp +++ b/eval.cpp @@ -658,19 +658,19 @@ namespace Sass { { case Textual::NUMBER: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), "", zero); break; case Textual::PERCENTAGE: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), "%", zero); break; case Textual::DIMENSION: result = new (ctx.mem) Number(t->pstate(), - atof(num.c_str()), + sass_atof(num.c_str()), Token(number(text.c_str()), t->pstate()), zero); break; diff --git a/util.cpp b/util.cpp index f6a89db71b..50bc915a9d 100644 --- a/util.cpp +++ b/util.cpp @@ -21,6 +21,16 @@ namespace Sass { return ret; } + /* Locale unspecific atof function. */ + double sass_atof(const char *str) + { + char* locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + double val = atof(str); + setlocale(LC_NUMERIC, locale); + return val; + } + // double escape every escape sequences // escape unescaped quotes and backslashes string string_escape(const string& str) diff --git a/util.hpp b/util.hpp index b320c444dc..5d01045cc6 100644 --- a/util.hpp +++ b/util.hpp @@ -9,6 +9,7 @@ namespace Sass { using namespace std; char* sass_strdup(const char* str); + double sass_atof(const char* str); string string_escape(const string& str); string string_unescape(const string& str); string evacuate_quotes(const string& str);