parsanol 1.3.105-arm-linux → 1.3.107-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: 1b2a5b84cfa562c1535e789fbb1b255eacbed37ba883009bebc3cdcdd6ee5bf1
4
- data.tar.gz: cff2ac2a93078d48366061fc0e929ce7a959db8ee5200f6202b8ee56bd33ab1e
3
+ metadata.gz: 82264b2e58bdcd642b09e1b7d39e5848685758e4be7b5d04c3d1ccbf8891e1f0
4
+ data.tar.gz: 4abb4bd1c4195f5efb724a23f14be6e24451c284f25748eec7e914129280d4a8
5
5
  SHA512:
6
- metadata.gz: 46131326bda0bcc9f9304c88dcf935b67675f9d18aea638bff38334b9f245fba87aad2b7a6433df41349af6dec63dae68e9c33c2b7d287748cc783b26bdc3a9b
7
- data.tar.gz: c75b6c38c1f04df3e9de16313c4bf5bbecd57c2490a57c20704b6144b593e884613f1353ae8317975e381119542f831bf6161d82fd53f54edd802b2d96cc50e2
6
+ metadata.gz: 31541ed28f1e205cb5abcbb0d90f59dbd18ebc9734026f458a2458d8c1cdf97b6a17686c7dc1c9bf6f3fccc5342dde8d078561d403f32dadae225b11edef0171
7
+ data.tar.gz: 499beed8e7e159aaf36223cc318f4fea3f40f69456b3fb48c5ce1278bd592cf323470905d165dfb22777ca5e607cb96a17de16c039b56395185a42dfac8a1a08
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/3.2/parsanol_native.so
3
- size: 34428344 bytes
4
- sha256: 82db84475e85ac3ef05019f2004550c38f91468e1354b9f951163d4b5c5d44e5
3
+ size: 34490424 bytes
4
+ sha256: a3cdb4f287af7a777278dc7ef4db062d9c3aa6b30e6667ffb26542d25215dec2
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/3.3/parsanol_native.so
3
- size: 34613524 bytes
4
- sha256: 1928bf70d15ea9f75289d47f0063597d0d2d529a46736cdf38950615b0a8fb7a
3
+ size: 34675616 bytes
4
+ sha256: 8ccc77dbd45591ffcd1cceabed8a2324593f3cdfbdd3acc463fbec81702381a8
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/3.4/parsanol_native.so
3
- size: 34607716 bytes
4
- sha256: 204869abbb95705c268e17b3fb9ec5f2f7cf25760d198c592ab122161bab66c3
3
+ size: 34669796 bytes
4
+ sha256: 5b8b78e7c01dca71d2abe00446475964e6bf894dacc0aadb427910ab80eebf38
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/4.0/parsanol_native.so
3
- size: 34617472 bytes
4
- sha256: 3ea07ee24359603075cd6150c5aa50850349aa5e7bb327bc164dd14ef04e441e
3
+ size: 34679512 bytes
4
+ sha256: 05dea26c9937b15e9f7f9ad8800d4cb29fc365a056640cecea6ac992d03092a3
Binary file
@@ -29,6 +29,16 @@ module Parsanol
29
29
  Parser.available?
30
30
  end
31
31
 
32
+ # Whether the reference walker is available (TODO.sota/8 B1): the
33
+ # `_parse_handle_reference` export ships with parsanol >= 0.18.18.
34
+ # Probed rather than assumed so a checkout whose extension predates
35
+ # the export can report the missing fifth fuzz path instead of
36
+ # raising NoMethodError mid-run.
37
+ def reference_available?
38
+ available? && Parser.extension_loaded? &&
39
+ respond_to?(:_parse_handle_reference)
40
+ end
41
+
32
42
  # Parse input with a Ruby grammar, returning clean AST with lazy line/column.
33
43
  #
34
44
  # Uses batch FFI format for maximum performance (3-5x faster than object-by-object).
@@ -355,6 +365,40 @@ module Parsanol
355
365
  raise_native_parse_error(e, grammar, input)
356
366
  end
357
367
  end
368
+
369
+ # Reference-walker path (TODO.sota/8 B1): the plain recursive
370
+ # relation — no VM, no memo tables, no fused scans, no terminal
371
+ # fast paths. Deliberately slow; the cross-engine differential
372
+ # fuzz runs it as an independent path, so a native failure here is
373
+ # a real disagreement and must not be rescued into a different
374
+ # engine's answer.
375
+ def parse_reference(grammar, input)
376
+ unless reference_available?
377
+ raise NotImplementedError,
378
+ "reference mode requires the native extension with " \
379
+ "parsanol >= 0.18.18"
380
+ end
381
+
382
+ handle = Parser.grammar_handle(grammar)
383
+
384
+ begin
385
+ BatchDecoder.decode_and_flatten(_parse_handle_reference(handle, input),
386
+ input, Parsanol::Slice)
387
+ rescue ArgumentError
388
+ # Handle dropped Rust-side (e.g. cache cleared): re-register once.
389
+ Parser.invalidate_handle(handle)
390
+ begin
391
+ BatchDecoder.decode_and_flatten(
392
+ _parse_handle_reference(Parser.grammar_handle(grammar), input),
393
+ input, Parsanol::Slice
394
+ )
395
+ rescue RuntimeError => e
396
+ raise_native_parse_error(e, grammar, input)
397
+ end
398
+ rescue RuntimeError => e
399
+ raise_native_parse_error(e, grammar, input)
400
+ end
401
+ end
358
402
  end
