Simplify remaining spec_helper requires
[walrus.git] / lib / walrus / grammar.rb
1 # Copyright 2007-2014 Greg Hurrell. All rights reserved.
2 # Licensed under the terms of the BSD 2-clause license.
3
4 require 'walrat/grammar'
5 require 'walrus'
6 require 'pathname'
7
8 module Walrus
9   # The parser is currently quite slow, although perfectly usable.
10   # The quickest route to optimizing it may be to replace it with a C parser
11   # inside a Ruby extension, possibly generated using Ragel
12   class Grammar < Walrat::Grammar
13     autoload :AssignmentExpression, 'walrus/grammar/assignment_expression'
14     autoload :BlockDirective,       'walrus/grammar/block_directive'
15     autoload :Comment,              'walrus/grammar/comment'
16     autoload :DefDirective,         'walrus/grammar/def_directive'
17     autoload :EchoDirective,        'walrus/grammar/echo_directive'
18     autoload :EscapeSequence,       'walrus/grammar/escape_sequence'
19     autoload :ImportDirective,      'walrus/grammar/import_directive'
20     autoload :IncludeDirective,     'walrus/grammar/include_directive'
21     autoload :InstanceVariable,     'walrus/grammar/instance_variable'
22     autoload :Literal,              'walrus/grammar/literal'
23     autoload :MessageExpression,    'walrus/grammar/message_expression'
24     autoload :MultilineComment,     'walrus/grammar/multiline_comment'
25     autoload :Placeholder,          'walrus/grammar/placeholder'
26     autoload :RawDirective,         'walrus/grammar/raw_directive'
27     autoload :RawText,              'walrus/grammar/raw_text'
28     autoload :RubyDirective,        'walrus/grammar/ruby_directive'
29     autoload :RubyExpression,       'walrus/grammar/ruby_expression'
30     autoload :SetDirective,         'walrus/grammar/set_directive'
31     autoload :SilentDirective,      'walrus/grammar/silent_directive'
32     autoload :SlurpDirective,       'walrus/grammar/slurp_directive'
33     autoload :SuperDirective,       'walrus/grammar/super_directive'
34
35     starting_symbol :template
36     skipping    :whitespace_or_newlines
37     rule        :whitespace_or_newlines, /\s+/
38
39     # only spaces and tabs, not newlines
40     rule        :whitespace,  /[ \t]+/
41     rule        :newline,     /(\r\n|\r|\n)/
42
43     # optional whitespace (tabs and spaces only) followed by a
44     # backslash/newline (note: this is not escape-aware)
45     rule        :line_continuation, /[ \t]*\\\n/
46     rule        :end_of_input,      /\z/
47
48     rule        :template,
49                 :template_element.zero_or_more &
50                 :end_of_input.and?
51     rule        :template_element,
52                 :raw_text           |
53                 :comment            |
54                 :multiline_comment  |
55                 :directive          |
56                 :placeholder        |
57                 :escape_sequence
58
59     # anything at all other than the three characters which have special
60     # meaning in Walrus: $, \ and #
61     rule        :raw_text,  /[^\$\\#]+/
62     production  :raw_text
63
64     rule        :string_literal,
65                 :single_quoted_string_literal |
66                 :double_quoted_string_literal
67     skipping    :string_literal, nil
68     node        :string_literal, :literal
69
70     rule        :single_quoted_string_literal,
71                 "'".skip &
72                 :single_quoted_string_content.optional &
73                 "'".skip
74     node        :single_quoted_string_literal,
75                 :string_literal
76     production  :single_quoted_string_literal
77     rule        :single_quoted_string_content,
78                 /(\\(?!').|\\'|[^'\\]+)+/
79     rule        :double_quoted_string_literal,
80                 '"'.skip &
81                 :double_quoted_string_content.optional &
82                 '"'.skip
83     node        :double_quoted_string_literal, :string_literal
84     production  :double_quoted_string_literal
85     rule        :double_quoted_string_content,
86                 /(\\(?!").|\\"|[^"\\]+)+/
87
88     # TODO: support 1_000_000 syntax for numeric_literals
89     rule        :numeric_literal, /\d+\.\d+|\d+(?!\.)/
90     node        :numeric_literal, :literal
91     production  :numeric_literal
92
93     # this matches both "foo" and "Foo::bar"
94     rule        :identifier, /([A-Z][a-zA-Z0-9_]*::)*[a-z_][a-zA-Z0-9_]*/
95     node        :identifier, :literal
96     production  :identifier
97
98     # this matches both "Foo" and "Foo::Bar"
99     rule        :constant, /([A-Z][a-zA-Z0-9_]*::)*[A-Z][a-zA-Z0-9_]*/
100     node        :constant, :literal
101     production  :constant
102
103     rule        :symbol_literal, /:[a-zA-Z_][a-zA-Z0-9_]*/
104     node        :symbol_literal, :literal
105     production  :symbol_literal
106
107     rule        :escape_sequence, '\\'.skip & /[\$\\#]/
108     production  :escape_sequence
109
110     rule        :comment, '##'.skip & /.*$/
111     production  :comment
112
113     # nested multiline comments
114     rule        :multiline_comment,
115                 '#*'.skip & :comment_content.zero_or_more('') & '*#'.skip
116     skipping    :multiline_comment, nil
117     production  :multiline_comment, :content
118
119     rule        :comment_content,
120                 (:comment & :newline.skip)  |
121                 :multiline_comment          |
122                 /(                # three possibilities:
123                   [^*#]+      |   # - any run of chars other than # or *
124                   \#(?!\#|\*) |   # - any # not followed by # or a *
125                   \*(?!\#)        # - any * not followed by  #
126                  )+               # match the three possibilities repeatedly
127                 /x
128
129     rule        :directive, :block_directive    |
130                             :def_directive      |
131                             :echo_directive     |
132                             :extends_directive  |
133                             :import_directive   |
134                             :include_directive  |
135                             :raw_directive      |
136                             :ruby_directive     |
137                             :set_directive      |
138                             :silent_directive   |
139                             :slurp_directive    |
140                             :super_directive
141
142     node        :directive
143
144     # directives may span multiple lines if and only if the last character on
145     # the line is a backslash \
146     skipping    :directive, :whitespace | :line_continuation
147
148     # "Directive tags can be closed explicitly with #, or implicitly with the
149     # end of the line"
150     # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/language.directives.closures.html
151     # This means that to follow a directive by a comment on the same line you
152     # must explicitly close the directive first (otherwise the grammar would
153     # be ambiguous).
154     # Note that "skipping" the end_of_input here is harmless as it isn't
155     # actually consumed.
156     rule        :directive_end,
157                 ( /#/ | :newline | :end_of_input ).skip
158
159     rule        :block_directive,
160                 '#block'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
161                 :template_element.zero_or_more([]) &
162                 '#end'.skip & :directive_end
163     production  :block_directive, :identifier, :params, :content
164
165     rule        :def_directive,
166                 '#def'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
167                 :template_element.zero_or_more([]) &
168                 '#end'.skip & :directive_end
169     production  :def_directive, :identifier, :params, :content
170
171     rule        :echo_directive_long_form,
172                 '#echo'.skip & :ruby_expression_list & :directive_end
173     rule        :echo_directive_short_form,
174                 '#='.skip & :ruby_expression_list & '#'.skip
175     rule        :echo_directive,
176                 :echo_directive_long_form | :echo_directive_short_form
177     production  :echo_directive, :expression
178
179     rule        :import_directive,
180                 '#import'.skip & :string_literal & :directive_end
181     production  :import_directive, :class_name
182
183     rule        :extends_directive,
184                 '#extends'.skip & :string_literal & :directive_end
185     node        :extends_directive, :import_directive
186     production  :extends_directive, :class_name
187
188     rule        :include_directive, '#include'.skip & :include_subparslet
189     production  :include_directive, :file_name, :subtree
190
191     rule        :include_subparslet, lambda { |string, options|
192
193       # scans a string literal
194       parslet   = :string_literal & :directive_end
195       file_name = parslet.parse string, options
196
197       # if options contains non-nil "origin" then try to construct relative
198       # path; otherwise just look in current working directory
199       if options[:origin]
200         current_location  = Pathname.new(options[:origin]).dirname
201         include_target    = current_location + file_name.to_s
202       else
203         include_target    = Pathname.new file_name.to_s
204       end
205
206       # read file into string
207       content = include_target.read
208
209       # try to parse string in sub-parser
210       sub_options = { :origin => include_target.to_s }
211       sub_result  = nil
212       catch :AndPredicateSuccess do
213         sub_result = Parser.new.parse content, sub_options
214       end
215
216       # want to insert a bunch of nodes (a subtree) into the parse tree
217       # without advancing the location counters
218       sub_tree = Walrat::ArrayResult.new [ file_name, sub_result || [] ]
219       sub_tree.start  = file_name.start
220       sub_tree.end    = file_name.end
221       sub_tree
222     }
223
224     rule        :raw_directive,
225                 '#raw'.skip &
226                 ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
227     production  :raw_directive, :content
228
229     # In order to parse "here documents" we adopt a model similar to the one
230     # proposed in this message to the ANTLR interest list:
231     # http://www.antlr.org:8080/pipermail/antlr-interest/2005-September/013673.html
232     rule        :here_document_marker,  /<<(-?)([a-zA-Z0-9_]+)[ \t]*\n/
233     rule        :line,                  /^.*\n/
234     rule        :here_document,         lambda { |string, options|
235
236       # for the time-being, not sure if there is much benefit in calling
237       # memoizing_parse here
238       state     = Walrat::ParserState.new string, options
239       parsed    = rules[:here_document_marker].parse state.remainder, state.options
240       state.skipped parsed
241       marker    = parsed.match_data
242       indenting = (marker[1] != '')
243
244       if indenting # whitespace allowed before end marker
245         end_marker = /^[ \t]*#{marker[2]}[ \t]*(\n|\z)/.to_parseable  # will eat trailing newline
246       else         # no whitespace allowed before end marker
247         end_marker = /^#{marker[2]}[ \t]*(\n|\z)/.to_parseable        # will eat trailing newline
248       end
249
250       while true do
251         begin
252           skipped = end_marker.parse state.remainder, state.options
253           state.skipped skipped   # found end marker, skip it
254           break                   # all done
255         rescue Walrat::ParseError # didn't find end marker yet, consume a line
256           parsed = rules[:line].parse state.remainder, state.options
257           state.parsed parsed
258         end
259       end
260
261       # caller will want a String, not an Array
262       results         = state.results
263       document        = Walrat::StringResult.new Array(results).join
264       document.start  = results.start
265       document.end    = results.end
266       document
267     }
268
269     rule        :ruby_directive,
270                 '#ruby'.skip &
271                 ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
272     production  :ruby_directive, :content
273
274     # Unlike a normal Ruby assignement expression, the lvalue of a "#set"
275     # directive is an identifier preceded by a dollar sign.
276     rule        :set_directive,
277                 '#set'.skip &
278                 /\$(?![ \r\n\t])/.skip &
279                 :placeholder_name &
280                 '='.skip &
281                 (:addition_expression | :unary_expression) &
282                 :directive_end
283     production  :set_directive, :placeholder, :expression
284
285     rule        :silent_directive_long_form,
286                 '#silent'.skip & :ruby_expression_list & :directive_end
287     rule        :silent_directive_short_form,
288                 '# '.skip & :ruby_expression_list & '#'.skip
289     rule        :silent_directive,
290                 :silent_directive_long_form | :silent_directive_short_form
291     production  :silent_directive, :expression
292
293     # Accept multiple expressions separated by a semi-colon.
294     rule        :ruby_expression_list,
295                 :ruby_expression >> (';'.skip & :ruby_expression ).zero_or_more
296
297     rule        :slurp_directive,
298                 '#slurp' & :whitespace.optional.skip & :newline.skip
299     production  :slurp_directive
300
301     rule        :super_directive,
302                 :super_with_parentheses | :super_without_parentheses
303     rule        :super_with_parentheses,
304                 '#super'.skip & :parameter_list.optional & :directive_end
305     node        :super_with_parentheses, :super_directive
306     production  :super_with_parentheses, :params
307
308     rule        :super_without_parentheses,
309                 '#super'.skip &
310                 :parameter_list_without_parentheses &
311                 :directive_end
312     node        :super_without_parentheses, :super_directive
313     production  :super_without_parentheses, :params
314
315     # The "def_parameter_list" is a special case of parameter list which
316     # disallows interpolated placeholders.
317     rule        :def_parameter_list,
318                 '('.skip & ( :def_parameter >> ( ','.skip & :def_parameter ).zero_or_more ).optional & ')'.skip
319     rule        :def_parameter,
320                 :assignment_expression | :identifier
321
322     rule        :parameter_list,
323                 '('.skip & ( :parameter >> ( ','.skip & :parameter ).zero_or_more ).optional & ')'.skip
324     rule        :parameter_list_without_parentheses,
325                 :parameter >> ( ','.skip & :parameter ).zero_or_more
326     rule        :parameter,
327                 :placeholder | :ruby_expression
328
329     rule        :placeholder,
330                 :long_placeholder | :short_placeholder
331
332     rule        :long_placeholder,
333                 '${'.skip &
334                 :placeholder_name &
335                 :placeholder_parameters.optional([]) &
336                 '}'.skip
337     node        :long_placeholder, :placeholder
338     production  :long_placeholder, :name, :params
339
340     rule        :short_placeholder,
341                 /\$(?![ \r\n\t])/.skip &
342                 :placeholder_name &
343                 :placeholder_parameters.optional([])
344     node        :short_placeholder, :placeholder
345     production  :short_placeholder, :name, :params
346
347     rule        :placeholder_name, :identifier
348     rule        :placeholder_parameters,
349                 '('.skip & (:placeholder_parameter >> (','.skip & :placeholder_parameter).zero_or_more).optional & ')'.skip
350     rule        :placeholder_parameter, :placeholder | :ruby_expression
351
352     # simplified Ruby subset
353     rule        :ruby_expression,
354                 :assignment_expression  |
355                 :addition_expression    |
356                 :unary_expression
357
358     rule        :literal_expression,
359                 :string_literal   |
360                 :numeric_literal  |
361                 :array_literal    |
362                 :hash_literal     |
363                 :lvalue           |
364                 :symbol_literal
365
366     rule        :unary_expression,
367                 :message_expression | :literal_expression
368
369     rule        :lvalue,
370                 :class_variable | :instance_variable | :identifier | :constant
371
372     rule        :array_literal,
373                 '['.skip & ( :ruby_expression >> (','.skip & :ruby_expression ).zero_or_more ).optional & ']'.skip
374     node        :array_literal, :ruby_expression
375     production  :array_literal, :elements
376
377     rule        :hash_literal,
378                 '{'.skip & ( :hash_assignment >> (','.skip & :hash_assignment ).zero_or_more ).optional & '}'.skip
379     node        :hash_literal, :ruby_expression
380     production  :hash_literal, :pairs
381
382     rule        :hash_assignment,
383                 :unary_expression &
384                 '=>'.skip &
385                 (:addition_expression | :unary_expression)
386     node        :hash_assignment, :ruby_expression
387     production  :hash_assignment, :lvalue, :expression
388
389     rule        :assignment_expression,
390                 :lvalue & '='.skip & (:addition_expression | :unary_expression)
391     production  :assignment_expression, :lvalue, :expression
392
393     # addition is left-associative (left-recursive)
394     rule        :addition_expression,
395                 :addition_expression & '+'.skip & :unary_expression |
396                 :unary_expression & '+'.skip & :unary_expression
397     node        :addition_expression, :ruby_expression
398     production  :addition_expression, :left, :right
399
400     # message expressions are left-associative (left-recursive)
401     rule        :message_expression,
402                 :message_expression & '.'.skip & :method_expression |
403                 :literal_expression & '.'.skip & :method_expression
404     production  :message_expression, :target, :message
405
406     rule        :method_expression,
407                 :method_with_parentheses | :method_without_parentheses
408     node        :method_expression, :ruby_expression
409
410     rule        :method_with_parentheses,
411                 :identifier & :method_parameter_list.optional([])
412     node        :method_with_parentheses, :method_expression
413     production  :method_with_parentheses, :name, :params
414     rule        :method_without_parentheses,
415                 :identifier & :method_parameter_list_without_parentheses
416     node        :method_without_parentheses, :method_expression
417     production  :method_without_parentheses, :method_expression, :name, :params
418
419     rule        :method_parameter_list,
420                 '('.skip & ( :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more ).optional & ')'.skip
421     rule        :method_parameter,
422                 :ruby_expression
423     rule        :method_parameter_list_without_parentheses,
424                 :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more
425
426     rule        :class_variable, '@@'.skip & :identifier
427     skipping    :class_variable, nil
428     node        :class_variable, :ruby_expression
429     production  :class_variable
430
431     rule        :instance_variable, '@'.skip & :identifier
432     skipping    :instance_variable, nil
433     production  :instance_variable
434
435     # TODO: regexp literal expression
436
437     # Ruby + allowing placeholders for unary expressions
438     rule        :extended_ruby_expression,
439                 :extended_unary_expression | :ruby_expression
440     rule        :extended_unary_expression,
441                 :placeholder | :unary_expression
442   end # class Grammar
443 end # module Walrus