walrus 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. data/bin/walrus +44 -0
  2. data/ext/jindex/extconf.rb +11 -0
  3. data/ext/jindex/jindex.c +79 -0
  4. data/ext/mkdtemp/extconf.rb +11 -0
  5. data/ext/mkdtemp/mkdtemp.c +41 -0
  6. data/lib/walrus/additions/module.rb +36 -0
  7. data/lib/walrus/additions/string.rb +37 -0
  8. data/lib/walrus/additions/test/unit/error_collector.rb +62 -0
  9. data/lib/walrus/compile_error.rb +28 -0
  10. data/lib/walrus/compiler.rb +124 -0
  11. data/lib/walrus/contrib/spec/walruscloth_spec.rb +32 -0
  12. data/lib/walrus/contrib/walruscloth.rb +82 -0
  13. data/lib/walrus/diff.rb +89 -0
  14. data/lib/walrus/document.rb +98 -0
  15. data/lib/walrus/grammar/additions/proc.rb +20 -0
  16. data/lib/walrus/grammar/additions/regexp.rb +21 -0
  17. data/lib/walrus/grammar/additions/string.rb +52 -0
  18. data/lib/walrus/grammar/additions/symbol.rb +42 -0
  19. data/lib/walrus/grammar/and_predicate.rb +40 -0
  20. data/lib/walrus/grammar/array_result.rb +19 -0
  21. data/lib/walrus/grammar/continuation_wrapper_exception.rb +28 -0
  22. data/lib/walrus/grammar/left_recursion_exception.rb +27 -0
  23. data/lib/walrus/grammar/location_tracking.rb +105 -0
  24. data/lib/walrus/grammar/match_data_wrapper.rb +65 -0
  25. data/lib/walrus/grammar/memoizing.rb +41 -0
  26. data/lib/walrus/grammar/memoizing_cache.rb +94 -0
  27. data/lib/walrus/grammar/node.rb +60 -0
  28. data/lib/walrus/grammar/not_predicate.rb +40 -0
  29. data/lib/walrus/grammar/parse_error.rb +39 -0
  30. data/lib/walrus/grammar/parser_state.rb +181 -0
  31. data/lib/walrus/grammar/parslet.rb +28 -0
  32. data/lib/walrus/grammar/parslet_choice.rb +120 -0
  33. data/lib/walrus/grammar/parslet_combination.rb +26 -0
  34. data/lib/walrus/grammar/parslet_combining.rb +154 -0
  35. data/lib/walrus/grammar/parslet_merge.rb +88 -0
  36. data/lib/walrus/grammar/parslet_omission.rb +57 -0
  37. data/lib/walrus/grammar/parslet_repetition.rb +97 -0
  38. data/lib/walrus/grammar/parslet_repetition_default.rb +58 -0
  39. data/lib/walrus/grammar/parslet_sequence.rb +202 -0
  40. data/lib/walrus/grammar/predicate.rb +57 -0
  41. data/lib/walrus/grammar/proc_parslet.rb +52 -0
  42. data/lib/walrus/grammar/regexp_parslet.rb +73 -0
  43. data/lib/walrus/grammar/skipped_substring_exception.rb +36 -0
  44. data/lib/walrus/grammar/string_enumerator.rb +45 -0
  45. data/lib/walrus/grammar/string_parslet.rb +75 -0
  46. data/lib/walrus/grammar/string_result.rb +24 -0
  47. data/lib/walrus/grammar/symbol_parslet.rb +63 -0
  48. data/lib/walrus/grammar.rb +170 -0
  49. data/lib/walrus/no_parameter_marker.rb +19 -0
  50. data/lib/walrus/parser.rb +420 -0
  51. data/lib/walrus/runner.rb +356 -0
  52. data/lib/walrus/template.rb +75 -0
  53. data/lib/walrus/walrus_grammar/assignment_expression.rb +24 -0
  54. data/lib/walrus/walrus_grammar/block_directive.rb +28 -0
  55. data/lib/walrus/walrus_grammar/comment.rb +24 -0
  56. data/lib/walrus/walrus_grammar/def_directive.rb +64 -0
  57. data/lib/walrus/walrus_grammar/echo_directive.rb +44 -0
  58. data/lib/walrus/walrus_grammar/escape_sequence.rb +24 -0
  59. data/lib/walrus/walrus_grammar/import_directive.rb +44 -0
  60. data/lib/walrus/walrus_grammar/include_directive.rb +27 -0
  61. data/lib/walrus/walrus_grammar/instance_variable.rb +24 -0
  62. data/lib/walrus/walrus_grammar/literal.rb +24 -0
  63. data/lib/walrus/walrus_grammar/message_expression.rb +25 -0
  64. data/lib/walrus/walrus_grammar/multiline_comment.rb +54 -0
  65. data/lib/walrus/walrus_grammar/placeholder.rb +40 -0
  66. data/lib/walrus/walrus_grammar/raw_directive.rb +42 -0
  67. data/lib/walrus/walrus_grammar/raw_text.rb +45 -0
  68. data/lib/walrus/walrus_grammar/ruby_directive.rb +29 -0
  69. data/lib/walrus/walrus_grammar/ruby_expression.rb +31 -0
  70. data/lib/walrus/walrus_grammar/set_directive.rb +24 -0
  71. data/lib/walrus/walrus_grammar/silent_directive.rb +44 -0
  72. data/lib/walrus/walrus_grammar/slurp_directive.rb +25 -0
  73. data/lib/walrus/walrus_grammar/super_directive.rb +27 -0
  74. data/lib/walrus.rb +64 -0
  75. data/spec/acceptance/acceptance_spec.rb +97 -0
  76. data/spec/acceptance/block/basic_block.expected +1 -0
  77. data/spec/acceptance/block/basic_block.tmpl +3 -0
  78. data/spec/acceptance/block/nested_blocks.expected +5 -0
  79. data/spec/acceptance/block/nested_blocks.tmpl +11 -0
  80. data/spec/acceptance/comments/comments_and_text.expected +3 -0
  81. data/spec/acceptance/comments/comments_and_text.tmpl +6 -0
  82. data/spec/acceptance/comments/single_comment.expected +0 -0
  83. data/spec/acceptance/comments/single_comment.tmpl +1 -0
  84. data/spec/acceptance/def/alternative_def_calling_conventions.expected +3 -0
  85. data/spec/acceptance/def/alternative_def_calling_conventions.tmpl +18 -0
  86. data/spec/acceptance/def/basic_def_block_no_output.expected +0 -0
  87. data/spec/acceptance/def/basic_def_block_no_output.tmpl +17 -0
  88. data/spec/acceptance/def/defs_can_be_called_multiple_times.expected +3 -0
  89. data/spec/acceptance/def/defs_can_be_called_multiple_times.tmpl +6 -0
  90. data/spec/acceptance/def/defs_can_be_dynamic.expected +4 -0
  91. data/spec/acceptance/def/defs_can_be_dynamic.tmpl +12 -0
  92. data/spec/acceptance/echo/echo_directive_with_numeric_literal.expected +1 -0
  93. data/spec/acceptance/echo/echo_directive_with_numeric_literal.tmpl +1 -0
  94. data/spec/acceptance/echo/echo_expression_list.expected +1 -0
  95. data/spec/acceptance/echo/echo_expression_list.tmpl +1 -0
  96. data/spec/acceptance/echo/echo_short_notation.expected +1 -0
  97. data/spec/acceptance/echo/echo_short_notation.tmpl +1 -0
  98. data/spec/acceptance/echo/echo_simple_expression.expected +1 -0
  99. data/spec/acceptance/echo/echo_simple_expression.tmpl +1 -0
  100. data/spec/acceptance/echo/echo_single_quoted_string_literal.expected +1 -0
  101. data/spec/acceptance/echo/echo_single_quoted_string_literal.tmpl +1 -0
  102. data/spec/acceptance/echo/multiple_echo_statements.expected +1 -0
  103. data/spec/acceptance/echo/multiple_echo_statements.tmpl +2 -0
  104. data/spec/acceptance/includes/basic_included_file.txt +1 -0
  105. data/spec/acceptance/includes/basic_includer.complex +3 -0
  106. data/spec/acceptance/includes/basic_includer.expected +3 -0
  107. data/spec/acceptance/includes/basic_includer.rb +38 -0
  108. data/spec/acceptance/includes/complicated_included_file.txt +3 -0
  109. data/spec/acceptance/includes/complicated_includer.complex +3 -0
  110. data/spec/acceptance/includes/complicated_includer.expected +3 -0
  111. data/spec/acceptance/includes/complicated_includer.rb +41 -0
  112. data/spec/acceptance/includes/nested_include_1.txt +3 -0
  113. data/spec/acceptance/includes/nested_include_2.txt +1 -0
  114. data/spec/acceptance/includes/nested_includer.complex +3 -0
  115. data/spec/acceptance/includes/nested_includer.expected +4 -0
  116. data/spec/acceptance/includes/nested_includer.rb +41 -0
  117. data/spec/acceptance/inheritance/basic_child.complex +10 -0
  118. data/spec/acceptance/inheritance/basic_child.expected +9 -0
  119. data/spec/acceptance/inheritance/basic_child.rb +54 -0
  120. data/spec/acceptance/inheritance/basic_parent.complex +5 -0
  121. data/spec/acceptance/inheritance/basic_parent.expected +3 -0
  122. data/spec/acceptance/inheritance/basic_parent.rb +41 -0
  123. data/spec/acceptance/inheritance/importing_child.complex +8 -0
  124. data/spec/acceptance/inheritance/importing_child.expected +7 -0
  125. data/spec/acceptance/inheritance/importing_child.rb +46 -0
  126. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.complex +8 -0
  127. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.expected +7 -0
  128. data/spec/acceptance/inheritance/subdirectory/importing_child_in_subdirectory.rb +44 -0
  129. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.expected +0 -0
  130. data/spec/acceptance/multiline_comments/multiline_comment_with_directives_inside.tmpl +15 -0
  131. data/spec/acceptance/multiline_comments/simple_multiline_comment.expected +2 -0
  132. data/spec/acceptance/multiline_comments/simple_multiline_comment.tmpl +4 -0
  133. data/spec/acceptance/raw/complicated_raw_example.expected +57 -0
  134. data/spec/acceptance/raw/complicated_raw_example.tmpl +79 -0
  135. data/spec/acceptance/raw-text/UTF_8.expected +12 -0
  136. data/spec/acceptance/raw-text/UTF_8.tmpl +12 -0
  137. data/spec/acceptance/raw-text/empty_file.expected +0 -0
  138. data/spec/acceptance/raw-text/empty_file.tmpl +0 -0
  139. data/spec/acceptance/raw-text/multi_line.expected +4 -0
  140. data/spec/acceptance/raw-text/multi_line.tmpl +4 -0
  141. data/spec/acceptance/raw-text/single_line.expected +1 -0
  142. data/spec/acceptance/raw-text/single_line.tmpl +1 -0
  143. data/spec/acceptance/raw-text/single_line_whitespace.expected +1 -0
  144. data/spec/acceptance/raw-text/single_line_whitespace.tmpl +1 -0
  145. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.expected +1 -0
  146. data/spec/acceptance/ruby/ruby_directive_is_just_like_silent.tmpl +4 -0
  147. data/spec/acceptance/ruby/ruby_directive_using_here_doc.expected +1 -0
  148. data/spec/acceptance/ruby/ruby_directive_using_here_doc.tmpl +4 -0
  149. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.expected +1 -0
  150. data/spec/acceptance/ruby/ruby_directive_using_here_doc_alt_syntax.tmpl +4 -0
  151. data/spec/acceptance/ruby/ruby_directive_with_accumulate.expected +1 -0
  152. data/spec/acceptance/ruby/ruby_directive_with_accumulate.tmpl +4 -0
  153. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.expected +1 -0
  154. data/spec/acceptance/ruby/ruby_directive_with_accumulate_and_block.tmpl +6 -0
  155. data/spec/acceptance/set/unused_set.expected +0 -0
  156. data/spec/acceptance/set/unused_set.tmpl +1 -0
  157. data/spec/acceptance/set/used_set.expected +1 -0
  158. data/spec/acceptance/set/used_set.tmpl +2 -0
  159. data/spec/acceptance/silent/silent_and_echo_combined.expected +1 -0
  160. data/spec/acceptance/silent/silent_and_echo_combined.tmpl +2 -0
  161. data/spec/acceptance/silent/silent_short_notation.expected +1 -0
  162. data/spec/acceptance/silent/silent_short_notation.tmpl +1 -0
  163. data/spec/acceptance/silent/simple_silent_directive.expected +0 -0
  164. data/spec/acceptance/silent/simple_silent_directive.tmpl +1 -0
  165. data/spec/acceptance/slurp/basic_slurp_demo.expected +1 -0
  166. data/spec/acceptance/slurp/basic_slurp_demo.tmpl +4 -0
  167. data/spec/acceptance/super/super_with_no_effect.expected +4 -0
  168. data/spec/acceptance/super/super_with_no_effect.tmpl +5 -0
  169. data/spec/additions/module_spec.rb +126 -0
  170. data/spec/additions/string_spec.rb +99 -0
  171. data/spec/compiler_spec.rb +55 -0
  172. data/spec/grammar/additions/proc_spec.rb +25 -0
  173. data/spec/grammar/additions/regexp_spec.rb +37 -0
  174. data/spec/grammar/additions/string_spec.rb +106 -0
  175. data/spec/grammar/and_predicate_spec.rb +29 -0
  176. data/spec/grammar/continuation_wrapper_exception_spec.rb +23 -0
  177. data/spec/grammar/match_data_wrapper_spec.rb +41 -0
  178. data/spec/grammar/memoizing_cache_spec.rb +112 -0
  179. data/spec/grammar/node_spec.rb +126 -0
  180. data/spec/grammar/not_predicate_spec.rb +29 -0
  181. data/spec/grammar/parser_state_spec.rb +172 -0
  182. data/spec/grammar/parslet_choice_spec.rb +49 -0
  183. data/spec/grammar/parslet_combining_spec.rb +287 -0
  184. data/spec/grammar/parslet_merge_spec.rb +33 -0
  185. data/spec/grammar/parslet_omission_spec.rb +58 -0
  186. data/spec/grammar/parslet_repetition_spec.rb +77 -0
  187. data/spec/grammar/parslet_sequence_spec.rb +49 -0
  188. data/spec/grammar/parslet_spec.rb +23 -0
  189. data/spec/grammar/predicate_spec.rb +53 -0
  190. data/spec/grammar/proc_parslet_spec.rb +52 -0
  191. data/spec/grammar/regexp_parslet_spec.rb +347 -0
  192. data/spec/grammar/string_enumerator_spec.rb +94 -0
  193. data/spec/grammar/string_parslet_spec.rb +143 -0
  194. data/spec/grammar/symbol_parslet_spec.rb +30 -0
  195. data/spec/grammar_spec.rb +545 -0
  196. data/spec/parser_spec.rb +1418 -0
  197. data/spec/spec_helper.rb +34 -0
  198. data/spec/walrus_grammar/comment_spec.rb +39 -0
  199. data/spec/walrus_grammar/echo_directive_spec.rb +63 -0
  200. data/spec/walrus_grammar/escape_sequence_spec.rb +85 -0
  201. data/spec/walrus_grammar/literal_spec.rb +41 -0
  202. data/spec/walrus_grammar/message_expression_spec.rb +37 -0
  203. data/spec/walrus_grammar/multiline_comment_spec.rb +58 -0
  204. data/spec/walrus_grammar/placeholder_spec.rb +48 -0
  205. data/spec/walrus_grammar/raw_directive_spec.rb +81 -0
  206. data/spec/walrus_grammar/raw_text_spec.rb +65 -0
  207. data/spec/walrus_grammar/silent_directive_spec.rb +34 -0
  208. metadata +291 -0