359
403
  end
360
404
  end
@@ -108,13 +108,24 @@ module Parsanol
108
108
  end
109
109
 
110
110
  root_atom(entry_name).parse(input)
111
+ when :reference
112
+ # The plain recursive relation (TODO.sota/8 B1): same shaping
113
+ # pipeline, no VM, no memo tables, no fused scans. Never falls
114
+ # back to :ruby — a reference result produced by another
115
+ # engine would defeat the differential. Deliberately slow.
116
+ unless Native.reference_available?
117
+ raise NotImplementedError, "reference mode requires the native " \
118
+ "extension with parsanol >= 0.18.18"
119
+ end
120
+
121
+ Native.parse_reference(root_atom(entry_name), input)
111
122
  when :ruby
112
123
  # Deepest-failure reporting parity with the native engine: the
113
124
  # default Tree reporter keeps the most recent (root) failure,
114
125
  # which reports position 1:1 for grammars with entry wrappers.
115
126
  root_atom(entry_name).parse(input, reporter: ErrorReporter::Deepest.new)
116
127
  else
117
- raise ArgumentError, "unknown mode #{mode.inspect} (use :native or :ruby)"
128
+ raise ArgumentError, "unknown mode #{mode.inspect} (use :native, :reference or :ruby)"
118
129
  end
119
130
  end
120
131
 
@@ -250,44 +261,82 @@ module Parsanol
250
261
  return [[], []] unless body_rule
251
262
 
252
263
  body_atom = compiler.atom_for(body_rule)
264
+ # R2 declared form: the baked resync pattern owns the skip
265
+ # spans (author-deterministic — no trial scanning). Falls back
266
+ # to the inferred trial scan when the rule declares none.
267
+ pattern_atom = compiler.resync_atom_for(body_rule) ||
268
+ compiler.resync_atom_for(entry(entry_name)["root"])
253
269
  max = input.length
254
270
  resyncs = []
255
271
  items = []
256
272
  rounds = 0
257
273
  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
274
+ attempt = try_iteration(body_atom, input, cursor)
275
+ if attempt
276
+ items << root_atom(entry_name).flatten(attempt[0])
277
+ cursor = attempt[1]
278
+ until cursor >= max
279
+ attempt = try_iteration(body_atom, input, cursor)
280
+ break unless attempt
281
+
282
+ items << root_atom(entry_name).flatten(attempt[0])
283
+ cursor = attempt[1]
284
+ end
285
+ break if cursor >= max
286
+ end
287
+ if pattern_atom
288
+ # Declared: the pattern match (gap + delimiter) is the
289
+ # annotated error span.
290
+ span = scan_pattern(pattern_atom, input, cursor)
291
+ break unless span
292
+
293
+ resume = span[1]
294
+ resyncs << {
295
+ "offset" => cursor,
296
+ "skipped" => input[cursor...resume],
297
+ "resumed_at" => resume,
298
+ }
299
+ else
300
+ # Inferred: scan for the next viable iteration start.
301
+ found = nil
302
+ scan = cursor
303
+ while scan < max
304
+ attempt = try_iteration(body_atom, input, scan)
305
+ if attempt && attempt[1] > scan
306
+ found = [scan, attempt[1], attempt[0]]
307
+ break
308
+ end
309
+ scan += 1
265
310
  end
266
- scan += 1
311
+ break unless found
312
+
313
+ skip_start, resume, value = found
314
+ resyncs << {
315
+ "offset" => cursor,
316
+ "skipped" => input[cursor...skip_start],
317
+ "resumed_at" => skip_start,
318
+ }
319
+ items << root_atom(entry_name).flatten(value)
267
320
  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
321
  cursor = resume
278
322
  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
323
  end
288
324
  [items, resyncs]
289
325
  end
290
326
 
327
+ # Scan forward from `from` for the first position where the
328
+ # declared pattern matches with progress: [match_start, match_end].
329
+ def scan_pattern(pattern_atom, input, from)
330
+ scan = from
331
+ while scan < input.length
332
+ attempt = try_iteration(pattern_atom, input, scan)
333
+ return [scan, attempt[1]] if attempt
334
+
335
+ scan += 1
336
+ end
337
+ nil
338
+ end
339
+
291
340
  # Apply the body atom at one position: [value, end_pos] on a
292
341
  # progressing match, nil otherwise.
293
342
  def try_iteration(atom, input, pos)
@@ -130,6 +130,17 @@ module Parsanol
130
130
  "(bind the class before compiling, or fix the custom declaration)"
131
131
  end
132
132
 
