parsanol 1.3.114 → 1.3.115
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/README.adoc +11 -0
- data/lib/parsanol/parg/frontend.rb +46 -3
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +21 -2
- 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: 116a1ab6a5ed95d4b3998c1d1d0b4860ed7dabafb5464c8aa8ff7d8602d9c221
|
|
4
|
+
data.tar.gz: 0f4799930aa443744b0ed3367fc139ab18eb565aca24a640a642d37b80596736
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df5ecd79facb4f09fcbbd5328ce3a4d2713fb230322a1f6ff4fccfd68fc6b12cbb6fa5db573df86035e3ce85c1626bed58b75c494a38701305456d35c34f6502
|
|
7
|
+
data.tar.gz: c1f70bd90dd3ba48126dbb010d9ba89013b39cab654284224afe5031d7713caae84cc3c26b6b454f508b6fa7083504a52bc49dc1c18651a9c381042a94588b71
|
data/Cargo.lock
CHANGED
|
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "parsanol"
|
|
310
|
-
version = "0.19.
|
|
310
|
+
version = "0.19.10"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "19a06284aa6f46b116ad287275b3febfbd5f36b0f85b130f492d8244afea8c13"
|
|
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.19.
|
|
329
|
+
version = "0.19.10"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "dd454669ba1439d5c4f1bb3cd71861cda751586a3ddf2521383f8fbbb4b19049"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
data/README.adoc
CHANGED
|
@@ -31,6 +31,17 @@ While maintaining full API compatibility with Parslet, Parsanol features a compl
|
|
|
31
31
|
* <<debug-tools,Debug Tools>> - Tracing and grammar visualization
|
|
32
32
|
* <<benchmarking,Benchmarking>> - Built-in performance testing tools
|
|
33
33
|
|
|
34
|
+
== Releases
|
|
35
|
+
|
|
36
|
+
parsanol is one product on **four runtimes**: **rs** (crates.io +
|
|
37
|
+
`@parsanol/wasm` + release binaries), **ruby** (rubygems), **python**
|
|
38
|
+
(PyPI, `parsanol`), and **typescript** (npm, `parsanol` — the
|
|
39
|
+
wasm-backed runtime). **Every engine update releases all four
|
|
40
|
+
together**; a release that ships some runtimes and not the rest is
|
|
41
|
+
incomplete. If the runtime you use lags the latest grammar-language
|
|
42
|
+
features, check the other registries' changelogs and the release train
|
|
43
|
+
on the https://github.com/parsanol/parsanol-ruby/actions[Release workflow].
|
|
44
|
+
|
|
34
45
|
== Installation
|
|
35
46
|
|
|
36
47
|
Add this line to your application's Gemfile:
|
|
@@ -20,9 +20,24 @@ module Parsanol
|
|
|
20
20
|
ENTRY_LINE = /\Aentry\s+(\w+)\s*:\s*(\w+)/
|
|
21
21
|
USE_LINE = /\Ause\s+(\w+)/
|
|
22
22
|
GRAMMAR_LINE = /\Agrammar\s+(\w+)\s+version\s+"([^"]+)"/
|
|
23
|
+
# The capture-edition tail: the canonical numeric spelling or the
|
|
24
|
+
# quoted aliases (ruby#361); `mode` stays the deprecated keyword.
|
|
25
|
+
EDITION_TAIL = /
|
|
26
|
+
\s+(?:edition\s+(?:"(?<quoted>[^"]+)"|(?<number>\d+))|mode\s+"(?<mode>[^"]+)")
|
|
27
|
+
/x
|
|
28
|
+
GLUE_LINE = /\Aglue\s+([a-z_][a-z0-9_]*(?:\s+[a-z_][a-z0-9_]*)*)\s*\z/
|
|
23
29
|
|
|
24
30
|
module_function
|
|
25
31
|
|
|
32
|
+
# Brace depth for the line-oriented collection: braces inside
|
|
33
|
+
# string literals are grammar data — test pins quote whole
|
|
34
|
+
# language forms, including deliberately malformed ones whose
|
|
35
|
+
# unbalanced braces would push the section depth off (ruby#369).
|
|
36
|
+
def brace_delta(line)
|
|
37
|
+
bare = line.gsub(/"(?:[^"\\]|\\.)*"/, "")
|
|
38
|
+
bare.scan("{").count - bare.scan("}").count
|
|
39
|
+
end
|
|
40
|
+
|
|
26
41
|
def parse(source)
|
|
27
42
|
SelfHost.validate(source) unless SelfHost.available? && !SelfHost.valid?(source)
|
|
28
43
|
|
|
@@ -43,7 +58,17 @@ module Parsanol
|
|
|
43
58
|
end
|
|
44
59
|
next if stripped.empty? || stripped.start_with?("#")
|
|
45
60
|
|
|
46
|
-
if state[:
|
|
61
|
+
if state[:rule_depth]&.positive?
|
|
62
|
+
# A brace-form rule body spans lines (ruby#311): keep
|
|
63
|
+
# appending until its braces balance.
|
|
64
|
+
state[:rule_lines][state[:rule_depth_name]] << line
|
|
65
|
+
state[:rule_depth] += brace_delta(line)
|
|
66
|
+
if state[:rule_depth].zero?
|
|
67
|
+
merge_rule(document, state[:rule_lines][state[:rule_depth_name]])
|
|
68
|
+
state[:rule_depth] = nil
|
|
69
|
+
state[:rule_depth_name] = nil
|
|
70
|
+
end
|
|
71
|
+
elsif state[:section]
|
|
47
72
|
advance_section(document, state, line, deferred, preprocess_texts)
|
|
48
73
|
else
|
|
49
74
|
route_top_level(document, stripped, line, state,
|
|
@@ -59,7 +84,7 @@ module Parsanol
|
|
|
59
84
|
# Consumes one line inside a deferred/closed-by-brace section.
|
|
60
85
|
def advance_section(document, state, line, deferred, preprocess_texts)
|
|
61
86
|
section = state[:section]
|
|
62
|
-
state[:depth] +=
|
|
87
|
+
state[:depth] += brace_delta(line)
|
|
63
88
|
if state[:depth].positive?
|
|
64
89
|
preprocess_texts.last << line if section == "preprocess"
|
|
65
90
|
state[:section_lines] << line
|
|
@@ -91,9 +116,19 @@ module Parsanol
|
|
|
91
116
|
when USE_LINE
|
|
92
117
|
document.uses << Regexp.last_match(1)
|
|
93
118
|
doc_comments = []
|
|
119
|
+
when GLUE_LINE
|
|
120
|
+
document.glue_rules.concat(Regexp.last_match(1).split)
|
|
121
|
+
doc_comments = []
|
|
94
122
|
when GRAMMAR_LINE
|
|
95
123
|
document.grammar_name = Regexp.last_match(1)
|
|
96
124
|
document.version = Regexp.last_match(2)
|
|
125
|
+
tail = stripped.match(EDITION_TAIL)
|
|
126
|
+
document.edition = if tail.nil?
|
|
127
|
+
"v1"
|
|
128
|
+
else
|
|
129
|
+
level = tail[:number] || tail[:quoted] || tail[:mode]
|
|
130
|
+
["2", "v2"].include?(level) ? "v2" : "v1"
|
|
131
|
+
end
|
|
97
132
|
doc_comments = []
|
|
98
133
|
when NAMED_SECTION
|
|
99
134
|
state[:section] = Regexp.last_match(1)
|
|
@@ -128,7 +163,15 @@ module Parsanol
|
|
|
128
163
|
document.docs[rule_name] = doc_comments.join("\n") unless doc_comments.empty?
|
|
129
164
|
doc_comments = []
|
|
130
165
|
state[:rule_lines][rule_name] = line
|
|
131
|
-
|
|
166
|
+
depth = brace_delta(line)
|
|
167
|
+
if depth.positive?
|
|
168
|
+
# Brace-form body opens (ruby#311): the rule completes on a
|
|
169
|
+
# later line; the continuation loop appends and merges then.
|
|
170
|
+
state[:rule_depth] = depth
|
|
171
|
+
state[:rule_depth_name] = rule_name
|
|
172
|
+
else
|
|
173
|
+
merge_rule(document, line)
|
|
174
|
+
end
|
|
132
175
|
else
|
|
133
176
|
doc_comments = []
|
|
134
177
|
end
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -452,6 +452,11 @@ module Parsanol
|
|
|
452
452
|
@subs = {} # [obj_id, flag] => pc or :pending
|
|
453
453
|
@pending = {} # [obj_id, flag] => [[call_idx, body], ...]
|
|
454
454
|
@backtracking_prone = false
|
|
455
|
+
# Grammar rules form a shared graph. Re-analyzing every reference
|
|
456
|
+
# repeats whole alternative trees during inlining. These caches
|
|
457
|
+
# live only for this compiler and retain the depth limit's meaning.
|
|
458
|
+
@first_byte_tables = {}.compare_by_identity
|
|
459
|
+
@nullability = {}.compare_by_identity
|
|
455
460
|
end
|
|
456
461
|
|
|
457
462
|
attr_reader :ops, :backtracking_prone
|
|
@@ -484,6 +489,13 @@ module Parsanol
|
|
|
484
489
|
def first_byte_table(atom, depth = 0)
|
|
485
490
|
return nil if depth > 10
|
|
486
491
|
|
|
492
|
+
cached = (@first_byte_tables[atom] ||= {})
|
|
493
|
+
return cached[depth] if cached.key?(depth)
|
|
494
|
+
|
|
495
|
+
cached[depth] = compute_first_byte_table(atom, depth)&.freeze
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def compute_first_byte_table(atom, depth)
|
|
487
499
|
case atom
|
|
488
500
|
when Parsanol::Atoms::Str
|
|
489
501
|
b = atom.str.getbyte(0)
|
|
@@ -508,7 +520,7 @@ module Parsanol
|
|
|
508
520
|
i += 1
|
|
509
521
|
end
|
|
510
522
|
else
|
|
511
|
-
t = ct
|
|
523
|
+
t = ct.dup
|
|
512
524
|
end
|
|
513
525
|
unless nullable?(child, depth + 1)
|
|
514
526
|
nullable_all = false
|
|
@@ -529,7 +541,7 @@ module Parsanol
|
|
|
529
541
|
i += 1
|
|
530
542
|
end
|
|
531
543
|
else
|
|
532
|
-
t = bt
|
|
544
|
+
t = bt.dup
|
|
533
545
|
end
|
|
534
546
|
end
|
|
535
547
|
t
|
|
@@ -572,6 +584,13 @@ module Parsanol
|
|
|
572
584
|
def nullable?(atom, depth = 0)
|
|
573
585
|
return false if depth > 10
|
|
574
586
|
|
|
587
|
+
cached = (@nullability[atom] ||= {})
|
|
588
|
+
return cached[depth] if cached.key?(depth)
|
|
589
|
+
|
|
590
|
+
cached[depth] = compute_nullable(atom, depth)
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def compute_nullable(atom, depth)
|
|
575
594
|
case atom
|
|
576
595
|
when Parsanol::Atoms::Str then atom.str.bytesize.zero?
|
|
577
596
|
when Parsanol::Atoms::Re then false
|