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