parsanol 1.3.120-arm-linux → 1.3.122-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: 77711562d9591e8a438d229e2fb8dac148a7b8b522d7f1c2c4c0b94eea1fde0e
4
- data.tar.gz: bcdc10500b54b5ae24dde57248d547cfd931a0d6fc52d71d1667575441c5974c
3
+ metadata.gz: e6d37024ae75900c7fe4fd31f8c92119dd42ccb7af254240155b264be432c1fe
4
+ data.tar.gz: f78c1fa51c5ed6a72de5cbdd4e6124173f122d32a730b47b77d30ca5c385d6d6
5
5
  SHA512:
6
- metadata.gz: c727d8109a8622bcb3c8e863632b2e7b728aa64e4f6b42bf9e8c342bd3a5e4f1b09f62b009e46816aec4fcd8f22acc165a7c18eb6522d3040f389a1fd80fb19a
7
- data.tar.gz: ab7d8a0ec2269fe160898483356f6d41a022e22f497b27ce0bce80c5c142bbbc3ef700c582cb5cc0c704f556e9c111712e5e6ee5228855efd8f839fa22382ef1
6
+ metadata.gz: b5d12a29c0ef17c9f3895d814192b33b74fe3366262d988af73fdf1e5d1603f17cff7925ac77f45cde7f21799f97f832604c4bddffaca686b3a72fb1eb70a2f4
7
+ data.tar.gz: 8b1c7627fd9b3c946ce9a76b7445dfe6c1ebbb0ceb3fff7b061cf6a781e3d2ed498c1e205d321038a2195d7ebc050ffdd632e2ef8b71501de76a2b344016cb87
@@ -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: 39543680 bytes
4
- sha256: cb23ae5b9e3c6d9e196ba94c32b99ff12ceac4d65f9803f11768b78d4755fcc4
3
+ size: 39740052 bytes
4
+ sha256: b70dc2b64af8f8c052309c13ef755d8a156697af55b2017eec94d1ad43eef2f2
@@ -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: 39728972 bytes
4
- sha256: 591bb9417818c14220442123b595a34b95e695b6333ae1659bb52af80c8569b6
3
+ size: 39925296 bytes
4
+ sha256: 3d60d5cad8c1bf5f3ae6463c82d31f8a1027a779dc4c35be5004997d2557c862
@@ -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: 39723064 bytes
4
- sha256: ac2443455cc3d52d53c1060ea134d91a7b39e4b97d8eee4fdf5c2d71e010cdc4
3
+ size: 39919396 bytes
4
+ sha256: ccea4473a43405abe096c873c2c695ba93c271245db30d1b77f04b27df7b47a3
@@ -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: 39732740 bytes
4
- sha256: d183ddfcb7c9a08e75711fcd852bef72e7a7f65b27a14bb8fdc5c365b73df9c1
3
+ size: 39929152 bytes
4
+ sha256: 4d612343474dca5ded544fa60a9fc23bbc28d7a935ccec5546db3cbd62676540
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/native/libparsanol.so
3
- size: 35864872 bytes
4
- sha256: e0bbe3d01539221af1af284ab8c0c04eeb2437f7f45e2dd44286b65c2f2a0038
3
+ size: 36060976 bytes
4
+ sha256: 2ee2499abdc8e1b8707658bddd5a8a861961057ff0d5276849b33ec2f1e67f31
@@ -178,13 +178,27 @@ module Parsanol
178
178
 
179
179
  def append_atom_structure(atom, visited = {}.compare_by_identity, output = +"")
180
180
  if visited[atom]
181
+ # An Entity's self-reference keeps its name in the
182
+ # fingerprint, mirroring the non-visited branch (ruby#404).
183
+ if atom.is_a?(Parsanol::Atoms::Entity)
184
+ return output << "[:entity, " << atom.rule_name.to_s.inspect << ", [:cycle, " << atom.class.name.inspect << "]]"
185
+ end
186
+
181
187
  return output << "[:cycle, " << atom.class.name.inspect << "]"
182
188
  end
183
189
 
184
190
  visited[atom] = true
185
191
  case atom
186
192
  when Parsanol::Atoms::Entity
