parsanol 1.3.92 → 1.3.93
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/native/parser.rb +46 -47
- data/lib/parsanol/parg/artifact.rb +21 -4
- data/lib/parsanol/parg/bindings.rb +13 -0
- data/lib/parsanol/parg/compiler.rb +33 -5
- data/lib/parsanol/parg/document.rb +9 -1
- data/lib/parsanol/parg/parser.rb +1 -1
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +6 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7081ebdd8fe55e2b6144cb065f5d3dc082fc190be716f63938389d79b8ade40
|
|
4
|
+
data.tar.gz: cd656fbeaefe6990a5561496ec4bcf4fce282aec0339bd0a2152dcbfa4d2cd70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf4c27ab92936b01428a5c0367999049843bca750ecac9518307562f669b9736fca68d961c5ebcb450e7d32dc4e850526005a6ef6bd05c9ff974500a3017c3fa
|
|
7
|
+
data.tar.gz: bffe515b3b11ab613f72da6f8c2bd78bce1548b320923b05bb47e69aaf88bf5399c6f4aa825d01eb51dd13b1012c6ce078ca77475c554e0d650760d5f6d874d2
|
data/Cargo.lock
CHANGED
|
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "parsanol"
|
|
310
|
-
version = "0.18.
|
|
310
|
+
version = "0.18.8"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "92651c1fc80fad436b29aa642bec9f652bb0170f8ff0dc2a9f5b1411532fe732"
|
|
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.
|
|
329
|
+
version = "0.18.8"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "4423949424ed9894fe79f6df7fec71d5fa7549249390e9d52e34c77a78cdad13"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
|
@@ -167,59 +167,58 @@ module Parsanol
|
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
def grammar_structure_hash(atom)
|
|
170
|
-
Digest::MD5.hexdigest(
|
|
170
|
+
Digest::MD5.hexdigest(append_atom_structure(atom))
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
-
def
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if visited[obj_id]
|
|
177
|
-
return [:cycle, atom.class.name]
|
|
173
|
+
def append_atom_structure(atom, visited = {}.compare_by_identity, output = +"")
|
|
174
|
+
if visited[atom]
|
|
175
|
+
return output << "[:cycle, " << atom.class.name.inspect << "]"
|
|
178
176
|
end
|
|
179
177
|
|
|
180
|
-
visited[
|
|
181
|
-
|
|
178
|
+
visited[atom] = true
|
|
182
179
|
case atom
|
|
183
|
-
when
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
[:seq,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
[:trivia_capture, atom.capturers.to_a.sort,
|
|
216
|
-
|
|
217
|
-
when
|
|
218
|
-
[:trivia,
|
|
219
|
-
|
|
220
|
-
|
|
180
|
+
when Parsanol::Atoms::Entity
|
|
181
|
+
append_atom_structure(atom.parslet, visited, output)
|
|
182
|
+
when Parsanol::Atoms::Str
|
|
183
|
+
output << "[:str, " << atom.str.inspect << "]"
|
|
184
|
+
when Parsanol::Atoms::Re
|
|
185
|
+
output << "[:re, " << atom.match.inspect << "]"
|
|
186
|
+
when Parsanol::Atoms::Sequence, Parsanol::Atoms::Alternative
|
|
187
|
+
children = atom.is_a?(Parsanol::Atoms::Sequence) ? atom.parslets : atom.alternatives
|
|
188
|
+
output << (atom.is_a?(Parsanol::Atoms::Sequence) ? "[:seq, [" : "[:alt, [")
|
|
189
|
+
index = 0
|
|
190
|
+
while index < children.length
|
|
191
|
+
output << ", " unless index.zero?
|
|
192
|
+
append_atom_structure(children[index], visited, output)
|
|
193
|
+
index += 1
|
|
194
|
+
end
|
|
195
|
+
output << "]]"
|
|
196
|
+
when Parsanol::Atoms::Repetition
|
|
197
|
+
output << "[:rep, " << atom.min.inspect << ", " << atom.max.inspect << ", "
|
|
198
|
+
append_atom_structure(atom.parslet, visited, output) << "]"
|
|
199
|
+
when Parsanol::Atoms::Named
|
|
200
|
+
output << "[:named, " << atom.name.to_s.inspect << ", "
|
|
201
|
+
append_atom_structure(atom.parslet, visited, output) << "]"
|
|
202
|
+
when Parsanol::Atoms::Lookahead
|
|
203
|
+
output << "[:lookahead, " << atom.positive.inspect << ", "
|
|
204
|
+
append_atom_structure(atom.bound_parslet, visited, output) << "]"
|
|
205
|
+
when Parsanol::Atoms::Constant
|
|
206
|
+
# The inspected constant remains a string inside the fingerprint.
|
|
207
|
+
output << "[:constant, " << atom.value.inspect.inspect << "]"
|
|
208
|
+
when Parsanol::Atoms::Lookbehind
|
|
209
|
+
output << "[:lookbehind, " << atom.count.inspect << ", " << atom.pattern.inspect << ", " << atom.positive.inspect << "]"
|
|
210
|
+
# Capturing trivia must precede its Trivia and Ignored parents.
|
|
211
|
+
when Parsanol::Atoms::TriviaCapture
|
|
212
|
+
output << "[:trivia_capture, " << atom.capturers.to_a.sort.inspect << ", "
|
|
213
|
+
append_atom_structure(atom.wrapped_atom, visited, output) << "]"
|
|
214
|
+
when Parsanol::Atoms::Trivia
|
|
215
|
+
output << "[:trivia, "
|
|
216
|
+
append_atom_structure(atom.wrapped_atom, visited, output) << "]"
|
|
217
|
+
when Parsanol::Atoms::Ignored
|
|
218
|
+
output << "[:ignored, "
|
|
219
|
+
append_atom_structure(atom.wrapped_atom, visited, output) << "]"
|
|
221
220
|
else
|
|
222
|
-
[:unknown, atom.class.name]
|
|
221
|
+
output << "[:unknown, " << atom.class.name.inspect << "]"
|
|
223
222
|
end
|
|
224
223
|
end
|
|
225
224
|
end
|
|
@@ -157,16 +157,33 @@ module Parsanol
|
|
|
157
157
|
hash = test.is_a?(Document::Test) ? test_to_hash(test) : test
|
|
158
158
|
entry_name = hash["entry"] || entries.first
|
|
159
159
|
begin
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
shape = parse(entry_name, hash["input"], mode: mode)
|
|
161
|
+
case hash["kind"]
|
|
162
|
+
when "reject"
|
|
162
163
|
"test #{hash['input'].inspect}: expected the input to be rejected"
|
|
163
|
-
|
|
164
|
+
when "example"
|
|
165
|
+
bound = apply_bindings(entry_name, shape)
|
|
164
166
|
expect = hash["expect"].to_h { |key, value| [key.to_sym, value] }
|
|
165
|
-
mismatched = expect.reject
|
|
167
|
+
mismatched = expect.reject do |key, value|
|
|
168
|
+
bound.key?(key) && Bindings.canonical(bound[key]) == Bindings.canonical(value)
|
|
169
|
+
end
|
|
166
170
|
next if mismatched.empty?
|
|
167
171
|
|
|
168
172
|
"test #{hash['input'].inspect}: expected captures " \
|
|
169
173
|
"#{mismatched.transform_values(&:inspect).inspect}, got #{bound.inspect}"
|
|
174
|
+
when "capture"
|
|
175
|
+
captures = Bindings.collect_captures(shape)
|
|
176
|
+
mismatched = hash["expect"].reject do |key, value|
|
|
177
|
+
actual = captures[key.to_sym]&.first
|
|
178
|
+
!actual.nil? && Bindings.canonical(actual) == Bindings.canonical(value)
|
|
179
|
+
end
|
|
180
|
+
next if mismatched.empty?
|
|
181
|
+
|
|
182
|
+
actuals = mismatched.keys.to_h do |key|
|
|
183
|
+
[key, Bindings.canonical(captures[key.to_sym]&.first)]
|
|
184
|
+
end
|
|
185
|
+
"test #{hash['input'].inspect}: expected captures " \
|
|
186
|
+
"#{mismatched.transform_values(&:inspect).inspect}, got #{actuals.inspect}"
|
|
170
187
|
end
|
|
171
188
|
rescue Parsanol::ParseFailed
|
|
172
189
|
unless hash["kind"] == "reject"
|
|
@@ -23,6 +23,19 @@ module Parsanol
|
|
|
23
23
|
out
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Canonical test-value form: one equality surface for capture and
|
|
27
|
+
# example expectations on both sides of the comparison (Slice vs
|
|
28
|
+
# source literal was comparing heterogeneous types). Terminals
|
|
29
|
+
# reduce to their text; structure recurses; keys stringify.
|
|
30
|
+
def canonical(value)
|
|
31
|
+
case value
|
|
32
|
+
when Hash then value.to_h { |key, item| [key.to_s, canonical(item)] }
|
|
33
|
+
when Array then value.map { |item| canonical(item) }
|
|
34
|
+
when nil, true, false, Integer then value
|
|
35
|
+
else String(value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
26
39
|
def collect_captures(shape)
|
|
27
40
|
found = Hash.new { |hash, key| hash[key] = [] }
|
|
28
41
|
queue = [shape]
|
|
@@ -314,22 +314,43 @@ module Parsanol
|
|
|
314
314
|
def run_tests
|
|
315
315
|
return [] if @document.tests.empty?
|
|
316
316
|
|
|
317
|
-
default_entry = resolve_default_entry
|
|
318
317
|
view = test_view
|
|
319
318
|
@document.tests.filter_map do |test|
|
|
320
319
|
entry_name = test.entry || default_entry
|
|
321
320
|
rule = @document.entries.fetch(entry_name)
|
|
322
321
|
begin
|
|
323
322
|
shape = atom_for(rule).parse(test.input)
|
|
324
|
-
|
|
323
|
+
case test.kind
|
|
324
|
+
when :reject
|
|
325
325
|
"test #{test.input.inspect}: expected the input to be rejected"
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
when :example
|
|
327
|
+
bindings = binding_list(rule)
|
|
328
|
+
if bindings.empty?
|
|
329
|
+
raise CompileError,
|
|
330
|
+
"example tests need a bindings section (use `capture` " \
|
|
331
|
+
"for raw-capture assertions)"
|
|
332
|
+
end
|
|
333
|
+
bound = Bindings.apply(view, { "bindings" => bindings }, shape)
|
|
334
|
+
mismatched = test.expect.reject do |key, value|
|
|
335
|
+
bound.key?(key) && Bindings.canonical(bound[key]) == Bindings.canonical(value)
|
|
336
|
+
end
|
|
329
337
|
next if mismatched.empty?
|
|
330
338
|
|
|
331
339
|
"test #{test.input.inspect}: expected captures " \
|
|
332
340
|
"#{mismatched.transform_values(&:inspect).inspect}, got #{bound.inspect}"
|
|
341
|
+
when :capture
|
|
342
|
+
captures = Bindings.collect_captures(shape)
|
|
343
|
+
mismatched = test.expect.reject do |key, value|
|
|
344
|
+
actual = captures[key.to_sym]&.first
|
|
345
|
+
!actual.nil? && Bindings.canonical(actual) == Bindings.canonical(value)
|
|
346
|
+
end
|
|
347
|
+
next if mismatched.empty?
|
|
348
|
+
|
|
349
|
+
actuals = mismatched.keys.to_h do |key|
|
|
350
|
+
[key, Bindings.canonical(captures[key.to_sym]&.first)]
|
|
351
|
+
end
|
|
352
|
+
"test #{test.input.inspect}: expected captures " \
|
|
353
|
+
"#{mismatched.transform_values(&:inspect).inspect}, got #{actuals.inspect}"
|
|
333
354
|
end
|
|
334
355
|
rescue Parsanol::ParseFailed
|
|
335
356
|
unless test.kind == :reject
|
|
@@ -339,6 +360,13 @@ module Parsanol
|
|
|
339
360
|
end
|
|
340
361
|
end
|
|
341
362
|
|
|
363
|
+
# Only consulted when a test omits its entry: a multi-entry
|
|
364
|
+
# grammar whose tests are all explicit must compile (parsanol-
|
|
365
|
+
# ruby#216). Memoized so the raise happens once, on first need.
|
|
366
|
+
def default_entry
|
|
367
|
+
@default_entry ||= resolve_default_entry
|
|
368
|
+
end
|
|
369
|
+
|
|
342
370
|
def resolve_default_entry
|
|
343
371
|
own = @document.own_entries
|
|
344
372
|
return own.first if own && own.size == 1
|
|
@@ -5,7 +5,15 @@ module Parsanol
|
|
|
5
5
|
# Parsed PARG document: grammar rules plus the binding sections.
|
|
6
6
|
class Document
|
|
7
7
|
Binding = Struct.new(:capture, :path, :type, :card, :preprocess)
|
|
8
|
-
|
|
8
|
+
# `capture` = phase 1 (grammar layer): asserts the raw capture tree,
|
|
9
|
+
# no bindings section needed. `example` = phase 2 (bindings layer):
|
|
10
|
+
# asserts the bound, preprocessed attribute hash.
|
|
11
|
+
# `capture` = phase 1 (grammar layer): asserts the raw capture tree,
|
|
12
|
+
# no bindings section needed. `example` = phase 2 (bindings layer):
|
|
13
|
+
# asserts the bound, preprocessed attribute hash.
|
|
14
|
+
TEST_KINDS = %i[accept reject example capture].freeze
|
|
15
|
+
# Test kinds whose expectations are `{ key: value }` pairs.
|
|
16
|
+
PAIR_KINDS = %w[example capture].freeze
|
|
9
17
|
|
|
10
18
|
Test = Struct.new(:entry, :kind, :input, :expect)
|
|
11
19
|
|
data/lib/parsanol/parg/parser.rb
CHANGED
|
@@ -83,7 +83,7 @@ module Parsanol
|
|
|
83
83
|
kind = ident.value
|
|
84
84
|
input = unquote(expect(:str).value)
|
|
85
85
|
expect_pairs = {}
|
|
86
|
-
if kind
|
|
86
|
+
if Document::PAIR_KINDS.include?(kind) && peek&.type == :punct && peek.value == "{"
|
|
87
87
|
advance
|
|
88
88
|
skip_newlines
|
|
89
89
|
until peek&.type == :punct && peek.value == "}"
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -146,7 +146,12 @@ module Parsanol
|
|
|
146
146
|
# The shared program's compile-time analysis (heavy-memo
|
|
147
147
|
# seeding) covered this structure; the fresh root inherits
|
|
148
148
|
# it so its first parse memoizes like the original's.
|
|
149
|
-
|
|
149
|
+
if @heavy_by_hash&.[](hash_key)
|
|
150
|
+
(@heavy ||= {}.compare_by_identity)[root] = true
|
|
151
|
+
trim_cache(@heavy)
|
|
152
|
+
end
|
|
153
|
+
cache[root] = shared
|
|
154
|
+
trim_cache(cache)
|
|
150
155
|
return shared
|
|
151
156
|
end
|
|
152
157
|
|