parsanol 1.3.98-arm-linux → 1.3.100-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 +4 -4
- data/lib/parsanol/3.2/parsanol_native.so +2 -2
- data/lib/parsanol/3.3/parsanol_native.so +2 -2
- data/lib/parsanol/3.4/parsanol_native.so +2 -2
- data/lib/parsanol/4.0/parsanol_native.so +2 -2
- data/lib/parsanol/atoms/base.rb +7 -1
- data/lib/parsanol/error_reporter/deepest.rb +4 -0
- data/lib/parsanol/native/libparsanol.so +0 -0
- data/lib/parsanol/native.rb +4 -2
- data/lib/parsanol/parg/artifact.rb +7 -0
- data/lib/parsanol/parg/bindings.rb +3 -0
- data/lib/parsanol/parg/compiler.rb +136 -6
- data/lib/parsanol/parg/document.rb +1 -1
- data/lib/parsanol/parg/parser.rb +6 -1
- data/lib/parsanol/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b89e681adaa5fac7a4145ea6001417cb764a1970f80723bffedafaf6332143b0
|
|
4
|
+
data.tar.gz: 9d651ebffc31cdeb3105ed71758ad2f25add9add525f46f5cc1fd74e92baddc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd4ab064d5b6a4d47cb1d70955b0351dd1c254adb4dca64bff7886b66687c02b78a4260248bca8a89c5271fb04b1b592ddc17a994824593de0c23807299747fd
|
|
7
|
+
data.tar.gz: f876a042b577c4fd3b7408fd62c86fc5b7d33541db18d91996d0d02c6197e1fa66d6561cd2c670a120d7a2b6816ad8d2a4ea5a28fb4fd15fc91e2b7f7e922670
|
|
@@ -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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34547268 bytes
|
|
4
|
+
sha256: 5ffe08395738b583a3472e832256ff294510921a25ff6e53dc90c951905191ec
|
|
@@ -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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34732408 bytes
|
|
4
|
+
sha256: c187d2bd208b1d8aef9c66f4d3188496789e48b93dc6fb59c65cbbfcd300a6ca
|
|
@@ -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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34726616 bytes
|
|
4
|
+
sha256: 14dcfeb7a0bad418583ac54f7430c5212c273c5df0a3e0ae3eaba8ed14156907
|
|
@@ -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:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 34736332 bytes
|
|
4
|
+
sha256: 8eb3b9649e9070ee5ec045863c45cdc50a4ae76faf0c0c45fa6f26f15e503db4
|
data/lib/parsanol/atoms/base.rb
CHANGED
|
@@ -236,9 +236,15 @@ module Parsanol
|
|
|
236
236
|
end
|
|
237
237
|
|
|
238
238
|
# Reports detailed error by reparsing with reporter.
|
|
239
|
+
#
|
|
240
|
+
# The default reporter is Deepest (parsanol-ruby#265): the native
|
|
241
|
+
# engine's failure diagnostics report the furthest failure, and a
|
|
242
|
+
# consumer's file:line:col degrades to "line 1 char 1" when the
|
|
243
|
+
# interpreter raises the outermost sequence instead. Explicit
|
|
244
|
+
# reporters (Tree for full cause trees) are honored unchanged.
|
|
239
245
|
def report_detailed_error(input, consume_all, reporter, _initial_error)
|
|
240
246
|
input.bytepos = 0
|
|
241
|
-
error_reporter = reporter || Parsanol::ErrorReporter::
|
|
247
|
+
error_reporter = reporter || Parsanol::ErrorReporter::Deepest.new
|
|
242
248
|
success, cause = run_with_context(input, error_reporter, consume_all)
|
|
243
249
|
|
|
244
250
|
# Second parse should also fail
|
|
@@ -98,12 +98,16 @@ module Parsanol
|
|
|
98
98
|
# @return [Array<Integer, Parsanol::Cause>] [depth, deepest_leaf]
|
|
99
99
|
#
|
|
100
100
|
def find_deepest_leaf(node, rank = 0)
|
|
101
|
+
return [rank, node] if node.nil?
|
|
102
|
+
|
|
101
103
|
best_node = node
|
|
102
104
|
best_rank = rank
|
|
103
105
|
|
|
104
106
|
kids = node.children
|
|
105
107
|
if kids && !kids.empty?
|
|
106
108
|
kids.each do |kid|
|
|
109
|
+
next if kid.nil?
|
|
110
|
+
|
|
107
111
|
kid_rank, kid_node = find_deepest_leaf(kid, rank + 1)
|
|
108
112
|
|
|
109
113
|
if kid_rank > best_rank
|
|
Binary file
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -282,8 +282,10 @@ module Parsanol
|
|
|
282
282
|
if parsanol_grammar
|
|
283
283
|
# No native diagnostics (e.g. incomplete-input errors): one
|
|
284
284
|
# interpreter pass with the reporter attached — a success
|
|
285
|
-
# recovers the tree, a failure raises the cause tree.
|
|
286
|
-
|
|
285
|
+
# recovers the tree, a failure raises the cause tree. Deepest
|
|
286
|
+
# keeps the fallback's diagnostics at parity with the direct
|
|
287
|
+
# interpreter path (parsanol-ruby#265).
|
|
288
|
+
reporter = Parsanol::ErrorReporter::Deepest.new
|
|
287
289
|
source = Parsanol::Source.new(input)
|
|
288
290
|
success, value = grammar.run_with_context(source, reporter, true)
|
|
289
291
|
return grammar.finalize_result(value) if success
|
|
@@ -165,6 +165,13 @@ module Parsanol
|
|
|
165
165
|
case hash["kind"]
|
|
166
166
|
when "reject"
|
|
167
167
|
"test #{hash['input'].inspect}: expected the input to be rejected"
|
|
168
|
+
when "shape"
|
|
169
|
+
actual = Bindings.canonical(shape)
|
|
170
|
+
expected = Bindings.canonical(hash["expect"])
|
|
171
|
+
next if actual == expected
|
|
172
|
+
|
|
173
|
+
"test #{hash['input'].inspect}: expected shape #{expected.inspect}, " \
|
|
174
|
+
"got #{actual.inspect}"
|
|
168
175
|
when "example"
|
|
169
176
|
bound = apply_bindings(entry_name, shape)
|
|
170
177
|
expect = hash["expect"].to_h { |key, value| [key.to_sym, value] }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Parsanol
|
|
4
6
|
module PARG
|
|
5
7
|
# Applies an entry's artifact bindings to a parsanol-shape parse tree,
|
|
@@ -32,6 +34,7 @@ module Parsanol
|
|
|
32
34
|
when Hash then value.to_h { |key, item| [key.to_s, canonical(item)] }
|
|
33
35
|
when Array then value.map { |item| canonical(item) }
|
|
34
36
|
when nil, true, false, Integer then value
|
|
37
|
+
when Float then JSON.generate(value) # B6: the JSON number text
|
|
35
38
|
else String(value)
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -53,8 +53,13 @@ module Parsanol
|
|
|
53
53
|
@dynamic = false
|
|
54
54
|
@tables = {}
|
|
55
55
|
@warnings = []
|
|
56
|
+
@expanded = false
|
|
56
57
|
end
|
|
57
58
|
|
|
59
|
+
# Reserved prefix for the tier rules synthesized by the direct
|
|
60
|
+
# left-recursion expansion (see expand_left_recursion!).
|
|
61
|
+
LEFT_RECURSION_PREFIX = "__left_"
|
|
62
|
+
|
|
58
63
|
# QoL shorthands (ABNF core rules): materialized on reference when
|
|
59
64
|
# the grammar does not define its own rule of that name.
|
|
60
65
|
BUILTIN_CLASSES = {
|
|
@@ -67,6 +72,7 @@ module Parsanol
|
|
|
67
72
|
}.freeze
|
|
68
73
|
|
|
69
74
|
def compile
|
|
75
|
+
expand_left_recursion!
|
|
70
76
|
@document.rules.each_key { |name| atom_for(name) }
|
|
71
77
|
errors = Lints.errors(@document, self)
|
|
72
78
|
raise CompileError, errors.join("\n") unless errors.empty?
|
|
@@ -121,6 +127,7 @@ module Parsanol
|
|
|
121
127
|
end
|
|
122
128
|
|
|
123
129
|
def atom_for(name)
|
|
130
|
+
expand_left_recursion! unless @expanded
|
|
124
131
|
@atom_cache[name] ||= begin
|
|
125
132
|
atom = build_atom(fetch_rule(name))
|
|
126
133
|
# Atomic rules build without injection entirely: token-shaped
|
|
@@ -348,6 +355,106 @@ module Parsanol
|
|
|
348
355
|
# In-file grammar tests run at compile time: the build fails when an
|
|
349
356
|
# accept/example test does not parse or an example's expected capture
|
|
350
357
|
# pairs do not match the bound result.
|
|
358
|
+
# Direct left recursion (owner decision 2026-10-21, parsanol-ruby#251):
|
|
359
|
+
# rewrite `r = r op1 r / r op2 r / ... / primary` into a chain of
|
|
360
|
+
# ordinary left-folding tier rules — the ANTLR 4 model. Branches in
|
|
361
|
+
# source order are precedence tiers, earlier binding tighter; the
|
|
362
|
+
# non-recursive alternatives are the primary (loosest operand). The
|
|
363
|
+
# rewritten tiers are plain rules, so every engine inherits the
|
|
364
|
+
# feature through the unchanged artifact format and the fold value is
|
|
365
|
+
# exactly the flattening of the equivalent loop grammar (V1/V3).
|
|
366
|
+
# Indirect left recursion keeps its compile error.
|
|
367
|
+
def expand_left_recursion!
|
|
368
|
+
return if @expanded
|
|
369
|
+
|
|
370
|
+
@expanded = true
|
|
371
|
+
if @document.rules.any? { |name, _| name.start_with?(LEFT_RECURSION_PREFIX) }
|
|
372
|
+
raise CompileError,
|
|
373
|
+
"rule names beginning with #{LEFT_RECURSION_PREFIX.inspect} are reserved " \
|
|
374
|
+
"for the compiler's left-recursion expansion"
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Snapshot: expansion adds tier rules to @document.rules.
|
|
378
|
+
@document.rules.dup.each_key do |name|
|
|
379
|
+
body = @document.rules[name]
|
|
380
|
+
next unless left_recursive_body?(name, body)
|
|
381
|
+
|
|
382
|
+
expand_left_recursive_rule(name, body)
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def left_recursive_body?(name, body)
|
|
387
|
+
branches = body.kind == :alt ? body.a : [body]
|
|
388
|
+
branches.any? { |branch| leftmost_refs(branch, foldable: true).include?(name) }
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def expand_left_recursive_rule(name, body)
|
|
392
|
+
branches = body.kind == :alt ? body.a : [body]
|
|
393
|
+
left, primary =
|
|
394
|
+
branches.partition { |branch| leftmost_refs(branch, foldable: true).include?(name) }
|
|
395
|
+
if primary.empty?
|
|
396
|
+
raise CompileError,
|
|
397
|
+
"rule #{name.inspect} is left-recursive and has no " \
|
|
398
|
+
"non-recursive alternative to anchor the rewrite"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
rules = @document.rules
|
|
402
|
+
prev = "#{LEFT_RECURSION_PREFIX}#{name}_primary"
|
|
403
|
+
rules[prev] = primary.length == 1 ? primary.first : Node.new(:alt, primary)
|
|
404
|
+
left.each_with_index do |branch, index|
|
|
405
|
+
tier = "#{LEFT_RECURSION_PREFIX}#{name}_#{index + 1}"
|
|
406
|
+
suffix = left_recursive_suffix(name, branch)
|
|
407
|
+
suffix = substitute_refs(suffix, name, prev)
|
|
408
|
+
rules[tier] = Node.new(:seq, [Node.new(:ref, prev), Node.new(:rep, suffix, 0, nil)])
|
|
409
|
+
prev = tier
|
|
410
|
+
end
|
|
411
|
+
rules[name] = Node.new(:ref, prev)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# A left-recursive alternative must BEGIN with a bare reference to the
|
|
415
|
+
# rule itself; everything after that reference is the per-iteration
|
|
416
|
+
# suffix, which must consume input (a nullable suffix would loop
|
|
417
|
+
# forever at the fold).
|
|
418
|
+
def left_recursive_suffix(name, branch)
|
|
419
|
+
if branch.kind == :ref
|
|
420
|
+
raise CompileError,
|
|
421
|
+
"left-recursive alternative in rule #{name.inspect} consists only of the " \
|
|
422
|
+
"recursive reference and would loop forever"
|
|
423
|
+
end
|
|
424
|
+
unless branch.kind == :seq && (head = branch.a.first)&.kind == :ref && head.a == name
|
|
425
|
+
raise CompileError,
|
|
426
|
+
"left-recursive alternative in rule #{name.inspect} must begin with a " \
|
|
427
|
+
"bare reference to #{name.inspect} (write `#{name} <rest>`; captures and " \
|
|
428
|
+
"predicates around the leading reference are not supported)"
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
rest = branch.a[1..]
|
|
432
|
+
rest_node = rest.length == 1 ? rest.first : Node.new(:seq, rest)
|
|
433
|
+
if nullable?(rest_node)
|
|
434
|
+
raise CompileError,
|
|
435
|
+
"left-recursive alternative in rule #{name.inspect} can match empty after " \
|
|
436
|
+
"the leading reference and would loop forever at the fold"
|
|
437
|
+
end
|
|
438
|
+
rest_node
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def substitute_refs(node, from, to)
|
|
442
|
+
if node.kind == :ref
|
|
443
|
+
return Node.new(:ref, to) if node.a == from
|
|
444
|
+
|
|
445
|
+
return node
|
|
446
|
+
end
|
|
447
|
+
case node.kind
|
|
448
|
+
when :seq then Node.new(:seq, node.a.map { |child| substitute_refs(child, from, to) })
|
|
449
|
+
when :alt then Node.new(:alt, node.a.map { |child| substitute_refs(child, from, to) })
|
|
450
|
+
when :rep then Node.new(:rep, substitute_refs(node.a, from, to), node.b, node.c)
|
|
451
|
+
when :opt then Node.new(:opt, substitute_refs(node.a, from, to))
|
|
452
|
+
when :pred then Node.new(:pred, node.a, substitute_refs(node.b, from, to), node.c)
|
|
453
|
+
when :cap then Node.new(:cap, node.a, substitute_refs(node.b, from, to))
|
|
454
|
+
else node
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
351
458
|
def run_tests
|
|
352
459
|
return [] if @document.tests.empty?
|
|
353
460
|
|
|
@@ -360,6 +467,13 @@ module Parsanol
|
|
|
360
467
|
case test.kind
|
|
361
468
|
when :reject
|
|
362
469
|
"test #{test.input.inspect}: expected the input to be rejected"
|
|
470
|
+
when :shape
|
|
471
|
+
actual = Bindings.canonical(shape)
|
|
472
|
+
expected = Bindings.canonical(test.expect)
|
|
473
|
+
next if actual == expected
|
|
474
|
+
|
|
475
|
+
"test #{test.input.inspect}: expected shape #{expected.inspect}, " \
|
|
476
|
+
"got #{actual.inspect}"
|
|
363
477
|
when :example
|
|
364
478
|
bindings = binding_list(rule)
|
|
365
479
|
if bindings.empty?
|
|
@@ -704,19 +818,24 @@ module Parsanol
|
|
|
704
818
|
nil
|
|
705
819
|
end
|
|
706
820
|
|
|
707
|
-
|
|
821
|
+
# A self-reference under a predicate cannot anchor a fold (and the
|
|
822
|
+
# lookbehind validation rejects rule bodies outright), so the
|
|
823
|
+
# rewrite's scan stops there; cycle detection still walks through.
|
|
824
|
+
def leftmost_refs(node, foldable: false)
|
|
708
825
|
case node.kind
|
|
709
826
|
when :ref then [node.a]
|
|
710
827
|
when :seq
|
|
711
828
|
refs = []
|
|
712
829
|
node.a.each do |item|
|
|
713
|
-
refs.concat(leftmost_refs(item))
|
|
830
|
+
refs.concat(leftmost_refs(item, foldable: foldable))
|
|
714
831
|
break unless nullable?(item)
|
|
715
832
|
end
|
|
716
833
|
refs.uniq
|
|
717
|
-
when :alt then node.a.flat_map { |branch| leftmost_refs(branch) }.uniq
|
|
718
|
-
when :rep, :opt then leftmost_refs(node.a)
|
|
719
|
-
when :pred
|
|
834
|
+
when :alt then node.a.flat_map { |branch| leftmost_refs(branch, foldable: foldable) }.uniq
|
|
835
|
+
when :rep, :opt then leftmost_refs(node.a, foldable: foldable)
|
|
836
|
+
when :pred
|
|
837
|
+
foldable ? [] : leftmost_refs(node.b)
|
|
838
|
+
when :cap then leftmost_refs(node.b, foldable: foldable)
|
|
720
839
|
else []
|
|
721
840
|
end
|
|
722
841
|
end
|
|
@@ -790,6 +909,17 @@ module Parsanol
|
|
|
790
909
|
|
|
791
910
|
# ---- artifact envelope ----------------------------------------------
|
|
792
911
|
|
|
912
|
+
# Test expectations are JSON-shaped: pair expectations are maps with
|
|
913
|
+
# symbol keys; `shape` expectations may be any value. Normalize keys
|
|
914
|
+
# recursively so the envelope is plain JSON.
|
|
915
|
+
def normalize_expect(value)
|
|
916
|
+
case value
|
|
917
|
+
when Hash then value.to_h { |key, item| [key.to_s, normalize_expect(item)] }
|
|
918
|
+
when Array then value.map { |item| normalize_expect(item) }
|
|
919
|
+
else value
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
793
923
|
def build_envelope
|
|
794
924
|
entries = @document.entries.to_h do |name, rule|
|
|
795
925
|
[name, entry_envelope(rule)]
|
|
@@ -819,7 +949,7 @@ module Parsanol
|
|
|
819
949
|
"entry" => test.entry || (@document.own_entries.first if @document.own_entries.size == 1),
|
|
820
950
|
"kind" => test.kind.to_s,
|
|
821
951
|
"input" => test.input,
|
|
822
|
-
"expect" => test.expect
|
|
952
|
+
"expect" => normalize_expect(test.expect),
|
|
823
953
|
}
|
|
824
954
|
end,
|
|
825
955
|
"docs" => @document.docs,
|
|
@@ -11,7 +11,7 @@ module Parsanol
|
|
|
11
11
|
# `capture` = phase 1 (grammar layer): asserts the raw capture tree,
|
|
12
12
|
# no bindings section needed. `example` = phase 2 (bindings layer):
|
|
13
13
|
# asserts the bound, preprocessed attribute hash.
|
|
14
|
-
TEST_KINDS = %i[accept reject example capture].freeze
|
|
14
|
+
TEST_KINDS = %i[accept reject example capture shape].freeze
|
|
15
15
|
# Test kinds whose expectations are `{ key: value }` pairs.
|
|
16
16
|
PAIR_KINDS = %w[example capture].freeze
|
|
17
17
|
|
data/lib/parsanol/parg/parser.rb
CHANGED
|
@@ -83,7 +83,12 @@ module Parsanol
|
|
|
83
83
|
kind = ident.value
|
|
84
84
|
input = unquote(expect(:str).value)
|
|
85
85
|
expect_pairs = {}
|
|
86
|
-
if
|
|
86
|
+
if kind == "shape"
|
|
87
|
+
# Whole-tree assertion: the expectation is an arbitrary value
|
|
88
|
+
# (text, array, object), not a capture-name map.
|
|
89
|
+
expect_pairs = parse_test_value
|
|
90
|
+
elsif Document::PAIR_KINDS.include?(kind) &&
|
|
91
|
+
peek&.type == :punct && peek.value == "{"
|
|
87
92
|
advance
|
|
88
93
|
skip_newlines
|
|
89
94
|
until peek&.type == :punct && peek.value == "}"
|
data/lib/parsanol/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.3.100
|
|
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-
|
|
11
|
+
date: 2026-10-21 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
|