birman 0.2.2 → 0.3.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,940 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Birman
4
+ class Compiler
5
+ # Writes the Parser's methods: one per matcher (shared by equal
6
+ # matchers), plus a packrat memo wrapper (ref_<rule>) per rule.
7
+ #
8
+ # Each matcher becomes a fixed template, following Birman's matchers
9
+ # line by line. A method takes a byte position p and returns where
10
+ # its match ended, or FAIL. A matcher whose value something reads
11
+ # leaves it in its own instance variable, which the caller copies
12
+ # straight away: the next match may overwrite it.
13
+ # [class.Compiler.Emitter]
14
+ # purpose = "Turns each matcher into a method of the generated Parser"
15
+ class Emitter
16
+ # How to call a compiled matcher: the name of the method to call with a
17
+ # position, and the expression for its value just after a
18
+ # successful call (nil when nothing reads it).
19
+ Call = Struct.new(:name, :value)
20
+
21
+ PUNCTUATION = {
22
+ "=" => "eq", "+" => "plus", "-" => "minus", "*" => "star", "/" => "slash", "(" => "lparen",
23
+ ")" => "rparen", "{" => "lbrace", "}" => "rbrace", "[" => "lbracket", "]" => "rbracket",
24
+ "," => "comma", ":" => "colon", ";" => "semicolon", "." => "dot", "<" => "lt", ">" => "gt",
25
+ "!" => "bang", "?" => "question", "&" => "amp", "|" => "bar", "%" => "percent", "^" => "caret"
26
+ }.freeze
27
+
28
+ # An inline RBS signature for every position-taking method: one
29
+ # position Spinel can't type would widen them all to boxed values.
30
+ POSITION = "#: (Integer) -> Integer\n"
31
+
32
+ SAVE = <<~RUBY
33
+ of = @furthest
34
+ oe = @expected
35
+ @furthest = p
36
+ @expected = NONE
37
+ RUBY
38
+
39
+ # Rules whose values something reads (Refs met wanting a value).
40
+ attr_reader :read_rules
41
+
42
+ # valued: the rules to keep values for. If read_rules turns out
43
+ # bigger, compile again with those (see Compiler#to_ruby).
44
+ def initialize(grammar, valued)
45
+ @grammar = grammar
46
+ @valued = valued
47
+ @read_rules = [grammar.class.root]
48
+ @types = Types.new(grammar)
49
+ @nodes = Nodes.new
50
+ @actions = Actions.new(grammar)
51
+ @methods = []
52
+ @compiled = {}
53
+ @taken = {}
54
+ @values = {}
55
+ @rules = {}
56
+ @ignores = []
57
+ end
58
+
59
+ def compile
60
+ root = @grammar.class.root or raise Error, "#{@grammar.class} has no root rule"
61
+ queue = [root, ignore_rule].compact
62
+ while (name = queue.shift)
63
+ next if @rules.key?(name)
64
+
65
+ @rules[name] = compile_rule(name)
66
+ queue.concat(@pending)
67
+ end
68
+ refuse_clashes
69
+ end
70
+
71
+ def node_classes = @nodes.sources
72
+
73
+ def node_names = @nodes.names
74
+
75
+ def parser_body
76
+ [*@actions.constants.map { "#{_1}\n" }, initializer, parse_method, "private\n",
77
+ *@actions.methods, *@methods, *@ignores.map { skip_method(_1) }, init_values, init_memos,
78
+ *@rules.map { |name, call| ref_method(name, call) }].join("\n")
79
+ end
80
+
81
+ private
82
+
83
+ def ignore_rule = @grammar.class.ignore
84
+
85
+ def refuse_clashes
86
+ met = @rules.keys.group_by { ident(_1) }.values.find { _1.size > 1 }
87
+ raise Error, "rules #{met.map(&:inspect).join(" and ")} would have one name in the generated parser" if met
88
+
89
+ clash = @actions.names.find { @taken.key?(_1.to_s) || @rules.key?(_1) }
90
+ raise Error, "the grammar method #{clash} would clash with a generated method" if clash
91
+
92
+ shared = (@actions.constant_names & @nodes.names).first
93
+ raise Error, "#{shared} is both a node class and a constant copied actions read" if shared
94
+ end
95
+
96
+ # A rule's name as part of a Ruby identifier: rule(:valid?) is ref_valid_3f.
97
+ def ident(name) = name.to_s.gsub(/[^A-Za-z0-9_]/) { format("_%02x", _1.ord) }
98
+
99
+ def compile_rule(name)
100
+ @pending = []
101
+ @rule = name
102
+ @prefix = ident(name)
103
+ matcher = begin
104
+ @grammar.matcher_for(name)
105
+ rescue ArgumentError => e
106
+ raise Error, e.message
107
+ end
108
+ want = @valued.include?(name)
109
+ call = compile_matcher(matcher, want, "rule_#{ident(name)}")
110
+ @values["@v_ref_#{ident(name)}"] = @types.initial(Ref.new(name)) if want
111
+ call
112
+ end
113
+
114
+ # --- Dispatch ---
115
+
116
+ def compile_matcher(matcher, want, name = nil)
117
+ key = [matcher, want]
118
+ return @compiled[key] if @compiled.key?(key)
119
+
120
+ @compiled[key] = translate(matcher, want, name)
121
+ end
122
+
123
+ def translate(matcher, want, name) # rubocop:disable Metrics
124
+ case matcher
125
+ when Str then str(matcher, want)
126
+ when Pat then pat(matcher, want, name)
127
+ when Captures then captures(matcher, want, name)
128
+ when Lit then lit(matcher, want)
129
+ when Seq then seq(matcher, want, name)
130
+ when Pick then pick(matcher, want, name)
131
+ when SepBy then sep_by(matcher, want, name)
132
+ when Choice then choice(matcher, want, name)
133
+ when Rep then rep(matcher, want, name)
134
+ when Maybe then maybe(matcher, want, name)
135
+ when Not then negative(matcher, want, name)
136
+ when And then positive(matcher, want, name)
137
+ when Both then both(matcher, want, name)
138
+ when Const then const(matcher, want)
139
+ when Send, Action then transform(matcher, want, name)
140
+ when Label then label(matcher, want, name)
141
+ when Check then check(matcher, want, name)
142
+ when Named then compile_matcher(matcher.inner, want, name)
143
+ when To then to(matcher, want, name)
144
+ when Infix then infix(matcher, want, name)
145
+ when Lexeme then lexeme(matcher, want, name)
146
+ when Quiet then quiet(matcher, want, name)
147
+ when Ref then ref(matcher, want)
148
+ when Map
149
+ raise Error, "rule :#{@rule} uses .map with a block, which can't be compiled; " \
150
+ "use .map(:method_name) or .action(:method_name)"
151
+ else raise Error, "rule :#{@rule} uses #{matcher.class}, which the compiler doesn't know"
152
+ end
153
+ end
154
+
155
+ # --- Methods ---
156
+
157
+ # Defines a method for matcher, its body from the block (given the
158
+ # value variable, or nil if no value is kept), and returns its Call.
159
+ # The slot is taken first, so a method comes before those it calls.
160
+ def define(kind, matcher, keep, name = nil, value: nil)
161
+ name = claim(name || "#{@prefix}_#{kind}")
162
+ ivar = "@v_#{name}" if keep
163
+ @values[ivar] = @types.initial(matcher) if ivar
164
+ slot = @methods.size
165
+ @methods << nil
166
+ body = yield(ivar).gsub(/^[ \t]+$/, "").gsub(/\n{3,}/, "\n\n")
167
+ @methods[slot] = "# #{describe(matcher)}\n#{POSITION}def #{name}(p)\n#{indent(body.chomp)}\nend\n"
168
+ Call.new(name, value || ivar)
169
+ end
170
+
171
+ # name, or name with a number added if it's taken. ref_ and skip_
172
+ # names (and @v_ref_ values) are the rules' own (see ref_method and
173
+ # skip_method), so a part of rule :ref is rule_ref_seq, not ref_seq.
174
+ def claim(name)
175
+ name = "rule_#{name}" if name.start_with?("ref_", "skip_")
176
+ candidate = name
177
+ n = 1
178
+ candidate = "#{name}#{n += 1}" while @taken.key?(candidate)
179
+ @taken[candidate] = true
180
+ candidate
181
+ end
182
+
183
+ def describe(matcher)
184
+ kind = matcher.class.name.split("::").last
185
+ kind = "#{kind} #{matcher.name.inspect}" if matcher.is_a?(Label) || matcher.is_a?(Check)
186
+ "#{kind}: #{EBNF.expr(matcher.to_ast, EBNF::CHOICE)}"
187
+ end
188
+
189
+ # How an EBNF expression appears in a generated string literal.
190
+ def expected(matcher) = EBNF.expr(matcher.to_ast, EBNF::CHOICE).inspect
191
+
192
+ def indent(text) = text.lines.map { _1.strip.empty? ? "\n" : " #{_1}" }.join
193
+
194
+ # --- Terminals ---
195
+
196
+ def str(matcher, want)
197
+ text = matcher.str
198
+ call = @compiled[[matcher, false]] ||= define(:str, matcher, false, "str_#{slug(text)}") do
199
+ next "p" if text.empty?
200
+
201
+ test = if text.bytesize == 1
202
+ "p < @len && @input.getbyte(p) == #{text.getbyte(0)}"
203
+ else
204
+ "bytes_at?(p, #{text.inspect})"
205
+ end
206
+ "return fail_at(p, #{text.inspect.inspect}) unless #{test}\n\np + #{text.bytesize}"
207
+ end
208
+ Call.new(call.name, want ? text.inspect : nil)
209
+ end
210
+
211
+ def pat(matcher, want, name)
212
+ regexp = anchored(matcher.re)
213
+ define(:pat, matcher, want, name) do |v|
214
+ found = if v
215
+ "unless len.nil?\n #{v} = @input.byteslice(p, len)\n return p + len\nend\n"
216
+ else
217
+ "return p + len unless len.nil?\n\n"
218
+ end
219
+ "@sc.pos = p\nlen = @sc.skip(#{regexp})\n#{found}fail_at(p, #{matcher.source.inspect})"
220
+ end
221
+ end
222
+
223
+ # Groups by number: Spinel's scanner can't look them up by name.
224
+ # Named groups are numbered in order, and once a group is named the
225
+ # unnamed ones don't capture (see Captures).
226
+ def captures(matcher, want, name)
227
+ regexp = anchored(matcher.pat.re)
228
+ define(:captures, matcher, want, name) do |v|
229
+ pairs = matcher.pat.re.names.each_with_index.map { |group, i| "#{hash_key(group.to_sym)} @sc[#{i + 1}]" }
230
+ found = if v
231
+ "unless len.nil?\n #{v} = { #{pairs.join(", ")} }\n return p + len\nend\n"
232
+ else
233
+ "return p + len unless len.nil?\n\n"
234
+ end
235
+ "@sc.pos = p\nlen = @sc.skip(#{regexp})\n#{found}fail_at(p, #{matcher.pat.source.inspect})"
236
+ end
237
+ end
238
+
239
+ def anchored(re)
240
+ Regexps.anchored(re)
241
+ rescue Error => e
242
+ raise Error, "rule :#{@rule}: #{e.message}"
243
+ end
244
+
245
+ # A literal is whatever the grammar says it stands for (keyword or
246
+ # exact string, then the ignore rule), resolved now.
247
+ def lit(matcher, want)
248
+ outer = @prefix
249
+ @prefix = "lit_#{slug(matcher.text)}"
250
+ compile_matcher(@grammar.literal_matcher(matcher.text), want, @prefix)
251
+ ensure
252
+ @prefix = outer
253
+ end
254
+
255
+ def slug(text)
256
+ return text if text.match?(/\A\w+\z/)
257
+
258
+ text.each_char.map { |c| PUNCTUATION[c] || c.bytes.map { format("x%02x", _1) }.join }.join("_")
259
+ end
260
+
261
+ # --- Sequences ---
262
+
263
+ def seq(matcher, want, name, build: nil)
264
+ wanted = seq_wanted(matcher, want || build)
265
+ define(:seq, matcher, want, name) do |v|
266
+ lines, locals = steps(matcher.parts, matcher.commit_at, wanted)
267
+ lines << "#{v} = #{build ? build.call(locals) : seq_value(matcher, locals)}" if v
268
+ [*lines, "q"].join("\n")
269
+ end
270
+ end
271
+
272
+ def seq_wanted(matcher, want)
273
+ return [] unless want
274
+ return [matcher.only_at] if matcher.only_at
275
+
276
+ named = matcher.parts.each_index.select { matcher.parts[_1].capture_key }
277
+ named.empty? ? matcher.parts.each_index.to_a : named
278
+ end
279
+
280
+ def seq_value(matcher, locals)
281
+ return locals.fetch(matcher.only_at) if matcher.only_at
282
+
283
+ keys = matcher.parts.map(&:capture_key)
284
+ return "[#{locals.values.join(", ")}]" if keys.none?
285
+
286
+ pairs = {}
287
+ keys.each_with_index { |key, i| pairs[key] = locals[i] if key }
288
+ "{ #{pairs.map { |key, local| "#{hash_key(key)} #{local}" }.join(", ")} }"
289
+ end
290
+
291
+ def hash_key(key) = key.is_a?(Symbol) && key.match?(/\A[a-z_]\w*\z/) ? "#{key}:" : "#{key.inspect} =>"
292
+
293
+ # The parts in order from p, ending at q. A part that fails before
294
+ # commit_at fails the sequence; one at or after it ends the parse.
295
+ # Returns the lines and {index => local} for the wanted values.
296
+ def steps(parts, commit_at, wanted)
297
+ locals = {}
298
+ lines = parts.each_with_index.flat_map do |part, i|
299
+ keep = wanted.include?(i)
300
+ locals[i] = "v#{i}" if keep
301
+ step(compile_matcher(part, keep), part, i.zero? ? "p" : "q", commit_at && i >= commit_at, keep && "v#{i}")
302
+ end
303
+ lines.unshift("q = p") if parts.empty?
304
+ [lines, locals]
305
+ end
306
+
307
+ def step(call, part, at, committed, local)
308
+ keep = local ? ["#{local} = #{call.value}"] : []
309
+ return ["q = #{call.name}(#{at})", "return FAIL if q == FAIL\n", *keep] unless committed
310
+
311
+ ["r = #{call.name}(#{at})", "commit_failure(#{at}, #{expected(part)}) if r == FAIL", *keep, "q = r"]
312
+ end
313
+
314
+ def pick(matcher, want, name)
315
+ wanted = want ? [matcher.index] : []
316
+ define(:pick, matcher, want, name) do |v|
317
+ lines, locals = steps(matcher.parts, nil, wanted)
318
+ lines << "#{v} = #{locals.fetch(matcher.index)}" if v
319
+ [*lines, "q"].join("\n")
320
+ end
321
+ end
322
+
323
+ def sep_by(matcher, want, name)
324
+ define(:sep_by, matcher, want, name) do |v|
325
+ item = compile_matcher(matcher.item, want)
326
+ sep = compile_matcher(matcher.sep, false)
327
+ <<~RUBY
328
+ q = #{item.name}(p)
329
+ #{sep_by_none(matcher, v)}
330
+ #{"values = [#{item.value}]" if v}
331
+ while true
332
+ s = #{sep.name}(q)
333
+ break if s == FAIL
334
+
335
+ r = #{item.name}(s)
336
+ #{"q = s if r == FAIL # a trailing separator\n " if matcher.trailing}break if r == FAIL || r == q
337
+
338
+ #{"values << #{item.value}" if v}
339
+ q = r
340
+ end
341
+ #{"#{v} = values" if v}
342
+ q
343
+ RUBY
344
+ end
345
+ end
346
+
347
+ def sep_by_none(matcher, value)
348
+ return "return FAIL if q == FAIL\n" unless matcher.min.zero?
349
+ return "return p if q == FAIL\n" unless value
350
+
351
+ "if q == FAIL\n #{value} = []\n return p\nend\n"
352
+ end
353
+
354
+ # --- Choice and repetition ---
355
+
356
+ def choice(matcher, want, name)
357
+ define(:choice, matcher, want, name) do |v|
358
+ *alts, last = matcher.alts.map { compile_matcher(_1, want) }
359
+ tries = alts.map do |alt|
360
+ taken = v ? "if q != FAIL\n #{v} = #{alt.value}\n return q\nend\n" : "return q if q != FAIL\n"
361
+ "q = #{alt.name}(p)\n#{taken}"
362
+ end
363
+ [*tries, "q = #{last.name}(p)", ("#{v} = #{last.value} if q != FAIL" if v), "q"].compact.join("\n")
364
+ end
365
+ end
366
+
367
+ def rep(matcher, want, name)
368
+ define(:rep, matcher, want, name) do |v|
369
+ inner = compile_matcher(matcher.inner, want)
370
+ <<~RUBY
371
+ n = 0
372
+ #{"values = []" if v}
373
+ while #{matcher.max ? "n < #{matcher.max}" : "true"}
374
+ r = #{inner.name}(p)
375
+ break if r == FAIL
376
+
377
+ # Matching nothing ends the loop, but counts as the first match.
378
+ if r == p
379
+ if n.zero?
380
+ #{"values << #{inner.value}" if v}
381
+ n = 1
382
+ end
383
+ break
384
+ end
385
+ #{"values << #{inner.value}" if v}
386
+ n += 1
387
+ p = r
388
+ end
389
+ #{"return FAIL if n < #{matcher.min}" if matcher.min.positive?}
390
+ #{"#{v} = values" if v}
391
+ p
392
+ RUBY
393
+ end
394
+ end
395
+
396
+ def maybe(matcher, want, name)
397
+ define(:maybe, matcher, want, name) do |v|
398
+ inner = compile_matcher(matcher.inner, want)
399
+ next "q = #{inner.name}(p)\nq == FAIL ? p : q" unless v
400
+
401
+ "q = #{inner.name}(p)\nif q == FAIL\n #{v} = nil\n return p\nend\n#{v} = #{inner.value}\nq"
402
+ end
403
+ end
404
+
405
+ # --- Lookahead ---
406
+
407
+ # Not: isolated, a commit failure inside is just a failure, and its
408
+ # diagnostics are dropped either way.
409
+ def negative(matcher, want, name)
410
+ define(:not, matcher, false, name, value: want ? "nil" : nil) do
411
+ inner = compile_matcher(matcher.inner, false)
412
+ "#{SAVE}#{lookahead(inner)}@furthest = of\n@expected = oe\nq == FAIL ? p : FAIL"
413
+ end
414
+ end
415
+
416
+ # A commit failure inside a lookahead is just a failure. The rules it
417
+ # unwound through are left in progress, so they're abandoned. Each
418
+ # active lookahead holds one of Spinel's 64 begin blocks, so they're
419
+ # counted (see Compiler::LOOKAHEAD_DEPTH).
420
+ def lookahead(inner)
421
+ <<~RUBY
422
+ raise TooDeep, "lookaheads nested too deeply" if @lookaheads >= LOOKAHEAD_DEPTH
423
+
424
+ @lookaheads += 1
425
+ depth = @in_progress_at.size
426
+ begin
427
+ q = #{inner.name}(p)
428
+ rescue Committed
429
+ abandon(depth)
430
+ q = FAIL
431
+ end
432
+ @lookaheads -= 1
433
+ RUBY
434
+ end
435
+
436
+ def positive(matcher, want, name)
437
+ define(:and, matcher, false, name, value: want ? "nil" : nil) do
438
+ inner = compile_matcher(matcher.inner, false)
439
+ "#{SAVE}#{lookahead(inner)}peek_end(p, q, of, oe, #{expected(matcher.inner)}) ? p : FAIL"
440
+ end
441
+ end
442
+
443
+ def both(matcher, want, name)
444
+ define(:both, matcher, want, name) do |v|
445
+ left = compile_matcher(matcher.left, false)
446
+ right = compile_matcher(matcher.right, want)
447
+ "#{SAVE}#{lookahead(left)}return FAIL unless peek_end(p, q, of, oe, #{expected(matcher.left)})\n\n" \
448
+ "q = #{right.name}(p)\n#{"#{v} = #{right.value} if q != FAIL\n" if v}q"
449
+ end
450
+ end
451
+
452
+ # --- Values ---
453
+
454
+ def const(matcher, want)
455
+ inner = compile_matcher(matcher.inner, false)
456
+ return inner unless want
457
+
458
+ unless Literals.literal?(matcher.constant)
459
+ raise Error, "rule :#{@rule} uses .means(#{matcher.constant.inspect}), which isn't a literal"
460
+ end
461
+
462
+ Call.new(inner.name, matcher.constant.inspect)
463
+ end
464
+
465
+ # Send and Action. Both run even when nothing reads their value, as
466
+ # the interpreter's do: either may raise (an action checking its
467
+ # value, nil.upcase).
468
+ def transform(matcher, want, name)
469
+ check_send(matcher) if matcher.is_a?(Send)
470
+
471
+ kind = matcher.is_a?(Send) ? :send : :action
472
+ define(kind, matcher, want, name) do |v|
473
+ inner = compile_matcher(matcher.inner, true)
474
+ call = apply(matcher, inner.value)
475
+ "q = #{inner.name}(p)\n#{"#{v} = " if v}#{call} if q != FAIL\nq"
476
+ end
477
+ end
478
+
479
+ def apply(matcher, value)
480
+ name = plain_name(matcher)
481
+ # String#to_i, Bignums included (Spinel's overflows past 63 bits).
482
+ if name == :to_i && matcher.is_a?(Send)
483
+ return "BirmanRT.to_i(#{value})" if @types.of(matcher.inner) == :str
484
+ return "(#{value}.nil? ? 0 : BirmanRT.to_i(#{value}))" if @types.str_or_nil?(matcher.inner)
485
+ end
486
+ return "#{value}.#{name}" if matcher.is_a?(Send)
487
+
488
+ @actions.copy(name, @rule, "action")
489
+ "#{name}(#{value})"
490
+ end
491
+
492
+ # The method name of a .map, .action or .check, which the generated
493
+ # code calls as written.
494
+ def plain_name(matcher)
495
+ name = matcher.method_name
496
+ return name if name.match?(/\A[a-z_]\w*[?!]?\z/)
497
+
498
+ form = { Send => "map", Action => "action", Check => "check" }.fetch(matcher.class)
499
+ raise Error, "rule :#{@rule} uses .#{form}(#{name.inspect}), which isn't a plain method name"
500
+ end
501
+
502
+ # .map(:hex) and .map(:oct) raise past 63 bits in a Spinel binary, as
503
+ # does .map(:to_i) unless the value is a String (BirmanRT.to_i reads
504
+ # those, and nil, as from str.maybe, is 0) or an Integer.
505
+ def check_send(matcher)
506
+ name = matcher.method_name
507
+ return unless %i[hex oct].include?(name) || (name == :to_i && !to_i_safe?(matcher.inner))
508
+
509
+ unknown = " (its value isn't always a String or an Integer)" if name == :to_i
510
+ raise Error, "rule :#{@rule} uses .map(:#{name})#{unknown}: #{Overflows::MESSAGES.fetch(name)}"
511
+ end
512
+
513
+ def to_i_safe?(inner) = %i[str int].include?(@types.of(inner)) || @types.str_or_nil?(inner)
514
+
515
+ # --- Nodes ---
516
+
517
+ def to(matcher, want, name)
518
+ return compile_matcher(matcher.inner, false) unless want
519
+
520
+ node = @nodes.name_for(matcher.node, @rule)
521
+ keys = hash_keys(matcher.inner)
522
+ return seq_node(matcher, node, name) if keys.is_a?(Array) && direct_seq?(matcher.inner)
523
+
524
+ define(:to, matcher, true, name) do |v|
525
+ inner = compile_matcher(matcher.inner, true)
526
+ args = node_args(matcher.node, keys, inner.value)
527
+ value = "x = #{inner.value}\n" if keys == :unknown && !matcher.node.members.empty?
528
+ "q = #{inner.name}(p)\nreturn FAIL if q == FAIL\n\n#{value}#{v} = #{node_call(node, args, "span(p, q)")}\nq"
529
+ end
530
+ end
531
+
532
+ # A seq of named parts builds the node from its locals, no Hash.
533
+ def seq_node(matcher, node, name)
534
+ fields = hash_keys(matcher.inner).to_h { [_1, nil] }
535
+ node_fields(matcher.node, fields) # checked before compiling the seq
536
+ build = ->(locals) { node_new(matcher.inner, node, matcher.node, locals) }
537
+ seq(matcher.inner, true, name, build:).tap { @compiled[[matcher, true]] = _1 }
538
+ end
539
+
540
+ def node_new(seq, node, node_class, locals)
541
+ by_key = {}
542
+ seq.parts.each_with_index { |part, i| by_key[part.capture_key] = locals[i] if part.capture_key }
543
+ node_call(node, node_fields(node_class, by_key), "span(p, q)")
544
+ end
545
+
546
+ # node.new(fields..., span), with no stray comma when there are no fields.
547
+ def node_call(node, args, span) = "#{node}.new(#{[*args, span].join(", ")})"
548
+
549
+ def direct_seq?(matcher) = matcher.is_a?(Seq) && matcher.only_at.nil?
550
+
551
+ # The keys of the Hash that matcher's value is: nil if it's never a
552
+ # Hash, :unknown if that's only known at parse time.
553
+ def hash_keys(matcher, seen = []) # rubocop:disable Metrics
554
+ case matcher
555
+ when Seq
556
+ return hash_keys(matcher.parts[matcher.only_at], seen) if matcher.only_at
557
+
558
+ keys = matcher.parts.filter_map(&:capture_key)
559
+ keys.empty? ? nil : keys.uniq
560
+ when Pick then hash_keys(matcher.parts[matcher.index], seen)
561
+ when Label, Check, Quiet, Named, Lexeme then hash_keys(matcher.inner, seen)
562
+ when Both then hash_keys(matcher.right, seen)
563
+ when Captures then matcher.pat.re.names.map(&:to_sym)
564
+ when Lit, Str, Pat, Rep, SepBy, Not, And, To then nil
565
+ when Infix then hash_keys(matcher.operand, seen) && :unknown # with no operator, the operand's value
566
+ when Send then Types::SENDS.key?(matcher.method_name) && matcher.method_name != :to_h ? nil : :unknown
567
+ when Const then matcher.constant.is_a?(Hash) ? matcher.constant.keys : nil
568
+ when Choice
569
+ keys = matcher.alts.map { hash_keys(_1, seen) }.uniq
570
+ keys.size == 1 ? keys.first : :unknown
571
+ when Ref
572
+ seen.include?(matcher.name) ? :unknown : hash_keys(@grammar.matcher_for(matcher.name), [*seen, matcher.name])
573
+ else :unknown
574
+ end
575
+ end
576
+
577
+ # Like Birman.build: a Hash value fills the fields by name, and any
578
+ # other value is the first field. Fields left out get their defaults.
579
+ def node_args(node, keys, value)
580
+ return node_fields(node, keys.to_h { [_1, "#{value}[#{_1.inspect}]"] }) if keys.is_a?(Array)
581
+ return [] if node.members.empty? # As in To#match: a node with no fields drops the value.
582
+
583
+ first, *rest = node.members
584
+ defaults = @nodes.defaults(node)
585
+ unless (rest - defaults.keys).empty?
586
+ raise Error, "rule :#{@rule} builds a node with #{node.members.size} fields from one value"
587
+ end
588
+ return node_fields(node, first => value) if keys.nil?
589
+
590
+ node.members.map { unknown_field(_1, _1 == first, defaults[_1]) }
591
+ end
592
+
593
+ # A field from x, which may or may not be a Hash, and may be one
594
+ # that leaves the field out.
595
+ def unknown_field(member, first, default)
596
+ key = member.inspect
597
+ from_hash = default ? "(x.key?(#{key}) ? x[#{key}] : #{default})" : "x.fetch(#{key})"
598
+ "x.is_a?(Hash) ? #{from_hash} : #{first ? "x" : default}"
599
+ end
600
+
601
+ # node.new's field arguments: each field from given (sources by field),
602
+ # or its default, written out so that each node gets its own.
603
+ def node_fields(node, given)
604
+ defaults = @nodes.defaults(node)
605
+ unless (given.keys - node.members).empty? && (node.members - given.keys - defaults.keys).empty?
606
+ raise Error, "rule :#{@rule} builds a node with fields #{node.members.inspect} " \
607
+ "from named parts #{given.keys.inspect}"
608
+ end
609
+
610
+ node.members.map { |member| given.fetch(member) { defaults.fetch(member) } }
611
+ end
612
+
613
+ def infix(matcher, want, name)
614
+ node = @nodes.name_for(matcher.node, @rule)
615
+ members = matcher.node.members
616
+ unless members.sort == %i[left op right]
617
+ raise Error, "rule :#{@rule}: infix needs a node with fields op, left and right"
618
+ end
619
+
620
+ define(:infix, matcher, want, name) do |v|
621
+ infix_body(matcher, node, members, v)
622
+ end
623
+ end
624
+
625
+ def infix_body(matcher, node, members, value)
626
+ operand = compile_matcher(matcher.operand, true)
627
+ op = compile_matcher(matcher.op, true)
628
+ fields = { op: "op", left: "left", right: operand.value }
629
+ <<~RUBY
630
+ r = #{operand.name}(p)
631
+ return FAIL if r == FAIL
632
+
633
+ left = #{operand.value}
634
+ q = r
635
+ while true
636
+ o = #{op.name}(q)
637
+ break if o == FAIL
638
+
639
+ op = #{op.value}
640
+ r = #{operand.name}(o)
641
+ break if r == FAIL
642
+
643
+ left = #{node}.new(#{members.map { fields.fetch(_1) }.join(", ")}, span(p, r))
644
+ q = r
645
+ end
646
+ #{"#{value} = left" if value}
647
+ q
648
+ RUBY
649
+ end
650
+
651
+ # --- Diagnostics, tokens, rules ---
652
+
653
+ def label(matcher, want, name)
654
+ inner = nil
655
+ call = define(:label, matcher, false, name) do
656
+ inner = compile_matcher(matcher.inner, want)
657
+ "#{SAVE}q = #{inner.name}(p)\nlabel_end(p, q, of, oe, #{matcher.name.inspect})"
658
+ end
659
+ Call.new(call.name, inner.value) # label_end leaves values alone
660
+ end
661
+
662
+ # The predicate runs whether or not anything reads the value (so the
663
+ # inner value is always kept), as an action does, but only after a
664
+ # match: after a failure the value variable is stale.
665
+ # `? true : false` keeps ok a bool for Spinel.
666
+ def check(matcher, want, name)
667
+ inner = nil
668
+ call = define(:check, matcher, false, name) do
669
+ inner = compile_matcher(matcher.inner, true)
670
+ predicate = plain_name(matcher)
671
+ @actions.copy(predicate, @rule, "check")
672
+ "#{SAVE}q = #{inner.name}(p)\nok = q == FAIL || (#{predicate}(#{inner.value}) ? true : false)\n" \
673
+ "check_end(p, q, ok, of, oe, #{matcher.name.inspect})"
674
+ end
675
+ Call.new(call.name, want ? inner.value : nil) # check_end leaves values alone
676
+ end
677
+
678
+ def quiet(matcher, want, name)
679
+ inner = nil
680
+ call = define(:quiet, matcher, false, name) do
681
+ inner = compile_matcher(matcher.inner, want)
682
+ "#{SAVE}q = #{inner.name}(p)\n@furthest = of\n@expected = oe\nq"
683
+ end
684
+ Call.new(call.name, inner.value)
685
+ end
686
+
687
+ def lexeme(matcher, want, name)
688
+ return compile_matcher(matcher.inner, want, name) unless ignore_rule
689
+
690
+ @ignores |= [ignore_rule]
691
+ define(:lexeme, matcher, want, name) do |v|
692
+ inner = compile_matcher(matcher.inner, want)
693
+ keep = "#{v} = #{inner.value}\n" if v
694
+ "q = #{inner.name}(p)\nreturn FAIL if q == FAIL\n\n#{keep}skip_#{ident(ignore_rule)}(q)"
695
+ end
696
+ end
697
+
698
+ def ref(matcher, want)
699
+ @pending << matcher.name
700
+ @read_rules |= [matcher.name] if want
701
+ Call.new("ref_#{ident(matcher.name)}", want ? "@v_ref_#{ident(matcher.name)}" : nil)
702
+ end
703
+
704
+ # --- The rest of the Parser ---
705
+
706
+ def initializer
707
+ <<~RUBY
708
+ def initialize(input)
709
+ super
710
+ @sc = StringScanner.new(input)
711
+ init_values
712
+ end
713
+ RUBY
714
+ end
715
+
716
+ def parse_method
717
+ start = ignore_rule ? "start = ref_#{ident(ignore_rule)}(0)\nstart = 0 if start == FAIL" : "start = 0"
718
+ root = @grammar.class.root
719
+ <<~RUBY
720
+ # Grammar#parse: Success(value) if all of the input parses, else Failure.
721
+ def parse
722
+ bad = BirmanRT.invalid_byte(@input)
723
+ return invalid_input(bad) if bad != FAIL
724
+
725
+ reset
726
+ init_memos
727
+ #{indent(start)}
728
+ q = ref_#{ident(root)}(start)
729
+ return Success.new(@v_ref_#{ident(root)}) if q == @len
730
+
731
+ fail_at(q, "end of input") if q != FAIL
732
+ failure(@furthest, @expected, false)
733
+ rescue Committed => e
734
+ failure(e.pos, e.expected, true)
735
+ rescue SystemStackError, TooDeep
736
+ failure_because(@position, "input is nested too deeply")
737
+ end
738
+ RUBY
739
+ end
740
+
741
+ # Lexeme's tail: the ignore rule, recorded so spans leave it out.
742
+ def skip_method(rule)
743
+ <<~RUBY
744
+ #{POSITION.chomp}
745
+ def skip_#{ident(rule)}(q)
746
+ s = ref_#{ident(rule)}(q)
747
+ return q if s == FAIL
748
+
749
+ ignored_after(q, s)
750
+ s
751
+ end
752
+ RUBY
753
+ end
754
+
755
+ def init_values
756
+ "def init_values\n#{indent(@values.map { |ivar, initial| "#{ivar} = #{initial}\n" }.join)}end\n"
757
+ end
758
+
759
+ def init_memos
760
+ lines = @rules.keys.flat_map do |name|
761
+ (@valued.include?(name) ? %w[mp mv mf me] : %w[mp mf me]).map { "@#{_1}_#{ident(name)} = {}\n" }
762
+ end
763
+ lines += @rules.keys.flat_map { round_memos(_1) }
764
+ "def init_memos\n#{indent(lines.join)}end\n"
765
+ end
766
+
767
+ # A cycle's leader numbers its rounds in @gp_; the cycle's other
768
+ # rules keep a memo for the round (see in_round).
769
+ def round_memos(name)
770
+ leader = @grammar.left_recursion.leader(name)
771
+ tables = if leader.nil? then []
772
+ elsif leader == name then leads_others?(name) ? %w[gp] : []
773
+ else @valued.include?(name) ? %w[rr rp rv rf re] : %w[rr rp rf re]
774
+ end
775
+ tables.map { "@#{_1}_#{ident(name)} = {}\n" }
776
+ end
777
+
778
+ # Ref: the rule's memo entry at p, computed the first time with
779
+ # fresh failure tracking, which is then merged into the caller's.
780
+ # No begin/ensure: Spinel allows only 64 of them active at once, and
781
+ # each level of nesting in the input would use one. Instead, entries
782
+ # being computed are kept on a stack, and a lookahead that catches a
783
+ # commit failure marks the ones it unwound through ABSENT (never
784
+ # deleted: Hash#delete is O(size) in Spinel), so the rule can be
785
+ # tried again there.
786
+ def ref_method(name, call)
787
+ leader = @grammar.left_recursion.leader(name)
788
+ return growing_ref_method(name, call) if leader == name
789
+
790
+ valued = @valued.include?(name)
791
+ id = ident(name)
792
+ hit = leader ? "unless q.nil? || q == ABSENT || q == IN_PROGRESS" : "unless q.nil? || q == ABSENT"
793
+ <<~RUBY
794
+ #{POSITION.chomp}
795
+ def ref_#{id}(p)
796
+ q = @mp_#{id}[p]
797
+ #{hit}
798
+ left_recursion(#{name.to_s.inspect}) if q == IN_PROGRESS
799
+ #{" @v_ref_#{id} = @mv_#{id}[p] if q != FAIL" if valued}
800
+ merge(@mf_#{id}[p], @me_#{id}[p])
801
+ return q
802
+ end#{"\n#{indent(in_round(name, leader, call)).chomp}" if leader}
803
+ @mp_#{id}[p] = IN_PROGRESS
804
+ @position = p
805
+ of = @furthest
806
+ oe = @expected
807
+ @furthest = p
808
+ @expected = NONE
809
+ @in_progress << @mp_#{id}
810
+ @in_progress_at << p
811
+ q = #{call.name}(p)
812
+ @in_progress.pop
813
+ @in_progress_at.pop
814
+ f = @furthest
815
+ ex = @expected
816
+ @furthest = of
817
+ @expected = oe
818
+ @mp_#{id}[p] = q
819
+ #{indent("if q != FAIL\n @v_ref_#{id} = #{call.value || "nil"}\n @mv_#{id}[p] = @v_ref_#{id}\nend").chomp if valued}
820
+ @mf_#{id}[p] = f
821
+ @me_#{id}[p] = ex
822
+ merge(f, ex)
823
+ q
824
+ end
825
+ RUBY
826
+ end
827
+
828
+ # The middle of Ref for a rule in a cycle led by another rule, as
829
+ # Birman::Context#memoize, after a final memo hit: while the leader
830
+ # grows at p (its @gp_ entry is the round's number, above 0), the
831
+ # rule is memoized there for the round only, in its @rr_ (the round
832
+ # each entry is for), @rp_, @rv_, @rf_ and @re_ tables, with fresh
833
+ # failure tracking merged into the caller's.
834
+ def in_round(name, leader, call)
835
+ valued = @valued.include?(name)
836
+ id = ident(name)
837
+ <<~RUBY
838
+ g = @gp_#{ident(leader)}[p]
839
+ unless g.nil? || g <= 0
840
+ r = @rr_#{id}[p]
841
+ unless r.nil? || r != g
842
+ q = @rp_#{id}[p]
843
+ #{" @v_ref_#{id} = @rv_#{id}[p] if q != FAIL" if valued}
844
+ merge(@rf_#{id}[p], @re_#{id}[p])
845
+ return q
846
+ end
847
+ @position = p
848
+ of = @furthest
849
+ oe = @expected
850
+ @furthest = p
851
+ @expected = NONE
852
+ q = #{call.name}(p)
853
+ #{indent("if q != FAIL\n @v_ref_#{id} = #{call.value || "nil"}\n @rv_#{id}[p] = @v_ref_#{id}\nend").chomp if valued}
854
+ f = @furthest
855
+ ex = @expected
856
+ @furthest = of
857
+ @expected = oe
858
+ @rr_#{id}[p] = g
859
+ @rp_#{id}[p] = q
860
+ @rf_#{id}[p] = f
861
+ @re_#{id}[p] = ex
862
+ merge(f, ex)
863
+ return q
864
+ end
865
+ RUBY
866
+ end
867
+
868
+ # Does name lead a cycle through other rules too?
869
+ def leads_others?(name)
870
+ analysis = @grammar.left_recursion
871
+ analysis.leader(name) == name && !analysis.cycle(name).one?
872
+ end
873
+
874
+ # Ref for a cycle's leader: seed-growing, as Birman::Context#grow.
875
+ # The memo entry starts as a failing seed, so the rule's own call is
876
+ # an ordinary memo hit, and each match that gets further replaces
877
+ # it. Each attempt starts with fresh failure tracking, so a commit
878
+ # failure reports only its own attempt's, and is then merged into
879
+ # the rule's, so every attempt's expectations are kept. A leader of
880
+ # other rules numbers each round (@round counts them across the
881
+ # parse) in its @gp_ table at p, and sets it to 0 when done. A
882
+ # lookahead that catches a commit failure mid-growth abandons the
883
+ # entry (ABSENT), seed, round number and all.
884
+ def growing_ref_method(name, call)
885
+ valued = @valued.include?(name)
886
+ id = ident(name)
887
+ marks = leads_others?(name)
888
+ <<~RUBY
889
+ #{POSITION.chomp}
890
+ def ref_#{id}(p)
891
+ q = @mp_#{id}[p]
892
+ unless q.nil? || q == ABSENT
893
+ #{" @v_ref_#{id} = @mv_#{id}[p] if q != FAIL" if valued}
894
+ merge(@mf_#{id}[p], @me_#{id}[p])
895
+ return q
896
+ end
897
+ @mp_#{id}[p] = FAIL
898
+ @mf_#{id}[p] = p
899
+ @me_#{id}[p] = NONE
900
+ @position = p
901
+ of = @furthest
902
+ oe = @expected
903
+ @furthest = p
904
+ @expected = NONE
905
+ @in_progress << @mp_#{id}
906
+ @in_progress_at << p#{"\n @in_progress << @gp_#{id}\n @in_progress_at << p" if marks}
907
+ while true#{"\n @round += 1\n @gp_#{id}[p] = @round" if marks}
908
+ af = @furthest
909
+ ae = @expected
910
+ @furthest = p
911
+ @expected = NONE
912
+ q = #{call.name}(p)
913
+ f = @furthest
914
+ ex = @expected
915
+ @furthest = af
916
+ @expected = ae
917
+ merge(f, ex)
918
+ break if q <= @mp_#{id}[p]
919
+
920
+ @mp_#{id}[p] = q
921
+ #{" @mv_#{id}[p] = #{call.value || "nil"}" if valued}
922
+ end
923
+ @in_progress.pop
924
+ @in_progress_at.pop#{"\n @gp_#{id}[p] = 0\n @in_progress.pop\n @in_progress_at.pop" if marks}
925
+ q = @mp_#{id}[p]
926
+ #{" @v_ref_#{id} = @mv_#{id}[p] if q != FAIL" if valued}
927
+ f = @furthest
928
+ ex = @expected
929
+ @furthest = of
930
+ @expected = oe
931
+ @mf_#{id}[p] = f
932
+ @me_#{id}[p] = ex
933
+ merge(f, ex)
934
+ q
935
+ end
936
+ RUBY
937
+ end
938
+ end
939
+ end
940
+ end