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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +59 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +3 -0
  6. data/LICENSE.txt +21 -0
  7. data/LICENSE_PEST.txt +23 -0
  8. data/README.md +124 -0
  9. data/Rakefile +23 -0
  10. data/Steepfile +19 -0
  11. data/benchmarks/jsonpath_ips.rb +33 -0
  12. data/examples/calculator_pratt.rb +157 -0
  13. data/examples/calculator_prec_climber.rb +225 -0
  14. data/examples/calculator_stack_vm.rb +291 -0
  15. data/examples/csv.rb +73 -0
  16. data/examples/ini.rb +90 -0
  17. data/examples/json_example.rb +141 -0
  18. data/examples/jsonpath/README.md +3 -0
  19. data/examples/jsonpath/jsonpath.pest +182 -0
  20. data/examples/jsonpath/lib/jsonpath/ast.rb +362 -0
  21. data/examples/jsonpath/lib/jsonpath/function_extensions.rb +201 -0
  22. data/examples/jsonpath/lib/jsonpath/node.rb +20 -0
  23. data/examples/jsonpath/lib/jsonpath/query.rb +25 -0
  24. data/examples/jsonpath/lib/jsonpath.rb +453 -0
  25. data/lib/pestle/errors.rb +98 -0
  26. data/lib/pestle/grammar/builtin_rules/ascii.rb +38 -0
  27. data/lib/pestle/grammar/builtin_rules/special.rb +63 -0
  28. data/lib/pestle/grammar/builtin_rules/unicode.rb +291 -0
  29. data/lib/pestle/grammar/errors.rb +62 -0
  30. data/lib/pestle/grammar/expression.rb +90 -0
  31. data/lib/pestle/grammar/expressions/choice.rb +36 -0
  32. data/lib/pestle/grammar/expressions/group.rb +27 -0
  33. data/lib/pestle/grammar/expressions/identifier.rb +26 -0
  34. data/lib/pestle/grammar/expressions/postfix.rb +272 -0
  35. data/lib/pestle/grammar/expressions/prefix.rb +51 -0
  36. data/lib/pestle/grammar/expressions/range.rb +26 -0
  37. data/lib/pestle/grammar/expressions/sequence.rb +38 -0
  38. data/lib/pestle/grammar/expressions/stack.rb +192 -0
  39. data/lib/pestle/grammar/expressions/string.rb +46 -0
  40. data/lib/pestle/grammar/lexer.rb +464 -0
  41. data/lib/pestle/grammar/parser.rb +340 -0
  42. data/lib/pestle/grammar/rule.rb +98 -0
  43. data/lib/pestle/pair.rb +325 -0
  44. data/lib/pestle/parser.rb +48 -0
  45. data/lib/pestle/pratt.rb +74 -0
  46. data/lib/pestle/state.rb +220 -0
  47. data/lib/pestle/version.rb +5 -0
  48. data/lib/pestle.rb +24 -0
  49. data/sig/errors.rbs +22 -0
  50. data/sig/grammar/ascii.rbs +9 -0
  51. data/sig/grammar/choice.rbs +14 -0
  52. data/sig/grammar/errors.rbs +22 -0
  53. data/sig/grammar/expression.rbs +39 -0
  54. data/sig/grammar/group.rbs +14 -0
  55. data/sig/grammar/identifier.rbs +11 -0
  56. data/sig/grammar/lexer.rbs +85 -0
  57. data/sig/grammar/parser.rbs +57 -0
  58. data/sig/grammar/postfix.rbs +112 -0
  59. data/sig/grammar/prefix.rbs +27 -0
  60. data/sig/grammar/range.rbs +20 -0
  61. data/sig/grammar/rule.rbs +40 -0
  62. data/sig/grammar/sequence.rbs +14 -0
  63. data/sig/grammar/special.rbs +39 -0
  64. data/sig/grammar/stack.rbs +57 -0
  65. data/sig/grammar/string.rbs +27 -0
  66. data/sig/grammar/unicode.rbs +15 -0
  67. data/sig/pair.rbs +168 -0
  68. data/sig/parser.rbs +16 -0
  69. data/sig/pestle.rbs +5 -0
  70. data/sig/pratt.rbs +27 -0
  71. data/sig/state.rbs +95 -0
  72. data/sig/stdlib/strscan.rbs +3 -0
  73. data.tar.gz.sig +0 -0
  74. metadata +141 -0
  75. 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