parsanol 1.3.15-aarch64-linux → 1.3.16-aarch64-linux
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 +4 -4
- data/lib/parsanol/atoms/base.rb +8 -13
- data/lib/parsanol/native.rb +14 -1
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +193 -15
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47f7be12e8a16ae18e07d621fc699bd49d55367c4e17a6f43c3ba1a00f12bfc7
|
|
4
|
+
data.tar.gz: 3877f2d6c8d48495efd798abf72bd96bbc7e6fc643cf6de78ca0a6b589445b37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6e19f01bc6b7fdb3168fbe5ad272182e590e685c1a12d3fa97c7064791f825efdd72cca971aa385af6ecb443a6e28e3052168d7bf6aaccf458ae4cfaac37cd1
|
|
7
|
+
data.tar.gz: 66e2a1c85f1667ac98451fc943b25e9697ed7c227854ccf0e31ce214a68b56918a6f68cc1a21688a29b4bb360cafcc2711e12e493313c63c9e96908baff07e3e
|
data/lib/parsanol/atoms/base.rb
CHANGED
|
@@ -35,20 +35,15 @@ module Parsanol
|
|
|
35
35
|
# Compiled-VM fast path: String input, full-consumption semantics.
|
|
36
36
|
# On failure (or unsupported grammar / budget exhaustion) fall
|
|
37
37
|
# through to the interpreter, which also produces the exact
|
|
38
|
-
# cause-tree diagnostics.
|
|
38
|
+
# cause-tree diagnostics. run_for memoizes heavy-backtracking
|
|
39
|
+
# grammars transparently.
|
|
39
40
|
if must_consume_all && source.is_a?(String) &&
|
|
40
41
|
(program = VM.program_for(self))
|
|
41
|
-
result = VM.
|
|
42
|
+
result = VM.run_for(self, program, source, true)
|
|
42
43
|
if result == VM::BAIL
|
|
43
44
|
# Internal bail: fall back to the interpreter and skip the VM
|
|
44
45
|
# for this grammar from now on.
|
|
45
46
|
VM.disable_for!(self)
|
|
46
|
-
elsif result.first == :heavy
|
|
47
|
-
# Succeeded but burned >1/8 of the step budget —
|
|
48
|
-
# heavy-backtracking grammar; the memoizing interpreter is
|
|
49
|
-
# the better engine from now on.
|
|
50
|
-
VM.disable_for!(self)
|
|
51
|
-
return finalize_result(result[1])
|
|
52
47
|
elsif result.first
|
|
53
48
|
return finalize_result(result[1])
|
|
54
49
|
end
|
|
@@ -185,6 +180,11 @@ module Parsanol
|
|
|
185
180
|
# Alias for ok (legacy compatibility)
|
|
186
181
|
alias succ ok
|
|
187
182
|
|
|
183
|
+
# Finalizes result by flattening.
|
|
184
|
+
def finalize_result(value)
|
|
185
|
+
flatten(value)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
188
|
private
|
|
189
189
|
|
|
190
190
|
# Converts raw input to Source if needed.
|
|
@@ -223,11 +223,6 @@ module Parsanol
|
|
|
223
223
|
|
|
224
224
|
cause.raise
|
|
225
225
|
end
|
|
226
|
-
|
|
227
|
-
# Finalizes result by flattening.
|
|
228
|
-
def finalize_result(value)
|
|
229
|
-
flatten(value)
|
|
230
|
-
end
|
|
231
226
|
end
|
|
232
227
|
end
|
|
233
228
|
end
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -194,7 +194,20 @@ module Parsanol
|
|
|
194
194
|
# pre-serialized JSON grammars the native message is wrapped in a
|
|
195
195
|
# Parsanol::ParseFailed directly.
|
|
196
196
|
def raise_native_parse_error(error, grammar, input)
|
|
197
|
-
|
|
197
|
+
if grammar.respond_to?(:parse)
|
|
198
|
+
# One interpreter pass with the error reporter attached covers
|
|
199
|
+
# both jobs: a success means the native backend could not
|
|
200
|
+
# express the grammar (recover the tree), a failure raises the
|
|
201
|
+
# parslet-compatible cause tree. The native backend has
|
|
202
|
+
# already failed, so a separate plain attempt first would be a
|
|
203
|
+
# wasted parse.
|
|
204
|
+
reporter = Parsanol::ErrorReporter::Tree.new
|
|
205
|
+
source = Parsanol::Source.new(input)
|
|
206
|
+
success, value = grammar.run_with_context(source, reporter, true)
|
|
207
|
+
return grammar.finalize_result(value) if success
|
|
208
|
+
|
|
209
|
+
value.raise
|
|
210
|
+
end
|
|
198
211
|
|
|
199
212
|
source = Parsanol::Source.new(input)
|
|
200
213
|
cause = Parsanol::Cause.new(error.message, source, source.bytepos)
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -71,9 +71,18 @@ module Parsanol
|
|
|
71
71
|
# and may skip the VM for this grammar afterwards. A clean
|
|
72
72
|
# [false, nil] means the input genuinely does not parse.
|
|
73
73
|
BAIL = Object.new.freeze
|
|
74
|
+
# Step budget exhausted on the naive pass — internal to #run, which
|
|
75
|
+
# retries once with packrat memoization before giving up.
|
|
76
|
+
BUDGET = Object.new.freeze
|
|
77
|
+
# Memo marker for a subroutine currently being evaluated at a given
|
|
78
|
+
# position. Re-entry (left recursion) fails that path instead of
|
|
79
|
+
# looping forever.
|
|
80
|
+
PENDING = Object.new.freeze
|
|
74
81
|
|
|
75
82
|
STEP_BUDGET_FACTOR = 200
|
|
76
83
|
STEP_BUDGET_FIXED = 10_000
|
|
84
|
+
MEMO_BUDGET_FACTOR = 4_096
|
|
85
|
+
MEMO_BUDGET_CELLS_FACTOR = 16
|
|
77
86
|
|
|
78
87
|
class << self
|
|
79
88
|
@programs = {}
|
|
@@ -108,13 +117,27 @@ module Parsanol
|
|
|
108
117
|
|
|
109
118
|
def clear_program_cache
|
|
110
119
|
@programs&.clear
|
|
120
|
+
@heavy&.clear
|
|
111
121
|
end
|
|
112
122
|
|
|
113
123
|
# Compiles the grammar rooted at +atom+. Returns the flat program
|
|
114
124
|
# Array, or nil when the grammar uses unsupported atoms.
|
|
115
125
|
def compile(atom)
|
|
126
|
+
program = compile_with_inline(atom, true)
|
|
127
|
+
# Inlining duplicates every non-recursive rule body at each
|
|
128
|
+
# reference site; grammars with many cross-referencing rules
|
|
129
|
+
# explode past the program cap even though the atom tree is
|
|
130
|
+
# modest. Recompiling with inlining off (each rule a CALL/RET
|
|
131
|
+
# subroutine) keeps the program O(atoms) at a small per-rule
|
|
132
|
+
# dispatch cost — far better than refusing the grammar outright.
|
|
133
|
+
return program unless program == :oversize
|
|
134
|
+
|
|
135
|
+
compile_with_inline(atom, false)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def compile_with_inline(atom, inline)
|
|
116
139
|
root = atom.is_a?(Parsanol::Parser) ? atom.root : atom
|
|
117
|
-
compiler = Compiler.new
|
|
140
|
+
compiler = Compiler.new(inline: inline)
|
|
118
141
|
return nil unless compiler.compile_atom(root, true)
|
|
119
142
|
|
|
120
143
|
# HALT terminates the MAIN program; subroutines follow it so the
|
|
@@ -128,8 +151,58 @@ module Parsanol
|
|
|
128
151
|
# Executes a compiled program. Returns [true, value] on success;
|
|
129
152
|
# [false, nil] on failure or budget exhaustion — callers fall back
|
|
130
153
|
# to the interpreter, which reproduces exact diagnostics.
|
|
154
|
+
#
|
|
155
|
+
# Heavy-backtracking grammars get a second chance: the naive pass
|
|
156
|
+
# runs without memoization (zero overhead on the happy path), and
|
|
157
|
+
# only if it blows the step budget does a memoized pass run. The
|
|
158
|
+
# memo converts repeated rule+position work into single entries, so
|
|
159
|
+
# grammars that the interpreter needed (slow) memoization for now
|
|
160
|
+
# run at VM speed.
|
|
131
161
|
def run(program, input, consume_all)
|
|
132
|
-
Executor.new(program, input, consume_all)
|
|
162
|
+
executor = Executor.new(program, input, consume_all)
|
|
163
|
+
result = executor.execute
|
|
164
|
+
if result.equal?(BUDGET)
|
|
165
|
+
executor = Executor.new(program, input, consume_all, memoize: true)
|
|
166
|
+
result = executor.execute
|
|
167
|
+
result = BAIL if result.equal?(BUDGET)
|
|
168
|
+
end
|
|
169
|
+
result
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Grammar-aware entry used by Base#parse. Grammars that have shown
|
|
173
|
+
# heavy-backtracking behavior (budget bust, or a success that
|
|
174
|
+
# burned the step-density threshold) memoize from the first
|
|
175
|
+
# instruction on subsequent parses, skipping the doomed naive
|
|
176
|
+
# pass; a heavy success is retried memoized in the same call so
|
|
177
|
+
# the VM keeps the grammar instead of surrendering it to the
|
|
178
|
+
# interpreter.
|
|
179
|
+
def run_for(atom, program, input, consume_all)
|
|
180
|
+
root = atom.is_a?(Parsanol::Parser) ? atom.root : atom
|
|
181
|
+
id = root.object_id
|
|
182
|
+
heavy = (@heavy ||= {})
|
|
183
|
+
if heavy[id]
|
|
184
|
+
result = Executor.new(program, input, consume_all,
|
|
185
|
+
memoize: true).execute
|
|
186
|
+
return BAIL if result.equal?(BUDGET)
|
|
187
|
+
|
|
188
|
+
return result
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
result = Executor.new(program, input, consume_all).execute
|
|
192
|
+
if result.equal?(BUDGET) ||
|
|
193
|
+
(result.is_a?(Array) && result.first == :heavy)
|
|
194
|
+
heavy[id] = true
|
|
195
|
+
memo_result = Executor.new(program, input, consume_all,
|
|
196
|
+
memoize: true).execute
|
|
197
|
+
# A heavy naive success already answered; the memo pass only
|
|
198
|
+
# proves the grammar stays VM-viable (BUDGET would bail to the
|
|
199
|
+
# interpreter). When the naive pass never answered, the memo
|
|
200
|
+
# pass's outcome is the result.
|
|
201
|
+
return BAIL if memo_result.equal?(BUDGET)
|
|
202
|
+
|
|
203
|
+
result = memo_result if result.equal?(BUDGET)
|
|
204
|
+
end
|
|
205
|
+
result
|
|
133
206
|
end
|
|
134
207
|
|
|
135
208
|
# Converts the VM value tree into the interpreter's value tree.
|
|
@@ -159,8 +232,9 @@ module Parsanol
|
|
|
159
232
|
# as subroutines, keyed by [body object_id, consume_all] so spine
|
|
160
233
|
# sites (consume_all=true) and inner sites stay semantically distinct.
|
|
161
234
|
class Compiler
|
|
162
|
-
def initialize
|
|
235
|
+
def initialize(inline: true)
|
|
163
236
|
@ops = []
|
|
237
|
+
@inline = inline
|
|
164
238
|
@subs = {} # [obj_id, flag] => pc or :pending
|
|
165
239
|
@pending = {} # [obj_id, flag] => [[call_idx, body], ...]
|
|
166
240
|
end
|
|
@@ -168,7 +242,7 @@ module Parsanol
|
|
|
168
242
|
attr_reader :ops
|
|
169
243
|
|
|
170
244
|
def to_program
|
|
171
|
-
return
|
|
245
|
+
return :oversize if @ops.size > MAX_PROGRAM
|
|
172
246
|
|
|
173
247
|
@ops.flatten!(1)
|
|
174
248
|
end
|
|
@@ -244,7 +318,21 @@ module Parsanol
|
|
|
244
318
|
|
|
245
319
|
emit(DROP)
|
|
246
320
|
self
|
|
247
|
-
|
|
321
|
+
when Parsanol::Atoms::Scope
|
|
322
|
+
# Scope only affects capture state; the result tree is the
|
|
323
|
+
# inner atom's tree unchanged. The block is pure DSL evaluated
|
|
324
|
+
# once at compile time. Any Capture/Dynamic inside still fails
|
|
325
|
+
# compilation on its own, so passthrough cannot diverge.
|
|
326
|
+
inner = begin
|
|
327
|
+
atom.block.call
|
|
328
|
+
rescue StandardError
|
|
329
|
+
nil
|
|
330
|
+
end
|
|
331
|
+
return nil if inner.nil?
|
|
332
|
+
return nil unless compile_atom(inner, consume_all)
|
|
333
|
+
|
|
334
|
+
self
|
|
335
|
+
# Dynamic, Capture, Cut, Custom, Infix, unknown
|
|
248
336
|
end
|
|
249
337
|
end
|
|
250
338
|
|
|
@@ -401,7 +489,7 @@ module Parsanol
|
|
|
401
489
|
# round trip entirely. A body currently being compiled (directly
|
|
402
490
|
# or indirectly) is recursive and must stay a subroutine.
|
|
403
491
|
# rubocop:disable Lint/HashCompareByIdentity -- object_id keys; would otherwise pin every atom alive
|
|
404
|
-
|
|
492
|
+
if @inline && !(@compiling ||= {})[body.object_id]
|
|
405
493
|
@compiling[body.object_id] = true
|
|
406
494
|
# rubocop:enable Lint/HashCompareByIdentity
|
|
407
495
|
result = compile_atom(body, consume_all)
|
|
@@ -456,10 +544,11 @@ module Parsanol
|
|
|
456
544
|
# (with its adaptive memoization) is the better engine; #run reports
|
|
457
545
|
# this via :heavy so Base#parse can skip the VM for the grammar.
|
|
458
546
|
class Executor
|
|
459
|
-
def initialize(program, input, consume_all)
|
|
547
|
+
def initialize(program, input, consume_all, memoize: false)
|
|
460
548
|
@ops = program
|
|
461
549
|
@input = input
|
|
462
550
|
@consume_all = consume_all
|
|
551
|
+
@memoize = memoize
|
|
463
552
|
end
|
|
464
553
|
|
|
465
554
|
def execute # rubocop:disable Metrics/MethodLength, Metrics/BlockLength, Metrics/BlockNesting -- single dispatch loop; hot path
|
|
@@ -471,7 +560,15 @@ module Parsanol
|
|
|
471
560
|
# interpreter's Source#matches? semantics exactly.
|
|
472
561
|
scanner = StringScanner.new(input)
|
|
473
562
|
n = bytes.size
|
|
474
|
-
|
|
563
|
+
# The memoized pass is polynomial (bounded by distinct rule ×
|
|
564
|
+
# position pairs); give it room proportional to grammar + input so
|
|
565
|
+
# packrat coverage of failing inputs isn't cut short.
|
|
566
|
+
budget = if @memoize
|
|
567
|
+
(MEMO_BUDGET_CELLS_FACTOR * ops.size) +
|
|
568
|
+
(MEMO_BUDGET_FACTOR * n) + STEP_BUDGET_FIXED
|
|
569
|
+
else
|
|
570
|
+
(STEP_BUDGET_FACTOR * n) + STEP_BUDGET_FIXED
|
|
571
|
+
end
|
|
475
572
|
steps = 0
|
|
476
573
|
|
|
477
574
|
pc = 0
|
|
@@ -481,13 +578,24 @@ module Parsanol
|
|
|
481
578
|
frames = []
|
|
482
579
|
calls = []
|
|
483
580
|
|
|
581
|
+
memo = @memoize ? {} : nil
|
|
582
|
+
memo_stack = []
|
|
583
|
+
trace = ENV["VM_TRACE"] ? [] : nil
|
|
584
|
+
|
|
484
585
|
# rubocop:disable-next Metrics/BlockLength -- the dispatch loop IS execute
|
|
485
586
|
loop do
|
|
486
587
|
steps += 1
|
|
487
|
-
|
|
588
|
+
if steps > budget
|
|
589
|
+
if trace
|
|
590
|
+
warn "BUDGET at steps=#{steps} pc=#{pc} pos=#{pos} rstack=#{rstack.size} bt=#{bt.size} calls=#{calls.size}"
|
|
591
|
+
warn "trace tail: #{trace.last(30).inspect}"
|
|
592
|
+
end
|
|
593
|
+
return BUDGET
|
|
594
|
+
end
|
|
595
|
+
trace << [pc, pos] if trace
|
|
488
596
|
|
|
489
597
|
if pc == FAIL
|
|
490
|
-
pc, pos = unwind(bt, rstack, frames, calls)
|
|
598
|
+
pc, pos = unwind(bt, rstack, frames, calls, memo, memo_stack)
|
|
491
599
|
return [false, nil] if pc == :fail
|
|
492
600
|
return BAIL if pc == :bail
|
|
493
601
|
|
|
@@ -579,11 +687,45 @@ module Parsanol
|
|
|
579
687
|
rstack << VM::NamedValue.new(ops[pc + 1], rstack.pop)
|
|
580
688
|
pc += 4
|
|
581
689
|
when CALL
|
|
690
|
+
sub = ops[pc + 1]
|
|
691
|
+
if memo
|
|
692
|
+
table = (memo[sub] ||= {})
|
|
693
|
+
hit = table[pos]
|
|
694
|
+
if hit
|
|
695
|
+
if hit.equal?(PENDING) || hit.equal?(:fail)
|
|
696
|
+
# :fail — the body already failed at this position;
|
|
697
|
+
# replay the failure without re-running it.
|
|
698
|
+
# PENDING — re-entry at the same rule+position (left
|
|
699
|
+
# recursion); the interpreter loops forever here, so
|
|
700
|
+
# failing this path keeps the VM bounded.
|
|
701
|
+
pc = FAIL
|
|
702
|
+
next
|
|
703
|
+
end
|
|
704
|
+
# Memo hit: replay the subroutine's stack effect without
|
|
705
|
+
# executing it. Values are immutable (packed spans,
|
|
706
|
+
# NamedValue, frozen-shape Arrays), so sharing is safe.
|
|
707
|
+
rstack.concat(hit[1])
|
|
708
|
+
pos = hit[0]
|
|
709
|
+
pc += 4
|
|
710
|
+
next
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
table[pos] = PENDING
|
|
714
|
+
# Lockstep with calls: unwind rolls both back by clen.
|
|
715
|
+
memo_stack << sub << pos << rstack.size
|
|
716
|
+
end
|
|
582
717
|
calls << (pc + 4)
|
|
583
|
-
pc =
|
|
718
|
+
pc = sub
|
|
584
719
|
when RET
|
|
585
720
|
pc = calls.pop
|
|
586
721
|
return BAIL if pc.nil?
|
|
722
|
+
|
|
723
|
+
if memo
|
|
724
|
+
mlen = memo_stack.pop
|
|
725
|
+
mpos = memo_stack.pop
|
|
726
|
+
msub = memo_stack.pop
|
|
727
|
+
memo[msub][mpos] = [pos, rstack[mlen..]]
|
|
728
|
+
end
|
|
587
729
|
when LOOK_POS
|
|
588
730
|
bt << ops[pc + 1] << pos << rstack.size << frames.size << calls.size << K_LOOK_FAIL
|
|
589
731
|
pc += 4
|
|
@@ -735,6 +877,8 @@ module Parsanol
|
|
|
735
877
|
values << [:maybe]
|
|
736
878
|
end
|
|
737
879
|
when RUN_RE
|
|
880
|
+
elem_start = pos
|
|
881
|
+
count = 0
|
|
738
882
|
loop do
|
|
739
883
|
b = bytes[pos]
|
|
740
884
|
m = if kb && b && b < 128
|
|
@@ -746,12 +890,19 @@ module Parsanol
|
|
|
746
890
|
end
|
|
747
891
|
break unless m
|
|
748
892
|
|
|
893
|
+
count += 1
|
|
749
894
|
w = CHAR_WIDTH[b]
|
|
750
895
|
w = char_width_slow(input, pos) if w.zero?
|
|
751
896
|
values << ((pos << 20) | w)
|
|
752
897
|
pos += w
|
|
753
898
|
end
|
|
899
|
+
if count < kc
|
|
900
|
+
pos = elem_start
|
|
901
|
+
failed = true
|
|
902
|
+
end
|
|
754
903
|
when RUN_STR
|
|
904
|
+
elem_start = pos
|
|
905
|
+
count = 0
|
|
755
906
|
loop do
|
|
756
907
|
i = 0
|
|
757
908
|
ln = kb
|
|
@@ -765,9 +916,14 @@ module Parsanol
|
|
|
765
916
|
end
|
|
766
917
|
break unless m
|
|
767
918
|
|
|
919
|
+
count += 1
|
|
768
920
|
values << ((pos << 20) | ln)
|
|
769
921
|
pos += ln
|
|
770
922
|
end
|
|
923
|
+
if count < kc
|
|
924
|
+
pos = elem_start
|
|
925
|
+
failed = true
|
|
926
|
+
end
|
|
771
927
|
end
|
|
772
928
|
break if failed
|
|
773
929
|
|
|
@@ -878,6 +1034,10 @@ module Parsanol
|
|
|
878
1034
|
value = VM.materialize(rstack[0], input)
|
|
879
1035
|
# More than ~100 steps per input byte means heavy
|
|
880
1036
|
# backtracking; the memoizing interpreter wins there.
|
|
1037
|
+
if @memoize
|
|
1038
|
+
return [true, value]
|
|
1039
|
+
end
|
|
1040
|
+
|
|
881
1041
|
return steps > (n << 9) + 1000 ? [:heavy, value] : [true, value]
|
|
882
1042
|
else
|
|
883
1043
|
return BAIL
|
|
@@ -890,7 +1050,7 @@ module Parsanol
|
|
|
890
1050
|
# Unwind one backtrack entry. Returns [pc, pos] or :fail. Repetition
|
|
891
1051
|
# entries with count >= min complete their tagged array and jump to
|
|
892
1052
|
# the exit continuation with the last iteration end position.
|
|
893
|
-
def unwind(bt, rstack, frames, calls) # rubocop:disable Naming/MethodParameterName -- stack register names
|
|
1053
|
+
def unwind(bt, rstack, frames, calls, memo, memo_stack) # rubocop:disable Naming/MethodParameterName -- stack register names
|
|
894
1054
|
return :fail if bt.empty?
|
|
895
1055
|
|
|
896
1056
|
# Everything above this entry belongs to constructs being
|
|
@@ -910,6 +1070,24 @@ module Parsanol
|
|
|
910
1070
|
|
|
911
1071
|
rstack.slice!(rlen..) if rstack.size > rlen
|
|
912
1072
|
calls.slice!(clen..) if calls.size > clen
|
|
1073
|
+
if memo
|
|
1074
|
+
# memo_stack carries three slots per live call, so the frames of
|
|
1075
|
+
# every call abandoned by this unwind are the tail above 3*clen.
|
|
1076
|
+
# Every abandoned frame is a call whose body failed — cache that
|
|
1077
|
+
# failure too (full packrat memoization), so later explorations
|
|
1078
|
+
# that reach the same rule+position fail immediately instead of
|
|
1079
|
+
# re-running the body. Grammar inputs that don't parse explore
|
|
1080
|
+
# every alternative; without failure entries those explorations
|
|
1081
|
+
# repeat exponentially and blow the step budget.
|
|
1082
|
+
floor = clen * 3
|
|
1083
|
+
while memo_stack.size > floor
|
|
1084
|
+
memo_stack.pop
|
|
1085
|
+
mpos = memo_stack.pop
|
|
1086
|
+
msub = memo_stack.pop
|
|
1087
|
+
table = memo[msub]
|
|
1088
|
+
table[mpos] = :fail if table
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
913
1091
|
|
|
914
1092
|
case kind
|
|
915
1093
|
when K_ALT
|
|
@@ -930,7 +1108,7 @@ module Parsanol
|
|
|
930
1108
|
check_full = @ops[epc + 2]
|
|
931
1109
|
if check_full == 1 && end_pos != @input.bytesize
|
|
932
1110
|
frames.slice!(cbase..)
|
|
933
|
-
unwind(bt, rstack, frames, calls)
|
|
1111
|
+
unwind(bt, rstack, frames, calls, memo, memo_stack)
|
|
934
1112
|
else
|
|
935
1113
|
values = rstack.pop(rstack.size - rbase)
|
|
936
1114
|
values.unshift(tag)
|
|
@@ -940,11 +1118,11 @@ module Parsanol
|
|
|
940
1118
|
end
|
|
941
1119
|
else
|
|
942
1120
|
frames.slice!(cbase..)
|
|
943
|
-
unwind(bt, rstack, frames, calls)
|
|
1121
|
+
unwind(bt, rstack, frames, calls, memo, memo_stack)
|
|
944
1122
|
end
|
|
945
1123
|
when K_LOOK_FAIL
|
|
946
1124
|
frames.slice!(cbase..) if frames.size > cbase
|
|
947
|
-
unwind(bt, rstack, frames, calls)
|
|
1125
|
+
unwind(bt, rstack, frames, calls, memo, memo_stack)
|
|
948
1126
|
when K_NEG
|
|
949
1127
|
# Negative lookahead body failed -> lookahead succeeds.
|
|
950
1128
|
frames.slice!(cbase..) if frames.size > cbase
|