1 #ifndef SASS_PARSER_H 2 #define SASS_PARSER_H 3 4 // sass.hpp must go before all system headers to get the 5 // __EXTENSIONS__ fix on Solaris. 6 #include "sass.hpp" 7 8 #include <string> 9 #include <vector> 10 11 #include "ast.hpp" 12 #include "position.hpp" 13 #include "context.hpp" 14 #include "position.hpp" 15 #include "prelexer.hpp" 16 #include "source.hpp" 17 18 #ifndef MAX_NESTING 19 // Note that this limit is not an exact science 20 // it depends on various factors, which some are 21 // not under our control (compile time or even OS 22 // dependent settings on the available stack size) 23 // It should fix most common segfault cases though. 24 #define MAX_NESTING 512 25 #endif 26 27 struct Lookahead { 28 const char* found; 29 const char* error; 30 const char* position; 31 bool parsable; 32 bool has_interpolants; 33 bool is_custom_property; 34 }; 35 36 namespace Sass { 37 38 class Parser : public SourceSpan { 39 public: 40 41 enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; 42 43 Context& ctx; 44 sass::vector<Block_Obj> block_stack; 45 sass::vector<Scope> stack; 46 SourceDataObj source; 47 const char* begin; 48 const char* position; 49 const char* end; 50 Offset before_token; 51 Offset after_token; 52 SourceSpan pstate; 53 Backtraces traces; 54 size_t indentation; 55 size_t nestings; 56 bool allow_parent; 57 Token lexed; 58 59 Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); 60 61 // special static parsers to convert strings into certain selectors 62 static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); 63 64 #ifdef __clang__ 65 66 // lex and peak uses the template parameter to branch on the action, which 67 // triggers clangs tautological comparison on the single-comparison 68 // branches. This is not a bug, just a merging of behaviour into 69 // one function 70 71 #pragma clang diagnostic push 72 #pragma clang diagnostic ignored "-Wtautological-compare" 73 74 #endif 75 76 77 // skip current token and next whitespace 78 // moves SourceSpan right before next token 79 void advanceToNextToken(); 80 81 bool peek_newline(const char* start = 0); 82 83 // skip over spaces, tabs and line comments 84 template <Prelexer::prelexer mx> sneak(const char * start=0)85 const char* sneak(const char* start = 0) 86 { 87 using namespace Prelexer; 88 89 // maybe use optional start position from arguments? 90 const char* it_position = start ? start : position; 91 92 // skip white-space? 93 if (mx == spaces || 94 mx == no_spaces || 95 mx == css_comments || 96 mx == css_whitespace || 97 mx == optional_spaces || 98 mx == optional_css_comments || 99 mx == optional_css_whitespace 100 ) { 101 return it_position; 102 } 103 104 // skip over spaces, tabs and sass line comments 105 const char* pos = optional_css_whitespace(it_position); 106 // always return a valid position 107 return pos ? pos : it_position; 108 109 } 110 111 // match will not skip over space, tabs and line comment 112 // return the position where the lexer match will occur 113 template <Prelexer::prelexer mx> match(const char * start=0)114 const char* match(const char* start = 0) 115 { 116 // match the given prelexer 117 return mx(position); 118 } 119 120 // peek will only skip over space, tabs and line comment 121 // return the position where the lexer match will occur 122 template <Prelexer::prelexer mx> peek(const char * start=0)123 const char* peek(const char* start = 0) 124 { 125 126 // sneak up to the actual token we want to lex 127 // this should skip over white-space if desired 128 const char* it_before_token = sneak < mx >(start); 129 130 // match the given prelexer 131 const char* match = mx(it_before_token); 132 133 // check if match is in valid range 134 return match <= end ? match : 0; 135 136 } 137 138 // white-space handling is built into the lexer 139 // this way you do not need to parse it yourself 140 // some matchers don't accept certain white-space 141 // we do not support start arg, since we manipulate 142 // sourcemap offset and we modify the position pointer! 143 // lex will only skip over space, tabs and line comment 144 template <Prelexer::prelexer mx> lex(bool lazy=true,bool force=false)145 const char* lex(bool lazy = true, bool force = false) 146 { 147 148 if (*position == 0) return 0; 149 150 // position considered before lexed token 151 // we can skip whitespace or comments for 152 // lazy developers (but we need control) 153 const char* it_before_token = position; 154 155 // sneak up to the actual token we want to lex 156 // this should skip over white-space if desired 157 if (lazy) it_before_token = sneak < mx >(position); 158 159 // now call matcher to get position after token 160 const char* it_after_token = mx(it_before_token); 161 162 // check if match is in valid range 163 if (it_after_token > end) return 0; 164 165 // maybe we want to update the parser state anyway? 166 if (force == false) { 167 // assertion that we got a valid match 168 if (it_after_token == 0) return 0; 169 // assertion that we actually lexed something 170 if (it_after_token == it_before_token) return 0; 171 } 172 173 // create new lexed token object (holds the parse results) 174 lexed = Token(position, it_before_token, it_after_token); 175 176 // advance position (add whitespace before current token) 177 before_token = after_token.add(position, it_before_token); 178 179 // update after_token position for current token 180 after_token.add(it_before_token, it_after_token); 181 182 // ToDo: could probably do this incremental on original object (API wants offset?) 183 pstate = SourceSpan(source, before_token, after_token - before_token); 184 185 // advance internal char iterator 186 return position = it_after_token; 187 188 } 189 190 // lex_css skips over space, tabs, line and block comment 191 // all block comments will be consumed and thrown away 192 // source-map position will point to token after the comment 193 template <Prelexer::prelexer mx> lex_css()194 const char* lex_css() 195 { 196 // copy old token 197 Token prev = lexed; 198 // store previous pointer 199 const char* oldpos = position; 200 Offset bt = before_token; 201 Offset at = after_token; 202 SourceSpan op = pstate; 203 // throw away comments 204 // update srcmap position 205 lex < Prelexer::css_comments >(); 206 // now lex a new token 207 const char* pos = lex< mx >(); 208 // maybe restore prev state 209 if (pos == 0) { 210 pstate = op; 211 lexed = prev; 212 position = oldpos; 213 after_token = at; 214 before_token = bt; 215 } 216 // return match 217 return pos; 218 } 219 220 // all block comments will be skipped and thrown away 221 template <Prelexer::prelexer mx> peek_css(const char * start=0)222 const char* peek_css(const char* start = 0) 223 { 224 // now peek a token (skip comments first) 225 return peek< mx >(peek < Prelexer::css_comments >(start)); 226 } 227 228 #ifdef __clang__ 229 230 #pragma clang diagnostic pop 231 232 #endif 233 234 void error(sass::string msg); 235 // generate message with given and expected sample 236 // text before and in the middle are configurable 237 void css_error(const sass::string& msg, 238 const sass::string& prefix = " after ", 239 const sass::string& middle = ", was: ", 240 const bool trim = true); 241 void read_bom(); 242 243 Block_Obj parse(); 244 Import_Obj parse_import(); 245 Definition_Obj parse_definition(Definition::Type which_type); 246 Parameters_Obj parse_parameters(); 247 Parameter_Obj parse_parameter(); 248 Mixin_Call_Obj parse_include_directive(); 249 Arguments_Obj parse_arguments(); 250 Argument_Obj parse_argument(); 251 Assignment_Obj parse_assignment(); 252 StyleRuleObj parse_ruleset(Lookahead lookahead); 253 SelectorListObj parseSelectorList(bool chroot); 254 ComplexSelectorObj parseComplexSelector(bool chroot); 255 Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); 256 CompoundSelectorObj parseCompoundSelector(); 257 SimpleSelectorObj parse_simple_selector(); 258 PseudoSelectorObj parse_negated_selector2(); 259 Expression* parse_binominal(); 260 SimpleSelectorObj parse_pseudo_selector(); 261 AttributeSelectorObj parse_attribute_selector(); 262 Block_Obj parse_block(bool is_root = false); 263 Block_Obj parse_css_block(bool is_root = false); 264 bool parse_block_nodes(bool is_root = false); 265 bool parse_block_node(bool is_root = false); 266 267 Declaration_Obj parse_declaration(); 268 ExpressionObj parse_map(); 269 ExpressionObj parse_bracket_list(); 270 ExpressionObj parse_list(bool delayed = false); 271 ExpressionObj parse_comma_list(bool delayed = false); 272 ExpressionObj parse_space_list(); 273 ExpressionObj parse_disjunction(); 274 ExpressionObj parse_conjunction(); 275 ExpressionObj parse_relation(); 276 ExpressionObj parse_expression(); 277 ExpressionObj parse_operators(); 278 ExpressionObj parse_factor(); 279 ExpressionObj parse_value(); 280 Function_Call_Obj parse_calc_function(); 281 Function_Call_Obj parse_function_call(); 282 Function_Call_Obj parse_function_call_schema(); 283 String_Obj parse_url_function_string(); 284 String_Obj parse_url_function_argument(); 285 String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); 286 String_Obj parse_string(); 287 ValueObj parse_static_value(); 288 String_Schema_Obj parse_css_variable_value(); 289 String_Obj parse_ie_property(); 290 String_Obj parse_ie_keyword_arg(); 291 String_Schema_Obj parse_value_schema(const char* stop); 292 String_Obj parse_identifier_schema(); 293 If_Obj parse_if_directive(bool else_if = false); 294 ForRuleObj parse_for_directive(); 295 EachRuleObj parse_each_directive(); 296 WhileRuleObj parse_while_directive(); 297 MediaRule_Obj parseMediaRule(); 298 sass::vector<CssMediaQuery_Obj> parseCssMediaQueries(); 299 sass::string parseIdentifier(); 300 CssMediaQuery_Obj parseCssMediaQuery(); 301 Return_Obj parse_return_directive(); 302 Content_Obj parse_content_directive(); 303 void parse_charset_directive(); 304 List_Obj parse_media_queries(); 305 Media_Query_Obj parse_media_query(); 306 Media_Query_ExpressionObj parse_media_expression(); 307 SupportsRuleObj parse_supports_directive(); 308 SupportsConditionObj parse_supports_condition(bool top_level); 309 SupportsConditionObj parse_supports_negation(); 310 SupportsConditionObj parse_supports_operator(bool top_level); 311 SupportsConditionObj parse_supports_interpolation(); 312 SupportsConditionObj parse_supports_declaration(); 313 SupportsConditionObj parse_supports_condition_in_parens(bool parens_required); 314 AtRootRuleObj parse_at_root_block(); 315 At_Root_Query_Obj parse_at_root_query(); 316 String_Schema_Obj parse_almost_any_value(); 317 AtRuleObj parse_directive(); 318 WarningRuleObj parse_warning(); 319 ErrorRuleObj parse_error(); 320 DebugRuleObj parse_debug(); 321 322 Value* color_or_string(const sass::string& lexed) const; 323 324 // be more like ruby sass 325 ExpressionObj lex_almost_any_value_token(); 326 ExpressionObj lex_almost_any_value_chars(); 327 ExpressionObj lex_interp_string(); 328 ExpressionObj lex_interp_uri(); 329 ExpressionObj lex_interpolation(); 330 331 // these will throw errors 332 Token lex_variable(); 333 Token lex_identifier(); 334 335 void parse_block_comments(bool store = true); 336 337 Lookahead lookahead_for_value(const char* start = 0); 338 Lookahead lookahead_for_selector(const char* start = 0); 339 Lookahead lookahead_for_include(const char* start = 0); 340 341 ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, Operand op); 342 ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, sass::vector<Operand>& ops, size_t i = 0); 343 344 void throw_syntax_error(sass::string message, size_t ln = 0); 345 void throw_read_error(sass::string message, size_t ln = 0); 346 347 348 template <Prelexer::prelexer open, Prelexer::prelexer close> lex_interp()349 ExpressionObj lex_interp() 350 { 351 if (lex < open >(false)) { 352 String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); 353 // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; 354 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); 355 if (position[0] == '#' && position[1] == '{') { 356 ExpressionObj itpl = lex_interpolation(); 357 if (!itpl.isNull()) schema->append(itpl); 358 while (lex < close >(false)) { 359 // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; 360 schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); 361 if (position[0] == '#' && position[1] == '{') { 362 ExpressionObj itpl = lex_interpolation(); 363 if (!itpl.isNull()) schema->append(itpl); 364 } else { 365 return schema; 366 } 367 } 368 } else { 369 return SASS_MEMORY_NEW(String_Constant, pstate, lexed); 370 } 371 } 372 return {}; 373 } 374 375 public: 376 static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed); 377 static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed); 378 static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed); 379 static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed); 380 private: lexed_number(const sass::string & parsed)381 Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); }; lexed_dimension(const sass::string & parsed)382 Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); }; lexed_percentage(const sass::string & parsed)383 Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); }; lexed_hex_color(const sass::string & parsed)384 Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); }; 385 386 static const char* re_attr_sensitive_close(const char* src); 387 static const char* re_attr_insensitive_close(const char* src); 388 389 }; 390 391 size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); 392 } 393 394 #endif 395