parsanol 1.3.80 → 1.3.82

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1cf70af68139adf8136c539ac11fc7e82c2e4c3975d8d47970b3c90d3842574
4
- data.tar.gz: 30334852e279a556f196c1c0738b3348a9436cde57492638eddb43464567ec6e
3
+ metadata.gz: c56435897ed6f65bc101a9df1cdb0c0510917824295fa003893cdf7e447a32a4
4
+ data.tar.gz: 8308c7b4bc8179fbf7867ab1769c53b1f6759ce6ad8cd7ca2dc9362e8c5a3c58
5
5
  SHA512:
6
- metadata.gz: d8ca94e8d24797ea68e041668e9e6d024b0e9a1c8c30d07c4a9a1c7063e1c23db9cdfbf67bd9ee66978df0ee43f6b53a390b6f360b89bd756169e4d48fdc8df4
7
- data.tar.gz: 2137eb6520d33c6a0654940c791caf28f560b5975dbf83055965b095c9410420a229f6aaa9498bfbf856ca0618f8da6c8d18c34c8896d671859eabec5bd7cc87
6
+ metadata.gz: 7ad6958bee6a30a11ce020e95a1b084bd26155f7293c2c127c2ff264850da1df22bbde4eb1f321f8e442448492d5612adc25fc3ccd19fe8a65b3e9ea7a2f9731
7
+ data.tar.gz: 4e353b492c5113f197822c528bf37622b54bc6eae4d2a4bb73f171cbce7edadd00c96ed55928eda73a9d2746d68e14fb4fd212ec2c768bca30a733123a32c1c8
data/Cargo.lock CHANGED
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
307
307
 
308
308
  [[package]]
309
309
  name = "parsanol"
310
- version = "0.17.2"
310
+ version = "0.18.0"
311
311
  source = "registry+https://github.com/rust-lang/crates.io-index"
