#ifndef SASS_PARSER_H #define SASS_PARSER_H // sass.hpp must go before all system headers to get the // __EXTENSIONS__ fix on Solaris. #include "sass.hpp" #include #include #include "ast.hpp" #include "position.hpp" #include "context.hpp" #include "position.hpp" #include "prelexer.hpp" #include "source.hpp" #ifndef MAX_NESTING // Note that this limit is not an exact science // it depends on various factors, which some are // not under our control (compile time or even OS // dependent settings on the available stack size) // It should fix most common segfault cases though. #define MAX_NESTING 512 #endif struct Lookahead { const char* found; const char* error; const char* position; bool parsable; bool has_interpolants; bool is_custom_property; }; namespace Sass { class Parser : public SourceSpan { public: enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; Context& ctx; sass::vector block_stack; sass::vector stack; SourceDataObj source; const char* begin; const char* position; const char* end; Offset before_token; Offset after_token; SourceSpan pstate; Backtraces traces; size_t indentation; size_t nestings; bool allow_parent; Token lexed; Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); // special static parsers to convert strings into certain selectors static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); #ifdef __clang__ // lex and peak uses the template parameter to branch on the action, which // triggers clangs tautological comparison on the single-comparison // branches. This is not a bug, just a merging of behaviour into // one function #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wtautological-compare" #endif // skip current token and next whitespace // moves SourceSpan right before next token void advanceToNextToken(); bool peek_newline(const char* start = 0); // skip over spaces, tabs and line comments template const char* sneak(const char* start = 0) { using namespace Prelexer; // maybe use optional start position from arguments? const char* it_position = start ? start : position; // skip white-space? if (mx == spaces || mx == no_spaces || mx == css_comments || mx == css_whitespace || mx == optional_spaces || mx == optional_css_comments || mx == optional_css_whitespace ) { return it_position; } // skip over spaces, tabs and sass line comments const char* pos = optional_css_whitespace(it_position); // always return a valid position return pos ? pos : it_position; } // match will not skip over space, tabs and line comment // return the position where the lexer match will occur template const char* match(const char* start = 0) { // match the given prelexer return mx(position); } // peek will only skip over space, tabs and line comment // return the position where the lexer match will occur template const char* peek(const char* start = 0) { // sneak up to the actual token we want to lex // this should skip over white-space if desired const char* it_before_token = sneak < mx >(start); // match the given prelexer const char* match = mx(it_before_token); // check if match is in valid range return match <= end ? match : 0; } // white-space handling is built into the lexer // this way you do not need to parse it yourself // some matchers don't accept certain white-space // we do not support start arg, since we manipulate // sourcemap offset and we modify the position pointer! // lex will only skip over space, tabs and line comment template const char* lex(bool lazy = true, bool force = false) { if (*position == 0) return 0; // position considered before lexed token // we can skip whitespace or comments for // lazy developers (but we need control) const char* it_before_token = position; // sneak up to the actual token we want to lex // this should skip over white-space if desired if (lazy) it_before_token = sneak < mx >(position); // now call matcher to get position after token const char* it_after_token = mx(it_before_token); // check if match is in valid range if (it_after_token > end) return 0; // maybe we want to update the parser state anyway? if (force == false) { // assertion that we got a valid match if (it_after_token == 0) return 0; // assertion that we actually lexed something if (it_after_token == it_before_token) return 0; } // create new lexed token object (holds the parse results) lexed = Token(position, it_before_token, it_after_token); // advance position (add whitespace before current token) before_token = after_token.add(position, it_before_token); // update after_token position for current token after_token.add(it_before_token, it_after_token); // ToDo: could probably do this incremental on original object (API wants offset?) pstate = SourceSpan(source, before_token, after_token - before_token); // advance internal char iterator return position = it_after_token; } // lex_css skips over space, tabs, line and block comment // all block comments will be consumed and thrown away // source-map position will point to token after the comment template const char* lex_css() { // copy old token Token prev = lexed; // store previous pointer const char* oldpos = position; Offset bt = before_token; Offset at = after_token; SourceSpan op = pstate; // throw away comments // update srcmap position lex < Prelexer::css_comments >(); // now lex a new token const char* pos = lex< mx >(); // maybe restore prev state if (pos == 0) { pstate = op; lexed = prev; position = oldpos; after_token = at; before_token = bt; } // return match return pos; } // all block comments will be skipped and thrown away template const char* peek_css(const char* start = 0) { // now peek a token (skip comments first) return peek< mx >(peek < Prelexer::css_comments >(start)); } #ifdef __clang__ #pragma clang diagnostic pop #endif void error(sass::string msg); // generate message with given and expected sample // text before and in the middle are configurable void css_error(const sass::string& msg, const sass::string& prefix = " after ", const sass::string& middle = ", was: ", const bool trim = true); void read_bom(); Block_Obj parse(); Import_Obj parse_import(); Definition_Obj parse_definition(Definition::Type which_type); Parameters_Obj parse_parameters(); Parameter_Obj parse_parameter(); Mixin_Call_Obj parse_include_directive(); Arguments_Obj parse_arguments(); Argument_Obj parse_argument(); Assignment_Obj parse_assignment(); StyleRuleObj parse_ruleset(Lookahead lookahead); SelectorListObj parseSelectorList(bool chroot); ComplexSelectorObj parseComplexSelector(bool chroot); Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); CompoundSelectorObj parseCompoundSelector(); SimpleSelectorObj parse_simple_selector(); PseudoSelectorObj parse_negated_selector2(); Expression* parse_binominal(); SimpleSelectorObj parse_pseudo_selector(); AttributeSelectorObj parse_attribute_selector(); Block_Obj parse_block(bool is_root = false); Block_Obj parse_css_block(bool is_root = false); bool parse_block_nodes(bool is_root = false); bool parse_block_node(bool is_root = false); Declaration_Obj parse_declaration(); ExpressionObj parse_map(); ExpressionObj parse_bracket_list(); ExpressionObj parse_list(bool delayed = false); ExpressionObj parse_comma_list(bool delayed = false); ExpressionObj parse_space_list(); ExpressionObj parse_disjunction(); ExpressionObj parse_conjunction(); ExpressionObj parse_relation(); ExpressionObj parse_expression(); ExpressionObj parse_operators(); ExpressionObj parse_factor(); ExpressionObj parse_value(); Function_Call_Obj parse_calc_function(); Function_Call_Obj parse_function_call(); Function_Call_Obj parse_function_call_schema(); String_Obj parse_url_function_string(); String_Obj parse_url_function_argument(); String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); String_Obj parse_string(); ValueObj parse_static_value(); String_Schema_Obj parse_css_variable_value(); String_Obj parse_ie_property(); String_Obj parse_ie_keyword_arg(); String_Schema_Obj parse_value_schema(const char* stop); String_Obj parse_identifier_schema(); If_Obj parse_if_directive(bool else_if = false); ForRuleObj parse_for_directive(); EachRuleObj parse_each_directive(); WhileRuleObj parse_while_directive(); MediaRule_Obj parseMediaRule(); sass::vector parseCssMediaQueries(); sass::string parseIdentifier(); CssMediaQuery_Obj parseCssMediaQuery(); Return_Obj parse_return_directive(); Content_Obj parse_content_directive(); void parse_charset_directive(); List_Obj parse_media_queries(); Media_Query_Obj parse_media_query(); Media_Query_ExpressionObj parse_media_expression(); SupportsRuleObj parse_supports_directive(); SupportsConditionObj parse_supports_condition(bool top_level); SupportsConditionObj parse_supports_negation(); SupportsConditionObj parse_supports_operator(bool top_level); SupportsConditionObj parse_supports_interpolation(); SupportsConditionObj parse_supports_declaration(); SupportsConditionObj parse_supports_condition_in_parens(bool parens_required); AtRootRuleObj parse_at_root_block(); At_Root_Query_Obj parse_at_root_query(); String_Schema_Obj parse_almost_any_value(); AtRuleObj parse_directive(); WarningRuleObj parse_warning(); ErrorRuleObj parse_error(); DebugRuleObj parse_debug(); Value* color_or_string(const sass::string& lexed) const; // be more like ruby sass ExpressionObj lex_almost_any_value_token(); ExpressionObj lex_almost_any_value_chars(); ExpressionObj lex_interp_string(); ExpressionObj lex_interp_uri(); ExpressionObj lex_interpolation(); // these will throw errors Token lex_variable(); Token lex_identifier(); void parse_block_comments(bool store = true); Lookahead lookahead_for_value(const char* start = 0); Lookahead lookahead_for_selector(const char* start = 0); Lookahead lookahead_for_include(const char* start = 0); ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, Operand op); ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, sass::vector& ops, size_t i = 0); void throw_syntax_error(sass::string message, size_t ln = 0); void throw_read_error(sass::string message, size_t ln = 0); template ExpressionObj lex_interp() { if (lex < open >(false)) { String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { ExpressionObj itpl = lex_interpolation(); if (!itpl.isNull()) schema->append(itpl); while (lex < close >(false)) { // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { ExpressionObj itpl = lex_interpolation(); if (!itpl.isNull()) schema->append(itpl); } else { return schema; } } } else { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } } return {}; } public: static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed); static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed); static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed); static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed); private: Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); }; Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); }; Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); }; Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); }; static const char* re_attr_sensitive_close(const char* src); static const char* re_attr_insensitive_close(const char* src); }; size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); } #endif