187
- append_atom_structure(atom.parslet, visited, output)
193
+ # The rule name belongs in the fingerprint (rs#404-class
194
+ # collision): Entity is transparent in the serialized graph's
195
+ # STRUCTURE only when the target is spelled out, but the JSON
196
+ # keeps the Entity node itself — two webs whose Entities point
197
+ # at different rules share a transparent fingerprint while
198
+ # serializing differently, and the serializer cache then
199
+ # serves one grammar's JSON for the other.
200
+ output << "[:entity, " << atom.rule_name.to_s.inspect << ", "
201
+ append_atom_structure(atom.parslet, visited, output) << "]"
188
202
  when Parsanol::Atoms::Str
189
203
  output << "[:str, " << atom.str.inspect << "]"
190
204
  when Parsanol::Atoms::Re
@@ -13,6 +13,10 @@ module Parsanol
13
13
  def apply(artifact, entry, shape)
14
14
  bindings = entry["bindings"] || []
15
15
  captures = collect_captures(shape)
16
+ # Snapshot before the assignment pass: reading a missing key off
17
+ # `captures` inserts it (default-proc hash), which would make the
18
+ # #413 lost-check below see phantom presence.
19
+ present = captures.keys
16
20
  out = {}
17
21
  scalar, arrays = bindings.partition { |binding| !binding["path"].to_s.include?("[]") }
18
22
  scalar.each do |binding|
@@ -22,9 +26,38 @@ module Parsanol
22
26
  arrays.group_by { |binding| binding["path"].split("[]").first }.each do |prefix, group|
23
27
  assign_array(out, artifact, prefix, group, captures, shape)
24
28
  end
29
+ # Every declared capture that the tree carries must reach the
30
+ # bound map (parsanol-ruby#413): a capture present in the parse
31
+ # but bound nowhere is silent data loss, the one case the
32
+ # binding layer's other checks miss. Absence in the TREE is
33
+ # legitimate optionality and never raises.
34
+ lost = bindings.select do |binding|
35
+ capture = binding["capture"].to_sym
36
+ present.include?(capture) && !bound?(out, binding)
37
+ end
38
+ unless lost.empty?
39
+ raise BindError,
40
+ "entry #{entry['name'].inspect}: capture #{lost.map { |b| b['capture'].inspect }.join(', ')} " \
41
+ "is present in the parse tree but bound nowhere"
42
+ end
25
43
  out
26
44
  end
27
45
 
46
+ # Did this binding contribute anything to the bound map? Scalars
47
+ # bind under their leaf key; array groups bind under their prefix.
48
+ def bound?(out, binding)
49
+ path = binding["path"].to_s
50
+ if path.include?("[]")
51
+ prefix = path.split("[]").first.to_sym
52
+ rows = out[prefix]
53
+ rows.is_a?(Array) && rows.any? do |row|
54
+ row.is_a?(Hash) && row.key?(leaf_key(binding))
55
+ end
56
+ else
57
+ out.key?(leaf_key(binding))
58
+ end
59
+ end
60
+
28
61
  # Canonical test-value form: one equality surface for capture and
29
62
  # example expectations on both sides of the comparison (Slice vs
30
63
  # source literal was comparing heterogeneous types). Terminals
@@ -65,24 +98,30 @@ module Parsanol
65
98
 
66
99
  def assign_array(out, artifact, prefix, group, captures, shape = nil)
67
100
  # Position-aware pairing (parsanol-ruby#381): when the shape
68
- # nests the group's instances under the prefix key, each
101
+ # nests the group's instances under the PREFIX KEY, each
69
102
  # instance's subtree owns its captures — pairing by OCCURRENCE
70
103
  # index landed a sparse capture (one that fires in only some
71
104
  # instances) on the wrong instance. Per-instance collection
72
105
  # 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.
106
+ # when every instance fires every capture.
107
+ #
108
+ # Identification is strictly keyed: the bare top array of a
109
+ # repetition-root entry is NOT claimed as instances
110
+ # (parsanol-ruby#408/#412 — that fallback claimed per-token rule
111
+ # siblings as group instances, splitting paired captures into
112
+ # single-field rows and sprouting phantom rows for unrelated
113
+ # siblings). A capture outside a keyed instance array keeps the
114
+ # legacy occurrence pairing. Phantom rows materialize as {}
115
+ # (the #408 contract: the engines agree by always emitting the
116
+ # row; the contracted phantom representation is {} , never nil).
75
117
  instances = shape && group_instances(shape, prefix)
76
118
  if instances
77
- # The occurrence path's `return if count.zero?`: a repetition
78
- # that fired zero iterations (the tree carries `parts: []`)
119
+ # parsanol-rs#370: a repetition that fired zero iterations
79
120
  # binds no key — the recorded native expectations and the
