parsanol 1.3.103-arm-linux → 1.3.105-arm-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: 7d3cf2d4d76879a579ca674f3304cae0388d9fea825722d6194bbec055492230
4
- data.tar.gz: a1e13b713cfd2f39228757c8239924d83e18518e9eb40fcf6ddce0d5bf1fe359
3
+ metadata.gz: 1b2a5b84cfa562c1535e789fbb1b255eacbed37ba883009bebc3cdcdd6ee5bf1
4
+ data.tar.gz: cff2ac2a93078d48366061fc0e929ce7a959db8ee5200f6202b8ee56bd33ab1e
5
5
  SHA512:
6
- metadata.gz: 30d3712aa28138ba5d3cbc69654d0096fac2a4816841b910593984b99aa1f545e9f21bf14394d385f19b49c475387be4e8d78635ba5e3da44026bb843c1fc306
7
- data.tar.gz: 7a16afa441e72786dae5e74d76bf1853a664f7982ad332b6603d68a90715cf5584fee6ad36db6cd6fd8648a3fbb49d6b2418d9098a51abc577990df4f64fd40b
6
+ metadata.gz: 46131326bda0bcc9f9304c88dcf935b67675f9d18aea638bff38334b9f245fba87aad2b7a6433df41349af6dec63dae68e9c33c2b7d287748cc783b26bdc3a9b
7
+ data.tar.gz: c75b6c38c1f04df3e9de16313c4bf5bbecd57c2490a57c20704b6144b593e884613f1353ae8317975e381119542f831bf6161d82fd53f54edd802b2d96cc50e2
@@ -222,10 +222,97 @@ module Parsanol
222
222
  # initial parse's (the native FFI's coarse failure position
223
223
  # loses the deepest offset).
224
224
  captures, reparse_error = recovery_journal(entry_name, input, mode)
225
+ partial = recovery_partial(entry_name)
226
+ resyncs = []
227
+ if partial && reparse_error
228
+ items = partial.values.first
229
+ extended, resyncs = resync_extension(entry_name, input,
230
+ reparse_error["offset"])
231
+ items.concat(extended) if extended
232
+ end
225
233
  { "ok" => false,
226
234
  "error" => reparse_error || recovery_error(e),
227
235
  "captures" => captures,
228
- "partial" => recovery_partial(entry_name) }
236
+ "partial" => partial,
237
+ "resyncs" => resyncs }
238
+ end
239
+
240
+ # R2 (TODO.sota/1), inferred resync on the interpreter — the same
241
+ # policy as the rs engine (parsanol-rs#280): from the failure
242
+ # offset, scan forward for the next byte position where a
243
+ # spine-body iteration matches with progress; the skipped span is
244
+ # annotated and the recovered items join the partial tree.
245
+ # Bounded (8 rounds, each must advance) so recovery stays linear.
246
+ # Returns [items, resyncs]; [nil, []] when the grammar has no
247
+ # resolvable spine.
248
+ def resync_extension(entry_name, input, cursor)
249
+ body_rule = spine_body_rule(entry_name)
250
+ return [[], []] unless body_rule
251
+
252
+ body_atom = compiler.atom_for(body_rule)
253
+ max = input.length
254
+ resyncs = []
255
+ items = []
256
+ rounds = 0
257
+ while rounds < 8
258
+ found = nil
259
+ scan = cursor
260
+ while scan < max
261
+ attempt = try_iteration(body_atom, input, scan)
262
+ if attempt && attempt[1] > scan
263
+ found = [scan, attempt[1], attempt[0]]
264
+ break
265
+ end
266
+ scan += 1
267
+ end
268
+ break unless found
269
+
270
+ skip_start, resume, value = found
271
+ resyncs << {
272
+ "offset" => cursor,
273
+ "skipped" => input[cursor...skip_start],
274
+ "resumed_at" => skip_start,
275
+ }
276
+ items << root_atom(entry_name).flatten(value)
277
+ cursor = resume
278
+ rounds += 1
279
+ until cursor >= max
280
+ attempt = try_iteration(body_atom, input, cursor)
281
+ break unless attempt && attempt[1] > cursor
282
+
283
+ items << root_atom(entry_name).flatten(attempt[0])
284
+ cursor = attempt[1]
285
+ end
286
+ break if cursor >= max
287
+ end
288
+ [items, resyncs]
289
+ end
290
+
291
+ # Apply the body atom at one position: [value, end_pos] on a
292
+ # progressing match, nil otherwise.
293
+ def try_iteration(atom, input, pos)
294
+ source = Parsanol::Source.new(input)
295
+ source.bytepos = pos
296
+ context = Parsanol::Atoms::Context.new(nil, parser_class: atom.class)
297
+ success, value = atom.apply(source, context, false)
298
+ success && source.bytepos > pos ? [value, source.bytepos] : nil
299
+ end
300
+
301
+ # The root rule's repetition-body rule name, when the grammar is
302
+ # a repetition spine (root = rep of a single rule reference).
303
+ # Conservative: anything but rep/opt/cap wrapping a bare ref
304
+ # returns nil and resync is skipped.
305
+ SPINE_UNWRAP_KINDS = %i[rep opt cap].freeze
306
+
307
+ def spine_body_rule(entry_name)
308
+ root_rule = entry(entry_name)["root"]
309
+ node = compiler.document.rules[root_rule.to_sym] ||
310
+ compiler.document.rules[root_rule]
311
+ while node.is_a?(Parsanol::PARG::Node) &&
312
+ SPINE_UNWRAP_KINDS.include?(node.kind)
313
+ node = node.a
314
+ end
315
+ node.is_a?(Parsanol::PARG::Node) && node.kind == :ref ? node.a : nil
229
316
  end
