parsanol 1.3.63 → 1.3.65
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/state.rb +151 -0
- data/lib/parsanol/atoms.rb +1 -0
- data/lib/parsanol/native/serializer.rb +36 -0
- data/lib/parsanol/parg/artifact.rb +6 -1
- data/lib/parsanol/parg/compiler.rb +46 -1
- data/lib/parsanol/parg/document.rb +4 -1
- data/lib/parsanol/parg/parser.rb +52 -1
- data/lib/parsanol/parser.rb +5 -0
- data/lib/parsanol/scope.rb +8 -0
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +9 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a40f12d1d7a5fc67b1ef3255e9d9c5f4c38fd6a8fb266995974beec982728ca1
|
|
4
|
+
data.tar.gz: 297a013726a6e9ff9e81b385a8074e45afd5e9a22739e28852219f35cd1ad3a4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f171865bebc41146db262fcbdf5f05bdd3daf0e424cf29eaf26351408ca5d35db904057dfd64f0b57bd6a18190e48596bfd2af21ebfbf75d79a7fbc421c2bd1
|
|
7
|
+
data.tar.gz: d69f48dcc19e090e4b7c0fa880f142d9a76ff95e1aa6d8f73803558902c66621abbc7f28d24ca683ecadab7a5f6855fb88913dfecf05e5699a12780e98722ff7
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Parsanol
|
|
4
|
+
module Atoms
|
|
5
|
+
# PARG runtime-state atoms (parsanol-ruby#129): per-parse mutable
|
|
6
|
+
# slots living in the parse context's capture store, so capture
|
|
7
|
+
# rollback, scope discipline and the dispatch semantics are the
|
|
8
|
+
# proven dynamic-callback ones. All four are pure data — they
|
|
9
|
+
# serialize as structured tags and rebuild identically in any
|
|
10
|
+
# runtime that implements the state machine.
|
|
11
|
+
class StateBase < Base
|
|
12
|
+
def cached?
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Slots and captures may live in a parent scope frame (an earlier
|
|
19
|
+
# element's frame); Scope#[] only reads the active one.
|
|
20
|
+
def lookup(context, key)
|
|
21
|
+
context.captures.fetch(key)
|
|
22
|
+
rescue Parsanol::Scope::UndefinedVariable
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def mark_unsafe(context)
|
|
27
|
+
# The atom reads mutable parse state (captures), so no enclosing
|
|
28
|
+
# composite result may be memoized — replaying it would skip the
|
|
29
|
+
# re-evaluation (same contract as Atoms::Dynamic).
|
|
30
|
+
context.mark_cache_unsafe!
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Writes a state slot. Two forms: a literal value (always succeeds,
|
|
35
|
+
# consumes nothing) or an inline expression — the expression matches
|
|
36
|
+
# at the position and the slot stores its consumed text (the
|
|
37
|
+
# block_open pattern: match the delimiter, remember it).
|
|
38
|
+
class StateSet < StateBase
|
|
39
|
+
def initialize(slot, value: nil, atom: nil)
|
|
40
|
+
super()
|
|
41
|
+
@slot = slot
|
|
42
|
+
@value = value
|
|
43
|
+
@atom = atom
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :slot, :value, :atom
|
|
47
|
+
|
|
48
|
+
def try(source, context, _consume_all)
|
|
49
|
+
mark_unsafe(context)
|
|
50
|
+
if @atom.nil?
|
|
51
|
+
context.captures[@slot] = @value
|
|
52
|
+
return [true, ""]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
start = source.bytepos
|
|
56
|
+
outcome = @atom.apply(source, context, false)
|
|
57
|
+
return outcome unless outcome.first
|
|
58
|
+
|
|
59
|
+
span = source.input.byteslice(start, source.bytepos - start)
|
|
60
|
+
context.captures[@slot] = span
|
|
61
|
+
outcome
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_s_inner(_prec)
|
|
65
|
+
"set #{@slot}=#{@value || '…'}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Matches the state slot's current value verbatim at the position —
|
|
70
|
+
# the block-delimiter comparison ("exactly what was captured above").
|
|
71
|
+
class StateMatch < StateBase
|
|
72
|
+
def initialize(slot)
|
|
73
|
+
super()
|
|
74
|
+
@slot = slot
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attr_reader :slot
|
|
78
|
+
|
|
79
|
+
def try(source, context, _consume_all)
|
|
80
|
+
mark_unsafe(context)
|
|
81
|
+
expected = lookup(context, @slot)
|
|
82
|
+
return [false, nil] if expected.nil?
|
|
83
|
+
|
|
84
|
+
actual = source.peek(expected.length)
|
|
85
|
+
return [false, nil] if actual != expected
|
|
86
|
+
|
|
87
|
+
source.bytepos += expected.length
|
|
88
|
+
[true, actual]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_s_inner(_prec)
|
|
92
|
+
"state(#{@slot})"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Dispatches on the slot's current value to a rule reference. The
|
|
97
|
+
# resolver maps a rule name to its atom lazily (rules may still be
|
|
98
|
+
# under construction when this atom is built). A nil arm fails the
|
|
99
|
+
# atom — the native dynamic-callback parity.
|
|
100
|
+
class StateSwitch < StateBase
|
|
101
|
+
def initialize(slot, arms, default, resolver)
|
|
102
|
+
super()
|
|
103
|
+
@slot = slot
|
|
104
|
+
@arms = arms
|
|
105
|
+
@default = default
|
|
106
|
+
@resolver = resolver
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
attr_reader :slot, :arms, :default
|
|
110
|
+
|
|
111
|
+
def try(source, context, consume_all)
|
|
112
|
+
mark_unsafe(context)
|
|
113
|
+
current = lookup(context, @slot)
|
|
114
|
+
rule_name = @arms[current] || @default
|
|
115
|
+
return [false, nil] if rule_name.nil?
|
|
116
|
+
|
|
117
|
+
@resolver.call(rule_name).apply(source, context, consume_all)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def to_s_inner(_prec)
|
|
121
|
+
"switch(#{@slot})"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# A foreign atom: a class resolved from the artifact's customs
|
|
126
|
+
# binding at compile time. The instance runs as-is on the Ruby
|
|
127
|
+
# engine; the serialized tag carries the binding so any runtime can
|
|
128
|
+
# decide support loudly.
|
|
129
|
+
class CustomRef < Base
|
|
130
|
+
def initialize(name, klass)
|
|
131
|
+
super()
|
|
132
|
+
@name = name
|
|
133
|
+
@atom = klass.new
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
attr_reader :name
|
|
137
|
+
|
|
138
|
+
def cached?
|
|
139
|
+
false
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def try(source, context, consume_all)
|
|
143
|
+
@atom.try(source, context, consume_all)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def to_s_inner(_prec)
|
|
147
|
+
"custom(#{@name})"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
data/lib/parsanol/atoms.rb
CHANGED
|
@@ -38,6 +38,7 @@ module Parsanol
|
|
|
38
38
|
require "parsanol/atoms/entity"
|
|
39
39
|
require "parsanol/atoms/capture"
|
|
40
40
|
require "parsanol/atoms/dynamic"
|
|
41
|
+
require "parsanol/atoms/state"
|
|
41
42
|
require "parsanol/atoms/scope"
|
|
42
43
|
require "parsanol/atoms/infix"
|
|
43
44
|
# Load visitor pattern (must be after all atom classes)
|
|
@@ -68,6 +68,14 @@ module Parsanol
|
|
|
68
68
|
serialize_capture(atom)
|
|
69
69
|
when Parsanol::Atoms::Scope
|
|
70
70
|
serialize_scope(atom)
|
|
71
|
+
when Parsanol::Atoms::StateSet
|
|
72
|
+
serialize_state_set(atom)
|
|
73
|
+
when Parsanol::Atoms::StateMatch
|
|
74
|
+
serialize_state_match(atom)
|
|
75
|
+
when Parsanol::Atoms::StateSwitch
|
|
76
|
+
serialize_state_switch(atom)
|
|
77
|
+
when Parsanol::Atoms::CustomRef
|
|
78
|
+
serialize_custom_ref(atom)
|
|
71
79
|
when Parsanol::Atoms::Dynamic
|
|
72
80
|
serialize_dynamic(atom)
|
|
73
81
|
when Parsanol::Atoms::Ignored
|
|
@@ -219,6 +227,34 @@ module Parsanol
|
|
|
219
227
|
}
|
|
220
228
|
end
|
|
221
229
|
|
|
230
|
+
def serialize_state_set(atom)
|
|
231
|
+
payload = { "slot" => atom.slot.to_s }
|
|
232
|
+
if atom.atom
|
|
233
|
+
payload["expr"] = serialize_atom(atom.atom)
|
|
234
|
+
else
|
|
235
|
+
payload["value"] = atom.value
|
|
236
|
+
end
|
|
237
|
+
{ "StateSet" => payload }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def serialize_state_match(atom)
|
|
241
|
+
{ "StateMatch" => { "slot" => atom.slot.to_s } }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def serialize_state_switch(atom)
|
|
245
|
+
{
|
|
246
|
+
"StateSwitch" => {
|
|
247
|
+
"slot" => atom.slot.to_s,
|
|
248
|
+
"arms" => atom.arms.transform_keys(&:to_s),
|
|
249
|
+
"default" => atom.default,
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def serialize_custom_ref(atom)
|
|
255
|
+
{ "CustomRef" => { "name" => atom.name } }
|
|
256
|
+
end
|
|
257
|
+
|
|
222
258
|
def serialize_dynamic(atom)
|
|
223
259
|
# Dynamic evaluates a Ruby block at parse time.
|
|
224
260
|
# Register the block and get a callback ID for FFI.
|
|
@@ -57,10 +57,15 @@ module Parsanol
|
|
|
57
57
|
# path is a straight register-and-run — no Ruby recompilation. The
|
|
58
58
|
# Ruby atom runtime (mode: :ruby) remains available explicitly, and
|
|
59
59
|
# serves as the fallback on platforms without the extension.
|
|
60
|
+
#
|
|
61
|
+
# Dynamic artifacts (runtime state, custom atoms — #129) route to
|
|
62
|
+
# the Ruby engine: the envelope carries the PARG source precisely
|
|
63
|
+
# so a Ruby runtime can rebuild the live atom tree; the closures
|
|
64
|
+
# are not portable JSON.
|
|
60
65
|
def parse(entry_name, input, mode: :native)
|
|
61
66
|
case mode
|
|
62
67
|
when :native
|
|
63
|
-
if Native.available?
|
|
68
|
+
if !envelope["dynamic"] && Native.available?
|
|
64
69
|
return Native.parse(JSON.generate(entry(entry_name).fetch("grammar")), input)
|
|
65
70
|
end
|
|
66
71
|
|
|
@@ -50,6 +50,7 @@ module Parsanol
|
|
|
50
50
|
@atom_cache = {}
|
|
51
51
|
@first_cache = {}.compare_by_identity
|
|
52
52
|
@ref_stack = []
|
|
53
|
+
@dynamic = false
|
|
53
54
|
@tables = {}
|
|
54
55
|
@warnings = []
|
|
55
56
|
end
|
|
@@ -67,6 +68,16 @@ module Parsanol
|
|
|
67
68
|
Result.new(@atom_cache, @warnings, envelope)
|
|
68
69
|
end
|
|
69
70
|
|
|
71
|
+
def resolve_custom(name)
|
|
72
|
+
class_name = @document.customs.fetch(name)
|
|
73
|
+
klass = Object.const_get(class_name)
|
|
74
|
+
Atoms::CustomRef.new(name, klass)
|
|
75
|
+
rescue NameError
|
|
76
|
+
raise CompileError,
|
|
77
|
+
"custom atom #{name.inspect} cannot resolve #{class_name.inspect} " \
|
|
78
|
+
"(bind the class before compiling, or fix the custom declaration)"
|
|
79
|
+
end
|
|
80
|
+
|
|
70
81
|
def atom_for(name)
|
|
71
82
|
@atom_cache[name] ||= build_atom(fetch_rule(name))
|
|
72
83
|
end
|
|
@@ -157,6 +168,13 @@ module Parsanol
|
|
|
157
168
|
end
|
|
158
169
|
|
|
159
170
|
def fetch_rule(name)
|
|
171
|
+
# Customs are opaque terminals for the analysis passes (first
|
|
172
|
+
# sets, left-recursion): a literal stand-in keeps them
|
|
173
|
+
# non-nullable with a singleton first set.
|
|
174
|
+
if !@document.rules.key?(name) && @document.customs.key?(name)
|
|
175
|
+
return Node.new(:lit, "\u0001", false)
|
|
176
|
+
end
|
|
177
|
+
|
|
160
178
|
@document.rules[name] || raise(CompileError, "unknown rule #{name.inspect}")
|
|
161
179
|
end
|
|
162
180
|
|
|
@@ -194,7 +212,31 @@ module Parsanol
|
|
|
194
212
|
end
|
|
195
213
|
|
|
196
214
|
Atoms::Named.new(build_atom(node.b), node.a.to_sym)
|
|
197
|
-
when :
|
|
215
|
+
when :set
|
|
216
|
+
@dynamic = true
|
|
217
|
+
slot = node.a.to_sym
|
|
218
|
+
value = node.b
|
|
219
|
+
if value.is_a?(String)
|
|
220
|
+
Atoms::StateSet.new(slot, value: value)
|
|
221
|
+
else
|
|
222
|
+
Atoms::StateSet.new(slot, atom: build_atom(value))
|
|
223
|
+
end
|
|
224
|
+
when :state_match
|
|
225
|
+
@dynamic = true
|
|
226
|
+
Atoms::StateMatch.new(node.a.to_sym)
|
|
227
|
+
when :switch
|
|
228
|
+
@dynamic = true
|
|
229
|
+
slot = node.a.to_sym
|
|
230
|
+
arms = node.b.to_h { |match, rule| [match, rule] }
|
|
231
|
+
default = arms.delete(:default)
|
|
232
|
+
Atoms::StateSwitch.new(slot, arms, default, method(:atom_for))
|
|
233
|
+
when :ref
|
|
234
|
+
if @document.customs.key?(node.a)
|
|
235
|
+
@dynamic = true
|
|
236
|
+
resolve_custom(node.a)
|
|
237
|
+
else
|
|
238
|
+
Atoms::Entity.new(node.a) { atom_for(node.a) }
|
|
239
|
+
end
|
|
198
240
|
when :table
|
|
199
241
|
values = table_column(node.a, node.b)
|
|
200
242
|
Atoms::Alternative.new(*values.map { |value| Atoms::Str.new(value) })
|
|
@@ -430,6 +472,9 @@ module Parsanol
|
|
|
430
472
|
"docs" => @document.docs,
|
|
431
473
|
"source" => @document.source,
|
|
432
474
|
}
|
|
475
|
+
envelope["customs"] = @document.customs unless @document.customs.empty?
|
|
476
|
+
envelope["states"] = @document.states unless @document.states.empty?
|
|
477
|
+
envelope["dynamic"] = true if @dynamic
|
|
433
478
|
envelope["checksum"] = Compiler.checksum(envelope)
|
|
434
479
|
envelope
|
|
435
480
|
end
|
|
@@ -10,7 +10,8 @@ module Parsanol
|
|
|
10
10
|
Test = Struct.new(:entry, :kind, :input, :expect)
|
|
11
11
|
|
|
12
12
|
attr_accessor :grammar_name, :version, :source, :uses
|
|
13
|
-
attr_reader :rules, :bindings, :preprocess, :entries, :docs, :tests, :render, :derive
|
|
13
|
+
attr_reader :rules, :bindings, :preprocess, :entries, :docs, :tests, :render, :derive,
|
|
14
|
+
:customs, :states
|
|
14
15
|
# Entries authored in this file (pre-import); nil-entry tests resolve
|
|
15
16
|
# against these first.
|
|
16
17
|
attr_accessor :own_entries
|
|
@@ -29,6 +30,8 @@ module Parsanol
|
|
|
29
30
|
@tests = []
|
|
30
31
|
@uses = []
|
|
31
32
|
@own_entries = nil
|
|
33
|
+
@customs = {}
|
|
34
|
+
@states = {}
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
def validate!
|
data/lib/parsanol/parg/parser.rb
CHANGED
|
@@ -18,8 +18,9 @@ module Parsanol
|
|
|
18
18
|
|
|
19
19
|
IN_BLOCK_SECTIONS = %w[entry bindings preprocess test].freeze
|
|
20
20
|
|
|
21
|
+
STATE_KEYWORDS = %w[custom state].freeze
|
|
21
22
|
KEYWORDS = %w[grammar as alt from_table column bindings render derive
|
|
22
|
-
preprocess entry table_lookup].freeze
|
|
23
|
+
preprocess entry table_lookup custom state set switch].freeze
|
|
23
24
|
|
|
24
25
|
def initialize(text)
|
|
25
26
|
@tokens = Lexer.new(text).tokens
|
|
@@ -160,6 +161,19 @@ module Parsanol
|
|
|
160
161
|
skip_newlines
|
|
161
162
|
next
|
|
162
163
|
end
|
|
164
|
+
if peek&.type == :ident && STATE_KEYWORDS.include?(peek.value)
|
|
165
|
+
keyword = advance.value
|
|
166
|
+
slot = ident.value
|
|
167
|
+
if keyword == "custom"
|
|
168
|
+
punct("=")
|
|
169
|
+
document.customs[slot] = unquote(expect(:str).value)
|
|
170
|
+
else
|
|
171
|
+
punct(":")
|
|
172
|
+
document.states[slot] = ident.value
|
|
173
|
+
end
|
|
174
|
+
skip_newlines
|
|
175
|
+
next
|
|
176
|
+
end
|
|
163
177
|
name = rule_name
|
|
164
178
|
document.docs[name] = docs.join("\n") unless docs.empty?
|
|
165
179
|
punct("=")
|
|
@@ -274,6 +288,43 @@ module Parsanol
|
|
|
274
288
|
|
|
275
289
|
def parse_ident(token)
|
|
276
290
|
case token.value
|
|
291
|
+
when "state"
|
|
292
|
+
Node.new(:state_match, ident.value)
|
|
293
|
+
when "set"
|
|
294
|
+
slot = ident.value
|
|
295
|
+
punct("=")
|
|
296
|
+
# A string writes the literal; a bare name copies the matched
|
|
297
|
+
# text of a capture (the block-delimiter pattern: set the slot
|
|
298
|
+
# from what the opening delimiter matched).
|
|
299
|
+
value = if peek&.type == :str
|
|
300
|
+
unquote(expect(:str).value)
|
|
301
|
+
else
|
|
302
|
+
punct("(")
|
|
303
|
+
inner = parse_choice
|
|
304
|
+
punct(")")
|
|
305
|
+
inner
|
|
306
|
+
end
|
|
307
|
+
Node.new(:set, slot, value)
|
|
308
|
+
when "switch"
|
|
309
|
+
slot = ident.value
|
|
310
|
+
punct("{")
|
|
311
|
+
skip_newlines
|
|
312
|
+
arms = []
|
|
313
|
+
until peek&.type == :punct && peek.value == "}"
|
|
314
|
+
match = if peek&.type == :ident && peek.value == "_"
|
|
315
|
+
advance
|
|
316
|
+
:default
|
|
317
|
+
else
|
|
318
|
+
unquote(expect(:str).value)
|
|
319
|
+
end
|
|
320
|
+
arrow = advance
|
|
321
|
+
raise ParseError, "expected \"->\", got #{arrow&.type}:#{arrow&.value}" unless arrow&.value == "->"
|
|
322
|
+
|
|
323
|
+
arms << [match, ident.value]
|
|
324
|
+
skip_newlines
|
|
325
|
+
end
|
|
326
|
+
punct("}")
|
|
327
|
+
Node.new(:switch, slot, arms)
|
|
277
328
|
when "alt"
|
|
278
329
|
ident("from_table")
|
|
279
330
|
table = unquote(expect(:str).value)
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -238,6 +238,11 @@ module Parsanol
|
|
|
238
238
|
# The degradation warning prints once per reason per process — not
|
|
239
239
|
# per parser instance and not per parse.
|
|
240
240
|
def native_expressible?
|
|
241
|
+
# The ext attaches its methods lazily (available? triggers the load);
|
|
242
|
+
# without this guard a first-touch call NoMethodErrors inside the
|
|
243
|
+
# serializer (Dynamic.register -> Native.register_callback).
|
|
244
|
+
return false unless Parsanol::Native.available?
|
|
245
|
+
|
|
241
246
|
Parsanol::Native::Parser.grammar_handle(root)
|
|
242
247
|
true
|
|
243
248
|
rescue Parsanol::Native::UnsupportedGrammar => e
|
data/lib/parsanol/scope.rb
CHANGED
|
@@ -84,6 +84,14 @@ module Parsanol
|
|
|
84
84
|
@active_frame.fetch(key)
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
# Chain-aware read: the active frame, then its parents ([] is
|
|
88
|
+
# deliberately active-frame-only for capture-scope discipline; state
|
|
89
|
+
# atoms read across frames — a slot may have been written inside an
|
|
90
|
+
# earlier sibling element's frame).
|
|
91
|
+
def fetch(key)
|
|
92
|
+
@active_frame.fetch(key)
|
|
93
|
+
end
|
|
94
|
+
|
|
87
95
|
# Chain-aware membership test, mirroring Parslet's captures.key?.
|
|
88
96
|
# Checks the current frame and all parents without raising.
|
|
89
97
|
#
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -536,11 +536,19 @@ module Parsanol
|
|
|
536
536
|
if tables.all? && pairwise_disjoint?(tables)
|
|
537
537
|
dispatch_idx = emit(BYTE_DISPATCH, nil, nil, nil)
|
|
538
538
|
starts = []
|
|
539
|
+
# A dispatched branch that matched must not fall through into
|
|
540
|
+
# the next branch's code: the tables are lead-byte disjoint,
|
|
541
|
+
# but the remaining input can still satisfy a later branch's
|
|
542
|
+
# terminals (pubid: " - " — the '-' branch ran the ' ' branch's
|
|
543
|
+
# STR on the byte after the dash). Jump every branch but the
|
|
544
|
+
# last past the dispatch.
|
|
545
|
+
jumps = []
|
|
539
546
|
idx = 0
|
|
540
547
|
while idx < count
|
|
541
548
|
starts << flat(@ops.size)
|
|
542
549
|
return nil unless compile_atom(alts[idx], consume_all)
|
|
543
550
|
|
|
551
|
+
jumps << emit(JMP, nil, nil, nil) unless idx == count - 1
|
|
544
552
|
idx += 1
|
|
545
553
|
end
|
|
546
554
|
table = Array.new(256, -1)
|
|
@@ -555,6 +563,7 @@ module Parsanol
|
|
|
555
563
|
bi2
|
|
556
564
|
end
|
|
557
565
|
patch(dispatch_idx, 1, table)
|
|
566
|
+
jumps.each { |j| patch(j, 1, flat(@ops.size)) }
|
|
558
567
|
return self
|
|
559
568
|
end
|
|
560
569
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parsanol
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.65
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/parsanol/atoms/repetition.rb
|
|
71
71
|
- lib/parsanol/atoms/scope.rb
|
|
72
72
|
- lib/parsanol/atoms/sequence.rb
|
|
73
|
+
- lib/parsanol/atoms/state.rb
|
|
73
74
|
- lib/parsanol/atoms/str.rb
|
|
74
75
|
- lib/parsanol/atoms/visitor.rb
|
|
75
76
|
- lib/parsanol/buffer.rb
|