80
- # pubid builders rely on the absent reading (the empty-array
81
- # binding regression, caught by the pubid corpus gate; see
82
- # parsanol-rs#370).
121
+ # pubid builders rely on the absent reading.
83
122
  return if instances.empty?
84
123
 
85
- list = (out[prefix.to_sym] ||= [])
124
+ list = []
86
125
  instances.each do |instance|
87
126
  found = collect_captures(instance)
88
127
  element = {}
@@ -94,6 +133,7 @@ module Parsanol
94
133
  end
95
134
  list << element
96
135
  end
136
+ out[prefix.to_sym] = list
97
137
  return
98
138
  end
99
139
 
@@ -115,10 +155,12 @@ module Parsanol
115
155
  end
116
156
  end
117
157
 
118
- # The `prefix[]` group's instance subtrees: the first array under
119
- # a key naming the prefix, walked in collect_captures' BFS order.
120
- # A repetition-root entry's shape IS the instances array (the wire
121
- # has no key for it) — that is the fallback when no key matches.
158
+ # The `prefix[]` group's instance subtrees: the array under a key
159
+ # naming the prefix, walked in collect_captures' BFS order. There
160
+ # is deliberately NO fallback for a repetition-root entry — a bare
161
+ # top array cannot be told apart from per-token rule siblings
162
+ # (#408/#412), so claiming it is magic; those shapes pair by
163
+ # occurrence instead.
122
164
  def group_instances(shape, prefix)
123
165
  queue = [shape]
124
166
  until queue.empty?
@@ -133,7 +175,7 @@ module Parsanol
133
175
  when Array then queue.concat(node)
134
176
  end
135
177
  end
136
- shape if shape.is_a?(Array) && shape.any?(Hash)
178
+ nil
137
179
  end
138
180
 
139
181
  def validate_path!(binding)
@@ -199,10 +241,42 @@ module Parsanol
199
241
  # string-keyed hashes, repeats as arrays — so (any)-bound keys
200
242
  # survive a cross-engine gate without consumer shims.
201
243
  plain(value)
244
+ when "text"
245
+ # The matched span as text (ruby#382): a capture whose body
246
+ # carries inner captures binds a hash of them; (text) returns
247
+ # the span those captures jointly matched while the inner
248
+ # captures still fire as their own bindings.
249
+ span_text(value)
202
250
  else value
203
251
  end
204
252
  end
205
253
 
254
+ # The matched span of a capture value: the byte range from the
255
+ # first to the last slice in the subtree, cut from the input the
256
+ # slices carry. A plain Slice or String is already its span.
257
+ def span_text(value)
258
+ return value.to_s if value.is_a?(Parsanol::Slice) || value.is_a?(String)
259
+
260
+ slices = []
261
+ walk = lambda do |node|
262
+ case node
263
+ when Parsanol::Slice then slices << node
264
+ when Hash then node.each_value { |item| Array(item).each(&walk) }
265
+ when Array then node.each(&walk)
266
+ end
267
+ end
268
+ walk.call(value)
269
+ return plain(value) if slices.empty?
270
+
271
+ start = slices.map(&:offset).min
272
+ stop = slices.map { |slice| slice.offset + slice.content.bytesize }.max
273
+ input = slices.first.input
274
+ return plain(value) unless input
275
+
276
+ span = input.byteslice(start, stop - start)
277
+ span&.force_encoding(Encoding::UTF_8)
278
+ end
279
+
206
280
  # The engine-neutral reduction behind (any): capture leaves
207
281
  # (Parsanol::Slice, native wire objects) and scalars render as
208
282
  # their text, hashes as string-keyed plain hashes, arrays
@@ -261,6 +261,10 @@ module Parsanol
261
261
  # the declaration means the tree never sees this subtree named,
262
262
  # inside or out.
263
263
  return atom if glue_rule?(rule_name)
264
+ # A capture spans the rules it references (ruby#400): their
265
+ # bodies compile unwrapped so the capture's value equals its
266
+ # edition-1 value.
267
+ return atom if captured_reachable?(rule_name)
264
268
 
265
269
  wrap_uncaptured(atom)
266
270
  end
@@ -273,6 +277,57 @@ module Parsanol
273
277
  @document.glue_rules.include?(name.to_s)
274
278
  end
275
279
 
