rjq 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
- data/ARCHITECTURE.md +38 -0
- data/CHANGELOG.md +7 -0
- data/COMPATIBILITY.md +48 -0
- data/CONTRIBUTING.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +192 -0
- data/SECURITY.md +10 -0
- data/bin/rjq +9 -0
- data/lib/rjq/ast.rb +1475 -0
- data/lib/rjq/builtins/array.rb +3 -0
- data/lib/rjq/builtins/core.rb +3 -0
- data/lib/rjq/builtins/date.rb +3 -0
- data/lib/rjq/builtins/format.rb +3 -0
- data/lib/rjq/builtins/io.rb +3 -0
- data/lib/rjq/builtins/math.rb +3 -0
- data/lib/rjq/builtins/regex.rb +3 -0
- data/lib/rjq/builtins/sql.rb +3 -0
- data/lib/rjq/builtins/stream.rb +3 -0
- data/lib/rjq/builtins/string.rb +3 -0
- data/lib/rjq/builtins.rb +2103 -0
- data/lib/rjq/cli.rb +459 -0
- data/lib/rjq/color.rb +36 -0
- data/lib/rjq/compiler.rb +392 -0
- data/lib/rjq/errors.rb +76 -0
- data/lib/rjq/json/dumper.rb +191 -0
- data/lib/rjq/json/input_buffer.rb +99 -0
- data/lib/rjq/json/parser.rb +405 -0
- data/lib/rjq/json/stream_parser.rb +526 -0
- data/lib/rjq/json.rb +4 -0
- data/lib/rjq/lexer.rb +344 -0
- data/lib/rjq/math_functions.rb +168 -0
- data/lib/rjq/module_loader.rb +178 -0
- data/lib/rjq/modules.rb +88 -0
- data/lib/rjq/number.rb +189 -0
- data/lib/rjq/opcodes.rb +130 -0
- data/lib/rjq/parser.rb +755 -0
- data/lib/rjq/path.rb +250 -0
- data/lib/rjq/runtime.rb +377 -0
- data/lib/rjq/semantic_analyzer.rb +209 -0
- data/lib/rjq/value.rb +287 -0
- data/lib/rjq/version.rb +5 -0
- data/lib/rjq/vm.rb +1359 -0
- data/lib/rjq.rb +41 -0
- metadata +106 -0
data/lib/rjq/parser.rb
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
class Parser
|
|
5
|
+
DEFAULT_MAX_FILTER_DEPTH = 256
|
|
6
|
+
ASSIGNMENT_OPERATORS = ['=', '|=', '+=', '-=', '*=', '/=', '%=', '//='].freeze
|
|
7
|
+
COMPARISON_OPERATORS = %w[== != < <= > >=].freeze
|
|
8
|
+
ADDITIVE_OPERATORS = %w[+ -].freeze
|
|
9
|
+
MULTIPLICATIVE_OPERATORS = %w[* / %].freeze
|
|
10
|
+
PRECEDENCE = {
|
|
11
|
+
'|' => 1,
|
|
12
|
+
'as' => 2,
|
|
13
|
+
',' => 3,
|
|
14
|
+
'=' => 4,
|
|
15
|
+
'|=' => 4,
|
|
16
|
+
'+=' => 4,
|
|
17
|
+
'-=' => 4,
|
|
18
|
+
'*=' => 4,
|
|
19
|
+
'/=' => 4,
|
|
20
|
+
'%=' => 4,
|
|
21
|
+
'//=' => 4,
|
|
22
|
+
'or' => 5,
|
|
23
|
+
'and' => 6,
|
|
24
|
+
'//' => 7,
|
|
25
|
+
'==' => 8,
|
|
26
|
+
'!=' => 8,
|
|
27
|
+
'<' => 8,
|
|
28
|
+
'<=' => 8,
|
|
29
|
+
'>' => 8,
|
|
30
|
+
'>=' => 8,
|
|
31
|
+
'+' => 9,
|
|
32
|
+
'-' => 9,
|
|
33
|
+
'*' => 10,
|
|
34
|
+
'/' => 10,
|
|
35
|
+
'%' => 10
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
38
|
+
def initialize(source, allow_comments: true, source_name: '<top-level>', initial_line: 1, initial_column: 1,
|
|
39
|
+
start_offset: 0, max_filter_depth: DEFAULT_MAX_FILTER_DEPTH)
|
|
40
|
+
unless max_filter_depth.is_a?(Integer) && max_filter_depth.positive?
|
|
41
|
+
raise ArgumentError, 'max_filter_depth must be a positive Integer'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@tokens = if source.is_a?(Array)
|
|
45
|
+
source
|
|
46
|
+
else
|
|
47
|
+
Lexer.new(source, allow_comments: allow_comments, source_name: source_name,
|
|
48
|
+
initial_line: initial_line, initial_column: initial_column,
|
|
49
|
+
start_offset: start_offset).tokenize
|
|
50
|
+
end
|
|
51
|
+
@index = 0
|
|
52
|
+
@definitions = []
|
|
53
|
+
@max_filter_depth = max_filter_depth
|
|
54
|
+
@filter_depth = 0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def parse
|
|
58
|
+
directives = parse_directives
|
|
59
|
+
parse_definitions
|
|
60
|
+
body = eof? ? AST::Identity.new : parse_expression
|
|
61
|
+
expect(:eof)
|
|
62
|
+
AST::Program.new(body, @definitions, directives)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def parse_directives
|
|
68
|
+
directives = []
|
|
69
|
+
while keyword?('include') || keyword?('import') || keyword?('module')
|
|
70
|
+
directives << parse_directive
|
|
71
|
+
end
|
|
72
|
+
directives
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_directive
|
|
76
|
+
type = consume.value.to_sym
|
|
77
|
+
if type == :module
|
|
78
|
+
metadata = parse_directive_metadata
|
|
79
|
+
expect(:semicolon)
|
|
80
|
+
return AST::ModuleDirective.new(type: type, metadata: metadata)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
path = expect(:string).value
|
|
84
|
+
raise error('Import path must be constant') unless path.is_a?(String)
|
|
85
|
+
|
|
86
|
+
metadata = parse_directive_metadata unless %i[semicolon eof].include?(current.type) || keyword?('as')
|
|
87
|
+
alias_name = parse_module_alias if type == :import
|
|
88
|
+
expect(:semicolon)
|
|
89
|
+
AST::ModuleDirective.new(type: type, name: path, metadata: metadata, alias_name: alias_name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def parse_directive_metadata
|
|
93
|
+
case current.type
|
|
94
|
+
when :lbrace
|
|
95
|
+
parse_object_literal
|
|
96
|
+
when :lbracket
|
|
97
|
+
parse_array_literal
|
|
98
|
+
when :lparen
|
|
99
|
+
consume
|
|
100
|
+
value = parse_expression_allowing_comma
|
|
101
|
+
expect(:rparen)
|
|
102
|
+
value
|
|
103
|
+
else
|
|
104
|
+
parse_prefix
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def parse_module_alias
|
|
109
|
+
expect_keyword('as')
|
|
110
|
+
case current.type
|
|
111
|
+
when :identifier
|
|
112
|
+
consume.value
|
|
113
|
+
when :variable
|
|
114
|
+
"$#{consume.value}"
|
|
115
|
+
else
|
|
116
|
+
raise error('expected module alias')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def parse_definitions
|
|
121
|
+
while keyword?('def')
|
|
122
|
+
name, params, body = parse_function_definition
|
|
123
|
+
expect(:semicolon)
|
|
124
|
+
@definitions << AST::FunctionDefinition.new(name, params, body)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def parse_function_definition
|
|
129
|
+
expect_keyword('def')
|
|
130
|
+
name = parse_name
|
|
131
|
+
params = []
|
|
132
|
+
if consume?(:lparen)
|
|
133
|
+
until current.type == :rparen
|
|
134
|
+
params << parse_param
|
|
135
|
+
break unless %i[semicolon comma].include?(current.type)
|
|
136
|
+
|
|
137
|
+
consume
|
|
138
|
+
end
|
|
139
|
+
expect(:rparen)
|
|
140
|
+
end
|
|
141
|
+
expect(:colon)
|
|
142
|
+
[name, params, parse_expression]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def parse_param
|
|
146
|
+
if current.type == :variable
|
|
147
|
+
"$#{consume.value}"
|
|
148
|
+
else
|
|
149
|
+
expect(:identifier).value
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def parse_binding_pattern
|
|
154
|
+
case current.type
|
|
155
|
+
when :variable
|
|
156
|
+
[:var, consume.value]
|
|
157
|
+
when :lbrace
|
|
158
|
+
parse_object_binding_pattern
|
|
159
|
+
when :lbracket
|
|
160
|
+
parse_array_binding_pattern
|
|
161
|
+
else
|
|
162
|
+
raise error('expected binding pattern')
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def parse_object_binding_pattern
|
|
167
|
+
expect(:lbrace)
|
|
168
|
+
pairs = []
|
|
169
|
+
raise error('expected binding object key') if current.type == :rbrace
|
|
170
|
+
|
|
171
|
+
until consume?(:rbrace)
|
|
172
|
+
key =
|
|
173
|
+
case current.type
|
|
174
|
+
when :identifier, :string, :keyword
|
|
175
|
+
consume.value
|
|
176
|
+
when :variable
|
|
177
|
+
variable = consume.value
|
|
178
|
+
pairs << if consume?(:colon)
|
|
179
|
+
[variable, [:both, [:var, variable], parse_binding_pattern]]
|
|
180
|
+
else
|
|
181
|
+
[variable, [:var, variable]]
|
|
182
|
+
end
|
|
183
|
+
next if consume?(:comma)
|
|
184
|
+
|
|
185
|
+
expect(:rbrace)
|
|
186
|
+
break
|
|
187
|
+
when :lparen
|
|
188
|
+
consume
|
|
189
|
+
expression = parse_expression
|
|
190
|
+
expect(:rparen)
|
|
191
|
+
expression
|
|
192
|
+
else
|
|
193
|
+
raise error('expected binding object key')
|
|
194
|
+
end
|
|
195
|
+
pairs << [key, consume?(:colon) ? parse_binding_pattern : [:var, key]]
|
|
196
|
+
next if consume?(:comma)
|
|
197
|
+
|
|
198
|
+
expect(:rbrace)
|
|
199
|
+
break
|
|
200
|
+
end
|
|
201
|
+
[:object, pairs]
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def parse_array_binding_pattern
|
|
205
|
+
expect(:lbracket)
|
|
206
|
+
patterns = []
|
|
207
|
+
raise error('expected binding pattern') if current.type == :rbracket
|
|
208
|
+
|
|
209
|
+
until consume?(:rbracket)
|
|
210
|
+
patterns << parse_binding_pattern
|
|
211
|
+
next if consume?(:comma)
|
|
212
|
+
|
|
213
|
+
expect(:rbracket)
|
|
214
|
+
break
|
|
215
|
+
end
|
|
216
|
+
[:array, patterns]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse_expression(min_precedence = 0)
|
|
220
|
+
@filter_depth += 1
|
|
221
|
+
raise error("filter nesting exceeds #{@max_filter_depth}") if @filter_depth > @max_filter_depth
|
|
222
|
+
|
|
223
|
+
start_token = current
|
|
224
|
+
left = parse_prefix
|
|
225
|
+
loop do
|
|
226
|
+
op = current_operator
|
|
227
|
+
break unless op
|
|
228
|
+
|
|
229
|
+
precedence = PRECEDENCE[op]
|
|
230
|
+
break unless precedence && precedence >= min_precedence
|
|
231
|
+
|
|
232
|
+
consume
|
|
233
|
+
left = parse_infix(left, op, precedence)
|
|
234
|
+
end
|
|
235
|
+
add_source_span(left, start_token)
|
|
236
|
+
ensure
|
|
237
|
+
@filter_depth -= 1
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def parse_infix(left, op, precedence)
|
|
241
|
+
if op == 'as'
|
|
242
|
+
pattern = parse_binding_pattern
|
|
243
|
+
alternatives = [pattern]
|
|
244
|
+
while current.type == :question && @tokens[@index + 1]&.type == :operator && @tokens[@index + 1]&.value == '//'
|
|
245
|
+
consume
|
|
246
|
+
consume
|
|
247
|
+
alternatives << parse_binding_pattern
|
|
248
|
+
end
|
|
249
|
+
pattern = [:alternatives, alternatives] if alternatives.length > 1
|
|
250
|
+
expect(:pipe)
|
|
251
|
+
return AST::Binding.new(left, pattern, parse_expression)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
right = parse_expression(ASSIGNMENT_OPERATORS.include?(op) ? precedence : precedence + 1)
|
|
255
|
+
case op
|
|
256
|
+
when '|'
|
|
257
|
+
AST::Pipe.new(left, right)
|
|
258
|
+
when ','
|
|
259
|
+
AST::Comma.new(left, right)
|
|
260
|
+
when *ASSIGNMENT_OPERATORS
|
|
261
|
+
AST::Assignment.new(left, op, right)
|
|
262
|
+
else
|
|
263
|
+
AST::BinaryOp.new(left, op, right)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def parse_prefix
|
|
268
|
+
start_token = current
|
|
269
|
+
node =
|
|
270
|
+
case current.type
|
|
271
|
+
when :dot
|
|
272
|
+
consume
|
|
273
|
+
parse_dot_suffix(AST::Identity.new, required: false)
|
|
274
|
+
when :operator
|
|
275
|
+
parse_operator_prefix
|
|
276
|
+
when :number
|
|
277
|
+
AST::Literal.new(consume.value)
|
|
278
|
+
when :string
|
|
279
|
+
AST::StringLiteral.new(consume.value)
|
|
280
|
+
when :variable
|
|
281
|
+
parse_variable
|
|
282
|
+
when :identifier
|
|
283
|
+
parse_identifier
|
|
284
|
+
when :format
|
|
285
|
+
parse_format
|
|
286
|
+
when :keyword
|
|
287
|
+
parse_keyword
|
|
288
|
+
when :lparen
|
|
289
|
+
consume
|
|
290
|
+
expression = parse_expression_allowing_comma
|
|
291
|
+
expect(:rparen)
|
|
292
|
+
expression
|
|
293
|
+
when :lbracket
|
|
294
|
+
parse_array_literal
|
|
295
|
+
when :lbrace
|
|
296
|
+
parse_object_literal
|
|
297
|
+
else
|
|
298
|
+
raise error("unexpected token #{current.type}")
|
|
299
|
+
end
|
|
300
|
+
node = add_source_span(node, start_token) unless node.source_span
|
|
301
|
+
add_source_span(parse_postfix(node), start_token)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def parse_operator_prefix
|
|
305
|
+
if current.value == '-'
|
|
306
|
+
consume
|
|
307
|
+
AST::UnaryOp.new('-', parse_expression(10))
|
|
308
|
+
elsif current.value == '..'
|
|
309
|
+
consume
|
|
310
|
+
AST::Recurse.new
|
|
311
|
+
else
|
|
312
|
+
raise error("unexpected operator #{current.value}")
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def parse_identifier
|
|
317
|
+
name = parse_name
|
|
318
|
+
return AST::FunctionCall.new(name, parse_arguments) if consume?(:lparen)
|
|
319
|
+
|
|
320
|
+
AST::FunctionCall.new(name, [])
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def parse_variable
|
|
324
|
+
name = consume.value
|
|
325
|
+
return AST::Variable.new(name) unless current.type == :colon && @tokens[@index + 1]&.type == :colon
|
|
326
|
+
|
|
327
|
+
consume
|
|
328
|
+
consume
|
|
329
|
+
AST::FunctionCall.new("#{name}::#{parse_name}", current.type == :lparen ? parse_arguments_after_lparen : [])
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def parse_arguments_after_lparen
|
|
333
|
+
expect(:lparen)
|
|
334
|
+
parse_arguments
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def parse_name
|
|
338
|
+
name = expect(:identifier).value
|
|
339
|
+
while current.type == :colon && @tokens[@index + 1]&.type == :colon
|
|
340
|
+
consume
|
|
341
|
+
consume
|
|
342
|
+
name = "#{name}::#{expect(:identifier).value}"
|
|
343
|
+
end
|
|
344
|
+
name
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def parse_format
|
|
348
|
+
name = "@#{consume.value}"
|
|
349
|
+
return AST::Format.new(name, parse_prefix) if current.type == :string
|
|
350
|
+
|
|
351
|
+
AST::Format.new(name)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def parse_keyword
|
|
355
|
+
case current.value
|
|
356
|
+
when 'null'
|
|
357
|
+
consume
|
|
358
|
+
AST::Literal.new(nil)
|
|
359
|
+
when 'true'
|
|
360
|
+
consume
|
|
361
|
+
AST::Literal.new(true)
|
|
362
|
+
when 'false'
|
|
363
|
+
consume
|
|
364
|
+
AST::Literal.new(false)
|
|
365
|
+
when 'if'
|
|
366
|
+
parse_if
|
|
367
|
+
when 'def'
|
|
368
|
+
parse_scoped_definition
|
|
369
|
+
when 'try'
|
|
370
|
+
parse_try
|
|
371
|
+
when 'reduce'
|
|
372
|
+
parse_reduce
|
|
373
|
+
when 'foreach'
|
|
374
|
+
parse_foreach
|
|
375
|
+
when 'label'
|
|
376
|
+
parse_label
|
|
377
|
+
when 'break'
|
|
378
|
+
parse_break
|
|
379
|
+
when 'include', 'import', 'module'
|
|
380
|
+
raise error('module directives are only allowed before definitions and the filter body')
|
|
381
|
+
when 'not'
|
|
382
|
+
consume
|
|
383
|
+
AST::FunctionCall.new('not', [])
|
|
384
|
+
else
|
|
385
|
+
raise error("unexpected keyword #{current.value}")
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def parse_if
|
|
390
|
+
expect_keyword('if')
|
|
391
|
+
condition = parse_expression_allowing_pipe
|
|
392
|
+
expect_keyword('then')
|
|
393
|
+
then_branch = parse_expression_allowing_pipe
|
|
394
|
+
else_branch =
|
|
395
|
+
if keyword?('elif')
|
|
396
|
+
AST::If.new(parse_elif_condition, parse_elif_then, parse_elif_else)
|
|
397
|
+
elsif keyword?('end')
|
|
398
|
+
AST::Identity.new
|
|
399
|
+
else
|
|
400
|
+
expect_keyword('else')
|
|
401
|
+
parse_expression_allowing_pipe
|
|
402
|
+
end
|
|
403
|
+
expect_keyword('end')
|
|
404
|
+
AST::If.new(condition, then_branch, else_branch)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def parse_elif_condition
|
|
408
|
+
expect_keyword('elif')
|
|
409
|
+
parse_expression_allowing_pipe
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def parse_elif_then
|
|
413
|
+
expect_keyword('then')
|
|
414
|
+
parse_expression_allowing_pipe
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
def parse_elif_else
|
|
418
|
+
if keyword?('elif')
|
|
419
|
+
AST::If.new(parse_elif_condition, parse_elif_then, parse_elif_else)
|
|
420
|
+
elsif keyword?('end')
|
|
421
|
+
AST::Identity.new
|
|
422
|
+
else
|
|
423
|
+
expect_keyword('else')
|
|
424
|
+
parse_expression_allowing_pipe
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def parse_try
|
|
429
|
+
expect_keyword('try')
|
|
430
|
+
body = parse_expression
|
|
431
|
+
handler = nil
|
|
432
|
+
handler = parse_expression_stopping_at_comma_or_pipe if consume_keyword?('catch')
|
|
433
|
+
AST::Try.new(body, handler)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def parse_scoped_definition
|
|
437
|
+
name, params, body = parse_function_definition
|
|
438
|
+
expect(:semicolon)
|
|
439
|
+
AST::ScopedDefinition.new(AST::FunctionDefinition.new(name, params, body), parse_expression)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
def parse_reduce
|
|
443
|
+
expect_keyword('reduce')
|
|
444
|
+
generator = parse_expression_until_keywords('as')
|
|
445
|
+
expect_keyword('as')
|
|
446
|
+
pattern = parse_binding_pattern
|
|
447
|
+
expect(:lparen)
|
|
448
|
+
initial = parse_expression
|
|
449
|
+
expect(:semicolon)
|
|
450
|
+
update = parse_expression
|
|
451
|
+
expect(:rparen)
|
|
452
|
+
AST::Reduce.new(generator, pattern, initial, update)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def parse_foreach
|
|
456
|
+
expect_keyword('foreach')
|
|
457
|
+
generator = parse_expression_until_keywords('as')
|
|
458
|
+
expect_keyword('as')
|
|
459
|
+
pattern = parse_binding_pattern
|
|
460
|
+
expect(:lparen)
|
|
461
|
+
initial = parse_expression
|
|
462
|
+
expect(:semicolon)
|
|
463
|
+
update = parse_expression
|
|
464
|
+
extract = consume?(:semicolon) ? parse_expression : nil
|
|
465
|
+
expect(:rparen)
|
|
466
|
+
AST::Foreach.new(generator, pattern, initial, update, extract)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def parse_label
|
|
470
|
+
expect_keyword('label')
|
|
471
|
+
label = expect(:variable).value
|
|
472
|
+
expect(:pipe)
|
|
473
|
+
AST::Label.new(label, parse_expression)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def parse_break
|
|
477
|
+
expect_keyword('break')
|
|
478
|
+
AST::Break.new(expect(:variable).value)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def parse_arguments
|
|
482
|
+
args = []
|
|
483
|
+
until consume?(:rparen)
|
|
484
|
+
args << parse_expression
|
|
485
|
+
next if consume?(:semicolon)
|
|
486
|
+
|
|
487
|
+
expect(:rparen)
|
|
488
|
+
break
|
|
489
|
+
end
|
|
490
|
+
args
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def parse_array_literal
|
|
494
|
+
expect(:lbracket)
|
|
495
|
+
return AST::ArrayLiteral.new(nil) if consume?(:rbracket)
|
|
496
|
+
|
|
497
|
+
expression = parse_expression_allowing_comma
|
|
498
|
+
expect(:rbracket)
|
|
499
|
+
AST::ArrayLiteral.new(expression)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def parse_object_literal
|
|
503
|
+
expect(:lbrace)
|
|
504
|
+
pairs = []
|
|
505
|
+
until consume?(:rbrace)
|
|
506
|
+
pairs << parse_object_pair
|
|
507
|
+
next if consume?(:comma)
|
|
508
|
+
|
|
509
|
+
expect(:rbrace)
|
|
510
|
+
break
|
|
511
|
+
end
|
|
512
|
+
AST::ObjectLiteral.new(pairs)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def parse_object_pair
|
|
516
|
+
case current.type
|
|
517
|
+
when :identifier, :keyword
|
|
518
|
+
key = consume.value
|
|
519
|
+
value = consume?(:colon) ? parse_expression_stopping_at_comma : AST::Field.new(AST::Identity.new, key)
|
|
520
|
+
when :string
|
|
521
|
+
key = consume.value
|
|
522
|
+
value =
|
|
523
|
+
if consume?(:colon)
|
|
524
|
+
parse_expression_stopping_at_comma
|
|
525
|
+
elsif key.is_a?(Array)
|
|
526
|
+
AST::Index.new(AST::Identity.new, AST::StringLiteral.new(key))
|
|
527
|
+
else
|
|
528
|
+
AST::Field.new(AST::Identity.new, key)
|
|
529
|
+
end
|
|
530
|
+
when :variable
|
|
531
|
+
variable = consume.value
|
|
532
|
+
if consume?(:colon)
|
|
533
|
+
key = AST::Variable.new(variable)
|
|
534
|
+
value = parse_expression_stopping_at_comma
|
|
535
|
+
else
|
|
536
|
+
key = variable
|
|
537
|
+
value = AST::Variable.new(variable)
|
|
538
|
+
end
|
|
539
|
+
when :lparen
|
|
540
|
+
consume
|
|
541
|
+
key = parse_expression
|
|
542
|
+
expect(:rparen)
|
|
543
|
+
expect(:colon)
|
|
544
|
+
value = parse_expression_stopping_at_comma
|
|
545
|
+
else
|
|
546
|
+
raise error('expected object key')
|
|
547
|
+
end
|
|
548
|
+
AST::ObjectLiteral::Pair.new(key: key, value: value)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def parse_postfix(node)
|
|
552
|
+
loop do
|
|
553
|
+
node =
|
|
554
|
+
case current.type
|
|
555
|
+
when :dot
|
|
556
|
+
consume
|
|
557
|
+
parse_dot_suffix(node, required: true)
|
|
558
|
+
when :lbracket
|
|
559
|
+
parse_bracket_access(node)
|
|
560
|
+
when :question
|
|
561
|
+
consume
|
|
562
|
+
AST::Optional.new(node)
|
|
563
|
+
else
|
|
564
|
+
return node
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def parse_dot_suffix(base, required:)
|
|
570
|
+
case current.type
|
|
571
|
+
when :identifier
|
|
572
|
+
AST::Field.new(base, consume.value)
|
|
573
|
+
when :keyword
|
|
574
|
+
return base unless token_adjacent_to_previous?
|
|
575
|
+
|
|
576
|
+
AST::Field.new(base, consume.value)
|
|
577
|
+
when :string
|
|
578
|
+
value = consume.value
|
|
579
|
+
value.is_a?(Array) ? AST::Index.new(base, AST::StringLiteral.new(value)) : AST::Field.new(base, value)
|
|
580
|
+
when :lbracket
|
|
581
|
+
parse_bracket_access(base)
|
|
582
|
+
else
|
|
583
|
+
raise error('expected field or bracket expression after dot') if required
|
|
584
|
+
|
|
585
|
+
base
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def token_adjacent_to_previous?
|
|
590
|
+
previous.line == current.line && previous.column + previous.value.to_s.length == current.column
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def parse_bracket_access(base)
|
|
594
|
+
expect(:lbracket)
|
|
595
|
+
return AST::Iterate.new(base) if consume?(:rbracket)
|
|
596
|
+
|
|
597
|
+
if consume?(:colon)
|
|
598
|
+
if consume?(:rbracket)
|
|
599
|
+
finish_node = nil
|
|
600
|
+
else
|
|
601
|
+
finish_node = parse_expression(PRECEDENCE.fetch(',') + 1)
|
|
602
|
+
expect(:rbracket)
|
|
603
|
+
end
|
|
604
|
+
return AST::Slice.new(base, nil, finish_node)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
start_node = parse_expression_allowing_comma
|
|
608
|
+
if consume?(:colon)
|
|
609
|
+
if consume?(:rbracket)
|
|
610
|
+
finish_node = nil
|
|
611
|
+
else
|
|
612
|
+
finish_node = parse_expression(PRECEDENCE.fetch(',') + 1)
|
|
613
|
+
expect(:rbracket)
|
|
614
|
+
end
|
|
615
|
+
AST::Slice.new(base, start_node, finish_node)
|
|
616
|
+
else
|
|
617
|
+
expect(:rbracket)
|
|
618
|
+
AST::Index.new(base, start_node)
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def current_operator
|
|
623
|
+
return nil if stopped_keyword?
|
|
624
|
+
|
|
625
|
+
return '|' if current.type == :pipe && !stop_at_pipe?
|
|
626
|
+
return ',' if current.type == :comma && !stop_at_comma?
|
|
627
|
+
return current.value if current.type == :operator
|
|
628
|
+
return current.value if current.type == :keyword && %w[as and or].include?(current.value)
|
|
629
|
+
|
|
630
|
+
nil
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def parse_expression_stopping_at_comma
|
|
634
|
+
@stop_at_comma = @stop_at_comma.to_i + 1
|
|
635
|
+
parse_expression
|
|
636
|
+
ensure
|
|
637
|
+
@stop_at_comma -= 1
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def parse_expression_stopping_at_comma_or_pipe
|
|
641
|
+
@stop_at_comma = @stop_at_comma.to_i + 1
|
|
642
|
+
@stop_at_pipe = @stop_at_pipe.to_i + 1
|
|
643
|
+
parse_expression
|
|
644
|
+
ensure
|
|
645
|
+
@stop_at_comma -= 1
|
|
646
|
+
@stop_at_pipe -= 1
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def parse_expression_allowing_comma
|
|
650
|
+
previous = @stop_at_comma
|
|
651
|
+
@stop_at_comma = 0
|
|
652
|
+
parse_expression
|
|
653
|
+
ensure
|
|
654
|
+
@stop_at_comma = previous
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def parse_expression_allowing_pipe
|
|
658
|
+
previous = @stop_at_pipe
|
|
659
|
+
@stop_at_pipe = 0
|
|
660
|
+
parse_expression
|
|
661
|
+
ensure
|
|
662
|
+
@stop_at_pipe = previous
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def parse_expression_until_keywords(*keywords)
|
|
666
|
+
@stop_keywords ||= []
|
|
667
|
+
@stop_keywords.push(keywords.flatten)
|
|
668
|
+
parse_expression
|
|
669
|
+
ensure
|
|
670
|
+
@stop_keywords.pop
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def stop_at_comma?
|
|
674
|
+
@stop_at_comma.to_i.positive?
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def stop_at_pipe?
|
|
678
|
+
@stop_at_pipe.to_i.positive?
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def stopped_keyword?
|
|
682
|
+
current.type == :keyword && @stop_keywords&.any? { |keywords| keywords.include?(current.value) }
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def expect(type)
|
|
686
|
+
raise error("expected #{type}, got #{current.type}") unless current.type == type
|
|
687
|
+
|
|
688
|
+
consume
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def expect_keyword(value)
|
|
692
|
+
raise error("expected #{value}") unless keyword?(value)
|
|
693
|
+
|
|
694
|
+
consume
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def consume?(type)
|
|
698
|
+
return false unless current.type == type
|
|
699
|
+
|
|
700
|
+
consume
|
|
701
|
+
true
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def consume_keyword?(value)
|
|
705
|
+
return false unless keyword?(value)
|
|
706
|
+
|
|
707
|
+
consume
|
|
708
|
+
true
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
def consume_until_semicolon
|
|
712
|
+
consume until %i[semicolon eof].include?(current.type)
|
|
713
|
+
consume?(:semicolon)
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def keyword?(value)
|
|
717
|
+
current.type == :keyword && current.value == value
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def consume
|
|
721
|
+
token = current
|
|
722
|
+
@index += 1
|
|
723
|
+
token
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def previous
|
|
727
|
+
@tokens[@index - 1]
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
def current
|
|
731
|
+
@tokens[@index]
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def eof?
|
|
735
|
+
current.type == :eof
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def error(message)
|
|
739
|
+
ParseError.new("#{message} at #{current.filename || '<top-level>'}, line #{current.line}, column #{current.column}")
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
def add_source_span(node, start_token)
|
|
743
|
+
return node unless node.is_a?(AST::Node)
|
|
744
|
+
|
|
745
|
+
finish_token = previous || start_token
|
|
746
|
+
node.with_source_span(AST::SourceSpan.new(
|
|
747
|
+
filename: start_token.filename || '<top-level>',
|
|
748
|
+
line: start_token.line,
|
|
749
|
+
column: start_token.column,
|
|
750
|
+
start_offset: start_token.start_offset,
|
|
751
|
+
end_offset: finish_token.end_offset
|
|
752
|
+
))
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
end
|