ext/libsass/src/parser.cpp in sassc-1.11.4 vs ext/libsass/src/parser.cpp in sassc-1.12.0

- old
+ new

@@ -1,9 +1,6 @@ #include "sass.hpp" -#include <cstdlib> -#include <iostream> -#include <vector> #include "parser.hpp" #include "file.hpp" #include "inspect.hpp" #include "constants.hpp" #include "util.hpp" @@ -22,36 +19,38 @@ // are delayed (as with argument lists). To achieve this we need to // pass status to the list parser, so this can be set correctly. // Another case with delayed values are colors. In compressed mode // only processed values get compressed (other are left as written). +#include <cstdlib> +#include <iostream> +#include <vector> #include <typeinfo> -#include <tuple> 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); Block_Obj root = SASS_MEMORY_NEW(Block, pstate); p.block_stack.push_back(root); root->is_root(true); return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, 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); Block_Obj root = SASS_MEMORY_NEW(Block, pstate); p.block_stack.push_back(root); @@ -65,13 +64,13 @@ pstate += pstate.offset; pstate.offset.column = 0; 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); } @@ -79,13 +78,13 @@ { return peek_linefeed(start ? start : position) && ! peek_css<exactly<'{'>>(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); Block_Obj root = SASS_MEMORY_NEW(Block, pstate); p.block_stack.push_back(root); @@ -94,25 +93,39 @@ } /* main entry point to parse root block */ Block_Obj Parser::parse() { - bool is_root = false; - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // consume unicode BOM read_bom(); - // custom headers + // scan the input to find invalid utf8 sequences + const char* it = utf8::find_invalid(position, end); + + // report invalid utf8 + if (it != end) { + pstate += Offset::init(position, it); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); + } + + // create a block AST node to hold children + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // check seems a bit esoteric but works if (ctx.resources.size() == 1) { - is_root = true; - ctx.apply_custom_headers(&root, path, pstate); + // apply headers only on very first include + ctx.apply_custom_headers(root, path, pstate); } + // parse children nodes block_stack.push_back(root); - /* bool rv = */ parse_block_nodes(is_root); + parse_block_nodes(true); block_stack.pop_back(); - // update for end position + // update final position root->update_pstate(pstate); if (position != end) { css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); } @@ -136,11 +149,11 @@ } // create new block and push to the selector stack Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); block_stack.push_back(block); - if (!parse_block_nodes()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); if (!lex_css < exactly<'}'> >()) { css_error("Invalid CSS", " after ", ": expected \"}\", was "); } @@ -152,19 +165,18 @@ // parse comments after block // lex < optional_css_comments >(); block_stack.pop_back(); - return &block; + return block; } // convenience function for block parsing // will create a new block ad-hoc for you // also updates the `in_at_root` flag Block_Obj Parser::parse_block(bool is_root) { - LOCAL_FLAG(in_at_root, is_root); return parse_css_block(is_root); } // the main block parsing function // parses stuff between `{` and `}` @@ -212,68 +224,81 @@ Lookahead lookahead_result; // also parse block comments // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(&parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(&parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(&parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(&parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(&parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(&parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(&parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(&parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(&parse_return_directive()); } + if (lex < variable >(true)) { block->append(parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } // parse imports to process later else if (lex < kwd_import >(true)) { 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 + // import stub will fetch this in expand Import_Obj imp = parse_import(); // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(&imp); + if (!imp->urls().empty()) block->append(imp); // process all resources now (add Import_Stub nodes) for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); } } else if (lex < kwd_extend >(true)) { Lookahead lookahead = lookahead_for_include(position); if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_Obj target; - if (lookahead.has_interpolants) target = &parse_selector_schema(lookahead.found); - else target = &parse_selector_list(true); - block->append(SASS_MEMORY_NEW(Extension, pstate, &target)); + Selector_List_Obj target; + if (!lookahead.has_interpolants) { + target = parse_selector_list(true); + } + else { + target = SASS_MEMORY_NEW(Selector_List, pstate); + target->schema(parse_selector_schema(lookahead.found, true)); + } + + block->append(SASS_MEMORY_NEW(Extension, pstate, target)); } // selector may contain interpolations which need delayed evaluation - else if (!(lookahead_result = lookahead_for_selector(position)).error) - { block->append(&parse_ruleset(lookahead_result, is_root)); } + else if ( + !(lookahead_result = lookahead_for_selector(position)).error && + !lookahead_result.is_custom_property + ) + { + block->append(parse_ruleset(lookahead_result)); + } // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(&parse_media_block()); } - else if (lex < kwd_at_root >(true)) { block->append(&parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(&parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(&parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(&parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(&parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(&parse_definition(Definition::FUNCTION)); } + else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } // ignore the @charset directive for now else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(&parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(&parse_prefixed_directive()); } - else if (lex< at_keyword >(true)) { block->append(&parse_directive()); } + else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(parse_directive()); } - else if (is_root /* && block->is_root() */) { + else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { lex< css_whitespace >(); if (position >= end) return true; css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); } // parse a declaration @@ -281,11 +306,11 @@ { // ToDo: how does it handle parse errors? // maybe we are expected to parse something? Declaration_Obj decl = parse_declaration(); decl->tabs(indentation); - block->append(&decl); + block->append(decl); // maybe we have a "sub-block" if (peek< exactly<'{'> >()) { if (decl->is_indented()) ++ indentation; // parse a propset that rides on the declaration's property stack.push_back(Scope::Properties); @@ -310,32 +335,32 @@ if (lex< quoted_string >()) { to_import.push_back(std::pair<std::string,Function_Call_Obj>(std::string(lexed), 0)); } else if (lex< uri_prefix >()) { Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", &args); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); if (lex< quoted_string >()) { - Expression_Obj the_url = &parse_string(); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + Expression_Obj quoted_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); } - else if (String_Obj the_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + else if (String_Obj string_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); } else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - Expression_Obj the_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url)); + Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); + 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); - to_import.push_back(std::pair<std::string, Function_Call_Obj>("", &result)); + if (!lex< exactly<')'> >()) error("URI is missing ')'"); + to_import.push_back(std::pair<std::string, Function_Call_Obj>("", 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<','> >()); if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { @@ -343,53 +368,61 @@ imp->import_queries(import_queries); } for(auto location : to_import) { if (location.second) { - imp->urls().push_back(&location.second); - } else if (!ctx.call_importers(unquote(location.first), path, pstate, &imp)) { - ctx.import_url(&imp, location.first, path); + imp->urls().push_back(location.second); } + // check if custom importers want to take over the handling + else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { + // nobody wants it, so we do our import + ctx.import_url(imp, location.first, path); + } } return imp; } 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); else stack.push_back(Scope::Function); Block_Obj body = parse_block(); stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, &params, &body, which_type); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); } Parameters_Obj Parser::parse_parameters() { - std::string name(lexed); - Position position = after_token; Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do params->append(&parse_parameter()); - while (lex_css< exactly<','> >()); + do { + if (peek< exactly<')'> >()) break; + params->append(parse_parameter()); + } while (lex_css< exactly<','> >()); } - if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } } return params; } Parameter_Obj Parser::parse_parameter() { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + } while (lex< alternatives < spaces, block_comment > >()); lex < variable >(); std::string name(Util::normalize_underscores(lexed)); ParserState pos = pstate; Expression_Obj val; @@ -400,31 +433,36 @@ val = parse_space_list(); } else if (lex< exactly< ellipsis > >()) { is_rest = true; } - return SASS_MEMORY_NEW(Parameter, pos, name, &val, is_rest); + return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); } Arguments_Obj Parser::parse_arguments() { - std::string name(lexed); - Position position = after_token; Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); if (lex_css< exactly<'('> >()) { // if there's anything there at all if (!peek_css< exactly<')'> >()) { - do args->append(&parse_argument()); - while (lex_css< exactly<','> >()); + do { + if (peek< exactly<')'> >()) break; + args->append(parse_argument()); + } while (lex_css< exactly<','> >()); } - if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } } return args; } Argument_Obj Parser::parse_argument() { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { position += 2; css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } @@ -439,11 +477,11 @@ } else { bool is_arglist = false; bool is_keyword = false; Expression_Obj val = parse_space_list(); - List_Ptr l = SASS_MEMORY_CAST(List, val); + List_Ptr l = Cast<List>(val); if (lex_css< exactly< ellipsis > >()) { if (val->concrete_type() == Expression::MAP || ( (l != NULL && l->separator() == SASS_HASH) )) is_keyword = true; else is_arglist = true; @@ -455,18 +493,18 @@ Assignment_Obj Parser::parse_assignment() { 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 "); } Expression_Obj val; Lookahead lookahead = lookahead_for_value(position); if (lookahead.has_interpolants && lookahead.found) { - val = &parse_value_schema(lookahead.found); + val = parse_value_schema(lookahead.found); } else { val = parse_list(); } bool is_default = false; bool is_global = false; @@ -476,45 +514,54 @@ } return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); } // a ruleset connects a selector and a block - Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead, bool is_root) + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) { + NESTING_GUARD(nestings); + // inherit is_root from parent block + Block_Obj parent = block_stack.back(); + bool is_root = parent && parent->is_root(); // make sure to move up the the last position lex < optional_css_whitespace >(false, true); // create the connector object (add parts later) Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(&parse_selector_list(is_root)); - else ruleset->selector(&parse_selector_schema(lookahead.found)); + if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + else { + Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); + list->schema(parse_selector_schema(lookahead.position, false)); + ruleset->selector(list); + } // then parse the inner block stack.push_back(Scope::Rules); ruleset->block(parse_block()); stack.pop_back(); // update for end position ruleset->update_pstate(pstate); ruleset->block()->update_pstate(pstate); - // inherit is_root from parent block // need this info for sanity checks ruleset->is_root(is_root); // return AST Node return ruleset; } // parse a selector schema that will be evaluated in the eval stage // uses a string schema internally to do the actual schema handling // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector) + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) { + NESTING_GUARD(nestings); // move up to the start lex< optional_spaces >(); const char* i = position; // selector schema re-uses string schema implementation String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Ptr selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->connect_parent(chroot == false); selector_schema->media_block(last_media_block); // process until end while (i < end_of_selector) { // try to parse mutliple interpolants @@ -523,27 +570,28 @@ if (i < p) { std::string parsed(i, p); String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); pstate += Offset(parsed); str->update_pstate(pstate); - schema->append(&str); + schema->append(str); } - // check if the interpolation only contains white-space (error out) - if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } // skip over all nested inner interpolations up to our own delimiter const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, end_of_selector); + // check if the interpolation never ends of only contains white-space (error out) + if (!j || peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { + position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } // 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); // add to the string schema - schema->append(&interpolant); + schema->append(interpolant); // advance parser state pstate.add(p+2, j); // advance position i = j; } @@ -555,11 +603,11 @@ std::string parsed(i, end_of_selector); String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); pstate += Offset(parsed); str->update_pstate(pstate); i = end_of_selector; - schema->append(&str); + schema->append(str); } // exit loop } } // EO until eos @@ -572,11 +620,11 @@ schema->update_pstate(pstate); after_token = before_token = pstate; // return parsed result - return selector_schema; + return selector_schema.detach(); } // EO parse_selector_schema void Parser::parse_charset_directive() { @@ -609,28 +657,33 @@ } // EO parse_include_directive // parse a list of complex selectors // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool in_root) + Selector_List_Obj Parser::parse_selector_list(bool chroot) { - bool reloop = true; + bool reloop; bool had_linefeed = false; + NESTING_GUARD(nestings); Complex_Selector_Obj sel; Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); group->media_block(last_media_block); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + do { reloop = false; had_linefeed = had_linefeed || peek_newline(); - if (peek_css< class_char < selector_list_delims > >()) + if (peek_css< alternatives < class_char < selector_list_delims > > >()) break; // in case there are superfluous commas at the end - // now parse the complex selector - sel = parse_complex_selector(in_root); + sel = parse_complex_selector(chroot); if (!sel) return group.detach(); sel->has_line_feed(had_linefeed); @@ -660,42 +713,42 @@ // a complex selector combines a compound selector with another // complex selector, with one of four combinator operations. // the compound selector (head) is optional, since the combinator // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool in_root) + Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) { - String_Ptr reference = 0; + NESTING_GUARD(nestings); + String_Obj reference = 0; lex < block_comment >(); advanceToNextToken(); Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + if (peek < end_of_file >()) return 0; + // parse the left hand side Compound_Selector_Obj lhs; // special case if it starts with combinator ([+~>]) if (!peek_css< class_char < selector_combinator_ops > >()) { // parse the left hand side lhs = parse_compound_selector(); } - // check for end of file condition - if (peek < end_of_file >()) return 0; // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator; + Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { // comments are allowed, but not spaces? combinator = Complex_Selector::REFERENCE; if (!lex < re_reference_combinator >()) return 0; reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? } - else /* if (lex< zero >()) */ combinator = Complex_Selector::ANCESTOR_OF; if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; // lex < block_comment >(); sel->head(lhs); @@ -713,11 +766,11 @@ sel->tail(parse_complex_selector(true)); } // add a parent selector if we are not in a root // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !in_at_root && !in_root) { + if (!sel->has_parent_ref() && !chroot) { // create the objects to wrap parent selector reference Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); parent->media_block(last_media_block); head->media_block(last_media_block); @@ -758,11 +811,11 @@ // remove all block comments (don't skip white-space) lex< delimited_by< slash_star, star_slash, false > >(false); // parse functional if (match < re_pseudo_selector >()) { - seq->append(&parse_simple_selector()); + seq->append(parse_simple_selector()); } // parse parent selector else if (lex< exactly<'&'> >(false)) { // this produces a linefeed!? @@ -793,15 +846,15 @@ else if (peek_css < class_char < complex_selector_delims > >()) break; // otherwise parse another simple selector else { Simple_Selector_Obj sel = parse_simple_selector(); if (!sel) return 0; - seq->append(&sel); + seq->append(sel); } } - if (seq && !peek_css<exactly<'{'>>()) { + if (seq && !peek_css<alternatives<end_of_file,exactly<'{'>>>()) { seq->has_line_break(peek_newline()); } // EO while true return seq; @@ -816,33 +869,33 @@ return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); } else if (lex< id_name >()) { return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); } - else if (lex< quoted_string >()) { - return SASS_MEMORY_NEW(Element_Selector, pstate, unquote(lexed)); - } else if (lex< alternatives < variable, number, static_reference_combinator > >()) { return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); } else if (peek< pseudo_not >()) { - return &parse_negated_selector(); + return parse_negated_selector(); } else if (peek< re_pseudo_selector >()) { - return &parse_pseudo_selector(); + return parse_pseudo_selector(); } else if (peek< exactly<':'> >()) { - return &parse_pseudo_selector(); + return parse_pseudo_selector(); } else if (lex < exactly<'['> >()) { - return &parse_attribute_selector(); + return parse_attribute_selector(); } else if (lex< placeholder >()) { Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); sel->media_block(last_media_block); return sel; } + else { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } // failed return 0; } Wrapped_Selector_Obj Parser::parse_negated_selector() @@ -850,14 +903,14 @@ lex< pseudo_not >(); std::string name(lexed); 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); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); } // a pseudo selector often starts with one or two colons // it can contain more selectors inside parentheses Simple_Selector_Obj Parser::parse_pseudo_selector() { @@ -886,19 +939,19 @@ exactly<')'> > >() ) { lex_css< alternatives < static_value, binomial > >(); - String_Constant_Ptr expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (expr && lex_css< exactly<')'> >()) { + String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (lex_css< exactly<')'> >()) { expr->can_compress_whitespace(true); return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); } } else if (Selector_List_Obj wrapped = parse_selector_list(true)) { if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, &wrapped); + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); } } } // EO if pseudo selector @@ -914,84 +967,115 @@ // unreachable statement return 0; } + const char* Parser::re_attr_sensitive_close(const char* src) + { + return alternatives < exactly<']'>, exactly<'/'> >(src); + } + + const char* Parser::re_attr_insensitive_close(const char* src) + { + return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + } + 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< alternatives < exactly<']'>, exactly<'/'> > >()) return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + } 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); String_Obj value = 0; if (lex_css< identifier >()) { value = SASS_MEMORY_NEW(String_Constant, p, lexed); } else if (lex_css< quoted_string >()) { - value = &parse_interpolated_chunk(lexed, true); // needed! + 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< alternatives < exactly<']'>, exactly<'/'> > >()) error("unterminated attribute selector for " + name, pstate); - return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); + } + error("unterminated attribute selector for " + name); + return NULL; // to satisfy compilers (error must not return) } /* parse block comment and add to block */ void Parser::parse_block_comments() { Block_Obj block = block_stack.back(); 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)); } } Declaration_Obj Parser::parse_declaration() { String_Obj prop; + bool is_custom_property = false; if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; prop = parse_identifier_schema(); } else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { css_error("Invalid CSS", " after ", ": expected \"}\", was "); } 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 (!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); + } lex < css_comments >(false); - if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate); - if (peek_css< exactly<'{'> >()) is_indented = false; // don't indent if value is empty if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, &parse_static_value()/*, lex<kwd_important>()*/); + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex<kwd_important>()*/); } else { Expression_Obj value; Lookahead lookahead = lookahead_for_value(position); if (lookahead.found) { if (lookahead.has_interpolants) { - value = &parse_value_schema(lookahead.found); + value = parse_value_schema(lookahead.found); } else { - value = &parse_list(DELAYED); + value = parse_list(DELAYED); } } else { - value = &parse_list(DELAYED); - if (List_Ptr list = SASS_MEMORY_CAST(List, value)) { - if (list->length() == 0 && !peek< exactly <'{'> >()) { + value = parse_list(DELAYED); + if (List_Ptr list = Cast<List>(value)) { + if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } } } lex < css_comments >(false); @@ -1001,10 +1085,11 @@ return decl; } } // parse +/- and return false if negative + // this is never hit via spec tests bool Parser::parse_number_prefix() { bool positive = true; while(true) { if (lex < block_comment >()) continue; @@ -1018,17 +1103,23 @@ return positive; } Expression_Obj Parser::parse_map() { + NESTING_GUARD(nestings); Expression_Obj key = parse_list(); List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); // it's not a map so return the lexed value as a list value if (!lex_css< exactly<':'> >()) { return key; } + List_Obj l = Cast<List>(key); + if (l && l->separator() == SASS_COMMA) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + Expression_Obj value = parse_space_list(); map->append(key); map->append(value); @@ -1036,53 +1127,88 @@ { // allow trailing commas - #495 if (peek_css< exactly<')'> >(position)) { break; } - Expression_Obj key = parse_space_list(); + key = parse_space_list(); if (!(lex< exactly<':'> >())) { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - Expression_Obj value = parse_space_list(); + value = parse_space_list(); map->append(key); map->append(value); } ParserState ps = map->pstate(); ps.offset = pstate - ps + pstate.offset; map->pstate(ps); - return &map; + return map; } + Expression_Obj Parser::parse_bracket_list() + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + } + + bool has_paren = peek_css< exactly<'('> >() != NULL; + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + List_Obj l = Cast<List>(list); + if (!l || l->is_bracketed() || has_paren) { + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); + bracketed_list->append(list); + return bracketed_list; + } + l->is_bracketed(true); + return l; + } + + // if we got so far, we actually do have a comma list + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); + // wrap the first expression + bracketed_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + bracketed_list->append(parse_space_list()); + } + // return the list + return bracketed_list; + } + // parse list returns either a space separated list, // a comma separated list or any bare expression found. // so to speak: we unwrap items from lists if possible here! Expression_Obj Parser::parse_list(bool delayed) { + NESTING_GUARD(nestings); return parse_comma_list(delayed); } // will return singletons unwrapped Expression_Obj Parser::parse_comma_list(bool delayed) { + NESTING_GUARD(nestings); // check if we have an empty list // return the empty list as such - if (peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<':'>, - end_of_file, - exactly<ellipsis>, - default_flag, - global_flag - > >(position)) + if (peek_css< list_terminator >(position)) { // return an empty list (nothing to delay) return SASS_MEMORY_NEW(List, pstate, 0); } @@ -1102,78 +1228,49 @@ comma_list->append(list); while (lex_css< exactly<','> >()) { // check for abort condition - if (peek_css< alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<':'>, - end_of_file, - exactly<ellipsis>, - default_flag, - global_flag - > >(position) + if (peek_css< list_terminator >(position) ) { break; } // otherwise add another expression comma_list->append(parse_space_list()); } // return the list - return &comma_list; + return comma_list; } // EO parse_comma_list // will return singletons unwrapped Expression_Obj Parser::parse_space_list() { + NESTING_GUARD(nestings); Expression_Obj disj1 = parse_disjunction(); // if it's a singleton, return it (don't wrap it) - if (peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<','>, - exactly<':'>, - end_of_file, - exactly<ellipsis>, - default_flag, - global_flag - > >(position) - ) { return disj1; } + if (peek_css< space_list_terminator >(position) + ) { + return disj1; } List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); space_list->append(disj1); - while (!(peek_css< alternatives < - // exactly<'!'>, - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<','>, - exactly<':'>, - end_of_file, - exactly<ellipsis>, - default_flag, - global_flag - > >(position)) && peek_css< optional_css_whitespace >() != end + while ( + !(peek_css< space_list_terminator >(position)) && + peek_css< optional_css_whitespace >() != end ) { // the space is parsed implicitly? space_list->append(parse_disjunction()); } // return the list - return &space_list; + return space_list; } // EO parse_space_list // parse logical OR operation Expression_Obj Parser::parse_disjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side conjunction Expression_Obj conj = parse_conjunction(); // parse multiple right hand sides @@ -1191,18 +1288,19 @@ // EO parse_disjunction // parse logical AND operation Expression_Obj Parser::parse_conjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side relation Expression_Obj rel = parse_relation(); // parse multiple right hand sides std::vector<Expression_Obj> operands; while (lex_css< kwd_and >()) { - operands.push_back(&parse_relation()); + operands.push_back(parse_relation()); } // if it's a singleton, return it directly if (operands.size() == 0) return rel; // fold all operands into one binary expression Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); @@ -1213,10 +1311,11 @@ // EO parse_conjunction // parse comparison operations Expression_Obj Parser::parse_relation() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side expression Expression_Obj lhs = parse_expression(); std::vector<Expression_Obj> operands; @@ -1244,12 +1343,11 @@ // we checked the possibilities on top of fn : Sass_OP::EQ; // is directly adjacent to expression? bool right_ws = peek < css_comments >() != NULL; operators.push_back({ op, left_ws, right_ws }); - operands.push_back(&parse_expression()); - left_ws = peek < css_comments >() != NULL; + operands.push_back(parse_expression()); } // we are called recursively for list, so we first // fold inner binary expression which has delayed // correctly set to zero. After folding we also unwrap // single nested items. So we cannot set delay on the @@ -1266,10 +1364,11 @@ // called from parse_for_directive // called from parse_media_expression // parse addition and subtraction operations Expression_Obj Parser::parse_expression() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parses multiple add and subtract operations // NOTE: make sure that identifiers starting with // NOTE: dashes do NOT count as subtract operation @@ -1295,11 +1394,11 @@ ) { bool right_ws = peek < css_comments >() != NULL; operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(&parse_operators()); + operands.push_back(parse_operators()); left_ws = peek < css_comments >() != NULL; } if (operands.size() == 0) return lhs; Expression_Obj ex = fold_operands(lhs, operands, operators); @@ -1309,10 +1408,11 @@ } // parse addition and subtraction operations Expression_Obj Parser::parse_operators() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); Expression_Obj factor = parse_factor(); // if it's a singleton, return it (don't wrap it) std::vector<Expression_Obj> operands; // factors @@ -1323,11 +1423,11 @@ const char* right_ws = peek < css_comments >(); switch(*lexed.begin) { case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); break; + default: throw std::runtime_error("unknown static op parsed"); } operands.push_back(parse_factor()); left_ws = peek < css_comments >(); } // operands and operators to binary expression @@ -1341,105 +1441,239 @@ // called from parse_operators // called from parse_value_schema Expression_Obj Parser::parse_factor() { + NESTING_GUARD(nestings); lex < css_comments >(false); if (lex_css< exactly<'('> >()) { // 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; + return value; } + else if (lex_css< exactly<'['> >()) { + // explicit bracketed + Expression_Obj value = parse_bracket_list(); + // lex the expected closing square bracket + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); + return value; + } // string may be interpolated // if (lex< quoted_string >()) { // return &parse_string(); // } else if (peek< ie_property >()) { - return &parse_ie_property(); + return parse_ie_property(); } else if (peek< ie_keyword_arg >()) { - return &parse_ie_keyword_arg(); + return parse_ie_keyword_arg(); } else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return &parse_calc_function(); + return parse_calc_function(); } else if (lex < functional_schema >()) { - return &parse_function_call_schema(); + return parse_function_call_schema(); } else if (lex< identifier_schema >()) { String_Obj string = parse_identifier_schema(); - if (String_Schema_Ptr schema = SASS_MEMORY_CAST(String_Schema, string)) { + if (String_Schema_Ptr schema = Cast<String_Schema>(string)) { if (lex < exactly < '(' > >()) { - schema->append(&parse_list()); + schema->append(parse_list()); lex < exactly < ')' > >(); } } - return &string; + return string; } else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return &parse_url_function_string(); + return parse_url_function_string(); } else if (peek< re_functional >()) { - return &parse_function_call(); + return parse_function_call(); } else if (lex< exactly<'+'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } else if (lex< exactly<'-'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } + else if (lex< exactly<'/'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } else if (lex< sequence< kwd_not > >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, &parse_factor()); + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } + // this whole branch is never hit via spec tests else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return &parse_value(); // prefix is positive - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_value()); + if (parse_number_prefix()) return parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); return ex; } else { return parse_value(); } } + bool number_has_zero(const std::string& parsed) + { + size_t L = parsed.length(); + return !( (L > 0 && parsed.substr(0, 1) == ".") || + (L > 1 && parsed.substr(0, 2) == "0.") || + (L > 1 && parsed.substr(0, 2) == "-.") || + (L > 2 && parsed.substr(0, 3) == "-0.") ); + } + + Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "", + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "%", + true); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + { + size_t L = parsed.length(); + size_t num_pos = parsed.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = L; + size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); + if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { + unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); + } + if (unit_pos == std::string::npos) unit_pos = L; + const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(num.c_str()), + Token(number(parsed.c_str())), + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + { + Color_Ptr color = NULL; + if (parsed[0] != '#') { + return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); + } + // chop off the '#' + std::string hext(parsed.substr(1)); + if (parsed.length() == 4) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast<double>(strtol(r.c_str(), NULL, 16)), + static_cast<double>(strtol(g.c_str(), NULL, 16)), + static_cast<double>(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 7) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast<double>(strtol(r.c_str(), NULL, 16)), + static_cast<double>(strtol(g.c_str(), NULL, 16)), + static_cast<double>(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 9) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + std::string a(parsed.substr(7,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast<double>(strtol(r.c_str(), NULL, 16)), + static_cast<double>(strtol(g.c_str(), NULL, 16)), + static_cast<double>(strtol(b.c_str(), NULL, 16)), + static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + color->is_interpolant(false); + color->is_delayed(false); + return color; + } + + Value_Ptr Parser::color_or_string(const std::string& lexed) const + { + if (auto color = name_to_color(lexed)) { + auto c = SASS_MEMORY_NEW(Color, color); + c->is_delayed(true); + c->pstate(pstate); + c->disp(lexed); + return c; + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + // parse one value for a list Expression_Obj Parser::parse_value() { lex< css_comments >(false); if (lex< ampersand >()) { + if (match< ampersand >()) { + warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); + } return SASS_MEMORY_NEW(Parent_Selector, pstate); } if (lex< kwd_important >()) { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } // parse `10%4px` into separated items and not a schema if (lex< sequence < percentage, lookahead < number > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + { return lexed_percentage(lexed); } if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + { return lexed_number(lexed); } // string may be interpolated if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return &parse_string(); } + { return parse_string(); } if (const char* stop = peek< value_schema >()) - { return &parse_value_schema(stop); } + { return parse_value_schema(stop); } // string may be interpolated if (lex< quoted_string >()) - { return &parse_string(); } + { return parse_string(); } if (lex< kwd_true >()) { return SASS_MEMORY_NEW(Boolean, pstate, true); } if (lex< kwd_false >()) @@ -1447,33 +1681,46 @@ if (lex< kwd_null >()) { return SASS_MEMORY_NEW(Null, pstate); } if (lex< identifier >()) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + return color_or_string(lexed); } if (lex< percentage >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); } + { return lexed_percentage(lexed); } // match hex number first because 0x000 looks like a number followed by an identifier if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed); } + { return lexed_hex_color(lexed); } + if (lex< hexa >()) + { + std::string s = lexed.to_string(); + + deprecated( + "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", + "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", + true, pstate + ); + + return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); + } + if (lex< sequence < exactly <'#'>, identifier > >()) { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } // also handle the 10em- foo special case // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed); } + { return lexed_dimension(lexed); } if (lex< sequence< static_component, one_plus< strict_identifier > > >()) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } if (lex< number >()) - { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); } + { return lexed_number(lexed); } if (lex< variable >()) { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } // Special case handling for `%` proceeding an interpolant. @@ -1486,74 +1733,141 @@ return 0; } // 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 const char* p = constant ? find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end) : find_first_in_interval< exactly<hash_lbrace>, 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_Ptr 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<hash_lbrace> >(i, chunk.end) : find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, chunk.end); 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 if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2; css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(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); + 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; } - return schema; + return schema.detach(); } - String_Constant_Obj Parser::parse_static_value() + String_Schema_Obj Parser::parse_css_variable_value(bool top_level) { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj tok; + if (!(tok = parse_css_variable_value_token(top_level))) { + return NULL; + } + + schema->concat(tok); + while ((tok = parse_css_variable_value_token(top_level))) { + schema->concat(tok); + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if ( + (top_level && lex< css_variable_top_level_value >(false)) || + (!top_level && lex< css_variable_value >(false)) + ) { + Token str(lexed); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); + } + else if (Expression_Obj tok = lex_interpolation()) { + if (String_Schema_Ptr s = Cast<String_Schema>(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else if (lex< quoted_string >()) { + Expression_Obj tok = parse_string(); + if (String_Schema_Ptr s = Cast<String_Schema>(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else { + if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { + if (lex< exactly<'('> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + } + else if (lex< exactly<'['> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } + else if (lex< exactly<'{'> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } + } + } + + return schema->length() > 0 ? schema.detach() : NULL; + } + + Value_Obj Parser::parse_static_value() + { lex< static_value >(); Token str(lexed); // static values always have trailing white- // space and end delimiter (\s*[;]$) included - -- pstate.offset.column; + --pstate.offset.column; + --after_token.column; --str.end; --position; - String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace()); - return str_node; + return color_or_string(str.time_wspace());; } String_Obj Parser::parse_string() { return parse_interpolated_chunk(Token(lexed)); @@ -1581,18 +1895,18 @@ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(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); + 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 if (i < str.end) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); @@ -1612,13 +1926,17 @@ lex< alternatives< identifier_schema, identifier > >(); kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); } lex< exactly<'='> >(); kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(&parse_list()); - else if (lex< number >()) kwd_arg->append(SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, Util::normalize_decimals(lexed))); - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(&parse_list()); } + if (peek< variable >()) kwd_arg->append(parse_list()); + else if (lex< number >()) { + std::string parsed(lexed); + Util::normalize_decimals(parsed); + kwd_arg->append(lexed_number(parsed)); + } + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } return kwd_arg; } String_Schema_Obj Parser::parse_value_schema(const char* stop) { @@ -1627,11 +1945,11 @@ if (peek<exactly<'}'>>()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - const char* e = 0; + const char* e; const char* ee = end; end = stop; size_t num_items = 0; bool need_space = false; while (position < stop) { @@ -1642,23 +1960,23 @@ if (need_space) { need_space = false; // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); } if ((e = peek< re_functional >()) && e < stop) { - schema->append(&parse_function_call()); + schema->append(parse_function_call()); } // lex an interpolant /#{...}/ else if (lex< exactly < hash_lbrace > >()) { // Try to lex static expression first if (peek< exactly< rbrace > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - Expression_Obj ex = 0; + Expression_Obj ex; if (lex< re_static_expression >()) { ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { - ex = parse_list(); + ex = parse_list(true); } ex->is_interpolant(true); schema->append(ex); if (!lex < exactly < rbrace > >()) { css_error("Invalid CSS", " after ", ": expected \"}\", was "); @@ -1672,17 +1990,17 @@ // lex a quoted string else if (lex< quoted_string >()) { // need_space = true; // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); // else need_space = true; - schema->append(&parse_string()); + schema->append(parse_string()); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; } if (peek < exactly < '-' > >()) break; } - else if (lex< sequence < identifier > >()) { + else if (lex< identifier >()) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; } } @@ -1691,30 +2009,30 @@ std::string name(Util::normalize_underscores(lexed)); schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); } // lex percentage value else if (lex< percentage >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed)); + schema->append(lexed_percentage(lexed)); } // lex dimension value else if (lex< dimension >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed)); + schema->append(lexed_dimension(lexed)); } // lex number value else if (lex< number >()) { - schema->append( SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed)); + schema->append(lexed_number(lexed)); } // lex hex color value else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, lexed)); + schema->append(lexed_hex_color(lexed)); } else if (lex< sequence < exactly <'#'>, identifier > >()) { schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); } // lex a value in parentheses else if (peek< parenthese_scope >()) { - schema->append(&parse_factor()); + schema->append(parse_factor()); } else { break; } ++num_items; @@ -1744,36 +2062,36 @@ p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, id.end); if (p) { if (i < p) { // accumulate the preceding segment if it's nonempty const char* o = position; position = i; - schema->append(&parse_value_schema(p)); + schema->append(parse_value_schema(p)); position = o; } // we need to skip anything inside strings // create a new target in parser/prelexer if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p; css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(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); i = j; } 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 if (i < end) { const char* o = position; position = i; - schema->append(&parse_value_schema(id.end)); + schema->append(parse_value_schema(id.end)); position = o; } break; } } @@ -1794,13 +2112,13 @@ lex< skip_over_scopes < exactly < '(' >, exactly < ')' > > >(); - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, &parse_interpolated_chunk(Token(arg_beg, arg_end))); + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(&arg); + args->append(arg); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } String_Obj Parser::parse_url_function_string() { @@ -1816,20 +2134,20 @@ if (lex< real_uri_suffix >()) { suffix = std::string(lexed); } std::string uri(""); - if (&url_string) { + if (url_string) { uri = url_string->to_string({ NESTED, 5 }); } - if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&url_string)) { + if (String_Schema_Ptr schema = Cast<String_Schema>(url_string)) { String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); res->append(schema); res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return &res; + return res; } else { std::string res = prefix + uri + suffix; return SASS_MEMORY_NEW(String_Constant, pstate, res); } } @@ -1848,11 +2166,11 @@ // TODO: error checking for unclosed interpolants while (pp && peek< exactly< hash_lbrace > >(pp)) { pp = sequence< interpolant, real_uri_value >(pp); } position = pp; - return &parse_interpolated_chunk(Token(p, position)); + return parse_interpolated_chunk(Token(p, position)); } else if (uri != "") { std::string res = Util::rtrim(uri); return SASS_MEMORY_NEW(String_Constant, pstate, res); } @@ -1863,10 +2181,13 @@ Function_Call_Obj Parser::parse_function_call() { lex< identifier >(); std::string name(lexed); + if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) + { error("Cannot call content-exists() except within a mixin."); } + ParserState call_pos = pstate; Arguments_Obj args = parse_arguments(); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } @@ -1895,14 +2216,14 @@ // only throw away comment if we parse a case // we want all other comments to be parsed if (lex_css< elseif_directive >()) { alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(&parse_if_directive(true)); + alternative->append(parse_if_directive(true)); } else if (lex_css< kwd_else_directive >()) { - alternative = &parse_block(root); + alternative = parse_block(root); } stack.pop_back(); return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); } @@ -1911,16 +2232,16 @@ stack.push_back(Scope::Control); ParserState for_source_position = pstate; 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(); return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); } @@ -1958,14 +2279,14 @@ bool root = block_stack.back()->is_root(); std::vector<std::string> vars; 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(); return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); } @@ -1977,10 +2298,14 @@ bool root = block_stack.back()->is_root(); // create the initial while call object While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); // parse mandatory predicate Expression_Obj predicate = parse_list(); + List_Obj l = Cast<List>(predicate); + if (!predicate || (l && !l->length())) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); + } call->predicate(predicate); // parse mandatory block call->block(parse_block(root)); // return ast node stack.pop_back(); @@ -1995,23 +2320,23 @@ Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); media_block->media_queries(parse_media_queries()); Media_Block_Obj prev_media_block = last_media_block; - last_media_block = &media_block; + last_media_block = media_block; media_block->block(parse_css_block()); - last_media_block = &prev_media_block; + last_media_block = prev_media_block; stack.pop_back(); return media_block.detach(); } List_Obj Parser::parse_media_queries() { advanceToNextToken(); List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(&parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(&parse_media_query()); + if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(parse_media_query()); queries->update_pstate(pstate); return queries.detach(); } // Expression_Ptr Parser::parse_media_query() @@ -2020,58 +2345,61 @@ advanceToNextToken(); Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - if (lex < identifier_schema >()) media_query->media_type(&parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(&parse_interpolated_chunk(lexed)); - else media_query->append(&parse_media_expression()); + if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); + else media_query->append(parse_media_expression()); - while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); if (lex < identifier_schema >()) { String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); - schema->append(&media_query->media_type()); + schema->append(media_query->media_type()); schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - schema->append(&parse_identifier_schema()); + schema->append(parse_identifier_schema()); media_query->media_type(schema); } - while (lex_css < kwd_and >()) media_query->append(&parse_media_expression()); + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); media_query->update_pstate(pstate); return media_query; } Media_Query_Expression_Obj Parser::parse_media_expression() { if (lex < identifier_schema >()) { String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, &ss, 0, true); + 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 = 0; + 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(); + feature = parse_expression(); Expression_Obj expression = 0; if (lex_css< exactly<':'> >()) { - expression = &parse_list(DELAYED); + 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); } // lexed after `kwd_supports_directive` // these are very similar to media blocks Supports_Block_Obj Parser::parse_supports_directive() { Supports_Condition_Obj cond = parse_supports_condition(); + if (!cond) { + css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); + } // create the ast node object for the support queries Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); // additional block is mandatory // parse inner block query->block(parse_block()); @@ -2082,39 +2410,39 @@ // parse one query operation // may encounter nested queries Supports_Condition_Obj Parser::parse_supports_condition() { lex < css_whitespace >(); - Supports_Condition_Obj cond = 0; + Supports_Condition_Obj cond; if ((cond = parse_supports_negation())) return cond; if ((cond = parse_supports_operator())) return cond; if ((cond = parse_supports_interpolation())) return cond; return cond; } Supports_Condition_Obj Parser::parse_supports_negation() { if (!lex < kwd_not >()) return 0; Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - return SASS_MEMORY_NEW(Supports_Negation, pstate, &cond); + return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); } Supports_Condition_Obj Parser::parse_supports_operator() { Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - if (!&cond) return 0; + if (cond.isNull()) return 0; while (true) { Supports_Operator::Operand op = Supports_Operator::OR; if (lex < kwd_and >()) { op = Supports_Operator::AND; } else if(!lex < kwd_or >()) { break; } lex < css_whitespace >(); Supports_Condition_Obj right = parse_supports_condition_in_parens(); // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast<Supports_Condition_Ptr>(cond)); - cond = SASS_MEMORY_NEW(Supports_Operator, pstate, &cond, &right, op); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); } return cond; } Supports_Condition_Obj Parser::parse_supports_interpolation() @@ -2122,114 +2450,121 @@ if (!lex < interpolant >()) return 0; String_Obj interp = parse_interpolated_chunk(lexed); if (!interp) return 0; - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, &interp); + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); } // TODO: This needs some major work. Although feature conditions // look like declarations their semantics differ significantly Supports_Condition_Obj Parser::parse_supports_declaration() { - Supports_Condition_Ptr cond = 0; + Supports_Condition_Ptr cond; // parse something declaration like - Declaration_Obj declaration = parse_declaration(); - if (!declaration) error("@supports condition expected declaration", pstate); + Expression_Obj feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!feature || !expression) error("@supports condition expected declaration"); cond = SASS_MEMORY_NEW(Supports_Declaration, - declaration->pstate(), - &declaration->property(), - declaration->value()); + feature->pstate(), + feature, + expression); // ToDo: maybe we need an additional error condition? return cond; } Supports_Condition_Obj Parser::parse_supports_condition_in_parens() { Supports_Condition_Obj interp = parse_supports_interpolation(); - if (&interp != 0) return interp; + if (interp != 0) return interp; if (!lex < exactly <'('> >()) return 0; lex < css_whitespace >(); Supports_Condition_Obj cond = parse_supports_condition(); - if (&cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); + if (cond != 0) { + 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; } At_Root_Block_Obj Parser::parse_at_root_block() { + stack.push_back(Scope::AtRoot); ParserState at_source_position = pstate; Block_Obj body = 0; At_Root_Query_Obj expr; Lookahead lookahead_result; - LOCAL_FLAG(in_at_root, true); if (lex_css< exactly<'('> >()) { expr = parse_at_root_query(); } if (peek_css < exactly<'{'> >()) { lex <optional_spaces>(); - body = &parse_block(true); + body = parse_block(true); } else if ((lookahead_result = lookahead_for_selector(position)).found) { - Ruleset_Obj r = parse_ruleset(lookahead_result, false); + Ruleset_Obj r = parse_ruleset(lookahead_result); body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(&r); + body->append(r); } At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); - if (&expr) at_root->expression(&expr); + if (!expr.isNull()) at_root->expression(expr); + stack.pop_back(); return at_root; } 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); if (expression->concrete_type() == Expression::LIST) { - value = SASS_MEMORY_CAST(List, expression); + value = Cast<List>(expression); } else value->append(expression); At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, value->pstate(), feature, - &value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate); + value); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); return cond; } Directive_Obj Parser::parse_special_directive() { 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 + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); Lookahead lookahead = lookahead_for_include(position); if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(&parse_selector_list(true)); + at_rule->selector(parse_selector_list(false)); } lex < css_comments >(false); if (lex < static_property >()) { - at_rule->value(&parse_interpolated_chunk(Token(lexed))); + at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { at_rule->value(parse_list()); } lex < css_comments >(false); @@ -2239,28 +2574,29 @@ } return at_rule; } + // this whole branch is never hit via spec tests Directive_Obj Parser::parse_prefixed_directive() { 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); if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(&parse_selector_list(true)); + at_rule->selector(parse_selector_list(false)); } lex < css_comments >(false); if (lex < static_property >()) { - at_rule->value(&parse_interpolated_chunk(Token(lexed))); + at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(&parse_list()); + at_rule->value(parse_list()); } lex < css_comments >(false); if (peek< exactly<'{'> >()) { @@ -2274,21 +2610,21 @@ Directive_Obj Parser::parse_directive() { Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); String_Schema_Obj val = parse_almost_any_value(); // strip left and right if they are of type string - directive->value(&val); + directive->value(val); if (peek< exactly<'{'> >()) { directive->block(parse_block()); } return directive; } Expression_Obj Parser::lex_interpolation() { if (lex < interpolant >(true) != NULL) { - return &parse_interpolated_chunk(lexed, true); + return parse_interpolated_chunk(lexed, true); } return 0; } Expression_Obj Parser::lex_interp_uri() @@ -2297,11 +2633,11 @@ return lex_interp< re_string_uri_open, re_string_uri_close >(); } Expression_Obj Parser::lex_interp_string() { - Expression_Obj rv = 0; + Expression_Obj rv; if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; return rv; } @@ -2357,19 +2693,21 @@ return NULL; } Expression_Obj Parser::lex_almost_any_value_token() { - Expression_Obj rv = 0; + Expression_Obj rv; if (*position == 0) return 0; - if ((rv = &lex_almost_any_value_chars())) return rv; + if ((rv = lex_almost_any_value_chars())) return rv; // if ((rv = lex_block_comment())) return rv; // if ((rv = lex_single_line_comment())) return rv; - if ((rv = &lex_interp_string())) return rv; - if ((rv = &lex_interp_uri())) return rv; - if ((rv = &lex_interpolation())) return rv; - return rv; + if ((rv = lex_interp_string())) return rv; + if ((rv = lex_interp_uri())) return rv; + if ((rv = lex_interpolation())) return rv; + if (lex< alternatives< hex, hex0 > >()) + { return lexed_hex_color(lexed); } + return rv; } String_Schema_Obj Parser::parse_almost_any_value() { @@ -2382,11 +2720,11 @@ if (*position == 0) { schema->rtrim(); return schema.detach(); } - while ((token = &lex_almost_any_value_token())) { + while ((token = lex_almost_any_value_token())) { schema->append(token); } lex < css_whitespace >(); @@ -2400,11 +2738,11 @@ if (stack.back() != Scope::Root && stack.back() != Scope::Function && 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)); } Error_Obj Parser::parse_error() @@ -2412,11 +2750,11 @@ if (stack.back() != Scope::Root && stack.back() != Scope::Function && 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)); } Debug_Obj Parser::parse_debug() @@ -2424,21 +2762,21 @@ if (stack.back() != Scope::Root && stack.back() != Scope::Function && 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)); } Return_Obj Parser::parse_return_directive() { // check that we do not have an empty list (ToDo: check if we got all cases) if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, &parse_list()); + return SASS_MEMORY_NEW(Return, pstate, parse_list()); } Lookahead Parser::lookahead_for_selector(const char* start) { // init result struct @@ -2450,16 +2788,24 @@ if (const char* q = peek < re_selector_list >(p) ) { + bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; + bool could_be_escaped = false; while (p < q) { // did we have interpolations? if (*p == '#' && *(p+1) == '{') { rv.has_interpolants = true; p = q; break; } + // A property that's ambiguous with a nested selector is interpreted as a + // custom property. + if (*p == ':' && !could_be_escaped) { + rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); + } + could_be_escaped = *p == '\\'; ++ p; } // store anyway } @@ -2467,10 +2813,11 @@ rv.error = q; rv.position = q; // check expected opening bracket // only after successfull matching if (peek < exactly<'{'> >(q)) rv.found = q; + // else if (peek < end_of_file >(q)) rv.found = q; else if (peek < exactly<'('> >(q)) rv.found = q; // else if (peek < exactly<';'> >(q)) rv.found = q; // else if (peek < exactly<'}'> >(q)) rv.found = q; if (rv.found || *p == 0) rv.error = 0; } @@ -2536,10 +2883,11 @@ > >, sequence < // optional_spaces, alternatives < + // end_of_file, exactly<'{'>, exactly<'}'>, exactly<';'> > > @@ -2620,12 +2968,13 @@ break; case 0x84: skip = check_bom_chars(source, end, gb_18030_bom, 4); encoding = "GB-18030"; 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; } size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) { @@ -2639,18 +2988,18 @@ Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& operands, Operand op) { for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); } return base; } Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& operands, std::vector<Operand>& ops, size_t i) { - if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&base)) { + if (String_Schema_Ptr schema = Cast<String_Schema>(base)) { // return schema; if (schema->has_interpolants()) { if (i + 1 < operands.size() && ( (ops[0].operand == Sass_OP::EQ) || (ops[0].operand == Sass_OP::ADD) @@ -2661,76 +3010,86 @@ || (ops[0].operand == Sass_OP::GT) || (ops[0].operand == Sass_OP::LTE) || (ops[0].operand == Sass_OP::GTE) )) { Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, &rhs); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); return rhs; } // return schema; } } for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&operands[i])) { + if (String_Schema_Ptr schema = Cast<String_Schema>(operands[i])) { if (schema->has_interpolants()) { if (i + 1 < S) { + // this whole branch is never hit via spec tests Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, &rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, &rhs); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); return base; } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); return base; } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], &base, operands[i]); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } - Binary_Expression_Ptr b = static_cast<Binary_Expression_Ptr>(&base); + Binary_Expression_Ptr b = Cast<Binary_Expression>(base.ptr()); if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { base->is_delayed(true); } } // nested binary expression are never to be delayed - if (Binary_Expression_Ptr b = dynamic_cast<Binary_Expression_Ptr>(&base)) { - if (SASS_MEMORY_CAST(Binary_Expression, b->left())) base->set_delayed(false); - if (SASS_MEMORY_CAST(Binary_Expression, b->right())) base->set_delayed(false); + if (Binary_Expression_Ptr b = Cast<Binary_Expression>(base)) { + if (Cast<Binary_Expression>(b->left())) base->set_delayed(false); + if (Cast<Binary_Expression>(b->right())) base->set_delayed(false); } return base; } 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 - void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle) + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) { int max_len = 18; const char* end = this->end; while (*end != 0) ++ end; const char* pos = peek < optional_spaces >(); + if (!pos) pos = position; const char* last_pos(pos); if (last_pos > source) { utf8::prior(last_pos, source); } // backup position to last significant char - while (last_pos > source && last_pos < end) { + while (trim && last_pos > source && last_pos < end) { if (!Prelexer::is_space(*last_pos)) break; utf8::prior(last_pos, source); } bool ellipsis_left = false; const char* pos_left(last_pos); const char* end_left(last_pos); - utf8::next(pos_left, end); - utf8::next(end_left, end); + if (*pos_left) utf8::next(pos_left, end); + if (*end_left) utf8::next(end_left, end); while (pos_left > source) { if (utf8::distance(pos_left, end_left) >= max_len) { utf8::prior(pos_left, source); ellipsis_left = *(pos_left) != '\n' && *(pos_left) != '\r'; @@ -2767,10 +3126,12 @@ std::string right(pos_right, end_right); size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // 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)); } }