parsanol 1.3.90-aarch64-linux → 1.3.92-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: 0a5eae02b01793cba6627f5e7e91d63789b3b4870da638d45868b6857dc557bd
4
- data.tar.gz: c32369c415f06ed312afe42cbb2621c59918b4ca10c8c1945d9b39bd69e9244f
3
+ metadata.gz: d97e0ef9b1ba9281cfce8b847384654d3420fdc8c0ad4fbd0284af5a35522653
4
+ data.tar.gz: 9836bdd4bf811c8bc7ef99820e0b0c96a088bfbf33cbf52749ffdf3adb4aa2f3
5
5
  SHA512:
6
- metadata.gz: 9c3fa5252fa8b34f870c741a0df64f27edf52e587217a07c56dbaefd344259870e483def84a1260eebb867af9da4c9b18ac8d799b2d18f678a44865d9c691bfa
7
- data.tar.gz: 59d50b74eb42a3451108150ba3ba99bb537f561fad472fab7b6645859688adbbda8345e0090a03c975b1da20bc365b7005b4f04ad4a30629c08a419286bc7776
6
+ metadata.gz: f0d8078fe690e009410e0c54268468370fe7897007cdbb261de6bca40f64301b1fb45a098f41beffe7beb3bfba1be2fbe0533864484e063d89081ebee4d0f791
7
+ data.tar.gz: 37346c31aeb72a9e5ca78fbee2135a43d10414134644633b8b224414a6eca32e162292d81bd8b9009dd91e9806617a72365d68681e670699c5f46b9f7b329086
@@ -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: 40163120 bytes
4
- sha256: b4ba2e137bf79ff65acc08112463958c8e939ff55ac33e4be62e169deadb34bc
3
+ size: 40176952 bytes
4
+ sha256: 58f5e7bcb9c7665ab38daba62f63533a89966dbe2b9e2d8c56d2c37bd7afeb37
@@ -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: 40398232 bytes
4
- sha256: 3340fdd237c5a4891ac783d4813fa0a50dc187c9696838749b5a034d7191c35e
3
+ size: 40412064 bytes
4
+ sha256: c2b4e8331d61feecb2146feb05047ff9b20da8543910429eb87c3111b6a871ed
@@ -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: 40389704 bytes
4
- sha256: 5b82593f060f232222f35db442182aef4672803e7fbb947cb49d72596967bcda
3
+ size: 40403520 bytes
4
+ sha256: 35be9d4c9905ab8a9d0fcce4c55d7909850b093d1065cf658f131a2081f9ca31
@@ -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: 40399720 bytes
4
- sha256: f188a3cbded7e033a982b4347c4f600727e2852ee5e39c2f4fb5896a55c5ca09
3
+ size: 40413544 bytes
4
+ sha256: 62c8e268e3990442aa746edfd13a0d24bc2fe40cbd58db8a845c7dfb67efdab0
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: data/lib/parsanol/native/libparsanol.so
3
- size: 35972480 bytes
4
- sha256: 9070921a0aa8beda1912fe436442ae2c172b3a258feaea6b3209b3c18f32ec03
3
+ size: 35960704 bytes
4
+ sha256: 579c6ce133fca3eec741131db52977ea97348767e59d3cb1c8e0149dc6f1bb91
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
3
4
  require "json"
4
5
  require "yaml"
5
6
 
@@ -11,20 +12,21 @@ module Parsanol
11
12
  # consume; the embedded PARG source lets a Ruby runtime recompile the atom
12
13
  # tree locally without any other dependency.
13
14
  class Artifact
14
- attr_reader :envelope, :path, :tables_dir
15
+ attr_reader :envelope, :path, :tables_dir, :imports_dir
15
16
 
16
- def self.load(path, tables_dir: nil)
17
+ def self.load(path, tables_dir: nil, imports_dir: nil)
17
18
  envelope = JSON.parse(PARG.read_utf8(path))
18
- new(envelope, path, tables_dir || File.dirname(path))
19
+ new(envelope, path, tables_dir || File.dirname(path),
20
+ imports_dir: imports_dir)
19
21
  end
20
22
 
21
- def self.from_json(text, tables_dir: nil)
22
- new(JSON.parse(text), nil, tables_dir)
23
+ def self.from_json(text, tables_dir: nil, imports_dir: nil)
24
+ new(JSON.parse(text), nil, tables_dir, imports_dir: imports_dir)
23
25
  end
24
26
 
25
27
  SUPPORTED_SHAPE = "parsanol-tree/v2"
26
28
 
27
- def initialize(envelope, path, tables_dir)
29
+ def initialize(envelope, path, tables_dir, imports_dir: nil)
28
30
  unless envelope["shape"] == SUPPORTED_SHAPE