230
317
 
231
318
  # R1.5 (TODO.sota/1): the deepest repetition prefix the failed
@@ -244,14 +331,33 @@ module Parsanol
244
331
  { root_name => flattened }
245
332
  end
246
333
 
334
+ # Recovery error wire — the rs engine's F7 shape (offset, line,
335
+ # column, expected at the deepest position, ranked distinct
336
+ # positions deepest-first), computed from the interpreter's cause
337
+ # tree. `message` rides along for consumers that surface the
338
+ # rendered text (lutaml-lml digs line/column from it).
339
+ # Parity with parsanol::portable::parser::MAX_RANKED_FAILURES.
340
+ MAX_RANKED_FAILURES = 8
341
+
247
342
  def recovery_error(err)
248
343
  cause = err.parse_failure_cause
249
- # The raised cause can sit shallower than the real failure (a
250
- # consume-all leftover reported at the partial run's end);
251
- # recovery annotations carry the DEEPEST position in the cause
252
- # tree — the same semantics as the rs engine's failure wire.
253
- { "offset" => cause && deepest_position(cause),
254
- "message" => err.message }
344
+ return { "offset" => nil, "message" => err.message } unless cause
345
+
346
+ ranks = collect_ranks(cause)
347
+ offset = ranks.keys.max
348
+ line, column = cause.source&.line_and_column(offset)
349
+ {
350
+ "offset" => offset,
351
+ "line" => line,
352
+ "column" => column,
353
+ # The deepest entry mirrors ranked's head — same as the rs
354
+ # wire, where failure_wire and ranked[0] agree.
355
+ "expected" => ranks[offset] || [],
356
+ "ranked" => ranks.sort_by { |pos, _| -pos }
357
+ .first(MAX_RANKED_FAILURES)
358
+ .map { |pos, labels| [pos, labels] },
359
+ "message" => err.message,
360
+ }
255
361
  end
256
362
 
257
363
  def deepest_position(cause)
@@ -264,6 +370,22 @@ module Parsanol
264
370
  max
265
371
  end
266
372
 
373
+ # Distinct failure positions with the labels expected at each —
374
+ # every cause node contributes its position and message; deeper
375
+ # duplicates of the same position merge. Capped like the rs wire
376
+ # (MAX_RANKED_FAILURES) at the reader, deepest first.
377
+ def collect_ranks(cause, into = {})
378
+ labels = into[cause.position] ||= []
379
+ text = cause.message.to_s
380
+ labels << text unless labels.include?(text)
381
+ Array(cause.children).each do |child|
382
+ next if child.nil?
383
+
384
+ collect_ranks(child, into)
385
+ end
386
+ into
387
+ end
388
+
267
389
  # Journaling re-parse — always on the Ruby engine (journaling
268
390
  # rides the interpreter's diagnostic reparse; the native engine
269
391
  # has no journal). Tree parity with the native engine is enforced
@@ -40,6 +40,10 @@ module Parsanol
40
40
  end
41
41
  end
42
42
 
43
+ # Read access for recovery tooling (the artifact's inferred
44
+ # resync walks the rule graph to find the spine body).
45
+ attr_reader :document
46
+
43
47
  def initialize(document, tables_dir, bindings: nil)
44
48
  @document = document
45
49
  @tables_dir = tables_dir
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.103"
4
+ VERSION = "1.3.105"
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.103
4
+ version: 1.3.105
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-21 00:00:00.000000000 Z
11
+ date: 2026-10-22 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