parsanol 1.3.113-arm-linux → 1.3.115-arm-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/README.adoc +11 -0
- data/lib/parsanol/3.2/parsanol_native.so +2 -2
- data/lib/parsanol/3.3/parsanol_native.so +2 -2
- data/lib/parsanol/3.4/parsanol_native.so +2 -2
- data/lib/parsanol/4.0/parsanol_native.so +2 -2
- data/lib/parsanol/native/libparsanol.so +2 -2
- data/lib/parsanol/parg/compiler.rb +19 -5
- data/lib/parsanol/parg/document.rb +5 -1
- data/lib/parsanol/parg/frontend.rb +46 -3
- data/lib/parsanol/parg/parser.rb +18 -0
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +21 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87e094abf1d96459b9558d746f9a436b32e0b3ac58d6529b565159a771e1a9e9
|
|
4
|
+
data.tar.gz: a4331601780b6d3681b45ed55dfbdbe94c227d59cff2c743d3804b5ad2a263bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 491dd96953046d5f729a5bbb403cdb4ce0ce7d1db7365b14fbd395145067e26e1a43968dae4d31176f6e77dc19978039a6297363e27ea63dfbd225370b42430d
|
|
7
|
+
data.tar.gz: 01e15a6809c13dde391770ba64eac2edca3c794df440278e8099832696d212f6c8446d7eded2c03d443879c11b7877314a1178cf1b328c5c6f0da576aaf3edec
|
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:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.2/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39170404 bytes
|
|
4
|
+
sha256: bbab9454dfa91df09fdcc00143cf18ff89d277657f975d13959c01255f7c5707
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.3/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39355636 bytes
|
|
4
|
+
sha256: 808eb780f0317adb4f98e7323b90e7d99e55d729a8d961b1b63fcf4660ab4562
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/3.4/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39349796 bytes
|
|
4
|
+
sha256: bbc22c4d658fc4a0a9a72344e5765a44ec9985e0ef1373704cc31014565e711d
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/4.0/parsanol_native.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39359512 bytes
|
|
4
|
+
sha256: 017f6f3c4909e39c878a6f28f562d577a8b02442de38a0ac9760b48873871426
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/parsanol/native/libparsanol.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35531016 bytes
|
|
4
|
+
sha256: 968ae4d54093a740fd7ce2e59fe10ab73dd853e23a852e5d22e292168a3f9c5a
|
|
@@ -233,12 +233,24 @@ module Parsanol
|
|
|
233
233
|
# compile time — the tree shape consumers read, opt-in via
|
|
234
234
|
# `mode` in the grammar header. Engines see ordinary
|
|
235
235
|
# Named atoms; identity rides the differential corpus.
|
|
236
|
-
def apply_modern_captures(atom)
|
|
236
|
+
def apply_modern_captures(atom, rule_name)
|
|
237
237
|
return atom if @document.edition == "v1"
|
|
238
|
+
# A glue rule's own body compiles unwrapped (parsanol-ruby#364):
|
|
239
|
+
# the declaration means the tree never sees this subtree named,
|
|
240
|
+
# inside or out.
|
|
241
|
+
return atom if glue_rule?(rule_name)
|
|
238
242
|
|
|
239
243
|
wrap_uncaptured(atom)
|
|
240
244
|
end
|
|
241
245
|
|
|
246
|
+
# The `glue` declaration's verdict (parsanol-ruby#364): references
|
|
247
|
+
# to declared glue rules never wrap — the trivia exemption's
|
|
248
|
+
# "glue never becomes a capture", author-declared for rules that
|
|
249
|
+
# eat more than whitespace.
|
|
250
|
+
def glue_rule?(name)
|
|
251
|
+
@document.glue_rules.include?(name.to_s)
|
|
252
|
+
end
|
|
253
|
+
|
|
242
254
|
def wrap_uncaptured(atom)
|
|
243
255
|
case atom
|
|
244
256
|
when Atoms::Named
|
|
@@ -254,10 +266,11 @@ module Parsanol
|
|
|
254
266
|
# capture-producing refs both stay unwrapped; the bodies
|
|
255
267
|
# agree by design.
|
|
256
268
|
if trivia_only?(atom.rule_name.to_s, { atom.rule_name.to_s => true }) ||
|
|
269
|
+
glue_rule?(atom.rule_name.to_s) ||
|
|
257
270
|
produces_capture?(atom.parslet)
|
|
258
271
|
atom
|
|
259
272
|
else
|
|
260
|
-
Atoms::Named.new(atom, atom.rule_name.
|
|
273
|
+
Atoms::Named.new(atom, atom.rule_name.to_sym)
|
|
261
274
|
end
|
|
262
275
|
# rubocop:enable Lint/DuplicateBranch
|
|
263
276
|
when Atoms::Sequence, Atoms::Alternative
|
|
@@ -440,9 +453,10 @@ module Parsanol
|
|
|
440
453
|
case atom
|
|
441
454
|
when Atoms::Named then nil
|
|
442
455
|
when Atoms::Entity
|
|
443
|
-
# Mirror wrap_uncaptured's verdicts: trivia glue
|
|
444
|
-
# capture-producing refs
|
|
456
|
+
# Mirror wrap_uncaptured's verdicts: trivia glue, declared
|
|
457
|
+
# glue, and capture-producing refs all stay bare.
|
|
445
458
|
unless trivia_only?(atom.rule_name.to_s, { atom.rule_name.to_s => true }) ||
|
|
459
|
+
glue_rule?(atom.rule_name.to_s) ||
|
|
446
460
|
produces_capture?(atom.parslet)
|
|
447
461
|
lines << "rule #{site}: reference to #{atom.rule_name} produces no capture -- " \
|
|
448
462
|
"modern mode would name it #{atom.rule_name}"
|
|
@@ -482,7 +496,7 @@ module Parsanol
|
|
|
482
496
|
# atom_for; a mutual-reference grammar must hit the cache,
|
|
483
497
|
# not rebuild), then the cache takes the wrapped tree.
|
|
484
498
|
@atom_cache[name] = atom
|
|
485
|
-
atom = apply_modern_captures(atom)
|
|
499
|
+
atom = apply_modern_captures(atom, name)
|
|
486
500
|
@atom_cache[name] = atom
|
|
487
501
|
atom
|
|
488
502
|
end
|
|
@@ -18,7 +18,7 @@ module Parsanol
|
|
|
18
18
|
Test = Struct.new(:entry, :kind, :input, :expect)
|
|
19
19
|
|
|
20
20
|
attr_accessor :grammar_name, :version, :edition, :source, :uses, :skip_declaration, :skip_source,
|
|
21
|
-
:atomic_rules, :skip_capture_key, :brace_rules
|
|
21
|
+
:atomic_rules, :skip_capture_key, :brace_rules, :glue_rules
|
|
22
22
|
# R2 declared resync: rule name -> skip pattern (the span matched
|
|
23
23
|
# at a recovery point is consumed as annotated error text).
|
|
24
24
|
attr_reader :resyncs
|
|
@@ -60,6 +60,10 @@ module Parsanol
|
|
|
60
60
|
# contiguously).
|
|
61
61
|
@atomic_rules = []
|
|
62
62
|
@brace_rules = []
|
|
63
|
+
# Rules declared `glue <name> ...` (parsanol-ruby#364): the
|
|
64
|
+
# edition-2 wrap never names them — references stay bare and
|
|
65
|
+
# the rule's own body compiles unwrapped.
|
|
66
|
+
@glue_rules = []
|
|
63
67
|
@authored_rules = nil
|
|
64
68
|
# R2 declared resync: rule -> skip pattern. Recovery consumes
|
|
65
69
|
# each pattern match at a failure point as annotated error text
|
|
@@ -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/parg/parser.rb
CHANGED
|
@@ -338,6 +338,24 @@ module Parsanol
|
|
|
338
338
|
skip_newlines
|
|
339
339
|
next
|
|
340
340
|
end
|
|
341
|
+
# `glue <rule> [<rule>...]` (parsanol-ruby#364): names rules
|
|
342
|
+
# whose references edition 2 never wraps — glue the tree must
|
|
343
|
+
# not see. Same-line names accumulate; the declaration may
|
|
344
|
+
# appear any number of times.
|
|
345
|
+
if peek&.type == :ident && peek.value == "glue"
|
|
346
|
+
advance
|
|
347
|
+
names = []
|
|
348
|
+
while peek&.type == :ident
|
|
349
|
+
names << ident.value
|
|
350
|
+
end
|
|
351
|
+
if names.empty?
|
|
352
|
+
raise ParseError, "glue must name at least one rule"
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
document.glue_rules.concat(names)
|
|
356
|
+
skip_newlines
|
|
357
|
+
next
|
|
358
|
+
end
|
|
341
359
|
# `atomic <rule> = ...` suppresses skip-trivia injection inside
|
|
342
360
|
# the rule body: token-shaped rules match their input
|
|
343
361
|
# contiguously instead of spanning trivia between iterations.
|
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
|
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.115
|
|
5
5
|
platform: arm-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-29 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
|