29
31
  raise ArtifactError,
30
32
  "unsupported artifact shape #{envelope['shape'].inspect} " \
@@ -38,12 +40,30 @@ module Parsanol
38
40
  @envelope = envelope
39
41
  @path = path
40
42
  @tables_dir = tables_dir
43
+ # parsanol-ruby#208: `use`-importing grammars need the imported
44
+ # sources when the pure-Ruby path recompiles the embedded source;
45
+ # in-memory artifacts (path nil) pass the directory explicitly.
46
+ @imports_dir = imports_dir
41
47
  verify_checksum!
42
48
  @compiler = nil
43
49
  end
44
50
 
45
51
  def version = envelope["version"]
46
52
 
53
+ # Freshness (parsanol-ruby#209): what source revision and compiler
54
+ # produced this artifact. `source_hash` is the SHA-256 of the .parg
55
+ # text at compile time; nil on artifacts baked before the field
56
+ # existed, so `fresh_against?` answers false rather than guessing.
57
+ def source_hash = envelope["source_hash"]
58
+
59
+ def compiler_version = envelope["compiler_version"]
60
+
61
+ def fresh_against?(source_text)
62
+ return false if source_hash.nil?
63
+
64
+ source_hash == Digest::SHA256.hexdigest(source_text.to_s)
65
+ end
66
+
47
67
  def entries = envelope["entries"].keys
48
68
 
49
69
  def entry(name)
@@ -263,8 +283,10 @@ module Parsanol
263
283
  # without this the pure-Ruby path fails on artifacts whose
264
284
  # native atom graphs inline everything. The imported sources
265
285
  # sit beside the artifact (vendors ship them together).
266
- imports_dir = path && File.directory?(File.dirname(path)) ? File.dirname(path) : nil
267
- Imports.merge!(document, [imports_dir].compact) if imports_dir
286
+ dirs = [path ? File.dirname(path) : nil, @imports_dir]
287
+ .compact
288
+ .select { |dir| File.directory?(dir) }
289
+ Imports.merge!(document, dirs) unless dirs.empty?
268
290
  Compiler.new(document, @tables_dir)
269
291
  end
270
292
  end
@@ -22,6 +22,10 @@ module Parsanol
22
22
  test <file.parg|artifact.json> run the grammar's test section
23
23
  parse <file> [--entry E] INPUT parse one input; prints the tree
24
24
  and the bound captures
25
+ check --source x.parg --artifact y.json
26
+ verify the artifact was compiled
27
+ from that source (source hash +
28
+ compiler version); exit 1 stale
25
29
  repl <file> [--entry E] interactive parse loop (blank
26
30
  line quits, :quit exits)
27
31
  doc <file> [rule] print rule documentation
@@ -53,6 +57,7 @@ module Parsanol
53
57
  when "test" then test
54
58
  when "schema" then schema
55
59
  when "parse" then parse
60
+ when "check" then check
56
61
  when "repl" then repl
57
62
  when "doc" then doc
58
63
  when "lsp" then Lsp.new.run && 0
@@ -88,6 +93,33 @@ module Parsanol
88
93
  0
89
94
  end
90
95
 
96
+ # Freshness (parsanol-ruby#209): verify an artifact was compiled
97
+ # from the given source. The stale-shipped-artifact incident class
98
+ # (an old artifact silently taking precedence over the corrected
99
+ # grammar) is undetectable without the recorded source hash.
100
+ # Exit 0 fresh, 1 stale or unrecorded.
101
+ def check
102
+ source = flag_value("--source")
103
+ artifact_path = flag_value("--artifact")
104
+ unless source && artifact_path && File.file?(source) && File.file?(artifact_path)
105
+ warn USAGE
106
+ return 1
107
+ end
108
+ artifact = Artifact.load(artifact_path)
109
+ if artifact.source_hash.nil?
110
+ puts "STALE? #{artifact_path}: no source hash recorded (pre-freshness artifact)"
111
+ return 1
112
+ end
113
+ if artifact.fresh_against?(PARG.read_utf8(source))
114
+ puts "FRESH #{artifact_path} == #{source} (compiler #{artifact.compiler_version})"
115
+ 0
116
+ else
117
+ puts "STALE #{artifact_path} does not match #{source} " \
118
+ "(built from #{artifact.source_hash[0, 12]}… by #{artifact.compiler_version})"
119
+ 1
120
+ end
121
+ end
122
+
91
123
  def test
92
124
  target = @argv.shift
93
125
  if File.directory?(target)
@@ -200,11 +232,13 @@ module Parsanol
200
232
 
201
233
  def artifact_for(file)
202
234
  if file.end_with?(".json")
