prism 0.22.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,577 @@
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 AndNode.
226
+ def visit_and_node(node)
227
+ visit_binary_operator(node)
228
+ end
229
+
230
+ # Visit an OrNode.
231
+ def visit_or_node(node)
232
+ visit_binary_operator(node)
233
+ end
234
+
235
+ # Visit a TrueNode.
236
+ def visit_true_node(node)
237
+ bounds(node.location)
238
+ on_var_ref(on_kw("true"))
239
+ end
240
+
241
+ # Visit a FalseNode.
242
+ def visit_false_node(node)
243
+ bounds(node.location)
244
+ on_var_ref(on_kw("false"))
245
+ end
246
+
247
+ # Visit a FloatNode node.
248
+ def visit_float_node(node)
249
+ visit_number(node) { |text| on_float(text) }
250
+ end
251
+
252
+ # Visit a ImaginaryNode node.
253
+ def visit_imaginary_node(node)
254
+ visit_number(node) { |text| on_imaginary(text) }
255
+ end
256
+
257
+ # Visit an IntegerNode node.
258
+ def visit_integer_node(node)
259
+ visit_number(node) { |text| on_int(text) }
260
+ end
261
+
262
+ # Visit a ParenthesesNode node.
263
+ def visit_parentheses_node(node)
264
+ body =
265
+ if node.body.nil?
266
+ on_stmts_add(on_stmts_new, on_void_stmt)
267
+ else
268
+ visit(node.body)
269
+ end
270
+
271
+ bounds(node.location)
272
+ on_paren(body)
273
+ end
274
+
275
+ # Visit a BeginNode node.
276
+ # This is not at all bulletproof against different structures of begin/rescue/else/ensure/end.
277
+ def visit_begin_node(node)
278
+ rescue_val = node.rescue_clause ? on_rescue(nil, nil, visit(node.rescue_clause), nil) : nil
279
+ ensure_val = node.ensure_clause ? on_ensure(visit(node.ensure_clause.statements)) : nil
280
+ on_begin(on_bodystmt(visit(node.statements), rescue_val, nil, ensure_val))
281
+ end
282
+
283
+ # Visit a RescueNode node.
284
+ def visit_rescue_node(node)
285
+ visit(node.statements)
286
+ end
287
+
288
+ # Visit a ProgramNode node.
289
+ def visit_program_node(node)
290
+ statements = visit(node.statements)
291
+ bounds(node.location)
292
+ on_program(statements)
293
+ end
294
+
295
+ # Visit a RangeNode node.
296
+ def visit_range_node(node)
297
+ left = visit(node.left)
298
+ right = visit(node.right)
299
+
300
+ bounds(node.location)
301
+ if node.exclude_end?
302
+ on_dot3(left, right)
303
+ else
304
+ on_dot2(left, right)
305
+ end
306
+ end
307
+
308
+ # Visit a RationalNode node.
309
+ def visit_rational_node(node)
310
+ visit_number(node) { |text| on_rational(text) }
311
+ end
312
+
313
+ # Visit a StringNode node.
314
+ def visit_string_node(node)
315
+ bounds(node.content_loc)
316
+ tstring_val = on_tstring_content(node.unescaped.to_s)
317
+ on_string_literal(on_string_add(on_string_content, tstring_val))
318
+ end
319
+
320
+ # Visit an XStringNode node.
321
+ def visit_x_string_node(node)
322
+ bounds(node.content_loc)
323
+ tstring_val = on_tstring_content(node.unescaped.to_s)
324
+ on_xstring_literal(on_xstring_add(on_xstring_new, tstring_val))
325
+ end
326
+
327
+ # Visit an InterpolatedStringNode node.
328
+ def visit_interpolated_string_node(node)
329
+ parts = node.parts.map do |part|
330
+ case part
331
+ when StringNode
332
+ bounds(part.content_loc)
333
+ on_tstring_content(part.content)
334
+ when EmbeddedStatementsNode
335
+ on_string_embexpr(visit(part))
336
+ else
337
+ raise NotImplementedError, "Unexpected node type in InterpolatedStringNode"
338
+ end
339
+ end
340
+
341
+ string_list = parts.inject(on_string_content) do |items, item|
342
+ on_string_add(items, item)
343
+ end
344
+
345
+ on_string_literal(string_list)
346
+ end
347
+
348
+ # Visit an EmbeddedStatementsNode node.
349
+ def visit_embedded_statements_node(node)
350
+ visit(node.statements)
351
+ end
352
+
353
+ # Visit a SymbolNode node.
354
+ def visit_symbol_node(node)
355
+ if (opening = node.opening) && (['"', "'"].include?(opening[-1]) || opening.start_with?("%s"))
356
+ bounds(node.value_loc)
357
+ tstring_val = on_tstring_content(node.value.to_s)
358
+ return on_dyna_symbol(on_string_add(on_string_content, tstring_val))
359
+ end
360
+
361
+ bounds(node.value_loc)
362
+ ident_val = on_ident(node.value.to_s)
363
+ on_symbol_literal(on_symbol(ident_val))
364
+ end
365
+
366
+ # Visit a StatementsNode node.
367
+ def visit_statements_node(node)
368
+ bounds(node.location)
369
+ node.body.inject(on_stmts_new) do |stmts, stmt|
370
+ on_stmts_add(stmts, visit(stmt))
371
+ end
372
+ end
373
+
374
+ ############################################################################
375
+ # Entrypoints for subclasses
376
+ ############################################################################
377
+
378
+ # This is a convenience method that runs the SexpBuilder subclass parser.
379
+ def self.sexp_raw(source)
380
+ SexpBuilder.new(source).parse
381
+ end
382
+
383
+ # This is a convenience method that runs the SexpBuilderPP subclass parser.
384
+ def self.sexp(source)
385
+ SexpBuilderPP.new(source).parse
386
+ end
387
+
388
+ private
389
+
390
+ # Generate Ripper events for a CallNode with no opening_loc
391
+ def visit_no_paren_call(node)
392
+ # No opening_loc can mean an operator. It can also mean a
393
+ # method call with no parentheses.
394
+ if node.message.match?(/^[[:punct:]]/)
395
+ left = visit(node.receiver)
396
+ if node.arguments&.arguments&.length == 1
397
+ right = visit(node.arguments.arguments.first)
398
+
399
+ return on_binary(left, node.name, right)
400
+ elsif !node.arguments || node.arguments.empty?
401
+ return on_unary(node.name, left)
402
+ else
403
+ raise NotImplementedError, "More than two arguments for operator"
404
+ end
405
+ elsif node.call_operator_loc.nil?
406
+ # In Ripper a method call like "puts myvar" with no parentheses is a "command".
407
+ bounds(node.message_loc)
408
+ ident_val = on_ident(node.message)
409
+
410
+ # Unless it has a block, and then it's an fcall (e.g. "foo { bar }")
411
+ if node.block
412
+ block_val = visit(node.block)
413
+ # In these calls, even if node.arguments is nil, we still get an :args_new call.
414
+ args = if node.arguments.nil?
415
+ on_args_new
416
+ else
417
+ on_args_add_block(visit_elements(node.arguments.arguments))
418
+ end
419
+ method_args_val = on_method_add_arg(on_fcall(ident_val), args)
420
+ return on_method_add_block(method_args_val, block_val)
421
+ else
422
+ if node.arguments.nil?
423
+ return on_command(ident_val, nil)
424
+ else
425
+ args = on_args_add_block(visit_elements(node.arguments.arguments), false)
426
+ return on_command(ident_val, args)
427
+ end
428
+ end
429
+ else
430
+ operator = node.call_operator_loc.slice
431
+ if operator == "." || operator == "&."
432
+ left_val = visit(node.receiver)
433
+
434
+ bounds(node.call_operator_loc)
435
+ operator_val = operator == "." ? on_period(node.call_operator) : on_op(node.call_operator)
436
+
437
+ bounds(node.message_loc)
438
+ right_val = on_ident(node.message)
439
+
440
+ call_val = on_call(left_val, operator_val, right_val)
441
+
442
+ if node.block
443
+ block_val = visit(node.block)
444
+ return on_method_add_block(call_val, block_val)
445
+ else
446
+ return call_val
447
+ end
448
+ else
449
+ raise NotImplementedError, "operator other than . or &. for call: #{operator.inspect}"
450
+ end
451
+ end
452
+ end
453
+
454
+ # Visit a list of elements, like the elements of an array or arguments.
455
+ def visit_elements(elements)
456
+ bounds(elements.first.location)
457
+ elements.inject(on_args_new) do |args, element|
458
+ on_args_add(args, visit(element))
459
+ end
460
+ end
461
+
462
+ # Visit an operation-and-assign node, such as +=.
463
+ def visit_binary_op_assign(node, operator: node.operator)
464
+ bounds(node.name_loc)
465
+ ident_val = on_ident(node.name.to_s)
466
+
467
+ bounds(node.operator_loc)
468
+ op_val = on_op(operator)
469
+
470
+ on_opassign(on_var_field(ident_val), op_val, visit(node.value))
471
+ end
472
+
473
+ # In Prism this is a CallNode with :[] as the operator.
474
+ # In Ripper it's an :aref.
475
+ def visit_aref_node(node)
476
+ first_arg_val = visit(node.arguments.arguments[0])
477
+ args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false)
478
+ on_aref(visit(node.receiver), args_val)
479
+ end
480
+
481
+ # In Prism this is a CallNode with :[]= as the operator.
482
+ # In Ripper it's an :aref_field.
483
+ def visit_aref_field_node(node)
484
+ first_arg_val = visit(node.arguments.arguments[0])
485
+ args_val = on_args_add_block(on_args_add(on_args_new, first_arg_val), false)
486
+ assign_val = visit(node.arguments.arguments[1])
487
+ on_assign(on_aref_field(visit(node.receiver), args_val), assign_val)
488
+ end
489
+
490
+ # Visit a node that represents a number. We need to explicitly handle the
491
+ # unary - operator.
492
+ def visit_number(node)
493
+ slice = node.slice
494
+ location = node.location
495
+
496
+ if slice[0] == "-"
497
+ bounds_values(location.start_line, location.start_column + 1)
498
+ value = yield slice[1..-1]
499
+
500
+ bounds(node.location)
501
+ on_unary(visit_unary_operator(:-@), value)
502
+ else
503
+ bounds(location)
504
+ yield slice
505
+ end
506
+ end
507
+
508
+ if RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) < Gem::Version.new("9.4.6.0")
509
+ # JRuby before 9.4.6.0 uses :- for unary minus instead of :-@
510
+ def visit_unary_operator(value)
511
+ value == :-@ ? :- : value
512
+ end
513
+ else
514
+ # For most Rubies and JRuby after 9.4.6.0 this is a no-op.
515
+ def visit_unary_operator(value)
516
+ value
517
+ end
518
+ end
519
+
520
+ if RUBY_ENGINE == "jruby"
521
+ # For JRuby, "no block" in an on_block_var is nil
522
+ def no_block_value
523
+ nil
524
+ end
525
+ else
526
+ # For CRuby et al, "no block" in an on_block_var is false
527
+ def no_block_value
528
+ false
529
+ end
530
+ end
531
+
532
+ # Visit a binary operator node like an AndNode or OrNode
533
+ def visit_binary_operator(node)
534
+ left_val = visit(node.left)
535
+ right_val = visit(node.right)
536
+ on_binary(left_val, node.operator.to_sym, right_val)
537
+ end
538
+
539
+ # This method is responsible for updating lineno and column information
540
+ # to reflect the current node.
541
+ #
542
+ # This method could be drastically improved with some caching on the start
543
+ # of every line, but for now it's good enough.
544
+ def bounds(location)
545
+ @lineno = location.start_line
546
+ @column = location.start_column
547
+ end
548
+
549
+ # If we need to do something unusual, we can directly update the line number
550
+ # and column to reflect the current node.
551
+ def bounds_values(lineno, column)
552
+ @lineno = lineno
553
+ @column = column
554
+ end
555
+
556
+ # Lazily initialize the parse result.
557
+ def result
558
+ @result ||= Prism.parse(source)
559
+ end
560
+
561
+ def _dispatch0; end # :nodoc:
562
+ def _dispatch1(_); end # :nodoc:
563
+ def _dispatch2(_, _); end # :nodoc:
564
+ def _dispatch3(_, _, _); end # :nodoc:
565
+ def _dispatch4(_, _, _, _); end # :nodoc:
566
+ def _dispatch5(_, _, _, _, _); end # :nodoc:
567
+ def _dispatch7(_, _, _, _, _, _, _); end # :nodoc:
568
+
569
+ alias_method :on_parse_error, :_dispatch1
570
+ alias_method :on_magic_comment, :_dispatch2
571
+
572
+ (::Ripper::SCANNER_EVENT_TABLE.merge(::Ripper::PARSER_EVENT_TABLE)).each do |event, arity|
573
+ alias_method :"on_#{event}", :"_dispatch#{arity}"
574
+ end
575
+ end
576
+ end
577
+ end