20550e2018-03-06Pontus Östlund #include "sass.hpp" #include "parser.hpp" #include "file.hpp" #include "inspect.hpp" #include "constants.hpp" #include "util.hpp" #include "prelexer.hpp" #include "color_maps.hpp" #include "sass/functions.h" #include "error_handling.hpp" // Notes about delayed: some ast nodes can have delayed evaluation so // they can preserve their original semantics if needed. This is most // prominently exhibited by the division operation, since it is not // only a valid operation, but also a valid css statement (i.e. for // fonts, as in `16px/24px`). When parsing lists and expression we // unwrap single items from lists and other operations. A nested list // must not be delayed, only the items of the first level sometimes // 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> namespace Sass { using namespace Constants; using namespace Prelexer; Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source) { pstate.offset.column = 0; pstate.offset.line = 0; Parser p(ctx, pstate); 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) { pstate.offset.column = 0; pstate.offset.line = 0; Parser p(ctx, pstate); 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); root->is_root(true); return p; } void Parser::advanceToNextToken() { lex < css_comments >(false); // advance to position 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) { Parser p = Parser::from_c_str(beg, ctx, pstate, source); // ToDo: ruby sass errors on parent references // ToDo: remap the source-map entries somehow return p.parse_selector_list(false); } bool Parser::peek_newline(const char* start) { return peek_linefeed(start ? start : position) && ! peek_css<exactly<'{'>>(start); } Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source) { Parser p(ctx, pstate); 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); root->is_root(true); return p; } /* main entry point to parse root block */ Block_Obj Parser::parse() { // consume unicode BOM read_bom(); // 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); throw Exception::InvalidSass(pstate, "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) { // apply headers only on very first include ctx.apply_custom_headers(root, path, pstate); } // parse children nodes block_stack.push_back(root); parse_block_nodes(true); block_stack.pop_back(); // update final position root->update_pstate(pstate); if (position != end) { css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); } return root; } // convenience function for block parsing // will create a new block ad-hoc for you // this is the base block parsing function Block_Obj Parser::parse_css_block(bool is_root) { // parse comments before block // lex < optional_css_comments >(); // lex mandatory opener or error out if (!lex_css < exactly<'{'> >()) { css_error("Invalid CSS", " after ", ": expected \"{\", was "); } // 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(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); if (!lex_css < exactly<'}'> >()) { css_error("Invalid CSS", " after ", ": expected \"}\", was "); } // update for end position // this seems to be done somewhere else // but that fixed selector schema issue // block->update_pstate(pstate); // parse comments after block // lex < optional_css_comments >(); block_stack.pop_back(); 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) { return parse_css_block(is_root); } // the main block parsing function // parses stuff between `{` and `}` bool Parser::parse_block_nodes(bool is_root) { // loop until end of string while (position < end) { // we should be able to refactor this parse_block_comments(); lex < css_whitespace >(); if (lex < exactly<';'> >()) continue; if (peek < end_of_file >()) return true; if (peek < exactly<'}'> >()) return true; if (parse_block_node(is_root)) continue; parse_block_comments(); if (lex_css < exactly<';'> >()) continue; if (peek_css < end_of_file >()) return true; if (peek_css < exactly<'}'> >()) return true; // illegal sass return false; } // return success return true; } // parser for a single node in a block // semicolons must be lexed beforehand bool Parser::parse_block_node(bool is_root) { Block_Obj block = block_stack.back(); parse_block_comments(); // throw away white-space // includes line comments lex < css_whitespace >(); 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()); } // 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); } } // 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); // 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_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)); } // 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)); } // 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 (is_root /* && 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 else { // 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); // 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); decl->block(parse_block()); stack.pop_back(); if (decl->is_indented()) -- indentation; } } // something matched return true; } // EO parse_block_nodes // parse imports inside the Import_Obj Parser::parse_import() { Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); std::vector<std::pair<std::string,Function_Call_Obj>> to_import; bool first = true; do { while (lex< block_comment >()); 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); if (lex< quoted_string >()) { Expression_Obj quoted_url = parse_string(); args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_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 braced_url = parse_list(); // parse_interpolated_chunk(lexed); args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); } else { error("malformed URL", pstate); } if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate); 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); } first = false; } while (lex_css< exactly<','> >()); if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { List_Obj import_queries = parse_media_queries(); imp->import_queries(import_queries); } for(auto location : to_import) { if (location.second) { 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); std::string name(Util::normalize_underscores(lexed)); if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) { error("Invalid function name \"" + name + "\".", pstate); } 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); } 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<','> >()); } if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); } return params; } Parameter_Obj Parser::parse_parameter() { while (lex< alternatives < spaces, block_comment > >()); lex < variable >(); std::string name(Util::normalize_underscores(lexed)); ParserState pos = pstate; Expression_Obj val; bool is_rest = false; while (lex< alternatives < spaces, block_comment > >()); if (lex< exactly<':'> >()) { // there's a default value while (lex< block_comment >()); val = parse_space_list(); } else if (lex< exactly< ellipsis > >()) { is_rest = true; } 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<','> >()); } if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position); } return args; } Argument_Obj Parser::parse_argument() { if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { position += 2; css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } Argument_Obj arg; if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { lex_css< variable >(); std::string name(Util::normalize_underscores(lexed)); ParserState p = pstate; lex_css< exactly<':'> >(); Expression_Obj val = parse_space_list(); arg = SASS_MEMORY_NEW(Argument, p, val, name); } else { bool is_arglist = false; bool is_keyword = false; Expression_Obj val = parse_space_list(); 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; } arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); } return arg; } 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 (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); } else { val = parse_list(); } bool is_default = false; bool is_global = false; while (peek< alternatives < default_flag, global_flag > >()) { if (lex< default_flag >()) is_default = true; else if (lex< global_flag >()) is_global = true; } 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) { 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(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); // 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, 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_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 if (const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, end_of_selector)) { // accumulate the preceding segment if the position has advanced 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); } // 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(); // set status on the list expression interpolant->is_interpolant(true); // schema->has_interpolants(true); // add to the string schema schema->append(interpolant); // advance parser state pstate.add(p+2, j); // advance position i = j; } // no more interpolants have been found // add the last segment if there is one else { // make sure to add the last bits of the string up to the end (if any) if (i < end_of_selector) { 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); } // exit loop } } // EO until eos // update position position = i; // update for end position selector_schema->update_pstate(pstate); schema->update_pstate(pstate); after_token = before_token = pstate; // return parsed result return selector_schema.detach(); } // EO parse_selector_schema void Parser::parse_charset_directive() { lex < sequence < quoted_string, optional_spaces, exactly <';'> > >(); } // called after parsing `kwd_include_directive` Mixin_Call_Obj Parser::parse_include_directive() { // lex identifier into `lexed` var lex_identifier(); // may error out // normalize underscores to hyphens std::string name(Util::normalize_underscores(lexed)); // create the initial mixin call object Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); // parse mandatory arguments call->arguments(parse_arguments()); // parse optional block if (peek < exactly <'{'> >()) { call->block(parse_block()); } // return ast node return call.detach(); } // 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 chroot) { 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< 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(chroot); if (!sel) return group.detach(); sel->has_line_feed(had_linefeed); had_linefeed = false; while (peek_css< exactly<','> >()) { lex< css_comments >(false); // consume everything up and including the comma separator reloop = lex< exactly<','> >() != 0; // remember line break (also between some commas) had_linefeed = had_linefeed || peek_newline(); // remember line break (also between some commas) } group->append(sel); } while (reloop); while (lex_css< kwd_optional >()) { group->is_optional(true); } // update for end position group->update_pstate(pstate); if (sel) sel->last()->has_line_break(false); return group.detach(); } // EO parse_selector_list // 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 chroot) { 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(); } // parse combinator between lhs and rhs 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? } if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; // lex < block_comment >(); sel->head(lhs); sel->combinator(combinator); sel->media_block(last_media_block); if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); // has linfeed after combinator? sel->has_line_break(peek_newline()); // sel->has_line_feed(has_line_feed); // check if we got the abort condition (ToDo: optimize) if (!peek_css< class_char < complex_selector_delims > >()) { // parse next selector in sequence 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() && !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); // add simple selector head->append(parent); // selector may not have any head yet if (!sel->head()) { sel->head(head); } // otherwise we need to create a new complex selector and set the old one as its tail else { sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); sel->media_block(last_media_block); } // peek for linefeed and remember result on head // if (peek_newline()) head->has_line_break(true); } sel->update_pstate(pstate); // complex selector return sel; } // EO parse_complex_selector // parse one compound selector, which is basically // a list of simple selectors (directly adjacent) // lex them exactly (without skipping white-space) Compound_Selector_Obj Parser::parse_compound_selector() { // init an empty compound selector wrapper Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); seq->media_block(last_media_block); // skip initial white-space lex< css_whitespace >(); // parse list while (true) { // 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()); } // parse parent selector else if (lex< exactly<'&'> >(false)) { // this produces a linefeed!? seq->has_parent_reference(true); seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); // parent selector only allowed at start // upcoming Sass may allow also trailing if (seq->length() > 1) { ParserState state(pstate); Simple_Selector_Obj cur = (*seq)[seq->length()-1]; Simple_Selector_Obj prev = (*seq)[seq->length()-2]; std::string sel(prev->to_string({ NESTED, 5 })); std::string found(cur->to_string({ NESTED, 5 })); if (lex < identifier >()) { found += std::string(lexed); } error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" "\"" + found + "\" may only be used at the beginning of a compound selector.", state); } } // parse type selector else if (lex< re_type_selector >(false)) { seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); } // peek for abort conditions else if (peek< spaces >()) break; else if (peek< end_of_file >()) { break; } else if (peek_css < class_char < selector_combinator_ops > >()) break; 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); } } if (seq && !peek_css<alternatives<end_of_file,exactly<'{'>>>()) { seq->has_line_break(peek_newline()); } // EO while true return seq; } // EO parse_compound_selector Simple_Selector_Obj Parser::parse_simple_selector() { lex < css_comments >(false); if (lex< class_name >()) { return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); } else if (lex< id_name >()) { return SASS_MEMORY_NEW(Id_Selector, pstate, 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(); } else if (peek< re_pseudo_selector >()) { return parse_pseudo_selector(); } else if (peek< exactly<':'> >()) { return parse_pseudo_selector(); } else if (lex < exactly<'['> >()) { 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() { 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); } name.erase(name.size() - 1); 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() { if (lex< sequence< optional < pseudo_prefix >, // we keep the space within the name, strange enough // ToDo: refactor output to schedule the space for it // or do we really want to keep the real white-space? sequence< identifier, optional < block_comment >, exactly<'('> > > >()) { std::string name(lexed); name.erase(name.size() - 1); ParserState p = pstate; // specially parse static stuff // ToDo: really everything static? if (peek_css < sequence < alternatives < static_value, binomial >, optional_css_whitespace, exactly<')'> > >() ) { lex_css< alternatives < static_value, binomial > >(); 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); } } } // EO if pseudo selector else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); } else if(lex < pseudo_prefix >()) { css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); } css_error("Invalid CSS", " after ", ": expected \")\", was "); // 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); std::string name(lexed); 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); } 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! } else { error("expected a string constant or identifier in attribute selector for " + name, pstate); } 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, pstate); 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); block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); } } Declaration_Obj Parser::parse_declaration() { String_Obj prop; if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { prop = parse_identifier_schema(); } else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { 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); 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>()*/); } else { Expression_Obj value; Lookahead lookahead = lookahead_for_value(position); if (lookahead.found) { if (lookahead.has_interpolants) { value = parse_value_schema(lookahead.found); } else { value = parse_list(DELAYED); } } else { value = parse_list(DELAYED); if (List_Ptr list = Cast<List>(value)) { if (list->length() == 0 && !peek< exactly <'{'> >()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } } } lex < css_comments >(false); Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex<kwd_important>()*/); decl->is_indented(is_indented); decl->update_pstate(pstate); 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; if (lex < number_prefix >()) continue; if (lex < exactly < '-' > >()) { positive = !positive; continue; } break; } 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); while (lex_css< exactly<','> >()) { // allow trailing commas - #495 if (peek_css< exactly<')'> >(position)) { break; } key = parse_space_list(); if (!(lex< exactly<':'> >())) { css_error("Invalid CSS", " after ", ": expected \":\", was "); } 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; } // 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)) { // return an empty list (nothing to delay) return SASS_MEMORY_NEW(List, pstate, 0); } // 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)) { // set_delay doesn't apply to list children // so this will only undelay single values if (!delayed) list->set_delayed(false); return list; } // if we got so far, we actually do have a comma list List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); // wrap the first expression 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) ) { break; } // otherwise add another expression comma_list->append(parse_space_list()); } // return the 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; } 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 ) { // the space is parsed implicitly? space_list->append(parse_disjunction()); } // return the 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 std::vector<Expression_Obj> operands; while (lex_css< kwd_or >()) operands.push_back(parse_conjunction()); // if it's a singleton, return it directly if (operands.size() == 0) return conj; // fold all operands into one binary expression Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); state.offset = pstate - state + pstate.offset; ex->pstate(state); return ex; } // 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()); } // 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 }); state.offset = pstate - state + pstate.offset; ex->pstate(state); return ex; } // 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; std::vector<Operand> operators; // if it's a singleton, return it (don't wrap it) while (peek< alternatives < kwd_eq, kwd_neq, kwd_gte, kwd_gt, kwd_lte, kwd_lt > >(position)) { // is directly adjancent to expression? bool left_ws = peek < css_comments >() != NULL; // parse the operator enum Sass_OP op = lex<kwd_eq>() ? Sass_OP::EQ : lex<kwd_neq>() ? Sass_OP::NEQ : lex<kwd_gte>() ? Sass_OP::GTE : lex<kwd_lte>() ? Sass_OP::LTE : lex<kwd_gt>() ? Sass_OP::GT : lex<kwd_lt>() ? Sass_OP::LT // 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()); } // 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 // returned result here, as we have lost nestings ... Expression_Obj ex = fold_operands(lhs, operands, operators); state.offset = pstate - state + pstate.offset; ex->pstate(state); return ex; } // parse_relation // parse expression valid for operations // called from parse_relation // 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 Expression_Obj lhs = parse_operators(); // if it's a singleton, return it (don't wrap it) if (!(peek_css< exactly<'+'> >(position) || // condition is a bit misterious, but some combinations should not be counted as operations (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) { return lhs; } std::vector<Expression_Obj> operands; std::vector<Operand> operators; bool left_ws = peek < css_comments >() != NULL; while ( lex_css< exactly<'+'> >() || ( ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) && lex_css< sequence< negate< digit >, exactly<'-'> > >() ) ) { 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()); left_ws = peek < css_comments >() != NULL; } if (operands.size() == 0) return lhs; Expression_Obj ex = fold_operands(lhs, operands, operators); state.offset = pstate - state + pstate.offset; ex->pstate(state); return ex; } // 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 std::vector<Operand> operators; // ops // lex operations to apply to lhs const char* left_ws = peek < css_comments >(); while (lex_css< class_char< static_ops > >()) { 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"); } operands.push_back(parse_factor()); left_ws = peek < css_comments >(); } // operands and operators to binary expression Expression_Obj ex = fold_operands(factor, operands, operators); state.offset = pstate - state + pstate.offset; ex->pstate(state); return ex; } // EO parse_operators // 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); // expression can be evaluated return value; } // string may be interpolated // if (lex< quoted_string >()) { // return &parse_string(); // } else if (peek< ie_property >()) { return parse_ie_property(); } else if (peek< ie_keyword_arg >()) { return parse_ie_keyword_arg(); } else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { return parse_calc_function(); } else if (lex < functional_schema >()) { return parse_function_call_schema(); } else if (lex< identifier_schema >()) { String_Obj string = parse_identifier_schema(); if (String_Schema_Ptr schema = Cast<String_Schema>(string)) { if (lex < exactly < '(' > >()) { schema->append(parse_list()); lex < exactly < ')' > >(); } } return string; } else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { return parse_url_function_string(); } else if (peek< re_functional >()) { return parse_function_call(); } else if (lex< exactly<'+'> >()) { 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()); 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()); 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 (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; } Expression_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; } // 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 lexed_percentage(lexed); } if (lex< sequence < number, lookahead< sequence < op, number > > > >()) { return lexed_number(lexed); } // string may be interpolated if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) { return parse_string(); } if (const char* stop = peek< value_schema >()) { return parse_value_schema(stop); } // string may be interpolated if (lex< quoted_string >()) { return parse_string(); } if (lex< kwd_true >()) { return SASS_MEMORY_NEW(Boolean, pstate, true); } if (lex< kwd_false >()) { return SASS_MEMORY_NEW(Boolean, pstate, false); } if (lex< kwd_null >()) { return SASS_MEMORY_NEW(Null, pstate); } if (lex< identifier >()) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } if (lex< percentage >()) { 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 lexed_hex_color(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 lexed_dimension(lexed); } if (lex< sequence< static_component, one_plus< strict_identifier > > >()) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } if (lex< number >()) { return lexed_number(lexed); } if (lex< variable >()) { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } // Special case handling for `%` proceeding an interpolant. if (lex< sequence< exactly<'%'>, optional< percentage > > >()) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); // unreachable statement 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) { 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)); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); return str_quoted; } String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); 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))); } // 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(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated error("unterminated interpolant inside string constant " + chunk.to_string(), pstate); } } 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))); break; } ++ i; } return schema.detach(); } String_Constant_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; --after_token.column; --str.end; --position; String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace()); return str_node; } String_Obj Parser::parse_string() { return parse_interpolated_chunk(Token(lexed)); } String_Obj Parser::parse_ie_property() { lex< ie_property >(); Token str(lexed); const char* i = str.begin; // see if there any interpolants const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(str.begin, str.end); if (!p) { return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); } String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); while (i < str.end) { p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, str.end); if (p) { if (i < p) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty } 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, 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(); interp_node->is_interpolant(true); schema->append(interp_node); i = j; } else { // throw an error if the interpolant is unterminated error("unterminated interpolant inside IE function " + str.to_string(), pstate); } } 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))); } break; } } return schema; } String_Obj Parser::parse_ie_keyword_arg() { String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); if (lex< variable >()) { kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); } else { 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 >()) { 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) { // initialize the string schema object to add tokens String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); if (peek<exactly<'}'>>()) { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } const char* e; const char* ee = end; end = stop; size_t num_items = 0; bool need_space = false; while (position < stop) { // parse space between tokens if (lex< spaces >() && num_items) { need_space = true; } if (need_space) { need_space = false; // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); } if ((e = peek< re_functional >()) && e < stop) { 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; if (lex< re_static_expression >()) { ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); } else { ex = parse_list(); } ex->is_interpolant(true); schema->append(ex); if (!lex < exactly < rbrace > >()) { css_error("Invalid CSS", " after ", ": expected \"}\", was "); } } // lex some string constants or other valid token // Note: [-+] chars are left over from i.e. `#{3}+3` else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); } // 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()); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; } if (peek < exactly < '-' > >()) break; } else if (lex< sequence < identifier > >()) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { // need_space = true; } } // lex (normalized) variable else if (lex< variable >()) { std::string name(Util::normalize_underscores(lexed)); schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); } // lex percentage value else if (lex< percentage >()) { schema->append(lexed_percentage(lexed)); } // lex dimension value else if (lex< dimension >()) { schema->append(lexed_dimension(lexed)); } // lex number value else if (lex< number >()) { schema->append(lexed_number(lexed)); } // lex hex color value else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { 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()); } else { break; } ++num_items; } if (position != stop) { schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); position = stop; } end = ee; return schema; } // this parses interpolation outside other strings // means the result must not be quoted again later String_Obj Parser::parse_identifier_schema() { Token id(lexed); const char* i = id.begin; // see if there any interpolants const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(id.begin, id.end); if (!p) { return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); } String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); while (i < id.end) { 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)); 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); 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); } } 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)); position = o; } break; } } return schema ? schema.detach() : 0; } // calc functions should preserve arguments Function_Call_Obj Parser::parse_calc_function() { lex< identifier >(); std::string name(lexed); ParserState call_pos = pstate; lex< exactly<'('> >(); ParserState arg_pos = pstate; const char* arg_beg = position; parse_list(); const char* arg_end = position; lex< skip_over_scopes < exactly < '(' >, exactly < ')' > > >(); 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); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } String_Obj Parser::parse_url_function_string() { std::string prefix(""); if (lex< uri_prefix >()) { prefix = std::string(lexed); } lex < optional_spaces >(); String_Obj url_string = parse_url_function_argument(); std::string suffix(""); if (lex< real_uri_suffix >()) { suffix = std::string(lexed); } std::string uri(""); if (url_string) { uri = url_string->to_string({ NESTED, 5 }); } 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; } else { std::string res = prefix + uri + suffix; return SASS_MEMORY_NEW(String_Constant, pstate, res); } } String_Obj Parser::parse_url_function_argument() { const char* p = position; std::string uri(""); if (lex< real_uri_value >(false)) { uri = lexed.to_string(); } if (peek< exactly< hash_lbrace > >()) { const char* pp = position; // 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)); } else if (uri != "") { std::string res = Util::rtrim(uri); return SASS_MEMORY_NEW(String_Constant, pstate, res); } return 0; } Function_Call_Obj Parser::parse_function_call() { lex< identifier >(); std::string name(lexed); ParserState call_pos = pstate; Arguments_Obj args = parse_arguments(); return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } Function_Call_Schema_Obj Parser::parse_function_call_schema() { String_Obj name = parse_identifier_schema(); ParserState source_position_of_call = pstate; Arguments_Obj args = parse_arguments(); return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); } Content_Obj Parser::parse_content_directive() { return SASS_MEMORY_NEW(Content, pstate); } If_Obj Parser::parse_if_directive(bool else_if) { stack.push_back(Scope::Control); ParserState if_source_position = pstate; bool root = block_stack.back()->is_root(); Expression_Obj predicate = parse_list(); Block_Obj block = parse_block(root); Block_Obj alternative = NULL; // 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)); } else if (lex_css< kwd_else_directive >()) { alternative = parse_block(root); } stack.pop_back(); return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); } For_Obj Parser::parse_for_directive() { 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); 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); 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); } // helper to parse a var token Token Parser::lex_variable() { // peek for dollar sign first if (!peek< exactly <'$'> >()) { css_error("Invalid CSS", " after ", ": expected \"$\", was "); } // we expect a simple identifier as the call name if (!lex< sequence < exactly <'$'>, identifier > >()) { lex< exactly <'$'> >(); // move pstate and position up css_error("Invalid CSS", " after ", ": expected identifier, was "); } // return object return token; } // helper to parse identifier Token Parser::lex_identifier() { // we expect a simple identifier as the call name if (!lex< identifier >()) { // ToDo: pstate wrong? css_error("Invalid CSS", " after ", ": expected identifier, was "); } // return object return token; } Each_Obj Parser::parse_each_directive() { stack.push_back(Scope::Control); ParserState each_source_position = pstate; 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); vars.push_back(Util::normalize_underscores(lexed)); } if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate); 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); } // called after parsing `kwd_while_directive` While_Obj Parser::parse_while_directive() { stack.push_back(Scope::Control); 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(); // return ast node return call.detach(); } // EO parse_while_directive Media_Block_Obj Parser::parse_media_block() { stack.push_back(Scope::Media); 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; media_block->block(parse_css_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()); queries->update_pstate(pstate); return queries.detach(); } // Expression_Ptr Parser::parse_media_query() Media_Query_Obj Parser::parse_media_query() { 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()); 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(SASS_MEMORY_NEW(String_Constant, pstate, " ")); schema->append(parse_identifier_schema()); media_query->media_type(schema); } 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); } if (!lex_css< exactly<'('> >()) { error("media query expression must begin with '('", pstate); } Expression_Obj feature; if (peek_css< exactly<')'> >()) { error("media feature required in media query expression", pstate); } feature = parse_expression(); Expression_Obj expression = 0; if (lex_css< exactly<':'> >()) { expression = parse_list(DELAYED); } if (!lex_css< exactly<')'> >()) { error("unclosed parenthesis in media query expression", pstate); } 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()); // return ast node return query; } // parse one query operation // may encounter nested queries Supports_Condition_Obj Parser::parse_supports_condition() { lex < css_whitespace >(); 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); } Supports_Condition_Obj Parser::parse_supports_operator() { Supports_Condition_Obj cond = parse_supports_condition_in_parens(); 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); } return cond; } Supports_Condition_Obj Parser::parse_supports_interpolation() { if (!lex < interpolant >()) return 0; String_Obj interp = parse_interpolated_chunk(lexed); if (!interp) return 0; 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; // parse something declaration like Declaration_Obj declaration = parse_declaration(); if (!declaration) error("@supports condition expected declaration", pstate); cond = SASS_MEMORY_NEW(Supports_Declaration, declaration->pstate(), declaration->property(), declaration->value()); // 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 (!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); } else { cond = parse_supports_declaration(); if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate); } lex < css_whitespace >(); return cond; } At_Root_Block_Obj Parser::parse_at_root_block() { ParserState at_source_position = pstate; Block_Obj body = 0; At_Root_Query_Obj expr; Lookahead lookahead_result; if (lex_css< exactly<'('> >()) { expr = parse_at_root_query(); } if (peek_css < exactly<'{'> >()) { lex <optional_spaces>(); body = parse_block(true); } else if ((lookahead_result = lookahead_for_selector(position)).found) { Ruleset_Obj r = parse_ruleset(lookahead_result); body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); body->append(r); } At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); if (!expr.isNull()) at_root->expression(expr); 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< 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); Expression_Obj expression = parse_list(); List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); if (expression->concrete_type() == Expression::LIST) { 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); return cond; } Directive_Obj Parser::parse_special_directive() { std::string kwd(lexed); if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate); // 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(false)); } lex < css_comments >(false); if (lex < static_property >()) { at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { at_rule->value(parse_list()); } lex < css_comments >(false); if (peek< exactly<'{'> >()) { at_rule->block(parse_block()); } 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); 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(false)); } lex < css_comments >(false); if (lex < static_property >()) { at_rule->value(parse_interpolated_chunk(Token(lexed))); } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { at_rule->value(parse_list()); } lex < css_comments >(false); if (peek< exactly<'{'> >()) { at_rule->block(parse_block()); } return at_rule; } 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); 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 0; } Expression_Obj Parser::lex_interp_uri() { // create a string schema by lexing optional interpolations return lex_interp< re_string_uri_open, re_string_uri_close >(); } Expression_Obj Parser::lex_interp_string() { 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; } Expression_Obj Parser::lex_almost_any_value_chars() { const char* match = lex < one_plus < alternatives < sequence < exactly <'\\'>, any_char >, sequence < negate < sequence < exactly < url_kwd >, exactly <'('> > >, neg_class_char < almost_any_value_class > >, sequence < exactly <'/'>, negate < alternatives < exactly <'/'>, exactly <'*'> > > >, sequence < exactly <'\\'>, exactly <'#'>, negate < exactly <'{'> > >, sequence < exactly <'!'>, negate < alpha > > > > >(false); if (match) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } return NULL; } Expression_Obj Parser::lex_almost_any_value_token() { Expression_Obj rv; if (*position == 0) return 0; 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; } String_Schema_Obj Parser::parse_almost_any_value() { String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); if (*position == 0) return 0; lex < spaces >(false); Expression_Obj token = lex_almost_any_value_token(); if (!token) return 0; schema->append(token); if (*position == 0) { schema->rtrim(); return schema.detach(); } while ((token = lex_almost_any_value_token())) { schema->append(token); } lex < css_whitespace >(); schema->rtrim(); return schema.detach(); } Warning_Obj Parser::parse_warning() { 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); } return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); } Error_Obj Parser::parse_error() { 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); } return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); } Debug_Obj Parser::parse_debug() { 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); } 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()); } Lookahead Parser::lookahead_for_selector(const char* start) { // init result struct Lookahead rv = Lookahead(); // get start position const char* p = start ? start : position; // match in one big "regex" rv.error = p; if (const char* q = peek < re_selector_list >(p) ) { bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; while (p < q) { // did we have interpolations? if (*p == '#' && *(p+1) == '{') { rv.has_interpolants = true; p = q; break; } ++ p; } // store anyway } // ToDo: remove 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; } rv.parsable = ! rv.has_interpolants; // return result return rv; } // EO lookahead_for_selector // used in parse_block_nodes and parse_special_directive // ToDo: actual usage is still not really clear to me? Lookahead Parser::lookahead_for_include(const char* start) { // we actually just lookahead for a selector Lookahead rv = lookahead_for_selector(start); // but the "found" rules are different if (const char* p = rv.position) { // check for additional abort condition if (peek < exactly<';'> >(p)) rv.found = p; else if (peek < exactly<'}'> >(p)) rv.found = p; } // return result return rv; } // EO lookahead_for_include // look ahead for a token with interpolation in it // we mostly use the result if there is an interpolation // everything that passes here gets parsed as one schema // meaning it will not be parsed as a space separated list Lookahead Parser::lookahead_for_value(const char* start) { // init result struct Lookahead rv = Lookahead(); // get start position const char* p = start ? start : position; // match in one big "regex" if (const char* q = peek < non_greedy < alternatives < // consume whitespace block_comment, // spaces, // main tokens sequence < interpolant, optional < quoted_string > >, identifier, variable, // issue #442 sequence < parenthese_scope, interpolant, optional < quoted_string > > >, sequence < // optional_spaces, alternatives < // end_of_file, exactly<'{'>, exactly<'}'>, exactly<';'> > > > >(p) ) { if (p == q) return rv; while (p < q) { // did we have interpolations? if (*p == '#' && *(p+1) == '{') { rv.has_interpolants = true; p = q; break; } ++ p; } // store anyway // ToDo: remove rv.position = q; // check expected opening bracket // only after successful matching if (peek < exactly<'{'> >(q)) rv.found = q; else if (peek < exactly<';'> >(q)) rv.found = q; else if (peek < exactly<'}'> >(q)) rv.found = q; } // return result return rv; } // EO lookahead_for_value void Parser::read_bom() { size_t skip = 0; std::string encoding; bool utf_8 = false; switch ((unsigned char) source[0]) { case 0xEF: skip = check_bom_chars(source, end, utf_8_bom, 3); encoding = "UTF-8"; utf_8 = true; break; case 0xFE: skip = check_bom_chars(source, end, utf_16_bom_be, 2); encoding = "UTF-16 (big endian)"; break; case 0xFF: skip = check_bom_chars(source, end, utf_16_bom_le, 2); skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); break; case 0x00: skip = check_bom_chars(source, end, utf_32_bom_be, 4); encoding = "UTF-32 (big endian)"; break; case 0x2B: skip = check_bom_chars(source, end, utf_7_bom_1, 4) | check_bom_chars(source, end, utf_7_bom_2, 4) | check_bom_chars(source, end, utf_7_bom_3, 4) | check_bom_chars(source, end, utf_7_bom_4, 4) | check_bom_chars(source, end, utf_7_bom_5, 5); encoding = "UTF-7"; break; case 0xF7: skip = check_bom_chars(source, end, utf_1_bom, 3); encoding = "UTF-1"; break; case 0xDD: skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); encoding = "UTF-EBCDIC"; break; case 0x0E: skip = check_bom_chars(source, end, scsu_bom, 3); encoding = "SCSU"; break; case 0xFB: skip = check_bom_chars(source, end, bocu_1_bom, 3); encoding = "BOCU-1"; 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); position += skip; } size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) { size_t skip = 0; if (src + len > end) return 0; for (size_t i = 0; i < len; ++i, ++skip) { if ((unsigned char) src[i] != bom[i]) return 0; } return skip; } 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]); } 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 = 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) || (ops[0].operand == Sass_OP::DIV) || (ops[0].operand == Sass_OP::MUL) || (ops[0].operand == Sass_OP::NEQ) || (ops[0].operand == Sass_OP::LT) || (ops[0].operand == Sass_OP::GT) || (ops[0].operand == Sass_OP::LTE) || (ops[0].operand == Sass_OP::GTE) )) { Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); 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 = 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); return base; } 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]); } } else { base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } 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 = 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); } // 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, 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 (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); 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'; utf8::next(pos_left, end); break; } const char* prev = pos_left; utf8::prior(prev, source); if (*prev == '\r') break; if (*prev == '\n') break; pos_left = prev; } if (pos_left < source) { pos_left = source; } bool ellipsis_right = false; const char* end_right(pos); const char* pos_right(pos); while (end_right < end) { if (utf8::distance(pos_right, end_right) > max_len) { ellipsis_left = *(pos_right) != '\n' && *(pos_right) != '\r'; break; } if (*end_right == '\r') break; if (*end_right == '\n') break; utf8::next(end_right, end); } // if (*end_right == 0) end_right ++; std::string left(pos_left, end_left); 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; // now pass new message to the more generic error function error(msg + prefix + quote(left) + middle + quote(right), pstate); } }