@@ -0,0 +1,420 @@
1
+ # Copyright 2007 Wincent Colaiuta
2
+ # This program is distributed in the hope that it will be useful, but WITHOUT
3
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
4
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
5
+ # in the accompanying file, "LICENSE.txt", for more details.
6
+ #
7
+ # $Id: /mirrors/Walrus/trunk/walrus/lib/walrus/parser.rb 6755 2007-04-13T14:13:42.727911Z wincent $
8
+
9
+ require 'walrus'
10
+ require 'pathname'
11
+
12
+ module Walrus
13
+
14
+ # The parser is currently quite slow, although perfectly usable.
15
+ # The quickest route to optimizing it may be to replace it with a C parser inside a Ruby extension,
16
+ # possibly generated using Ragel: http://www.cs.queensu.ca/~thurston/ragel/
17
+ class Parser
18
+
19
+ def parse(string, options = {})
20
+ @@grammar.parse(string, options)
21
+ end
22
+
23
+ def compile(string, options = {})
24
+ @@compiler ||= Compiler.new
25
+ parsed = []
26
+ catch :AndPredicateSuccess do # catch here because an empty file will throw
27
+ parsed = parse string, options
28
+ end
29
+ @@compiler.compile parsed, options
30
+ end
31
+
32
+ @@grammar ||= Grammar.subclass('WalrusGrammar') do
33
+
34
+ starting_symbol :template
35
+
36
+ skipping :whitespace_or_newlines
37
+
38
+ rule :whitespace_or_newlines, /\s+/
39
+
40
+ # only spaces and tabs, not newlines
41
+ rule :whitespace, /[ \t\v]+/
42
+ rule :newline, /(\r\n|\r|\n)/
43
+
44
+ # optional whitespace (tabs and spaces only) followed by a backslash/newline (note: this is not escape-aware)
45
+ rule :line_continuation, /[ \t\v]*\\\n/
46
+ rule :end_of_input, /\z/
47
+
48
+ rule :template, :template_element.zero_or_more & :end_of_input.and?
49
+ rule :template_element, :raw_text | :comment | :multiline_comment | :directive | :placeholder |:escape_sequence
50
+
51
+ # anything at all other than the three characters which have special meaning in Walrus: $, \ and #
52
+ rule :raw_text, /[^\$\\#]+/
53
+ production :raw_text.build(:node)
54
+
55
+ node :literal
56
+
57
+ rule :string_literal, :single_quoted_string_literal | :double_quoted_string_literal
58
+ skipping :string_literal, nil
59
+ node :string_literal, :literal
60
+
61
+ rule :single_quoted_string_literal, "'".skip & :single_quoted_string_content.optional & "'".skip
62
+ production :single_quoted_string_literal.build(:string_literal)
63
+ rule :single_quoted_string_content, /(\\(?!').|\\'|[^'\\]+)+/
64
+ rule :double_quoted_string_literal, '"'.skip & :double_quoted_string_content.optional & '"'.skip
65
+ production :double_quoted_string_literal.build(:string_literal)
66
+ rule :double_quoted_string_content, /(\\(?!").|\\"|[^"\\]+)+/
67
+
68
+ # TODO: support 1_000_000 syntax for numeric_literals
69
+ rule :numeric_literal, /\d+\.\d+|\d+(?!\.)/
70
+ production :numeric_literal.build(:literal)
71
+
72
+ # this matches both "foo" and "Foo::bar"
73
+ rule :identifier, /([A-Z][a-zA-Z0-9_]*::)*[a-z_][a-zA-Z0-9_]*/
74
+ production :identifier.build(:literal)
75
+
76
+ # this matches both "Foo" and "Foo::Bar"
77
+ rule :constant, /([A-Z][a-zA-Z0-9_]*::)*[A-Z][a-zA-Z0-9_]*/
78
+ production :constant.build(:literal)
79
+
80
+ rule :symbol_literal, /:[a-zA-Z_][a-zA-Z0-9_]*/
81
+ production :symbol_literal.build(:literal)
82
+
83
+ rule :escape_sequence, '\\'.skip & /[\$\\#]/
84
+ production :escape_sequence.build(:node)
85
+
86
+ rule :comment, '##'.skip & /.*$/
87
+ production :comment.build(:node)
88
+
89
+ # nested multiline comments
90
+ rule :multiline_comment, '#*'.skip & :comment_content.zero_or_more('') & '*#'.skip
91
+ skipping :multiline_comment, nil
92
+ production :multiline_comment.build(:comment, :content)
93
+
94
+ rule :comment_content, (:comment & :newline.skip) |
95
+ :multiline_comment |
96
+ /( # three possibilities:
97
+ [^*#]+ | # 1. any run of characters other than # or *
98
+ \#(?!\#|\*) | # 2. any # not followed by another # or a *
99
+ \*(?!\#) # 3. any * not followed by a #
100
+ )+ # match the three possibilities repeatedly
101
+ /x
102
+
103
+ rule :directive, :block_directive |
104
+ :def_directive |
105
+ :echo_directive |
106
+ :extends_directive |
107
+ :import_directive |
108
+ :include_directive |
109
+ :raw_directive |
110
+ :ruby_directive |
111
+ :set_directive |
112
+ :silent_directive |
113
+ :slurp_directive |
114
+ :super_directive
115
+
116
+ node :directive
117
+
118
+ # directives may span multiple lines if and only if the last character on the line is a backslash \
119
+ skipping :directive, :whitespace | :line_continuation
120
+
121
+ # "Directive tags can be closed explicitly with #, or implicitly with the end of the line"
122
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/language.directives.closures.html
123
+ # This means that to follow a directive by a comment on the same line you must explicitly close the directive first (otherwise the grammar would be ambiguous).
124
+ # Note that "skipping" the end_of_input here is harmless as it isn't actually consumed.
125
+ rule :directive_end, ( /#/ | :newline | :end_of_input ).skip
126
+
127
+ rule :block_directive, '#block'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
128
+ :template_element.zero_or_more([]) &
129
+ '#end'.skip & :directive_end
130
+
131
+ rule :def_directive, '#def'.skip & :identifier & :def_parameter_list.optional([]) & :directive_end &
132
+ :template_element.zero_or_more([]) &
133
+ '#end'.skip & :directive_end
134
+
135
+ production :def_directive.build(:directive, :identifier, :params, :content) # superclass (DefDirective) must be created
136
+ production :block_directive.build(:def_directive, :identifier, :params, :content) # before the subclass (BlockDirective)
137
+
138
+ # "The #echo directive is used to echo the output from expressions that can't be written as simple $placeholders."
139
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.echo.html
140
+ #
141
+ # Convenient alternative short syntax for the #echo directive, similar to ERB (http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/):
142
+ #
143
+ # #= expression(s) #
144
+ #
145
+ # Is a shortcut equivalent to:
146
+ #
147
+ # #echo expression(s) #
148
+ #
149
+ # This is similar to the ERB syntax, but even more concise:
150
+ #
151
+ # <%= expression(s) =>
152
+ #
153
+ # See also the #silent directive, which also has a shortcut syntax.
154
+ #
155
+ rule :echo_directive, '#echo'.skip & :ruby_expression_list & :directive_end | # long form
156
+ '#='.skip & :ruby_expression_list & '#'.skip # short form
157
+ production :echo_directive.build(:directive, :expression)
158
+
159
+ rule :extends_directive, '#extends'.skip & :string_literal & :directive_end
160
+ rule :import_directive, '#import'.skip & :string_literal & :directive_end
161
+ production :import_directive.build(:directive, :class_name) # superclass (ImportDirective) must be created
162
+ production :extends_directive.build(:import_directive, :class_name) # before the subclass (ExtendsDirective)
163
+
164
+ rule :include_directive, '#include'.skip & :include_subparslet
165
+ production :include_directive.build(:directive, :file_name, :subtree)
166
+
167
+ rule :include_subparslet, lambda { |string, options|
168
+
169
+ # scans a string literal
170
+ parslet = :string_literal & :directive_end
171
+ file_name = parslet.parse(string, options)
172
+
173
+ # if options contains non-nil "origin" then try to construct relative path; otherwise just look in current working directory
174
+ if options[:origin]
175
+ current_location = Pathname.new(options[:origin]).dirname
176
+ include_target = current_location + file_name.to_s
177
+ else
178
+ include_target = Pathname.new file_name.to_s
179
+ end
180
+
181
+ # read file into string
182
+ content = include_target.read
183
+
184
+ # try to parse string in sub-parser
185
+ sub_options = { :origin => include_target.to_s }
186
+ sub_result = nil
187
+ catch :AndPredicateSuccess do
188
+ sub_result = Parser.new.parse(content, sub_options)
189
+ end
190
+
191
+ # want to insert a bunch of nodes (a subtree) into the parse tree without advance the location counters
192
+ sub_tree = Grammar::ArrayResult.new [ file_name, sub_result ? sub_result : [] ]
193
+ sub_tree.start = file_name.start
194
+ sub_tree.end = file_name.end
195
+ sub_tree
196
+ }
197
+
198
+ # "Any section of a template definition that is inside a #raw ... #end raw tag pair will be printed verbatim without any parsing of $placeholders or other directives."
199
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.raw.html
200
+ # Unlike Cheetah, Walrus uses a bare "#end" marker and not an "#end raw" to mark the end of the raw block.
201
+ # The presence of a literal #end within a raw block is made possible by using an optional "here doc"-style delimiter:
202
+ #
203
+ # #raw <<END_MARKER
204
+ # content goes here
205
+ # END_MARKER
206
+ #
207
+ # Here the opening "END_MARKER" must be the last thing on the line (trailing whitespace up to and including the newline is allowed but it is not considered to be part of the quoted text). The final "END_MARKER" must be the very first and last thing on the line, or it will not be considered to be an end marker at all and will be considered part of the quoted text. The newline immediately prior to the end marker is included in the quoted text.
208
+ #
209
+ # Or, if the end marker is to be indented:
210
+ #
211
+ # #raw <<-END_MARKER
212
+ # content
213
+ # END_MARKER
214
+ #
215
+ # Here "END_MARKER" may be preceeded by whitespace (and whitespace only) but it must be the last thing on the line. The preceding whitespace is not considered to be part of the quoted text.
216
+ rule :raw_directive, '#raw'.skip & ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
217
+ production :raw_directive.build(:directive, :content)
218
+
219
+ # In order to parse "here documents" we adopt a model similar to the one proposed in this message to the ANTLR interest list:
220
+ # http://www.antlr.org:8080/pipermail/antlr-interest/2005-September/013673.html
221
+ rule :here_document, lambda { |string, options|
222
+
223
+ # for the time-being, not sure if there is much benefit in calling memoizing_parse here
224
+ state = Grammar::ParserState.new(string, options)
225
+ parsed = /<<(-?)([a-zA-Z0-9_]+)[ \t\v]*\n/.to_parseable.parse(state.remainder, state.options)
226
+ state.skipped(parsed)
227
+ marker = parsed.match_data
228
+ indenting = (marker[1] == '') ? false : true
229
+
230
+ if indenting # whitespace allowed before end marker
231
+ end_marker = /^[ \t\v]*#{marker[2]}[ \t\v]*(\n|\z)/.to_parseable # will eat trailing newline
232
+ else # no whitespace allowed before end marker
233
+ end_marker = /^#{marker[2]}[ \t\v]*(\n|\z)/.to_parseable # will eat trailing newline
234
+ end
235
+
236
+ line = /^.*\n/.to_parseable # for gobbling a line
237
+
238
+ while true do
239
+ begin
240
+ skipped = end_marker.parse(state.remainder, state.options)
241
+ state.skipped(skipped) # found end marker, skip it
242
+ break # all done
243
+ rescue Grammar::ParseError # didn't find end marker yet, consume a line
244
+ parsed = line.parse(state.remainder, state.options)
245
+ state.parsed(parsed)
246
+ end
247
+ end
248
+
249
+ # caller will want a String, not an Array
250
+ results = state.results
251
+ document = Grammar::StringResult.new(results.to_s)
252
+ document.start = results.start
253
+ document.end = results.end
254
+ document
255
+ }
256
+
257
+ rule :ruby_directive, '#ruby'.skip & ((:directive_end & /([^#]+|#(?!end)+)*/ & '#end'.skip & :directive_end) | :here_document)
258
+ production :ruby_directive.build(:directive, :content)
259
+
260
+ # Unlike a normal Ruby assignement expression, the lvalue of a "#set" directive is an identifier preceded by a dollar sign.
261
+ rule :set_directive, '#set'.skip & /\$(?![ \r\n\t\v])/.skip & :placeholder_name & '='.skip & (:addition_expression | :unary_expression) & :directive_end
262
+ production :set_directive.build(:directive, :placeholder, :expression)
263
+
264
+ # "#silent is the opposite of #echo. It executes an expression but discards the output."
265
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.silent.html
266
+ #
267
+ # Like the #echo directive, a convienient shorthand syntax is available:
268
+ #
269
+ # # expressions(s) #
270
+ #
271
+ # Equivalent to the long form:
272
+ #
273
+ # #silent expressions(s) #
274
+ #
275
+ # And similar to but more concise than the ERB syntax:
276
+ #
277
+ # <% expressions(s) %>
278
+ #
279
+ # Note that the space between the opening hash character and the expression(s) is required in order for Walrus to distinguish the shorthand for the #silent directive from the other directives. That is, the following is not legal:
280
+ #
281
+ # #expressions(s) #
282
+ #
283
+ rule :silent_directive, '#silent'.skip & :ruby_expression_list & :directive_end | # long form
284
+ '# '.skip & :ruby_expression_list & '#'.skip # short form
285
+ production :silent_directive.build(:directive, :expression)
286
+
287
+ # Accept multiple expressions separated by a semi-colon.
288
+ rule :ruby_expression_list, :ruby_expression >> (';'.skip & :ruby_expression ).zero_or_more
289
+
290
+ # "The #slurp directive eats up the trailing newline on the line it appears in, joining the following line onto the current line."
291
+ # http://www.cheetahtemplate.org/docs/users_guide_html_multipage/output.slurp.html
292
+ # The "slurp" directive must be the last thing on the line (not followed by a comment or directive end marker)
293
+ rule :slurp_directive, '#slurp' & :whitespace.optional.skip & :newline.skip
294
+ production :slurp_directive.build(:directive)
295
+
296
+ rule :super_directive, :super_with_parentheses | :super_without_parentheses
297
+ node :super_directive
298
+ rule :super_with_parentheses, '#super'.skip & :parameter_list.optional & :directive_end
299
+ production :super_with_parentheses.build(:super_directive, :params)
300
+ rule :super_without_parentheses, '#super'.skip & :parameter_list_without_parentheses & :directive_end
301
+ production :super_without_parentheses.build(:super_directive, :params)
302
+
303
+ # The "def_parameter_list" is a special case of parameter list which disallows interpolated placeholders.
304
+ rule :def_parameter_list, '('.skip & ( :def_parameter >> ( ','.skip & :def_parameter ).zero_or_more ).optional & ')'.skip
305
+ rule :def_parameter, :assignment_expression | :identifier
306
+
307
+ rule :parameter_list, '('.skip & ( :parameter >> ( ','.skip & :parameter ).zero_or_more ).optional & ')'.skip
308
+ rule :parameter_list_without_parentheses, :parameter >> ( ','.skip & :parameter ).zero_or_more
309
+ rule :parameter, :placeholder | :ruby_expression
310
+
311
+ # placeholders may be in long form (${foo}) or short form ($foo)
312
+ rule :placeholder, :long_placeholder | :short_placeholder
313
+ node :placeholder
314
+
315
+ # No whitespace allowed between the "$" and the opening "{"
316
+ rule :long_placeholder, '${'.skip & :placeholder_name & :placeholder_parameters.optional([]) & '}'.skip
317
+ production :long_placeholder.build(:placeholder, :name, :params)
318
+
319
+ # No whitespace allowed between the "$" and the placeholder_name
320
+ rule :short_placeholder, /\$(?![ \r\n\t\v])/.skip & :placeholder_name & :placeholder_parameters.optional([])
321
+ production :short_placeholder.build(:placeholder, :name, :params)
322
+
323
+ rule :placeholder_name, :identifier
324
+ rule :placeholder_parameters, '('.skip & (:placeholder_parameter >> (','.skip & :placeholder_parameter).zero_or_more).optional & ')'.skip
325
+ rule :placeholder_parameter, :placeholder | :ruby_expression
326
+
327
+ # simplified Ruby subset
328
+ rule :ruby_expression, :assignment_expression | :addition_expression | :unary_expression
329
+ node :ruby_expression
330
+
331
+ rule :literal_expression, :string_literal |
332
+ :numeric_literal |
333
+ :array_literal |
334
+ :hash_literal |
335
+ :lvalue |
336
+ :symbol_literal
337
+ rule :unary_expression, :message_expression | :literal_expression
338
+
339
+ rule :lvalue, :class_variable | :instance_variable | :identifier | :constant
340
+
341
+ rule :array_literal, '['.skip & ( :ruby_expression >> (','.skip & :ruby_expression ).zero_or_more ).optional & ']'.skip
342
+ production :array_literal.build(:ruby_expression, :elements)
343
+
344
+ rule :hash_literal, '{'.skip & ( :hash_assignment >> (','.skip & :hash_assignment ).zero_or_more ).optional & '}'.skip
345
+ production :hash_literal.build(:ruby_expression, :pairs)
346
+
347
+ rule :hash_assignment, :unary_expression & '=>'.skip & (:addition_expression | :unary_expression)
348
+ production :hash_assignment.build(:ruby_expression, :lvalue, :expression)
349
+
350
+ rule :assignment_expression, :lvalue & '='.skip & (:addition_expression | :unary_expression)
351
+ production :assignment_expression.build(:ruby_expression, :lvalue, :expression)
352
+
353
+ # addition is left-associative (left-recursive)
354
+ rule :addition_expression, :addition_expression & '+'.skip & :unary_expression |
355
+ :unary_expression & '+'.skip & :unary_expression
356
+
357
+ production :addition_expression.build(:ruby_expression, :left, :right)
358
+
359
+ # message expressions are left-associative (left-recursive)
360
+ rule :message_expression, :message_expression & '.'.skip & :method_expression |
361
+ :literal_expression & '.'.skip & :method_expression
362
+ production :message_expression.build(:ruby_expression, :target, :message)
363
+
364
+ rule :method_expression, :method_with_parentheses | :method_without_parentheses
365
+ node :method_expression, :ruby_expression
366
+
367
+ rule :method_with_parentheses, :identifier & :method_parameter_list.optional([])
368
+ production :method_with_parentheses.build(:method_expression, :name, :params)
369
+ rule :method_without_parentheses, :identifier & :method_parameter_list_without_parentheses
370
+ production :method_without_parentheses.build(:method_expression, :name, :params)
371
+
372
+ rule :method_parameter_list, '('.skip & ( :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more ).optional & ')'.skip
373
+ rule :method_parameter, :ruby_expression
374
+ rule :method_parameter_list_without_parentheses, :method_parameter >> ( ','.skip & :method_parameter ).zero_or_more
375
+
376
+ rule :class_variable, '@@'.skip & :identifier
377
+ skipping :class_variable, nil
378
+ production :class_variable.build(:ruby_expression)
379
+
380
+ rule :instance_variable, '@'.skip & :identifier
381
+ skipping :instance_variable, nil
382
+ production :instance_variable.build(:ruby_expression)
383
+
384
+ # TODO: regexp literal expression
385
+
386
+ # Ruby + allowing placeholders for unary expressions
387
+ rule :extended_ruby_expression, :extended_unary_expression | :ruby_expression
388
+ node :extended_ruby_expression
389
+
390
+ rule :extended_unary_expression, :placeholder | :unary_expression
391
+
392
+ # only after defining the grammar is it safe to extend the classes dynamically created during the grammar definition
393
+ # TODO: modify the code which creates classes on the fly so that the requires are no longer order-sensitive
394
+ require 'walrus/walrus_grammar/assignment_expression'
395
+ require 'walrus/walrus_grammar/block_directive'
396
+ require 'walrus/walrus_grammar/comment'
397
+ require 'walrus/walrus_grammar/def_directive'
398
+ require 'walrus/walrus_grammar/echo_directive'
399
+ require 'walrus/walrus_grammar/escape_sequence'
400
+ require 'walrus/walrus_grammar/import_directive'
401
+ require 'walrus/walrus_grammar/include_directive'
402
+ require 'walrus/walrus_grammar/instance_variable'
403
+ require 'walrus/walrus_grammar/literal'
404
+ require 'walrus/walrus_grammar/message_expression'
405
+ require 'walrus/walrus_grammar/multiline_comment'
406
+ require 'walrus/walrus_grammar/placeholder'
407
+ require 'walrus/walrus_grammar/raw_directive'
408
+ require 'walrus/walrus_grammar/raw_text'
409
+ require 'walrus/walrus_grammar/ruby_directive'
410
+ require 'walrus/walrus_grammar/ruby_expression'
411
+ require 'walrus/walrus_grammar/set_directive'
412
+ require 'walrus/walrus_grammar/silent_directive'
413
+ require 'walrus/walrus_grammar/slurp_directive'
414
+ require 'walrus/walrus_grammar/super_directive'
415
+
416
+ end
417
+
418
+ end # class Parser
419
+
420
+ end # module Walrus