parsanol 1.3.54-x86_64-linux → 1.3.56-x86_64-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/atoms/base.rb +1 -1
- data/lib/parsanol/atoms/cut.rb +1 -1
- data/lib/parsanol/atoms/repetition.rb +13 -2
- data/lib/parsanol/cause.rb +1 -1
- data/lib/parsanol/error_reporter/contextual.rb +1 -1
- data/lib/parsanol/lazy_result.rb +12 -3
- data/lib/parsanol/native/libparsanol.so +0 -0
- data/lib/parsanol/native/transformer.rb +12 -8
- data/lib/parsanol/native.rb +4 -2
- data/lib/parsanol/parser.rb +1 -1
- data/lib/parsanol/pattern.rb +1 -1
- data/lib/parsanol/pool.rb +1 -1
- data/lib/parsanol/rope.rb +1 -1
- data/lib/parsanol/slice.rb +4 -6
- data/lib/parsanol/source/line_cache.rb +1 -1
- data/lib/parsanol/source.rb +2 -2
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/vm.rb +88 -0
- data/lib/parsanol.rb +6 -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: 66df514ceaedfb9d973a67ba46aa9a207602d8bba9103864bfdb39db558dc6ab
|
|
4
|
+
data.tar.gz: 853d2a548335096d7672ab4b4292770a25c260ccaaae8fae79cd2567bb9fa391
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5ffb88f39d23d6fcb14d2ac9ca589b8f44699dbb6691911e75265253d9f48c3447e14dd7c77a11bfc4047ab3d99fa7a3c9e4be030d798564d7757338904d40af
|
|
7
|
+
data.tar.gz: 24f5ce2a2b09f7b0c9d12d758cac3fba3372a1ba5ca22d209c193856d405a307f593d9246c8dbd6e80a9e25f6b9746ee79b0819a701afd174950f45018180208
|
|
@@ -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: 35066072 bytes
|
|
4
|
+
sha256: 754a72fbf04eb7c51adb630381a3a695011edda945f6f728e5a22ae1bbfaf52a
|
|
@@ -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: 35296024 bytes
|
|
4
|
+
sha256: d85a6d3939482a73ed925708bdb6de3d1cc61ade10c01d2ecf4e7a24a6c3fe2b
|
|
@@ -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: 35295024 bytes
|
|
4
|
+
sha256: b820397175deb40fe91edbb41a2c90a1b1529c8ed2c8d3e32284089442c5baac
|
|
@@ -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: 35304912 bytes
|
|
4
|
+
sha256: 6ebf6401932f29a1efd4cf417197979d72d449a6fda639d04df1efdbdabbac2b
|
data/lib/parsanol/atoms/base.rb
CHANGED
|
@@ -208,7 +208,7 @@ module Parsanol
|
|
|
208
208
|
|
|
209
209
|
# Converts raw input to Source if needed.
|
|
210
210
|
def normalize_input(source)
|
|
211
|
-
source.
|
|
211
|
+
source.is_a?(Parsanol::Source) ? source : Parsanol::Source.new(source)
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
# Detects if we're in a Parser context.
|
data/lib/parsanol/atoms/cut.rb
CHANGED
|
@@ -42,7 +42,7 @@ module Parsanol
|
|
|
42
42
|
# 1. Mark the current position as a cut point
|
|
43
43
|
# 2. Empty the backtrack stack (we won't backtrack past here)
|
|
44
44
|
# 3. Aggressively evict cache entries before this position
|
|
45
|
-
context.cut!(source.bytepos)
|
|
45
|
+
context.cut!(source.bytepos)
|
|
46
46
|
|
|
47
47
|
[success, value]
|
|
48
48
|
end
|
|
@@ -66,7 +66,7 @@ module Parsanol
|
|
|
66
66
|
# @return [Array(Boolean, Object)] result
|
|
67
67
|
def try(source, context, consume_all)
|
|
68
68
|
# Check for tree memoization support
|
|
69
|
-
if context.
|
|
69
|
+
if context.use_tree_memoization?
|
|
70
70
|
return with_tree_cache(source, context, consume_all)
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -183,6 +183,7 @@ module Parsanol
|
|
|
183
183
|
last_failure = nil
|
|
184
184
|
|
|
185
185
|
loop do
|
|
186
|
+
iteration_start = source.bytepos
|
|
186
187
|
success, value = @parslet.apply(source, context, false)
|
|
187
188
|
|
|
188
189
|
unless success
|
|
@@ -193,6 +194,11 @@ module Parsanol
|
|
|
193
194
|
occurrence += 1
|
|
194
195
|
result[occurrence] = value
|
|
195
196
|
|
|
197
|
+
# A body match that consumes nothing cannot make progress;
|
|
198
|
+
# looping again would re-match empty forever. Count the empty
|
|
199
|
+
# match, then stop — the min check below applies as usual.
|
|
200
|
+
break if source.bytepos == iteration_start
|
|
201
|
+
|
|
196
202
|
break if @max && occurrence >= @max
|
|
197
203
|
end
|
|
198
204
|
|
|
@@ -254,6 +260,7 @@ module Parsanol
|
|
|
254
260
|
last_failure = nil
|
|
255
261
|
|
|
256
262
|
loop do
|
|
263
|
+
iteration_start = source.bytepos
|
|
257
264
|
success, value = @parslet.apply(source, context, false)
|
|
258
265
|
|
|
259
266
|
unless success
|
|
@@ -265,6 +272,10 @@ module Parsanol
|
|
|
265
272
|
result[occurrence] = value
|
|
266
273
|
positions << source.bytepos
|
|
267
274
|
|
|
275
|
+
# Zero-width body match: count it, stop iterating (the min
|
|
276
|
+
# check below applies as usual) — mirrors try_general.
|
|
277
|
+
break if source.bytepos == iteration_start
|
|
278
|
+
|
|
268
279
|
break if @max && occurrence >= @max
|
|
269
280
|
end
|
|
270
281
|
|
|
@@ -287,7 +298,7 @@ module Parsanol
|
|
|
287
298
|
context.store_tree_memo(cache_key, start_pos,
|
|
288
299
|
result[1, occurrence], end_pos)
|
|
289
300
|
end
|
|
290
|
-
context.release_array(positions)
|
|
301
|
+
context.release_array(positions)
|
|
291
302
|
|
|
292
303
|
# Check minimum
|
|
293
304
|
if occurrence < @min
|
data/lib/parsanol/cause.rb
CHANGED
|
@@ -74,7 +74,7 @@ module Parsanol
|
|
|
74
74
|
line_num, col_num = @source.line_and_column(@position)
|
|
75
75
|
|
|
76
76
|
formatted_msg = @message.map do |msg|
|
|
77
|
-
msg.
|
|
77
|
+
msg.is_a?(Parsanol::Slice) ? msg.content.inspect : msg.to_s
|
|
78
78
|
end.join
|
|
79
79
|
|
|
80
80
|
"#{formatted_msg} at line #{line_num} char #{col_num}#{@parsing_label}."
|
|
@@ -72,7 +72,7 @@ module Parsanol
|
|
|
72
72
|
cause = super
|
|
73
73
|
|
|
74
74
|
# Apply label if the atom has one
|
|
75
|
-
if atom.
|
|
75
|
+
if atom.is_a?(Parsanol::Atoms::Base) && (lbl = atom.label)
|
|
76
76
|
maybe_update_label(lbl, src.pos.bytepos)
|
|
77
77
|
cause.set_label(@active_label_text)
|
|
78
78
|
end
|
data/lib/parsanol/lazy_result.rb
CHANGED
|
@@ -103,6 +103,15 @@ module Parsanol
|
|
|
103
103
|
|
|
104
104
|
alias kind_of? is_a?
|
|
105
105
|
|
|
106
|
+
# The delegated interface: Array's full class contract (including
|
|
107
|
+
# Enumerable), computed once against the type rather than probed on
|
|
108
|
+
# instances.
|
|
109
|
+
ARRAY_METHODS = (
|
|
110
|
+
Array.instance_methods(true) + Enumerable.instance_methods + [:to_ary]
|
|
111
|
+
).freeze
|
|
112
|
+
|
|
113
|
+
private_constant :ARRAY_METHODS
|
|
114
|
+
|
|
106
115
|
# Respond to array methods.
|
|
107
116
|
#
|
|
108
117
|
# @param method [Symbol] Method name
|
|
@@ -110,7 +119,7 @@ module Parsanol
|
|
|
110
119
|
# @return [Boolean] true if responds
|
|
111
120
|
#
|
|
112
121
|
def respond_to?(method, include_private = false)
|
|
113
|
-
super ||
|
|
122
|
+
super || ARRAY_METHODS.include?(method)
|
|
114
123
|
end
|
|
115
124
|
|
|
116
125
|
# Delegate unknown methods to materialized array.
|
|
@@ -121,7 +130,7 @@ module Parsanol
|
|
|
121
130
|
# @return [Object] Result of method call
|
|
122
131
|
#
|
|
123
132
|
def method_missing(method, ...)
|
|
124
|
-
if
|
|
133
|
+
if ARRAY_METHODS.include?(method)
|
|
125
134
|
to_a.public_send(method, ...)
|
|
126
135
|
else
|
|
127
136
|
super
|
|
@@ -135,7 +144,7 @@ module Parsanol
|
|
|
135
144
|
# @return [Boolean] true if method is supported
|
|
136
145
|
#
|
|
137
146
|
def respond_to_missing?(method, include_private = false)
|
|
138
|
-
|
|
147
|
+
ARRAY_METHODS.include?(method) || super
|
|
139
148
|
end
|
|
140
149
|
|
|
141
150
|
# Compare with another object.
|
|
Binary file
|
|
@@ -391,15 +391,19 @@ module Parsanol
|
|
|
391
391
|
return left.is_a?(Hash) ? left.merge(right) : left + right
|
|
392
392
|
end
|
|
393
393
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
394
|
+
# String-like = String or Slice (both implement to_str). When
|
|
395
|
+
# only ONE side is string-like, the OTHER side wins — the
|
|
396
|
+
# original duck-typed priority, preserved exactly.
|
|
397
|
+
left_stringlike = left.is_a?(String) || left.is_a?(Parsanol::Slice)
|
|
398
|
+
right_stringlike = right.is_a?(String) || right.is_a?(Parsanol::Slice)
|
|
399
|
+
if left_stringlike && right_stringlike
|
|
400
|
+
return right if right.is_a?(Parsanol::Slice)
|
|
401
|
+
return left if left.is_a?(Parsanol::Slice)
|
|
402
|
+
|
|
403
|
+
return left + right
|
|
399
404
|
end
|
|
400
|
-
|
|
401
|
-
return
|
|
402
|
-
return right if left.respond_to?(:to_str)
|
|
405
|
+
return left if right_stringlike
|
|
406
|
+
return right if left_stringlike
|
|
403
407
|
|
|
404
408
|
return left + [right] if right.is_a?(Hash)
|
|
405
409
|
return [left] + right if left.is_a?(Hash)
|
data/lib/parsanol/native.rb
CHANGED
|
@@ -216,7 +216,9 @@ module Parsanol
|
|
|
216
216
|
# cause from that directly — the failure path never needs a
|
|
217
217
|
# reporter reparse. The single interpreter pass below stays only
|
|
218
218
|
# to recover inputs from grammars native cannot express.
|
|
219
|
-
|
|
219
|
+
parsanol_grammar =
|
|
220
|
+
grammar.is_a?(Parsanol::Atoms::Base) || grammar.is_a?(Parsanol::Parser)
|
|
221
|
+
if parsanol_grammar && (m = error.message.match(NATIVE_POS_MARKER))
|
|
220
222
|
source = Parsanol::Source.new(input)
|
|
221
223
|
success, value = grammar.run_with_context(source, nil, true)
|
|
222
224
|
return grammar.finalize_result(value) if success
|
|
@@ -227,7 +229,7 @@ module Parsanol
|
|
|
227
229
|
raise Parsanol::ParseFailed.new(cause.to_s, cause)
|
|
228
230
|
end
|
|
229
231
|
|
|
230
|
-
if
|
|
232
|
+
if parsanol_grammar
|
|
231
233
|
# No native diagnostics (e.g. incomplete-input errors): one
|
|
232
234
|
# interpreter pass with the reporter attached — a success
|
|
233
235
|
# recovers the tree, a failure raises the cause tree.
|
data/lib/parsanol/parser.rb
CHANGED
|
@@ -255,7 +255,7 @@ module Parsanol
|
|
|
255
255
|
# already produced. The full per-atom event stream of the Ruby
|
|
256
256
|
# engine remains available via an explicit `mode: :ruby`.
|
|
257
257
|
def feed_native_reporter(reporter, input, error)
|
|
258
|
-
return unless reporter.
|
|
258
|
+
return unless reporter.is_a?(Parsanol::ErrorReporter::Base)
|
|
259
259
|
|
|
260
260
|
source = Parsanol::Source.new(input)
|
|
261
261
|
cause = error.parse_failure_cause
|
data/lib/parsanol/pattern.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Parsanol
|
|
|
90
90
|
return true if pattern_val === target
|
|
91
91
|
|
|
92
92
|
# Check if pattern is a binding expression (like simple(:x))
|
|
93
|
-
if pattern_val.
|
|
93
|
+
if pattern_val.is_a?(Parsanol::Pattern::SubtreeBind) && pattern_val.can_bind?(target)
|
|
94
94
|
return capture_binding(target, pattern_val, captured)
|
|
95
95
|
end
|
|
96
96
|
|
data/lib/parsanol/pool.rb
CHANGED
data/lib/parsanol/rope.rb
CHANGED
data/lib/parsanol/slice.rb
CHANGED
|
@@ -142,18 +142,16 @@ module Parsanol
|
|
|
142
142
|
|
|
143
143
|
# Unified line/column computation:
|
|
144
144
|
# - String input: compute from input string
|
|
145
|
-
# - LineCache: delegate
|
|
145
|
+
# - Source / LineCache: delegate
|
|
146
146
|
def line_and_column_at(pos)
|
|
147
|
-
if @input.
|
|
148
|
-
# LineCache or duck-typed object
|
|
149
|
-
@input.line_and_column(pos)
|
|
150
|
-
else
|
|
151
|
-
# String input
|
|
147
|
+
if @input.is_a?(String)
|
|
152
148
|
prefix = @input.byteslice(0, pos) || ""
|
|
153
149
|
line = 1 + prefix.count("\n")
|
|
154
150
|
last_nl = prefix.rindex("\n")
|
|
155
151
|
column = last_nl ? pos - last_nl : pos + 1
|
|
156
152
|
[line, column]
|
|
153
|
+
else
|
|
154
|
+
@input.line_and_column(pos)
|
|
157
155
|
end
|
|
158
156
|
end
|
|
159
157
|
end
|
data/lib/parsanol/source.rb
CHANGED
|
@@ -33,9 +33,9 @@ module Parsanol
|
|
|
33
33
|
# @raise [ArgumentError] if input doesn't respond to to_str
|
|
34
34
|
#
|
|
35
35
|
def initialize(input)
|
|
36
|
-
unless input.
|
|
36
|
+
unless input.is_a?(String)
|
|
37
37
|
raise ArgumentError,
|
|
38
|
-
"Source requires a
|
|
38
|
+
"Source requires a String (got #{input.class})"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
@raw_string = input.to_str
|
data/lib/parsanol/version.rb
CHANGED
data/lib/parsanol/vm.rb
CHANGED
|
@@ -149,6 +149,7 @@ module Parsanol
|
|
|
149
149
|
def compile_with_inline(atom, inline)
|
|
150
150
|
root = atom.is_a?(Parsanol::Parser) ? atom.root : atom
|
|
151
151
|
compiler = Compiler.new(inline: inline)
|
|
152
|
+
validate_alternatives(root)
|
|
152
153
|
return nil unless compiler.compile_atom(root, true)
|
|
153
154
|
|
|
154
155
|
# HALT terminates the MAIN program; subroutines follow it so the
|
|
@@ -174,6 +175,81 @@ module Parsanol
|
|
|
174
175
|
compiler.to_program
|
|
175
176
|
end
|
|
176
177
|
|
|
178
|
+
def validate_choice_branches(atom)
|
|
179
|
+
atom.alternatives.each_with_index do |branch, idx|
|
|
180
|
+
walk_branch = branch
|
|
181
|
+
next if idx == atom.alternatives.size - 1
|
|
182
|
+
|
|
183
|
+
# Entities resolve lazily and may be recursive; resolve
|
|
184
|
+
# through the wrapper for the nullability check only.
|
|
185
|
+
while walk_branch.is_a?(Parsanol::Atoms::Entity) ||
|
|
186
|
+
walk_branch.is_a?(Parsanol::Atoms::Named)
|
|
187
|
+
walk_branch = begin
|
|
188
|
+
walk_branch.parslet
|
|
189
|
+
rescue StandardError
|
|
190
|
+
nil
|
|
191
|
+
end
|
|
192
|
+
break if walk_branch.nil?
|
|
193
|
+
end
|
|
194
|
+
next if walk_branch.nil?
|
|
195
|
+
next unless Compiler.new.nullable?(walk_branch)
|
|
196
|
+
|
|
197
|
+
raise Parsanol::GrammarError,
|
|
198
|
+
"wrong grammar: alternative branch #{idx} " \
|
|
199
|
+
"(#{branch.inspect}) can match empty input and is not " \
|
|
200
|
+
"the last branch — ordered choice commits to its empty " \
|
|
201
|
+
"match, so #{atom.alternatives.size - idx - 1} later " \
|
|
202
|
+
"branch(es) can never match at positions where it " \
|
|
203
|
+
"matches empty. Reorder branches so empty-matchable " \
|
|
204
|
+
"ones come last, or make the branch non-empty."
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Grammar validation, run once per compile: an ordered-choice
|
|
209
|
+
# branch that can match empty input while not being last commits
|
|
210
|
+
# to its empty match and permanently shadows every later branch —
|
|
211
|
+
# the grammar is wrong, and inputs it would mis-parse fail with a
|
|
212
|
+
# baffling cause. Raise before any input is seen.
|
|
213
|
+
def validate_alternatives(root)
|
|
214
|
+
visited = {}.compare_by_identity
|
|
215
|
+
walk = lambda do |atom, depth|
|
|
216
|
+
next if atom.nil? || depth > 20
|
|
217
|
+
next if visited[atom]
|
|
218
|
+
|
|
219
|
+
visited[atom] = true
|
|
220
|
+
case atom
|
|
221
|
+
when Parsanol::Atoms::Alternative
|
|
222
|
+
validate_choice_branches(atom)
|
|
223
|
+
atom.alternatives.each { |c| walk.call(c, depth + 1) }
|
|
224
|
+
when Parsanol::Atoms::Sequence
|
|
225
|
+
atom.parslets.each { |c| walk.call(c, depth + 1) }
|
|
226
|
+
when Parsanol::Atoms::Repetition, Parsanol::Atoms::Named
|
|
227
|
+
walk.call(atom.parslet, depth + 1)
|
|
228
|
+
when Parsanol::Atoms::Lookahead
|
|
229
|
+
walk.call(atom.bound_parslet, depth + 1)
|
|
230
|
+
when Parsanol::Atoms::Ignored
|
|
231
|
+
walk.call(atom.wrapped_atom, depth + 1)
|
|
232
|
+
when Parsanol::Atoms::Entity
|
|
233
|
+
begin
|
|
234
|
+
walk.call(atom.parslet, depth + 1)
|
|
235
|
+
rescue Parsanol::GrammarError
|
|
236
|
+
raise
|
|
237
|
+
rescue StandardError
|
|
238
|
+
# Lazily resolved / recursive entity that cannot resolve
|
|
239
|
+
# outside a parse: skip this branch of the walk.
|
|
240
|
+
nil
|
|
241
|
+
end
|
|
242
|
+
when Parsanol::Atoms::Scope
|
|
243
|
+
begin
|
|
244
|
+
walk.call(atom.block.call, depth + 1)
|
|
245
|
+
rescue StandardError
|
|
246
|
+
nil
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
walk.call(root, 0)
|
|
251
|
+
end
|
|
252
|
+
|
|
177
253
|
# Executes a compiled program. Returns [true, value] on success;
|
|
178
254
|
# [false, nil] on failure or budget exhaustion — callers fall back
|
|
179
255
|
# to the interpreter, which reproduces exact diagnostics.
|
|
@@ -977,12 +1053,24 @@ module Parsanol
|
|
|
977
1053
|
count = frames[cbase]
|
|
978
1054
|
return BAIL if count.nil?
|
|
979
1055
|
|
|
1056
|
+
old_end = frames[cbase + 1]
|
|
980
1057
|
count += 1
|
|
981
1058
|
frames[cbase] = count
|
|
982
1059
|
frames[cbase + 1] = pos
|
|
983
1060
|
max = ops[pc + 3]
|
|
984
1061
|
pc = if max && count >= max
|
|
985
1062
|
ops[pc + 2] # exit
|
|
1063
|
+
elsif pos == old_end
|
|
1064
|
+
# Zero-width body match: count it, stop iterating.
|
|
1065
|
+
# Mirrors Repetition#try_general: success with the
|
|
1066
|
+
# prefix when min is met, failure of the repetition
|
|
1067
|
+
# otherwise.
|
|
1068
|
+
if count >= frames[cbase + 3]
|
|
1069
|
+
ops[pc + 2] # exit
|
|
1070
|
+
else
|
|
1071
|
+
frames.slice!(cbase..)
|
|
1072
|
+
fail_pc
|
|
1073
|
+
end
|
|
986
1074
|
else
|
|
987
1075
|
ops[pc + 1] # test
|
|
988
1076
|
end
|
data/lib/parsanol.rb
CHANGED
|
@@ -44,6 +44,12 @@ module Parsanol
|
|
|
44
44
|
base.extend(ClassMethods)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Raised when the GRAMMAR itself is malformed — detected before any
|
|
48
|
+
# input is parsed. The canonical case: an ordered-choice branch that
|
|
49
|
+
# can match empty input while not being last, which commits to its
|
|
50
|
+
# empty match and permanently shadows every later branch.
|
|
51
|
+
class GrammarError < StandardError; end
|
|
52
|
+
|
|
47
53
|
# Exception raised when parsing fails. Contains detailed error information
|
|
48
54
|
# in the #parse_failure_cause attribute.
|
|
49
55
|
class ParseFailed < StandardError
|