liquor 0.1.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -9
  5. data/Gemfile +7 -0
  6. data/Guardfile +11 -0
  7. data/MIT-LICENSE +6 -2
  8. data/README.md +4 -122
  9. data/Rakefile +20 -23
  10. data/doc/language-spec.html +768 -0
  11. data/doc/language-spec.md +698 -0
  12. data/lib/liquor.rb +39 -68
  13. data/lib/liquor/ast_tools.rb +28 -0
  14. data/lib/liquor/compiler.rb +110 -0
  15. data/lib/liquor/context.rb +76 -254
  16. data/lib/liquor/diagnostics.rb +151 -0
  17. data/lib/liquor/drop/drop.rb +168 -0
  18. data/lib/liquor/drop/drop_delegation.rb +24 -0
  19. data/lib/liquor/drop/drop_scope.rb +118 -0
  20. data/lib/liquor/drop/dropable.rb +17 -0
  21. data/lib/liquor/emitter.rb +313 -0
  22. data/lib/liquor/extensions/kaminari.rb +14 -0
  23. data/lib/liquor/extensions/pagination.rb +235 -0
  24. data/lib/liquor/extensions/rails.rb +97 -0
  25. data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
  26. data/lib/liquor/extensions/tire.rb +30 -0
  27. data/lib/liquor/external.rb +79 -0
  28. data/lib/liquor/function.rb +94 -0
  29. data/lib/liquor/grammar/lexer.rb +1223 -0
  30. data/lib/liquor/grammar/lexer.rl +297 -0
  31. data/lib/liquor/grammar/parser.racc +288 -0
  32. data/lib/liquor/grammar/parser.rb +885 -0
  33. data/lib/liquor/library.rb +41 -0
  34. data/lib/liquor/manager.rb +146 -0
  35. data/lib/liquor/runtime.rb +167 -0
  36. data/lib/liquor/stdlib/builtin_functions.rb +315 -0
  37. data/lib/liquor/stdlib/builtin_tags.rb +228 -0
  38. data/lib/liquor/stdlib/html_truncater.rb +162 -0
  39. data/lib/liquor/stdlib/partial_tags.rb +76 -0
  40. data/lib/liquor/tag.rb +83 -14
  41. data/lib/liquor/version.rb +1 -1
  42. data/liquor.gemspec +29 -6
  43. data/spec/builtins_spec.rb +264 -0
  44. data/spec/compiler_spec.rb +136 -0
  45. data/spec/context_spec.rb +49 -0
  46. data/spec/drop_delegation_spec.rb +21 -0
  47. data/spec/drop_spec.rb +222 -0
  48. data/spec/errors_spec.rb +40 -0
  49. data/spec/external_spec.rb +207 -0
  50. data/spec/function_spec.rb +80 -0
  51. data/spec/lexer_spec.rb +173 -0
  52. data/spec/library_spec.rb +18 -0
  53. data/spec/manager_spec.rb +84 -0
  54. data/spec/parser_spec.rb +381 -0
  55. data/spec/partials_spec.rb +74 -0
  56. data/spec/runtime_spec.rb +97 -0
  57. data/spec/spec_helper.rb +94 -0
  58. data/spec/tag_spec.rb +7 -0
  59. metadata +216 -173
  60. data/AUTHORS +0 -2
  61. data/CHANGELOG +0 -48
  62. data/Gemfile.lock +0 -91
  63. data/History.txt +0 -44
  64. data/LICENSE +0 -23
  65. data/example/server/example_servlet.rb +0 -37
  66. data/example/server/liquid_servlet.rb +0 -28
  67. data/example/server/liquor_servlet.rb +0 -28
  68. data/example/server/server.rb +0 -12
  69. data/example/server/templates/index.liquid +0 -6
  70. data/example/server/templates/index.liquor +0 -6
  71. data/example/server/templates/products.liquid +0 -45
  72. data/example/server/templates/products.liquor +0 -45
  73. data/init.rb +0 -8
  74. data/lib/extras/liquid_view.rb +0 -51
  75. data/lib/extras/liquor_view.rb +0 -51
  76. data/lib/liquor/block.rb +0 -101
  77. data/lib/liquor/condition.rb +0 -120
  78. data/lib/liquor/document.rb +0 -17
  79. data/lib/liquor/drop.rb +0 -256
  80. data/lib/liquor/errors.rb +0 -11
  81. data/lib/liquor/extensions.rb +0 -72
  82. data/lib/liquor/file_system.rb +0 -62
  83. data/lib/liquor/htmltags.rb +0 -74
  84. data/lib/liquor/module_ex.rb +0 -60
  85. data/lib/liquor/standardfilters.rb +0 -315
  86. data/lib/liquor/strainer.rb +0 -58
  87. data/lib/liquor/tags/assign.rb +0 -33
  88. data/lib/liquor/tags/capture.rb +0 -35
  89. data/lib/liquor/tags/case.rb +0 -83
  90. data/lib/liquor/tags/comment.rb +0 -9
  91. data/lib/liquor/tags/content_for.rb +0 -54
  92. data/lib/liquor/tags/cycle.rb +0 -59
  93. data/lib/liquor/tags/for.rb +0 -136
  94. data/lib/liquor/tags/if.rb +0 -80
  95. data/lib/liquor/tags/ifchanged.rb +0 -20
  96. data/lib/liquor/tags/include.rb +0 -56
  97. data/lib/liquor/tags/unless.rb +0 -33
  98. data/lib/liquor/tags/yield.rb +0 -49
  99. data/lib/liquor/template.rb +0 -181
  100. data/lib/liquor/variable.rb +0 -52
  101. data/performance/shopify.rb +0 -92
  102. data/performance/shopify/comment_form.rb +0 -33
  103. data/performance/shopify/database.rb +0 -45
  104. data/performance/shopify/json_filter.rb +0 -7
  105. data/performance/shopify/liquid.rb +0 -18
  106. data/performance/shopify/liquor.rb +0 -18
  107. data/performance/shopify/money_filter.rb +0 -18
  108. data/performance/shopify/paginate.rb +0 -93
  109. data/performance/shopify/shop_filter.rb +0 -98
  110. data/performance/shopify/tag_filter.rb +0 -25
  111. data/performance/shopify/vision.database.yml +0 -945
  112. data/performance/shopify/weight_filter.rb +0 -11
  113. data/performance/tests/dropify/article.liquid +0 -74
  114. data/performance/tests/dropify/blog.liquid +0 -33
  115. data/performance/tests/dropify/cart.liquid +0 -66
  116. data/performance/tests/dropify/collection.liquid +0 -22
  117. data/performance/tests/dropify/index.liquid +0 -47
  118. data/performance/tests/dropify/page.liquid +0 -8
  119. data/performance/tests/dropify/product.liquid +0 -68
  120. data/performance/tests/dropify/theme.liquid +0 -105
  121. data/performance/tests/ripen/article.liquid +0 -74
  122. data/performance/tests/ripen/blog.liquid +0 -13
  123. data/performance/tests/ripen/cart.liquid +0 -54
  124. data/performance/tests/ripen/collection.liquid +0 -29
  125. data/performance/tests/ripen/index.liquid +0 -32
  126. data/performance/tests/ripen/page.liquid +0 -4
  127. data/performance/tests/ripen/product.liquid +0 -75
  128. data/performance/tests/ripen/theme.liquid +0 -85
  129. data/performance/tests/tribble/404.liquid +0 -56
  130. data/performance/tests/tribble/article.liquid +0 -98
  131. data/performance/tests/tribble/blog.liquid +0 -41
  132. data/performance/tests/tribble/cart.liquid +0 -134
  133. data/performance/tests/tribble/collection.liquid +0 -70
  134. data/performance/tests/tribble/index.liquid +0 -94
  135. data/performance/tests/tribble/page.liquid +0 -56
  136. data/performance/tests/tribble/product.liquid +0 -116
  137. data/performance/tests/tribble/search.liquid +0 -51
  138. data/performance/tests/tribble/theme.liquid +0 -90
  139. data/performance/tests/vogue/article.liquid +0 -66
  140. data/performance/tests/vogue/blog.liquid +0 -32
  141. data/performance/tests/vogue/cart.liquid +0 -58
  142. data/performance/tests/vogue/collection.liquid +0 -19
  143. data/performance/tests/vogue/index.liquid +0 -22
  144. data/performance/tests/vogue/page.liquid +0 -3
  145. data/performance/tests/vogue/product.liquid +0 -62
  146. data/performance/tests/vogue/theme.liquid +0 -122
  147. data/test/assign_test.rb +0 -11
  148. data/test/block_test.rb +0 -58
  149. data/test/capture_test.rb +0 -41
  150. data/test/condition_test.rb +0 -115
  151. data/test/content_for_test.rb +0 -15
  152. data/test/context_test.rb +0 -479
  153. data/test/drop_test.rb +0 -162
  154. data/test/error_handling_test.rb +0 -89
  155. data/test/extra/breakpoint.rb +0 -547
  156. data/test/extra/caller.rb +0 -80
  157. data/test/file_system_test.rb +0 -30
  158. data/test/filter_test.rb +0 -147
  159. data/test/helper.rb +0 -24
  160. data/test/html_tag_test.rb +0 -31
  161. data/test/if_else_test.rb +0 -139
  162. data/test/include_tag_test.rb +0 -129
  163. data/test/module_ex_test.rb +0 -89
  164. data/test/output_test.rb +0 -121
  165. data/test/parsing_quirks_test.rb +0 -54
  166. data/test/regexp_test.rb +0 -45
  167. data/test/security_test.rb +0 -41
  168. data/test/standard_filter_test.rb +0 -170
  169. data/test/standard_tag_test.rb +0 -405
  170. data/test/statements_test.rb +0 -137
  171. data/test/strainer_test.rb +0 -27
  172. data/test/template_test.rb +0 -82
  173. data/test/test_helper.rb +0 -28
  174. data/test/unless_else_test.rb +0 -27
  175. data/test/variable_test.rb +0 -173
  176. data/test/yield_test.rb +0 -24
