parsanol 1.3.5-aarch64-linux → 1.3.7-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/Rakefile +48 -48
- data/lib/parsanol/ast_visitor.rb +1 -1
- data/lib/parsanol/atoms/alternative.rb +3 -2
- data/lib/parsanol/atoms/base.rb +12 -6
- data/lib/parsanol/atoms/can_flatten.rb +8 -8
- data/lib/parsanol/atoms/context.rb +23 -16
- data/lib/parsanol/atoms/custom.rb +2 -2
- data/lib/parsanol/atoms/dynamic.rb +1 -1
- data/lib/parsanol/atoms/infix.rb +10 -5
- data/lib/parsanol/atoms/lookahead.rb +7 -4
- data/lib/parsanol/atoms/re.rb +1 -1
- data/lib/parsanol/atoms/repetition.rb +29 -11
- data/lib/parsanol/atoms/sequence.rb +3 -2
- data/lib/parsanol/atoms/str.rb +9 -3
- data/lib/parsanol/atoms.rb +20 -20
- data/lib/parsanol/builder_callbacks.rb +2 -2
- data/lib/parsanol/cause.rb +2 -2
- data/lib/parsanol/context.rb +2 -2
- data/lib/parsanol/error_reporter.rb +5 -5
- data/lib/parsanol/expression/treetop.rb +17 -17
- data/lib/parsanol/expression.rb +1 -1
- data/lib/parsanol/fast_mode.rb +50 -12
- data/lib/parsanol/first_set.rb +1 -1
- data/lib/parsanol/grammar_builder.rb +10 -8
- data/lib/parsanol/incremental_parser.rb +13 -8
- data/lib/parsanol/interval_tree.rb +12 -3
- data/lib/parsanol/lazy_result.rb +2 -2
- data/lib/parsanol/mermaid.rb +12 -9
- data/lib/parsanol/native/batch_decoder.rb +13 -9
- data/lib/parsanol/native/dynamic.rb +7 -6
- data/lib/parsanol/native/parser.rb +7 -5
- data/lib/parsanol/native/serializer.rb +42 -42
- data/lib/parsanol/native/transformer.rb +55 -28
- data/lib/parsanol/native/types.rb +3 -3
- data/lib/parsanol/native.rb +26 -20
- data/lib/parsanol/optimizer.rb +6 -6
- data/lib/parsanol/optimizers/choice_optimizer.rb +1 -1
- data/lib/parsanol/optimizers/cut_inserter.rb +5 -2
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +9 -3
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +5 -5
- data/lib/parsanol/optimizers/sequence_optimizer.rb +1 -1
- data/lib/parsanol/options/zero_copy.rb +1 -1
- data/lib/parsanol/options.rb +1 -1
- data/lib/parsanol/parallel.rb +4 -3
- data/lib/parsanol/parser.rb +18 -16
- data/lib/parsanol/parslet.rb +7 -7
- data/lib/parsanol/pattern/binding.rb +1 -1
- data/lib/parsanol/pattern.rb +4 -1
- data/lib/parsanol/pool.rb +3 -3
- data/lib/parsanol/pools/buffer_pool.rb +2 -2
- data/lib/parsanol/pools/position_pool.rb +2 -2
- data/lib/parsanol/position.rb +1 -1
- data/lib/parsanol/result_builder.rb +4 -4
- data/lib/parsanol/result_stream.rb +10 -5
- data/lib/parsanol/slice.rb +11 -8
- data/lib/parsanol/source.rb +14 -9
- data/lib/parsanol/source_location.rb +1 -1
- data/lib/parsanol/streaming_parser.rb +3 -3
- data/lib/parsanol/string_view.rb +4 -1
- data/lib/parsanol/transform.rb +2 -2
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/wasm_parser.rb +1 -1
- data/lib/parsanol.rb +37 -39
- data/parsanol.gemspec +30 -30
- metadata +1 -1
|
@@ -63,7 +63,8 @@ module Parsanol
|
|
|
63
63
|
when 2
|
|
64
64
|
match_pair(components[0], components[1], source, context, consume_all)
|
|
65
65
|
when 3
|
|
66
|
-
match_triple(components[0], components[1], components[2], source,
|
|
66
|
+
match_triple(components[0], components[1], components[2], source,
|
|
67
|
+
context, consume_all)
|
|
67
68
|
else
|
|
68
69
|
match_general(components, source, context, consume_all)
|
|
69
70
|
end
|
|
@@ -76,7 +77,7 @@ module Parsanol
|
|
|
76
77
|
# @param prec [Integer] precedence
|
|
77
78
|
# @return [String]
|
|
78
79
|
def to_s_inner(prec)
|
|
79
|
-
@parslets.map { |p| p.to_s(prec) }.join(
|
|
80
|
+
@parslets.map { |p| p.to_s(prec) }.join(" ")
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
# FIRST set is first element's FIRST set (with epsilon propagation).
|
data/lib/parsanol/atoms/str.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Parsanol
|
|
|
21
21
|
@char_count = @str.length
|
|
22
22
|
|
|
23
23
|
# Pre-built error messages (frozen)
|
|
24
|
-
@early_eof_msg =
|
|
24
|
+
@early_eof_msg = "Unexpected end of input"
|
|
25
25
|
@mismatch_msg = "Expected #{@str.inspect}, but got "
|
|
26
26
|
|
|
27
27
|
# Optimization: single-char fast path
|
|
@@ -75,7 +75,10 @@ module Parsanol
|
|
|
75
75
|
|
|
76
76
|
# Fast path for single-character strings.
|
|
77
77
|
def single_char_match(source, context)
|
|
78
|
-
|
|
78
|
+
if source.chars_left < 1
|
|
79
|
+
return context.err(self, source,
|
|
80
|
+
@early_eof_msg)
|
|
81
|
+
end
|
|
79
82
|
|
|
80
83
|
pos = source.pos
|
|
81
84
|
slice = source.consume(1)
|
|
@@ -88,7 +91,10 @@ module Parsanol
|
|
|
88
91
|
|
|
89
92
|
# Standard path for multi-character strings.
|
|
90
93
|
def multi_char_match(source, context)
|
|
91
|
-
|
|
94
|
+
if source.chars_left < @char_count
|
|
95
|
+
return context.err(self, source,
|
|
96
|
+
@early_eof_msg)
|
|
97
|
+
end
|
|
92
98
|
|
|
93
99
|
pos = source.pos
|
|
94
100
|
slice = source.consume(@char_count)
|
data/lib/parsanol/atoms.rb
CHANGED
|
@@ -21,26 +21,26 @@ module Parsanol
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Load atom implementations
|
|
24
|
-
require
|
|
25
|
-
require
|
|
26
|
-
require
|
|
27
|
-
require
|
|
28
|
-
require
|
|
29
|
-
require
|
|
30
|
-
require
|
|
31
|
-
require
|
|
32
|
-
require
|
|
33
|
-
require
|
|
34
|
-
require
|
|
35
|
-
require
|
|
36
|
-
require
|
|
37
|
-
require
|
|
38
|
-
require
|
|
39
|
-
require
|
|
40
|
-
require
|
|
41
|
-
require
|
|
42
|
-
require
|
|
24
|
+
require "parsanol/atoms/can_flatten"
|
|
25
|
+
require "parsanol/atoms/context"
|
|
26
|
+
require "parsanol/atoms/dsl"
|
|
27
|
+
require "parsanol/atoms/base"
|
|
28
|
+
require "parsanol/atoms/custom"
|
|
29
|
+
require "parsanol/atoms/ignored"
|
|
30
|
+
require "parsanol/atoms/named"
|
|
31
|
+
require "parsanol/atoms/lookahead"
|
|
32
|
+
require "parsanol/atoms/cut"
|
|
33
|
+
require "parsanol/atoms/alternative"
|
|
34
|
+
require "parsanol/atoms/sequence"
|
|
35
|
+
require "parsanol/atoms/repetition"
|
|
36
|
+
require "parsanol/atoms/re"
|
|
37
|
+
require "parsanol/atoms/str"
|
|
38
|
+
require "parsanol/atoms/entity"
|
|
39
|
+
require "parsanol/atoms/capture"
|
|
40
|
+
require "parsanol/atoms/dynamic"
|
|
41
|
+
require "parsanol/atoms/scope"
|
|
42
|
+
require "parsanol/atoms/infix"
|
|
43
43
|
# Load visitor pattern (must be after all atom classes)
|
|
44
|
-
require
|
|
44
|
+
require "parsanol/atoms/visitor"
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -204,7 +204,7 @@ module Parsanol
|
|
|
204
204
|
end
|
|
205
205
|
|
|
206
206
|
def on_success
|
|
207
|
-
@events <<
|
|
207
|
+
@events << "success"
|
|
208
208
|
end
|
|
209
209
|
|
|
210
210
|
def on_error(message)
|
|
@@ -228,7 +228,7 @@ module Parsanol
|
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
def on_nil
|
|
231
|
-
@events <<
|
|
231
|
+
@events << "nil"
|
|
232
232
|
end
|
|
233
233
|
|
|
234
234
|
def on_hash_start(size = nil)
|
data/lib/parsanol/cause.rb
CHANGED
|
@@ -113,10 +113,10 @@ module Parsanol
|
|
|
113
113
|
return if prefix_flags.size < 2
|
|
114
114
|
|
|
115
115
|
prefix_flags[1..-2].each do |is_last|
|
|
116
|
-
stream.print is_last ?
|
|
116
|
+
stream.print is_last ? " " : "| "
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
stream.print prefix_flags.last ?
|
|
119
|
+
stream.print prefix_flags.last ? "`- " : "|- "
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
end
|
data/lib/parsanol/context.rb
CHANGED
|
@@ -32,8 +32,8 @@ module Parsanol
|
|
|
32
32
|
#
|
|
33
33
|
# @param name [Symbol, String] method name
|
|
34
34
|
# @yield block to execute when method is called
|
|
35
|
-
def define_singleton_method(name, &
|
|
36
|
-
singleton_class.define_method(name, &
|
|
35
|
+
def define_singleton_method(name, &)
|
|
36
|
+
singleton_class.define_method(name, &)
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -50,7 +50,7 @@ module Parsanol
|
|
|
50
50
|
#
|
|
51
51
|
def err(atom, source, message, children = nil)
|
|
52
52
|
raise NotImplementedError,
|
|
53
|
-
|
|
53
|
+
"Error reporters must implement #err(atom, source, message, children)"
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Report an error at a specific position.
|
|
@@ -66,7 +66,7 @@ module Parsanol
|
|
|
66
66
|
#
|
|
67
67
|
def err_at(atom, source, message, pos, children = nil)
|
|
68
68
|
raise NotImplementedError,
|
|
69
|
-
|
|
69
|
+
"Error reporters must implement #err_at(atom, source, message, pos, children)"
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# Called when an expression successfully parses.
|
|
@@ -95,6 +95,6 @@ module Parsanol
|
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
require
|
|
99
|
-
require
|
|
100
|
-
require
|
|
98
|
+
require "parsanol/error_reporter/tree"
|
|
99
|
+
require "parsanol/error_reporter/deepest"
|
|
100
|
+
require "parsanol/error_reporter/contextual"
|
|
@@ -44,7 +44,7 @@ module Parsanol
|
|
|
44
44
|
|
|
45
45
|
# Alternative: 'a' / 'b'
|
|
46
46
|
rule(:alternatives) do
|
|
47
|
-
(simple >> (spaced(
|
|
47
|
+
(simple >> (spaced("/") >> simple).repeat).as(:alt)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Sequence by concatenation: 'a' 'b'
|
|
@@ -52,15 +52,15 @@ module Parsanol
|
|
|
52
52
|
|
|
53
53
|
# Occurrence modifiers: ?, *, +, {min,max}
|
|
54
54
|
rule(:occurrence) do
|
|
55
|
-
(atom.as(:repetition) >> spaced(
|
|
56
|
-
(atom.as(:repetition) >> spaced(
|
|
55
|
+
(atom.as(:repetition) >> spaced("*").as(:sign)) |
|
|
56
|
+
(atom.as(:repetition) >> spaced("+").as(:sign)) |
|
|
57
57
|
(atom.as(:repetition) >> repetition_spec) |
|
|
58
|
-
(atom.as(:maybe) >> spaced(
|
|
58
|
+
(atom.as(:maybe) >> spaced("?")) |
|
|
59
59
|
atom
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
rule(:atom) do
|
|
63
|
-
(spaced(
|
|
63
|
+
(spaced("(") >> expression.as(:unwrap) >> spaced(")")) |
|
|
64
64
|
dot |
|
|
65
65
|
string |
|
|
66
66
|
char_class
|
|
@@ -68,30 +68,30 @@ module Parsanol
|
|
|
68
68
|
|
|
69
69
|
# Character class: [a-z], [0-9], etc.
|
|
70
70
|
rule(:char_class) do
|
|
71
|
-
(str(
|
|
72
|
-
((str(
|
|
73
|
-
str(
|
|
71
|
+
(str("[") >>
|
|
72
|
+
((str("\\") >> any) | (str("]").absent? >> any)).repeat(1) >>
|
|
73
|
+
str("]")).as(:match) >> space?
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
# Any character: .
|
|
77
|
-
rule(:dot) { spaced(
|
|
77
|
+
rule(:dot) { spaced(".").as(:any) }
|
|
78
78
|
|
|
79
79
|
# String literal: 'hello'
|
|
80
80
|
rule(:string) do
|
|
81
81
|
str("'") >>
|
|
82
|
-
((str(
|
|
82
|
+
((str("\\") >> any) | (str("'").absent? >> any)).repeat.as(:string) >>
|
|
83
83
|
str("'") >> space?
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
# Repetition specification: {1,3}, {2,}, {,5}
|
|
87
87
|
rule(:repetition_spec) do
|
|
88
|
-
spaced(
|
|
89
|
-
integer.maybe.as(:min) >> spaced(
|
|
90
|
-
integer.maybe.as(:max) >> spaced(
|
|
88
|
+
spaced("{") >>
|
|
89
|
+
integer.maybe.as(:min) >> spaced(",") >>
|
|
90
|
+
integer.maybe.as(:max) >> spaced("}")
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
rule(:integer) do
|
|
94
|
-
match[
|
|
94
|
+
match["0-9"].repeat(1)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# Whitespace handling
|
|
@@ -115,7 +115,7 @@ module Parsanol
|
|
|
115
115
|
class Transform < Parsanol::Transform
|
|
116
116
|
# Repetition with sign: * (zero+) or + (one+)
|
|
117
117
|
rule(repetition: simple(:rep), sign: simple(:sign)) do
|
|
118
|
-
min = sign ==
|
|
118
|
+
min = sign == "+" ? 1 : 0
|
|
119
119
|
Parsanol::Atoms::Repetition.new(rep, min, nil)
|
|
120
120
|
end
|
|
121
121
|
|
|
@@ -124,7 +124,7 @@ module Parsanol
|
|
|
124
124
|
Parsanol::Atoms::Repetition.new(
|
|
125
125
|
rep,
|
|
126
126
|
Integer(min || 0),
|
|
127
|
-
(max && Integer(max)) || nil
|
|
127
|
+
(max && Integer(max)) || nil,
|
|
128
128
|
)
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -147,7 +147,7 @@ module Parsanol
|
|
|
147
147
|
rule(match: simple(:m)) { Parsanol::Atoms::Re.new("[#{m}]") }
|
|
148
148
|
|
|
149
149
|
# Any character: .
|
|
150
|
-
rule(any: simple(:_a)) { Parsanol::Atoms::Re.new(
|
|
150
|
+
rule(any: simple(:_a)) { Parsanol::Atoms::Re.new(".") }
|
|
151
151
|
end
|
|
152
152
|
end
|
|
153
153
|
end
|
data/lib/parsanol/expression.rb
CHANGED
data/lib/parsanol/fast_mode.rb
CHANGED
|
@@ -25,7 +25,10 @@ module Parsanol
|
|
|
25
25
|
unless (entry = @cache[beg]&.[](obj.object_id))
|
|
26
26
|
result = obj.try(source, self, consume_all)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
if obj.cached?
|
|
29
|
+
(@cache[beg] ||= {})[obj.object_id] =
|
|
30
|
+
[result, source.bytepos - beg]
|
|
31
|
+
end
|
|
29
32
|
|
|
30
33
|
return result
|
|
31
34
|
end
|
|
@@ -45,13 +48,24 @@ module Parsanol
|
|
|
45
48
|
case parslets.size
|
|
46
49
|
when 1
|
|
47
50
|
success, value = parslets[0].apply(source, context, consume_all)
|
|
48
|
-
success
|
|
51
|
+
if success
|
|
52
|
+
succ([:sequence,
|
|
53
|
+
value])
|
|
54
|
+
else
|
|
55
|
+
context.err(self, source, @error_msg,
|
|
56
|
+
[value])
|
|
57
|
+
end
|
|
49
58
|
when 2
|
|
50
59
|
success, v1 = parslets[0].apply(source, context, false)
|
|
51
60
|
return context.err(self, source, @error_msg, [v1]) unless success
|
|
52
61
|
|
|
53
62
|
success, v2 = parslets[1].apply(source, context, consume_all)
|
|
54
|
-
success
|
|
63
|
+
if success
|
|
64
|
+
succ([:sequence, v1,
|
|
65
|
+
v2])
|
|
66
|
+
else
|
|
67
|
+
context.err(self, source, @error_msg, [v2])
|
|
68
|
+
end
|
|
55
69
|
when 3
|
|
56
70
|
success, v1 = parslets[0].apply(source, context, false)
|
|
57
71
|
return context.err(self, source, @error_msg, [v1]) unless success
|
|
@@ -60,13 +74,19 @@ module Parsanol
|
|
|
60
74
|
return context.err(self, source, @error_msg, [v2]) unless success
|
|
61
75
|
|
|
62
76
|
success, v3 = parslets[2].apply(source, context, consume_all)
|
|
63
|
-
success
|
|
77
|
+
if success
|
|
78
|
+
succ([:sequence, v1, v2,
|
|
79
|
+
v3])
|
|
80
|
+
else
|
|
81
|
+
context.err(self, source, @error_msg, [v3])
|
|
82
|
+
end
|
|
64
83
|
else
|
|
65
84
|
result = [:sequence]
|
|
66
85
|
last_idx = parslets.size - 1
|
|
67
86
|
i = 0
|
|
68
87
|
while i <= last_idx
|
|
69
|
-
success, value = parslets[i].apply(source, context,
|
|
88
|
+
success, value = parslets[i].apply(source, context,
|
|
89
|
+
consume_all && i == last_idx)
|
|
70
90
|
return context.err(self, source, @error_msg, [value]) unless success
|
|
71
91
|
|
|
72
92
|
result << value
|
|
@@ -100,22 +120,37 @@ module Parsanol
|
|
|
100
120
|
case max
|
|
101
121
|
when 1
|
|
102
122
|
success, value = parslet.apply(source, context, consume_all)
|
|
103
|
-
return success ? succ([tag,
|
|
123
|
+
return success ? succ([tag,
|
|
124
|
+
value]) : context.err_at(self, source,
|
|
125
|
+
@error_msg, source.bytepos, [value])
|
|
104
126
|
when 2
|
|
105
127
|
success, v1 = parslet.apply(source, context, false)
|
|
106
|
-
|
|
128
|
+
unless success
|
|
129
|
+
return context.err_at(self, source, @error_msg, source.bytepos,
|
|
130
|
+
[v1])
|
|
131
|
+
end
|
|
107
132
|
|
|
108
133
|
success, v2 = parslet.apply(source, context, consume_all)
|
|
109
|
-
return success ? succ([tag, v1,
|
|
134
|
+
return success ? succ([tag, v1,
|
|
135
|
+
v2]) : context.err_at(self, source,
|
|
136
|
+
@error_msg, source.bytepos, [v2])
|
|
110
137
|
when 3
|
|
111
138
|
success, v1 = parslet.apply(source, context, false)
|
|
112
|
-
|
|
139
|
+
unless success
|
|
140
|
+
return context.err_at(self, source, @error_msg, source.bytepos,
|
|
141
|
+
[v1])
|
|
142
|
+
end
|
|
113
143
|
|
|
114
144
|
success, v2 = parslet.apply(source, context, false)
|
|
115
|
-
|
|
145
|
+
unless success
|
|
146
|
+
return context.err_at(self, source, @error_msg, source.bytepos,
|
|
147
|
+
[v2])
|
|
148
|
+
end
|
|
116
149
|
|
|
117
150
|
success, v3 = parslet.apply(source, context, consume_all)
|
|
118
|
-
return success ? succ([tag, v1, v2,
|
|
151
|
+
return success ? succ([tag, v1, v2,
|
|
152
|
+
v3]) : context.err_at(self, source,
|
|
153
|
+
@error_msg, source.bytepos, [v3])
|
|
119
154
|
end
|
|
120
155
|
end
|
|
121
156
|
|
|
@@ -140,7 +175,10 @@ module Parsanol
|
|
|
140
175
|
return context.err_at(self, source, @error_msg, start_pos, [break_on])
|
|
141
176
|
end
|
|
142
177
|
|
|
143
|
-
|
|
178
|
+
if consume_all && source.chars_left.positive?
|
|
179
|
+
return context.err(self, source, @unconsumed_msg,
|
|
180
|
+
[break_on])
|
|
181
|
+
end
|
|
144
182
|
|
|
145
183
|
succ(result)
|
|
146
184
|
end
|
data/lib/parsanol/first_set.rb
CHANGED
|
@@ -58,7 +58,7 @@ module Parsanol
|
|
|
58
58
|
return true if real_set1.empty? || real_set2.empty?
|
|
59
59
|
|
|
60
60
|
# Check if intersection is empty (using to_a for Opal compatibility)
|
|
61
|
-
|
|
61
|
+
!real_set1.to_a.intersect?(real_set2.to_a)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Check if all FIRST sets in a collection are mutually disjoint
|
|
@@ -72,7 +72,8 @@ module Parsanol
|
|
|
72
72
|
when Hash
|
|
73
73
|
grammar
|
|
74
74
|
else
|
|
75
|
-
raise ArgumentError,
|
|
75
|
+
raise ArgumentError,
|
|
76
|
+
"Expected GrammarBuilder or Hash, got #{grammar.class}"
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
@imports << { grammar: grammar_data, prefix: prefix }
|
|
@@ -92,7 +93,8 @@ module Parsanol
|
|
|
92
93
|
when Hash
|
|
93
94
|
grammar
|
|
94
95
|
else
|
|
95
|
-
raise ArgumentError,
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"Expected GrammarBuilder or Hash, got #{grammar.class}"
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
@imports << { grammar: grammar_data, prefix: prefix, rules: rules }
|
|
@@ -106,7 +108,7 @@ module Parsanol
|
|
|
106
108
|
{
|
|
107
109
|
rules: @rules,
|
|
108
110
|
root: @root,
|
|
109
|
-
imports: @imports
|
|
111
|
+
imports: @imports,
|
|
110
112
|
}
|
|
111
113
|
end
|
|
112
114
|
|
|
@@ -140,7 +142,7 @@ module Parsanol
|
|
|
140
142
|
if grammar_name
|
|
141
143
|
ref("#{grammar_name}:root")
|
|
142
144
|
else
|
|
143
|
-
ref(
|
|
145
|
+
ref("root")
|
|
144
146
|
end
|
|
145
147
|
end
|
|
146
148
|
|
|
@@ -149,9 +151,9 @@ module Parsanol
|
|
|
149
151
|
#
|
|
150
152
|
# @yield [GrammarBuilder] Builder to configure
|
|
151
153
|
# @return [Hash] Built grammar
|
|
152
|
-
def build(&
|
|
154
|
+
def build(&)
|
|
153
155
|
builder = new
|
|
154
|
-
builder.instance_eval(&
|
|
156
|
+
builder.instance_eval(&)
|
|
155
157
|
builder.build
|
|
156
158
|
end
|
|
157
159
|
|
|
@@ -170,8 +172,8 @@ module Parsanol
|
|
|
170
172
|
# Create a new grammar builder
|
|
171
173
|
#
|
|
172
174
|
# @return [GrammarBuilder] New builder
|
|
173
|
-
def grammar(&
|
|
174
|
-
GrammarBuilder.build(&
|
|
175
|
+
def grammar(&)
|
|
176
|
+
GrammarBuilder.build(&)
|
|
175
177
|
end
|
|
176
178
|
end
|
|
177
179
|
end
|
|
@@ -19,7 +19,7 @@ module Parsanol
|
|
|
19
19
|
class Edit
|
|
20
20
|
attr_reader :start, :deleted, :inserted
|
|
21
21
|
|
|
22
|
-
def initialize(start:, deleted:, inserted:
|
|
22
|
+
def initialize(start:, deleted:, inserted: "")
|
|
23
23
|
@start = start
|
|
24
24
|
@deleted = deleted
|
|
25
25
|
@inserted = inserted
|
|
@@ -61,13 +61,14 @@ module Parsanol
|
|
|
61
61
|
#
|
|
62
62
|
# @param grammar [Parsanol::Parser, Parsanol::Atoms::Base] Grammar to use
|
|
63
63
|
# @param initial_input [String] Initial input string
|
|
64
|
-
def initialize(grammar, initial_input =
|
|
64
|
+
def initialize(grammar, initial_input = "")
|
|
65
65
|
@grammar = grammar
|
|
66
66
|
@input = initial_input
|
|
67
67
|
|
|
68
68
|
if Parsanol::Native.available?
|
|
69
69
|
grammar_json = Parsanol::Native.serialize_grammar(grammar.root)
|
|
70
|
-
@native_parser = Parsanol::Native.incremental_parser_new(grammar_json,
|
|
70
|
+
@native_parser = Parsanol::Native.incremental_parser_new(grammar_json,
|
|
71
|
+
initial_input)
|
|
71
72
|
else
|
|
72
73
|
@native_parser = nil
|
|
73
74
|
end
|
|
@@ -81,7 +82,7 @@ module Parsanol
|
|
|
81
82
|
# @param start [Integer] Start position of edit
|
|
82
83
|
# @param deleted [Integer] Number of characters deleted
|
|
83
84
|
# @param inserted [String] Text to insert
|
|
84
|
-
def apply_edit(start:, deleted:, inserted:
|
|
85
|
+
def apply_edit(start:, deleted:, inserted: "")
|
|
85
86
|
edit = Edit.new(start: start, deleted: deleted, inserted: inserted)
|
|
86
87
|
@edits << edit
|
|
87
88
|
|
|
@@ -93,7 +94,8 @@ module Parsanol
|
|
|
93
94
|
|
|
94
95
|
return unless @native_parser
|
|
95
96
|
|
|
96
|
-
Parsanol::Native.incremental_parser_apply_edit(@native_parser, start,
|
|
97
|
+
Parsanol::Native.incremental_parser_apply_edit(@native_parser, start,
|
|
98
|
+
deleted, inserted)
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
# Convenience method to apply multiple edits
|
|
@@ -119,7 +121,9 @@ module Parsanol
|
|
|
119
121
|
return @cached_result if @cached_result
|
|
120
122
|
|
|
121
123
|
if @native_parser
|
|
122
|
-
@cached_result = Parsanol::Native.incremental_parser_reparse(
|
|
124
|
+
@cached_result = Parsanol::Native.incremental_parser_reparse(
|
|
125
|
+
@native_parser, @input
|
|
126
|
+
)
|
|
123
127
|
else
|
|
124
128
|
# Pure Ruby fallback - reparse from scratch
|
|
125
129
|
root = @grammar.root
|
|
@@ -164,14 +168,15 @@ module Parsanol
|
|
|
164
168
|
#
|
|
165
169
|
# @param new_input [String, nil] Optional new initial input
|
|
166
170
|
def reset(new_input = nil)
|
|
167
|
-
@input = new_input ||
|
|
171
|
+
@input = new_input || ""
|
|
168
172
|
@edits.clear
|
|
169
173
|
@cached_result = nil
|
|
170
174
|
|
|
171
175
|
return unless @native_parser && new_input
|
|
172
176
|
|
|
173
177
|
grammar_json = Parsanol::Native.serialize_grammar(@grammar.root)
|
|
174
|
-
@native_parser = Parsanol::Native.incremental_parser_new(grammar_json,
|
|
178
|
+
@native_parser = Parsanol::Native.incremental_parser_new(grammar_json,
|
|
179
|
+
@input)
|
|
175
180
|
end
|
|
176
181
|
end
|
|
177
182
|
end
|
|
@@ -132,7 +132,10 @@ module Parsanol
|
|
|
132
132
|
|
|
133
133
|
# Check right subtree
|
|
134
134
|
# Only search right if intervals starting there could overlap
|
|
135
|
-
|
|
135
|
+
if node.right && node.low < high
|
|
136
|
+
query_recursive(node.right, low, high,
|
|
137
|
+
results)
|
|
138
|
+
end
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
# Find exact interval match
|
|
@@ -154,10 +157,16 @@ module Parsanol
|
|
|
154
157
|
return nil if node.nil?
|
|
155
158
|
|
|
156
159
|
# Recursively delete from left subtree
|
|
157
|
-
|
|
160
|
+
if node.left
|
|
161
|
+
node.left = delete_overlapping_recursive(node.left, low, high,
|
|
162
|
+
deleted)
|
|
163
|
+
end
|
|
158
164
|
|
|
159
165
|
# Recursively delete from right subtree
|
|
160
|
-
|
|
166
|
+
if node.right
|
|
167
|
+
node.right = delete_overlapping_recursive(node.right, low, high,
|
|
168
|
+
deleted)
|
|
169
|
+
end
|
|
161
170
|
|
|
162
171
|
# Check if current node overlaps
|
|
163
172
|
if node.low < high && low < node.high
|
data/lib/parsanol/lazy_result.rb
CHANGED
|
@@ -85,10 +85,10 @@ module Parsanol
|
|
|
85
85
|
# @yield [element] Each element
|
|
86
86
|
# @return [Enumerator, self] Enumerator if no block, self otherwise
|
|
87
87
|
#
|
|
88
|
-
def each(&
|
|
88
|
+
def each(&)
|
|
89
89
|
return to_enum(:each) unless block_given?
|
|
90
90
|
|
|
91
|
-
to_a.each(&
|
|
91
|
+
to_a.each(&)
|
|
92
92
|
self
|
|
93
93
|
end
|
|
94
94
|
|
data/lib/parsanol/mermaid.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Parsanol
|
|
|
16
16
|
# Generates Mermaid diagram syntax from parser atoms.
|
|
17
17
|
class MermaidBuilder
|
|
18
18
|
def initialize
|
|
19
|
-
@lines = [
|
|
19
|
+
@lines = ["graph TD"]
|
|
20
20
|
@node_counter = 0
|
|
21
21
|
@connections = []
|
|
22
22
|
@seen_rules = Set.new
|
|
@@ -24,8 +24,8 @@ module Parsanol
|
|
|
24
24
|
|
|
25
25
|
# Entry point for parser visualization
|
|
26
26
|
def visit_parser(root_atom)
|
|
27
|
-
add_node(
|
|
28
|
-
traverse(root_atom,
|
|
27
|
+
add_node("Parser", "root")
|
|
28
|
+
traverse(root_atom, "Parser")
|
|
29
29
|
finalize
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -35,7 +35,7 @@ module Parsanol
|
|
|
35
35
|
|
|
36
36
|
@seen_rules << rule_name
|
|
37
37
|
|
|
38
|
-
node_id = add_node(rule_name.to_s.upcase,
|
|
38
|
+
node_id = add_node(rule_name.to_s.upcase, "rule")
|
|
39
39
|
connect(current_parent, node_id)
|
|
40
40
|
traverse(rule_block.call, node_id)
|
|
41
41
|
end
|
|
@@ -67,18 +67,18 @@ module Parsanol
|
|
|
67
67
|
|
|
68
68
|
# Leaf nodes
|
|
69
69
|
def visit_re(regexp)
|
|
70
|
-
add_node("match(#{regexp.inspect})",
|
|
70
|
+
add_node("match(#{regexp.inspect})", "terminal", style: "ellipse")
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def visit_str(string)
|
|
74
|
-
add_node("'#{string}'",
|
|
74
|
+
add_node("'#{string}'", "terminal", style: "ellipse")
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
private
|
|
78
78
|
|
|
79
79
|
attr_reader :current_parent
|
|
80
80
|
|
|
81
|
-
def add_node(label, _shape_type =
|
|
81
|
+
def add_node(label, _shape_type = "rect", _style = nil)
|
|
82
82
|
@node_counter += 1
|
|
83
83
|
node_id = "node_#{@node_counter}"
|
|
84
84
|
@lines << " #{node_id}[\"#{escape_mermaid(label)}\"]"
|
|
@@ -97,7 +97,7 @@ module Parsanol
|
|
|
97
97
|
@connections.each do |from, to|
|
|
98
98
|
@lines << " #{from} --> #{to}"
|
|
99
99
|
end
|
|
100
|
-
@lines <<
|
|
100
|
+
@lines << ""
|
|
101
101
|
@lines.join("\n")
|
|
102
102
|
end
|
|
103
103
|
|
|
@@ -125,7 +125,10 @@ module Parsanol
|
|
|
125
125
|
def mermaid_for_rule(rule_name)
|
|
126
126
|
builder = MermaidBuilder.new
|
|
127
127
|
rule_method = method(rule_name)
|
|
128
|
-
|
|
128
|
+
unless rule_method
|
|
129
|
+
raise NotImplementedError,
|
|
130
|
+
"Rule '#{rule_name}' not found"
|
|
131
|
+
end
|
|
129
132
|
|
|
130
133
|
rule_method.call.accept(builder)
|
|
131
134
|
builder.output
|