#ifndef SASS_PARSER_H #define SASS_PARSER_H #include #include #include "ast.hpp" #include "position.hpp" #include "context.hpp" #include "position.hpp" #include "prelexer.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 ParserState { public: enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; Context& ctx; std::vector block_stack; std::vector stack; Media_Block_Ptr last_media_block; const char* source; const char* position; const char* end; Position before_token; Position after_token; ParserState pstate; Backtraces traces; size_t indentation; size_t nestings; Token lexed; Parser(Context& ctx, const ParserState& pstate, Backtraces traces) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), traces(traces), indentation(0), nestings(0) { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); // special static parsers to convert strings into certain selectors static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); #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 ParserState 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 incremetal on original object (API wants offset?) pstate = ParserState(path, source, lexed, 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; Position bt = before_token; Position at = after_token; ParserState 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(std::string msg); void error(std::string msg, Position pos); // generate message with given and expected sample // text before and in the middle are configurable void css_error(const std::string& msg, const std::string& prefix = " after ", const std::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(); Ruleset_Obj parse_ruleset(Lookahead lookahead); Selector_List_Obj parse_selector_list(bool chroot); Complex_Selector_Obj parse_complex_selector(bool chroot); Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); Compound_Selector_Obj parse_compound_selector(); Simple_Selector_Obj parse_simple_selector(); Wrapped_Selector_Obj parse_negated_selector(); Simple_Selector_Obj parse_pseudo_selector(); Attribute_Selector_Obj 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); bool parse_number_prefix(); Declaration_Obj parse_declaration(); Expression_Obj parse_map(); Expression_Obj parse_bracket_list(); Expression_Obj parse_list(bool delayed = false); Expression_Obj parse_comma_list(bool delayed = false); Expression_Obj parse_space_list(); Expression_Obj parse_disjunction(); Expression_Obj parse_conjunction(); Expression_Obj parse_relation(); Expression_Obj parse_expression(); Expression_Obj parse_operators(); Expression_Obj parse_factor(); Expression_Obj parse_value(); Function_Call_Obj parse_calc_function(); Function_Call_Obj parse_function_call(); Function_Call_Schema_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(); Value_Obj parse_static_value(); String_Schema_Obj parse_css_variable_value(bool top_level = true); String_Schema_Obj parse_css_variable_value_token(bool top_level = true); 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); For_Obj parse_for_directive(); Each_Obj parse_each_directive(); While_Obj parse_while_directive(); Return_Obj parse_return_directive(); Content_Obj parse_content_directive(); void parse_charset_directive(); Media_Block_Obj parse_media_block(); List_Obj parse_media_queries(); Media_Query_Obj parse_media_query(); Media_Query_Expression_Obj parse_media_expression(); Supports_Block_Obj parse_supports_directive(); Supports_Condition_Obj parse_supports_condition(); Supports_Condition_Obj parse_supports_negation(); Supports_Condition_Obj parse_supports_operator(); Supports_Condition_Obj parse_supports_interpolation(); Supports_Condition_Obj parse_supports_declaration(); Supports_Condition_Obj parse_supports_condition_in_parens(); At_Root_Block_Obj parse_at_root_block(); At_Root_Query_Obj parse_at_root_query(); String_Schema_Obj parse_almost_any_value(); Directive_Obj parse_special_directive(); Directive_Obj parse_prefixed_directive(); Directive_Obj parse_directive(); Warning_Obj parse_warning(); Error_Obj parse_error(); Debug_Obj parse_debug(); Value_Ptr color_or_string(const std::string& lexed) const; // be more like ruby sass Expression_Obj lex_almost_any_value_token(); Expression_Obj lex_almost_any_value_chars(); Expression_Obj lex_interp_string(); Expression_Obj lex_interp_uri(); Expression_Obj lex_interpolation(); // these will throw errors Token lex_variable(); Token lex_identifier(); void parse_block_comments(); Lookahead lookahead_for_value(const char* start = 0); Lookahead lookahead_for_selector(const char* start = 0); Lookahead lookahead_for_include(const char* start = 0); Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); void throw_syntax_error(std::string message, size_t ln = 0); void throw_read_error(std::string message, size_t ln = 0); template Expression_Obj lex_interp() { if (lex < open >(false)) { String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { Expression_Obj itpl = lex_interpolation(); if (!itpl.isNull()) schema->append(itpl); while (lex < close >(false)) { // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); if (position[0] == '#' && position[1] == '{') { Expression_Obj itpl = lex_interpolation(); if (!itpl.isNull()) schema->append(itpl); } else { return schema; } } } else { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } } return 0; } public: static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); private: Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; Value_Ptr lexed_hex_color(const std::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