prism 0.22.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/README.md +2 -1
- data/docs/releasing.md +67 -17
- data/docs/ruby_parser_translation.md +19 -0
- data/docs/serialization.md +2 -0
- data/ext/prism/api_node.c +1982 -1538
- data/ext/prism/extension.c +12 -7
- data/ext/prism/extension.h +2 -2
- data/include/prism/diagnostic.h +3 -4
- data/include/prism/encoding.h +7 -0
- data/include/prism/util/pm_constant_pool.h +1 -1
- data/include/prism/util/pm_newline_list.h +4 -3
- data/include/prism/util/pm_strpbrk.h +4 -1
- data/include/prism/version.h +2 -2
- data/lib/prism/desugar_compiler.rb +225 -80
- data/lib/prism/dsl.rb +302 -299
- data/lib/prism/ffi.rb +103 -77
- data/lib/prism/lex_compat.rb +1 -0
- data/lib/prism/node.rb +3624 -2114
- data/lib/prism/node_ext.rb +25 -2
- data/lib/prism/parse_result.rb +56 -19
- data/lib/prism/serialize.rb +605 -303
- data/lib/prism/translation/parser/compiler.rb +1 -1
- data/lib/prism/translation/parser/rubocop.rb +11 -3
- data/lib/prism/translation/parser.rb +25 -12
- data/lib/prism/translation/parser33.rb +12 -0
- data/lib/prism/translation/parser34.rb +12 -0
- data/lib/prism/translation/ripper.rb +696 -0
- data/lib/prism/translation/ruby_parser.rb +1521 -0
- data/lib/prism/translation.rb +3 -3
- data/lib/prism.rb +0 -1
- data/prism.gemspec +6 -2
- data/src/diagnostic.c +10 -11
- data/src/encoding.c +16 -17
- data/src/options.c +7 -2
- data/src/prettyprint.c +3 -3
- data/src/prism.c +172 -97
- data/src/serialize.c +24 -13
- data/src/token_type.c +3 -3
- data/src/util/pm_constant_pool.c +1 -1
- data/src/util/pm_newline_list.c +6 -3
- data/src/util/pm_strpbrk.c +122 -14
- metadata +8 -4
- data/lib/prism/ripper_compat.rb +0 -285
@@ -0,0 +1,696 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
module Prism
|
6
|
+
module Translation
|
7
|
+
# Note: This integration is not finished, and therefore still has many
|
8
|
+
# inconsistencies with Ripper. If you'd like to help out, pull requests would
|
9
|
+
# be greatly appreciated!
|
10
|
+
#
|
11
|
+
# This class is meant to provide a compatibility layer between prism and
|
12
|
+
# Ripper. It functions by parsing the entire tree first and then walking it
|
13
|
+
# and executing each of the Ripper callbacks as it goes.
|
14
|
+
#
|
15
|
+
# This class is going to necessarily be slower than the native Ripper API. It
|
16
|
+
# is meant as a stopgap until developers migrate to using prism. It is also
|
17
|
+
# meant as a test harness for the prism parser.
|
18
|
+
#
|
19
|
+
# To use this class, you treat `Prism::Translation::Ripper` effectively as you would
|
20
|
+
# treat the `Ripper` class.
|
21
|
+
class Ripper < Compiler
|
22
|
+
# This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that
|
23
|
+
# returns the arrays of [type, *children].
|
24
|
+
class SexpBuilder < Ripper
|
25
|
+
private
|
26
|
+
|
27
|
+
::Ripper::PARSER_EVENTS.each do |event|
|
28
|
+
define_method(:"on_#{event}") do |*args|
|
29
|
+
[event, *args]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
::Ripper::SCANNER_EVENTS.each do |event|
|
34
|
+
define_method(:"on_#{event}") do |value|
|
35
|
+
[:"@#{event}", value, [lineno, column]]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that
|
41
|
+
# returns the same values as ::Ripper::SexpBuilder except with a couple of
|
42
|
+
# niceties that flatten linked lists into arrays.
|
43
|
+
class SexpBuilderPP < SexpBuilder
|
44
|
+
private
|
45
|
+
|
46
|
+
def _dispatch_event_new # :nodoc:
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
def _dispatch_event_push(list, item) # :nodoc:
|
51
|
+
list << item
|
52
|
+
list
|
53
|
+
end
|
54
|
+
|
55
|
+
::Ripper::PARSER_EVENT_TABLE.each do |event, arity|
|
56
|
+
case event
|
57
|
+
when /_new\z/
|
58
|
+
alias_method :"on_#{event}", :_dispatch_event_new if arity == 0
|
59
|
+
when /_add\z/
|
60
|
+
alias_method :"on_#{event}", :_dispatch_event_push
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# The source that is being parsed.
|
66
|
+
attr_reader :source
|
67
|
+
|
68
|
+
# The current line number of the parser.
|
69
|
+
attr_reader :lineno
|
70
|
+
|
71
|
+
# The current column number of the parser.
|
72
|
+
attr_reader :column
|
73
|
+
|
74
|
+
# Create a new Translation::Ripper object with the given source.
|
75
|
+
def initialize(source)
|
76
|
+
@source = source
|
77
|
+
@result = nil
|
78
|
+
@lineno = nil
|
79
|
+
@column = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
############################################################################
|
83
|
+
# Public interface
|
84
|
+
############################################################################
|
85
|
+
|
86
|
+
# True if the parser encountered an error during parsing.
|
87
|
+
def error?
|
88
|
+
result.failure?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Parse the source and return the result.
|
92
|
+
def parse
|
93
|
+
result.magic_comments.each do |magic_comment|
|
94
|
+
on_magic_comment(magic_comment.key, magic_comment.value)
|
95
|
+
end
|
96
|
+
|
97
|
+
if error?
|
98
|
+
result.errors.each do |error|
|
99
|
+
on_parse_error(error.message)
|
100
|
+
end
|
101
|
+
|
102
|
+
nil
|
103
|
+
else
|
104
|
+
result.value.accept(self)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
############################################################################
|
109
|
+
# Visitor methods
|
110
|
+
############################################################################
|
111
|
+
|
112
|
+
# Visit an ArrayNode node.
|
113
|
+
def visit_array_node(node)
|
114
|
+
elements = visit_elements(node.elements) unless node.elements.empty?
|
115
|
+
bounds(node.location)
|
116
|
+
on_array(elements)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Visit a CallNode node.
|
120
|
+
# Ripper distinguishes between many different method-call
|
121
|
+
# nodes -- unary and binary operators, "command" calls with
|
122
|
+
# no parentheses, and call/fcall/vcall.
|
123
|
+
def visit_call_node(node)
|
124
|
+
return visit_aref_node(node) if node.name == :[]
|
125
|
+
return visit_aref_field_node(node) if node.name == :[]=
|
126
|
+
|
127
|
+
if node.variable_call?
|
128
|
+
raise NotImplementedError unless node.receiver.nil?
|
129
|
+
|
130
|
+
bounds(node.message_loc)
|
131
|
+
return on_vcall(on_ident(node.message))
|
132
|
+
end
|
133
|
+
|
134
|
+
if node.opening_loc.nil?
|
135
|
+
return visit_no_paren_call(node)
|
136
|
+
end
|
137
|
+
|
138
|
+
# A non-operator method call with parentheses
|
139
|
+
|
140
|
+
args = if node.arguments.nil?
|
141
|
+
on_arg_paren(nil)
|
142
|
+
else
|
143
|
+
on_arg_paren(on_args_add_block(visit_elements(node.arguments.arguments), false))
|
144
|
+
end
|
145
|
+
|
146
|
+
bounds(node.message_loc)
|
147
|
+
ident_val = on_ident(node.message)
|
148
|
+
|
149
|
+
bounds(node.location)
|
150
|
+
args_call_val = on_method_add_arg(on_fcall(ident_val), args)
|
151
|
+
if node.block
|
152
|
+
block_val = visit(node.block)
|
153
|
+
|
154
|
+
return on_method_add_block(args_call_val, block_val)
|
155
|
+
else
|
156
|
+
return args_call_val
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Visit a LocalVariableWriteNode.
|
161
|
+
def visit_local_variable_write_node(node)
|
162
|
+
bounds(node.name_loc)
|
163
|
+
ident_val = on_ident(node.name.to_s)
|
164
|
+
on_assign(on_var_field(ident_val), visit(node.value))
|
165
|
+
end
|
166
|
+
|
167
|
+
# Visit a LocalVariableAndWriteNode.
|
168
|
+
def visit_local_variable_and_write_node(node)
|
169
|
+
visit_binary_op_assign(node)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Visit a LocalVariableOrWriteNode.
|
173
|
+
def visit_local_variable_or_write_node(node)
|
174
|
+
visit_binary_op_assign(node)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Visit nodes for +=, *=, -=, etc., called LocalVariableOperatorWriteNodes.
|
178
|
+
def visit_local_variable_operator_write_node(node)
|
179
|
+
visit_binary_op_assign(node, operator: "#{node.operator}=")
|
180
|
+
end
|
181
|
+
|
182
|
+
# Visit a LocalVariableReadNode.
|
183
|
+
def visit_local_variable_read_node(node)
|
184
|
+
bounds(node.location)
|
185
|
+
ident_val = on_ident(node.slice)
|
186
|
+
|
187
|
+
on_var_ref(ident_val)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Visit a BlockNode.
|
191
|
+
def visit_block_node(node)
|
192
|
+
params_val = node.parameters.nil? ? nil : visit(node.parameters)
|
193
|
+
|
194
|
+
body_val = node.body.nil? ? on_stmts_add(on_stmts_new, on_void_stmt) : visit(node.body)
|
195
|
+
|
196
|
+
on_brace_block(params_val, body_val)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Visit a BlockParametersNode.
|
200
|
+
def visit_block_parameters_node(node)
|
201
|
+
on_block_var(visit(node.parameters), no_block_value)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Visit a ParametersNode.
|
205
|
+
# This will require expanding as we support more kinds of parameters.
|
206
|
+
def visit_parameters_node(node)
|
207
|
+
#on_params(required, optional, nil, nil, nil, nil, nil)
|
208
|
+
on_params(visit_all(node.requireds), nil, nil, nil, nil, nil, nil)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Visit a RequiredParameterNode.
|
212
|
+
def visit_required_parameter_node(node)
|
213
|
+
bounds(node.location)
|
214
|
+
on_ident(node.name.to_s)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Visit a BreakNode.
|
218
|
+
def visit_break_node(node)
|
219
|
+
return on_break(on_args_new) if node.arguments.nil?
|
220
|
+
|
221
|
+
args_val = visit_elements(node.arguments.arguments)
|
222
|
+
on_break(on_args_add_block(args_val, false))
|
223
|
+
end
|
224
|
+
|
225
|
+
# Visit an AliasMethodNode.
|
226
|
+
def visit_alias_method_node(node)
|
227
|
+
# For both the old and new name, if there is a colon in the symbol
|
228
|
+
# name (e.g. 'alias :foo :bar') then we do *not* emit the [:symbol] wrapper around
|
229
|
+
# the lexer token (e.g. :@ident) inside [:symbol_literal]. But if there
|
230
|
+
# is no colon (e.g. 'alias foo bar') then we *do* still emit the [:symbol] wrapper.
|
231
|
+
|
232
|
+
if node.new_name.is_a?(SymbolNode) && !node.new_name.opening
|
233
|
+
new_name_val = visit_symbol_literal_node(node.new_name, no_symbol_wrapper: true)
|
234
|
+
else
|
235
|
+
new_name_val = visit(node.new_name)
|
236
|
+
end
|
237
|
+
if node.old_name.is_a?(SymbolNode) && !node.old_name.opening
|
238
|
+
old_name_val = visit_symbol_literal_node(node.old_name, no_symbol_wrapper: true)
|
239
|
+
else
|
240
|
+
old_name_val = visit(node.old_name)
|
241
|
+
end
|
242
|
+
|
243
|
+
on_alias(new_name_val, old_name_val)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Visit an AliasGlobalVariableNode.
|
247
|
+
def visit_alias_global_variable_node(node)
|
248
|
+
on_var_alias(visit(node.new_name), visit(node.old_name))
|
249
|
+
end
|
250
|
+
|
251
|
+
# Visit a GlobalVariableReadNode.
|
252
|
+
def visit_global_variable_read_node(node)
|
253
|
+
bounds(node.location)
|
254
|
+
on_gvar(node.name.to_s)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Visit a BackReferenceReadNode.
|
258
|
+
def visit_back_reference_read_node(node)
|
259
|
+
bounds(node.location)
|
260
|
+
on_backref(node.name.to_s)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Visit an AndNode.
|
264
|
+
def visit_and_node(node)
|
265
|
+
visit_binary_operator(node)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Visit an OrNode.
|
269
|
+
def visit_or_node(node)
|
270
|
+
visit_binary_operator(node)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Visit a TrueNode.
|
274
|
+
def visit_true_node(node)
|
275
|
+
bounds(node.location)
|
276
|
+
on_var_ref(on_kw("true"))
|
277
|
+
end
|
278
|
+
|
279
|
+
# Visit a FalseNode.
|
280
|
+
def visit_false_node(node)
|
281
|
+
bounds(node.location)
|
282
|
+
on_var_ref(on_kw("false"))
|
283
|
+
end
|
284
|
+
|
285
|
+
# Visit a FloatNode node.
|
286
|
+
def visit_float_node(node)
|
287
|
+
visit_number(node) { |text| on_float(text) }
|
288
|
+
end
|
289
|
+
|
290
|
+
# Visit a ImaginaryNode node.
|
291
|
+
def visit_imaginary_node(node)
|
292
|
+
visit_number(node) { |text| on_imaginary(text) }
|
293
|
+
end
|
294
|
+
|
295
|
+
# Visit an IntegerNode node.
|
296
|
+
def visit_integer_node(node)
|
297
|
+
visit_number(node) { |text| on_int(text) }
|
298
|
+
end
|
299
|
+
|
300
|
+
# Visit a ParenthesesNode node.
|
301
|
+
def visit_parentheses_node(node)
|
302
|
+
body =
|
303
|
+
if node.body.nil?
|
304
|
+
on_stmts_add(on_stmts_new, on_void_stmt)
|
305
|
+
else
|
306
|
+
visit(node.body)
|
307
|
+
end
|
308
|
+
|
309
|
+
bounds(node.location)
|
310
|
+
on_paren(body)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Visit a BeginNode node.
|
314
|
+
# This is not at all bulletproof against different structures of begin/rescue/else/ensure/end.
|
315
|
+
def visit_begin_node(node)
|
316
|
+
rescue_val = node.rescue_clause ? on_rescue(nil, nil, visit(node.rescue_clause), nil) : nil
|
317
|
+
ensure_val = node.ensure_clause ? on_ensure(visit(node.ensure_clause.statements)) : nil
|
318
|
+
on_begin(on_bodystmt(visit(node.statements), rescue_val, nil, ensure_val))
|
319
|
+
end
|
320
|
+
|
321
|
+
# Visit a RescueNode node.
|
322
|
+
def visit_rescue_node(node)
|
323
|
+
visit(node.statements)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Visit a ProgramNode node.
|
327
|
+
def visit_program_node(node)
|
328
|
+
statements = visit(node.statements)
|
329
|
+
bounds(node.location)
|
330
|
+
on_program(statements)
|
331
|
+
end
|
332
|
+
|
333
|
+
# Visit a RangeNode node.
|
334
|
+
def visit_range_node(node)
|
335
|
+
left = visit(node.left)
|
336
|
+
right = visit(node.right)
|
337
|
+
|
338
|
+
bounds(node.location)
|
339
|
+
if node.exclude_end?
|
340
|
+
on_dot3(left, right)
|
341
|
+
else
|
342
|
+
on_dot2(left, right)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Visit a RationalNode node.
|
347
|
+
def visit_rational_node(node)
|
348
|
+
visit_number(node) { |text| on_rational(text) }
|
349
|
+
end
|
350
|
+
|
351
|
+
# Visit a StringNode node.
|
352
|
+
def visit_string_node(node)
|
353
|
+
bounds(node.content_loc)
|
354
|
+
tstring_val = on_tstring_content(node.unescaped.to_s)
|
355
|
+
on_string_literal(on_string_add(on_string_content, tstring_val))
|
356
|
+
end
|
357
|
+
|
358
|
+
# Visit an XStringNode node.
|
359
|
+
def visit_x_string_node(node)
|
360
|
+
bounds(node.content_loc)
|
361
|
+
tstring_val = on_tstring_content(node.unescaped.to_s)
|
362
|
+
on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val))
|
363
|
+
end
|
364
|
+
|
365
|
+
# Visit an InterpolatedStringNode node.
|
366
|
+
def visit_interpolated_string_node(node)
|
367
|
+
on_string_literal(visit_enumerated_node(node))
|
368
|
+
end
|
369
|
+
|
370
|
+
# Visit an EmbeddedStatementsNode node.
|
371
|
+
def visit_embedded_statements_node(node)
|
372
|
+
visit(node.statements)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Visit a SymbolNode node.
|
376
|
+
def visit_symbol_node(node)
|
377
|
+
visit_symbol_literal_node(node)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Visit an InterpolatedSymbolNode node.
|
381
|
+
def visit_interpolated_symbol_node(node)
|
382
|
+
on_dyna_symbol(visit_enumerated_node(node))
|
383
|
+
end
|
384
|
+
|
385
|
+
# Visit a StatementsNode node.
|
386
|
+
def visit_statements_node(node)
|
387
|
+
bounds(node.location)
|
388
|
+
node.body.inject(on_stmts_new) do |stmts, stmt|
|
389
|
+
on_stmts_add(stmts, visit(stmt))
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
############################################################################
|
394
|
+
# Entrypoints for subclasses
|
395
|
+
############################################################################
|
396
|
+
|
397
|
+
# This is a convenience method that runs the SexpBuilder subclass parser.
|
398
|
+
def self.sexp_raw(source)
|
399
|
+
SexpBuilder.new(source).parse
|
400
|
+
end
|
401
|
+
|
402
|
+
# This is a convenience method that runs the SexpBuilderPP subclass parser.
|
403
|
+
def self.sexp(source)
|
404
|
+
SexpBuilderPP.new(source).parse
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
# Generate Ripper events for a CallNode with no opening_loc
|
410
|
+
def visit_no_paren_call(node)
|
411
|
+
# No opening_loc can mean an operator. It can also mean a
|
412
|
+
# method call with no parentheses.
|
413
|
+
if node.message.match?(/^[[:punct:]]/)
|
414
|
+
left = visit(node.receiver)
|
415
|
+
if node.arguments&.arguments&.length == 1
|
416
|
+
right = visit(node.arguments.arguments.first)
|
417
|
+
|
418
|
+
return on_binary(left, node.name, right)
|
419
|
+
elsif !node.arguments || node.arguments.empty?
|
420
|
+
return on_unary(node.name, left)
|
421
|
+
else
|
422
|
+
raise NotImplementedError, "More than two arguments for operator"
|
423
|
+
end
|
424
|
+
elsif node.call_operator_loc.nil?
|
425
|
+
# In Ripper a method call like "puts myvar" with no parentheses is a "command".
|
426
|
+
bounds(node.message_loc)
|
427
|
+
ident_val = on_ident(node.message)
|
428
|
+
|
429
|
+
# Unless it has a block, and then it's an fcall (e.g. "foo { bar }")
|
430
|
+
if node.block
|
431
|
+
block_val = visit(node.block)
|
432
|
+
# In these calls, even if node.arguments is nil, we still get an :args_new call.
|
433
|
+
args = if node.arguments.nil?
|
434
|
+
on_args_new
|
435
|
+
else
|
436
|
+
on_args_add_block(visit_elements(node.arguments.arguments))
|
437
|
+
end
|
438
|
+
method_args_val = on_method_add_arg(on_fcall(ident_val), args)
|
439
|
+
return on_method_add_block(method_args_val, block_val)
|
440
|
+
else
|
441
|
+
if node.arguments.nil?
|
442
|
+
return on_command(ident_val, nil)
|
443
|
+
else
|
444
|
+
args = on_args_add_block(visit_elements(node.arguments.arguments), false)
|
445
|
+
return on_command(ident_val, args)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
else
|
449
|
+
operator = node.call_operator_loc.slice
|
450
|
+
if operator == "." || operator == "&."
|
451
|
+
left_val = visit(node.receiver)
|
452
|
+
|
453
|
+
bounds(node.call_operator_loc)
|
454
|
+
operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator)
|
455
|
+
|
456
|
+
bounds(node.message_loc)
|
457
|
+
right_val = on_ident(node.message)
|
458
|
+
|
459
|
+
call_val = on_call(left_val, operator_val, right_val)
|
460
|
+
|
461
|
+
if node.block
|
462
|
+
block_val = visit(node.block)
|
463
|
+
return on_method_add_block(call_val, block_val)
|
464
|
+
else
|
465
|
+
return call_val
|
466
|
+
end
|
467
|
+
else
|
468
|
+
raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}"
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
# Visit a list of elements, like the elements of an array or arguments.
|
474
|
+
def visit_elements(elements)
|
475
|
+
bounds(elements.first.location)
|
476
|
+
elements.inject(on_args_new) do |args, element|
|
477
|
+
on_args_add(args, visit(element))
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
# Visit an InterpolatedStringNode or an InterpolatedSymbolNode node.
|
482
|
+
def visit_enumerated_node(node)
|
483
|
+
parts = node.parts.map do |part|
|
484
|
+
case part
|
485
|
+
when StringNode
|
486
|
+
bounds(part.content_loc)
|
487
|
+
on_tstring_content(part.content)
|
488
|
+
when EmbeddedStatementsNode
|
489
|
+
on_string_embexpr(visit(part))
|
490
|
+
else
|
491
|
+
raise NotImplementedError, "Unexpected node type in visit_enumerated_node"
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
parts.inject(on_string_content) do |items, item|
|
496
|
+
on_string_add(items, item)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
# Visit an operation-and-assign node, such as +=.
|
501
|
+
def visit_binary_op_assign(node, operator: node.operator)
|
502
|
+
bounds(node.name_loc)
|
503
|
+
ident_val = on_ident(node.name.to_s)
|
504
|
+
|
505
|
+
bounds(node.operator_loc)
|
506
|
+
op_val = on_op(operator)
|
507
|
+
|
508
|
+
on_opassign(on_var_field(ident_val), op_val, visit(node.value))
|
509
|
+
end
|
510
|
+
|
511
|
+
# In Prism this is a CallNode with :[] as the operator.
|
512
|
+
# In Ripper it's an :aref.
|
513
|
+
def visit_aref_node(node)
|
514
|
+
first_arg_val = visit(node.arguments.arguments[0])
|
515
|
+
args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false)
|
516
|
+
on_aref(visit(node.receiver), args_val)
|
517
|
+
end
|
518
|
+
|
519
|
+
# In Prism this is a CallNode with :[]= as the operator.
|
520
|
+
# In Ripper it's an :aref_field.
|
521
|
+
def visit_aref_field_node(node)
|
522
|
+
first_arg_val = visit(node.arguments.arguments[0])
|
523
|
+
args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false)
|
524
|
+
assign_val = visit(node.arguments.arguments[1])
|
525
|
+
on_assign(on_aref_field(visit(node.receiver), args_val), assign_val)
|
526
|
+
end
|
527
|
+
|
528
|
+
# In an alias statement Ripper will emit @kw instead of @ident if the object
|
529
|
+
# being aliased is a Ruby keyword. For instance, in the line "alias :foo :if",
|
530
|
+
# the :if is treated as a lexer keyword. So we need to know what symbols are
|
531
|
+
# also keywords.
|
532
|
+
RUBY_KEYWORDS = [
|
533
|
+
"alias",
|
534
|
+
"and",
|
535
|
+
"begin",
|
536
|
+
"BEGIN",
|
537
|
+
"break",
|
538
|
+
"case",
|
539
|
+
"class",
|
540
|
+
"def",
|
541
|
+
"defined?",
|
542
|
+
"do",
|
543
|
+
"else",
|
544
|
+
"elsif",
|
545
|
+
"end",
|
546
|
+
"END",
|
547
|
+
"ensure",
|
548
|
+
"false",
|
549
|
+
"for",
|
550
|
+
"if",
|
551
|
+
"in",
|
552
|
+
"module",
|
553
|
+
"next",
|
554
|
+
"nil",
|
555
|
+
"not",
|
556
|
+
"or",
|
557
|
+
"redo",
|
558
|
+
"rescue",
|
559
|
+
"retry",
|
560
|
+
"return",
|
561
|
+
"self",
|
562
|
+
"super",
|
563
|
+
"then",
|
564
|
+
"true",
|
565
|
+
"undef",
|
566
|
+
"unless",
|
567
|
+
"until",
|
568
|
+
"when",
|
569
|
+
"while",
|
570
|
+
"yield",
|
571
|
+
"__ENCODING__",
|
572
|
+
"__FILE__",
|
573
|
+
"__LINE__",
|
574
|
+
]
|
575
|
+
|
576
|
+
# Ripper has several methods of emitting a symbol literal. Inside an alias
|
577
|
+
# sometimes it suppresses the [:symbol] wrapper around ident. If the symbol
|
578
|
+
# is also the name of a keyword (e.g. :if) it will emit a :@kw wrapper, not
|
579
|
+
# an :@ident wrapper, with similar treatment for constants and operators.
|
580
|
+
def visit_symbol_literal_node(node, no_symbol_wrapper: false)
|
581
|
+
if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s"))
|
582
|
+
bounds(node.value_loc)
|
583
|
+
str_val = node.value.to_s
|
584
|
+
if str_val == ""
|
585
|
+
return on_dyna_symbol(on_string_content)
|
586
|
+
else
|
587
|
+
tstring_val = on_tstring_content(str_val)
|
588
|
+
return on_dyna_symbol(on_string_add(on_string_content, tstring_val))
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
bounds(node.value_loc)
|
593
|
+
node_name = node.value.to_s
|
594
|
+
if RUBY_KEYWORDS.include?(node_name)
|
595
|
+
token_val = on_kw(node_name)
|
596
|
+
elsif node_name.length == 0
|
597
|
+
raise NotImplementedError
|
598
|
+
elsif /[[:upper:]]/.match(node_name[0])
|
599
|
+
token_val = on_const(node_name)
|
600
|
+
elsif /[[:punct:]]/.match(node_name[0])
|
601
|
+
token_val = on_op(node_name)
|
602
|
+
else
|
603
|
+
token_val = on_ident(node_name)
|
604
|
+
end
|
605
|
+
sym_val = no_symbol_wrapper ? token_val : on_symbol(token_val)
|
606
|
+
on_symbol_literal(sym_val)
|
607
|
+
end
|
608
|
+
|
609
|
+
# Visit a node that represents a number. We need to explicitly handle the
|
610
|
+
# unary - operator.
|
611
|
+
def visit_number(node)
|
612
|
+
slice = node.slice
|
613
|
+
location = node.location
|
614
|
+
|
615
|
+
if slice[0] == "-"
|
616
|
+
bounds_values(location.start_line, location.start_column + 1)
|
617
|
+
value = yield slice[1..-1]
|
618
|
+
|
619
|
+
bounds(node.location)
|
620
|
+
on_unary(visit_unary_operator(:-@), value)
|
621
|
+
else
|
622
|
+
bounds(location)
|
623
|
+
yield slice
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0")
|
628
|
+
# JRuby before 9.4.6.0 uses :- for unary minus instead of :-@
|
629
|
+
def visit_unary_operator(value)
|
630
|
+
value == :-@ ? :- : value
|
631
|
+
end
|
632
|
+
else
|
633
|
+
# For most Rubies and JRuby after 9.4.6.0 this is a no-op.
|
634
|
+
def visit_unary_operator(value)
|
635
|
+
value
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
if RUBY_ENGINE == "jruby"
|
640
|
+
# For JRuby, "no block" in an on_block_var is nil
|
641
|
+
def no_block_value
|
642
|
+
nil
|
643
|
+
end
|
644
|
+
else
|
645
|
+
# For CRuby et al, "no block" in an on_block_var is false
|
646
|
+
def no_block_value
|
647
|
+
false
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# Visit a binary operator node like an AndNode or OrNode
|
652
|
+
def visit_binary_operator(node)
|
653
|
+
left_val = visit(node.left)
|
654
|
+
right_val = visit(node.right)
|
655
|
+
on_binary(left_val, node.operator.to_sym, right_val)
|
656
|
+
end
|
657
|
+
|
658
|
+
# This method is responsible for updating lineno and column information
|
659
|
+
# to reflect the current node.
|
660
|
+
#
|
661
|
+
# This method could be drastically improved with some caching on the start
|
662
|
+
# of every line, but for now it's good enough.
|
663
|
+
def bounds(location)
|
664
|
+
@lineno = location.start_line
|
665
|
+
@column = location.start_column
|
666
|
+
end
|
667
|
+
|
668
|
+
# If we need to do something unusual, we can directly update the line number
|
669
|
+
# and column to reflect the current node.
|
670
|
+
def bounds_values(lineno, column)
|
671
|
+
@lineno = lineno
|
672
|
+
@column = column
|
673
|
+
end
|
674
|
+
|
675
|
+
# Lazily initialize the parse result.
|
676
|
+
def result
|
677
|
+
@result ||= Prism.parse(source)
|
678
|
+
end
|
679
|
+
|
680
|
+
def _dispatch0; end # :nodoc:
|
681
|
+
def _dispatch1(_); end # :nodoc:
|
682
|
+
def _dispatch2(_, _); end # :nodoc:
|
683
|
+
def _dispatch3(_, _, _); end # :nodoc:
|
684
|
+
def _dispatch4(_, _, _, _); end # :nodoc:
|
685
|
+
def _dispatch5(_, _, _, _, _); end # :nodoc:
|
686
|
+
def _dispatch7(_, _, _, _, _, _, _); end # :nodoc:
|
687
|
+
|
688
|
+
alias_method :on_parse_error, :_dispatch1
|
689
|
+
alias_method :on_magic_comment, :_dispatch2
|
690
|
+
|
691
|
+
(::Ripper::SCANNER_EVENT_TABLE.merge(::Ripper::PARSER_EVENT_TABLE)).each do |event, arity|
|
692
|
+
alias_method :"on_#{event}", :"_dispatch#{arity}"
|
693
|
+
end
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|