parsanol 1.3.10-aarch64-linux → 1.3.12-aarch64-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/lib/parsanol/3.2/parsanol_native.so +0 -0
- data/lib/parsanol/3.3/parsanol_native.so +0 -0
- data/lib/parsanol/3.4/parsanol_native.so +0 -0
- data/lib/parsanol/4.0/parsanol_native.so +0 -0
- data/lib/parsanol/atoms/context.rb +33 -17
- data/lib/parsanol/native/serializer.rb +1 -0
- data/lib/parsanol/native/transformer.rb +24 -9
- data/lib/parsanol/native.rb +26 -2
- data/lib/parsanol/parser.rb +12 -3
- data/lib/parsanol/pools/buffer_pool.rb +8 -1
- data/lib/parsanol/source.rb +1 -1
- data/lib/parsanol/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bb54c6ec635514cfda943c45a429ce8a03d23040f57b6317b7f830c723a6485
|
|
4
|
+
data.tar.gz: 6d1a365f1f5c072bbe544205f5eb8af6bec545e2c66f5623899a4ed4e45959b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6610a51daaa8b1fda48ab02b060cb51e550c4fc84c9f774f65ca3e0c0908a46e23f47639edac780e34854d4397b099f0c2c6c3525663a9f3783dbf4afb0c033
|
|
7
|
+
data.tar.gz: '09d6e6f5a449d02f5b0039fbeee654b3328431c77f5390018947136b5e9298ab4bd49e4b5b3c346a38b357d242f040014d0d074000f09283f1aa7207a6e63be9'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,6 +28,9 @@ module Parsanol
|
|
|
28
28
|
:default => 1000,
|
|
29
29
|
}.freeze
|
|
30
30
|
|
|
31
|
+
# Number of observed backtrack events before packrat caching engages.
|
|
32
|
+
BACKTRACK_ACTIVATION_LIMIT = 64
|
|
33
|
+
|
|
31
34
|
# Creates a new parsing context.
|
|
32
35
|
#
|
|
33
36
|
# @param error_reporter [#err, #err_at] error reporter instance
|
|
@@ -40,7 +43,7 @@ module Parsanol
|
|
|
40
43
|
adaptive_cache_threshold: nil,
|
|
41
44
|
parser_class: nil)
|
|
42
45
|
# Core memoization cache: position -> { atom_id -> [result, advance] }
|
|
43
|
-
@memo =
|
|
46
|
+
@memo = {}
|
|
44
47
|
|
|
45
48
|
# Error reporting delegate
|
|
46
49
|
@reporter = error_reporter
|
|
@@ -55,7 +58,7 @@ module Parsanol
|
|
|
55
58
|
@evict_interval = 100
|
|
56
59
|
|
|
57
60
|
# Object pools for reducing allocations
|
|
58
|
-
@array_pool = Parsanol::Pools::ArrayPool.new(size: 10_000)
|
|
61
|
+
@array_pool = Parsanol::Pools::ArrayPool.new(size: 10_000, preallocate: false)
|
|
59
62
|
@buffer_pool = Parsanol::Pools::BufferPool.new(pool_size: 100)
|
|
60
63
|
|
|
61
64
|
# Selective memoization tracking
|
|
@@ -85,7 +88,8 @@ module Parsanol
|
|
|
85
88
|
|
|
86
89
|
@adaptive_threshold = threshold
|
|
87
90
|
@input_len = nil
|
|
88
|
-
@caching_active =
|
|
91
|
+
@caching_active = false
|
|
92
|
+
@backtrack_events = 0
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
# Attempts to parse using memoization. Returns cached result if available,
|
|
@@ -100,21 +104,18 @@ module Parsanol
|
|
|
100
104
|
# Skip caching for atoms that don't benefit from it
|
|
101
105
|
return atom.try(src, self, must_consume_all) unless atom.cached?
|
|
102
106
|
|
|
103
|
-
# Determine if caching should be active (lazy initialization)
|
|
104
|
-
if @caching_active.nil?
|
|
105
|
-
total_len = src.bytepos + src.chars_left
|
|
106
|
-
@input_len = total_len
|
|
107
|
-
@caching_active = total_len >= @adaptive_threshold
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# For small inputs, skip caching overhead
|
|
111
|
-
return atom.try(src, self, must_consume_all) unless @caching_active
|
|
112
|
-
|
|
113
107
|
# Use interval-based caching if enabled
|
|
114
108
|
return try_with_interval(atom, src, must_consume_all) if @use_intervals
|
|
115
109
|
|
|
110
|
+
# Adaptive activation: packrat memoization costs more than it saves
|
|
111
|
+
# on deterministic forward-only grammars, so start uncached and only
|
|
112
|
+
# engage once real backtracking (re-parsing behind the progress
|
|
113
|
+
# frontier) is observed.
|
|
114
|
+
return try_uncached_probe(atom, src, must_consume_all) unless @caching_active
|
|
115
|
+
|
|
116
116
|
pos = src.bytepos
|
|
117
117
|
key = atom.object_id
|
|
118
|
+
entry = @memo[pos]
|
|
118
119
|
|
|
119
120
|
# Periodic cache eviction to prevent unbounded growth
|
|
120
121
|
if pos > @furthest_pos
|
|
@@ -128,10 +129,10 @@ module Parsanol
|
|
|
128
129
|
end
|
|
129
130
|
end
|
|
130
131
|
|
|
131
|
-
# Check for cache hit
|
|
132
|
-
if
|
|
132
|
+
# Check for cache hit (avoid default-block Hash allocation per probe)
|
|
133
|
+
if entry&.key?(key)
|
|
133
134
|
@hit_stats[key] += 1
|
|
134
|
-
outcome, delta =
|
|
135
|
+
outcome, delta = entry[key]
|
|
135
136
|
src.bytepos = pos + delta
|
|
136
137
|
return outcome
|
|
137
138
|
end
|
|
@@ -144,7 +145,7 @@ module Parsanol
|
|
|
144
145
|
# Only cache if beneficial (heuristic)
|
|
145
146
|
attempts = @hit_stats[key] + @miss_stats[key]
|
|
146
147
|
if attempts <= @min_hits_for_cache || @hit_stats[key].positive?
|
|
147
|
-
@memo[pos][key] =
|
|
148
|
+
(@memo[pos] ||= {})[key] =
|
|
148
149
|
[outcome,
|
|
149
150
|
delta]
|
|
150
151
|
end
|
|
@@ -327,6 +328,21 @@ module Parsanol
|
|
|
327
328
|
|
|
328
329
|
private
|
|
329
330
|
|
|
331
|
+
# Executes an atom without memoization while watching for backtracking.
|
|
332
|
+
# A failed attempt at a position behind the progress frontier means work
|
|
333
|
+
# is being re-done; once that repeats, packrat caching engages.
|
|
334
|
+
def try_uncached_probe(atom, src, must_consume_all)
|
|
335
|
+
pos = src.bytepos
|
|
336
|
+
outcome = atom.try(src, self, must_consume_all)
|
|
337
|
+
if pos > @furthest_pos
|
|
338
|
+
@furthest_pos = pos
|
|
339
|
+
elsif !outcome[0] && pos < @furthest_pos
|
|
340
|
+
@backtrack_events += 1
|
|
341
|
+
@caching_active = true if @backtrack_events >= BACKTRACK_ACTIVATION_LIMIT
|
|
342
|
+
end
|
|
343
|
+
outcome
|
|
344
|
+
end
|
|
345
|
+
|
|
330
346
|
# Lookup cached result (uses object_id for speed)
|
|
331
347
|
def lookup(atom, pos)
|
|
332
348
|
@memo[pos][atom.object_id]
|
|
@@ -28,10 +28,19 @@ module Parsanol
|
|
|
28
28
|
# This is a class variable to share across all transformations
|
|
29
29
|
@@symbol_cache = {}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# Symbol tags from native parser
|
|
32
|
+
SEQUENCE_SYM = :sequence
|
|
33
|
+
REPETITION_SYM = :repetition
|
|
34
|
+
MAYBE_SYM = :maybe
|
|
35
|
+
MAYBE_TAG = ":maybe"
|
|
36
|
+
|
|
37
|
+
# `named` mirrors CanFlatten#flatten's named flag: inside a Named
|
|
38
|
+
# result (.as), an absent maybe flattens to nil; unnamed it flattens
|
|
39
|
+
# to "".
|
|
40
|
+
def self.transform(ast, named: false)
|
|
32
41
|
case ast
|
|
33
42
|
when Array
|
|
34
|
-
transform_array(ast)
|
|
43
|
+
transform_array(ast, named: named)
|
|
35
44
|
when Hash
|
|
36
45
|
transform_hash(ast)
|
|
37
46
|
else
|
|
@@ -52,11 +61,7 @@ module Parsanol
|
|
|
52
61
|
@@symbol_cache[key] ||= key.to_sym
|
|
53
62
|
end
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
SEQUENCE_SYM = :sequence
|
|
57
|
-
REPETITION_SYM = :repetition
|
|
58
|
-
|
|
59
|
-
def self.transform_array(arr)
|
|
64
|
+
def self.transform_array(arr, named: false)
|
|
60
65
|
return EMPTY_ARRAY if arr.empty? # Match Parsanol Ruby mode behavior
|
|
61
66
|
|
|
62
67
|
# Check if this is a tagged array from native parser
|
|
@@ -87,6 +92,16 @@ module Parsanol
|
|
|
87
92
|
i += 1
|
|
88
93
|
end
|
|
89
94
|
flatten_repetition(items)
|
|
95
|
+
elsif [MAYBE_SYM, MAYBE_TAG].include?(first)
|
|
96
|
+
# Maybe flattens to nil-or-value (named) or ""-or-value (unnamed),
|
|
97
|
+
# never to an array
|
|
98
|
+
len = arr.length
|
|
99
|
+
if len == 1
|
|
100
|
+
named ? nil : EMPTY_STRING
|
|
101
|
+
else
|
|
102
|
+
flattened = transform(arr[1])
|
|
103
|
+
named ? flattened : (flattened || EMPTY_STRING)
|
|
104
|
+
end
|
|
90
105
|
elsif first.is_a?(Symbol) || (first.is_a?(String) && first.start_with?(":"))
|
|
91
106
|
# Other tagged arrays - pass through
|
|
92
107
|
arr.map { |item| transform(item) }
|
|
@@ -115,7 +130,7 @@ module Parsanol
|
|
|
115
130
|
sym_key = cached_symbol(key)
|
|
116
131
|
|
|
117
132
|
# Transform the value
|
|
118
|
-
transformed = transform(value)
|
|
133
|
+
transformed = transform(value, named: true)
|
|
119
134
|
|
|
120
135
|
# Check if value is a tagged repetition from native parser
|
|
121
136
|
is_tagged_repetition = value.is_a?(Array) && !value.empty? &&
|
|
@@ -240,7 +255,7 @@ module Parsanol
|
|
|
240
255
|
is_repetition = value.is_a?(Array) && !value.empty? &&
|
|
241
256
|
value.first.is_a?(String) && value.first == REPETITION_TAG
|
|
242
257
|
|
|
243
|
-
transformed = transform(value)
|
|
258
|
+
transformed = transform(value, named: true)
|
|
244
259
|
|
|
245
260
|
result[sym_key] = if is_repetition
|
|
246
261
|
if transformed.is_a?(Array)
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -48,7 +48,11 @@ module Parsanol
|
|
|
48
48
|
# Use _parse_raw which returns properly tagged Ruby arrays via transform_ast.
|
|
49
49
|
# The batch format doesn't preserve :repetition/:sequence tags, so we use
|
|
50
50
|
# the direct FFI path. Apply the Ruby transformer to handle tags correctly.
|
|
51
|
-
|
|
51
|
+
begin
|
|
52
|
+
raw_ast = _parse_raw(grammar_json, input)
|
|
53
|
+
rescue RuntimeError => e
|
|
54
|
+
raise_native_parse_error(e, grammar, input)
|
|
55
|
+
end
|
|
52
56
|
BatchDecoder.decode_and_flatten(raw_ast, input, Parsanol::Slice)
|
|
53
57
|
end
|
|
54
58
|
|
|
@@ -69,7 +73,11 @@ module Parsanol
|
|
|
69
73
|
Parser.serialize_grammar(grammar)
|
|
70
74
|
end
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
begin
|
|
77
|
+
raw_ast = _parse_fresh_raw(grammar_json, input)
|
|
78
|
+
rescue RuntimeError => e
|
|
79
|
+
raise_native_parse_error(e, grammar, input)
|
|
80
|
+
end
|
|
73
81
|
BatchDecoder.decode_and_flatten(raw_ast, input, Parsanol::Slice)
|
|
74
82
|
end
|
|
75
83
|
|
|
@@ -182,6 +190,22 @@ module Parsanol
|
|
|
182
190
|
end
|
|
183
191
|
stats
|
|
184
192
|
end
|
|
193
|
+
|
|
194
|
+
# Translates a native backend failure into the Parsanol error protocol.
|
|
195
|
+
#
|
|
196
|
+
# When the grammar atom is at hand, reparses through the pure Ruby
|
|
197
|
+
# backend: a Ruby failure raises Parsanol::ParseFailed with the full
|
|
198
|
+
# cause-tree diagnostics, and a Ruby success recovers grammars the
|
|
199
|
+
# native serializer cannot express (e.g. custom atoms). For
|
|
200
|
+
# pre-serialized JSON grammars the native message is wrapped in a
|
|
201
|
+
# Parsanol::ParseFailed directly.
|
|
202
|
+
def raise_native_parse_error(error, grammar, input)
|
|
203
|
+
return grammar.parse(input) if grammar.respond_to?(:parse)
|
|
204
|
+
|
|
205
|
+
source = Parsanol::Source.new(input)
|
|
206
|
+
cause = Parsanol::Cause.new(error.message, source, source.bytepos)
|
|
207
|
+
raise Parsanol::ParseFailed.new(cause.to_s, cause)
|
|
208
|
+
end
|
|
185
209
|
end
|
|
186
210
|
end
|
|
187
211
|
end
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -112,10 +112,18 @@ module Parsanol
|
|
|
112
112
|
if mode_or_opts.is_a?(Hash) && !kwargs.key?(:mode)
|
|
113
113
|
# Legacy API: parse(input, options={})
|
|
114
114
|
merged = mode_or_opts.merge(kwargs)
|
|
115
|
-
|
|
115
|
+
if Parsanol::Native.available? && !merged.key?(:prefix) &&
|
|
116
|
+
!merged.key?(:reporter)
|
|
117
|
+
# Native backend by default; falls back to pure Ruby inside
|
|
118
|
+
# parse_native when the extension is missing.
|
|
119
|
+
parse_native(input, merged)
|
|
120
|
+
else
|
|
121
|
+
super(input, merged)
|
|
122
|
+
end
|
|
116
123
|
else
|
|
117
124
|
# New API: parse(input, mode:, **options)
|
|
118
|
-
mode = kwargs.delete(:mode) ||
|
|
125
|
+
mode = kwargs.delete(:mode) ||
|
|
126
|
+
(Parsanol::Native.available? ? :native : :ruby)
|
|
119
127
|
case mode
|
|
120
128
|
when :ruby
|
|
121
129
|
super(input, kwargs)
|
|
@@ -209,7 +217,8 @@ module Parsanol
|
|
|
209
217
|
if Parsanol::Native.available?
|
|
210
218
|
Parsanol::Native.parse(root, input)
|
|
211
219
|
else
|
|
212
|
-
|
|
220
|
+
Parsanol::Atoms::Base.instance_method(:parse).bind_call(self, input,
|
|
221
|
+
opts)
|
|
213
222
|
end
|
|
214
223
|
end
|
|
215
224
|
|
|
@@ -151,7 +151,14 @@ module Parsanol
|
|
|
151
151
|
# @return [Integer] Size class
|
|
152
152
|
#
|
|
153
153
|
def select_size_class(size)
|
|
154
|
-
|
|
154
|
+
i = 0
|
|
155
|
+
n = SIZE_CLASSES.length
|
|
156
|
+
while i < n
|
|
157
|
+
return SIZE_CLASSES[i] if SIZE_CLASSES[i] >= size
|
|
158
|
+
|
|
159
|
+
i += 1
|
|
160
|
+
end
|
|
161
|
+
next_power_of_2(size)
|
|
155
162
|
end
|
|
156
163
|
|
|
157
164
|
# Find next power of 2 greater than or equal to n.
|
data/lib/parsanol/source.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Parsanol
|
|
|
53
53
|
|
|
54
54
|
# Object pools for memory efficiency
|
|
55
55
|
# SlicePool: reduces Slice allocations during matching
|
|
56
|
-
@slice_pool = Parsanol::Pools::SlicePool.new(size: 5000)
|
|
56
|
+
@slice_pool = Parsanol::Pools::SlicePool.new(size: 5000, preallocate: false)
|
|
57
57
|
|
|
58
58
|
# PositionPool: reduces Position allocations for error reporting
|
|
59
59
|
@position_pool = Parsanol::Pools::PositionPool.new(size: 1000)
|
data/lib/parsanol/version.rb
CHANGED
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.12
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -151,6 +151,7 @@ files:
|
|
|
151
151
|
- README.adoc
|
|
152
152
|
- Rakefile
|
|
153
153
|
- lib/parsanol.rb
|
|
154
|
+
- lib/parsanol/3.2/parsanol_native.so
|
|
154
155
|
- lib/parsanol/3.3/parsanol_native.so
|
|
155
156
|
- lib/parsanol/3.4/parsanol_native.so
|
|
156
157
|
- lib/parsanol/4.0/parsanol_native.so
|
|
@@ -261,7 +262,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
261
262
|
requirements:
|
|
262
263
|
- - ">="
|
|
263
264
|
- !ruby/object:Gem::Version
|
|
264
|
-
version: '3.
|
|
265
|
+
version: '3.2'
|
|
265
266
|
- - "<"
|
|
266
267
|
- !ruby/object:Gem::Version
|
|
267
268
|
version: 4.1.dev
|