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> |
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(src: 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> |
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> |
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> |
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(begin: position, end: it_before_token); |
178 | |
179 | // update after_token position for current token |
180 | after_token.add(begin: it_before_token, end: 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> |
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> |
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 (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> |
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(element: 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(element: 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: |
381 | Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); }; |
382 | Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); }; |
383 | Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, 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 | |