312
- checksum = "2b6296dcfe8a2055273a1cb76ffb383c586dfbb6e647ff11b9f396f1b8f6fc36"
312
+ checksum = "dc899494d6b94ea8df14279b7dcbac89d59ba4d1be52f7b8ffe840674c6c8e3c"
313
313
  dependencies = [
314
314
  "ahash",
315
315
  "getrandom 0.3.4",
@@ -326,9 +326,9 @@ dependencies = [
326
326
 
327
327
  [[package]]
328
328
  name = "parsanol-derive"
329
- version = "0.17.2"
329
+ version = "0.18.0"
330
330
  source = "registry+https://github.com/rust-lang/crates.io-index"
331
- checksum = "60e96bd9d40164ef69ba879c97ba0d3efcf5e4183062ef6bf1eb66c4cdf91ae5"
331
+ checksum = "b437046956f356eee0eb9b407ff1f1f6286af1f70f1af1d305c54a64c65b64e2"
332
332
  dependencies = [
333
333
  "proc-macro2",
334
334
  "quote",
@@ -13,4 +13,4 @@ doc = false
13
13
 
14
14
  [dependencies]
15
15
  # 0.17.0 carries LookSpec + the widened opaque writeback
16
- parsanol = { version = "0.17.2" }
16
+ parsanol = { version = "0.18.0" }
@@ -32,7 +32,7 @@ magnus = { version = "0.9" }
32
32
  # parsanol parser library (crates.io; 0.17.0 carries the LookSpec regex
33
33
  # lookbehind (rs#163 gap 1) and the widened opaque writeback: the
34
34
  # lazy-continuation fix, parsanol-ruby#160)
35
- parsanol = { version = "0.17.2", features = ["ruby"] }
35
+ parsanol = { version = "0.18.0", features = ["ruby"] }
36
36
 
37
37
  # Logging
38
38
  log = "0.4"
@@ -18,14 +18,25 @@ module Parsanol
18
18
  def initialize(*components)
19
19
  super()
20
20
  @parslets = components
21
-
22
- # Pre-built error message
23
- @fail_msg = "Failed to match sequence (#{inspect})"
21
+ # The failure message renders LAZILY (error_msgs): constructing
22
+ # sequences is hot-path work for the builder/injector, and
23
+ # stringifying the whole subtree eagerly is exponential on
24
+ # shared cross-referencing grammars (parsanol-ruby#180 side
25
+ # observation: a 25-rule Fibonacci-shaped grammar took 6s).
26
+ @fail_msg = nil
24
27
  end
25
28
 
26
29
  # Error messages hash (for compatibility)
27
30
  def error_msgs
28
- { failed: @fail_msg }
31
+ { failed: fail_msg }
32
+ end
33
+
34
+ # Rendered lazily (see initialize); construction is hot-path
35
+ # builder/injector work and stringifying the whole subtree
36
+ # eagerly is exponential on shared cross-referencing grammars
37
+ # (parsanol-ruby#180 side observation).
38
+ def fail_msg
39
+ @fail_msg ||= "Failed to match sequence (#{inspect})"
29
40
  end
30
41
 
31
42
  # Appends a parser to this sequence with flattening.
@@ -104,7 +115,7 @@ module Parsanol
104
115
  # Single element sequence
105
116
  def match_single(parser, source, context, consume_all)
106
117
  success, value = parser.apply(source, context, consume_all)
107
- return context.err(self, source, @fail_msg, [value]) unless success
118
+ return context.err(self, source, fail_msg, [value]) unless success
108
119
 
109
120
  ok([:sequence, value])
110
121
  end
@@ -112,10 +123,10 @@ module Parsanol
112
123
  # Two-element sequence
113
124
  def match_pair(p1, p2, source, context, consume_all)
114
125
  success, v1 = p1.apply(source, context, false)
115
- return context.err(self, source, @fail_msg, [v1]) unless success
126
+ return context.err(self, source, fail_msg, [v1]) unless success
116
127
 
117
128
  success, v2 = p2.apply(source, context, consume_all)
118
- return context.err(self, source, @fail_msg, [v2]) unless success
129
+ return context.err(self, source, fail_msg, [v2]) unless success
119
130
 
120
131
  ok([:sequence, v1, v2])
121
132
  end
@@ -123,13 +134,13 @@ module Parsanol
123
134
  # Three-element sequence
124
135
  def match_triple(p1, p2, p3, source, context, consume_all)
125
136
  success, v1 = p1.apply(source, context, false)
126
- return context.err(self, source, @fail_msg, [v1]) unless success
137
+ return context.err(self, source, fail_msg, [v1]) unless success
127
138
 
128
139
  success, v2 = p2.apply(source, context, false)
129
- return context.err(self, source, @fail_msg, [v2]) unless success
140
+ return context.err(self, source, fail_msg, [v2]) unless success
130
141
 
131
142
  success, v3 = p3.apply(source, context, consume_all)
132
- return context.err(self, source, @fail_msg, [v3]) unless success
143
+ return context.err(self, source, fail_msg, [v3]) unless success
133
144
 
134
145
  ok([:sequence, v1, v2, v3])
135
146
  end
@@ -148,7 +159,7 @@ module Parsanol
148
159
  success, value = components[idx].apply(source, context, must_consume)
149
160
 
150
161
  unless success
151
- return context.err(self, source, @fail_msg, [value])
162
+ return context.err(self, source, fail_msg, [value])
152
163
  end
153
164
 
154
165
  result[idx + 1] = value
@@ -28,12 +28,16 @@ module Parsanol
28
28
  # +capturers+ maps leading-literal marker -> kind label (the
29
29
  # compiler derives markers from each capturer rule's first Str
30
30
  # atom, e.g. "//" => :line_comment); units not matching any
31
- # marker are whitespace-shaped and stay unrecorded.
32
- attr_reader :capturers
31
+ # marker are whitespace-shaped and stay unrecorded — unless
32
+ # +whitespace_kind+ is declared (parsanol-ruby#180): every
33
+ # unit then records under that label, giving source-preserving
34
+ # grammars the full ordered trivia stream.
35
+ attr_reader :capturers, :whitespace_kind
33
36
 
34
- def initialize(atom, capturers)
37
+ def initialize(atom, capturers, whitespace_kind: nil)
35
38
  super(atom)
36
39
  @capturers = capturers
40
+ @whitespace_kind = whitespace_kind
37
41
  end
38
42
 
39
43
  def apply(source, context, consume_all)
@@ -55,16 +59,16 @@ module Parsanol
55
59
  private
56
60
 
57
61
  def record(source, context, start_pos, end_pos)
58
- return unless @capturers
59
-
60
62
  text = source.input[start_pos...end_pos]
61
63
  # A trivia unit may lead with whitespace before the comment
62
64
  # shape; markers test the comment head.
63
65
  stripped = text.lstrip
64
- marker, label = @capturers.find { |m, _label| stripped.start_with?(m) }
65
- return unless marker
66
-
67
- context.push_captured_trivia(label, stripped.strip)
66
+ marker, label = @capturers&.find { |m, _label| stripped.start_with?(m) }
67
+ if marker
68
+ context.push_captured_trivia(label, stripped.strip)
69
+ elsif @whitespace_kind
70
+ context.push_captured_trivia(@whitespace_kind, text)
71
+ end
68
72
  end
69
73
  end
70
74
  end
@@ -205,6 +205,19 @@ module Parsanol
205
205
  [:constant, atom.value.inspect]
206
206
  when ::Parsanol::Atoms::Lookbehind
207
207
  [:lookbehind, atom.count, atom.pattern, atom.positive]
208
+ # Subclass first: TriviaCapture < Trivia < Ignored (the same
209
+ # dispatch discipline as the serializer's).
210
+ when ::Parsanol::Atoms::TriviaCapture
211
+ # The capturer table differentiates capture-carrying
212
+ # grammars from plain-trivia ones — without it the grammar
213
+ # cache serves one variant's wire to every other and the
214
+ # wrong marker table reaches the engine (parsanol-ruby#180).
215
+ [:trivia_capture, atom.capturers.to_a.sort,
216
+ atom_structure(atom.wrapped_atom, visited)]
217
+ when ::Parsanol::Atoms::Trivia
218
+ [:trivia, atom_structure(atom.wrapped_atom, visited)]
219
+ when ::Parsanol::Atoms::Ignored
220
+ [:ignored, atom_structure(atom.wrapped_atom, visited)]
208
221
  else
209
222
  [:unknown, atom.class.name]
210
223
  end
@@ -332,12 +332,16 @@ module Parsanol
332
332
 
333
333
  def serialize_trivia_capture(atom)
334
334
  inner_id = serialize_atom(atom.wrapped_atom)
335
- {
336
- "TriviaCapture" => {
337
- "atom" => inner_id,
338
- "rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
339
- },
335
+ payload = {
336
+ "atom" => inner_id,
337
+ "rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
340
338
  }
339
+ # parsanol-ruby#180: the optional whitespace kind — units that
340
+ # match no marker record under it (source-preserving grammars).
341
+ # Optional on the wire: engines predating it deserialize and
342
+ # simply don't record whitespace units (documented caveat).
343
+ payload["whitespace"] = atom.whitespace_kind.to_s if atom.whitespace_kind
344
+ { "TriviaCapture" => payload }
341
345
  end
342
346
 
343
347
  def serialize_trivia(atom)
@@ -65,24 +65,26 @@ module Parsanol
65
65
  def parse(entry_name, input, mode: :native)
66
66
  case mode
67
67
  when :native
68
- if Native.available? && !envelope["dynamic"]
69
- # Fast lane (parsanol-ruby#162): the per-instance atom
70
- # tree rides the DSL serialization/registration caches
68
+ if Native.available?
69
+ # Fast lane (parsanol-ruby#162): Native.parse with the
70
+ # per-instance root atom registers the grammar once
71
71
  # (GRAMMAR_CACHE / HANDLE_CACHE keyed on the stable root
72
- # atom), so an artifact parses at compiled speed after
73
- # the first call. The old path re-serialized the whole
74
- # grammar JSON and re-registered it per parse (~8x).
75
- # Dynamic artifacts carry ruby-tier atoms their wire
76
- # cannot express — they keep the interpreter.
77
- begin
78
- return root_atom(entry_name).parse(input, mode: :native)
79
- rescue Parsanol::ParseFailed
80
- # The atom-tree native path reports the root position; the
81
- # wire-registration path carries the deepest-failure
82
- # diagnostics (parsanol-ruby#171). Failures pay the
83
- # serialization once; successes keep the fast lane.
84
- return Native.parse(JSON.generate(entry(entry_name).fetch("grammar")), input)
85
- end
72
+ # atom) and every parse after the first runs the native
73
+ # engine by handle — the same path a DSL parser's
74
+ # mode: :native takes. Atoms::Base#parse must NOT carry
75
+ # this lane: it ignores :mode, so it would silently run
76
+ # the pure-Ruby VM and its interpreter fallback (the
77
+ # 5-7x real-document gap in #162, and the root-position
78
+ # diagnostics in #171 before the wire fallback).
79
+ #
80
+ # Dynamic-flagged artifacts parse natively too (rs#204):
81
+ # the state atoms (StateSet/StateMatch/StateSwitch)
82
+ # evaluate in the engine, riding the capture store's
83
+ # rollback discipline. Reaching a CustomRef aborts the
84
+ # native pass outright — Native.parse's error path then
85
+ # re-runs the interpreter, so customs stay Ruby-tier with
86
+ # identical trees.
87
+ return Native.parse(root_atom(entry_name), input)
86
88
  end
87
89
 
88
90
  root_atom(entry_name).parse(input)
@@ -52,7 +52,13 @@ module Parsanol
52
52
  define_method(:root) do
53
53
  atom = __send__(rule_name)
54
54
  skip_name = self.class.skip_rule_name
55
- skip_name ? Parsanol::Skip.inject(self, atom, skip_name)[0] : atom
55
+ if skip_name
56
+ Parsanol::Skip.inject(self, atom, skip_name,
57
+ captures: self.class.skip_captures,
58
+ whitespace: self.class.skip_whitespace)[0]
59
+ else
60
+ atom
61
+ end
56
62
  end
57
63
  end
58
64
 
@@ -63,6 +69,13 @@ module Parsanol
63
69
  # everything it references) builds without injection; it must be
64
70
  # non-nullable. Order-independent with +root+.
65
71
  #
72
+ # +capture:+ (parsanol-ruby#180) declares trivia capturers — a
73
+ # map of rule name to kind label. Units of the skip rule
74
+ # matching a capturer's leading literal attach to the next
75
+ # Named capture under +comments:+ (the PARG
76
+ # `skip = trivia capture: comments` shape) instead of being
77
+ # discarded; whitespace-shaped units never record.
78
+ #
66
79
  # @example
67
80
  # class MyParser < Parsanol::Parser
68
81
  # rule(:spaces) { match(/[ \t]/).repeat(1) }
@@ -72,8 +85,15 @@ module Parsanol
72
85
  # root :doc
73
86
  # end
74
87
  #
75
- def skip(rule_name)
88
+ # @example capturing comments
89
+ # rule(:trivia) { spaces | line_comment }
90
+ # rule(:line_comment) { str("//") >> match(/[^\n]/).repeat }
91
+ # skip :trivia, capture: { line_comment: :line_comment }
92
+ #
93
+ def skip(rule_name, capture: nil, whitespace: nil)
76
94
  @skip_rule_name = rule_name
95
+ @skip_captures = capture
96
+ @skip_whitespace = whitespace
77
97
  nil
78
98
  end
79
99
 
@@ -82,6 +102,18 @@ module Parsanol
82
102
  def skip_rule_name
83
103
  @skip_rule_name
84
104
  end
105
+
106
+ # The declared capturer map ({rule => kind label}), or nil —
107
+ # see {.skip}.
108
+ def skip_captures
109
+ @skip_captures
110
+ end
111
+
112
+ # The declared whitespace kind (units matching no capturer
113
+ # record under it), or nil — see {.skip}.
114
+ def skip_whitespace
115
+ @skip_whitespace
116
+ end
85
117
  end
86
118
 
87
119
  # Delegates matching to the root rule.
data/lib/parsanol/skip.rb CHANGED
@@ -13,7 +13,15 @@ module Parsanol
13
13
  # methods. Returns [injected_root, skip_atom] with the wrapper
14
14
  # built and the lint applied, or raises GrammarError for a
15
15
  # nullable skip.
16
- def self.inject(parser_instance, root_atom, skip_rule_name)
16
+ #
17
+ # +captures+ (parsanol-ruby#180) maps capturer rule names to kind
18
+ # labels (`skip :trivia, capture: { line_comment: :line }`); the
19
+ # injected wrapper becomes a TriviaCapture whose units matching a
20
+ # capturer's leading literal attach to the next Named capture
21
+ # under `comments:` — the PARG `skip = trivia capture: comments`
22
+ # shape. Whitespace-shaped units never record.
23
+ def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil,
24
+ whitespace: nil)
17
25
  skip_atom = parser_instance.__send__(skip_rule_name)
18
26
  skip_atom = skip_atom.parslet while skip_atom.is_a?(Atoms::Entity)
19
27
 
@@ -23,24 +31,88 @@ module Parsanol
23
31
  "empty loops forever at injection points"
24
32
  end
25
33
 
26
- injector = Injector.new(skip_atom)
34
+ markers = capturer_markers(parser_instance, captures)
35
+ whitespace_kind = whitespace&.to_sym
36
+ injector = Injector.new(skip_atom, markers, whitespace_kind)
27
37
  [injector.inject(root_atom, entry: true), skip_atom]
28
38
  end
29
39
 
40
+ # Derives each capturer's leading literal (the leftmost Str the
41
+ # rule can start with) and keys the marker table by it — the same
42
+ # derivation the PARG compiler applies to its skip declaration. A
43
+ # declared capturer without a derivable, non-blank literal is a
44
+ # declaration error: its units could never be told apart from
45
+ # whitespace-shaped trivia and it would silently record nothing.
46
+ def self.capturer_markers(parser_instance, captures)
47
+ return nil if captures.nil? || captures.empty?
48
+
49
+ captures.each_with_object({}) do |(rule_name, kind), markers|
50
+ atom = parser_instance.__send__(rule_name)
51
+ atom = atom.parslet while atom.is_a?(Atoms::Entity)
52
+ literal = leading_literal(atom)
53
+ if literal.nil? || literal.strip.empty?
54
+ raise GrammarError,
55
+ "skip capturer #{rule_name.inspect} has no leading " \
56
+ "literal: its units cannot be told apart from " \
57
+ "whitespace-shaped trivia"
58
+ end
59
+ markers[literal] = kind || rule_name
60
+ end
61
+ end
62
+
63
+ # The leftmost literal the atom can start with, or nil when the
64
+ # shape is not literal-led (alternatives with disagreeing
65
+ # branches, regexes, lookaheads).
66
+ def self.leading_literal(atom)
67
+ case atom
68
+ when Atoms::Str then atom.str
69
+ when Atoms::Sequence
70
+ atom.parslets.each do |child|
71
+ literal = leading_literal(child)
72
+ return literal if literal
73
+ end
74
+ nil
75
+ when Atoms::Alternative
76
+ first = nil
77
+ atom.alternatives.each do |branch|
78
+ literal = leading_literal(branch)
79
+ return nil if literal.nil? || (first && literal != first)
80
+
81
+ first ||= literal
82
+ end
83
+ first
84
+ when Atoms::Repetition
85
+ atom.min&.positive? ? leading_literal(atom.parslet) : nil
86
+ when Atoms::Named, Atoms::Capture, Atoms::Ignored, Atoms::Trivia
87
+ leading_literal(atom.parslet)
88
+ end
89
+ end
90
+
30
91
  # The injection walk. Mirrors the PARG compiler's semantics:
31
92
  # sequences interleave the wrapper before every child, alternatives
32
93
  # and repetitions descend, named captures descend (spans stay
33
94
  # clean — the wrapper is Trivia: Ignored + diagnostics
34
95
  # transparency), bare terminals gain a leading wrapper, and the
35
96
  # entry root takes leading and trailing wrappers.
97
+ # The walk is a pure function of the atom subtree, so results are
98
+ # memoized: dense cross-referencing grammars (expressir's ~250
99
+ # rules) revisit shared subtrees once per referencing path, which
100
+ # made the path-scoped visited-set exponential — a 25-rule
101
+ # Fibonacci-shaped grammar took 6.2s to build (parsanol-ruby#180
102
+ # side observation). In-progress atoms mark a cycle re-entry and
103
+ # return raw, exactly as the path-scoped set did.
36
104
  class Injector
37
- def initialize(skip_atom)
105
+ def initialize(skip_atom, markers = nil, whitespace_kind = nil)
38
106
  @skip_atom = skip_atom
107
+ @markers = markers
108
+ @whitespace_kind = whitespace_kind
39
109
  @wrapper_ids = {}.compare_by_identity
110
+ @memo = {}.compare_by_identity
111
+ @in_progress = {}.compare_by_identity
40
112
  end
41
113
 
42
114
  def inject(atom, entry: false)
43
- injected = walk(atom, {})
115
+ injected = walk(atom)
44
116
  return injected unless entry
45
117
 
46
118
  Atoms::Sequence.new(maybe, injected, maybe)
@@ -50,7 +122,14 @@ module Parsanol
50
122
 
51
123
  def maybe
52
124
  @maybe ||= begin
53
- wrapper = Atoms::Trivia.new(Atoms::Repetition.new(@skip_atom, 0, 1))
125
+ inner = Atoms::Repetition.new(@skip_atom, 0, 1)
126
+ wrapper =
127
+ if @markers || @whitespace_kind
128
+ Atoms::TriviaCapture.new(inner, @markers,
129
+ whitespace_kind: @whitespace_kind)
130
+ else
131
+ Atoms::Trivia.new(inner)
132
+ end
54
133
  @wrapper_ids[wrapper] = true
55
134
  wrapper
56
135
  end
@@ -60,50 +139,56 @@ module Parsanol
60
139
  @wrapper_ids.key?(atom)
61
140
  end
62
141
 
63
- def walk(atom, visited)
64
- return atom if visited.key?(atom)
142
+ def walk(atom)
143
+ cached = @memo[atom]
144
+ return cached if cached
145
+ return atom if @in_progress.key?(atom)
65
146
  return atom if atom.equal?(@skip_atom)
66
147
 
67
- visited = visited.merge(atom => true)
68
- case atom
69
- when Atoms::Sequence
70
- out = []
71
- atom.parslets.each do |child|
72
- injected = walk(child, visited)
73
- out << maybe unless wrapper?(injected) || injected.is_a?(Atoms::Trivia)
74
- out << injected
75
- end
76
- out.length == 1 ? out.first : Atoms::Sequence.new(*out)
77
- when Atoms::Alternative
78
- Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a, visited) })
79
- when Atoms::Repetition
80
- if wrapper?(atom)
81
- atom
148
+ @in_progress[atom] = true
149
+ injected =
150
+ case atom
151
+ when Atoms::Sequence
152
+ out = []
153
+ atom.parslets.each do |child|
154
+ injected = walk(child)
155
+ out << maybe unless wrapper?(injected) || injected.is_a?(Atoms::Trivia)
156
+ out << injected
157
+ end
158
+ out.length == 1 ? out.first : Atoms::Sequence.new(*out)
159
+ when Atoms::Alternative
160
+ Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a) })
161
+ when Atoms::Repetition
162
+ if wrapper?(atom)
163
+ atom
164
+ else
165
+ Atoms::Repetition.new(walk(atom.parslet), atom.min, atom.max,
166
+ atom.result_tag)
167
+ end
168
+ when Atoms::Named
169
+ Atoms::Named.new(walk(atom.parslet), atom.name)
170
+ when Atoms::Lookahead
171
+ Atoms::Lookahead.new(walk(atom.bound_parslet), atom.positive)
172
+ when Atoms::Ignored
173
+ Atoms::Ignored.new(walk(atom.wrapped_atom))
174
+ when Atoms::Entity
175
+ # Rule bodies own their injection when the grammar declares
176
+ # skip on every rule (PARG); in the DSL surface the root tree
177
+ # is walked once, so descend through resolved bodies with the
178
+ # identity visited-set guarding recursion.
179
+ begin
180
+ walk(atom.parslet)
181
+ rescue StandardError
182
+ atom
183
+ end
184
+ when Atoms::Str, Atoms::Re
185
+ Atoms::Sequence.new(maybe, atom)
82
186
  else
