parsanol 1.3.133 → 1.3.134
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/native/parser.rb +1 -1
- data/lib/parsanol/native/serializer.rb +95 -71
- data/lib/parsanol/native.rb +11 -1
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol.rb +7 -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: f08814380448c3eda0ac68afa1e30d4abdbfe7a1eb7dd40d0f1da7865df7b26a
|
|
4
|
+
data.tar.gz: 9c22bd914b428f88bbe351931a126dceb05fcc0a213ce6bbbbef3f710fa69693
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1041019c537d1ad263b1752ac2943c55573147a403eea07a0fa4a58ef2ea07cb8d36194a23845d35143b13aeaf25e757baf3ec22a2520c09a72e192a0dc1f4dc
|
|
7
|
+
data.tar.gz: 6054b35ed91e9f0fab5a04b4219ff3f94ebd3d981efe2a38d6f1a9650c2a7308e3a279c7aa94cee7b4503fe435e73f388e9578c520807771ccc7cbbdf7ada8af
|
|
@@ -99,7 +99,7 @@ module Parsanol
|
|
|
99
99
|
return handle
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
json = legacy ? GrammarSerializer.serialize(root_atom) : grammar_json(root_atom)
|
|
102
|
+
json = legacy ? GrammarSerializer.serialize(root_atom, preserve_identity: true) : grammar_json(root_atom)
|
|
103
103
|
handle = Native._register_grammar(json)
|
|
104
104
|
HANDLE_CACHE[cache_key] = handle
|
|
105
105
|
trim_caches
|
|
@@ -8,14 +8,22 @@ module Parsanol
|
|
|
8
8
|
# Serialize a Parslet grammar (root atom) to JSON string
|
|
9
9
|
#
|
|
10
10
|
# @param root [Parsanol::Atoms::Base] The root atom of the grammar
|
|
11
|
+
# @param preserve_identity [Boolean] emit exactly one wire atom per
|
|
12
|
+
# Ruby atom, even when an atom is reached again through a recursive
|
|
13
|
+
# rule while its own children are still being serialized. Parslet
|
|
14
|
+
# compatibility needs this: its memo is keyed by atom identity, so a
|
|
15
|
+
# duplicated wire atom splits one memo entry in two and the native
|
|
16
|
+
# engine can pick a different alternative than the Ruby engine. The
|
|
17
|
+
# default keeps the established wire layout of strict grammars.
|
|
11
18
|
# @return [String] JSON representation of the grammar
|
|
12
|
-
def self.serialize(root)
|
|
19
|
+
def self.serialize(root, preserve_identity: false)
|
|
13
20
|
# Create fresh instance for each serialization
|
|
14
21
|
# (state is specific to each grammar)
|
|
15
|
-
new.serialize(root)
|
|
22
|
+
new(preserve_identity: preserve_identity).serialize(root)
|
|
16
23
|
end
|
|
17
24
|
|
|
18
|
-
def initialize
|
|
25
|
+
def initialize(preserve_identity: false)
|
|
26
|
+
@preserve_identity = preserve_identity
|
|
19
27
|
@atoms = []
|
|
20
28
|
@atom_cache = {} # object_id => atom_id for deduplication
|
|
21
29
|
# Strong retention: cache keys are object_ids; without holding the
|
|
@@ -52,74 +60,19 @@ module Parsanol
|
|
|
52
60
|
# Don't create a new atom, just resolve and return the referenced atom_id
|
|
53
61
|
return serialize_entity(atom) if atom.is_a?(Parsanol::Atoms::Entity)
|
|
54
62
|
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
serialize_named(atom)
|
|
69
|
-
when Parsanol::Atoms::Lookahead
|
|
70
|
-
serialize_lookahead(atom)
|
|
71
|
-
when Parsanol::Atoms::Capture
|
|
72
|
-
serialize_capture(atom)
|
|
73
|
-
when Parsanol::Atoms::Scope
|
|
74
|
-
serialize_scope(atom)
|
|
75
|
-
when Parsanol::Atoms::StateSet
|
|
76
|
-
serialize_state_set(atom)
|
|
77
|
-
when Parsanol::Atoms::StateMatch
|
|
78
|
-
serialize_state_match(atom)
|
|
79
|
-
when Parsanol::Atoms::StateSwitch
|
|
80
|
-
serialize_state_switch(atom)
|
|
81
|
-
when Parsanol::Atoms::CustomRef
|
|
82
|
-
serialize_custom_ref(atom)
|
|
83
|
-
when Parsanol::Atoms::Dynamic
|
|
84
|
-
serialize_dynamic(atom)
|
|
85
|
-
when Parsanol::Atoms::TriviaCapture
|
|
86
|
-
# parsanol-ruby#152: the capture wrapper carries
|
|
87
|
-
# its marker table so the native engine records
|
|
88
|
-
# comment-shaped units into the pending channel
|
|
89
|
-
# and attaches them to the next Named capture
|
|
90
|
-
# under `comments:`. A distinct variant keeps the
|
|
91
|
-
# declared gate: engines predating it reject
|
|
92
|
-
# capture-carrying artifacts loudly.
|
|
93
|
-
serialize_trivia_capture(atom)
|
|
94
|
-
when Parsanol::Atoms::Trivia
|
|
95
|
-
# Wire-distinct from Ignore (rs#172): the Rust
|
|
96
|
-
# engine suppresses trivia-position diagnostics by
|
|
97
|
-
# atom kind. Engines predating the Trivia tag
|
|
98
|
-
# reject skip-carrying artifacts loudly (unknown
|
|
99
|
-
# variant), which is the declared gate.
|
|
100
|
-
serialize_trivia(atom)
|
|
101
|
-
when Parsanol::Atoms::Constant
|
|
102
|
-
# rs#137 follow-up: coradoc-markdown's Output
|
|
103
|
-
# atoms — empty match yielding a wire constant.
|
|
104
|
-
{ "Constant" => { "value" => serialize_constant_value(atom.value) } }
|
|
105
|
-
when Parsanol::Atoms::Lookbehind
|
|
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 } }
|
|
117
|
-
when Parsanol::Atoms::Ignored
|
|
118
|
-
serialize_ignored(atom)
|
|
119
|
-
else
|
|
120
|
-
# Fallback for unknown atom types
|
|
121
|
-
serialize_unknown(atom)
|
|
122
|
-
end
|
|
63
|
+
# Reserve the id before the children, as serialize_entity does, so a
|
|
64
|
+
# recursive path back to this atom reuses it instead of emitting a
|
|
65
|
+
# second copy.
|
|
66
|
+
if @preserve_identity
|
|
67
|
+
atom_id = @atoms.size
|
|
68
|
+
@atom_cache[cache_key] = atom_id
|
|
69
|
+
@retained_atoms << atom
|
|
70
|
+
@atoms << nil
|
|
71
|
+
@atoms[atom_id] = serialize_body(atom)
|
|
72
|
+
return atom_id
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
serialized = serialize_body(atom)
|
|
123
76
|
|
|
124
77
|
# Now reserve an atom_id and cache
|
|
125
78
|
atom_id = @atoms.size
|
|
@@ -130,6 +83,77 @@ module Parsanol
|
|
|
130
83
|
atom_id
|
|
131
84
|
end
|
|
132
85
|
|
|
86
|
+
def serialize_body(atom)
|
|
87
|
+
# Serialize based on atom type first (recursively)
|
|
88
|
+
case atom
|
|
89
|
+
when Parsanol::Atoms::Str
|
|
90
|
+
serialize_str(atom)
|
|
91
|
+
when Parsanol::Atoms::Re
|
|
92
|
+
serialize_re(atom)
|
|
93
|
+
when Parsanol::Atoms::Sequence
|
|
94
|
+
serialize_sequence(atom)
|
|
95
|
+
when Parsanol::Atoms::Alternative
|
|
96
|
+
serialize_alternative(atom)
|
|
97
|
+
when Parsanol::Atoms::Repetition
|
|
98
|
+
serialize_repetition(atom)
|
|
99
|
+
when Parsanol::Atoms::Named
|
|
100
|
+
serialize_named(atom)
|
|
101
|
+
when Parsanol::Atoms::Lookahead
|
|
102
|
+
serialize_lookahead(atom)
|
|
103
|
+
when Parsanol::Atoms::Capture
|
|
104
|
+
serialize_capture(atom)
|
|
105
|
+
when Parsanol::Atoms::Scope
|
|
106
|
+
serialize_scope(atom)
|
|
107
|
+
when Parsanol::Atoms::StateSet
|
|
108
|
+
serialize_state_set(atom)
|
|
109
|
+
when Parsanol::Atoms::StateMatch
|
|
110
|
+
serialize_state_match(atom)
|
|
111
|
+
when Parsanol::Atoms::StateSwitch
|
|
112
|
+
serialize_state_switch(atom)
|
|
113
|
+
when Parsanol::Atoms::CustomRef
|
|
114
|
+
serialize_custom_ref(atom)
|
|
115
|
+
when Parsanol::Atoms::Dynamic
|
|
116
|
+
serialize_dynamic(atom)
|
|
117
|
+
when Parsanol::Atoms::TriviaCapture
|
|
118
|
+
# parsanol-ruby#152: the capture wrapper carries
|
|
119
|
+
# its marker table so the native engine records
|
|
120
|
+
# comment-shaped units into the pending channel
|
|
121
|
+
# and attaches them to the next Named capture
|
|
122
|
+
# under `comments:`. A distinct variant keeps the
|
|
123
|
+
# declared gate: engines predating it reject
|
|
124
|
+
# capture-carrying artifacts loudly.
|
|
125
|
+
serialize_trivia_capture(atom)
|
|
126
|
+
when Parsanol::Atoms::Trivia
|
|
127
|
+
# Wire-distinct from Ignore (rs#172): the Rust
|
|
128
|
+
# engine suppresses trivia-position diagnostics by
|
|
129
|
+
# atom kind. Engines predating the Trivia tag
|
|
130
|
+
# reject skip-carrying artifacts loudly (unknown
|
|
131
|
+
# variant), which is the declared gate.
|
|
132
|
+
serialize_trivia(atom)
|
|
133
|
+
when Parsanol::Atoms::Constant
|
|
134
|
+
# rs#137 follow-up: coradoc-markdown's Output
|
|
135
|
+
# atoms — empty match yielding a wire constant.
|
|
136
|
+
{ "Constant" => { "value" => serialize_constant_value(atom.value) } }
|
|
137
|
+
when Parsanol::Atoms::Lookbehind
|
|
138
|
+
# rs#137/#163: the behind-window test — a fixed
|
|
139
|
+
# literal window or an end-anchored regex (the
|
|
140
|
+
# CommonMark flanking form).
|
|
141
|
+
look = if atom.regex?
|
|
142
|
+
{ "Regex" => { "source" => atom.regex_source } }
|
|
143
|
+
else
|
|
144
|
+
{ "Literal" => { "count" => atom.count,
|
|
145
|
+
"pattern" => atom.pattern } }
|
|
146
|
+
end
|
|
147
|
+
{ "Lookbehind" => { "look" => look,
|
|
148
|
+
"positive" => atom.positive } }
|
|
149
|
+
when Parsanol::Atoms::Ignored
|
|
150
|
+
serialize_ignored(atom)
|
|
151
|
+
else
|
|
152
|
+
# Fallback for unknown atom types
|
|
153
|
+
serialize_unknown(atom)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
133
157
|
def serialize_str(atom)
|
|
134
158
|
{
|
|
135
159
|
"Str" => {
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -402,12 +402,22 @@ module Parsanol
|
|
|
402
402
|
BatchDecoder.decode_and_flatten(raw, input, Parsanol::Slice)
|
|
403
403
|
end
|
|
404
404
|
|
|
405
|
+
# The native engine's recursion-limit abort (ParseError::
|
|
406
|
+
# RecursionLimitExceeded in parsanol-rs). It carries no position
|
|
407
|
+
# marker: it is a resource limit, not a refusal of the input.
|
|
408
|
+
NATIVE_RECURSION_LIMIT = /\A(?:Recursion limit exceeded\b|Maximum recursion depth \(\d+\) exceeded)/
|
|
409
|
+
|
|
405
410
|
def raise_compatible_parse_error(error, input)
|
|
406
411
|
message = error.message
|
|
407
412
|
position = message[NATIVE_POS_MARKER, 1].to_i
|
|
408
413
|
cause = Parsanol::Cause.new(message.sub(NATIVE_POS_MARKER, ""),
|
|
409
414
|
Parsanol::Source.new(input), position)
|
|
410
|
-
|
|
415
|
+
failure = if !message.match?(NATIVE_POS_MARKER) && message.match?(NATIVE_RECURSION_LIMIT)
|
|
416
|
+
Parsanol::RecursionLimitExceeded
|
|
417
|
+
else
|
|
418
|
+
Parsanol::ParseFailed
|
|
419
|
+
end
|
|
420
|
+
raise failure.new(cause.to_s, cause), cause: nil
|
|
411
421
|
end
|
|
412
422
|
|
|
413
423
|
# Reference-walker path (TODO.sota/8 B1): the plain recursive
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol.rb
CHANGED
|
@@ -66,6 +66,13 @@ module Parsanol
|
|
|
66
66
|
attr_reader :parse_failure_cause
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Raised when the native engine stops because the input nests deeper
|
|
70
|
+
# than its recursion limit. A subclass of ParseFailed with the same
|
|
71
|
+
# message and cause, so existing rescues behave as before; callers that
|
|
72
|
+
# want to retry on another engine can rescue this class instead of
|
|
73
|
+
# matching the message.
|
|
74
|
+
class RecursionLimitExceeded < ParseFailed; end
|
|
75
|
+
|
|
69
76
|
# Class methods added to classes that include Parsanol.
|
|
70
77
|
module ClassMethods
|
|
71
78
|
# Enable automatic rule optimization for all rules in this parser.
|