parsanol 1.3.79-arm-linux → 1.3.81-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/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 +0 -0
- data/lib/parsanol/native/parser.rb +13 -0
- data/lib/parsanol/parg/artifact.rb +19 -17
- data/lib/parsanol/parser.rb +26 -2
- data/lib/parsanol/skip.rb +70 -4
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +52 -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: 01fb11b456bec79f8f4bd6b6db018ef771260440414f75ef02f4e1520ec2caf2
|
|
4
|
+
data.tar.gz: 75e011ecd46597e4c008cbb3fd2c7e3d2bda46ba4cd716c1613a351a28d99fb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 602203c6a4b11eef7c4af0613212091742b1c2f4d9a0250ee27dc7e6d67a891a77c318a70feb7f38b168d64dfd6843ace2123687cc8a17713b0dd9fcb8c8bc43
|
|
7
|
+
data.tar.gz: 49afc1a994a7aeb1b3231446890bc729da4c6a76f520fa448c872437f45861fb4e912a1dfbf8476e13c37e4723dc21b3675e1e4dc8156851d645905006e0c1dd
|
|
@@ -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: 33956544 bytes
|
|
4
|
+
sha256: 5f8d55c4f6ab0f5bba8ea501b6ab13b16df2bfa1b63b823152d70bb86b95daaf
|
|
@@ -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: 34141808 bytes
|
|
4
|
+
sha256: c9ec101532d77f531c2b73fd93c48add52fcd2fc0a06e4047b9e1deb9dc5c4d6
|
|
@@ -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: 34135964 bytes
|
|
4
|
+
sha256: 42f14ef394d8a41ed832e9a9f61fbc0868056062d94df7ea62403290c5581c31
|
|
@@ -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: 34145720 bytes
|
|
4
|
+
sha256: 953c7f621b8c343ab150a9dada110ad29b94d571cc621d0be94c8235419bd2b1
|
|
Binary file
|
|
@@ -205,6 +205,19 @@ module Parsanol
|
|
|
205
205
|
[:constant, atom.value.inspect]
|
|
206
206
|
when ::Parsanol::Atoms::Lookbehind
|
|
207
207
|
[:lookbehind, atom.count, atom.pattern, atom.positive]
|
|
208
|
+
# Subclass first: TriviaCapture < Trivia < Ignored (the same
|
|
209
|
+
# dispatch discipline as the serializer's).
|
|
210
|
+
when ::Parsanol::Atoms::TriviaCapture
|
|
211
|
+
# The capturer table differentiates capture-carrying
|
|
212
|
+
# grammars from plain-trivia ones — without it the grammar
|
|
213
|
+
# cache serves one variant's wire to every other and the
|
|
214
|
+
# wrong marker table reaches the engine (parsanol-ruby#180).
|
|
215
|
+
[:trivia_capture, atom.capturers.to_a.sort,
|
|
216
|
+
atom_structure(atom.wrapped_atom, visited)]
|
|
217
|
+
when ::Parsanol::Atoms::Trivia
|
|
218
|
+
[:trivia, atom_structure(atom.wrapped_atom, visited)]
|
|
219
|
+
when ::Parsanol::Atoms::Ignored
|
|
220
|
+
[:ignored, atom_structure(atom.wrapped_atom, visited)]
|
|
208
221
|
else
|
|
209
222
|
[:unknown, atom.class.name]
|
|
210
223
|
end
|
|
@@ -65,24 +65,26 @@ module Parsanol
|
|
|
65
65
|
def parse(entry_name, input, mode: :native)
|
|
66
66
|
case mode
|
|
67
67
|
when :native
|
|
68
|
-
if Native.available?
|
|
69
|
-
# Fast lane (parsanol-ruby#162):
|
|
70
|
-
#
|
|
68
|
+
if Native.available?
|
|
69
|
+
# Fast lane (parsanol-ruby#162): Native.parse with the
|
|
70
|
+
# per-instance root atom registers the grammar once
|
|
71
71
|
# (GRAMMAR_CACHE / HANDLE_CACHE keyed on the stable root
|
|
72
|
-
# atom)
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
# atom) and every parse after the first runs the native
|
|
73
|
+
# engine by handle — the same path a DSL parser's
|
|
74
|
+
# mode: :native takes. Atoms::Base#parse must NOT carry
|
|
75
|
+
# this lane: it ignores :mode, so it would silently run
|
|
76
|
+
# the pure-Ruby VM and its interpreter fallback (the
|
|
77
|
+
# 5-7x real-document gap in #162, and the root-position
|
|
78
|
+
# diagnostics in #171 before the wire fallback).
|
|
79
|
+
#
|
|
80
|
+
# Dynamic-flagged artifacts parse natively too (rs#204):
|
|
81
|
+
# the state atoms (StateSet/StateMatch/StateSwitch)
|
|
82
|
+
# evaluate in the engine, riding the capture store's
|
|
83
|
+
# rollback discipline. Reaching a CustomRef aborts the
|
|
84
|
+
# native pass outright — Native.parse's error path then
|
|
85
|
+
# re-runs the interpreter, so customs stay Ruby-tier with
|
|
86
|
+
# identical trees.
|
|
87
|
+
return Native.parse(root_atom(entry_name), input)
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
root_atom(entry_name).parse(input)
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -52,7 +52,12 @@ module Parsanol
|
|
|
52
52
|
define_method(:root) do
|
|
53
53
|
atom = __send__(rule_name)
|
|
54
54
|
skip_name = self.class.skip_rule_name
|
|
55
|
-
|
|
55
|
+
if skip_name
|
|
56
|
+
Parsanol::Skip.inject(self, atom, skip_name,
|
|
57
|
+
captures: self.class.skip_captures)[0]
|
|
58
|
+
else
|
|
59
|
+
atom
|
|
60
|
+
end
|
|
56
61
|
end
|
|
57
62
|
end
|
|
58
63
|
|
|
@@ -63,6 +68,13 @@ module Parsanol
|
|
|
63
68
|
# everything it references) builds without injection; it must be
|
|
64
69
|
# non-nullable. Order-independent with +root+.
|
|
65
70
|
#
|
|
71
|
+
# +capture:+ (parsanol-ruby#180) declares trivia capturers — a
|
|
72
|
+
# map of rule name to kind label. Units of the skip rule
|
|
73
|
+
# matching a capturer's leading literal attach to the next
|
|
74
|
+
# Named capture under +comments:+ (the PARG
|
|
75
|
+
# `skip = trivia capture: comments` shape) instead of being
|
|
76
|
+
# discarded; whitespace-shaped units never record.
|
|
77
|
+
#
|
|
66
78
|
# @example
|
|
67
79
|
# class MyParser < Parsanol::Parser
|
|
68
80
|
# rule(:spaces) { match(/[ \t]/).repeat(1) }
|
|
@@ -72,8 +84,14 @@ module Parsanol
|
|
|
72
84
|
# root :doc
|
|
73
85
|
# end
|
|
74
86
|
#
|
|
75
|
-
|
|
87
|
+
# @example capturing comments
|
|
88
|
+
# rule(:trivia) { spaces | line_comment }
|
|
89
|
+
# rule(:line_comment) { str("//") >> match(/[^\n]/).repeat }
|
|
90
|
+
# skip :trivia, capture: { line_comment: :line_comment }
|
|
91
|
+
#
|
|
92
|
+
def skip(rule_name, capture: nil)
|
|
76
93
|
@skip_rule_name = rule_name
|
|
94
|
+
@skip_captures = capture
|
|
77
95
|
nil
|
|
78
96
|
end
|
|
79
97
|
|
|
@@ -82,6 +100,12 @@ module Parsanol
|
|
|
82
100
|
def skip_rule_name
|
|
83
101
|
@skip_rule_name
|
|
84
102
|
end
|
|
103
|
+
|
|
104
|
+
# The declared capturer map ({rule => kind label}), or nil —
|
|
105
|
+
# see {.skip}.
|
|
106
|
+
def skip_captures
|
|
107
|
+
@skip_captures
|
|
108
|
+
end
|
|
85
109
|
end
|
|
86
110
|
|
|
87
111
|
# Delegates matching to the root rule.
|
data/lib/parsanol/skip.rb
CHANGED
|
@@ -13,7 +13,14 @@ module Parsanol
|
|
|
13
13
|
# methods. Returns [injected_root, skip_atom] with the wrapper
|
|
14
14
|
# built and the lint applied, or raises GrammarError for a
|
|
15
15
|
# nullable skip.
|
|
16
|
-
|
|
16
|
+
#
|
|
17
|
+
# +captures+ (parsanol-ruby#180) maps capturer rule names to kind
|
|
18
|
+
# labels (`skip :trivia, capture: { line_comment: :line }`); the
|
|
19
|
+
# injected wrapper becomes a TriviaCapture whose units matching a
|
|
20
|
+
# capturer's leading literal attach to the next Named capture
|
|
21
|
+
# under `comments:` — the PARG `skip = trivia capture: comments`
|
|
22
|
+
# shape. Whitespace-shaped units never record.
|
|
23
|
+
def self.inject(parser_instance, root_atom, skip_rule_name, captures: nil)
|
|
17
24
|
skip_atom = parser_instance.__send__(skip_rule_name)
|
|
18
25
|
skip_atom = skip_atom.parslet while skip_atom.is_a?(Atoms::Entity)
|
|
19
26
|
|
|
@@ -23,10 +30,62 @@ module Parsanol
|
|
|
23
30
|
"empty loops forever at injection points"
|
|
24
31
|
end
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
markers = capturer_markers(parser_instance, captures)
|
|
34
|
+
injector = Injector.new(skip_atom, markers)
|
|
27
35
|
[injector.inject(root_atom, entry: true), skip_atom]
|
|
28
36
|
end
|
|
29
37
|
|
|
38
|
+
# Derives each capturer's leading literal (the leftmost Str the
|
|
39
|
+
# rule can start with) and keys the marker table by it — the same
|
|
40
|
+
# derivation the PARG compiler applies to its skip declaration. A
|
|
41
|
+
# declared capturer without a derivable, non-blank literal is a
|
|
42
|
+
# declaration error: its units could never be told apart from
|
|
43
|
+
# whitespace-shaped trivia and it would silently record nothing.
|
|
44
|
+
def self.capturer_markers(parser_instance, captures)
|
|
45
|
+
return nil if captures.nil? || captures.empty?
|
|
46
|
+
|
|
47
|
+
captures.each_with_object({}) do |(rule_name, kind), markers|
|
|
48
|
+
atom = parser_instance.__send__(rule_name)
|
|
49
|
+
atom = atom.parslet while atom.is_a?(Atoms::Entity)
|
|
50
|
+
literal = leading_literal(atom)
|
|
51
|
+
if literal.nil? || literal.strip.empty?
|
|
52
|
+
raise GrammarError,
|
|
53
|
+
"skip capturer #{rule_name.inspect} has no leading " \
|
|
54
|
+
"literal: its units cannot be told apart from " \
|
|
55
|
+
"whitespace-shaped trivia"
|
|
56
|
+
end
|
|
57
|
+
markers[literal] = kind || rule_name
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The leftmost literal the atom can start with, or nil when the
|
|
62
|
+
# shape is not literal-led (alternatives with disagreeing
|
|
63
|
+
# branches, regexes, lookaheads).
|
|
64
|
+
def self.leading_literal(atom)
|
|
65
|
+
case atom
|
|
66
|
+
when Atoms::Str then atom.str
|
|
67
|
+
when Atoms::Sequence
|
|
68
|
+
atom.parslets.each do |child|
|
|
69
|
+
literal = leading_literal(child)
|
|
70
|
+
return literal if literal
|
|
71
|
+
end
|
|
72
|
+
nil
|
|
73
|
+
when Atoms::Alternative
|
|
74
|
+
first = nil
|
|
75
|
+
atom.alternatives.each do |branch|
|
|
76
|
+
literal = leading_literal(branch)
|
|
77
|
+
return nil if literal.nil? || (first && literal != first)
|
|
78
|
+
|
|
79
|
+
first ||= literal
|
|
80
|
+
end
|
|
81
|
+
first
|
|
82
|
+
when Atoms::Repetition
|
|
83
|
+
atom.min&.positive? ? leading_literal(atom.parslet) : nil
|
|
84
|
+
when Atoms::Named, Atoms::Capture, Atoms::Ignored, Atoms::Trivia
|
|
85
|
+
leading_literal(atom.parslet)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
30
89
|
# The injection walk. Mirrors the PARG compiler's semantics:
|
|
31
90
|
# sequences interleave the wrapper before every child, alternatives
|
|
32
91
|
# and repetitions descend, named captures descend (spans stay
|
|
@@ -34,8 +93,9 @@ module Parsanol
|
|
|
34
93
|
# transparency), bare terminals gain a leading wrapper, and the
|
|
35
94
|
# entry root takes leading and trailing wrappers.
|
|
36
95
|
class Injector
|
|
37
|
-
def initialize(skip_atom)
|
|
96
|
+
def initialize(skip_atom, markers = nil)
|
|
38
97
|
@skip_atom = skip_atom
|
|
98
|
+
@markers = markers
|
|
39
99
|
@wrapper_ids = {}.compare_by_identity
|
|
40
100
|
end
|
|
41
101
|
|
|
@@ -50,7 +110,13 @@ module Parsanol
|
|
|
50
110
|
|
|
51
111
|
def maybe
|
|
52
112
|
@maybe ||= begin
|
|
53
|
-
|
|
113
|
+
inner = Atoms::Repetition.new(@skip_atom, 0, 1)
|
|
114
|
+
wrapper =
|
|
115
|
+
if @markers
|
|
116
|
+
Atoms::TriviaCapture.new(inner, @markers)
|
|
117
|
+
else
|
|
118
|
+
Atoms::Trivia.new(inner)
|
|
119
|
+
end
|
|
54
120
|
@wrapper_ids[wrapper] = true
|
|
55
121
|
wrapper
|
|
56
122
|
end
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -125,11 +125,37 @@ module Parsanol
|
|
|
125
125
|
return nil if cached.equal?(:fallback)
|
|
126
126
|
return cached if key?(cache, root)
|
|
127
127
|
|
|
128
|
+
# Structure-hash probe (rs#166): a fresh parser instance (or a
|
|
129
|
+
# rule called on one) misses the identity key above and would
|
|
130
|
+
# silently recompile the ENTIRE grammar per call — the fresh-
|
|
131
|
+
# parser recompile trap (25x table transforms in coradoc,
|
|
132
|
+
# metanorma/coradoc#265). The structure hash — the same key
|
|
133
|
+
# the native grammar cache uses — lets equivalent grammars
|
|
134
|
+
# share the compiled program; identity stays as the fast
|
|
135
|
+
# first level.
|
|
136
|
+
hashed = (@programs_by_hash ||= {})
|
|
137
|
+
hash_key = begin
|
|
138
|
+
Parsanol::Native::Parser.public_structure_hash(root)
|
|
139
|
+
rescue NotImplementedError
|
|
140
|
+
# A grammar with an unimplemented lazy rule (entities resolve
|
|
141
|
+
# eagerly for hashing) has no stable structure yet; the
|
|
142
|
+
# identity cache above still serves it.
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
if hash_key && (shared = hashed[hash_key]).is_a?(Array)
|
|
146
|
+
# The shared program's compile-time analysis (heavy-memo
|
|
147
|
+
# seeding) covered this structure; the fresh root inherits
|
|
148
|
+
# it so its first parse memoizes like the original's.
|
|
149
|
+
(@heavy ||= {}.compare_by_identity)[root] = true if @heavy_by_hash&.[](hash_key)
|
|
150
|
+
return shared
|
|
151
|
+
end
|
|
152
|
+
|
|
128
153
|
# A grammar the compiler cannot compile — e.g. one containing a
|
|
129
154
|
# lazily resolved Entity that a select-first literal index would
|
|
130
155
|
# never select — falls back to the interpreter, the source of
|
|
131
156
|
# truth. The interpreter resolves such entities lazily, so an
|
|
132
157
|
# unselected branch simply never raises.
|
|
158
|
+
@last_compile_prone = nil
|
|
133
159
|
program =
|
|
134
160
|
begin
|
|
135
161
|
compile(atom)
|
|
@@ -137,6 +163,19 @@ module Parsanol
|
|
|
137
163
|
:fallback
|
|
138
164
|
end
|
|
139
165
|
cache[root] = program
|
|
166
|
+
# Only real programs share. A symbol verdict (:fallback /
|
|
167
|
+
# :oversize) is a property of this particular compile
|
|
168
|
+
# attempt's path, not of the structure: serving it to a later
|
|
169
|
+
# grammar with the same structure would silently send a
|
|
170
|
+
# compile-capable grammar to the interpreter, unseeded — the
|
|
171
|
+
# order-dependent vm_fail_dispatch failure. Verdicts stay
|
|
172
|
+
# identity-local; a fresh root recompiles and re-derives
|
|
173
|
+
# both the verdict and the heavy-memo seed naturally.
|
|
174
|
+
if hash_key && program.is_a?(Array)
|
|
175
|
+
hashed[hash_key] = program
|
|
176
|
+
(@heavy_by_hash ||= {})[hash_key] = @last_compile_prone
|
|
177
|
+
trim_hashed_cache(hashed)
|
|
178
|
+
end
|
|
140
179
|
trim_cache(cache)
|
|
141
180
|
# :fallback = VM-incompatible; :oversize even non-inlined means
|
|
142
181
|
# the grammar is beyond the program cap — the interpreter
|
|
@@ -157,7 +196,9 @@ module Parsanol
|
|
|
157
196
|
|
|
158
197
|
def clear_program_cache
|
|
159
198
|
@programs&.clear
|
|
199
|
+
@programs_by_hash&.clear
|
|
160
200
|
@heavy&.clear
|
|
201
|
+
@heavy_by_hash&.clear
|
|
161
202
|
end
|
|
162
203
|
|
|
163
204
|
# FIFO eviction past the cache limit.
|
|
@@ -166,6 +207,11 @@ module Parsanol
|
|
|
166
207
|
nil
|
|
167
208
|
end
|
|
168
209
|
|
|
210
|
+
def trim_hashed_cache(hashed)
|
|
211
|
+
hashed.shift while hashed.size > PROGRAM_CACHE_LIMIT
|
|
212
|
+
nil
|
|
213
|
+
end
|
|
214
|
+
|
|
169
215
|
# Compiles the grammar rooted at +atom+. Returns the flat program
|
|
170
216
|
# Array, or nil when the grammar uses unsupported atoms.
|
|
171
217
|
def compile(atom)
|
|
@@ -205,8 +251,12 @@ module Parsanol
|
|
|
205
251
|
# rs#166: proneness rides the structure-hash share as a bool —
|
|
206
252
|
# a later identical-structure grammar inherits the seed instead
|
|
207
253
|
# of running its cold start unmemoized because compilation was
|
|
208
|
-
# skipped entirely.
|
|
209
|
-
|
|
254
|
+
# skipped entirely. Recorded on the VM singleton so the share
|
|
255
|
+
# store can read it after this compile; program_for resets it
|
|
256
|
+
# first, so a raised NotImplementedError cannot smear the
|
|
257
|
+
# previous grammar's proneness onto this one.
|
|
258
|
+
@last_compile_prone = compiler.backtracking_prone
|
|
259
|
+
if @last_compile_prone
|
|
210
260
|
(@heavy ||= {}.compare_by_identity)[root] = true
|
|
211
261
|
trim_cache(@heavy)
|
|
212
262
|
end
|
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.81
|
|
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-08 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
|