133
+ # The declared resync skip pattern for a rule, built as a raw
134
+ # atom (no trivia injection). Nil when the rule declares none.
135
+ def resync_atom_for(rule)
136
+ pattern = @document.resyncs[rule.to_sym] || @document.resyncs[rule.to_s]
137
+ return nil unless pattern
138
+
139
+ expand_left_recursion! unless @expanded
140
+ @resync_cache ||= {}
141
+ @resync_cache[rule.to_sym] ||= build_atom(pattern)
142
+ end
143
+
133
144
  def atom_for(name)
134
145
  expand_left_recursion! unless @expanded
135
146
  @atom_cache[name] ||= begin
@@ -925,6 +936,12 @@ module Parsanol
925
936
  end
926
937
 
927
938
  def build_envelope
939
+ @document.resyncs.each_key do |target|
940
+ unless @document.rules.key?(target.to_sym) ||
941
+ @document.rules.key?(target.to_s)
942
+ raise CompileError, "resync declared for unknown rule #{target.inspect}"
943
+ end
944
+ end
928
945
  entries = @document.entries.to_h do |name, rule|
929
946
  [name, entry_envelope(rule)]
930
947
  end
@@ -980,11 +997,20 @@ module Parsanol
980
997
  end
981
998
 
982
999
  def entry_envelope(rule)
983
- {
1000
+ envelope = {
984
1001
  "root" => rule,
985
1002
  "grammar" => JSON.parse(portable_json(rule)),
986
1003
  "bindings" => binding_list(rule),
987
1004
  }
1005
+ # R2 declared resync: the skip pattern bakes as its own
1006
+ # mini-grammar (same portable atoms shape) so every engine
1007
+ # matches identical error spans. Raw match — no trivia
1008
+ # injection: the pattern delimits junk, it is not syntax.
1009
+ if (pattern = @document.resyncs[rule.to_sym] || @document.resyncs[rule.to_s])
1010
+ envelope["resync"] =
1011
+ JSON.parse(Parsanol::Native::Parser.serialize_grammar(build_atom(pattern)))
1012
+ end
1013
+ envelope
988
1014
  end
989
1015
 
990
1016
  def portable_json(rule)
@@ -19,6 +19,9 @@ module Parsanol
19
19
 
20
20
  attr_accessor :grammar_name, :version, :source, :uses, :skip_declaration, :skip_source,
21
21
  :atomic_rules, :skip_capture_key
22
+ # R2 declared resync: rule name -> skip pattern (the span matched
23
+ # at a recovery point is consumed as annotated error text).
24
+ attr_reader :resyncs
22
25
  attr_reader :rules, :bindings, :preprocess, :entries, :docs, :tests, :render, :derive,
23
26
  :customs, :states
24
27
  # Entries authored in this file (pre-import); nil-entry tests resolve
@@ -45,6 +48,10 @@ module Parsanol
45
48
  # suppressed inside their bodies (token-shaped rules match
46
49
  # contiguously).
47
50
  @atomic_rules = []
51
+ # R2 declared resync: rule -> skip pattern. Recovery consumes
52
+ # each pattern match at a failure point as annotated error text
53
+ # and resumes the iteration after it.
54
+ @resyncs = {}
48
55
  end
49
56
 
50
57
  def validate!
@@ -257,6 +257,22 @@ module Parsanol
257
257
  skip_newlines
258
258
  next
259
259
  end
260
+ if peek&.type == :ident && peek.value == "resync"
261
+ advance
262
+ target = rule_name
263
+ punct("=")
264
+ node = parse_choice
265
+ if document.resyncs.key?(target.to_sym)
266
+ raise ParseError, "duplicate resync declaration for #{target.inspect}"
267
+ end
268
+
269
+ # The target rule may be declared later in the block
270
+ # (forward reference, like entry roots); the compiler
271
+ # rejects unknown targets at compile time.
272
+ document.resyncs[target.to_sym] = validate_resync_node(node)
273
+ skip_newlines
274
+ next
275
+ end
260
276
  if peek&.type == :ident && STATE_KEYWORDS.include?(peek.value)
261
277
  keyword = advance.value
262
278
  slot = ident.value
@@ -300,6 +316,30 @@ module Parsanol
300
316
  end
301
317
  end
302
318
 
319
+ # A resync pattern may be any expression EXCEPT captures and
320
+ # state operations: the span it matches is reported as skipped
321
+ # error text, so bindings and slots have no meaning inside it,
322
+ # and a capture would silently drop its data.
323
+ def validate_resync_node(node)
324
+ case node.kind
325
+ when :cap
326
+ raise ParseError,
327
+ "resync pattern may not contain captures (the matched " \
328
+ "span is annotated error text), got :cap"
329
+ when :seq, :alt
330
+ node.a.each { |child| validate_resync_node(child) }
331
+ node
332
+ when :rep, :opt
333
+ validate_resync_node(node.a)
334
+ node
335
+ when :pred
336
+ validate_resync_node(node.b)
337
+ node
338
+ else
339
+ node
340
+ end
341
+ end
342
+
303
343
  def validate_skip_node(node)
304
344
  case node.kind
305
345
  when :ref
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.105"
4
+ VERSION = "1.3.107"
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.105
4
+ version: 1.3.107
5
5
  platform: arm-linux
6
6
  authors:
7
7
  - Ribose Inc.