parsanol 1.3.83-aarch64-linux → 1.3.85-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d77e5143a8bb36ec74223556d54b300f26b80adc0c28c1c192712b69374ca17
4
- data.tar.gz: 49a1036eb86a943aebc9ec08e37c0f5fbf6b302480f928e36d91b502d327f03f
3
+ metadata.gz: d2fb094f721fc4cae062cf0516e8a210ac26bb9346ca4bd5ff39397218899372
4
+ data.tar.gz: 16d4ec0c27c22efe0dd59ad081425bc2bd1dd7a6c3bb58411460e3feb5362bc4
5
5
  SHA512:
6
- metadata.gz: 718f9b08d6cba2a180e07f7e59783fae4c322dc27c991be78a3a814d7bfce669620dc3d459eb8d6399428ee60f543f33dba9eeda4412306b04466cdc0d02aae3
7
- data.tar.gz: 6683bd143dc36f15c0c964d1b3b95a0d0bce3801fa1535f7a3672560899466bd4ed2deaecd9f0f1da7bee71eeab4afc8e493b1fe3628cd9e7f3c8cc74d362f3b
6
+ metadata.gz: c72ae01c19db7e97fb61a5c787d4629d0ef439df72dfbbd021afbec09b0f70c8eb3e6010df1b84060cd18fe9247753157373f0576fc57476f4a982542f91c3b0
7
+ data.tar.gz: '09cb081d4143380cc4fc11530fa31328ead71172f30714ec5f7e240c8055c2d9d7057d817d54ee09b4e7ce9019c38fb3730bda821f59b720f6b6e1bb44b640b8'
@@ -48,16 +48,25 @@ module Parsanol
48
48
  # Internal value for comparisons (nil becomes infinity)
49
49
  @max_internal = max_count || Float::INFINITY
50
50
 
51
- # Pre-built error messages
52
- @min_error = "Expected at least #{min_count} of #{parser.inspect}"
51
+ # Error messages render LAZILY (error_msgs): construction is
52
+ # hot-path builder/injector work, and stringifying the whole
53
+ # subtree eagerly is exponential on shared cross-referencing
54
+ # grammars (the same class of cost Sequence's fail_msg had,
55
+ # parsanol-ruby#180/#184).
53
56
  @extra_error = "Extra input after last repetition"
54
57
  end
55
58
 
56
59
  # Error messages hash (for compatibility)
57
60
  def error_msgs
58
- { minrep: @min_error, unconsumed: @extra_error }
61
+ { minrep: min_error, unconsumed: @extra_error }
59
62
  end
60
63
 
64
+ # Rendered lazily and memoized on first failure (see initialize).
65
+ def min_error
66
+ @min_error ||= "Expected at least #{@min} of #{@parslet.inspect}"
67
+ end
68
+ private :min_error
69
+
61
70
  # Executes the repetition.
62
71
  #
63
72
  # @param source [Parsanol::Source] input
@@ -110,8 +119,6 @@ module Parsanol
110
119
  first
111
120
  end
112
121
 
113
- private
114
-
115
122
  # Optional match (0 or 1)
116
123
  def try_maybe(source, context, _consume_all)
117
124
  success, value = @parslet.apply(source, context, false)
@@ -136,39 +143,39 @@ module Parsanol
136
143
  success, value = @parslet.apply(source, context, consume_all)
137
144
  return ok([@result_tag, value]) if success
138
145
 
139
- context.err_at(self, source, @min_error, source.bytepos, [value])
146
+ context.err_at(self, source, min_error, source.bytepos, [value])
140
147
  end
141
148
 
142
149
  def double_match(source, context, consume_all)
143
150
  success, v1 = @parslet.apply(source, context, false)
144
151
  unless success
