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,758 @@
1
+ # The runtime every parser made by Grammar.to_ruby ships with: the
2
+ # generator copies everything after the require below into the parser's
3
+ # namespace, so two generated parsers never share (or clash over) one.
4
+ # It's written in a deliberately small Ruby subset so Spinel can compile
5
+ # it: plain classes (no Data, no Struct), integer byte positions read
6
+ # with getbyte, no keyword arguments, no blocks, no metaprogramming. Inline RBS type
7
+ # comments give Spinel types it can't infer from empty literals; CRuby
8
+ # ignores them.
9
+ #
10
+ # Generated code calls this as follows:
11
+ #
12
+ # - Every match method takes a byte position and returns the byte
13
+ # position where its match ended, or FAIL (-1). Values go in instance
14
+ # variables, one per rule, so each one keeps a single type.
15
+ # - fail_at, merge and the save/restore of @furthest/@expected around a
16
+ # rule call do what Birman::Context's do, so the parser reports the
17
+ # same errors as the interpreter.
18
+
19
+ require "strscan"
20
+
21
+ module BirmanRT
22
+ FAIL = -1
23
+ IN_PROGRESS = -2
24
+ # A memo entry abandoned by a commit failure unwinding through it (a
25
+ # lookahead caught the commit). Written instead of deleting the key:
26
+ # Hash#delete on Spinel's Integer-keyed Hash is O(size).
27
+ ABSENT = -3
28
+
29
+ # Bignum literals: see BirmanRT.to_i and based_to_i.
30
+ TEN19 = 10_000_000_000_000_000_000
31
+ TWO64 = 18_446_744_073_709_551_616
32
+
33
+ # A Failure's expectations before anything has failed. It is never
34
+ # appended to: fail_at replaces an empty list rather than adding to it.
35
+ # Typed through Array.new: a bare [].freeze trips a Spinel pin bug.
36
+ NONE = Array.new(0, "").freeze
37
+
38
+ class Committed < StandardError
39
+ attr_reader :pos, :expected
40
+
41
+ #: (Integer, Array[String]) -> void
42
+ def initialize(pos, expected)
43
+ @pos = pos
44
+ @expected = expected
45
+ super("committed failure")
46
+ end
47
+ end
48
+
49
+ # Lookaheads nested past the parser's LOOKAHEAD_DEPTH: Parser#parse
50
+ # turns it into a Failure, as it does SystemStackError.
51
+ class TooDeep < StandardError
52
+ end
53
+
54
+ # Base class of every generated node, so a rule whose value is one of
55
+ # several node classes still has a single static type.
56
+ class Node
57
+ attr_reader :span
58
+
59
+ # Overridden by every node class: inspect, plus each node's span as
60
+ # [byte_from,byte_to], for the diff tests.
61
+ def dump
62
+ ""
63
+ end
64
+ end
65
+
66
+ class Span
67
+ attr_reader :input, :byte_from, :byte_to
68
+
69
+ def initialize(input, byte_from, byte_to)
70
+ @input = input
71
+ @byte_from = byte_from
72
+ @byte_to = byte_to
73
+ end
74
+
75
+ def text
76
+ @input.byteslice(@byte_from, @byte_to - @byte_from)
77
+ end
78
+
79
+ # Character offsets, as Birman::Span's.
80
+ def from
81
+ BirmanRT.char_count(@input, 0, @byte_from)
82
+ end
83
+
84
+ def to
85
+ BirmanRT.char_count(@input, 0, @byte_to)
86
+ end
87
+
88
+ def line
89
+ n = 1
90
+ i = 0
91
+ while i < @byte_from
92
+ n += 1 if @input.getbyte(i) == 10
93
+ i += 1
94
+ end
95
+ n
96
+ end
97
+
98
+ def column
99
+ i = @byte_from
100
+ i -= 1 while i.positive? && @input.getbyte(i - 1) != 10
101
+ BirmanRT.char_count(@input, i, @byte_from) + 1
102
+ end
103
+
104
+ def to_s
105
+ "line #{line}, column #{column}"
106
+ end
107
+
108
+ def inspect
109
+ "#<Birman::Span #{line}:#{column} #{text.inspect}>"
110
+ end
111
+ end
112
+
113
+ class Success
114
+ attr_reader :value
115
+
116
+ def initialize(value)
117
+ @value = value
118
+ end
119
+
120
+ def success?
121
+ true
122
+ end
123
+ end
124
+
125
+ # pos counts characters, as Birman::Failure's does; byte_pos is kept
126
+ # so line, column and found don't have to convert back.
127
+ class Failure
128
+ attr_reader :input, :pos, :byte_pos, :expected, :committed, :reason
129
+
130
+ #: (String, Integer, Array[String], bool, String?) -> void
131
+ def initialize(input, byte_pos, expected, committed, reason)
132
+ @input = input
133
+ @byte_pos = byte_pos
134
+ @pos = BirmanRT.char_count(input, 0, byte_pos)
135
+ @expected = expected
136
+ @committed = committed
137
+ @reason = reason
138
+ end
139
+
140
+ def success?
141
+ false
142
+ end
143
+
144
+ def line
145
+ n = 1
146
+ i = 0
147
+ while i < @byte_pos
148
+ n += 1 if @input.getbyte(i) == 10
149
+ i += 1
150
+ end
151
+ n
152
+ end
153
+
154
+ def column
155
+ BirmanRT.char_count(@input, line_start, @byte_pos) + 1
156
+ end
157
+
158
+ # The character at the failure, inspected, as Birman::Failure#found.
159
+ def found
160
+ return "end of input" if @byte_pos >= @input.bytesize
161
+
162
+ width = BirmanRT.single_byte?(@input) ? 1 : BirmanRT.utf8_width(@input, @byte_pos)
163
+ width = 1 if width.zero?
164
+ @input.byteslice(@byte_pos, width).inspect
165
+ end
166
+
167
+ # Birman::ErrorFormatter's wording.
168
+ def message
169
+ problem = @reason || "expected #{expected_list}, found #{found}"
170
+ "line #{line}, column #{column}: #{problem}\n #{source_line}\n #{caret_indent}^"
171
+ end
172
+
173
+ private
174
+
175
+ def line_start
176
+ i = @byte_pos
177
+ i -= 1 while i.positive? && @input.getbyte(i - 1) != 10
178
+ i
179
+ end
180
+
181
+ def source_line
182
+ start = line_start
183
+ stop = start
184
+ stop += 1 while stop < @input.bytesize && @input.getbyte(stop) != 10
185
+ stop -= 1 if stop > start && @input.getbyte(stop - 1) == 13
186
+ @input.byteslice(start, stop - start)
187
+ end
188
+
189
+ # Spaces up to the caret, except tabs stay tabs.
190
+ def caret_indent
191
+ out = +""
192
+ i = line_start
193
+ while i < @byte_pos
194
+ b = @input.getbyte(i)
195
+ out << (b == 9 ? "\t" : " ") unless BirmanRT.continuation?(b) && !BirmanRT.single_byte?(@input)
196
+ i += 1
197
+ end
198
+ out
199
+ end
200
+
201
+ def expected_list
202
+ return "nothing" if @expected.empty?
203
+ return @expected[0] if @expected.size == 1
204
+
205
+ "#{@expected[0, @expected.size - 1].join(", ")} or #{@expected[@expected.size - 1]}"
206
+ end
207
+ end
208
+
209
+ # Binary (and US-ASCII) input has a character per byte; otherwise
210
+ # it's UTF-8 (Parser#initialize refuses anything else).
211
+ def self.single_byte?(input)
212
+ encoding = input.encoding
213
+ encoding == Encoding::BINARY || encoding == Encoding::US_ASCII
214
+ end
215
+
216
+ def self.continuation?(b)
217
+ b >= 0x80 && b < 0xC0
218
+ end
219
+
220
+ def self.char_width(b)
221
+ return 1 if b < 0x80
222
+ return 2 if b < 0xE0
223
+ return 3 if b < 0xF0
224
+
225
+ 4
226
+ end
227
+
228
+ # The first byte of input that doesn't start a valid character, or
229
+ # FAIL: Birman.invalid_byte, by hand. Binary input is always valid,
230
+ # and US-ASCII is only ASCII.
231
+ def self.invalid_byte(input)
232
+ return FAIL if input.encoding == Encoding::BINARY
233
+
234
+ ascii = single_byte?(input)
235
+ i = 0
236
+ while i < input.bytesize
237
+ w = utf8_width(input, i)
238
+ # US-ASCII has no multibyte characters.
239
+ return i if w.zero? || (ascii && w > 1)
240
+
241
+ i += w
242
+ end
243
+ FAIL
244
+ end
245
+
246
+ # The width of the UTF-8 character at byte i, or 0 if the bytes there
247
+ # aren't one, as String#valid_encoding? sees it: the second byte's
248
+ # range rules out overlong forms, surrogates and code points past
249
+ # U+10FFFF, and a truncated character is invalid at its first byte.
250
+ def self.utf8_width(input, i)
251
+ b = input.getbyte(i)
252
+ return 1 if b < 0x80
253
+ return 0 if b < 0xC2 || b > 0xF4
254
+
255
+ w = char_width(b)
256
+ return 0 if i + w > input.bytesize
257
+
258
+ lo = 0x80
259
+ hi = 0xBF
260
+ if b == 0xE0
261
+ lo = 0xA0
262
+ elsif b == 0xF0
263
+ lo = 0x90
264
+ elsif b == 0xED
265
+ hi = 0x9F
266
+ elsif b == 0xF4
267
+ hi = 0x8F
268
+ end
269
+ c = input.getbyte(i + 1)
270
+ return 0 if c < lo || c > hi
271
+
272
+ k = 2
273
+ while k < w
274
+ return 0 unless continuation?(input.getbyte(i + k))
275
+
276
+ k += 1
277
+ end
278
+ w
279
+ end
280
+
281
+ # Characters in input's bytes from...to: in UTF-8, every byte that
282
+ # isn't a continuation byte starts one.
283
+ def self.char_count(input, from, to)
284
+ return to - from if single_byte?(input)
285
+
286
+ n = 0
287
+ i = from
288
+ while i < to
289
+ n += 1 unless continuation?(input.getbyte(i))
290
+ i += 1
291
+ end
292
+ n
293
+ end
294
+
295
+ # A value as the diff tests compare it: like inspect, but each node
296
+ # also shows its span as [byte_from,byte_to], and a string that isn't
297
+ # valid UTF-8 (a lone \ud83d escape in JSON, say) shows its bytes,
298
+ # since Spinel's String#inspect writes those differently from CRuby's.
299
+ def self.dump(value)
300
+ case value
301
+ when Node then value.dump
302
+ when Array then dump_array(value)
303
+ when Hash then dump_hash(value)
304
+ when String then value.valid_encoding? ? value.inspect : value.b.inspect
305
+ else value.inspect
306
+ end
307
+ end
308
+
309
+ def self.dump_array(values)
310
+ out = +"["
311
+ i = 0
312
+ while i < values.size
313
+ out << ", " if i.positive?
314
+ out << dump(values[i])
315
+ i += 1
316
+ end
317
+ out << "]"
318
+ end
319
+
320
+ def self.dump_hash(hash)
321
+ out = +"{"
322
+ keys = hash.keys
323
+ i = 0
324
+ while i < keys.size
325
+ out << ", " if i.positive?
326
+ out << dump(keys[i]) << " => " << dump(hash[keys[i]])
327
+ i += 1
328
+ end
329
+ out << "}"
330
+ end
331
+
332
+ # String#to_i, Bignums included. Spinel has no Bignum arithmetic by
333
+ # default (Integer overflow raises), and --int-overflow=promote would
334
+ # box every position in the parser. Arithmetic with a Bignum literal
335
+ # operand does promote, so longer numerals are built 18 digits at a
336
+ # time, scaled through one.
337
+ # A String only: the generator routes nothing else here, and anything
338
+ # else would be read wrongly rather than fail.
339
+ def self.to_i(text)
340
+ raise TypeError, "BirmanRT.to_i reads a String, not #{text.class}" unless text.is_a?(String)
341
+ return text.to_i if text.bytesize <= 18
342
+
343
+ i = 0
344
+ i += 1 while i < text.bytesize && space?(text.getbyte(i))
345
+ negative = i < text.bytesize && text.getbyte(i) == 45
346
+ i += 1 if i < text.bytesize && (text.getbyte(i) == 45 || text.getbyte(i) == 43)
347
+ n = digits_to_i(integer_digits(text, i))
348
+ negative ? -n : n
349
+ end
350
+
351
+ # The digits of the numeral starting at i, as to_i reads it: single
352
+ # underscores between digits are skipped, and anything else ends it.
353
+ def self.integer_digits(text, i)
354
+ out = +""
355
+ while i < text.bytesize
356
+ b = text.getbyte(i)
357
+ if digit?(b)
358
+ out << text.byteslice(i, 1)
359
+ elsif !(b == 95 && !out.empty? && i + 1 < text.bytesize && digit?(text.getbyte(i + 1)))
360
+ break
361
+ end
362
+ i += 1
363
+ end
364
+ out
365
+ end
366
+
367
+ def self.digits_to_i(digits)
368
+ n = 0
369
+ i = 0
370
+ while i < digits.bytesize
371
+ k = digits.bytesize - i
372
+ k = 18 if k > 18
373
+ n = (n * (TEN19 / (10**(19 - k)))) + digits.byteslice(i, k).to_i
374
+ i += k
375
+ end
376
+ n
377
+ end
378
+
379
+ # Kernel#Integer(value, base), for Grammar#large_integer in copied
380
+ # actions: Spinel's Integer raises RangeError past 63 bits, so long
381
+ # strings and big Floats are read here. Bases are Integers, or Floats
382
+ # truncated; Rational and NaN bases differ from Kernel's (TypeError,
383
+ # FloatDomainError).
384
+ #
385
+ # Each branch compiles whatever the callers pass: Spinel's is_a? doesn't
386
+ # narrow value's type, so a branch for another type would call a method
387
+ # value doesn't have. to_s and to_f are no-ops where they run, and keep
388
+ # the typed helpers' arguments typed.
389
+ def self.to_integer(value, base)
390
+ base = integer_base(base)
391
+ if value.is_a?(String)
392
+ string_to_integer(value.to_s, base)
393
+ elsif value.is_a?(Float) && base == 0
394
+ float_to_i(value.to_f)
395
+ else
396
+ base == 0 ? Integer(value) : Integer(value, base)
397
+ end
398
+ end
399
+
400
+ # Integer's syntax: whitespace around, a sign, a prefix (with base 0:
401
+ # 0x 0b 0o 0d or 0; with base 2, 8, 10 or 16, its own), digits with
402
+ # single underscores. 12 characters in any base are under 36**12,
403
+ # within 63 bits, so those go to Integer itself.
404
+ #: (String, Integer) -> untyped
405
+ def self.string_to_integer(text, base)
406
+ unless text.bytesize > 12 && (base == 0 || (base >= 2 && base <= 36))
407
+ return base == 0 ? Integer(text) : Integer(text, base)
408
+ end
409
+
410
+ base, digits, negative = integer_syntax(text, base)
411
+ raise ArgumentError, "invalid value for Integer(): #{text.inspect}" if digits.nil?
412
+
413
+ n = base == 10 ? digits_to_i(digits) : based_to_i(digits, base)
414
+ negative ? -n : n
415
+ end
416
+
417
+ # Past 2**62, Floats are whole numbers; Spinel's Float#to_i raises
418
+ # RangeError past 63 bits ("Bignum promotion pending").
419
+ FLOAT_CHUNK = 4_611_686_018_427_387_904.0
420
+
421
+ # Integer(f), exactly. A Float past 2**62 is halved below 2**61 (exact:
422
+ # only its exponent changes), converted natively, then doubled back
423
+ # through Bignum literals, up to 62 bits at a time. Each step is at
424
+ # least 2 bits (so at least twice halved), since 2**(64 - 1) overflows.
425
+ #: (Float) -> untyped
426
+ def self.float_to_i(f)
427
+ raise FloatDomainError, "NaN" if f.nan?
428
+ raise FloatDomainError, f > 0 ? "Infinity" : "-Infinity" if f.infinite?
429
+ return f.to_i if f.abs < FLOAT_CHUNK
430
+
431
+ e = 0
432
+ while f.abs >= FLOAT_CHUNK / 2
433
+ f /= 2
434
+ e += 1
435
+ end
436
+ n = f.to_i
437
+ while e > 0
438
+ k = e > 62 ? 62 : e
439
+ k -= 1 if e - k == 1
440
+ n = n * (TWO64 / (2**(64 - k)))
441
+ e -= k
442
+ end
443
+ n
444
+ end
445
+
446
+ # Kernel#Integer's base argument: an Integer, or a Float truncated (a
447
+ # Rational, which Kernel takes, raises TypeError here).
448
+ def self.integer_base(base)
449
+ return base if base.is_a?(Integer)
450
+ return base.to_i if base.is_a?(Float)
451
+ raise TypeError, "no implicit conversion from nil to integer" if base.nil?
452
+
453
+ raise TypeError, "no implicit conversion of #{base == true || base == false ? base : base.class} into Integer"
454
+ end
455
+
456
+ # [base, digits without underscores, negative], or nil if text isn't
457
+ # an integer in Integer's syntax for base (0: from its prefix).
458
+ def self.integer_syntax(text, base)
459
+ i = 0
460
+ i += 1 while i < text.bytesize && space?(text.getbyte(i))
461
+ negative = i < text.bytesize && text.getbyte(i) == 45
462
+ i += 1 if i < text.bytesize && (text.getbyte(i) == 45 || text.getbyte(i) == 43)
463
+ if text.getbyte(i) == 48 && i + 1 < text.bytesize
464
+ b = text.getbyte(i + 1)
465
+ if base == 0
466
+ base = prefix_base(b)
467
+ i += 2 unless base == 8 && !letter_prefix?(b) # a bare 0 is an octal digit
468
+ elsif base == prefix_base(b) && (base != 8 || letter_prefix?(b))
469
+ i += 2
470
+ end
471
+ end
472
+ base = 10 if base == 0
473
+ digits = based_digits(text, i, base)
474
+ return nil if digits.nil?
475
+
476
+ [base, digits, negative]
477
+ end
478
+
479
+ # The base a "0" prefix followed by b gives: 0x 0b 0o 0d, else octal.
480
+ def self.prefix_base(b)
481
+ return 16 if b == 120 || b == 88
482
+ return 2 if b == 98 || b == 66
483
+ return 10 if b == 100 || b == 68
484
+
485
+ 8
486
+ end
487
+
488
+ def self.letter_prefix?(b)
489
+ b == 111 || b == 79
490
+ end
491
+
492
+ # The digits from i to the end (bar trailing whitespace), in base, with
493
+ # single underscores between digits dropped; nil if anything else.
494
+ def self.based_digits(text, i, base)
495
+ out = +""
496
+ while i < text.bytesize
497
+ b = text.getbyte(i)
498
+ if digit_in?(b, base)
499
+ out << text.byteslice(i, 1)
500
+ elsif !(b == 95 && !out.empty? && i + 1 < text.bytesize && digit_in?(text.getbyte(i + 1), base))
501
+ break
502
+ end
503
+ i += 1
504
+ end
505
+ i += 1 while i < text.bytesize && space?(text.getbyte(i))
506
+ out.empty? || i < text.bytesize ? nil : out
507
+ end
508
+
509
+ # Digits are 0-9 then a-z (either case) for 10 to 35.
510
+ def self.digit_in?(b, base)
511
+ return b >= 48 && b < 48 + base if base <= 10
512
+
513
+ digit?(b) || (b >= 97 && b < 87 + base) || (b >= 65 && b < 55 + base)
514
+ end
515
+
516
+ # Under 2**62, so a chunk of digits is an Integer in Spinel.
517
+ CHUNK_LIMIT = 4_611_686_018_427_387_904
518
+
519
+ # digits in base, as many at a time as fit under CHUNK_LIMIT, scaled
520
+ # through a Bignum literal as digits_to_i does. Typed, since its
521
+ # arguments come out of integer_syntax's Array: boxed, Spinel's
522
+ # arithmetic would raise on overflow rather than promote.
523
+ #: (String, Integer) -> untyped
524
+ def self.based_to_i(digits, base)
525
+ size = 1
526
+ size += 1 while base**size <= CHUNK_LIMIT / base
527
+ n = 0
528
+ i = 0
529
+ while i < digits.bytesize
530
+ k = digits.bytesize - i
531
+ k = size if k > size
532
+ n = (n * ((TWO64 * (base**k)) / TWO64)) + digits.byteslice(i, k).to_i(base)
533
+ i += k
534
+ end
535
+ n
536
+ end
537
+
538
+ # Regexp classes, ASCII-only as Ruby's are: \s \d \w.
539
+ def self.space?(b)
540
+ b == 32 || (b >= 9 && b <= 13)
541
+ end
542
+
543
+ def self.digit?(b)
544
+ b >= 48 && b <= 57
545
+ end
546
+
547
+ def self.word?(b)
548
+ digit?(b) || alpha?(b) || b == 95
549
+ end
550
+
551
+ def self.alpha?(b)
552
+ (b >= 65 && b <= 90) || (b >= 97 && b <= 122)
553
+ end
554
+
555
+ # The parser state shared by every generated parser, the counterpart
556
+ # of Birman::Context.
557
+ class Parser
558
+ # @rbs @expected: Array[String]
559
+ # @rbs @in_progress_at: Array[Integer]
560
+
561
+ attr_reader :input
562
+
563
+ def initialize(input)
564
+ unless input.encoding == Encoding::UTF_8 || BirmanRT.single_byte?(input)
565
+ raise ArgumentError, "the generated parser reads UTF-8 or binary input, not #{input.encoding}"
566
+ end
567
+
568
+ @input = input
569
+ @len = input.bytesize
570
+ reset
571
+ end
572
+
573
+ private
574
+
575
+ # Per-parse state, fresh for every parse (as Birman's Context is), so
576
+ # a parse that ended in a commit failure, with rules still marked in
577
+ # progress, leaves nothing behind. Generated parsers also clear their
578
+ # memos (init_memos).
579
+ def reset
580
+ @furthest = 0
581
+ @expected = NONE
582
+ @token_ends = {}
583
+ @position = 0
584
+ @in_progress = []
585
+ @in_progress_at = []
586
+ @round = 0 # growth rounds of left-recursive cycles so far
587
+ @lookaheads = 0
588
+ end
589
+
590
+ #: (Integer, String) -> Integer
591
+ def fail_at(pos, what)
592
+ if pos > @furthest
593
+ @furthest = pos
594
+ @expected = [what]
595
+ elsif pos == @furthest
596
+ if @expected.empty?
597
+ @expected = [what]
598
+ else
599
+ @expected << what
600
+ end
601
+ end
602
+ FAIL
603
+ end
604
+
605
+ # Skips expectations already recorded at pos, as Context#merge does.
606
+ #: (Integer, Array[String]) -> void
607
+ def merge(pos, expected)
608
+ i = 0
609
+ while i < expected.size
610
+ what = expected[i]
611
+ fail_at(pos, what) unless pos == @furthest && @expected.include?(what)
612
+ i += 1
613
+ end
614
+ end
615
+
616
+ #: (Integer, String) -> void
617
+ def commit_failure(pos, described)
618
+ fail_at(pos, described) if @furthest < pos || @expected.empty?
619
+ raise Committed.new(@furthest, @expected.dup)
620
+ end
621
+
622
+ # Context#ignored_after / #token_end.
623
+ #: (Integer, Integer) -> void
624
+ def ignored_after(token_end, pos)
625
+ @token_ends[pos] = token_end if pos > token_end
626
+ end
627
+
628
+ #: (Integer) -> Integer
629
+ def token_end(pos)
630
+ e = @token_ends[pos]
631
+ e.nil? ? pos : e
632
+ end
633
+
634
+ # Where a node's span ends: the last token's end, with trailing
635
+ # whitespace and NULs trimmed (String#rstrip).
636
+ #: (Integer, Integer) -> Integer
637
+ def span_end(from, to)
638
+ to = token_end(to)
639
+ while to > from
640
+ b = @input.getbyte(to - 1)
641
+ break unless b.zero? || BirmanRT.space?(b)
642
+
643
+ to -= 1
644
+ end
645
+ to
646
+ end
647
+
648
+ #: (Integer, Integer) -> Span
649
+ def span(from, to)
650
+ Span.new(@input, from, span_end(from, to))
651
+ end
652
+
653
+ #: (Integer, Array[String], bool) -> Failure
654
+ def failure(byte_pos, expected, committed)
655
+ Failure.new(@input, byte_pos, expected.uniq, committed, nil)
656
+ end
657
+
658
+ #: (Integer, String) -> Failure
659
+ def failure_because(byte_pos, reason)
660
+ Failure.new(@input, byte_pos, [], false, reason)
661
+ end
662
+
663
+ # Grammar#parse's check, made before a regexp can raise on the bad
664
+ # byte: invalid input fails at the first one.
665
+ #: (Integer) -> Failure
666
+ def invalid_input(byte_pos)
667
+ failure_because(byte_pos, BirmanRT.single_byte?(@input) ? "invalid US-ASCII" : "invalid UTF-8")
668
+ end
669
+
670
+ # The end of a Label, after its isolated inner match: keep the
671
+ # deeper error, or say "expected <name>" if it got nowhere.
672
+ #: (Integer, Integer, Integer, Array[String], String) -> Integer
673
+ def label_end(p, q, of, oe, name)
674
+ f = @furthest
675
+ ex = @expected
676
+ @furthest = of
677
+ @expected = oe
678
+ if f == p && (q == FAIL || (q == p && !ex.empty?))
679
+ fail_at(p, name)
680
+ else
681
+ merge(f, ex)
682
+ end
683
+ q
684
+ end
685
+
686
+ # The end of a Check, after its isolated inner match: ok says the
687
+ # inner match failed or the predicate accepted it. A rejection drops
688
+ # what the inner match recorded and says "expected <name>" at p.
689
+ #: (Integer, Integer, bool, Integer, Array[String], String) -> Integer
690
+ def check_end(p, q, ok, of, oe, name)
691
+ f = @furthest
692
+ ex = @expected
693
+ @furthest = of
694
+ @expected = oe
695
+ return fail_at(p, name) unless ok
696
+
697
+ merge(f, ex)
698
+ q
699
+ end
700
+
701
+ # The end of a positive lookahead (Context#peek?), after its isolated
702
+ # inner match g: whether it matched. If not, the failure is recorded
703
+ # at p, as what it expected there or as the lookahead itself.
704
+ #: (Integer, Integer, Integer, Array[String], String) -> bool
705
+ def peek_end(p, g, of, oe, described)
706
+ f = @furthest
707
+ ex = @expected
708
+ @furthest = of
709
+ @expected = oe
710
+ return true if g != FAIL
711
+
712
+ if f == p && !ex.empty?
713
+ merge(p, ex)
714
+ else
715
+ fail_at(p, described)
716
+ end
717
+ false
718
+ end
719
+
720
+ # Grammar#large_integer, for copied actions: Kernel#Integer, Bignums
721
+ # included.
722
+ def large_integer(value, base = 0)
723
+ BirmanRT.to_integer(value, base)
724
+ end
725
+
726
+ # Exactly text's bytes at p?
727
+ #: (Integer, String) -> bool
728
+ def bytes_at?(p, text)
729
+ n = text.bytesize
730
+ return false if p + n > @len
731
+
732
+ i = 0
733
+ while i < n
734
+ return false if @input.getbyte(p + i) != text.getbyte(i)
735
+
736
+ i += 1
737
+ end
738
+ true
739
+ end
740
+
741
+ # A lookahead caught a commit failure: every rule entered since it
742
+ # began (depth entries ago) was unwound mid-match. Their memo entries
743
+ # still say IN_PROGRESS, or hold a left-recursive rule's seed; mark
744
+ # them ABSENT so they can be tried again.
745
+ #: (Integer) -> void
746
+ def abandon(depth)
747
+ while @in_progress_at.size > depth
748
+ memo = @in_progress.pop
749
+ p = @in_progress_at.pop
750
+ memo[p] = ABSENT
751
+ end
752
+ end
753
+
754
+ def left_recursion(name)
755
+ raise ArgumentError, "left recursion in rule :#{name}: it can reach itself without consuming input"
756
+ end
757
+ end
758
+ end