parsanol 1.3.116-aarch64-linux → 1.3.118-aarch64-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: 3866cf90352343093d7788b2554ae997619cd229a6a8c95affacd72d484ef058
4
- data.tar.gz: 75517165b8d0b7ecaf649696830a2d1e787fbd567a44c04f72c80d1db4e95ce0
3
+ metadata.gz: 528e8dbd52f42bdf29e0692ec6b78b45811d6200234a024be6d0239f260cffdd
4
+ data.tar.gz: 34113c97e3669e08bf58f4a1bc212c0bf4046000614f014dc8c961322bfbc57f
5
5
  SHA512:
6
- metadata.gz: 5fd74eb4265e6800f99e86b68e7af5bc87a26746c77c40997d37068eb0d3a02c5b0ec7e7dc2cc1e287539d8df230b0c40a5e1d456bfb46c06af5920eef24fe01
7
- data.tar.gz: b0d14e98182f6a8240929116ac2e28cac8cae3cb33fb968974b9c67cc46f89b1d91cf44b640f7ff77456f41b86b03131eeaf859ef7f6e7373fff0cb4f0e243e9
6
+ metadata.gz: 3ff05e12878bdfb59ac439f50dcf782d8738a38ccc885bb3de83b833de9980ab6112835386e96e5c06e9e88df831cea52f05748db4e86ea94cab13c29c62ccbc
7
+ data.tar.gz: f49ed696b260077f0f77a6e0d21acba191af25c0b39a4eecfaa84c0c5bf78e7760d1c812545ace2bc722841073bd6efdc150672a8146159534849c6f9ab3d9ea
@@ -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: 46473512 bytes
4
- sha256: 8830bace23447e874a0cb0beb18aefaea01e741127d417eab672ba509b6ccd05
3
+ size: 46566584 bytes
4
+ sha256: 870a9bf97e079764ecc2ea3d1af4fbfc24081e4fb0e8e2d5d21e454a7bc842db
@@ -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: 46708568 bytes
4
- sha256: ce64172e86ce450056b62c19d2576bedec7328882ba0a6b85d1daeb0f714ae60
3
+ size: 46805760 bytes
4
+ sha256: ce41490479d101f992a192d3ad9cb8d3ad550e128590797acd7cc17422c61bde
@@ -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: 46708400 bytes
4
- sha256: 1f46812ffca0a5c50bc2cfd85e60165f53f6c37c022646d50a9839a6398aafb4
3
+ size: 46809816 bytes
4
+ sha256: 449cec2f4d18b089ef179ba27214fbb7c7dd8f113e7fb77b2edfaed76ebaddbe
@@ -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: 46718376 bytes
4
- sha256: 4bd9b9a37f71b06587de7c46717e27bac5c92395b428ef21fa415d9ba7961b4c
3
+ size: 46819800 bytes
4
+ sha256: 7933405b216e4fba9a41ec2771c6239b73ee32645f67145c9370f9daa0bb8aa3
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/native/libparsanol.so
3
- size: 42172384 bytes
4
- sha256: 01059d0afea9e965e6cfe5d09aeb5573667b3ea860a227f6cebcef30d3581b09
3
+ size: 42275976 bytes
4
+ sha256: 3d3dc209b794a4b5927b609bb1ff442d18bfc40bcd349cee57c3a6dc1302a442
@@ -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
- grammar_json = (grammars[entry] ||= JSON.generate(artifact.entry(entry)["grammar"]))
144
- artifact.run_test_list(
145
- [test],
146
- parse_with: ->(input) { Native.parse_json_grammar(grammar_json, input) },
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
@@ -571,11 +571,16 @@ module Parsanol
571
571
 
572
572
  def parse_postfixed
573
573
  sign, behind = parse_guard_sign
574
- node = capture_postfix(parse_primary)
574
+ node = parse_primary
575
575
  if sign.nil?
576
- node
576
+ capture_postfix(node)
577
577
  else
578
- Node.new(:pred, sign, node, behind ? :behind : nil)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.116"
4
+ VERSION = "1.3.118"
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.116
4
+ version: 1.3.118
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-30 00:00:00.000000000 Z
11
+ date: 2026-10-31 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