280
+ # The rules a text capture transitively spans (ruby#400): a
281
+ # capture on a reference captures the referenced rule's whole
282
+ # match, so every rule under a capture atom — the direct
283
+ # reference and everything its body transitively references —
284
+ # compiles unwrapped. Edition 2's Named leaves then attach only
285
+ # to non-captured structure, and a capture's value equals its
286
+ # edition-1 value (`{num: "123"}`, never a leaf array).
287
+ def captured_reachable?(name)
288
+ @capture_reachable ||= begin
289
+ seeds = []
290
+ @document.rules.each_value do |node|
291
+ collect_refs_under_captures(node, seeds)
292
+ end
293
+ reachable = []
294
+ until seeds.empty?
295
+ rule = seeds.pop
296
+ next if reachable.include?(rule) || !@document.rules.key?(rule)
297
+
298
+ reachable << rule
299
+ collect_all_refs(@document.rules[rule], seeds)
300
+ end
301
+ reachable
302
+ end
303
+ @capture_reachable.include?(name.to_s)
304
+ end
305
+
306
+ def collect_refs_under_captures(node, out)
307
+ walk = lambda do |n, in_capture|
308
+ case n.kind
309
+ when :ref then out << n.a if in_capture
310
+ when :cap then walk.call(n.b, true)
311
+ when :pred then walk.call(n.b, in_capture)
312
+ when :alt, :seq then n.a.each { |child| walk.call(child, in_capture) }
313
+ when :rep, :opt then walk.call(n.a, in_capture)
314
+ end
315
+ end
316
+ walk.call(node, false)
317
+ end
318
+
319
+ def collect_all_refs(node, out)
320
+ walk = lambda do |n|
321
+ case n.kind
322
+ when :ref then out << n.a
323
+ when :alt, :seq then n.a.each { |child| walk.call(child) }
324
+ when :cap, :pred then walk.call(n.b)
325
+ when :rep, :opt then walk.call(n.a)
326
+ end
327
+ end
328
+ walk.call(node)
329
+ end
330
+
276
331
  def wrap_uncaptured(atom)
277
332
  case atom
278
333
  when Atoms::Named
@@ -289,6 +344,7 @@ module Parsanol
289
344
  # agree by design.
290
345
  if trivia_only?(atom.rule_name.to_s, { atom.rule_name.to_s => true }) ||
291
346
  glue_rule?(atom.rule_name.to_s) ||
347
+ captured_reachable?(atom.rule_name.to_s) ||
292
348
  produces_capture?(atom.parslet)
293
349
  atom
294
350
  else
@@ -476,9 +532,12 @@ module Parsanol
476
532
  when Atoms::Named then nil
477
533
  when Atoms::Entity
478
534
  # Mirror wrap_uncaptured's verdicts: trivia glue, declared
479
- # glue, and capture-producing refs all stay bare.
535
+ # glue, capture-reachable refs (ruby#400 — a capture spans
536
+ # them, so modern mode never names them), and
537
+ # capture-producing refs all stay bare.
480
538
  unless trivia_only?(atom.rule_name.to_s, { atom.rule_name.to_s => true }) ||
481
539
  glue_rule?(atom.rule_name.to_s) ||
540
+ captured_reachable?(atom.rule_name.to_s) ||
482
541
  produces_capture?(atom.parslet)
483
542
  lines << "rule #{site}: reference to #{atom.rule_name} produces no capture -- " \
484
543
  "modern mode would name it #{atom.rule_name}"
@@ -14,5 +14,9 @@ module Parsanol
14
14
 
15
15
  # Raised when an artifact envelope is corrupt or its checksum mismatches.
16
16
  class ArtifactError < Error; end
17
+
18
+ # A declared capture is present in the parse tree but reached the
19
+ # bound map nowhere (parsanol-ruby#413): silent data loss made loud.
20
+ class BindError < ArtifactError; end
17
21
  end
18
22
  end
data/lib/parsanol/parg.rb CHANGED
@@ -17,6 +17,7 @@ module Parsanol
17
17
  autoload :ParseError, "parsanol/parg/error"
18
18
  autoload :CompileError, "parsanol/parg/error"
19
19
  autoload :ArtifactError, "parsanol/parg/error"
20
+ autoload :BindError, "parsanol/parg/error"
20
21
  autoload :Node, "parsanol/parg/node"
21
22
  autoload :Export, "parsanol/parg/export"
22
23
  autoload :Lexer, "parsanol/parg/lexer"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.120"
4
+ VERSION = "1.3.122"
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.120
4
+ version: 1.3.122
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-11-01 00:00:00.000000000 Z
11
+ date: 2026-11-02 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