83
- Atoms::Repetition.new(walk(atom.parslet, visited), atom.min, atom.max,
84
- atom.result_tag)
85
- end
86
- when Atoms::Named
87
- Atoms::Named.new(walk(atom.parslet, visited), atom.name)
88
- when Atoms::Lookahead
89
- Atoms::Lookahead.new(walk(atom.bound_parslet, visited), atom.positive)
90
- when Atoms::Ignored
91
- Atoms::Ignored.new(walk(atom.wrapped_atom, visited))
92
- when Atoms::Entity
93
- # Rule bodies own their injection when the grammar declares
94
- # skip on every rule (PARG); in the DSL surface the root tree
95
- # is walked once, so descend through resolved bodies with the
96
- # identity visited-set guarding recursion.
97
- begin
98
- walk(atom.parslet, visited)
99
- rescue StandardError
100
187
  atom
101
188
  end
102
- when Atoms::Str, Atoms::Re
103
- Atoms::Sequence.new(maybe, atom)
104
- else
105
- atom
106
- end
189
+ @in_progress.delete(atom)
190
+ @memo[atom] = injected
191
+ injected
107
192
  end
108
193
  end
109
194
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.80"
4
+ VERSION = "1.3.82"
5
5
  end
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.80
4
+ version: 1.3.82
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.