parsanol 1.3.116 → 1.3.118
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/Cargo.lock +4 -4
- data/lib/parsanol/parg/artifact.rb +6 -0
- data/lib/parsanol/parg/bindings.rb +70 -2
- data/lib/parsanol/parg/compiler.rb +28 -5
- data/lib/parsanol/parg/parser.rb +8 -3
- 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: 6468b7f70f6c5e61ea1e3661e9a052206730f7f07f71fb9ec9e57777fe777a6d
|
|
4
|
+
data.tar.gz: 17e99bf63b7b0001499e2844c93e4aabf14f64760a85cfc79a6fdad5c290e51e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90a1d39c526c512b4d7a55bc328b0530a92ae010c4a4febb91cc8b5408d0d070c20ef4e2cda81695c739fb94bdd29421434eb806c52b6ac060ac85fd8380c615
|
|
7
|
+
data.tar.gz: '083ed02a83cfbbbe16ef6bd3ec922229a35c52f9d81935458b86fbce4433264038ad390ed958b4602b0a414a7aad7dadf06e052e1fdb9f40c935465c74665308'
|
data/Cargo.lock
CHANGED
|
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "parsanol"
|
|
310
|
-
version = "0.19.
|
|
310
|
+
version = "0.19.15"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "f14e6d53cab2665d789adc8e28ff2181899501ef56afc4d65100f09a6d9c8101"
|
|
313
313
|
dependencies = [
|
|
314
314
|
"ahash",
|
|
315
315
|
"getrandom 0.3.4",
|
|
@@ -326,9 +326,9 @@ dependencies = [
|
|
|
326
326
|
|
|
327
327
|
[[package]]
|
|
328
328
|
name = "parsanol-derive"
|
|
329
|
-
version = "0.19.
|
|
329
|
+
version = "0.19.15"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "a5066e2da7badb25efb562dbebb6d0fc287e6e4dc7bd06b1da4b502bf349d94d"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
|
@@ -86,6 +86,12 @@ module Parsanol
|
|
|
86
86
|
|
|
87
87
|
def compiler_version = envelope["compiler_version"]
|
|
88
88
|
|
|
89
|
+
# The behavior fingerprint (ruby#377): stable across producer
|
|
90
|
+
# metadata, so freshness gates can tell "re-baked by a newer
|
|
91
|
+
# compiler" from "the grammar changed". nil on artifacts baked
|
|
92
|
+
# before the field existed.
|
|
93
|
+
def semantics_hash = envelope["semantics_hash"]
|
|
94
|
+
|
|
89
95
|
def fresh_against?(source_text)
|
|
90
96
|
return false if source_hash.nil?
|
|
91
97
|
|
|
@@ -20,7 +20,7 @@ module Parsanol
|
|
|
20
20
|
assign_scalar(out, artifact, binding, captures)
|
|
21
21
|
end
|
|
22
22
|
arrays.group_by { |binding| binding["path"].split("[]").first }.each do |prefix, group|
|
|
23
|
-
assign_array(out, artifact, prefix, group, captures)
|
|
23
|
+
assign_array(out, artifact, prefix, group, captures, shape)
|
|
24
24
|
end
|
|
25
25
|
out
|
|
26
26
|
end
|
|
@@ -63,7 +63,32 @@ module Parsanol
|
|
|
63
63
|
out[leaf_key(binding)] = finalize(artifact, binding, value)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def assign_array(out, artifact, prefix, group, captures)
|
|
66
|
+
def assign_array(out, artifact, prefix, group, captures, shape = nil)
|
|
67
|
+
# Position-aware pairing (parsanol-ruby#381): when the shape
|
|
68
|
+
# nests the group's instances under the prefix key, each
|
|
69
|
+
# instance's subtree owns its captures — pairing by OCCURRENCE
|
|
70
|
+
# index landed a sparse capture (one that fires in only some
|
|
71
|
+
# instances) on the wrong instance. Per-instance collection
|
|
72
|
+
# fixes the pairing and is identical to the old flat pairing
|
|
73
|
+
# when every instance fires every capture. Captures outside
|
|
74
|
+
# any instance keep the legacy occurrence pairing.
|
|
75
|
+
instances = shape && group_instances(shape, prefix)
|
|
76
|
+
if instances
|
|
77
|
+
list = (out[prefix.to_sym] ||= [])
|
|
78
|
+
instances.each do |instance|
|
|
79
|
+
found = collect_captures(instance)
|
|
80
|
+
element = {}
|
|
81
|
+
group.each do |binding|
|
|
82
|
+
value = found[binding["capture"].to_sym].first
|
|
83
|
+
next if value.nil?
|
|
84
|
+
|
|
85
|
+
element[leaf_key(binding)] = finalize(artifact, binding, value)
|
|
86
|
+
end
|
|
87
|
+
list << element
|
|
88
|
+
end
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
|
|
67
92
|
count = group.map { |binding| captures[binding["capture"].to_sym].length }.max || 0
|
|
68
93
|
return if count.zero?
|
|
69
94
|
|
|
@@ -82,6 +107,27 @@ module Parsanol
|
|
|
82
107
|
end
|
|
83
108
|
end
|
|
84
109
|
|
|
110
|
+
# The `prefix[]` group's instance subtrees: the first array under
|
|
111
|
+
# a key naming the prefix, walked in collect_captures' BFS order.
|
|
112
|
+
# A repetition-root entry's shape IS the instances array (the wire
|
|
113
|
+
# has no key for it) — that is the fallback when no key matches.
|
|
114
|
+
def group_instances(shape, prefix)
|
|
115
|
+
queue = [shape]
|
|
116
|
+
until queue.empty?
|
|
117
|
+
node = queue.shift
|
|
118
|
+
case node
|
|
119
|
+
when Hash
|
|
120
|
+
node.each do |key, value|
|
|
121
|
+
return value if key.to_s == prefix && value.is_a?(Array)
|
|
122
|
+
|
|
123
|
+
queue << value if value.is_a?(Hash) || value.is_a?(Array)
|
|
124
|
+
end
|
|
125
|
+
when Array then queue.concat(node)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
shape if shape.is_a?(Array) && shape.any?(Hash)
|
|
129
|
+
end
|
|
130
|
+
|
|
85
131
|
def validate_path!(binding)
|
|
86
132
|
path = binding["path"].to_s
|
|
87
133
|
return unless path.include?(".") || path.include?("[")
|
|
@@ -139,9 +185,31 @@ module Parsanol
|
|
|
139
185
|
|
|
140
186
|
value.to_s
|
|
141
187
|
when "boolean" then value == true || value.to_s.casecmp("true").zero?
|
|
188
|
+
when "any"
|
|
189
|
+
# The ONE plain shape on every engine (ruby#383): scalars and
|
|
190
|
+
# capture leaves render as their text, named groups as plain
|
|
191
|
+
# string-keyed hashes, repeats as arrays — so (any)-bound keys
|
|
192
|
+
# survive a cross-engine gate without consumer shims.
|
|
193
|
+
plain(value)
|
|
142
194
|
else value
|
|
143
195
|
end
|
|
144
196
|
end
|
|
197
|
+
|
|
198
|
+
# The engine-neutral reduction behind (any): capture leaves
|
|
199
|
+
# (Parsanol::Slice, native wire objects) and scalars render as
|
|
200
|
+
# their text, hashes as string-keyed plain hashes, arrays
|
|
201
|
+
# recurse. Nil passes through (absent captures stay absent —
|
|
202
|
+
# assign_scalar/assign_array skip them upstream).
|
|
203
|
+
def plain(value)
|
|
204
|
+
case value
|
|
205
|
+
when nil then nil
|
|
206
|
+
when String then value
|
|
207
|
+
when Hash
|
|
208
|
+
value.to_h { |key, item| [key.to_s, plain(item)] }
|
|
209
|
+
when Array then value.map { |item| plain(item) }
|
|
210
|
+
else value.to_s
|
|
211
|
+
end
|
|
212
|
+
end
|
|
145
213
|
end
|
|
146
214
|
end
|
|
147
215
|
end
|
|
@@ -32,6 +32,18 @@ module Parsanol
|
|
|
32
32
|
"sha256:#{Digest::SHA256.hexdigest(JSON.generate(canonicalize(payload)))}"
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# Producer metadata: the checksum pins the exact producer, but the
|
|
36
|
+
# semantics hash (ruby#377) skips these fields so a re-bake by a
|
|
37
|
+
# newer compiler — or a different Ruby patch stamping
|
|
38
|
+
# unicode_tables — is byte-stable when the grammar's behavior is
|
|
39
|
+
# unchanged. Mirrors the cross-engine parity view.
|
|
40
|
+
SEMANTICS_EXCLUDED = %w[checksum compiler_version unicode_tables semantics_hash].freeze
|
|
41
|
+
|
|
42
|
+
def self.semantics_hash(envelope)
|
|
43
|
+
payload = envelope.except(*SEMANTICS_EXCLUDED)
|
|
44
|
+
"sha256:#{Digest::SHA256.hexdigest(JSON.generate(canonicalize(payload)))}"
|
|
45
|
+
end
|
|
46
|
+
|
|
35
47
|
def self.canonicalize(obj)
|
|
36
48
|
case obj
|
|
37
49
|
when Hash then obj.sort.to_h.transform_values { |value| canonicalize(value) }
|
|
@@ -137,14 +149,24 @@ module Parsanol
|
|
|
137
149
|
|
|
138
150
|
artifact = Artifact.from_json(JSON.generate(envelope), tables_dir: tables_dir)
|
|
139
151
|
default = envelope["default_entry"] || artifact.entries.first
|
|
152
|
+
# `Native.available?` is true under either engine tier, but the
|
|
153
|
+
# JSON-grammar fast path rides the MRI C-API extension's
|
|
154
|
+
# `_parse_raw` — an ffi-cdylib install is available without it.
|
|
155
|
+
# Probe rather than assume (same doctrine as
|
|
156
|
+
# Native.reference_available?): without the C-API symbol the
|
|
157
|
+
# self-test runs through the artifact's own parse lane, which
|
|
158
|
+
# picks native-or-pure-Ruby honestly instead of raising
|
|
159
|
+
# NoMethodError mid-run.
|
|
160
|
+
json_capable = Native.respond_to?(:_parse_raw)
|
|
140
161
|
grammars = {}
|
|
141
162
|
tests.filter_map do |test|
|
|
142
163
|
entry = test["entry"] || default
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
parse_with =
|
|
165
|
+
if json_capable
|
|
166
|
+
grammar_json = (grammars[entry] ||= JSON.generate(artifact.entry(entry)["grammar"]))
|
|
167
|
+
->(input) { Native.parse_json_grammar(grammar_json, input) }
|
|
168
|
+
end
|
|
169
|
+
artifact.run_test_list([test], parse_with: parse_with)
|
|
148
170
|
end.flatten
|
|
149
171
|
end
|
|
150
172
|
|
|
@@ -1352,6 +1374,7 @@ module Parsanol
|
|
|
1352
1374
|
envelope["customs"] = @document.customs unless @document.customs.empty?
|
|
1353
1375
|
envelope["states"] = @document.states unless @document.states.empty?
|
|
1354
1376
|
envelope["dynamic"] = true if @dynamic
|
|
1377
|
+
envelope["semantics_hash"] = Compiler.semantics_hash(envelope)
|
|
1355
1378
|
envelope["checksum"] = Compiler.checksum(envelope)
|
|
1356
1379
|
envelope
|
|
1357
1380
|
end
|
data/lib/parsanol/parg/parser.rb
CHANGED
|
@@ -571,11 +571,16 @@ module Parsanol
|
|
|
571
571
|
|
|
572
572
|
def parse_postfixed
|
|
573
573
|
sign, behind = parse_guard_sign
|
|
574
|
-
node =
|
|
574
|
+
node = parse_primary
|
|
575
575
|
if sign.nil?
|
|
576
|
-
node
|
|
576
|
+
capture_postfix(node)
|
|
577
577
|
else
|
|
578
|
-
|
|
578
|
+
# The capture binds the GUARD, not the guarded primary
|
|
579
|
+
# (parsanol-ruby#380): `!digit as year` is `( !digit ) as
|
|
580
|
+
# year` — a capture inside the lookahead would never bind
|
|
581
|
+
# (a negated guard consumes nothing), so `as` must wrap the
|
|
582
|
+
# pred, matching the rep path's postfix order (ruby#197).
|
|
583
|
+
capture_postfix(Node.new(:pred, sign, node, behind ? :behind : nil))
|
|
579
584
|
end
|
|
580
585
|
end
|
|
581
586
|
|
data/lib/parsanol/version.rb
CHANGED