parsanol 1.3.129 → 1.3.130
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 +12 -0
- data/lib/parsanol/parg/bindings/artifact.rb +183 -0
- data/lib/parsanol/parg/bindings.rb +3 -0
- data/lib/parsanol/parg/imports.rb +17 -1
- data/lib/parsanol/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55b823bf0f799b613ed22ad730a010327cbfb0b40411bd25690053602371cc0d
|
|
4
|
+
data.tar.gz: 61b8cd059944fc95c2f6b52f4a7be68d3083831a19a7479a1c5f187ce96c0004
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08137851d78de165952bdaf0afc3e64a6f215f47f945ef2be0c4f3f7a94313f7768c698cd72ef0a5b70c7b648a210f1b69028526166f0b770aa25c4175b5233b'
|
|
7
|
+
data.tar.gz: 66897e1afc6cb82c08a202285495d60c7b80e8da7cd5ecb5d16e947f36e036476184342e01f80191bf5be3ee68f6578d1d54d9ad430df69a0cbb1a34f814795d
|
data/Cargo.lock
CHANGED
|
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "parsanol"
|
|
310
|
-
version = "0.20.
|
|
310
|
+
version = "0.20.1"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "05d9bc5274d8d4eca8ee343b3f97df52691e303284f26a71b3903048019565c7"
|
|
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.20.
|
|
329
|
+
version = "0.20.1"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "80669ec1c0b53f0cab38799570a8df7c0f8edd8decd02940ea8bb124ad401e4b"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
|
@@ -95,6 +95,18 @@ module Parsanol
|
|
|
95
95
|
source_hash == Digest::SHA256.hexdigest(source_text.to_s)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
# The declared per-target binding artifact for this parse artifact
|
|
99
|
+
# (parsanol-ruby#432): the entry binding records extracted into
|
|
100
|
+
# their own checksummed artifact, pinned to this grammar by source
|
|
101
|
+
# hash. Targets: ruby, rs, ts, python.
|
|
102
|
+
def bindings_artifact(target)
|
|
103
|
+
Bindings::Artifact.emit(envelope, target)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def bindings_artifact_json(target)
|
|
107
|
+
Bindings::Artifact.emit_json(envelope, target)
|
|
108
|
+
end
|
|
109
|
+
|
|
98
110
|
def entries = envelope["entries"].keys
|
|
99
111
|
|
|
100
112
|
def entry(name)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Parsanol
|
|
6
|
+
module PARG
|
|
7
|
+
module Bindings
|
|
8
|
+
# The declared per-target binding artifact (parsanol-ruby#432): from
|
|
9
|
+
# one compiled grammar the toolchain emits one checksummed bindings
|
|
10
|
+
# artifact per target, each carrying the entry binding records and
|
|
11
|
+
# pinned to its grammar by source hash. Declared and diffable, not
|
|
12
|
+
# baked: consumers pin and verify what they load.
|
|
13
|
+
#
|
|
14
|
+
# Boundary: the records carry AST construction only — the
|
|
15
|
+
# production-to-binding mapping. Semantics live in the model layer
|
|
16
|
+
# (LML), never here. Execution resources (preprocess steps and their
|
|
17
|
+
# tables) resolve from the PAIRED parse artifact, which is why
|
|
18
|
+
# #pair_with! is mandatory before #apply.
|
|
19
|
+
class Artifact
|
|
20
|
+
KIND = "parg-bindings"
|
|
21
|
+
TARGETS = %w[ruby rs ts python].freeze
|
|
22
|
+
SUPPORTED_BINDING_VERSION = 1
|
|
23
|
+
|
|
24
|
+
attr_reader :target, :binding_version, :grammar, :grammar_version,
|
|
25
|
+
:grammar_source_hash, :entries, :payload
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# Emit the per-target bindings artifact from a compiled parse
|
|
29
|
+
# artifact envelope. Returns the verified artifact; the payload
|
|
30
|
+
# is checksummed with the artifact checksum scheme (canonical
|
|
31
|
+
# JSON sha256 over everything except the checksum itself), so
|
|
32
|
+
# every engine re-derives it identically.
|
|
33
|
+
def emit(envelope, target)
|
|
34
|
+
target = target.to_s
|
|
35
|
+
unless TARGETS.include?(target)
|
|
36
|
+
raise ArtifactError,
|
|
37
|
+
"unknown binding target #{target.inspect}; known: #{TARGETS.join(', ')}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
artifact = new(build(envelope, target))
|
|
41
|
+
artifact.verify
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The emitted artifact as JSON (the bake/CLI surface).
|
|
45
|
+
def emit_json(envelope, target)
|
|
46
|
+
JSON.generate(emit(envelope, target).payload)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Parse and fully verify an emitted bindings artifact. `target:`
|
|
50
|
+
# pins the expected target; omit it in multi-target tooling that
|
|
51
|
+
# accepts any declared target.
|
|
52
|
+
def from_json(json, target: nil)
|
|
53
|
+
artifact = new(JSON.parse(json))
|
|
54
|
+
artifact.verify
|
|
55
|
+
artifact.verify_target!(target) unless target.nil?
|
|
56
|
+
artifact
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def build(envelope, target)
|
|
62
|
+
payload = {
|
|
63
|
+
"artifact_kind" => KIND,
|
|
64
|
+
"target" => target,
|
|
65
|
+
"binding_version" => SUPPORTED_BINDING_VERSION,
|
|
66
|
+
"grammar" => envelope["grammar"],
|
|
67
|
+
"grammar_version" => envelope["version"],
|
|
68
|
+
"grammar_source_hash" => envelope["source_hash"],
|
|
69
|
+
"shape" => envelope["shape"],
|
|
70
|
+
"entries" => envelope["entries"].to_h do |name, entry|
|
|
71
|
+
[name, { "bindings" => entry["bindings"].to_a }]
|
|
72
|
+
end,
|
|
73
|
+
}
|
|
74
|
+
payload.merge("checksum" => Compiler.checksum(payload))
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def initialize(payload)
|
|
79
|
+
raise ArtifactError, "bindings artifact payload must be an object" unless payload.is_a?(Hash)
|
|
80
|
+
|
|
81
|
+
@payload = payload
|
|
82
|
+
@target = payload["target"]
|
|
83
|
+
@binding_version = payload["binding_version"]
|
|
84
|
+
@grammar = payload["grammar"]
|
|
85
|
+
@grammar_version = payload["grammar_version"]
|
|
86
|
+
@grammar_source_hash = payload["grammar_source_hash"]
|
|
87
|
+
@entries = payload["entries"]
|
|
88
|
+
@parg_artifact = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Structural verification: kind, checksum, binding version,
|
|
92
|
+
# target, entries map. Every loader path goes through here.
|
|
93
|
+
def verify
|
|
94
|
+
unless @payload["artifact_kind"] == KIND
|
|
95
|
+
raise ArtifactError,
|
|
96
|
+
"not a #{KIND} artifact (artifact_kind #{@payload['artifact_kind'].inspect})"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
expected = Compiler.checksum(@payload)
|
|
100
|
+
actual = @payload["checksum"]
|
|
101
|
+
unless actual.is_a?(String) && actual == expected
|
|
102
|
+
raise ArtifactError,
|
|
103
|
+
"bindings artifact checksum mismatch: expected #{expected}, got #{actual.inspect}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
unless TARGETS.include?(@target)
|
|
107
|
+
raise ArtifactError,
|
|
108
|
+
"unknown binding target #{@target.inspect}; known: #{TARGETS.join(', ')}"
|
|
109
|
+
end
|
|
110
|
+
unless @binding_version == SUPPORTED_BINDING_VERSION
|
|
111
|
+
raise ArtifactError,
|
|
112
|
+
"unsupported binding_version #{@binding_version.inspect} " \
|
|
113
|
+
"(supported: #{SUPPORTED_BINDING_VERSION})"
|
|
114
|
+
end
|
|
115
|
+
unless @entries.is_a?(Hash)
|
|
116
|
+
raise ArtifactError, "bindings artifact carries no entries map"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def verify_target!(target)
|
|
123
|
+
target = target.to_s
|
|
124
|
+
return if @target == target
|
|
125
|
+
|
|
126
|
+
raise ArtifactError,
|
|
127
|
+
"bindings artifact targets #{@target.inspect}, not #{target.inspect}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Pair with the parse artifact this layer was compiled from: same
|
|
131
|
+
# grammar, same version, same source. The pairing is what lets
|
|
132
|
+
# apply resolve preprocess references — they live in the parse
|
|
133
|
+
# artifact's checksummed envelope.
|
|
134
|
+
def pair_with!(parg_artifact)
|
|
135
|
+
envelope = parg_artifact.envelope
|
|
136
|
+
mismatches = []
|
|
137
|
+
mismatches << "grammar (#{@grammar.inspect} vs #{envelope['grammar'].inspect})" if @grammar != envelope["grammar"]
|
|
138
|
+
if @grammar_version != envelope["version"]
|
|
139
|
+
mismatches << "version (#{@grammar_version.inspect} vs #{envelope['version'].inspect})"
|
|
140
|
+
end
|
|
141
|
+
if @grammar_source_hash != envelope["source_hash"]
|
|
142
|
+
mismatches << "source_hash (#{@grammar_source_hash.inspect} vs #{envelope['source_hash'].inspect})"
|
|
143
|
+
end
|
|
144
|
+
unless mismatches.empty?
|
|
145
|
+
raise ArtifactError,
|
|
146
|
+
"bindings artifact does not pair with the parse artifact: #{mismatches.join(', ')}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
@parg_artifact = parg_artifact
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def entry(name)
|
|
154
|
+
@entries.fetch(name) { raise ArtifactError, "unknown entry #{name.inspect}" }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def bindings(name)
|
|
158
|
+
entry(name)["bindings"]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def entry_names
|
|
162
|
+
@entries.keys
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Materialize the bound map from a parse shape using the DECLARED
|
|
166
|
+
# records. The paired parse artifact supplies entry context and
|
|
167
|
+
# preprocess resolution; the same Bindings.apply runs as for the
|
|
168
|
+
# embedded records, so a standalone artifact and its parse
|
|
169
|
+
# artifact's baked records produce identical bound maps.
|
|
170
|
+
def apply(entry_name, shape)
|
|
171
|
+
parg = @parg_artifact
|
|
172
|
+
if parg.nil?
|
|
173
|
+
raise ArtifactError,
|
|
174
|
+
"pair_with! the parse artifact before applying bindings"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
crafted = { "name" => entry_name, "bindings" => bindings(entry_name) }
|
|
178
|
+
Bindings.apply(parg, crafted, shape)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -8,6 +8,9 @@ module Parsanol
|
|
|
8
8
|
# producing the attribute hash that fills a lutaml-model class:
|
|
9
9
|
# capture collection -> preprocessing -> type cast -> path assignment.
|
|
10
10
|
module Bindings
|
|
11
|
+
# The declared per-target binding artifact (parsanol-ruby#432).
|
|
12
|
+
autoload :Artifact, "parsanol/parg/bindings/artifact"
|
|
13
|
+
|
|
11
14
|
module_function
|
|
12
15
|
|
|
13
16
|
def apply(artifact, entry, shape)
|
|
@@ -46,6 +46,12 @@ module Parsanol
|
|
|
46
46
|
|
|
47
47
|
document.rules[qualified] = copy(node, prefix)
|
|
48
48
|
end
|
|
49
|
+
# The `atomic` exemption rides the rule name: a component's
|
|
50
|
+
# atomic rules qualify with everything else, or the exemption
|
|
51
|
+
# never matches post-merge.
|
|
52
|
+
used.atomic_rules.each do |name|
|
|
53
|
+
document.atomic_rules << "#{prefix}#{name}"
|
|
54
|
+
end
|
|
49
55
|
used.entries.each do |entry, rule|
|
|
50
56
|
document.entries["#{prefix}#{entry}"] = "#{prefix}#{rule}"
|
|
51
57
|
end
|
|
@@ -79,7 +85,17 @@ module Parsanol
|
|
|
79
85
|
when :pred
|
|
80
86
|
Node.new(:pred, node.a, copy(node.b, prefix), node.c)
|
|
81
87
|
when :cap then Node.new(:cap, node.a, copy(node.b, prefix))
|
|
82
|
-
|
|
88
|
+
# A qualified reference (`other.rule`) already names its own
|
|
89
|
+
# imported grammar — prefixing would double-qualify it
|
|
90
|
+
# (cross-import references compose: the other grammar's rules
|
|
91
|
+
# are prefixed by their own merge).
|
|
92
|
+
when :ref
|
|
93
|
+
name = node.a.to_s
|
|
94
|
+
if name.include?(".")
|
|
95
|
+
Node.new(:ref, name)
|
|
96
|
+
else
|
|
97
|
+
Node.new(:ref, "#{prefix}#{name}")
|
|
98
|
+
end
|
|
83
99
|
else node
|
|
84
100
|
end
|
|
85
101
|
end
|
data/lib/parsanol/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.3.130
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -118,6 +118,7 @@ files:
|
|
|
118
118
|
- lib/parsanol/parg/artifact.rb
|
|
119
119
|
- lib/parsanol/parg/authoring.rb
|
|
120
120
|
- lib/parsanol/parg/bindings.rb
|
|
121
|
+
- lib/parsanol/parg/bindings/artifact.rb
|
|
121
122
|
- lib/parsanol/parg/cli.rb
|
|
122
123
|
- lib/parsanol/parg/compiler.rb
|
|
123
124
|
- lib/parsanol/parg/derive.rb
|