parsanol 1.3.91 → 1.3.92

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: 2876669149be4d6b90a15246708d4a40ef6bb75d0d2991b53485cff21abd768e
4
- data.tar.gz: 1750418bf4195336601f90f0bc67bf8dd669069440def4be0ceba728f7a0ef47
3
+ metadata.gz: 11b0c549ac99782284291222059dfe02d3281a83a8fe854967cee08266846bbd
4
+ data.tar.gz: 06f587dfe6a5cfe5981ac12c432729560dcdd8501a2fa91936c28be9fb0c401f
5
5
  SHA512:
6
- metadata.gz: 6ba314ff5ac132d5bb04470b024e8df3b45e1736ae64a5a5ddfa743a96fbd6971211cf9e0a2fa31be1a6d24ed9b5a612ee685f6d3ad11e5e97b9adb6875c356a
7
- data.tar.gz: f50c7d6d2e8dcb09588127755fdc12a60917406b3ee2be068f8309138a8d36f4c92175e661915484872aa3b989c51e306b741a30e2e11073866d992df68251ab
6
+ metadata.gz: a8d385dae7c5100df8012111dd75da2e2bed6ba93689849ec505259c65ab69f3c41eeb37112c5ec8e4ba44feca7503985d04fe494295ff0844f1a3bf4bd4605b
7
+ data.tar.gz: af78fe7842a3c7b25b3ad17718e95c2d5dbcea3a2fb23f8b26d63d55ca095188f75c911459eafb8866fcf7545c11853f8680ff4898db85cbaed4f57e85763f1d
data/Cargo.lock CHANGED
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
307
307
 
308
308
  [[package]]
309
309
  name = "parsanol"
310
- version = "0.18.2"
310
+ version = "0.18.6"
311
311
  source = "registry+https://github.com/rust-lang/crates.io-index"
312
- checksum = "b059d18a92c61ba7c7c6241bb446196729dadf618e6e34c0b79935f0292d7a27"
312
+ checksum = "621395999299ffdf8757b0004bf68eee4ee499051d608f756c223508981e1f44"
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.18.2"
329
+ version = "0.18.6"
330
330
  source = "registry+https://github.com/rust-lang/crates.io-index"
331
- checksum = "4b9a6de83c376137ccce01cb8890427a8879b34b6ac4832cc0413c5624923001"
331
+ checksum = "48597b847c17492e8ee96d842f9784538fe2b523ada73bf0f0976a60287d5a71"
332
332
  dependencies = [
333
333
  "proc-macro2",
334
334
  "quote",
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Parsanol
4
- VERSION = "1.3.91"
4
+ VERSION = "1.3.92"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parsanol
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.91
4
+ version: 1.3.92
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.