parsanol 1.3.76 → 1.3.78
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 +2 -2
- data/ext/parsanol_native/Cargo.toml +4 -3
- data/lib/parsanol/atoms/lookbehind.rb +39 -9
- data/lib/parsanol/native/dynamic.rb +26 -5
- data/lib/parsanol/native/parser.rb +7 -0
- data/lib/parsanol/native/serializer.rb +11 -9
- data/lib/parsanol/parg/artifact.rb +10 -2
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +4 -0
- 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: 8b7c210627fda0c572855efe7f2e27930aff2e01f632de082abeb68dba5a99f7
|
|
4
|
+
data.tar.gz: e6bcb584fe1de6cb62f35273980dfe78e8e24688e76ba089a734606338fe94b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 49caa14b1f70ecefe02e75e44f572823613f30aabcfcfeb21b9477358cfb20fff09823ff6f2497c90353866de94af3c571072df859ecad702cfb532252547238
|
|
7
|
+
data.tar.gz: 6822401276fc2af955631834129ee5d75ffb64736f76d5719cba1180139d56a778fe8fad599ef59e8ab73cc634190644b4f148c808e1d92ff9065ed47ec05631
|
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.17.1"
|
|
311
311
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
312
|
-
checksum = "
|
|
312
|
+
checksum = "170442a26c6a0b5ac25fbdb9c249dbaee821a2d364d4a42219c32af11f008b3d"
|
|
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.17.1"
|
|
330
330
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
331
|
-
checksum = "
|
|
331
|
+
checksum = "c3567bd641f8a18ee4a1e0339d9e1d0bd804764c1d813efc25b8fd7f24ed8ad3"
|
|
332
332
|
dependencies = [
|
|
333
333
|
"proc-macro2",
|
|
334
334
|
"quote",
|
|
@@ -12,5 +12,5 @@ crate-type = ["cdylib"]
|
|
|
12
12
|
doc = false
|
|
13
13
|
|
|
14
14
|
[dependencies]
|
|
15
|
-
# 0.
|
|
16
|
-
parsanol = { version = "0.
|
|
15
|
+
# 0.17.0 carries LookSpec + the widened opaque writeback
|
|
16
|
+
parsanol = { version = "0.17.1" }
|
|
@@ -29,9 +29,10 @@ magnus = { version = "0.9" }
|
|
|
29
29
|
|
|
30
30
|
# parsanol parser library (crates.io; 0.12 carries the rs#174 native
|
|
31
31
|
# backtracking fix: dynamic-dependent memoization + outcome cache)
|
|
32
|
-
# parsanol parser library (crates.io; 0.
|
|
33
|
-
#
|
|
34
|
-
|
|
32
|
+
# parsanol parser library (crates.io; 0.17.0 carries the LookSpec regex
|
|
33
|
+
# lookbehind (rs#163 gap 1) and the widened opaque writeback: the
|
|
34
|
+
# lazy-continuation fix, parsanol-ruby#160)
|
|
35
|
+
parsanol = { version = "0.17.1", features = ["ruby"] }
|
|
35
36
|
|
|
36
37
|
# Logging
|
|
37
38
|
log = "0.4"
|
|
@@ -2,30 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
module Parsanol
|
|
4
4
|
module Atoms
|
|
5
|
-
# Inspects the
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
5
|
+
# Inspects the text behind the current position and consumes
|
|
6
|
+
# nothing, yielding nil.
|
|
7
|
+
#
|
|
8
|
+
# Two forms (rs#137/#163):
|
|
9
|
+
# - Literal: the previous +count+ bytes must equal +pattern+
|
|
10
|
+
# (the precedes?/does_not_precede? fixed-window guard).
|
|
11
|
+
# - Regex: +source+ is searched in the preceding text and must
|
|
12
|
+
# end at the position — the CommonMark flanking form, which is
|
|
13
|
+
# class-based, multibyte and variable-length.
|
|
10
14
|
class Lookbehind < Base
|
|
11
|
-
attr_reader :count, :pattern, :positive
|
|
15
|
+
attr_reader :count, :pattern, :regex_source, :positive
|
|
12
16
|
|
|
13
17
|
def initialize(count, pattern, positive: true)
|
|
14
18
|
super()
|
|
15
19
|
@count = count
|
|
16
20
|
@pattern = pattern
|
|
21
|
+
@regex_source = nil
|
|
22
|
+
@compiled = nil
|
|
17
23
|
@positive = positive
|
|
18
24
|
end
|
|
19
25
|
|
|
26
|
+
# Class-based flanking guard: +source+ must match the text
|
|
27
|
+
# ending at the position.
|
|
28
|
+
def self.regex(source, positive: true)
|
|
29
|
+
atom = new(0, "", positive: positive)
|
|
30
|
+
atom.instance_variable_set(:@regex_source, source)
|
|
31
|
+
atom
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def regex?
|
|
35
|
+
!@regex_source.nil?
|
|
36
|
+
end
|
|
37
|
+
|
|
20
38
|
def try(source, _context, _consume_all)
|
|
21
39
|
start = source.bytepos
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
if regex?
|
|
41
|
+
@compiled ||= Regexp.new("(?:#{@regex_source})\\z")
|
|
42
|
+
matched = @compiled.match?(source.input.byteslice(0, start))
|
|
43
|
+
else
|
|
44
|
+
behind =
|
|
45
|
+
if start >= @count
|
|
46
|
+
source.input.byteslice(start - @count, @count)
|
|
47
|
+
end
|
|
48
|
+
matched = !behind.nil? && behind == @pattern.b
|
|
49
|
+
end
|
|
24
50
|
[matched == @positive, nil]
|
|
25
51
|
end
|
|
26
52
|
|
|
27
53
|
def to_s_inner(_prec)
|
|
28
|
-
|
|
54
|
+
if regex?
|
|
55
|
+
"lookbehind(/#{@regex_source}/, #{@positive ? '+' : '-'})"
|
|
56
|
+
else
|
|
57
|
+
"lookbehind(#{@count}, #{@pattern.inspect}, #{@positive ? '+' : '-'})"
|
|
58
|
+
end
|
|
29
59
|
end
|
|
30
60
|
end
|
|
31
61
|
end
|
|
@@ -132,6 +132,12 @@ module Parsanol
|
|
|
132
132
|
#
|
|
133
133
|
def invoke_from_rust(callback_id, context)
|
|
134
134
|
block = @mutex.synchronize { @callbacks[callback_id]&.dig(:block) }
|
|
135
|
+
if ENV["PARSANOL_INVOKE_TRACE"]
|
|
136
|
+
cont = context[:captures][:cont]
|
|
137
|
+
warn "INVOKE id=#{callback_id} pos=#{context[:pos]} " \
|
|
138
|
+
"block=#{context[:captures][:block].inspect} " \
|
|
139
|
+
"cont=#{cont.class}@#{cont.object_id}"
|
|
140
|
+
end
|
|
135
141
|
return nil unless block
|
|
136
142
|
|
|
137
143
|
# Build DynamicContext from hash
|
|
@@ -142,18 +148,33 @@ module Parsanol
|
|
|
142
148
|
)
|
|
143
149
|
|
|
144
150
|
# Call the block. Parslet-canonical blocks take two arguments
|
|
145
|
-
# (source, context)
|
|
146
|
-
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
151
|
+
# (source, context). The source is a REAL Parsanol::Source
|
|
152
|
+
# positioned at the dispatch point: blocks that read source
|
|
153
|
+
# state (bytepos, line_and_column — the lazy-continuation
|
|
154
|
+
# guards do) previously got the context wrapper for both
|
|
155
|
+
# args and raised NoMethodError, which the bridge turned
|
|
156
|
+
# into a silent dispatch failure (rs#165). Consumption
|
|
157
|
+
# side-effects stay local to the call: the resolved fragment
|
|
158
|
+
# is the parse.
|
|
149
159
|
result = if block.arity == 2
|
|
150
|
-
|
|
160
|
+
dispatch_source = Parsanol::Source.new(context[:input])
|
|
161
|
+
dispatch_source.bytepos = context[:pos]
|
|
162
|
+
block.call(dispatch_source, ctx)
|
|
151
163
|
else
|
|
152
164
|
block.call(ctx)
|
|
153
165
|
end
|
|
154
166
|
|
|
155
167
|
return nil unless result
|
|
156
168
|
|
|
169
|
+
if ENV["PARSANOL_FRAG_TRACE"]
|
|
170
|
+
begin
|
|
171
|
+
json = result.to_atom_json
|
|
172
|
+
warn "FRAG id=#{callback_id} json=#{json[0, 120]}"
|
|
173
|
+
rescue StandardError => e
|
|
174
|
+
warn "FRAG id=#{callback_id} RAISE #{e.class}: #{e.message[0, 140]}"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
157
178
|
# Capture-write contract (GH-80): return the atom and the
|
|
158
179
|
# post-call captures hash; the Rust bridge diffs it against
|
|
159
180
|
# the seeded values and propagates the block's writes
|
|
@@ -121,6 +121,13 @@ module Parsanol
|
|
|
121
121
|
}
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
# Public (rs#166): the VM program cache shares compiled
|
|
125
|
+
# programs across fresh parser instances keyed on this —
|
|
126
|
+
# the same disambiguator the grammar cache uses.
|
|
127
|
+
def public_structure_hash(atom)
|
|
128
|
+
grammar_structure_hash(atom)
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
private
|
|
125
132
|
|
|
126
133
|
def grammar_cache_key(root_atom)
|
|
@@ -103,15 +103,17 @@ module Parsanol
|
|
|
103
103
|
# atoms — empty match yielding a wire constant.
|
|
104
104
|
{ "Constant" => { "value" => serialize_constant_value(atom.value) } }
|
|
105
105
|
when Parsanol::Atoms::Lookbehind
|
|
106
|
-
# rs#137
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 } }
|
|
115
117
|
when Parsanol::Atoms::Ignored
|
|
116
118
|
serialize_ignored(atom)
|
|
117
119
|
else
|
|
@@ -65,8 +65,16 @@ module Parsanol
|
|
|
65
65
|
def parse(entry_name, input, mode: :native)
|
|
66
66
|
case mode
|
|
67
67
|
when :native
|
|
68
|
-
if !envelope["dynamic"]
|
|
69
|
-
|
|
68
|
+
if Native.available? && !envelope["dynamic"]
|
|
69
|
+
# Fast lane (parsanol-ruby#162): the per-instance atom
|
|
70
|
+
# tree rides the DSL serialization/registration caches
|
|
71
|
+
# (GRAMMAR_CACHE / HANDLE_CACHE keyed on the stable root
|
|
72
|
+
# atom), so an artifact parses at compiled speed after
|
|
73
|
+
# the first call. The old path re-serialized the whole
|
|
74
|
+
# grammar JSON and re-registered it per parse (~8x).
|
|
75
|
+
# Dynamic artifacts carry ruby-tier atoms their wire
|
|
76
|
+
# cannot express — they keep the interpreter.
|
|
77
|
+
return root_atom(entry_name).parse(input, mode: :native)
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
root_atom(entry_name).parse(input)
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -202,6 +202,10 @@ module Parsanol
|
|
|
202
202
|
# starts memoized on its very first parse (fresh parser instances
|
|
203
203
|
# included — the seed is recomputed at every compile), so the
|
|
204
204
|
# cold-start never pays the doomed unmemoized exploration.
|
|
205
|
+
# rs#166: proneness rides the structure-hash share as a bool —
|
|
206
|
+
# a later identical-structure grammar inherits the seed instead
|
|
207
|
+
# of running its cold start unmemoized because compilation was
|
|
208
|
+
# skipped entirely.
|
|
205
209
|
if compiler.backtracking_prone
|
|
206
210
|
(@heavy ||= {}.compare_by_identity)[root] = true
|
|
207
211
|
trim_cache(@heavy)
|