parsanol 1.3.103 → 1.3.105
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/parg/artifact.rb +129 -7
- data/lib/parsanol/parg/compiler.rb +4 -0
- data/lib/parsanol/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3954375840e755bd28df5e62048c4ec4dc94ab56a5917c839e04901e95290c2f
|
|
4
|
+
data.tar.gz: ec2e66f50dab4a8e6cc30651d9b4fe6ec468e4441a1c62b697f3a28d61e86167
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 465b6dfa7765cf143cc78bd7eab75bf8c8c7bf9d20a859930a868be5563de33e44f50e2844a519f9179d4d1dbe9836a4392042c573cf58dbd106e110bf67a26c
|
|
7
|
+
data.tar.gz: 06c14f1c1111606323b4ec1e3c895a88fbb9109440158ed68eab0094840c9ce38e366bc2e049653790257873f720755196087d16abdc29aad3afbd8d07b3af54
|
|
@@ -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" =>
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
data/lib/parsanol/version.rb
CHANGED