parsanol 1.3.85 → 1.3.87

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: 38de1565e58ea110f8732c5eec590223281787eb7922f0b885ddfa76594353e4
4
- data.tar.gz: 745175be31f92a3c40bac11e846b317954dfa4abe24628802aeefb1ee1adc460
3
+ metadata.gz: a0b4e99564a7a93dc454c5e004acb05f2fb6884dc993a75c3eb30565c0360687
4
+ data.tar.gz: 64ce48196a227e8c4f73dbc1d16a5415a278ef8a7730aaa7c8e85b45b28e75b3
5
5
  SHA512:
6
- metadata.gz: 133c7393222f9e29c776def04d6f7b5764d344bad9d8e7dd5ce054dcacaafca8720c266fb2f7ecd48cbcc036d462747028c42659b0b2ce271aed9ede9e56aded
7
- data.tar.gz: 646eb2011402310a591dc4aec7c87a5ccf58e270d9177d5c3657dd776a0e0762765f3a7be731137cadbe57ac292922de7d58008ef2b0fc908268cc8a60433510
6
+ metadata.gz: 2fde0240109a63a418d2851ca9578293934c784a0e14c74ecf74032dab99c361c729fdb468c4a4c8fdbc39e0f836a00a04202ccc7cfc96485230adfe636c7772
7
+ data.tar.gz: 516e9a87cc5577b0f00d1f994783cb0da6132105052f466f3ed4afb45b41445d29234d96443c15e39b1557d14c93395457c10d9bc5b4a2d7d30ef8393dcc4ae1
@@ -157,7 +157,11 @@ module Parsanol
157
157
  # @param outer [Integer] caller's precedence
158
158
  # @return [String]
159
159
  def to_s(outer = TOP)
160
- text = label || to_s_inner(precedence)
160
+ # The inner text is cached per instance: renders walk the atom
161
+ # tree through shared references, and an uncached render is
162
+ # exponential on cross-referencing grammars (parsanol-ruby#190).
163
+ # The paren wrapper stays dynamic; the label is read live.
164
+ text = label || (@to_s_inner_cache ||= to_s_inner(precedence))
161
165
  outer < precedence ? "(#{text})" : text
162
166
  end
163
167
 
@@ -235,7 +235,10 @@ module Parsanol
235
235
  kids = atom.parslets.map { |child| inject_skip_trivia(child) }
236
236
  out = []
237
237
  kids.each do |child|
238
- out << skip_maybe_atom unless skip_wrapper?(child)
238
+ # parsanol-ruby#192: no injected skip directly before a
239
+ # lookahead — its contract is the raw next character.
240
+ out << skip_maybe_atom unless skip_wrapper?(child) ||
241
+ child.is_a?(Atoms::Lookahead)
239
242
  out << child
240
243
  end
241
244
  out.length == 1 ? out.first : Atoms::Sequence.new(*out)
@@ -271,7 +274,11 @@ module Parsanol
271
274
  # `atomic` rules deliberately stop doing.
272
275
  Atoms::Named.new(inject_skip_trivia(atom.parslet), atom.name)
273
276
  when Atoms::Lookahead
274
- Atoms::Lookahead.new(inject_skip_trivia(atom.bound_parslet), atom.positive)
277
+ # parsanol-ruby#192: a lookahead's contract is the RAW text
278
+ # ahead — injected separators inside would make keyword
279
+ # boundary checks examine the wrong character. Interiors
280
+ # stay injection-exempt, not even rule references re-scope.
281
+ atom
275
282
  when Atoms::Str, Atoms::Re
276
283
  # bare terminal as a whole rule body: leading trivia position
277
284
  Atoms::Sequence.new(skip_maybe_atom, atom)
data/lib/parsanol/skip.rb CHANGED
@@ -109,9 +109,13 @@ module Parsanol
109
109
  @wrapper_ids = {}.compare_by_identity
110
110
  # Results keyed per injection context: the same shared atom
111
111
  # can be reached both inside and outside repetition interiors,
112
- # and the refinement injects differently there.
113
- @memo = { true => {}.compare_by_identity, false => {}.compare_by_identity }
114
- @in_progress = { true => {}.compare_by_identity, false => {}.compare_by_identity }
112
+ # and the refinement injects differently there. The :exempt
113
+ # context (lookahead interiors, ruby#192) suppresses injection
114
+ # everywhere, rule references included.
115
+ @memo = { true => {}.compare_by_identity, false => {}.compare_by_identity,
116
+ exempt: {}.compare_by_identity }
117
+ @in_progress = { true => {}.compare_by_identity, false => {}.compare_by_identity,
118
+ exempt: {}.compare_by_identity }
115
119
  end
116
120
 
117
121
  def inject(atom, entry: false)
@@ -149,27 +153,33 @@ module Parsanol
149
153
  # terminals inside a repetition consumes trivia within the