145
- return context.err_at(self, source, @min_error, source.bytepos,
152
+ return context.err_at(self, source, min_error, source.bytepos,
146
153
  [v1])
147
154
  end
148
155
 
149
156
  success, v2 = @parslet.apply(source, context, consume_all)
150
157
  return ok([@result_tag, v1, v2]) if success
151
158
 
152
- context.err_at(self, source, @min_error, source.bytepos, [v2])
159
+ context.err_at(self, source, min_error, source.bytepos, [v2])
153
160
  end
154
161
 
155
162
  def triple_match(source, context, consume_all)
156
163
  success, v1 = @parslet.apply(source, context, false)
157
164
  unless success
158
- return context.err_at(self, source, @min_error, source.bytepos,
165
+ return context.err_at(self, source, min_error, source.bytepos,
159
166
  [v1])
160
167
  end
161
168
 
162
169
  success, v2 = @parslet.apply(source, context, false)
163
170
  unless success
164
- return context.err_at(self, source, @min_error, source.bytepos,
171
+ return context.err_at(self, source, min_error, source.bytepos,
165
172
  [v2])
166
173
  end
167
174
 
168
175
  success, v3 = @parslet.apply(source, context, consume_all)
169
176
  return ok([@result_tag, v1, v2, v3]) if success
170
177
 
171
- context.err_at(self, source, @min_error, source.bytepos, [v3])
178
+ context.err_at(self, source, min_error, source.bytepos, [v3])
172
179
  end
173
180
 
174
181
  # General repetition: builds the tagged result Array in place so
@@ -205,7 +212,7 @@ module Parsanol
205
212
  # Check minimum bound
206
213
  if occurrence < @min
207
214
  source.bytepos = start_pos
208
- return context.err_at(self, source, @min_error, start_pos,
215
+ return context.err_at(self, source, min_error, start_pos,
209
216
  failure_children(last_failure))
210
217
  end
211
218
 
@@ -283,7 +290,7 @@ module Parsanol
283
290
  if occurrence < @min
284
291
  context.release_array(positions)
285
292
  source.bytepos = start_pos
286
- return context.err_at(self, source, @min_error, start_pos,
293
+ return context.err_at(self, source, min_error, start_pos,
287
294
  failure_children(last_failure))
288
295
  end
289
296
 
@@ -303,7 +310,7 @@ module Parsanol
303
310
  # Check minimum
304
311
  if occurrence < @min
305
312
  source.bytepos = start_pos
306
- return context.err_at(self, source, @min_error, start_pos,
313
+ return context.err_at(self, source, min_error, start_pos,
307
314
  failure_children(last_failure))
308
315
  end
309
316
 
@@ -334,7 +334,10 @@ module Parsanol
334
334
  inner_id = serialize_atom(atom.wrapped_atom)
335
335
  payload = {
336
336
  "atom" => inner_id,
337
- "rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
337
+ # a whitespace-only trivia declaration (whitespace: :space with
338
+ # no capture:) has no capturer map — the wire field is optional
339
+ # (relaton with the expressir grammar, parsanol-ruby#180)
340
+ "rules" => atom.capturers&.map { |marker, label| [marker, label.to_s] } || [],
338
341
  }
339
342
  # parsanol-ruby#180: the optional whitespace kind — units that
340
343
  # match no marker record under it (source-preserving grammars).
@@ -202,6 +202,38 @@ module Parsanol
202
202
  stats
203
203
  end
204
204
 
205
+ # True when the grammar carries no atoms the native tier cannot
206
+ # express (Dynamic callbacks, artifact custom bindings). Cached by
207
+ # grammar identity — the scan walks the atom tree once.
208
+ def native_expressible?(grammar)
209
+ root = grammar.is_a?(Parsanol::Parser) ? grammar.root : grammar
210
+ cache = (@expressible_cache ||= {}.compare_by_identity)
211
+ cache.fetch(root) do
212
+ expressible = !contains_inexpressible?(root, {}.compare_by_identity)
213
+ cache[root] = expressible
214
+ end
215
+ end
216
+
217
+ def contains_inexpressible?(atom, seen)
218
+ return false if seen.key?(atom)
219
+
220
+ seen[atom] = true
221
+ case atom
222
+ when Parsanol::Atoms::Dynamic, Parsanol::Atoms::CustomRef then true
223
+ when Parsanol::Atoms::Sequence, Parsanol::Atoms::Alternative
224
+ children = atom.respond_to?(:parslets) ? atom.parslets : atom.alternatives
225
+ children.any? { |child| contains_inexpressible?(child, seen) }
226
+ when Parsanol::Atoms::Entity, Parsanol::Atoms::Repetition,
227
+ Parsanol::Atoms::Named, Parsanol::Atoms::Capture, Parsanol::Atoms::Ignored,
228
+ Parsanol::Atoms::Trivia, Parsanol::Atoms::TriviaCapture, Parsanol::Atoms::Lookahead
229
+ inner = atom.respond_to?(:parslet) ? atom.parslet : nil
230
+ inner ||= atom.wrapped_atom if atom.respond_to?(:wrapped_atom)
231
+ inner && contains_inexpressible?(inner, seen)
232
+ else
233
+ false
234
+ end
235
+ end
236
+
205
237
  # Translates a native backend failure into the Parsanol error protocol.
206
238
  #
207
239
  # When the grammar atom is at hand, reparses through the pure Ruby
@@ -227,9 +259,19 @@ module Parsanol
227
259
  # across long parses (#123).
228
260
  message = error.message
229
261
  if parsanol_grammar && (pos_str = message[NATIVE_POS_MARKER, 1])
262
+ # The interpreter reparse exists to RECOVER grammars the
263
+ # native tier cannot express (Dynamic/CustomRef atoms —
264
+ # parsanol-ruby#80/#129). For grammars without them the
265
+ # native failure is authoritative and the deepest-position
266
+ # diagnostics ride in this message — reparsing through the
267
+ # interpreter buys nothing and, on capture-heavy injected
268
+ # grammars, explodes (#190: the interpreter memo never
269
+ # stores capture-spanning outcomes).
230
270
  source = Parsanol::Source.new(input)
231
- success, value = grammar.run_with_context(source, nil, true)
232
- return grammar.finalize_result(value) if success
271
+ unless native_expressible?(grammar)
272
+ success, value = grammar.run_with_context(source, nil, true)
273
+ return grammar.finalize_result(value) if success
274
+ end
233
275
 
234
276
  pos = pos_str.to_i
235
277
  msg = message.sub(NATIVE_POS_MARKER, "")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.83"
4
+ VERSION = "1.3.85"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parsanol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.83
4
+ version: 1.3.85
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-08 00:00:00.000000000 Z
11
+ date: 2026-10-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A small Ruby library for constructing parsers in the PEG (Parsing Expression
14
14
  Grammar) fashion. Parsanol provides Parslet-compatible API with additional features