@@ -0,0 +1,297 @@
1
+ %%{
2
+
3
+ machine liquor;
4
+
5
+ action add_line_start {
6
+ line_starts.push(p + 1)
7
+ }
8
+
9
+ whitespace = [\t ]+;
10
+ symbol = [a-zA-Z_];
11
+ any_newline = '\n' @ add_line_start | any;
12
+ identifier = symbol ( symbol | digit )*;
13
+
14
+ lblock = '{%';
15
+ rblock = '%}';
16
+
17
+ linterp = '{{';
18
+ rinterp = '}}';
19
+
20
+ lcomment = '{!';
21
+ rcomment = '!}';
22
+
23
+ action string_append {
24
+ string << data[p]
25
+ }
26
+
27
+ action string_end {
28
+ tok.(:string, string.dup, ts: str_start)
29
+ string.clear
30
+ fgoto code;
31
+ }
32
+
33
+ action runaway {
34
+ runaway = true
35
+ }
36
+
37
+ action error {
38
+ symbol = data[p]
39
+ if symbol == "\n"
40
+ symbol = "end of line"
41
+ else
42
+ symbol = "`#{symbol.inspect[1..-2]}'"
43
+ end
44
+
45
+ error = SyntaxError.new("unexpected #{symbol}",
46
+ file: name,
47
+ line: line_starts.count - 1,
48
+ start: p - line_starts.last,
49
+ end: p - line_starts.last)
50
+ raise error
51
+ }
52
+
53
+ comment := |*
54
+ lcomment => { fcall comment; };
55
+ rcomment => { fret; };
56
+ any_newline;
57
+ *|;
58
+
59
+ dqstring := |*
60
+ '\\"' => { string << '"' };
61
+ '\\\\' => { string << '\\' };
62
+ '"' => string_end;
63
+ [^\n] @eof runaway
64
+ => string_append;
65
+ "\n" => error;
66
+ *|;
67
+
68
+ sqstring := |*
69
+ "\\'" => { string << "'" };
70
+ "\\\\" => { string << '\\' };
71
+ "'" => string_end;
72
+ [^\n] @eof runaway
73
+ => string_append;
74
+ "\n" => error;
75
+ *|;
76
+
77
+ integer := |*
78
+ digit+ => { tok.(:integer, data[ts...te].to_i) };
79
+ symbol => error;
80
+ any => { fhold; fgoto code; };
81
+ *|;
82
+
83
+ tag_start := |*
84
+ whitespace;
85
+
86
+ 'end ' identifier =>
87
+ { tag = data[ts + 4...te]
88
+ if tag_stack.last == tag
89
+ fixtok.(:lblock2)
90
+ tok.(:endtag)
91
+ pop_tag.()
92
+ else
93
+ (sl, sc), (el, ec) = loc.(ts), loc.(te - 1)
94
+ info = { file: name, line: sl, start: sc, end: ec }
95
+ if tag_stack.any?
96
+ raise SyntaxError.new("unmatched `end #{tag}', expected `end #{tag_stack.last}'", info)
97
+ else
98
+ raise SyntaxError.new("unexpected `end #{tag}'", info)
99
+ end
100
+ end
101
+ fgoto code;
102
+ };
103
+
104
+ identifier ':' =>
105
+ { fixtok.(:lblock2)
106
+ tok.(:keyword, data[ts...te - 1])
107
+ fgoto code;
108
+ };
109
+
110
+ identifier =>
111
+ { tag = data[ts...te]
112
+
113
+ if tag_conts.include? tag
114
+ fixtok.(:lblock2)
115
+ tok.(:keyword, tag)
116
+ fgoto code;
117
+ end
118
+
119
+ tok.(:ident, tag)
120
+ last_tag = tag
121
+
122
+ fgoto code;
123
+ };
124
+
125
+ any =>
126
+ { fhold; fgoto code; };
127
+ *|;
128
+
129
+ code := |*
130
+ whitespace;
131
+
132
+ identifier => { tok.(:ident, data[ts...te]) };
133
+
134
+ digit => { fhold; fgoto integer; };
135
+
136
+ identifier %{ kw_stop = p } ':' whitespace* rblock =>
137
+ { tok.(:keyword, data[ts...kw_stop], te: kw_stop)
138
+ tok.(:rblock, nil, ts: te - 2)
139
+ push_last_tag.()
140
+ fgoto plaintext;
141
+ };
142
+
143
+ identifier ':' =>
144
+ { tok.(:keyword, data[ts...te-1]) };
145
+
146
+ '=' whitespace* rblock =>
147
+ {
148
+ tok.(:keyword, '=', te: ts + 1)
149
+ tok.(:rblock, nil, ts: te - 2)
150
+ push_last_tag.()
151
+ fgoto plaintext;
152
+ };
153
+
154
+ '=' =>
155
+ { tok.(:keyword, '=') };
156
+
157
+ ',' => { tok.(:comma) };
158
+ '.' => { tok.(:dot) };
159
+
160
+ '[' => { tok.(:lbracket) };
161
+ ']' => { tok.(:rbracket) };
162
+
163
+ '(' => { tok.(:lparen) };
164
+ ')' => { tok.(:rparen) };
165
+
166
+ '|' => { tok.(:pipe) };
167
+
168
+ '+' => { tok.(:op_plus) };
169
+ '-' => { tok.(:op_minus) };
170
+ '*' => { tok.(:op_mul) };
171
+ '/' => { tok.(:op_div) };
172
+ '%' => { tok.(:op_mod) };
173
+
174
+ '==' => { tok.(:op_eq) };
175
+ '!=' => { tok.(:op_neq) };
176
+ '>' => { tok.(:op_gt) };
177
+ '>=' => { tok.(:op_geq) };
178
+ '<' => { tok.(:op_lt) };
179
+ '<=' => { tok.(:op_leq) };
180
+
181
+ '!' => { tok.(:op_not) };
182
+
183
+ '&&' => { tok.(:op_and) };
184
+ '||' => { tok.(:op_or) };
185
+
186
+ '"' => { str_start = p; fgoto dqstring; };
187
+ "'" => { str_start = p; fgoto sqstring; };
188
+
189
+ rinterp => { tok.(:rinterp); fgoto plaintext; };
190
+ rblock => { tok.(:rblock); last_tag = nil; fgoto plaintext; };
191
+
192
+ any => error;
193
+ *|;
194
+
195
+ plaintext := |*
196
+ ( '\\{' [%{!]? | '{' ( [^%{!] | '\n' @ add_line_start ) | any_newline - '{' )* =>
197
+ { tok.(:plaintext, data[ts...te]); };
198
+
199
+ '{{' =>
200
+ { tok.(:linterp); fgoto code; };
201
+
202
+ '{%' =>
203
+ { tok.(:lblock); fgoto tag_start; };
204
+
205
+ '{!' =>
206
+ { fcall comment; };
207
+
208
+ any_newline =>
209
+ { fhold; fgoto code; };
210
+ *|;
211
+
212
+ }%%
213
+
214
+ module Liquor
215
+ module Lexer
216
+ %% write data;
217
+
218
+ def self.lex(data, name='(code)', registered_tags={})
219
+ eof = data.length
220
+ ts = nil # token start
221
+ te = nil # token end
222
+ stack = []
223
+
224
+ # Strings
225
+ string = ""
226
+ str_start = nil
227
+ runaway = false
228
+
229
+ # Tags
230
+ kw_stop = nil
231
+
232
+ line_starts = [0]
233
+
234
+ find_line_start = ->(index) {
235
+ line_starts.
236
+ each_index.find { |i|
237
+ line_starts[i + 1].nil? || line_starts[i + 1] > index
238
+ }
239
+ }
240
+ loc = ->(index) {
241
+ line_start_index = find_line_start.(index)
242
+ [ line_start_index, index - line_starts[line_start_index] ]
243
+ }
244
+
245
+ # Tag stack
246
+ tag_stack = []
247
+ tag_conts = []
248
+ last_tag = nil
249
+
250
+ update_tag_cont = ->() {
251
+ tag = tag_stack.last
252
+ if registered_tags.include?(tag)
253
+ tag_conts = registered_tags[tag].continuations
254
+ end
255
+ }
256
+ push_last_tag = ->() {
257
+ if last_tag
258
+ tag_stack.push last_tag
259
+ last_tag = nil
260
+ update_tag_cont.()
261
+ end
262
+ }
263
+ pop_tag = ->() {
264
+ tag_stack.pop
265
+ update_tag_cont.()
266
+ }
267
+
268
+ tokens = []
269
+
270
+ fixtok = ->(new_type) {
271
+ tokens.last[0] = new_type
272
+ }
273
+ tok = ->(type, data=nil, options={}) {
274
+ sl, sc, el, ec = *loc.(options[:ts] || ts),
275
+ *loc.(options[:te] || te - 1)
276
+ tokens << [type, { file: name, line: sl, start: sc, end: ec }, *data]
277
+ }
278
+
279
+ %% write init;
280
+ %% write exec;
281
+
282
+ if runaway
283
+ line_start_index = find_line_start.(str_start)
284
+ line_start = line_starts[line_start_index]
285
+
286
+ error = SyntaxError.new("literal not terminated",
287
+ file: name,
288
+ line: line_start_index,
289
+ start: str_start - line_start,
290
+ end: str_start - line_start)
291
+ raise error
292
+ end
293
+
294
+ tokens
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,288 @@
1
+ class Liquor::Parser
2
+ token comma dot endtag ident integer keyword lblock lblock2 lbracket
3
+ linterp lparen op_div op_eq op_gt op_geq op_lt op_leq op_minus
4
+ op_mod op_mul op_neq op_not op_plus pipe plaintext rblock
5
+ rbracket rinterp rparen string tag_ident
6
+
7
+ prechigh
8
+ left dot
9
+ nonassoc op_uminus op_not
10
+ left op_mul op_div op_mod
11
+ left op_plus op_minus
12
+ left op_eq op_neq op_lt op_leq op_gt op_geq
13
+ left op_and
14
+ left op_or
15
+ preclow
16
+
17
+ expect 15
18
+
19
+ start block
20
+
21
+ rule
22
+ block: /* empty */
23
+ { result = [] }
24
+ | plaintext block
25
+ { result = [ val[0], *val[1] ] }
26
+ | interp block
27
+ { result = [ val[0], *val[1] ] }
28
+ | tag block
29
+ { result = [ val[0], *val[1] ] }
30
+
31
+ interp:
32
+ linterp expr rinterp
33
+ { result = [ :interp, retag(val), val[1] ] }
34
+ | linterp filter_chain rinterp
35
+ { result = [ :interp, retag(val), val[1] ] }
36
+
37
+ primary_expr:
38
+ ident
39
+ | lparen expr rparen
40
+ { result = [ val[1][0], retag(val), *val[1][2..-1] ] }
41
+
42
+ expr:
43
+ integer
44
+ | string
45
+ | tuple
46
+ | ident function_args
47
+ { result = [ :call, retag(val), val[0], val[1] ] }
48
+ | expr lbracket expr rbracket
49
+ { result = [ :index, retag(val), val[0], val[2] ] }
50
+ | expr dot ident function_args
51
+ { result = [ :external, retag(val), val[0], val[2], val[3] ] }
52
+ | expr dot ident
53
+ { result = [ :external, retag(val), val[0], val[2], nil ] }
54
+ | op_minus expr =op_uminus
55
+ { result = [ :uminus, retag(val), val[1] ] }
56
+ | op_not expr
57
+ { result = [ :not, retag(val), val[1] ] }
58
+ | expr op_mul expr
59
+ { result = [ :mul, retag(val), val[0], val[2] ] }
60
+ | expr op_div expr
61
+ { result = [ :div, retag(val), val[0], val[2] ] }
62
+ | expr op_mod expr
63
+ { result = [ :mod, retag(val), val[0], val[2] ] }
64
+ | expr op_plus expr
65
+ { result = [ :plus, retag(val), val[0], val[2] ] }
66
+ | expr op_minus expr
67
+ { result = [ :minus, retag(val), val[0], val[2] ] }
68
+ | expr op_eq expr
69
+ { result = [ :eq, retag(val), val[0], val[2] ] }
70
+ | expr op_neq expr
71
+ { result = [ :neq, retag(val), val[0], val[2] ] }
72
+ | expr op_lt expr
73
+ { result = [ :lt, retag(val), val[0], val[2] ] }
74
+ | expr op_leq expr
75
+ { result = [ :leq, retag(val), val[0], val[2] ] }
76
+ | expr op_gt expr
77
+ { result = [ :gt, retag(val), val[0], val[2] ] }
78
+ | expr op_geq expr
79
+ { result = [ :geq, retag(val), val[0], val[2] ] }
80
+ | expr op_and expr
81
+ { result = [ :and, retag(val), val[0], val[2] ] }
82
+ | expr op_or expr
83
+ { result = [ :or, retag(val), val[0], val[2] ] }
84
+ | primary_expr
85
+
86
+ tuple:
87
+ lbracket tuple_content rbracket
88
+ { result = [ :tuple, retag(val), val[1].compact ] }
89
+
90
+ tuple_content:
91
+ expr comma tuple_content
92
+ { result = [ val[0], *val[2] ] }
93
+ | expr
94
+ { result = [ val[0] ] }
95
+ | /* empty */
96
+ { result = [ ] }
97
+
98
+ function_args:
99
+ lparen function_args_inside rparen
100
+ { result = [ :args, retag(val), *val[1] ] }
101
+
102
+ function_args_inside:
103
+ expr function_keywords
104
+ { result = [ val[0], val[1][2] ] }
105
+ | function_keywords
106
+ { result = [ nil, val[0][2] ] }
107
+
108
+ function_keywords:
109
+ keyword expr function_keywords
110
+ { name = val[0][2].to_sym
111
+ tail = val[2][2]
112
+ loc = retag([ val[0], val[1] ])
113
+
114
+ if tail.include? name
115
+ @errors << SyntaxError.new("duplicate keyword argument `#{val[0][2]}'",
116
+ tail[name][1])
117
+ end
118
+
119
+ hash = {
120
+ name => [ val[1][0], loc, *val[1][2..-1] ]
121
+ }.merge(tail)
122
+
123
+ result = [ :keywords, retag([ loc, val[2] ]), hash ]
124
+ }
125
+ | /* empty */
126
+ { result = [ :keywords, nil, {} ] }
127
+
128
+ filter_chain:
129
+ expr pipe filter_chain_cont
130
+ { result = [ val[0], *val[2] ].
131
+ reduce { |tree, node| node[3][2] = tree; node }
132
+ }
133
+
134
+ filter_chain_cont:
135
+ filter_call pipe filter_chain_cont
136
+ { result = [ val[0], *val[2] ] }
137
+ | filter_call
138
+ { result = [ val[0] ] }
139
+
140
+ filter_call:
141
+ ident function_keywords
142
+ { ident_loc = val[0][1]
143
+ empty_args_loc = { line: ident_loc[:line],
144
+ start: ident_loc[:end] + 1,
145
+ end: ident_loc[:end] + 1, }
146
+ result = [ :call, val[0][1], val[0],
147
+ [ :args, val[1][1] || empty_args_loc, nil, val[1][2] ] ]
148
+ }
149
+
150
+ tag:
151
+ lblock ident expr tag_first_cont
152
+ { result = [ :tag, retag(val), val[1], val[2], *reduce_tag_args(val[3][2]) ] }
153
+ | lblock ident tag_first_cont
154
+ { result = [ :tag, retag(val), val[1], nil, *reduce_tag_args(val[2][2]) ] }
155
+
156
+ # Racc cannot do lookahead across rules. I had to add states
157
+ # explicitly to avoid S/R conflicts. You are not expected to
158
+ # understand this.
159
+
160
+ tag_first_cont:
161
+ rblock
162
+ { result = [ :cont, retag(val), [] ] }
163
+ | keyword tag_first_cont2
164
+ { result = [ :cont, retag(val), [ val[0], *val[1][2] ] ] }
165
+
166
+ tag_first_cont2:
167
+ rblock block lblock2 tag_next_cont
168
+ { result = [ :cont2, val[0][1], [ [:block, val[0][1], val[1] ], *val[3] ] ] }
169
+ | expr tag_first_cont
170
+ { result = [ :cont2, retag(val), [ val[0], *val[1][2] ] ] }
171
+
172
+ tag_next_cont:
173
+ endtag rblock
174
+ { result = [] }
175
+ | keyword tag_next_cont2
176
+ { result = [ val[0], *val[1] ] }
177
+
178
+ tag_next_cont2:
179
+ rblock block lblock2 tag_next_cont
180
+ { result = [ [:block, val[0][1], val[1] ], *val[3] ] }
181
+ | expr keyword tag_next_cont3
182
+ { result = [ val[0], val[1], *val[2] ] }
183
+
184
+ tag_next_cont3:
185
+ rblock block lblock2 tag_next_cont
186
+ { result = [ [:block, val[0][1], val[1] ], *val[3] ] }
187
+ | expr tag_next_cont
188
+ { result = [ val[0], *val[1] ] }
189
+
190
+ ---- inner
191
+ attr_reader :errors, :ast
192
+
193
+ def initialize(tags={})
194
+ super()
195
+
196
+ @errors = []
197
+ @ast = nil
198
+ @tags = tags
199
+ end
200
+
201
+ def success?
202
+ @errors.empty?
203
+ end
204
+
205
+ def parse(string, name='(code)')
206
+ @errors.clear
207
+ @name = name
208
+ @ast = nil
209
+
210
+ begin
211
+ @stream = Lexer.lex(string, @name, @tags)
212
+ @ast = do_parse
213
+ rescue Liquor::SyntaxError => e
214
+ @errors << e
215
+ end
216
+
217
+ success?
218
+ end
219
+
220
+ def next_token
221
+ tok = @stream.shift
222
+ [ tok[0], tok ] if tok
223
+ end
224
+
225
+ TOKEN_NAME_MAP = {
226
+ :comma => ',',
227
+ :dot => '.',
228
+ :lblock => '{%',
229
+ :rblock => '%}',
230
+ :linterp => '{{',
231
+ :rinterp => '}}',
232
+ :lbracket => '[',
233
+ :rbracket => ']',
234
+ :lparen => '(',
235
+ :rparen => ')',
236
+ :pipe => '|',
237
+ :op_not => '!',
238
+ :op_mul => '*',
239
+ :op_div => '/',
240
+ :op_mod => '%',
241
+ :op_plus => '+',
242
+ :op_minus => '-',
243
+ :op_eq => '==',
244
+ :op_neq => '!=',
245
+ :op_lt => '<',
246
+ :op_leq => '<=',
247
+ :op_gt => '>',
248
+ :op_geq => '>=',
249
+ :keyword => 'keyword argument name',
250
+ :kwarg => 'keyword argument',
251
+ :ident => 'identifier',
252
+ }
253
+
254
+ def on_error(error_token_id, error_token, value_stack)
255
+ if token_to_str(error_token_id) == "$end"
256
+ raise Liquor::SyntaxError.new("unexpected end of program", {
257
+ file: @name
258
+ })
259
+ else
260
+ type, (loc, value) = error_token
261
+ type = TOKEN_NAME_MAP[type] || type
262
+
263
+ raise Liquor::SyntaxError.new("unexpected token `#{type}'", loc)
264
+ end
265
+ end
266
+
267
+ def retag(nodes)
268
+ loc = nodes.map { |node| node[1] }.compact
269
+ first, *, last = loc
270
+ return first if last.nil?
271
+
272
+ {
273
+ file: first[:file],
274
+ line: first[:line],
275
+ start: first[:start],
276
+ end: last[:end],
277
+ }
278
+ end
279
+
280
+ def reduce_tag_args(list)
281
+ list.each_slice(2).reduce([]) { |args, (k, v)|
282
+ if v[0] == :block
283
+ args << [ :blockarg, retag([ k, v ]), k, v[2] || [] ]
284
+ else
285
+ args << [ :kwarg, retag([ k, v ]), k, v ]
286
+ end
287
+ }
288
+ end