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.
@@ -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