150
154
  # ENCLOSING capture's scope and mis-attaches recorded units when
151
155
  # the iteration fails.
152
- def walk(atom, rep_interior)
153
- cached = @memo[rep_interior][atom]
156
+ def walk(atom, mode)
157
+ cached = @memo[mode][atom]
154
158
  return cached if cached
155
- return atom if @in_progress[rep_interior].key?(atom)
159
+ return atom if @in_progress[mode].key?(atom)
156
160
  return atom if atom.equal?(@skip_atom)
157
161
 
158
- @in_progress[rep_interior][atom] = true
162
+ @in_progress[mode][atom] = true
159
163
  injected =
160
164
  case atom
161
165
  when Atoms::Sequence
162
166
  out = []
163
167
  atom.parslets.each do |child|
164
- walked = walk(child, rep_interior)
165
- unless rep_interior || wrapper?(walked) || walked.is_a?(Atoms::Trivia)
168
+ walked = walk(child, mode)
169
+ # parsanol-ruby#192: no wrapper directly before a
170
+ # lookahead — a lookahead's contract is the raw next
171
+ # character, and an injected separator between a
172
+ # keyword and its boundary check makes the check
173
+ # examine the wrong character.
174
+ unless mode || wrapper?(walked) || walked.is_a?(Atoms::Trivia) ||
175
+ walked.is_a?(Atoms::Lookahead)
166
176
  out << maybe
167
177
  end
168
178
  out << walked
169
179
  end
170
180
  out.length == 1 ? out.first : Atoms::Sequence.new(*out)
171
181
  when Atoms::Alternative
172
- Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a, rep_interior) })
182
+ Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a, mode) })
173
183
  when Atoms::Repetition
174
184
  if wrapper?(atom)
175
185
  atom
@@ -178,30 +188,49 @@ module Parsanol
178
188
  atom.result_tag)
179
189
  end
180
190
  when Atoms::Named
181
- Atoms::Named.new(walk(atom.parslet, rep_interior), atom.name)
191
+ Atoms::Named.new(walk(atom.parslet, mode), atom.name)
182
192
  when Atoms::Lookahead
183
- Atoms::Lookahead.new(walk(atom.bound_parslet, rep_interior), atom.positive)
193
+ # parsanol-ruby#192: a lookahead's contract is the RAW
194
+ # text ahead — no injection inside, rule references
195
+ # included (an injected separator between a keyword and
196
+ # its boundary check makes the check examine the wrong
197
+ # character).
198
+ Atoms::Lookahead.new(walk(atom.bound_parslet, :exempt), atom.positive)
184
199
  when Atoms::Ignored
185
- Atoms::Ignored.new(walk(atom.wrapped_atom, rep_interior))
200
+ Atoms::Ignored.new(walk(atom.wrapped_atom, mode))
186
201
  when Atoms::Entity
187
202
  # Rule bodies own their injection when the grammar declares
188
203
  # skip on every rule (PARG); in the DSL surface the root tree
189
- # is walked once, so descend through resolved bodies with
190
- # the in-progress set guarding recursion. A rule body is a
191
- # fresh injection scope even inside a repetition interior —
192
- # the d54a29e exception is for rule-reference children.
204
+ # is walked once, so inject the resolved body as a fresh
205
+ # injection scope (even inside a repetition interior — the
206
+ # d54a29e exception is for rule-reference children). The
207
+ # reference itself stays an Entity pointing at the injected
208
+ # body: inlining the body into every referencing site makes
209
+ # every rendered string (to_s, and failure messages built
210
+ # from inspect) expand the grammar exponentially on
211
+ # cross-referencing grammars — one failing Sequence rendered
212
+ # a multi-gigabyte message (parsanol-ruby#190).
193
213
  begin
194
- walk(atom.parslet, false)
214
+ body = walk(atom.parslet, mode == :exempt ? :exempt : false)
215
+ if body.is_a?(Atoms::Lookahead)
216
+ # A whole-body lookahead keeps its #192 boundary visible
217
+ # to enclosing sequences (no wrapper before it).
218
+ body
219
+ else
220
+ ref = Atoms::Entity.new(atom.rule_name) { body }
221
+ ref.label = atom.label if atom.label
222
+ ref
223
+ end
195
224
  rescue StandardError
196
225
  atom
197
226
  end
198
227
  when Atoms::Str, Atoms::Re
199
- rep_interior ? atom : Atoms::Sequence.new(maybe, atom)
228
+ mode ? atom : Atoms::Sequence.new(maybe, atom)
200
229
  else
201
230
  atom
202
231
  end
203
- @in_progress[rep_interior].delete(atom)
204
- @memo[rep_interior][atom] = injected
232
+ @in_progress[mode].delete(atom)
233
+ @memo[mode][atom] = injected
205
234
  injected
206
235
  end
207
236
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.85"
4
+ VERSION = "1.3.87"
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.85
4
+ version: 1.3.87
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.