parsanol 1.3.132-aarch64-linux → 1.3.134-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 +4 -4
- data/lib/parsanol/native/parser.rb +1 -1
- data/lib/parsanol/native/serializer.rb +95 -71
- data/lib/parsanol/native.rb +11 -1
- data/lib/parsanol/parg/cli.rb +22 -1
- data/lib/parsanol/parg/compiler.rb +2 -1
- data/lib/parsanol/parg/model_bindings.rb +126 -0
- data/lib/parsanol/parg.rb +1 -0
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol.rb +7 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a705fd766cfedfe219e944de94e684d5706a0f71f0a2322a74c235949efe89d0
|
|
4
|
+
data.tar.gz: 5be20b0bbf1a79c3302ca33f2df3489adbfce902463c9c3eb27417add5c34531
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8012b30cbbb2abe03ed46b7a2acb0e7b25fbbc6a2201242f2ff95eebc5a315d100e14859008661316e56c15f0900cdff22bb6ee992164f99a4a1a5b3dc1dcab7
|
|
7
|
+
data.tar.gz: e76a33d0062a73072b77b4eb187d21fa06dc29c71f6a1929f0878cc1e087759382247237475990b74d19d0074c9328aaa36ccf9c6bff2a742cdebe33bc88a79e
|
|
@@ -99,7 +99,7 @@ module Parsanol
|
|
|
99
99
|
return handle
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
json = legacy ? GrammarSerializer.serialize(root_atom) : grammar_json(root_atom)
|
|
102
|
+
json = legacy ? GrammarSerializer.serialize(root_atom, preserve_identity: true) : grammar_json(root_atom)
|
|
103
103
|
handle = Native._register_grammar(json)
|
|
104
104
|
HANDLE_CACHE[cache_key] = handle
|
|
105
105
|
trim_caches
|
|
@@ -8,14 +8,22 @@ module Parsanol
|
|
|
8
8
|
# Serialize a Parslet grammar (root atom) to JSON string
|
|
9
9
|
#
|
|
10
10
|
# @param root [Parsanol::Atoms::Base] The root atom of the grammar
|
|
11
|
+
# @param preserve_identity [Boolean] emit exactly one wire atom per
|
|
12
|
+
# Ruby atom, even when an atom is reached again through a recursive
|
|
13
|
+
# rule while its own children are still being serialized. Parslet
|
|
14
|
+
# compatibility needs this: its memo is keyed by atom identity, so a
|
|
15
|
+
# duplicated wire atom splits one memo entry in two and the native
|
|
16
|
+
# engine can pick a different alternative than the Ruby engine. The
|
|
17
|
+
# default keeps the established wire layout of strict grammars.
|
|
11
18
|
# @return [String] JSON representation of the grammar
|
|
12
|
-
def self.serialize(root)
|
|
19
|
+
def self.serialize(root, preserve_identity: false)
|
|
13
20
|
# Create fresh instance for each serialization
|
|
14
21
|
# (state is specific to each grammar)
|
|
15
|
-
new.serialize(root)
|
|
22
|
+
new(preserve_identity: preserve_identity).serialize(root)
|
|
16
23
|
end
|
|
17
24
|
|
|
18
|
-
def initialize
|
|
25
|
+
def initialize(preserve_identity: false)
|
|
26
|
+
@preserve_identity = preserve_identity
|
|
19
27
|
@atoms = []
|
|
20
28
|
@atom_cache = {} # object_id => atom_id for deduplication
|
|
21
29
|
# Strong retention: cache keys are object_ids; without holding the
|
|
@@ -52,74 +60,19 @@ module Parsanol
|
|
|
52
60
|
# Don't create a new atom, just resolve and return the referenced atom_id
|
|
53
61
|
return serialize_entity(atom) if atom.is_a?(Parsanol::Atoms::Entity)
|
|
54
62
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
serialize_named(atom)
|
|
69
|
-
when Parsanol::Atoms::Lookahead
|
|
70
|
-
serialize_lookahead(atom)
|
|
71
|
-
when Parsanol::Atoms::Capture
|
|
72
|
-
serialize_capture(atom)
|
|
73
|
-
when Parsanol::Atoms::Scope
|
|
74
|
-
serialize_scope(atom)
|
|
75
|
-
when Parsanol::Atoms::StateSet
|
|
76
|
-
serialize_state_set(atom)
|
|
77
|
-
when Parsanol::Atoms::StateMatch
|
|
78
|
-
serialize_state_match(atom)
|
|
79
|
-
when Parsanol::Atoms::StateSwitch
|
|
80
|
-
serialize_state_switch(atom)
|
|
81
|
-
when Parsanol::Atoms::CustomRef
|
|
82
|
-
serialize_custom_ref(atom)
|
|
83
|
-
when Parsanol::Atoms::Dynamic
|
|
84
|
-
serialize_dynamic(atom)
|
|
85
|
-
when Parsanol::Atoms::TriviaCapture
|
|
86
|
-
# parsanol-ruby#152: the capture wrapper carries
|
|
87
|
-
# its marker table so the native engine records
|
|
88
|
-
# comment-shaped units into the pending channel
|
|
89
|
-
# and attaches them to the next Named capture
|
|
90
|
-
# under `comments:`. A distinct variant keeps the
|
|
91
|
-
# declared gate: engines predating it reject
|
|
92
|
-
# capture-carrying artifacts loudly.
|
|
93
|
-
serialize_trivia_capture(atom)
|
|
94
|
-
when Parsanol::Atoms::Trivia
|
|
95
|
-
# Wire-distinct from Ignore (rs#172): the Rust
|
|
96
|
-
# engine suppresses trivia-position diagnostics by
|
|
97
|
-
# atom kind. Engines predating the Trivia tag
|
|
98
|
-
# reject skip-carrying artifacts loudly (unknown
|
|
99
|
-
# variant), which is the declared gate.
|
|
100
|
-
serialize_trivia(atom)
|
|
101
|
-
when Parsanol::Atoms::Constant
|
|
102
|
-
# rs#137 follow-up: coradoc-markdown's Output
|
|
103
|
-
# atoms — empty match yielding a wire constant.
|
|
104
|
-
{ "Constant" => { "value" => serialize_constant_value(atom.value) } }
|
|
105
|
-
when Parsanol::Atoms::Lookbehind
|
|
106
|
-
# rs#137/#163: the behind-window test — a fixed
|
|
107
|
-
# literal window or an end-anchored regex (the
|
|
108
|
-
# CommonMark flanking form).
|
|
109
|
-
look = if atom.regex?
|
|
110
|
-
{ "Regex" => { "source" => atom.regex_source } }
|
|
111
|
-
else
|
|
112
|
-
{ "Literal" => { "count" => atom.count,
|
|
113
|
-
"pattern" => atom.pattern } }
|
|
114
|
-
end
|
|
115
|
-
{ "Lookbehind" => { "look" => look,
|
|
116
|
-
"positive" => atom.positive } }
|
|
117
|
-
when Parsanol::Atoms::Ignored
|
|
118
|
-
serialize_ignored(atom)
|
|
119
|
-
else
|
|
120
|
-
# Fallback for unknown atom types
|
|
121
|
-
serialize_unknown(atom)
|
|
122
|
-
end
|
|
63
|
+
# Reserve the id before the children, as serialize_entity does, so a
|
|
64
|
+
# recursive path back to this atom reuses it instead of emitting a
|
|
65
|
+
# second copy.
|
|
66
|
+
if @preserve_identity
|
|
67
|
+
atom_id = @atoms.size
|
|
68
|
+
@atom_cache[cache_key] = atom_id
|
|
69
|
+
@retained_atoms << atom
|
|
70
|
+
@atoms << nil
|
|
71
|
+
@atoms[atom_id] = serialize_body(atom)
|
|
72
|
+
return atom_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
serialized = serialize_body(atom)
|
|
123
76
|
|
|
124
77
|
# Now reserve an atom_id and cache
|
|
125
78
|
atom_id = @atoms.size
|
|
@@ -130,6 +83,77 @@ module Parsanol
|
|
|
130
83
|
atom_id
|
|
131
84
|
end
|
|
132
85
|
|
|
86
|
+
def serialize_body(atom)
|
|
87
|
+
# Serialize based on atom type first (recursively)
|
|
88
|
+
case atom
|
|
89
|
+
when Parsanol::Atoms::Str
|
|
90
|
+
serialize_str(atom)
|
|
91
|
+
when Parsanol::Atoms::Re
|
|
92
|
+
serialize_re(atom)
|
|
93
|
+
when Parsanol::Atoms::Sequence
|
|
94
|
+
serialize_sequence(atom)
|
|
95
|
+
when Parsanol::Atoms::Alternative
|
|
96
|
+
serialize_alternative(atom)
|
|
97
|
+
when Parsanol::Atoms::Repetition
|
|
98
|
+
serialize_repetition(atom)
|
|
99
|
+
when Parsanol::Atoms::Named
|
|
100
|
+
serialize_named(atom)
|
|
101
|
+
when Parsanol::Atoms::Lookahead
|
|
102
|
+
serialize_lookahead(atom)
|
|
103
|
+
when Parsanol::Atoms::Capture
|
|
104
|
+
serialize_capture(atom)
|
|
105
|
+
when Parsanol::Atoms::Scope
|
|
106
|
+
serialize_scope(atom)
|
|
107
|
+
when Parsanol::Atoms::StateSet
|
|
108
|
+
serialize_state_set(atom)
|
|
109
|
+
when Parsanol::Atoms::StateMatch
|
|
110
|
+
serialize_state_match(atom)
|
|
111
|
+
when Parsanol::Atoms::StateSwitch
|
|
112
|
+
serialize_state_switch(atom)
|
|
113
|
+
when Parsanol::Atoms::CustomRef
|
|
114
|
+
serialize_custom_ref(atom)
|
|
115
|
+
when Parsanol::Atoms::Dynamic
|
|
116
|
+
serialize_dynamic(atom)
|
|
117
|
+
when Parsanol::Atoms::TriviaCapture
|
|
118
|
+
# parsanol-ruby#152: the capture wrapper carries
|
|
119
|
+
# its marker table so the native engine records
|
|
120
|
+
# comment-shaped units into the pending channel
|
|
121
|
+
# and attaches them to the next Named capture
|
|
122
|
+
# under `comments:`. A distinct variant keeps the
|
|
123
|
+
# declared gate: engines predating it reject
|
|
124
|
+
# capture-carrying artifacts loudly.
|
|
125
|
+
serialize_trivia_capture(atom)
|
|
126
|
+
when Parsanol::Atoms::Trivia
|
|
127
|
+
# Wire-distinct from Ignore (rs#172): the Rust
|
|
128
|
+
# engine suppresses trivia-position diagnostics by
|
|
129
|
+
# atom kind. Engines predating the Trivia tag
|
|
130
|
+
# reject skip-carrying artifacts loudly (unknown
|
|
131
|
+
# variant), which is the declared gate.
|
|
132
|
+
serialize_trivia(atom)
|
|
133
|
+
when Parsanol::Atoms::Constant
|
|
134
|
+
# rs#137 follow-up: coradoc-markdown's Output
|
|
135
|
+
# atoms — empty match yielding a wire constant.
|
|
136
|
+
{ "Constant" => { "value" => serialize_constant_value(atom.value) } }
|
|
137
|
+
when Parsanol::Atoms::Lookbehind
|
|
138
|
+
# rs#137/#163: the behind-window test — a fixed
|
|
139
|
+
# literal window or an end-anchored regex (the
|
|
140
|
+
# CommonMark flanking form).
|
|
141
|
+
look = if atom.regex?
|
|
142
|
+
{ "Regex" => { "source" => atom.regex_source } }
|
|
143
|
+
else
|
|
144
|
+
{ "Literal" => { "count" => atom.count,
|
|
145
|
+
"pattern" => atom.pattern } }
|
|
146
|
+
end
|
|
147
|
+
{ "Lookbehind" => { "look" => look,
|
|
148
|
+
"positive" => atom.positive } }
|
|
149
|
+
when Parsanol::Atoms::Ignored
|
|
150
|
+
serialize_ignored(atom)
|
|
151
|
+
else
|
|
152
|
+
# Fallback for unknown atom types
|
|
153
|
+
serialize_unknown(atom)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
133
157
|
def serialize_str(atom)
|
|
134
158
|
{
|
|
135
159
|
"Str" => {
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -402,12 +402,22 @@ module Parsanol
|
|
|
402
402
|
BatchDecoder.decode_and_flatten(raw, input, Parsanol::Slice)
|
|
403
403
|
end
|
|
404
404
|
|
|
405
|
+
# The native engine's recursion-limit abort (ParseError::
|
|
406
|
+
# RecursionLimitExceeded in parsanol-rs). It carries no position
|
|
407
|
+
# marker: it is a resource limit, not a refusal of the input.
|
|
408
|
+
NATIVE_RECURSION_LIMIT = /\A(?:Recursion limit exceeded\b|Maximum recursion depth \(\d+\) exceeded)/
|
|
409
|
+
|
|
405
410
|
def raise_compatible_parse_error(error, input)
|
|
406
411
|
message = error.message
|
|
407
412
|
position = message[NATIVE_POS_MARKER, 1].to_i
|
|
408
413
|
cause = Parsanol::Cause.new(message.sub(NATIVE_POS_MARKER, ""),
|
|
409
414
|
Parsanol::Source.new(input), position)
|
|
410
|
-
|
|
415
|
+
failure = if !message.match?(NATIVE_POS_MARKER) && message.match?(NATIVE_RECURSION_LIMIT)
|
|
416
|
+
Parsanol::RecursionLimitExceeded
|
|
417
|
+
else
|
|
418
|
+
Parsanol::ParseFailed
|
|
419
|
+
end
|
|
420
|
+
raise failure.new(cause.to_s, cause), cause: nil
|
|
411
421
|
end
|
|
412
422
|
|
|
413
423
|
# Reference-walker path (TODO.sota/8 B1): the plain recursive
|
data/lib/parsanol/parg/cli.rb
CHANGED
|
@@ -22,7 +22,12 @@ module Parsanol
|
|
|
22
22
|
compile to an artifact envelope;
|
|
23
23
|
--bindings also writes one
|
|
24
24
|
checksummed per-target binding
|
|
25
|
-
artifact beside it (#432)
|
|
25
|
+
artifact beside it (#432);
|
|
26
|
+
--model-bindings FILE embeds a
|
|
27
|
+
rule -> model sidecar (JSON/YAML)
|
|
28
|
+
in the artifact
|
|
29
|
+
bindings <artifact.json> dump the embedded rule -> model
|
|
30
|
+
binding table as JSON (coradoc#307)
|
|
26
31
|
test <file.parg|artifact.json> run the grammar's test section
|
|
27
32
|
parse <file> [--entry E] INPUT parse one input; prints the tree
|
|
28
33
|
and the bound captures
|
|
@@ -75,6 +80,7 @@ module Parsanol
|
|
|
75
80
|
when "lint" then lint
|
|
76
81
|
when "export" then export_grammar
|
|
77
82
|
when "diff" then diff_artifacts
|
|
83
|
+
when "bindings" then dump_bindings
|
|
78
84
|
when "lsp" then Lsp.new.run && 0
|
|
79
85
|
else
|
|
80
86
|
warn USAGE
|
|
@@ -145,6 +151,9 @@ module Parsanol
|
|
|
145
151
|
source = @argv.shift
|
|
146
152
|
out = flag_value("-o") || default_artifact_name(source)
|
|
147
153
|
envelope = compile_source(source).envelope
|
|
154
|
+
if (sidecar = flag_value("--model-bindings"))
|
|
155
|
+
envelope = ModelBindings.embed(envelope, ModelBindings.load(sidecar))
|
|
156
|
+
end
|
|
148
157
|
# Seal: the exact text written is the exact text the byte-level
|
|
149
158
|
# checksum covers (Compiler.seal_json includes the trailing
|
|
150
159
|
# newline).
|
|
@@ -162,6 +171,18 @@ module Parsanol
|
|
|
162
171
|
0
|
|
163
172
|
end
|
|
164
173
|
|
|
174
|
+
# `parg bindings ARTIFACT`: the embedded rule -> model table as JSON.
|
|
175
|
+
def dump_bindings
|
|
176
|
+
path = @argv.shift
|
|
177
|
+
unless path
|
|
178
|
+
warn "usage: parsanol parg bindings ARTIFACT.json"
|
|
179
|
+
return 2
|
|
180
|
+
end
|
|
181
|
+
envelope = ModelBindings.read_envelope(path)
|
|
182
|
+
puts ModelBindings.dump(envelope)
|
|
183
|
+
0
|
|
184
|
+
end
|
|
185
|
+
|
|
165
186
|
# Every --bindings flag value, `all` expanded to the target set.
|
|
166
187
|
# Empty when the flag is absent (compile emits no binding artifacts).
|
|
167
188
|
def bindings_targets
|
|
@@ -61,7 +61,8 @@ module Parsanol
|
|
|
61
61
|
# unicode_tables — is byte-stable when the grammar's behavior is
|
|
62
62
|
# unchanged. Mirrors the cross-engine parity view.
|
|
63
63
|
SEMANTICS_EXCLUDED = %w[checksum byte_checksum compiler_version
|
|
64
|
-
unicode_tables semantics_hash validated
|
|
64
|
+
unicode_tables semantics_hash validated
|
|
65
|
+
model_bindings].freeze
|
|
65
66
|
|
|
66
67
|
def self.semantics_hash(envelope)
|
|
67
68
|
payload = envelope.except(*SEMANTICS_EXCLUDED)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Parsanol
|
|
7
|
+
module PARG
|
|
8
|
+
# The rule -> model binding table embedded in an artifact (coradoc#307).
|
|
9
|
+
#
|
|
10
|
+
# A sidecar (JSON or YAML) declares, per rule, which model a raw-tree
|
|
11
|
+
# node inflates to:
|
|
12
|
+
#
|
|
13
|
+
# version: 1
|
|
14
|
+
# rules:
|
|
15
|
+
# thematic_break:
|
|
16
|
+
# keys: [hr] # exact capture-key set of the node
|
|
17
|
+
# model: HorizontalRule
|
|
18
|
+
# captures: {} # attribute => scalar capture key
|
|
19
|
+
# children: {} # attribute => nested-node capture key
|
|
20
|
+
# defaults: { style: "---" } # attributes fixed by the rule
|
|
21
|
+
#
|
|
22
|
+
# `parg compile --model-bindings FILE` embeds it under the envelope's
|
|
23
|
+
# additive "model_bindings" key; artifacts compiled without a sidecar
|
|
24
|
+
# carry no such key and are byte-identical to before. The table is not
|
|
25
|
+
# parse semantics, so semantics_hash ignores it; the checksum covers it.
|
|
26
|
+
module ModelBindings
|
|
27
|
+
KEY = "model_bindings"
|
|
28
|
+
VERSION = 1
|
|
29
|
+
RULE_FIELDS = %w[keys model captures children defaults].freeze
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# @param path [String] a .json / .yml / .yaml sidecar
|
|
34
|
+
# @return [Hash] the validated, string-keyed table
|
|
35
|
+
def load(path)
|
|
36
|
+
text = File.read(path, encoding: Encoding::UTF_8)
|
|
37
|
+
raw = File.extname(path).match?(/\.ya?ml\z/i) ? YAML.safe_load(text) : JSON.parse(text)
|
|
38
|
+
normalize(raw)
|
|
39
|
+
rescue JSON::ParserError, Psych::SyntaxError => e
|
|
40
|
+
raise ArtifactError, "model bindings #{path}: #{e.message}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Hash] the table with every rule filled to the full shape
|
|
44
|
+
def normalize(raw)
|
|
45
|
+
raise ArtifactError, "model bindings: expected a mapping" unless raw.is_a?(Hash)
|
|
46
|
+
|
|
47
|
+
table = stringify(raw)
|
|
48
|
+
unless table["version"] == VERSION
|
|
49
|
+
raise ArtifactError, "model bindings: unsupported version #{table['version'].inspect} (want #{VERSION})"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rules = table["rules"]
|
|
53
|
+
raise ArtifactError, "model bindings: `rules` must be a mapping" unless rules.is_a?(Hash)
|
|
54
|
+
|
|
55
|
+
{ "version" => VERSION, "rules" => rules.to_h { |name, rule| [name, normalize_rule(name, rule)] } }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def normalize_rule(name, rule)
|
|
59
|
+
raise ArtifactError, "model bindings: rule #{name.inspect} must be a mapping" unless rule.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
unknown = rule.keys - RULE_FIELDS
|
|
62
|
+
raise ArtifactError, "model bindings: rule #{name.inspect} has unknown field #{unknown.first.inspect}" unless unknown.empty?
|
|
63
|
+
unless rule["model"].is_a?(String) && !rule["model"].empty?
|
|
64
|
+
raise ArtifactError, "model bindings: rule #{name.inspect} needs a `model`"
|
|
65
|
+
end
|
|
66
|
+
unless rule["keys"].is_a?(Array) && !rule["keys"].empty?
|
|
67
|
+
raise ArtifactError, "model bindings: rule #{name.inspect} needs non-empty `keys`"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
"keys" => rule["keys"].map(&:to_s),
|
|
72
|
+
"model" => rule["model"],
|
|
73
|
+
"captures" => hash_field(name, rule, "captures"),
|
|
74
|
+
"children" => hash_field(name, rule, "children"),
|
|
75
|
+
"defaults" => hash_field(name, rule, "defaults"),
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def hash_field(name, rule, field)
|
|
80
|
+
value = rule.fetch(field, {})
|
|
81
|
+
raise ArtifactError, "model bindings: rule #{name.inspect} `#{field}` must be a mapping" unless value.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
value
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def stringify(value)
|
|
87
|
+
case value
|
|
88
|
+
when Hash then value.to_h { |key, item| [key.to_s, stringify(item)] }
|
|
89
|
+
when Array then value.map { |item| stringify(item) }
|
|
90
|
+
else value
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Embed a table in an envelope and re-seal the canonical checksum.
|
|
95
|
+
def embed(envelope, table)
|
|
96
|
+
sealed = envelope.merge(KEY => table)
|
|
97
|
+
sealed["checksum"] = Compiler.checksum(sealed)
|
|
98
|
+
sealed
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Read an artifact file for the dump: JSON plus checksum verification
|
|
102
|
+
# only, so no grammar source and no custom-atom class is needed.
|
|
103
|
+
def read_envelope(path)
|
|
104
|
+
envelope = JSON.parse(File.read(path, encoding: Encoding::UTF_8))
|
|
105
|
+
unless envelope.is_a?(Hash) && envelope["checksum"] == Compiler.checksum(envelope)
|
|
106
|
+
raise ArtifactError, "#{path}: artifact checksum mismatch"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
envelope
|
|
110
|
+
rescue JSON::ParserError => e
|
|
111
|
+
raise ArtifactError, "#{path}: #{e.message}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @return [Hash, nil] the embedded table, nil for an artifact without one
|
|
115
|
+
def from(envelope)
|
|
116
|
+
envelope[KEY]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The machine-readable dump: rule -> model/keys/captures/children/defaults.
|
|
120
|
+
def dump(envelope)
|
|
121
|
+
table = from(envelope) || { "version" => VERSION, "rules" => {} }
|
|
122
|
+
JSON.pretty_generate(table)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/parsanol/parg.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Parsanol
|
|
|
26
26
|
autoload :Compiler, "parsanol/parg/compiler"
|
|
27
27
|
autoload :Artifact, "parsanol/parg/artifact"
|
|
28
28
|
autoload :Bindings, "parsanol/parg/bindings"
|
|
29
|
+
autoload :ModelBindings, "parsanol/parg/model_bindings"
|
|
29
30
|
autoload :Render, "parsanol/parg/render"
|
|
30
31
|
autoload :Derive, "parsanol/parg/derive"
|
|
31
32
|
autoload :SelfHost, "parsanol/parg/selfhost"
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol.rb
CHANGED
|
@@ -66,6 +66,13 @@ module Parsanol
|
|
|
66
66
|
attr_reader :parse_failure_cause
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Raised when the native engine stops because the input nests deeper
|
|
70
|
+
# than its recursion limit. A subclass of ParseFailed with the same
|
|
71
|
+
# message and cause, so existing rescues behave as before; callers that
|
|
72
|
+
# want to retry on another engine can rescue this class instead of
|
|
73
|
+
# matching the message.
|
|
74
|
+
class RecursionLimitExceeded < ParseFailed; end
|
|
75
|
+
|
|
69
76
|
# Class methods added to classes that include Parsanol.
|
|
70
77
|
module ClassMethods
|
|
71
78
|
# Enable automatic rule optimization for all rules in this parser.
|
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.
|
|
4
|
+
version: 1.3.134
|
|
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-11-
|
|
11
|
+
date: 2026-11-05 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
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- lib/parsanol/parg/lints.rb
|
|
121
121
|
- lib/parsanol/parg/lsp.rb
|
|
122
122
|
- lib/parsanol/parg/lutaml.rb
|
|
123
|
+
- lib/parsanol/parg/model_bindings.rb
|
|
123
124
|
- lib/parsanol/parg/node.rb
|
|
124
125
|
- lib/parsanol/parg/parser.rb
|
|
125
126
|
- lib/parsanol/parg/preprocess.rb
|