parsanol 1.3.81 → 1.3.82
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/ext/parsanol_cdylib/Cargo.toml +1 -1
- data/ext/parsanol_native/Cargo.toml +1 -1
- data/lib/parsanol/atoms/sequence.rb +22 -11
- data/lib/parsanol/atoms/trivia.rb +13 -9
- data/lib/parsanol/native/serializer.rb +9 -5
- data/lib/parsanol/parser.rb +10 -2
- data/lib/parsanol/skip.rb +64 -45
- data/lib/parsanol/version.rb +1 -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: c56435897ed6f65bc101a9df1cdb0c0510917824295fa003893cdf7e447a32a4
|
|
4
|
+
data.tar.gz: 8308c7b4bc8179fbf7867ab1769c53b1f6759ce6ad8cd7ca2dc9362e8c5a3c58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ad6958bee6a30a11ce020e95a1b084bd26155f7293c2c127c2ff264850da1df22bbde4eb1f321f8e442448492d5612adc25fc3ccd19fe8a65b3e9ea7a2f9731
|
|
7
|
+
data.tar.gz: 4e353b492c5113f197822c528bf37622b54bc6eae4d2a4bb73f171cbce7edadd00c96ed55928eda73a9d2746d68e14fb4fd212ec2c768bca30a733123a32c1c8
|
data/Cargo.lock
CHANGED
|
@@ -307,9 +307,9 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "parsanol"
|
|
310
|
-
version = "0.
|
|
310
|
+
version = "0.18.0"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "dc899494d6b94ea8df14279b7dcbac89d59ba4d1be52f7b8ffe840674c6c8e3c"
|
|
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.
|
|
329
|
+
version = "0.18.0"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "b437046956f356eee0eb9b407ff1f1f6286af1f70f1af1d305c54a64c65b64e2"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
|
@@ -32,7 +32,7 @@ magnus = { version = "0.9" }
|
|
|
32
32
|
# parsanol parser library (crates.io; 0.17.0 carries the LookSpec regex
|
|
33
33
|
# lookbehind (rs#163 gap 1) and the widened opaque writeback: the
|
|
34
34
|
# lazy-continuation fix, parsanol-ruby#160)
|
|
35
|
-
parsanol = { version = "0.
|
|
35
|
+
parsanol = { version = "0.18.0", features = ["ruby"] }
|
|
36
36
|
|
|
37
37
|
# Logging
|
|
38
38
|
log = "0.4"
|
|
@@ -18,14 +18,25 @@ module Parsanol
|
|
|
18
18
|
def initialize(*components)
|
|
19
19
|
super()
|
|
20
20
|
@parslets = components
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
|
|
21
|
+
# The failure message renders LAZILY (error_msgs): constructing
|
|
22
|
+
# sequences is hot-path work for the builder/injector, and
|
|
23
|
+
# stringifying the whole subtree eagerly is exponential on
|
|
24
|
+
# shared cross-referencing grammars (parsanol-ruby#180 side
|
|
25
|
+
# observation: a 25-rule Fibonacci-shaped grammar took 6s).
|
|
26
|
+
@fail_msg = nil
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
# Error messages hash (for compatibility)
|
|
27
30
|
def error_msgs
|
|
28
|
-
{ failed:
|
|
31
|
+
{ failed: fail_msg }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Rendered lazily (see initialize); construction is hot-path
|
|
35
|
+
# builder/injector work and stringifying the whole subtree
|
|
36
|
+
# eagerly is exponential on shared cross-referencing grammars
|
|
37
|
+
# (parsanol-ruby#180 side observation).
|
|
38
|
+
def fail_msg
|
|
39
|
+
@fail_msg ||= "Failed to match sequence (#{inspect})"
|
|
29
40
|
end
|
|
30
41
|
|
|
31
42
|
# Appends a parser to this sequence with flattening.
|
|
@@ -104,7 +115,7 @@ module Parsanol
|
|
|
104
115
|
# Single element sequence
|
|
105
116
|
def match_single(parser, source, context, consume_all)
|
|
106
117
|
success, value = parser.apply(source, context, consume_all)
|
|
107
|
-
return context.err(self, source,
|
|
118
|
+
return context.err(self, source, fail_msg, [value]) unless success
|
|
108
119
|
|
|
109
120
|
ok([:sequence, value])
|
|
110
121
|
end
|
|
@@ -112,10 +123,10 @@ module Parsanol
|
|
|
112
123
|
# Two-element sequence
|
|
113
124
|
def match_pair(p1, p2, source, context, consume_all)
|
|
114
125
|
success, v1 = p1.apply(source, context, false)
|
|
115
|
-
return context.err(self, source,
|
|
126
|
+
return context.err(self, source, fail_msg, [v1]) unless success
|
|
116
127
|
|
|
117
128
|
success, v2 = p2.apply(source, context, consume_all)
|
|
118
|
-
return context.err(self, source,
|
|
129
|
+
return context.err(self, source, fail_msg, [v2]) unless success
|
|
119
130
|
|
|
120
131
|
ok([:sequence, v1, v2])
|
|
121
132
|
end
|
|
@@ -123,13 +134,13 @@ module Parsanol
|
|
|
123
134
|
# Three-element sequence
|
|
124
135
|
def match_triple(p1, p2, p3, source, context, consume_all)
|
|
125
136
|
success, v1 = p1.apply(source, context, false)
|
|
126
|
-
return context.err(self, source,
|
|
137
|
+
return context.err(self, source, fail_msg, [v1]) unless success
|
|
127
138
|
|
|
128
139
|
success, v2 = p2.apply(source, context, false)
|
|
129
|
-
return context.err(self, source,
|
|
140
|
+
return context.err(self, source, fail_msg, [v2]) unless success
|
|
130
141
|
|
|
131
142
|
success, v3 = p3.apply(source, context, consume_all)
|
|
132
|
-
return context.err(self, source,
|
|
143
|
+
return context.err(self, source, fail_msg, [v3]) unless success
|
|
133
144
|
|
|
134
145
|
ok([:sequence, v1, v2, v3])
|
|
135
146
|
end
|
|
@@ -148,7 +159,7 @@ module Parsanol
|
|
|
148
159
|
success, value = components[idx].apply(source, context, must_consume)
|
|
149
160
|
|
|
150
161
|
unless success
|
|
151
|
-
return context.err(self, source,
|
|
162
|
+
return context.err(self, source, fail_msg, [value])
|
|
152
163
|
end
|
|
153
164
|
|
|
154
165
|
result[idx + 1] = value
|
|
@@ -28,12 +28,16 @@ module Parsanol
|
|
|
28
28
|
# +capturers+ maps leading-literal marker -> kind label (the
|
|
29
29
|
# compiler derives markers from each capturer rule's first Str
|
|
30
30
|
# atom, e.g. "//" => :line_comment); units not matching any
|
|
31
|
-
# marker are whitespace-shaped and stay unrecorded
|
|
32
|
-
|
|
31
|
+
# marker are whitespace-shaped and stay unrecorded — unless
|
|
32
|
+
# +whitespace_kind+ is declared (parsanol-ruby#180): every
|
|
33
|
+
# unit then records under that label, giving source-preserving
|
|
34
|
+
# grammars the full ordered trivia stream.
|
|
35
|
+
attr_reader :capturers, :whitespace_kind
|
|
33
36
|
|
|
34
|
-
def initialize(atom, capturers)
|
|
37
|
+
def initialize(atom, capturers, whitespace_kind: nil)
|
|
35
38
|
super(atom)
|
|
36
39
|
@capturers = capturers
|
|
40
|
+
@whitespace_kind = whitespace_kind
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def apply(source, context, consume_all)
|
|
@@ -55,16 +59,16 @@ module Parsanol
|
|
|
55
59
|
private
|
|
56
60
|
|
|
57
61
|
def record(source, context, start_pos, end_pos)
|
|
58
|
-
return unless @capturers
|
|
59
|
-
|
|
60
62
|
text = source.input[start_pos...end_pos]
|
|
61
63
|
# A trivia unit may lead with whitespace before the comment
|
|
62
64
|
# shape; markers test the comment head.
|
|
63
65
|
stripped = text.lstrip
|
|
64
|
-
marker, label = @capturers
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
marker, label = @capturers&.find { |m, _label| stripped.start_with?(m) }
|
|
67
|
+
if marker
|
|
68
|
+
context.push_captured_trivia(label, stripped.strip)
|
|
69
|
+
elsif @whitespace_kind
|
|
70
|
+
context.push_captured_trivia(@whitespace_kind, text)
|
|
71
|
+
end
|
|
68
72
|
end
|
|
69
73
|
end
|
|
70
74
|
end
|
|
@@ -332,12 +332,16 @@ module Parsanol
|
|
|
332
332
|
|
|
333
333
|
def serialize_trivia_capture(atom)
|
|
334
334
|
inner_id = serialize_atom(atom.wrapped_atom)
|
|
335
|
-
{
|
|
336
|
-
"
|
|
337
|
-
|
|
338
|
-
"rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
|
|
339
|
-
},
|
|
335
|
+
payload = {
|
|
336
|
+
"atom" => inner_id,
|
|
337
|
+
"rules" => atom.capturers.map { |marker, label| [marker, label.to_s] },
|
|
340
338
|
}
|
|
339
|
+
# parsanol-ruby#180: the optional whitespace kind — units that
|
|
340
|
+
# match no marker record under it (source-preserving grammars).
|
|
341
|
+
# Optional on the wire: engines predating it deserialize and
|
|
342
|
+
# simply don't record whitespace units (documented caveat).
|
|
343
|
+
payload["whitespace"] = atom.whitespace_kind.to_s if atom.whitespace_kind
|
|
344
|
+
{ "TriviaCapture" => payload }
|
|
341
345
|
end
|
|
342
346
|
|
|
343
347
|
def serialize_trivia(atom)
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -54,7 +54,8 @@ module Parsanol
|
|
|
54
54
|
skip_name = self.class.skip_rule_name
|
|
55
55
|
if skip_name
|
|
56
56
|
Parsanol::Skip.inject(self, atom, skip_name,
|
|
57
|
-
captures: self.class.skip_captures
|
|
57
|
+
captures: self.class.skip_captures,
|
|
58
|
+
whitespace: self.class.skip_whitespace)[0]
|
|
58
59
|
else
|
|
59
60
|
atom
|
|
60
61
|
end
|
|
@@ -89,9 +90,10 @@ module Parsanol
|
|
|
89
90
|
# rule(:line_comment) { str("//") >> match(/[^\n]/).repeat }
|
|
90
91
|
# skip :trivia, capture: { line_comment: :line_comment }
|
|
91
92
|
#
|
|
92
|
-
def skip(rule_name, capture: nil)
|
|
93
|
+
def skip(rule_name, capture: nil, whitespace: nil)
|
|
93
94
|
@skip_rule_name = rule_name
|
|
94
95
|
@skip_captures = capture
|
|
96
|
+
@skip_whitespace = whitespace
|
|
95
97
|
nil
|
|
96
98
|
end
|
|
97
99
|
|
|
@@ -106,6 +108,12 @@ module Parsanol
|
|
|
106
108
|
def skip_captures
|
|
107
109
|
@skip_captures
|
|
108
110
|
end
|
|
111
|
+
|
|
112
|
+
# The declared whitespace kind (units matching no capturer
|
|
113
|
+
# record under it), or nil — see {.skip}.
|
|
114
|
+
def skip_whitespace
|
|
115
|
+
@skip_whitespace
|
|
116
|
+
end
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
# Delegates matching to the root rule.
|
data/lib/parsanol/skip.rb
CHANGED
|
@@ -20,7 +20,8 @@ module Parsanol
|
|
|
20
20
|
# capturer's leading literal attach to the next Named capture
|
|
21
21
|
# under `comments:` — the PARG `skip = trivia capture: comments`
|
|
22
22
|
# shape. Whitespace-shaped units never record.
|
|
23
|
-
def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil
|
|
23
|
+
def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil,
|
|
24
|
+
whitespace: nil)
|
|
24
25
|
skip_atom = parser_instance.__send__(skip_rule_name)
|
|
25
26
|
skip_atom = skip_atom.parslet while skip_atom.is_a?(Atoms::Entity)
|
|
26
27
|
|
|
@@ -31,7 +32,8 @@ module Parsanol
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
markers = capturer_markers(parser_instance, captures)
|
|
34
|
-
|
|
35
|
+
whitespace_kind = whitespace&.to_sym
|
|
36
|
+
injector = Injector.new(skip_atom, markers, whitespace_kind)
|
|
35
37
|
[injector.inject(root_atom, entry: true), skip_atom]
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -92,15 +94,25 @@ module Parsanol
|
|
|
92
94
|
# clean — the wrapper is Trivia: Ignored + diagnostics
|
|
93
95
|
# transparency), bare terminals gain a leading wrapper, and the
|
|
94
96
|
# entry root takes leading and trailing wrappers.
|
|
97
|
+
# The walk is a pure function of the atom subtree, so results are
|
|
98
|
+
# memoized: dense cross-referencing grammars (expressir's ~250
|
|
99
|
+
# rules) revisit shared subtrees once per referencing path, which
|
|
100
|
+
# made the path-scoped visited-set exponential — a 25-rule
|
|
101
|
+
# Fibonacci-shaped grammar took 6.2s to build (parsanol-ruby#180
|
|
102
|
+
# side observation). In-progress atoms mark a cycle re-entry and
|
|
103
|
+
# return raw, exactly as the path-scoped set did.
|
|
95
104
|
class Injector
|
|
96
|
-
def initialize(skip_atom, markers = nil)
|
|
105
|
+
def initialize(skip_atom, markers = nil, whitespace_kind = nil)
|
|
97
106
|
@skip_atom = skip_atom
|
|
98
107
|
@markers = markers
|
|
108
|
+
@whitespace_kind = whitespace_kind
|
|
99
109
|
@wrapper_ids = {}.compare_by_identity
|
|
110
|
+
@memo = {}.compare_by_identity
|
|
111
|
+
@in_progress = {}.compare_by_identity
|
|
100
112
|
end
|
|
101
113
|
|
|
102
114
|
def inject(atom, entry: false)
|
|
103
|
-
injected = walk(atom
|
|
115
|
+
injected = walk(atom)
|
|
104
116
|
return injected unless entry
|
|
105
117
|
|
|
106
118
|
Atoms::Sequence.new(maybe, injected, maybe)
|
|
@@ -112,8 +124,9 @@ module Parsanol
|
|
|
112
124
|
@maybe ||= begin
|
|
113
125
|
inner = Atoms::Repetition.new(@skip_atom, 0, 1)
|
|
114
126
|
wrapper =
|
|
115
|
-
if @markers
|
|
116
|
-
Atoms::TriviaCapture.new(inner, @markers
|
|
127
|
+
if @markers || @whitespace_kind
|
|
128
|
+
Atoms::TriviaCapture.new(inner, @markers,
|
|
129
|
+
whitespace_kind: @whitespace_kind)
|
|
117
130
|
else
|
|
118
131
|
Atoms::Trivia.new(inner)
|
|
119
132
|
end
|
|
@@ -126,50 +139,56 @@ module Parsanol
|
|
|
126
139
|
@wrapper_ids.key?(atom)
|
|
127
140
|
end
|
|
128
141
|
|
|
129
|
-
def walk(atom
|
|
130
|
-
|
|
142
|
+
def walk(atom)
|
|
143
|
+
cached = @memo[atom]
|
|
144
|
+
return cached if cached
|
|
145
|
+
return atom if @in_progress.key?(atom)
|
|
131
146
|
return atom if atom.equal?(@skip_atom)
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Atoms::Alternative
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
atom
|
|
148
|
+
@in_progress[atom] = true
|
|
149
|
+
injected =
|
|
150
|
+
case atom
|
|
151
|
+
when Atoms::Sequence
|
|
152
|
+
out = []
|
|
153
|
+
atom.parslets.each do |child|
|
|
154
|
+
injected = walk(child)
|
|
155
|
+
out << maybe unless wrapper?(injected) || injected.is_a?(Atoms::Trivia)
|
|
156
|
+
out << injected
|
|
157
|
+
end
|
|
158
|
+
out.length == 1 ? out.first : Atoms::Sequence.new(*out)
|
|
159
|
+
when Atoms::Alternative
|
|
160
|
+
Atoms::Alternative.new(*atom.alternatives.map { |a| walk(a) })
|
|
161
|
+
when Atoms::Repetition
|
|
162
|
+
if wrapper?(atom)
|
|
163
|
+
atom
|
|
164
|
+
else
|
|
165
|
+
Atoms::Repetition.new(walk(atom.parslet), atom.min, atom.max,
|
|
166
|
+
atom.result_tag)
|
|
167
|
+
end
|
|
168
|
+
when Atoms::Named
|
|
169
|
+
Atoms::Named.new(walk(atom.parslet), atom.name)
|
|
170
|
+
when Atoms::Lookahead
|
|
171
|
+
Atoms::Lookahead.new(walk(atom.bound_parslet), atom.positive)
|
|
172
|
+
when Atoms::Ignored
|
|
173
|
+
Atoms::Ignored.new(walk(atom.wrapped_atom))
|
|
174
|
+
when Atoms::Entity
|
|
175
|
+
# Rule bodies own their injection when the grammar declares
|
|
176
|
+
# skip on every rule (PARG); in the DSL surface the root tree
|
|
177
|
+
# is walked once, so descend through resolved bodies with the
|
|
178
|
+
# identity visited-set guarding recursion.
|
|
179
|
+
begin
|
|
180
|
+
walk(atom.parslet)
|
|
181
|
+
rescue StandardError
|
|
182
|
+
atom
|
|
183
|
+
end
|
|
184
|
+
when Atoms::Str, Atoms::Re
|
|
185
|
+
Atoms::Sequence.new(maybe, atom)
|
|
148
186
|
else
|
|
149
|
-
Atoms::Repetition.new(walk(atom.parslet, visited), atom.min, atom.max,
|
|
150
|
-
atom.result_tag)
|
|
151
|
-
end
|
|
152
|
-
when Atoms::Named
|
|
153
|
-
Atoms::Named.new(walk(atom.parslet, visited), atom.name)
|
|
154
|
-
when Atoms::Lookahead
|
|
155
|
-
Atoms::Lookahead.new(walk(atom.bound_parslet, visited), atom.positive)
|
|
156
|
-
when Atoms::Ignored
|
|
157
|
-
Atoms::Ignored.new(walk(atom.wrapped_atom, visited))
|
|
158
|
-
when Atoms::Entity
|
|
159
|
-
# Rule bodies own their injection when the grammar declares
|
|
160
|
-
# skip on every rule (PARG); in the DSL surface the root tree
|
|
161
|
-
# is walked once, so descend through resolved bodies with the
|
|
162
|
-
# identity visited-set guarding recursion.
|
|
163
|
-
begin
|
|
164
|
-
walk(atom.parslet, visited)
|
|
165
|
-
rescue StandardError
|
|
166
187
|
atom
|
|
167
188
|
end
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
atom
|
|
172
|
-
end
|
|
189
|
+
@in_progress.delete(atom)
|
|
190
|
+
@memo[atom] = injected
|
|
191
|
+
injected
|
|
173
192
|
end
|
|
174
193
|
end
|
|
175
194
|
end
|
data/lib/parsanol/version.rb
CHANGED