203
- Parsanol::PARG::Artifact.load(file, tables_dir: @tables_dir || default_tables_dir(file))
235
+ Parsanol::PARG::Artifact.load(file, tables_dir: @tables_dir || default_tables_dir(file),
236
+ imports_dir: File.dirname(file))
204
237
  else
205
238
  artifact = compile_source(file)
206
239
  Parsanol::PARG::Artifact.from_json(JSON.generate(artifact.envelope),
207
- tables_dir: @tables_dir || default_tables_dir(file))
240
+ tables_dir: @tables_dir || default_tables_dir(file),
241
+ imports_dir: File.dirname(file))
208
242
  end
209
243
  end
210
244
 
@@ -758,6 +758,12 @@ module Parsanol
758
758
  end,
759
759
  "docs" => @document.docs,
760
760
  "source" => @document.source,
761
+ # Freshness (parsanol-ruby#209): what the artifact was compiled
762
+ # from and with, so loaders can detect a stale artifact and fall
763
+ # back to a reference implementation instead of failing obscurely
764
+ # at parse time. Checksummed like the rest of the envelope.
765
+ "source_hash" => Digest::SHA256.hexdigest(@document.source.to_s),
766
+ "compiler_version" => Parsanol::VERSION,
761
767
  }
762
768
  if @document.skip_declaration
763
769
  # parsanol-ruby#134: declared trivia rule. Injection is baked
@@ -77,6 +77,13 @@ module Parsanol
77
77
  # `skip = trivia capture: comments` shape) instead of being
78
78
  # discarded; whitespace-shaped units never record.
79
79
  #
80
+ # +exempt:+ (parsanol-ruby#195) lists rule names whose bodies
81
+ # build WITHOUT injection — the DSL mirror of the PARG
82
+ # compiler's automatic skip-rule-closure exemption. Declare it
83
+ # for rules that manage their own trivia explicitly (expressir's
84
+ # remark rules): a wrapper inside them consumes text the rule
85
+ # itself captures, corrupting the value and double-recording it.
86
+ #
80
87
  # @example
81
88
  # class MyParser < Parsanol::Parser
82
89
  # rule(:spaces) { match(/[ \t]/).repeat(1) }
data/lib/parsanol/skip.rb CHANGED
@@ -41,11 +41,46 @@ module Parsanol
41
41
 
42
42
  markers = capturer_markers(parser_instance, captures)
43
43
  whitespace_kind = whitespace&.to_sym
44
- exempt_names = exempt.nil? ? nil : Set.new(exempt.map(&:to_s))
44
+ exempt_names = Set.new(exempt.nil? ? [] : exempt.map(&:to_s))
45
+ exempt_names.merge(closure_names(skip_atom))
45
46
  injector = Injector.new(skip_atom, markers, whitespace_kind, exempt_names)
46
47
  [injector.inject(root_atom, entry: true), skip_atom]
47
48
  end
48
49
 
50
+ # The skip rule's transitive rule-reference closure (parsanol-ruby#195
51
+ # follow-up): those rule bodies build without injection — the same
52
+ # exemption the PARG compiler applies to its skip declaration's
53
+ # closure. The docstring promised this; the code exempted only the
54
+ # skip atom itself. Rules the grammar manages explicitly stay
55
+ # declarable through +exempt:+ (their names merge with these).
56
+ def self.closure_names(skip_atom)
57
+ names = Set.new
58
+ seen = {}.compare_by_identity
59
+ queue = [skip_atom]
60
+ until queue.empty?
61
+ node = queue.shift
62
+ next if node.nil? || seen.key?(node)
63
+
64
+ seen[node] = true
65
+ case node
66
+ when Atoms::Entity
67
+ names << node.rule_name.to_s
68
+ begin
69
+ queue << node.parslet
70
+ rescue StandardError
71
+ nil
72
+ end
73
+ else
74
+ queue.concat(node.parslets) if node.respond_to?(:parslets)
75
+ queue.concat(node.alternatives) if node.respond_to?(:alternatives)
76
+ queue << node.parslet if node.respond_to?(:parslet)
77
+ queue << node.bound_parslet if node.respond_to?(:bound_parslet)
78
+ queue << node.wrapped_atom if node.respond_to?(:wrapped_atom)
79
+ end
80
+ end
81
+ names
82
+ end
83
+
49
84
  # Derives each capturer's leading literal (the leftmost Str the
50
85
  # rule can start with) and keys the marker table by it — the same
51
86
  # derivation the PARG compiler applies to its skip declaration. A
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.90"
4
+ VERSION = "1.3.92"
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.90
4
+ version: 1.3.92
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-10 00:00:00.000000000 Z
11
+ date: 2026-10-15 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