pestle 0.1.0
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.rubocop.yml +59 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/LICENSE_PEST.txt +23 -0
- data/README.md +124 -0
- data/Rakefile +23 -0
- data/Steepfile +19 -0
- data/benchmarks/jsonpath_ips.rb +33 -0
- data/examples/calculator_pratt.rb +157 -0
- data/examples/calculator_prec_climber.rb +225 -0
- data/examples/calculator_stack_vm.rb +291 -0
- data/examples/csv.rb +73 -0
- data/examples/ini.rb +90 -0
- data/examples/json_example.rb +141 -0
- data/examples/jsonpath/README.md +3 -0
- data/examples/jsonpath/jsonpath.pest +182 -0
- data/examples/jsonpath/lib/jsonpath/ast.rb +362 -0
- data/examples/jsonpath/lib/jsonpath/function_extensions.rb +201 -0
- data/examples/jsonpath/lib/jsonpath/node.rb +20 -0
- data/examples/jsonpath/lib/jsonpath/query.rb +25 -0
- data/examples/jsonpath/lib/jsonpath.rb +453 -0
- data/lib/pestle/errors.rb +98 -0
- data/lib/pestle/grammar/builtin_rules/ascii.rb +38 -0
- data/lib/pestle/grammar/builtin_rules/special.rb +63 -0
- data/lib/pestle/grammar/builtin_rules/unicode.rb +291 -0
- data/lib/pestle/grammar/errors.rb +62 -0
- data/lib/pestle/grammar/expression.rb +90 -0
- data/lib/pestle/grammar/expressions/choice.rb +36 -0
- data/lib/pestle/grammar/expressions/group.rb +27 -0
- data/lib/pestle/grammar/expressions/identifier.rb +26 -0
- data/lib/pestle/grammar/expressions/postfix.rb +272 -0
- data/lib/pestle/grammar/expressions/prefix.rb +51 -0
- data/lib/pestle/grammar/expressions/range.rb +26 -0
- data/lib/pestle/grammar/expressions/sequence.rb +38 -0
- data/lib/pestle/grammar/expressions/stack.rb +192 -0
- data/lib/pestle/grammar/expressions/string.rb +46 -0
- data/lib/pestle/grammar/lexer.rb +464 -0
- data/lib/pestle/grammar/parser.rb +340 -0
- data/lib/pestle/grammar/rule.rb +98 -0
- data/lib/pestle/pair.rb +325 -0
- data/lib/pestle/parser.rb +48 -0
- data/lib/pestle/pratt.rb +74 -0
- data/lib/pestle/state.rb +220 -0
- data/lib/pestle/version.rb +5 -0
- data/lib/pestle.rb +24 -0
- data/sig/errors.rbs +22 -0
- data/sig/grammar/ascii.rbs +9 -0
- data/sig/grammar/choice.rbs +14 -0
- data/sig/grammar/errors.rbs +22 -0
- data/sig/grammar/expression.rbs +39 -0
- data/sig/grammar/group.rbs +14 -0
- data/sig/grammar/identifier.rbs +11 -0
- data/sig/grammar/lexer.rbs +85 -0
- data/sig/grammar/parser.rbs +57 -0
- data/sig/grammar/postfix.rbs +112 -0
- data/sig/grammar/prefix.rbs +27 -0
- data/sig/grammar/range.rbs +20 -0
- data/sig/grammar/rule.rbs +40 -0
- data/sig/grammar/sequence.rbs +14 -0
- data/sig/grammar/special.rbs +39 -0
- data/sig/grammar/stack.rbs +57 -0
- data/sig/grammar/string.rbs +27 -0
- data/sig/grammar/unicode.rbs +15 -0
- data/sig/pair.rbs +168 -0
- data/sig/parser.rbs +16 -0
- data/sig/pestle.rbs +5 -0
- data/sig/pratt.rbs +27 -0
- data/sig/state.rbs +95 -0
- data/sig/stdlib/strscan.rbs +3 -0
- data.tar.gz.sig +0 -0
- metadata +141 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# A terminal pointing to a rule, either a grammar rule or a built-in rule.
|
|
5
|
+
class Identifier < Terminal
|
|
6
|
+
attr_reader :value
|
|
7
|
+
|
|
8
|
+
def initialize(value, tag: nil)
|
|
9
|
+
super(tag: tag)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
@value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(state, pairs)
|
|
18
|
+
unless @tag.nil?
|
|
19
|
+
state.with_tag(@tag || raise) do
|
|
20
|
+
return state.rules[@value].parse(state, pairs)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
state.rules[@value].parse(state, pairs)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# An optional expression `<expression>?`.
|
|
5
|
+
class Optional < Expression
|
|
6
|
+
attr_reader :expression
|
|
7
|
+
|
|
8
|
+
def initialize(expression)
|
|
9
|
+
super(tag: nil)
|
|
10
|
+
@expression = expression
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"#{@expression}?"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(state, pairs)
|
|
18
|
+
children = [] # : Array[Pestle::Pair]
|
|
19
|
+
pairs.concat(children) if @expression.parse(state, children)
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def children = [@expression]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# An expression repeated zero or more times `<expression>*`.
|
|
27
|
+
class Repeat < Expression
|
|
28
|
+
attr_reader :expression
|
|
29
|
+
|
|
30
|
+
def initialize(expression)
|
|
31
|
+
super(tag: nil)
|
|
32
|
+
@expression = expression
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
"#{@expression}*"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse(state, pairs)
|
|
40
|
+
children = [] # : Array[Pestle::Pair]
|
|
41
|
+
loop do
|
|
42
|
+
state.checkpoint
|
|
43
|
+
if @expression.parse(state, children)
|
|
44
|
+
state.ok
|
|
45
|
+
pairs.concat(children)
|
|
46
|
+
children.clear
|
|
47
|
+
state.parse_trivia(children)
|
|
48
|
+
else
|
|
49
|
+
state.restore
|
|
50
|
+
break
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
true # Always succeed
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def children = [@expression]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# An expression repeated one or more times `<expression>+`.
|
|
60
|
+
class RepeatOnce < Expression
|
|
61
|
+
attr_reader :expression
|
|
62
|
+
|
|
63
|
+
def initialize(expression)
|
|
64
|
+
super(tag: nil)
|
|
65
|
+
@expression = expression
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
"#{@expression}+"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse(state, pairs)
|
|
73
|
+
children = [] # : Array[Pestle::Pair]
|
|
74
|
+
|
|
75
|
+
state.checkpoint
|
|
76
|
+
if @expression.parse(state, children)
|
|
77
|
+
state.ok
|
|
78
|
+
pairs.concat(children)
|
|
79
|
+
children.clear
|
|
80
|
+
state.parse_trivia(children)
|
|
81
|
+
else
|
|
82
|
+
state.restore
|
|
83
|
+
return false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
loop do
|
|
87
|
+
state.checkpoint
|
|
88
|
+
if @expression.parse(state, children)
|
|
89
|
+
state.ok
|
|
90
|
+
pairs.concat(children)
|
|
91
|
+
children.clear
|
|
92
|
+
state.parse_trivia(children)
|
|
93
|
+
else
|
|
94
|
+
state.restore
|
|
95
|
+
break
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def children = [@expression]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# An expression repeated a specified number of times `<expression>{n}`.
|
|
106
|
+
class RepeatExact < Expression
|
|
107
|
+
attr_reader :expression, :number
|
|
108
|
+
|
|
109
|
+
def initialize(expression, number)
|
|
110
|
+
super(tag: nil)
|
|
111
|
+
@expression = expression
|
|
112
|
+
@number = number
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_s
|
|
116
|
+
"#{@expression}{#{@number}}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse(state, pairs)
|
|
120
|
+
return true if @number.zero?
|
|
121
|
+
|
|
122
|
+
children = [] # : Array[Pestle::Pair]
|
|
123
|
+
count = 0
|
|
124
|
+
|
|
125
|
+
state.checkpoint
|
|
126
|
+
|
|
127
|
+
loop do
|
|
128
|
+
state.parse_trivia(children)
|
|
129
|
+
break unless @expression.parse(state, children)
|
|
130
|
+
|
|
131
|
+
count += 1
|
|
132
|
+
|
|
133
|
+
break if count == @number
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if count == @number
|
|
137
|
+
pairs.concat(children)
|
|
138
|
+
state.ok
|
|
139
|
+
true
|
|
140
|
+
else
|
|
141
|
+
state.restore
|
|
142
|
+
false
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def children = [@expression]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# An expression repeated at least specified number of times `<expression>{n,}`.
|
|
150
|
+
class RepeatMin < Expression
|
|
151
|
+
attr_reader :expression, :number
|
|
152
|
+
|
|
153
|
+
def initialize(expression, number)
|
|
154
|
+
super(tag: nil)
|
|
155
|
+
@expression = expression
|
|
156
|
+
@number = number
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def to_s
|
|
160
|
+
"#{@expression}{#{@number},}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def parse(state, pairs)
|
|
164
|
+
return true if @number.zero?
|
|
165
|
+
|
|
166
|
+
children = [] # : Array[Pestle::Pair]
|
|
167
|
+
count = 0
|
|
168
|
+
|
|
169
|
+
state.checkpoint
|
|
170
|
+
|
|
171
|
+
loop do
|
|
172
|
+
state.parse_trivia(children)
|
|
173
|
+
break unless @expression.parse(state, children)
|
|
174
|
+
|
|
175
|
+
count += 1
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if count >= @number
|
|
179
|
+
pairs.concat(children)
|
|
180
|
+
state.ok
|
|
181
|
+
true
|
|
182
|
+
else
|
|
183
|
+
state.restore
|
|
184
|
+
false
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def children = [@expression]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# An expression repeated at most specified number of times `<expression>{,n}`.
|
|
192
|
+
class RepeatMax < Expression
|
|
193
|
+
attr_reader :expression, :number
|
|
194
|
+
|
|
195
|
+
def initialize(expression, number)
|
|
196
|
+
super(tag: nil)
|
|
197
|
+
@expression = expression
|
|
198
|
+
@number = number
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def to_s
|
|
202
|
+
"#{@expression}{,#{@number}}"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def parse(state, pairs)
|
|
206
|
+
return true if @number.zero?
|
|
207
|
+
|
|
208
|
+
children = [] # : Array[Pestle::Pair]
|
|
209
|
+
count = 0
|
|
210
|
+
|
|
211
|
+
state.checkpoint
|
|
212
|
+
|
|
213
|
+
loop do
|
|
214
|
+
state.parse_trivia(children)
|
|
215
|
+
break unless @expression.parse(state, children)
|
|
216
|
+
|
|
217
|
+
count += 1
|
|
218
|
+
|
|
219
|
+
break if count == @number
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
pairs.concat(children)
|
|
223
|
+
state.ok
|
|
224
|
+
true
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def children = [@expression]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# An expression repeated a specified range of times `<expression>{n,m}`.
|
|
231
|
+
class RepeatMinMax < Expression
|
|
232
|
+
attr_reader :expression, :min, :max
|
|
233
|
+
|
|
234
|
+
def initialize(expression, min, max)
|
|
235
|
+
super(tag: nil)
|
|
236
|
+
@expression = expression
|
|
237
|
+
@min = min
|
|
238
|
+
@max = max
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def to_s
|
|
242
|
+
"#{@expression}{#{@min},#{@max}}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def parse(state, pairs)
|
|
246
|
+
children = [] # : Array[Pestle::Pair]
|
|
247
|
+
count = 0
|
|
248
|
+
|
|
249
|
+
state.checkpoint
|
|
250
|
+
|
|
251
|
+
loop do
|
|
252
|
+
state.parse_trivia(children)
|
|
253
|
+
break unless @expression.parse(state, children)
|
|
254
|
+
|
|
255
|
+
count += 1
|
|
256
|
+
|
|
257
|
+
break if count == @max
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
if count.between?(@min, @max)
|
|
261
|
+
pairs.concat(children)
|
|
262
|
+
state.ok
|
|
263
|
+
true
|
|
264
|
+
else
|
|
265
|
+
state.restore
|
|
266
|
+
false
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def children = [@expression]
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# A positive predicate expression `&<expression>`.
|
|
5
|
+
class PositivePredicate < Expression
|
|
6
|
+
attr_reader :expression
|
|
7
|
+
|
|
8
|
+
def initialize(expression, tag: nil)
|
|
9
|
+
super(tag: tag)
|
|
10
|
+
@expression = expression
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"#{tag_s}&#{@expression}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
18
|
+
state.checkpoint
|
|
19
|
+
matched = @expression.parse(state, [])
|
|
20
|
+
state.restore
|
|
21
|
+
matched
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def children = [@expression]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A negative predicate expression `!<expression>`.
|
|
28
|
+
class NegativePredicate < Expression
|
|
29
|
+
attr_reader :expression
|
|
30
|
+
|
|
31
|
+
def initialize(expression, tag: nil)
|
|
32
|
+
super(tag: tag)
|
|
33
|
+
@expression = expression
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
"#{tag_s}!#{@expression}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
41
|
+
state.neg_pred_stack << state.scanner.pos
|
|
42
|
+
state.checkpoint
|
|
43
|
+
matched = @expression.parse(state, [])
|
|
44
|
+
state.restore
|
|
45
|
+
state.neg_pred_stack.pop
|
|
46
|
+
!matched
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def children = [@expression]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# An expression matching a single character from a continuous range.
|
|
5
|
+
class Range < Terminal
|
|
6
|
+
attr_reader :start, :stop
|
|
7
|
+
|
|
8
|
+
def initialize(start, stop, tag: nil)
|
|
9
|
+
super(tag: tag)
|
|
10
|
+
@start = start
|
|
11
|
+
@stop = stop
|
|
12
|
+
@re = /[#{Regexp.escape(start)}-#{Regexp.escape(stop)}]/
|
|
13
|
+
@s = to_s
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
"#{tag_s}['#{@start}'..'#{@stop}']"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
21
|
+
matched = !state.scanner.scan(@re).nil?
|
|
22
|
+
state.track(@s, matched)
|
|
23
|
+
matched
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# The sequence operator `~`.
|
|
5
|
+
class Sequence < Expression
|
|
6
|
+
attr_reader :expressions
|
|
7
|
+
|
|
8
|
+
def initialize(*expressions)
|
|
9
|
+
super(tag: nil)
|
|
10
|
+
@expressions = expressions
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
seq = @expressions.map(&:to_s).join(" ~ ")
|
|
15
|
+
"#{tag_s}#{seq}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parse(state, pairs)
|
|
19
|
+
children = [] # : Array[Pestle::Pair]
|
|
20
|
+
state.checkpoint
|
|
21
|
+
|
|
22
|
+
@expressions.each_with_index do |expr, i|
|
|
23
|
+
unless expr.parse(state, children)
|
|
24
|
+
state.restore
|
|
25
|
+
return false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
state.parse_trivia(children) if i < @expressions.length - 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
pairs.concat(children)
|
|
32
|
+
state.ok
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def children = @expressions
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# A PUSH terminal with a string literal argument.
|
|
5
|
+
class PushLiteral < Terminal
|
|
6
|
+
attr_reader :value
|
|
7
|
+
|
|
8
|
+
def initialize(value, tag: nil)
|
|
9
|
+
super(tag: tag)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"#{tag_s}PUSH(\"#{value}\")"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
18
|
+
state.stack_push(@value)
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A PUSH terminal with an expression.
|
|
24
|
+
class Push < Expression
|
|
25
|
+
attr_reader :expression
|
|
26
|
+
|
|
27
|
+
def initialize(expression, tag: nil)
|
|
28
|
+
super(tag: tag)
|
|
29
|
+
@expression = expression
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
"#{tag_s}PUSH( #{@expression} )"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse(state, pairs)
|
|
37
|
+
start_byte_pos = state.scanner.pos
|
|
38
|
+
children = [] # : Array[Pestle::Pair]
|
|
39
|
+
|
|
40
|
+
if @expression.parse(state, children)
|
|
41
|
+
pairs.concat(children)
|
|
42
|
+
state.stack_push(state.text.byteslice(start_byte_pos...state.scanner.pos) || raise)
|
|
43
|
+
true
|
|
44
|
+
else
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def children = [@expression]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A PEEK terminal with a range expression.
|
|
53
|
+
# Matches the range from the bottom of the stack to the top.
|
|
54
|
+
class PeekSlice < Terminal
|
|
55
|
+
attr_reader :start, :stop
|
|
56
|
+
|
|
57
|
+
def initialize(start, stop, tag: nil)
|
|
58
|
+
super(tag: tag)
|
|
59
|
+
@start = start
|
|
60
|
+
@stop = stop
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_s
|
|
64
|
+
"#{tag_s}PEEK[#{@start}..#{@stop}]"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
68
|
+
start_pos = state.scanner.pos
|
|
69
|
+
state.stack_peek_slice(@start, @stop).each do |s|
|
|
70
|
+
matched = !state.scanner.scan(s).nil? # steep:ignore ArgumentTypeMismatch
|
|
71
|
+
state.track(s, matched)
|
|
72
|
+
|
|
73
|
+
next if matched
|
|
74
|
+
|
|
75
|
+
state.scanner.pos = start_pos
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# A PEEK terminal looking at the top of the stack.
|
|
84
|
+
class Peek < Terminal
|
|
85
|
+
def to_s
|
|
86
|
+
"#{tag_s}PEEK"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
90
|
+
peeked = state.stack_peek
|
|
91
|
+
state.track("PEEK", !peeked.nil?)
|
|
92
|
+
|
|
93
|
+
return false if peeked.nil?
|
|
94
|
+
|
|
95
|
+
matched = !state.scanner.scan(peeked).nil? # steep:ignore ArgumentTypeMismatch
|
|
96
|
+
state.track(peeked, matched)
|
|
97
|
+
matched
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# A PEEK_ALL terminal matching the entire stack, top to bottom.
|
|
102
|
+
class PeekAll < Terminal
|
|
103
|
+
def to_s
|
|
104
|
+
"#{tag_s}PEEK_ALL"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse(state, pairs)
|
|
108
|
+
start_pos = state.scanner.pos
|
|
109
|
+
children = [] # : Array[Pestle::Pair]
|
|
110
|
+
|
|
111
|
+
state.user_stack.reverse_each.with_index do |s, i|
|
|
112
|
+
matched = !state.scanner.scan(s).nil? # steep:ignore ArgumentTypeMismatch
|
|
113
|
+
state.track(s, matched)
|
|
114
|
+
|
|
115
|
+
unless matched
|
|
116
|
+
state.scanner.pos = start_pos
|
|
117
|
+
return false
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
state.parse_trivia(children) if i < state.user_stack.length
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
pairs.concat(children)
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# A POP terminal popping off the top of the stack.
|
|
129
|
+
class Pop < Terminal
|
|
130
|
+
def to_s
|
|
131
|
+
"#{tag_s}POP"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
135
|
+
peeked = state.stack_peek
|
|
136
|
+
state.track("POP", !peeked.nil?)
|
|
137
|
+
|
|
138
|
+
return false if peeked.nil?
|
|
139
|
+
|
|
140
|
+
matched = !state.scanner.scan(peeked).nil? # steep:ignore ArgumentTypeMismatch
|
|
141
|
+
state.track(peeked, matched)
|
|
142
|
+
|
|
143
|
+
if matched
|
|
144
|
+
state.stack_pop
|
|
145
|
+
true
|
|
146
|
+
else
|
|
147
|
+
false
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# A POP_ALL terminal matching the entire stack, top to bottom.
|
|
153
|
+
class PopAll < Terminal
|
|
154
|
+
def to_s
|
|
155
|
+
"#{tag_s}POP_ALL"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def parse(state, pairs)
|
|
159
|
+
start_pos = state.scanner.pos
|
|
160
|
+
children = [] # : Array[Pestle::Pair]
|
|
161
|
+
|
|
162
|
+
state.user_stack.reverse_each.with_index do |s, i|
|
|
163
|
+
matched = !state.scanner.scan(s).nil? # steep:ignore ArgumentTypeMismatch
|
|
164
|
+
state.track(s, matched)
|
|
165
|
+
|
|
166
|
+
unless matched
|
|
167
|
+
state.scanner.pos = start_pos
|
|
168
|
+
return false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
state.parse_trivia(children) if i < state.user_stack.length
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
state.stack_clear
|
|
175
|
+
pairs.concat(children)
|
|
176
|
+
true
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# A DROP terminal that matches if the stack is not empty.
|
|
181
|
+
class Drop < Terminal
|
|
182
|
+
def to_s
|
|
183
|
+
"#{tag_s}DROP"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
187
|
+
matched = !state.stack_pop.nil?
|
|
188
|
+
state.track("DROP", matched)
|
|
189
|
+
matched
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pestle::Grammar
|
|
4
|
+
# An expression matching a string literal.
|
|
5
|
+
class StringLiteral < Terminal
|
|
6
|
+
attr_reader :value
|
|
7
|
+
|
|
8
|
+
def initialize(value, tag: nil)
|
|
9
|
+
super(tag: tag)
|
|
10
|
+
@value = value
|
|
11
|
+
@re = /#{Regexp.escape(value)}/
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
@value.inspect
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
19
|
+
matched = !state.scanner.scan(@re).nil?
|
|
20
|
+
state.track(@value, matched)
|
|
21
|
+
matched
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# An expression matching a string literal case insensitively.
|
|
26
|
+
class InsensitiveString < Terminal
|
|
27
|
+
attr_reader :value
|
|
28
|
+
|
|
29
|
+
def initialize(value, tag: nil)
|
|
30
|
+
super(tag: tag)
|
|
31
|
+
@value = value
|
|
32
|
+
@re = /#{Regexp.escape(value)}/i
|
|
33
|
+
@s = to_s
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
"^#{@value.inspect}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse(state, pairs) # rubocop: disable Lint/UnusedMethodArgument
|
|
41
|
+
matched = !state.scanner.scan(@re).nil?
|
|
42
|
+
state.track(@s, matched)
|
|
43
|
+
matched
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|