liquor 0.1.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -9
- data/Gemfile +7 -0
- data/Guardfile +11 -0
- data/MIT-LICENSE +6 -2
- data/README.md +4 -122
- data/Rakefile +20 -23
- data/doc/language-spec.html +768 -0
- data/doc/language-spec.md +698 -0
- data/lib/liquor.rb +39 -68
- data/lib/liquor/ast_tools.rb +28 -0
- data/lib/liquor/compiler.rb +110 -0
- data/lib/liquor/context.rb +76 -254
- data/lib/liquor/diagnostics.rb +151 -0
- data/lib/liquor/drop/drop.rb +168 -0
- data/lib/liquor/drop/drop_delegation.rb +24 -0
- data/lib/liquor/drop/drop_scope.rb +118 -0
- data/lib/liquor/drop/dropable.rb +17 -0
- data/lib/liquor/emitter.rb +313 -0
- data/lib/liquor/extensions/kaminari.rb +14 -0
- data/lib/liquor/extensions/pagination.rb +235 -0
- data/lib/liquor/extensions/rails.rb +97 -0
- data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
- data/lib/liquor/extensions/tire.rb +30 -0
- data/lib/liquor/external.rb +79 -0
- data/lib/liquor/function.rb +94 -0
- data/lib/liquor/grammar/lexer.rb +1223 -0
- data/lib/liquor/grammar/lexer.rl +297 -0
- data/lib/liquor/grammar/parser.racc +288 -0
- data/lib/liquor/grammar/parser.rb +885 -0
- data/lib/liquor/library.rb +41 -0
- data/lib/liquor/manager.rb +146 -0
- data/lib/liquor/runtime.rb +167 -0
- data/lib/liquor/stdlib/builtin_functions.rb +315 -0
- data/lib/liquor/stdlib/builtin_tags.rb +228 -0
- data/lib/liquor/stdlib/html_truncater.rb +162 -0
- data/lib/liquor/stdlib/partial_tags.rb +76 -0
- data/lib/liquor/tag.rb +83 -14
- data/lib/liquor/version.rb +1 -1
- data/liquor.gemspec +29 -6
- data/spec/builtins_spec.rb +264 -0
- data/spec/compiler_spec.rb +136 -0
- data/spec/context_spec.rb +49 -0
- data/spec/drop_delegation_spec.rb +21 -0
- data/spec/drop_spec.rb +222 -0
- data/spec/errors_spec.rb +40 -0
- data/spec/external_spec.rb +207 -0
- data/spec/function_spec.rb +80 -0
- data/spec/lexer_spec.rb +173 -0
- data/spec/library_spec.rb +18 -0
- data/spec/manager_spec.rb +84 -0
- data/spec/parser_spec.rb +381 -0
- data/spec/partials_spec.rb +74 -0
- data/spec/runtime_spec.rb +97 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/tag_spec.rb +7 -0
- metadata +216 -173
- data/AUTHORS +0 -2
- data/CHANGELOG +0 -48
- data/Gemfile.lock +0 -91
- data/History.txt +0 -44
- data/LICENSE +0 -23
- data/example/server/example_servlet.rb +0 -37
- data/example/server/liquid_servlet.rb +0 -28
- data/example/server/liquor_servlet.rb +0 -28
- data/example/server/server.rb +0 -12
- data/example/server/templates/index.liquid +0 -6
- data/example/server/templates/index.liquor +0 -6
- data/example/server/templates/products.liquid +0 -45
- data/example/server/templates/products.liquor +0 -45
- data/init.rb +0 -8
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/extras/liquor_view.rb +0 -51
- data/lib/liquor/block.rb +0 -101
- data/lib/liquor/condition.rb +0 -120
- data/lib/liquor/document.rb +0 -17
- data/lib/liquor/drop.rb +0 -256
- data/lib/liquor/errors.rb +0 -11
- data/lib/liquor/extensions.rb +0 -72
- data/lib/liquor/file_system.rb +0 -62
- data/lib/liquor/htmltags.rb +0 -74
- data/lib/liquor/module_ex.rb +0 -60
- data/lib/liquor/standardfilters.rb +0 -315
- data/lib/liquor/strainer.rb +0 -58
- data/lib/liquor/tags/assign.rb +0 -33
- data/lib/liquor/tags/capture.rb +0 -35
- data/lib/liquor/tags/case.rb +0 -83
- data/lib/liquor/tags/comment.rb +0 -9
- data/lib/liquor/tags/content_for.rb +0 -54
- data/lib/liquor/tags/cycle.rb +0 -59
- data/lib/liquor/tags/for.rb +0 -136
- data/lib/liquor/tags/if.rb +0 -80
- data/lib/liquor/tags/ifchanged.rb +0 -20
- data/lib/liquor/tags/include.rb +0 -56
- data/lib/liquor/tags/unless.rb +0 -33
- data/lib/liquor/tags/yield.rb +0 -49
- data/lib/liquor/template.rb +0 -181
- data/lib/liquor/variable.rb +0 -52
- data/performance/shopify.rb +0 -92
- data/performance/shopify/comment_form.rb +0 -33
- data/performance/shopify/database.rb +0 -45
- data/performance/shopify/json_filter.rb +0 -7
- data/performance/shopify/liquid.rb +0 -18
- data/performance/shopify/liquor.rb +0 -18
- data/performance/shopify/money_filter.rb +0 -18
- data/performance/shopify/paginate.rb +0 -93
- data/performance/shopify/shop_filter.rb +0 -98
- data/performance/shopify/tag_filter.rb +0 -25
- data/performance/shopify/vision.database.yml +0 -945
- data/performance/shopify/weight_filter.rb +0 -11
- data/performance/tests/dropify/article.liquid +0 -74
- data/performance/tests/dropify/blog.liquid +0 -33
- data/performance/tests/dropify/cart.liquid +0 -66
- data/performance/tests/dropify/collection.liquid +0 -22
- data/performance/tests/dropify/index.liquid +0 -47
- data/performance/tests/dropify/page.liquid +0 -8
- data/performance/tests/dropify/product.liquid +0 -68
- data/performance/tests/dropify/theme.liquid +0 -105
- data/performance/tests/ripen/article.liquid +0 -74
- data/performance/tests/ripen/blog.liquid +0 -13
- data/performance/tests/ripen/cart.liquid +0 -54
- data/performance/tests/ripen/collection.liquid +0 -29
- data/performance/tests/ripen/index.liquid +0 -32
- data/performance/tests/ripen/page.liquid +0 -4
- data/performance/tests/ripen/product.liquid +0 -75
- data/performance/tests/ripen/theme.liquid +0 -85
- data/performance/tests/tribble/404.liquid +0 -56
- data/performance/tests/tribble/article.liquid +0 -98
- data/performance/tests/tribble/blog.liquid +0 -41
- data/performance/tests/tribble/cart.liquid +0 -134
- data/performance/tests/tribble/collection.liquid +0 -70
- data/performance/tests/tribble/index.liquid +0 -94
- data/performance/tests/tribble/page.liquid +0 -56
- data/performance/tests/tribble/product.liquid +0 -116
- data/performance/tests/tribble/search.liquid +0 -51
- data/performance/tests/tribble/theme.liquid +0 -90
- data/performance/tests/vogue/article.liquid +0 -66
- data/performance/tests/vogue/blog.liquid +0 -32
- data/performance/tests/vogue/cart.liquid +0 -58
- data/performance/tests/vogue/collection.liquid +0 -19
- data/performance/tests/vogue/index.liquid +0 -22
- data/performance/tests/vogue/page.liquid +0 -3
- data/performance/tests/vogue/product.liquid +0 -62
- data/performance/tests/vogue/theme.liquid +0 -122
- data/test/assign_test.rb +0 -11
- data/test/block_test.rb +0 -58
- data/test/capture_test.rb +0 -41
- data/test/condition_test.rb +0 -115
- data/test/content_for_test.rb +0 -15
- data/test/context_test.rb +0 -479
- data/test/drop_test.rb +0 -162
- data/test/error_handling_test.rb +0 -89
- data/test/extra/breakpoint.rb +0 -547
- data/test/extra/caller.rb +0 -80
- data/test/file_system_test.rb +0 -30
- data/test/filter_test.rb +0 -147
- data/test/helper.rb +0 -24
- data/test/html_tag_test.rb +0 -31
- data/test/if_else_test.rb +0 -139
- data/test/include_tag_test.rb +0 -129
- data/test/module_ex_test.rb +0 -89
- data/test/output_test.rb +0 -121
- data/test/parsing_quirks_test.rb +0 -54
- data/test/regexp_test.rb +0 -45
- data/test/security_test.rb +0 -41
- data/test/standard_filter_test.rb +0 -170
- data/test/standard_tag_test.rb +0 -405
- data/test/statements_test.rb +0 -137
- data/test/strainer_test.rb +0 -27
- data/test/template_test.rb +0 -82
- data/test/test_helper.rb +0 -28
- data/test/unless_else_test.rb +0 -27
- data/test/variable_test.rb +0 -173
- 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
|