erblite 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +21 -0
- data/README.md +19 -0
- data/bin/erblite +53 -0
- data/lib/erblite/builtins.rb +1332 -0
- data/lib/erblite/error.rb +52 -0
- data/lib/erblite/format.rb +212 -0
- data/lib/erblite/interpreter.rb +486 -0
- data/lib/erblite/json.rb +331 -0
- data/lib/erblite/lexer.rb +649 -0
- data/lib/erblite/nodes.rb +46 -0
- data/lib/erblite/parser.rb +953 -0
- data/lib/erblite/sandbox.rb +192 -0
- data/lib/erblite/values.rb +139 -0
- data/lib/erblite/version.rb +13 -0
- data/lib/erblite.rb +134 -0
- metadata +57 -0
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
# Copyright 2026 OpenC3, Inc.
|
|
2
|
+
# All Rights Reserved.
|
|
3
|
+
#
|
|
4
|
+
# This program is distributed in the hope that it will be useful,
|
|
5
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
6
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
7
|
+
# See LICENSE.md for more details.
|
|
8
|
+
|
|
9
|
+
# frozen_string_literal: true
|
|
10
|
+
|
|
11
|
+
module ERBLite
|
|
12
|
+
# Recursive-descent parser producing the AST in Nodes. It resolves local
|
|
13
|
+
# variable scopes while parsing, the same way Ruby does: a name is a local
|
|
14
|
+
# variable once an assignment to it has been seen.
|
|
15
|
+
class Parser
|
|
16
|
+
include Nodes
|
|
17
|
+
|
|
18
|
+
ASSIGN_OPS = %w[= += -= *= /= %= **= ||= &&= <<= >>= |= &= ^=].to_h { |o| [o, true] }.freeze
|
|
19
|
+
MODIFIERS = %w[if unless while until].freeze
|
|
20
|
+
EQUALITY_OPS = %w[== != <=> ===].freeze
|
|
21
|
+
OPERATOR_METHODS = %w[+ - * / % ** == != < > <= >= <=> === << >> & | ^ ! ~ []].to_h { |o| [o, true] }.freeze
|
|
22
|
+
MAX_DEPTH = 256
|
|
23
|
+
|
|
24
|
+
def self.parse(tokens, filename, host_names)
|
|
25
|
+
new(tokens, filename, host_names).parse_program
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(tokens, filename, host_names)
|
|
29
|
+
@toks = tokens
|
|
30
|
+
@pos = 0
|
|
31
|
+
@filename = filename
|
|
32
|
+
@scopes = [host_names.to_h { |n| [n, true] }]
|
|
33
|
+
@ctx = []
|
|
34
|
+
@no_do = false
|
|
35
|
+
@no_command = false
|
|
36
|
+
@depth = 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_program
|
|
40
|
+
body = parse_statements
|
|
41
|
+
t = peek
|
|
42
|
+
unexpected(t) unless t.type == :eof
|
|
43
|
+
body
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
# -- token helpers ---------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def peek(n = 0)
|
|
51
|
+
@toks[@pos + n] || @toks.last
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def advance
|
|
55
|
+
t = @toks[@pos]
|
|
56
|
+
@pos += 1 if @pos < @toks.length - 1
|
|
57
|
+
t
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def op?(value, t = peek)
|
|
61
|
+
t.type == :op && t.value == value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def kw?(value, t = peek)
|
|
65
|
+
t.type == :kw && t.value == value
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def accept_op(value)
|
|
69
|
+
return nil unless op?(value)
|
|
70
|
+
|
|
71
|
+
advance
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def expect_op(value)
|
|
75
|
+
t = peek
|
|
76
|
+
unexpected(t, "expected '#{value}'") unless op?(value, t)
|
|
77
|
+
advance
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def expect_kw(value)
|
|
81
|
+
t = peek
|
|
82
|
+
unexpected(t, "expected '#{value}'") unless kw?(value, t)
|
|
83
|
+
advance
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def term?(t = peek)
|
|
87
|
+
t.type == :nl || t.type == :semi || t.type == :tagend
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def skip_terms
|
|
91
|
+
advance while term?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def skip_nl
|
|
95
|
+
advance while peek.type == :nl
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def error(msg, line)
|
|
99
|
+
raise Error.new("SyntaxError", msg, @filename, line)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def describe(t)
|
|
103
|
+
case t.type
|
|
104
|
+
when :eof then "end of template"
|
|
105
|
+
when :tagend then "end of tag (%>)"
|
|
106
|
+
when :oend then "end of output tag (%>)"
|
|
107
|
+
when :nl then "newline"
|
|
108
|
+
when :text then "template text"
|
|
109
|
+
when :ostart then "output tag (<%=)"
|
|
110
|
+
when :str then "string literal"
|
|
111
|
+
when :label then "label '#{t.value}:'"
|
|
112
|
+
else "'#{t.value}'"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def unexpected(t, extra = nil)
|
|
117
|
+
if t.type == :kw && CodeLexer::RESERVED.include?(t.value)
|
|
118
|
+
error("'#{t.value}' is not supported in ERBLite", t.line)
|
|
119
|
+
end
|
|
120
|
+
error("syntax error, unexpected #{describe(t)}#{extra ? " (#{extra})" : ""}", t.line)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def nest
|
|
124
|
+
@depth += 1
|
|
125
|
+
if @depth > MAX_DEPTH
|
|
126
|
+
raise Error.new("LimitError", "template nesting is too deep", @filename, peek.line)
|
|
127
|
+
end
|
|
128
|
+
yield
|
|
129
|
+
ensure
|
|
130
|
+
@depth -= 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def with_flags(no_do: false, no_command: false)
|
|
134
|
+
saved = [@no_do, @no_command]
|
|
135
|
+
@no_do = no_do
|
|
136
|
+
@no_command = no_command
|
|
137
|
+
yield
|
|
138
|
+
ensure
|
|
139
|
+
@no_do, @no_command = saved
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# -- scopes ----------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def lookup(name)
|
|
145
|
+
i = @scopes.length - 1
|
|
146
|
+
while i >= 0
|
|
147
|
+
return @scopes.length - 1 - i if @scopes[i][name]
|
|
148
|
+
|
|
149
|
+
i -= 1
|
|
150
|
+
end
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def declare(name)
|
|
155
|
+
d = lookup(name)
|
|
156
|
+
return d if d
|
|
157
|
+
|
|
158
|
+
@scopes.last[name] = true
|
|
159
|
+
0
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# -- statements ------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def stmts_end?(t)
|
|
165
|
+
case t.type
|
|
166
|
+
when :eof, :oend then true
|
|
167
|
+
when :kw then %w[end else elsif when in then do].include?(t.value)
|
|
168
|
+
when :op then t.value == "}" || t.value == ")" || t.value == "]"
|
|
169
|
+
else false
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def parse_statements
|
|
174
|
+
line = peek.line
|
|
175
|
+
stmts = []
|
|
176
|
+
loop do
|
|
177
|
+
skip_terms
|
|
178
|
+
t = peek
|
|
179
|
+
break if stmts_end?(t)
|
|
180
|
+
|
|
181
|
+
s = parse_statement
|
|
182
|
+
stmts << s
|
|
183
|
+
next if s.is_a?(Text) || s.is_a?(Output)
|
|
184
|
+
|
|
185
|
+
t = peek
|
|
186
|
+
next if term?(t) || stmts_end?(t)
|
|
187
|
+
|
|
188
|
+
unexpected(t)
|
|
189
|
+
end
|
|
190
|
+
Seq.new(stmts, line)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def parse_statement
|
|
194
|
+
t = peek
|
|
195
|
+
case t.type
|
|
196
|
+
when :text
|
|
197
|
+
advance
|
|
198
|
+
Text.new(t.value, t.line)
|
|
199
|
+
when :ostart
|
|
200
|
+
advance
|
|
201
|
+
# Like ERB's `_erbout << ((code).to_s)`, the tag may hold several
|
|
202
|
+
# statements; the value of the last one is output.
|
|
203
|
+
e = with_flags { parse_statements }
|
|
204
|
+
unexpected(peek) unless peek.type == :oend
|
|
205
|
+
advance
|
|
206
|
+
Output.new(e, t.line)
|
|
207
|
+
else
|
|
208
|
+
parse_stmt_mod
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def parse_stmt_mod
|
|
213
|
+
s = parse_simple
|
|
214
|
+
while (t = peek).type == :kw && MODIFIERS.include?(t.value)
|
|
215
|
+
advance
|
|
216
|
+
c = parse_expr
|
|
217
|
+
s = case t.value
|
|
218
|
+
when "if" then If.new(c, s, nil, t.line)
|
|
219
|
+
when "unless" then If.new(c, nil, s, t.line)
|
|
220
|
+
when "while" then While.new(c, s, false, t.line)
|
|
221
|
+
else While.new(c, s, true, t.line)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
s
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def expr_start?(t)
|
|
228
|
+
case t.type
|
|
229
|
+
when :int, :float, :str, :sym, :dsym, :words, :symwords, :ident, :const, :label then true
|
|
230
|
+
when :kw then %w[nil true false not if unless while until case for begin self].include?(t.value)
|
|
231
|
+
when :op then ["(", "[", "{", "-", "!", "~", "+", "::", "..", "..."].include?(t.value)
|
|
232
|
+
else false
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def parse_simple
|
|
237
|
+
t = peek
|
|
238
|
+
if kw?("break") || kw?("next")
|
|
239
|
+
advance
|
|
240
|
+
kind = @ctx.last
|
|
241
|
+
error("Invalid #{t.value}", t.line) unless kind
|
|
242
|
+
nt = peek
|
|
243
|
+
value = expr_start?(nt) && !(nt.type == :kw && MODIFIERS.include?(nt.value)) ? parse_expr : nil
|
|
244
|
+
return t.value == "break" ? Break.new(value, kind, t.line) : Next.new(value, kind, t.line)
|
|
245
|
+
end
|
|
246
|
+
return parse_masgn if t.type == :ident && op?(",", peek(1)) && masgn_ahead?
|
|
247
|
+
|
|
248
|
+
e = parse_expr
|
|
249
|
+
if e.is_a?(LAsgn) && op?(",")
|
|
250
|
+
values = [e.value]
|
|
251
|
+
while accept_op(",")
|
|
252
|
+
skip_nl
|
|
253
|
+
values << parse_ternary
|
|
254
|
+
end
|
|
255
|
+
e = LAsgn.new(e.name, e.depth, Arr.new(values, e.line), e.line)
|
|
256
|
+
end
|
|
257
|
+
e
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def masgn_ahead?
|
|
261
|
+
i = @pos
|
|
262
|
+
loop do
|
|
263
|
+
return false unless @toks[i]&.type == :ident
|
|
264
|
+
|
|
265
|
+
i += 1
|
|
266
|
+
t = @toks[i]
|
|
267
|
+
return true if t&.type == :op && t.value == "="
|
|
268
|
+
return false unless t&.type == :op && t.value == ","
|
|
269
|
+
|
|
270
|
+
i += 1
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def parse_masgn
|
|
275
|
+
line = peek.line
|
|
276
|
+
names = [advance.value]
|
|
277
|
+
while accept_op(",")
|
|
278
|
+
names << advance.value
|
|
279
|
+
end
|
|
280
|
+
expect_op("=")
|
|
281
|
+
skip_nl
|
|
282
|
+
targets = names.map { |n| [n, declare(n)] }
|
|
283
|
+
values = [parse_ternary]
|
|
284
|
+
while accept_op(",")
|
|
285
|
+
skip_nl
|
|
286
|
+
values << parse_ternary
|
|
287
|
+
end
|
|
288
|
+
MAsgn.new(targets, values, line)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# -- expressions -----------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
def parse_expr
|
|
294
|
+
left = parse_not_expr
|
|
295
|
+
while (t = peek).type == :kw && (t.value == "and" || t.value == "or")
|
|
296
|
+
advance
|
|
297
|
+
skip_nl
|
|
298
|
+
right = parse_not_expr
|
|
299
|
+
left = t.value == "and" ? And.new(left, right, t.line) : Or.new(left, right, t.line)
|
|
300
|
+
end
|
|
301
|
+
left
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def parse_not_expr
|
|
305
|
+
if kw?("not")
|
|
306
|
+
t = advance
|
|
307
|
+
return Not.new(nest { parse_not_expr }, t.line)
|
|
308
|
+
end
|
|
309
|
+
parse_ternary
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def parse_ternary
|
|
313
|
+
c = parse_range
|
|
314
|
+
return c unless op?("?")
|
|
315
|
+
|
|
316
|
+
t = advance
|
|
317
|
+
skip_nl
|
|
318
|
+
a = nest { parse_ternary }
|
|
319
|
+
skip_nl
|
|
320
|
+
expect_op(":")
|
|
321
|
+
skip_nl
|
|
322
|
+
b = nest { parse_ternary }
|
|
323
|
+
If.new(c, a, b, t.line)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def parse_range
|
|
327
|
+
if op?("..") || op?("...")
|
|
328
|
+
t = advance
|
|
329
|
+
return RangeLit.new(Lit.new(nil, t.line), parse_oror, t.value == "...", t.line)
|
|
330
|
+
end
|
|
331
|
+
l = parse_oror
|
|
332
|
+
if op?("..") || op?("...")
|
|
333
|
+
t = advance
|
|
334
|
+
r = expr_start?(peek) && peek.type != :label ? parse_oror : nil
|
|
335
|
+
return RangeLit.new(l, r, t.value == "...", t.line)
|
|
336
|
+
end
|
|
337
|
+
l
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def parse_oror
|
|
341
|
+
l = parse_andand
|
|
342
|
+
while op?("||")
|
|
343
|
+
t = advance
|
|
344
|
+
l = Or.new(l, parse_andand, t.line)
|
|
345
|
+
end
|
|
346
|
+
l
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def parse_andand
|
|
350
|
+
l = parse_equality
|
|
351
|
+
while op?("&&")
|
|
352
|
+
t = advance
|
|
353
|
+
l = And.new(l, parse_equality, t.line)
|
|
354
|
+
end
|
|
355
|
+
l
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def parse_equality
|
|
359
|
+
l = parse_compare
|
|
360
|
+
t = peek
|
|
361
|
+
if t.type == :op && EQUALITY_OPS.include?(t.value)
|
|
362
|
+
advance
|
|
363
|
+
r = parse_compare
|
|
364
|
+
l = if t.value == "!="
|
|
365
|
+
Not.new(Call.new(l, "==", [r], nil, false, t.line), t.line)
|
|
366
|
+
else
|
|
367
|
+
Call.new(l, t.value, [r], nil, false, t.line)
|
|
368
|
+
end
|
|
369
|
+
t2 = peek
|
|
370
|
+
unexpected(t2) if t2.type == :op && EQUALITY_OPS.include?(t2.value)
|
|
371
|
+
end
|
|
372
|
+
l
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def binary_loop(ops)
|
|
376
|
+
l = yield
|
|
377
|
+
while (t = peek).type == :op && ops.include?(t.value)
|
|
378
|
+
advance
|
|
379
|
+
l = Call.new(l, t.value, [yield], nil, false, t.line)
|
|
380
|
+
end
|
|
381
|
+
l
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def parse_compare = binary_loop(%w[< <= > >=]) { parse_bor }
|
|
385
|
+
def parse_bor = binary_loop(%w[| ^]) { parse_band }
|
|
386
|
+
def parse_band = binary_loop(%w[&]) { parse_shift }
|
|
387
|
+
def parse_shift = binary_loop(%w[<< >>]) { parse_add }
|
|
388
|
+
def parse_add = binary_loop(%w[+ -]) { parse_mul }
|
|
389
|
+
def parse_mul = binary_loop(%w[* / %]) { parse_uminus }
|
|
390
|
+
|
|
391
|
+
def parse_uminus
|
|
392
|
+
if op?("-")
|
|
393
|
+
t = advance
|
|
394
|
+
return Call.new(nest { parse_uminus }, "-@", [], nil, false, t.line)
|
|
395
|
+
end
|
|
396
|
+
parse_pow
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def parse_pow
|
|
400
|
+
t = peek
|
|
401
|
+
if (t.type == :int || t.type == :float) && t.neg && op?("**", peek(1))
|
|
402
|
+
advance
|
|
403
|
+
advance
|
|
404
|
+
exp = nest { parse_uminus }
|
|
405
|
+
base = Lit.new(-t.value, t.line)
|
|
406
|
+
return Call.new(Call.new(base, "**", [exp], nil, false, t.line), "-@", [], nil, false, t.line)
|
|
407
|
+
end
|
|
408
|
+
base = parse_unary
|
|
409
|
+
if op?("**")
|
|
410
|
+
op = advance
|
|
411
|
+
exp = nest { parse_uminus }
|
|
412
|
+
return Call.new(base, "**", [exp], nil, false, op.line)
|
|
413
|
+
end
|
|
414
|
+
base
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def parse_unary
|
|
418
|
+
t = peek
|
|
419
|
+
if t.type == :op
|
|
420
|
+
case t.value
|
|
421
|
+
when "!"
|
|
422
|
+
advance
|
|
423
|
+
return Not.new(nest { parse_unary }, t.line)
|
|
424
|
+
when "~"
|
|
425
|
+
advance
|
|
426
|
+
return Call.new(nest { parse_unary }, "~", [], nil, false, t.line)
|
|
427
|
+
when "+"
|
|
428
|
+
advance
|
|
429
|
+
return Call.new(nest { parse_unary }, "+@", [], nil, false, t.line)
|
|
430
|
+
when "-"
|
|
431
|
+
return nest { parse_uminus }
|
|
432
|
+
when "*", "**", "&"
|
|
433
|
+
error("splat and block-pass operators are not supported here", t.line)
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
parse_postfix
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def command_start?(t)
|
|
440
|
+
return false unless t.spc
|
|
441
|
+
|
|
442
|
+
case t.type
|
|
443
|
+
when :int, :float, :str, :sym, :dsym, :label, :const, :ident then true
|
|
444
|
+
when :kw then t.value == "nil" || t.value == "true" || t.value == "false"
|
|
445
|
+
when :op
|
|
446
|
+
case t.value
|
|
447
|
+
when "(", "[", "!" then true
|
|
448
|
+
when "&" then peek(1).type == :sym && !peek(1).spc
|
|
449
|
+
when "-"
|
|
450
|
+
nt = peek(1)
|
|
451
|
+
(nt.type == :int || nt.type == :float) && !nt.spc
|
|
452
|
+
else false
|
|
453
|
+
end
|
|
454
|
+
else false
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def parse_postfix
|
|
459
|
+
e = parse_primary
|
|
460
|
+
loop do
|
|
461
|
+
t = peek
|
|
462
|
+
break unless t.type == :op
|
|
463
|
+
|
|
464
|
+
case t.value
|
|
465
|
+
when ".", "&."
|
|
466
|
+
advance
|
|
467
|
+
nt = advance
|
|
468
|
+
name = nt.value
|
|
469
|
+
if nt.type == :op && (OPERATOR_METHODS[nt.value] || nt.value == "[")
|
|
470
|
+
if nt.value == "["
|
|
471
|
+
unexpected(peek, "expected ']'") unless op?("]")
|
|
472
|
+
advance
|
|
473
|
+
name = "[]"
|
|
474
|
+
end
|
|
475
|
+
elsif nt.type != :ident && nt.type != :const
|
|
476
|
+
unexpected(nt, "expected a method name")
|
|
477
|
+
end
|
|
478
|
+
args = []
|
|
479
|
+
bp = nil
|
|
480
|
+
if op?("(") && !peek.spc
|
|
481
|
+
advance
|
|
482
|
+
args, bp = parse_arglist(")")
|
|
483
|
+
elsif !@no_command && command_start?(peek)
|
|
484
|
+
args, bp = parse_command_args
|
|
485
|
+
end
|
|
486
|
+
blk = parse_block_opt
|
|
487
|
+
error("both block arg and actual block given", nt.line) if blk && bp
|
|
488
|
+
e = Call.new(e, name, args, blk || bp, t.value == "&.", nt.line)
|
|
489
|
+
when "::"
|
|
490
|
+
advance
|
|
491
|
+
nt = advance
|
|
492
|
+
unexpected(nt, "expected a constant name") unless nt.type == :const
|
|
493
|
+
e = Colon2.new(e, nt.value, nt.line)
|
|
494
|
+
when "["
|
|
495
|
+
advance
|
|
496
|
+
args, bp = parse_arglist("]")
|
|
497
|
+
error("block argument is not allowed in []", t.line) if bp
|
|
498
|
+
e = Call.new(e, "[]", args, nil, false, t.line)
|
|
499
|
+
else
|
|
500
|
+
return parse_assignment(e, t) if ASSIGN_OPS[t.value]
|
|
501
|
+
|
|
502
|
+
break
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
e
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def parse_assignment(target, t)
|
|
509
|
+
op = t.value
|
|
510
|
+
case target
|
|
511
|
+
when VCall, LVar
|
|
512
|
+
advance
|
|
513
|
+
skip_nl
|
|
514
|
+
depth = declare(target.name)
|
|
515
|
+
rhs = nest { parse_ternary }
|
|
516
|
+
return LAsgn.new(target.name, depth, rhs, t.line) if op == "="
|
|
517
|
+
|
|
518
|
+
OpAsgn.new(LVar.new(target.name, depth, target.line), op[0..-2], rhs, t.line)
|
|
519
|
+
when Call
|
|
520
|
+
if target.name == "[]" && !target.block && !target.safe
|
|
521
|
+
advance
|
|
522
|
+
skip_nl
|
|
523
|
+
rhs = nest { parse_ternary }
|
|
524
|
+
return IndexAsgn.new(target.recv, target.args, rhs, t.line) if op == "="
|
|
525
|
+
|
|
526
|
+
OpAsgn.new(target, op[0..-2], rhs, t.line)
|
|
527
|
+
elsif target.recv && target.args.empty? && !target.block && !target.safe && target.name.match?(/\A[a-z_][A-Za-z0-9_]*\z/)
|
|
528
|
+
advance
|
|
529
|
+
skip_nl
|
|
530
|
+
rhs = nest { parse_ternary }
|
|
531
|
+
return AttrAsgn.new(target.recv, target.name, rhs, t.line) if op == "="
|
|
532
|
+
|
|
533
|
+
OpAsgn.new(target, op[0..-2], rhs, t.line)
|
|
534
|
+
else
|
|
535
|
+
error("cannot assign to this expression", t.line)
|
|
536
|
+
end
|
|
537
|
+
when Const, Colon2
|
|
538
|
+
error("dynamic constant assignment is not supported", t.line)
|
|
539
|
+
else
|
|
540
|
+
error("cannot assign to this expression", t.line)
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# -- primaries -------------------------------------------------------------
|
|
545
|
+
|
|
546
|
+
def parse_primary
|
|
547
|
+
t = peek
|
|
548
|
+
nest do
|
|
549
|
+
case t.type
|
|
550
|
+
when :int, :float
|
|
551
|
+
advance
|
|
552
|
+
Lit.new(t.value, t.line)
|
|
553
|
+
when :str
|
|
554
|
+
advance
|
|
555
|
+
Str.new(parse_parts(t.value), t.line)
|
|
556
|
+
when :sym
|
|
557
|
+
advance
|
|
558
|
+
Lit.new(t.value.to_sym, t.line)
|
|
559
|
+
when :dsym
|
|
560
|
+
advance
|
|
561
|
+
DSym.new(parse_parts(t.value), t.line)
|
|
562
|
+
when :words
|
|
563
|
+
advance
|
|
564
|
+
Arr.new(t.value.map { |w| Str.new([w], t.line) }, t.line)
|
|
565
|
+
when :symwords
|
|
566
|
+
advance
|
|
567
|
+
Arr.new(t.value.map { |w| Lit.new(w.to_sym, t.line) }, t.line)
|
|
568
|
+
when :ident
|
|
569
|
+
parse_identifier
|
|
570
|
+
when :const
|
|
571
|
+
parse_constant
|
|
572
|
+
when :kw
|
|
573
|
+
parse_keyword(t)
|
|
574
|
+
when :op
|
|
575
|
+
case t.value
|
|
576
|
+
when "(" then parse_paren
|
|
577
|
+
when "[" then parse_array
|
|
578
|
+
when "{" then parse_hash
|
|
579
|
+
else unexpected(t)
|
|
580
|
+
end
|
|
581
|
+
else
|
|
582
|
+
unexpected(t)
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
def parse_keyword(t)
|
|
588
|
+
case t.value
|
|
589
|
+
when "nil"
|
|
590
|
+
advance
|
|
591
|
+
Lit.new(nil, t.line)
|
|
592
|
+
when "true"
|
|
593
|
+
advance
|
|
594
|
+
Lit.new(true, t.line)
|
|
595
|
+
when "false"
|
|
596
|
+
advance
|
|
597
|
+
Lit.new(false, t.line)
|
|
598
|
+
when "if", "unless" then parse_if
|
|
599
|
+
when "while", "until" then parse_while
|
|
600
|
+
when "for" then parse_for
|
|
601
|
+
when "case" then parse_case
|
|
602
|
+
when "begin" then error("begin/rescue is not supported in ERBLite", t.line)
|
|
603
|
+
when "self" then error("self is not supported in ERBLite", t.line)
|
|
604
|
+
else unexpected(t)
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def parse_parts(raw)
|
|
609
|
+
raw.map do |p|
|
|
610
|
+
next p if p.is_a?(String)
|
|
611
|
+
|
|
612
|
+
sub_parse(p[1], p[2])
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def sub_parse(toks, line)
|
|
617
|
+
saved = [@toks, @pos]
|
|
618
|
+
@toks = toks + [Token.new(:eof, nil, toks.last ? toks.last.line : line)]
|
|
619
|
+
@pos = 0
|
|
620
|
+
body = with_flags { parse_statements }
|
|
621
|
+
unexpected(peek) unless peek.type == :eof
|
|
622
|
+
body
|
|
623
|
+
ensure
|
|
624
|
+
@toks, @pos = saved
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def parse_identifier
|
|
628
|
+
t = advance
|
|
629
|
+
name = t.value
|
|
630
|
+
depth = lookup(name)
|
|
631
|
+
nt = peek
|
|
632
|
+
if op?("(", nt) && !nt.spc
|
|
633
|
+
advance
|
|
634
|
+
args, bp = parse_arglist(")")
|
|
635
|
+
blk = parse_block_opt
|
|
636
|
+
error("both block arg and actual block given", t.line) if blk && bp
|
|
637
|
+
FCall.new(name, args, blk || bp, t.line)
|
|
638
|
+
elsif depth
|
|
639
|
+
LVar.new(name, depth, t.line)
|
|
640
|
+
elsif !@no_command && command_start?(nt)
|
|
641
|
+
args, bp = parse_command_args
|
|
642
|
+
blk = parse_block_opt
|
|
643
|
+
error("both block arg and actual block given", t.line) if blk && bp
|
|
644
|
+
FCall.new(name, args, blk || bp, t.line)
|
|
645
|
+
else
|
|
646
|
+
blk = parse_block_opt
|
|
647
|
+
blk ? FCall.new(name, [], blk, t.line) : VCall.new(name, t.line)
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def parse_constant
|
|
652
|
+
t = advance
|
|
653
|
+
nt = peek
|
|
654
|
+
if op?("(", nt) && !nt.spc
|
|
655
|
+
advance
|
|
656
|
+
args, bp = parse_arglist(")")
|
|
657
|
+
blk = parse_block_opt
|
|
658
|
+
error("both block arg and actual block given", t.line) if blk && bp
|
|
659
|
+
FCall.new(t.value, args, blk || bp, t.line)
|
|
660
|
+
elsif !@no_command && command_start?(nt)
|
|
661
|
+
args, bp = parse_command_args
|
|
662
|
+
FCall.new(t.value, args, bp, t.line)
|
|
663
|
+
else
|
|
664
|
+
Const.new(t.value, t.line)
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def parse_paren
|
|
669
|
+
t = advance
|
|
670
|
+
body = with_flags { parse_statements }
|
|
671
|
+
expect_op(")")
|
|
672
|
+
return Lit.new(nil, t.line) if body.stmts.empty?
|
|
673
|
+
return body.stmts[0] if body.stmts.length == 1
|
|
674
|
+
|
|
675
|
+
body
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def parse_array
|
|
679
|
+
t = advance
|
|
680
|
+
args, bp = parse_arglist("]")
|
|
681
|
+
error("block argument is not allowed in an array literal", t.line) if bp
|
|
682
|
+
Arr.new(args, t.line)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def parse_hash
|
|
686
|
+
t = advance
|
|
687
|
+
pairs = []
|
|
688
|
+
with_flags do
|
|
689
|
+
loop do
|
|
690
|
+
skip_nl
|
|
691
|
+
break if op?("}")
|
|
692
|
+
|
|
693
|
+
kt = peek
|
|
694
|
+
if kt.type == :label
|
|
695
|
+
advance
|
|
696
|
+
skip_nl
|
|
697
|
+
pairs << [Lit.new(kt.value.to_sym, kt.line), parse_ternary]
|
|
698
|
+
else
|
|
699
|
+
k = parse_ternary
|
|
700
|
+
skip_nl
|
|
701
|
+
expect_op("=>")
|
|
702
|
+
skip_nl
|
|
703
|
+
pairs << [k, parse_ternary]
|
|
704
|
+
end
|
|
705
|
+
skip_nl
|
|
706
|
+
break unless accept_op(",")
|
|
707
|
+
end
|
|
708
|
+
skip_nl
|
|
709
|
+
expect_op("}")
|
|
710
|
+
end
|
|
711
|
+
HashLit.new(pairs, t.line)
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
# Parses arguments up to +closer+. Returns [args, block_pass].
|
|
715
|
+
def parse_arglist(closer)
|
|
716
|
+
args = []
|
|
717
|
+
pairs = []
|
|
718
|
+
bp_ref = [nil]
|
|
719
|
+
with_flags do
|
|
720
|
+
loop do
|
|
721
|
+
skip_nl
|
|
722
|
+
break if op?(closer)
|
|
723
|
+
|
|
724
|
+
parse_one_arg(args, pairs, bp_ref)
|
|
725
|
+
skip_nl
|
|
726
|
+
break unless accept_op(",")
|
|
727
|
+
end
|
|
728
|
+
skip_nl
|
|
729
|
+
expect_op(closer)
|
|
730
|
+
end
|
|
731
|
+
args << HashLit.new(pairs, pairs.first[0].line) unless pairs.empty?
|
|
732
|
+
[args, bp_ref[0]]
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def parse_command_args
|
|
736
|
+
args = []
|
|
737
|
+
pairs = []
|
|
738
|
+
bp_ref = [nil]
|
|
739
|
+
with_flags(no_do: true, no_command: true) do
|
|
740
|
+
loop do
|
|
741
|
+
parse_one_arg(args, pairs, bp_ref)
|
|
742
|
+
break unless accept_op(",")
|
|
743
|
+
|
|
744
|
+
skip_nl
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
args << HashLit.new(pairs, pairs.first[0].line) unless pairs.empty?
|
|
748
|
+
[args, bp_ref[0]]
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
def parse_one_arg(args, pairs, bp_ref)
|
|
752
|
+
t = peek
|
|
753
|
+
error("block argument must be the last argument", t.line) if bp_ref[0]
|
|
754
|
+
if op?("&")
|
|
755
|
+
advance
|
|
756
|
+
st = advance
|
|
757
|
+
error("only &:symbol block arguments are supported", st.line) unless st.type == :sym
|
|
758
|
+
bp_ref[0] = BlockPass.new(st.value, st.line)
|
|
759
|
+
elsif t.type == :label
|
|
760
|
+
advance
|
|
761
|
+
skip_nl
|
|
762
|
+
pairs << [Lit.new(t.value.to_sym, t.line), parse_ternary]
|
|
763
|
+
else
|
|
764
|
+
e = parse_ternary
|
|
765
|
+
if op?("=>")
|
|
766
|
+
advance
|
|
767
|
+
skip_nl
|
|
768
|
+
pairs << [e, parse_ternary]
|
|
769
|
+
else
|
|
770
|
+
error("positional argument after keyword arguments", t.line) unless pairs.empty?
|
|
771
|
+
args << e
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def parse_block_opt
|
|
777
|
+
if op?("{")
|
|
778
|
+
parse_block("}")
|
|
779
|
+
elsif kw?("do") && !@no_do
|
|
780
|
+
parse_block("end")
|
|
781
|
+
end
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
def parse_block(closer)
|
|
785
|
+
t = advance
|
|
786
|
+
@scopes.push({})
|
|
787
|
+
@ctx.push(:block)
|
|
788
|
+
begin
|
|
789
|
+
with_flags do
|
|
790
|
+
params = []
|
|
791
|
+
if op?("||")
|
|
792
|
+
advance
|
|
793
|
+
elsif op?("|")
|
|
794
|
+
advance
|
|
795
|
+
params = parse_params({})
|
|
796
|
+
expect_op("|")
|
|
797
|
+
end
|
|
798
|
+
body = parse_statements
|
|
799
|
+
closer == "}" ? expect_op("}") : expect_kw("end")
|
|
800
|
+
BlockNode.new(params, body, t.line)
|
|
801
|
+
end
|
|
802
|
+
ensure
|
|
803
|
+
@ctx.pop
|
|
804
|
+
@scopes.pop
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def parse_params(seen)
|
|
809
|
+
list = []
|
|
810
|
+
loop do
|
|
811
|
+
t = peek
|
|
812
|
+
if op?("(")
|
|
813
|
+
advance
|
|
814
|
+
list << parse_params(seen)
|
|
815
|
+
expect_op(")")
|
|
816
|
+
elsif t.type == :ident && t.value.match?(/\A[a-z_][A-Za-z0-9_]*\z/)
|
|
817
|
+
advance
|
|
818
|
+
if seen[t.value] && !t.value.start_with?("_")
|
|
819
|
+
error("duplicated argument name", t.line)
|
|
820
|
+
end
|
|
821
|
+
seen[t.value] = true
|
|
822
|
+
@scopes.last[t.value] = true
|
|
823
|
+
list << t.value
|
|
824
|
+
else
|
|
825
|
+
error("unsupported block parameter (only names and (a, b) are allowed)", t.line)
|
|
826
|
+
end
|
|
827
|
+
break unless accept_op(",")
|
|
828
|
+
end
|
|
829
|
+
list
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
def parse_then
|
|
833
|
+
t = peek
|
|
834
|
+
if term?(t)
|
|
835
|
+
skip_terms
|
|
836
|
+
advance if kw?("then")
|
|
837
|
+
elsif kw?("then", t)
|
|
838
|
+
advance
|
|
839
|
+
else
|
|
840
|
+
unexpected(t, "expected 'then' or a newline")
|
|
841
|
+
end
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def parse_if
|
|
845
|
+
t = advance
|
|
846
|
+
cond = with_flags { parse_expr }
|
|
847
|
+
parse_then
|
|
848
|
+
body = with_flags { parse_statements }
|
|
849
|
+
if t.value == "unless"
|
|
850
|
+
els = nil
|
|
851
|
+
if kw?("else")
|
|
852
|
+
advance
|
|
853
|
+
els = with_flags { parse_statements }
|
|
854
|
+
end
|
|
855
|
+
expect_kw("end")
|
|
856
|
+
return If.new(cond, els, body, t.line)
|
|
857
|
+
end
|
|
858
|
+
node = If.new(cond, body, nil, t.line)
|
|
859
|
+
tail = node
|
|
860
|
+
loop do
|
|
861
|
+
if kw?("elsif")
|
|
862
|
+
et = advance
|
|
863
|
+
c = with_flags { parse_expr }
|
|
864
|
+
parse_then
|
|
865
|
+
b = with_flags { parse_statements }
|
|
866
|
+
n = If.new(c, b, nil, et.line)
|
|
867
|
+
tail.els = n
|
|
868
|
+
tail = n
|
|
869
|
+
elsif kw?("else")
|
|
870
|
+
advance
|
|
871
|
+
tail.els = with_flags { parse_statements }
|
|
872
|
+
break
|
|
873
|
+
else
|
|
874
|
+
break
|
|
875
|
+
end
|
|
876
|
+
end
|
|
877
|
+
expect_kw("end")
|
|
878
|
+
node
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def loop_header_end
|
|
882
|
+
if kw?("do")
|
|
883
|
+
advance
|
|
884
|
+
elsif term?
|
|
885
|
+
skip_terms
|
|
886
|
+
else
|
|
887
|
+
unexpected(peek, "expected 'do' or a newline")
|
|
888
|
+
end
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def parse_while
|
|
892
|
+
t = advance
|
|
893
|
+
cond = with_flags(no_do: true) { parse_expr }
|
|
894
|
+
loop_header_end
|
|
895
|
+
@ctx.push(:loop)
|
|
896
|
+
body = begin
|
|
897
|
+
with_flags { parse_statements }
|
|
898
|
+
ensure
|
|
899
|
+
@ctx.pop
|
|
900
|
+
end
|
|
901
|
+
expect_kw("end")
|
|
902
|
+
While.new(cond, body, t.value == "until", t.line)
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
def parse_for
|
|
906
|
+
t = advance
|
|
907
|
+
names = []
|
|
908
|
+
loop do
|
|
909
|
+
nt = advance
|
|
910
|
+
unexpected(nt, "expected a variable name") unless nt.type == :ident
|
|
911
|
+
names << nt.value
|
|
912
|
+
break unless accept_op(",")
|
|
913
|
+
end
|
|
914
|
+
expect_kw("in")
|
|
915
|
+
iter = with_flags(no_do: true) { parse_expr }
|
|
916
|
+
loop_header_end
|
|
917
|
+
vars = names.map { |n| [n, declare(n)] }
|
|
918
|
+
@ctx.push(:loop)
|
|
919
|
+
body = begin
|
|
920
|
+
with_flags { parse_statements }
|
|
921
|
+
ensure
|
|
922
|
+
@ctx.pop
|
|
923
|
+
end
|
|
924
|
+
expect_kw("end")
|
|
925
|
+
For.new(vars, iter, body, t.line)
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
def parse_case
|
|
929
|
+
t = advance
|
|
930
|
+
subject = term?(peek) || kw?("when") ? nil : with_flags { parse_expr }
|
|
931
|
+
skip_terms
|
|
932
|
+
whens = []
|
|
933
|
+
while kw?("when")
|
|
934
|
+
advance
|
|
935
|
+
conds = [with_flags { parse_ternary }]
|
|
936
|
+
while accept_op(",")
|
|
937
|
+
skip_nl
|
|
938
|
+
conds << with_flags { parse_ternary }
|
|
939
|
+
end
|
|
940
|
+
parse_then
|
|
941
|
+
whens << [conds, with_flags { parse_statements }]
|
|
942
|
+
end
|
|
943
|
+
unexpected(peek, "expected 'when'") if whens.empty?
|
|
944
|
+
els = nil
|
|
945
|
+
if kw?("else")
|
|
946
|
+
advance
|
|
947
|
+
els = with_flags { parse_statements }
|
|
948
|
+
end
|
|
949
|
+
expect_kw("end")
|
|
950
|
+
Case.new(subject, whens, els, t.line)
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
end
|