prism 0.19.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -1
  3. data/Makefile +5 -0
  4. data/README.md +9 -6
  5. data/config.yml +236 -38
  6. data/docs/build_system.md +19 -2
  7. data/docs/cruby_compilation.md +27 -0
  8. data/docs/parser_translation.md +34 -0
  9. data/docs/parsing_rules.md +19 -0
  10. data/docs/releasing.md +84 -16
  11. data/docs/ruby_api.md +1 -1
  12. data/docs/ruby_parser_translation.md +19 -0
  13. data/docs/serialization.md +19 -5
  14. data/ext/prism/api_node.c +1989 -1525
  15. data/ext/prism/extension.c +130 -30
  16. data/ext/prism/extension.h +2 -2
  17. data/include/prism/ast.h +1700 -505
  18. data/include/prism/defines.h +8 -0
  19. data/include/prism/diagnostic.h +49 -7
  20. data/include/prism/encoding.h +17 -0
  21. data/include/prism/options.h +40 -14
  22. data/include/prism/parser.h +34 -18
  23. data/include/prism/util/pm_buffer.h +9 -0
  24. data/include/prism/util/pm_constant_pool.h +18 -0
  25. data/include/prism/util/pm_newline_list.h +4 -14
  26. data/include/prism/util/pm_strpbrk.h +4 -1
  27. data/include/prism/version.h +2 -2
  28. data/include/prism.h +19 -2
  29. data/lib/prism/debug.rb +11 -5
  30. data/lib/prism/desugar_compiler.rb +225 -80
  31. data/lib/prism/dot_visitor.rb +36 -14
  32. data/lib/prism/dsl.rb +302 -299
  33. data/lib/prism/ffi.rb +107 -76
  34. data/lib/prism/lex_compat.rb +17 -1
  35. data/lib/prism/node.rb +4580 -2607
  36. data/lib/prism/node_ext.rb +27 -4
  37. data/lib/prism/parse_result.rb +75 -29
  38. data/lib/prism/serialize.rb +633 -305
  39. data/lib/prism/translation/parser/compiler.rb +1838 -0
  40. data/lib/prism/translation/parser/lexer.rb +335 -0
  41. data/lib/prism/translation/parser/rubocop.rb +45 -0
  42. data/lib/prism/translation/parser.rb +190 -0
  43. data/lib/prism/translation/parser33.rb +12 -0
  44. data/lib/prism/translation/parser34.rb +12 -0
  45. data/lib/prism/translation/ripper.rb +696 -0
  46. data/lib/prism/translation/ruby_parser.rb +1521 -0
  47. data/lib/prism/translation.rb +11 -0
  48. data/lib/prism.rb +1 -1
  49. data/prism.gemspec +18 -7
  50. data/rbi/prism.rbi +150 -88
  51. data/rbi/prism_static.rbi +15 -3
  52. data/sig/prism.rbs +996 -961
  53. data/sig/prism_static.rbs +123 -46
  54. data/src/diagnostic.c +264 -219
  55. data/src/encoding.c +21 -26
  56. data/src/node.c +2 -6
  57. data/src/options.c +29 -5
  58. data/src/prettyprint.c +176 -44
  59. data/src/prism.c +1499 -564
  60. data/src/serialize.c +35 -21
  61. data/src/token_type.c +353 -4
  62. data/src/util/pm_buffer.c +11 -0
  63. data/src/util/pm_constant_pool.c +37 -11
  64. data/src/util/pm_newline_list.c +6 -15
  65. data/src/util/pm_string.c +0 -7
  66. data/src/util/pm_strpbrk.c +122 -14
  67. metadata +16 -5
  68. data/docs/building.md +0 -29
  69. data/lib/prism/ripper_compat.rb +0 -207
@@ -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