syntax_tree 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3098 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ class Parser < Ripper
5
+ # A special parser error so that we can get nice syntax displays on the
6
+ # error message when prettier prints out the results.
7
+ class ParseError < StandardError
8
+ attr_reader :lineno, :column
9
+
10
+ def initialize(error, lineno, column)
11
+ super(error)
12
+ @lineno = lineno
13
+ @column = column
14
+ end
15
+ end
16
+
17
+ # Represents a line in the source. If this class is being used, it means
18
+ # that every character in the string is 1 byte in length, so we can just
19
+ # return the start of the line + the index.
20
+ class SingleByteString
21
+ attr_reader :start
22
+
23
+ def initialize(start)
24
+ @start = start
25
+ end
26
+
27
+ def [](byteindex)
28
+ start + byteindex
29
+ end
30
+ end
31
+
32
+ # Represents a line in the source. If this class is being used, it means
33
+ # that there are characters in the string that are multi-byte, so we will
34
+ # build up an array of indices, such that array[byteindex] will be equal to
35
+ # the index of the character within the string.
36
+ class MultiByteString
37
+ attr_reader :start, :indices
38
+
39
+ def initialize(start, line)
40
+ @start = start
41
+ @indices = []
42
+
43
+ line.each_char.with_index(start) do |char, index|
44
+ char.bytesize.times { @indices << index }
45
+ end
46
+ end
47
+
48
+ # Technically it's possible for the column index to be a negative value if
49
+ # there's a BOM at the beginning of the file, which is the reason we need
50
+ # to compare it to 0 here.
51
+ def [](byteindex)
52
+ indices[byteindex < 0 ? 0 : byteindex]
53
+ end
54
+ end
55
+
56
+ # [String] the source being parsed
57
+ attr_reader :source
58
+
59
+ # [Array[ String ]] the list of lines in the source
60
+ attr_reader :lines
61
+
62
+ # [Array[ SingleByteString | MultiByteString ]] the list of objects that
63
+ # represent the start of each line in character offsets
64
+ attr_reader :line_counts
65
+
66
+ # [Array[ untyped ]] a running list of tokens that have been found in the
67
+ # source. This list changes a lot as certain nodes will "consume" these
68
+ # tokens to determine their bounds.
69
+ attr_reader :tokens
70
+
71
+ # [Array[ Comment | EmbDoc ]] the list of comments that have been found
72
+ # while parsing the source.
73
+ attr_reader :comments
74
+
75
+ def initialize(source, *)
76
+ super
77
+
78
+ # We keep the source around so that we can refer back to it when we're
79
+ # generating the AST. Sometimes it's easier to just reference the source
80
+ # string when you want to check if it contains a certain character, for
81
+ # example.
82
+ @source = source
83
+
84
+ # Similarly, we keep the lines of the source string around to be able to
85
+ # check if certain lines contain certain characters. For example, we'll
86
+ # use this to generate the content that goes after the __END__ keyword.
87
+ # Or we'll use this to check if a comment has other content on its line.
88
+ @lines = source.split(/\r?\n/)
89
+
90
+ # This is the full set of comments that have been found by the parser.
91
+ # It's a running list. At the end of every block of statements, they will
92
+ # go in and attempt to grab any comments that are on their own line and
93
+ # turn them into regular statements. So at the end of parsing the only
94
+ # comments left in here will be comments on lines that also contain code.
95
+ @comments = []
96
+
97
+ # This is the current embdoc (comments that start with =begin and end with
98
+ # =end). Since they can't be nested, there's no need for a stack here, as
99
+ # there can only be one active. These end up getting dumped into the
100
+ # comments list before getting picked up by the statements that surround
101
+ # them.
102
+ @embdoc = nil
103
+
104
+ # This is an optional node that can be present if the __END__ keyword is
105
+ # used in the file. In that case, this will represent the content after
106
+ # that keyword.
107
+ @__end__ = nil
108
+
109
+ # Heredocs can actually be nested together if you're using interpolation,
110
+ # so this is a stack of heredoc nodes that are currently being created.
111
+ # When we get to the token that finishes off a heredoc node, we pop the
112
+ # top one off. If there are others surrounding it, then the body events
113
+ # will now be added to the correct nodes.
114
+ @heredocs = []
115
+
116
+ # This is a running list of tokens that have fired. It's useful mostly for
117
+ # maintaining location information. For example, if you're inside the
118
+ # handle of a def event, then in order to determine where the AST node
119
+ # started, you need to look backward in the tokens to find a def keyword.
120
+ # Most of the time, when a parser event consumes one of these events, it
121
+ # will be deleted from the list. So ideally, this list stays pretty short
122
+ # over the course of parsing a source string.
123
+ @tokens = []
124
+
125
+ # Here we're going to build up a list of SingleByteString or
126
+ # MultiByteString objects. They're each going to represent a string in the
127
+ # source. They are used by the `char_pos` method to determine where we are
128
+ # in the source string.
129
+ @line_counts = []
130
+ last_index = 0
131
+
132
+ @source.lines.each do |line|
133
+ if line.size == line.bytesize
134
+ @line_counts << SingleByteString.new(last_index)
135
+ else
136
+ @line_counts << MultiByteString.new(last_index, line)
137
+ end
138
+
139
+ last_index += line.size
140
+ end
141
+
142
+ # Make sure line counts is filled out with the first and last line at
143
+ # minimum so that it has something to compare against if the parser is in
144
+ # a lineno=2 state for an empty file.
145
+ @line_counts << SingleByteString.new(0) if @line_counts.empty?
146
+ @line_counts << SingleByteString.new(last_index)
147
+ end
148
+
149
+ private
150
+
151
+ # --------------------------------------------------------------------------
152
+ # :section: Helper methods
153
+ # The following methods are used by the ripper event handlers to either
154
+ # determine their bounds or query other nodes.
155
+ # --------------------------------------------------------------------------
156
+
157
+ # This represents the current place in the source string that we've gotten
158
+ # to so far. We have a memoized line_counts object that we can use to get
159
+ # the number of characters that we've had to go through to get to the
160
+ # beginning of this line, then we add the number of columns into this line
161
+ # that we've gone through.
162
+ def char_pos
163
+ line_counts[lineno - 1][column]
164
+ end
165
+
166
+ # As we build up a list of tokens, we'll periodically need to go backwards
167
+ # and find the ones that we've already hit in order to determine the
168
+ # location information for nodes that use them. For example, if you have a
169
+ # module node then you'll look backward for a kw token to determine your
170
+ # start location.
171
+ #
172
+ # This works with nesting since we're deleting tokens from the list once
173
+ # they've been used up. For example if you had nested module declarations
174
+ # then the innermost declaration would grab the last kw node that matches
175
+ # "module" (which would happen to be the innermost keyword). Then the outer
176
+ # one would only be able to grab the first one. In this way all of the
177
+ # tokens act as their own stack.
178
+ def find_token(type, value = :any, consume: true, location: nil)
179
+ index =
180
+ tokens.rindex do |token|
181
+ token.is_a?(type) && (value == :any || (token.value == value))
182
+ end
183
+
184
+ if consume
185
+ # If we're expecting to be able to find a token and consume it, but
186
+ # can't actually find it, then we need to raise an error. This is
187
+ # _usually_ caused by a syntax error in the source that we're printing.
188
+ # It could also be caused by accidentally attempting to consume a token
189
+ # twice by two different parser event handlers.
190
+ unless index
191
+ token = value == :any ? type.name.split("::", 2).last : value
192
+ message = "Cannot find expected #{token}"
193
+
194
+ if location
195
+ lineno = location.start_line
196
+ column = location.start_char - line_counts[lineno - 1].start
197
+ raise ParseError.new(message, lineno, column)
198
+ else
199
+ raise ParseError.new(message, lineno, column)
200
+ end
201
+ end
202
+
203
+ tokens.delete_at(index)
204
+ elsif index
205
+ tokens[index]
206
+ end
207
+ end
208
+
209
+ # A helper function to find a :: operator. We do special handling instead of
210
+ # using find_token here because we don't pop off all of the :: operators so
211
+ # you could end up getting the wrong information if you have for instance
212
+ # ::X::Y::Z.
213
+ def find_colon2_before(const)
214
+ index =
215
+ tokens.rindex do |token|
216
+ token.is_a?(Op) && token.value == "::" &&
217
+ token.location.start_char < const.location.start_char
218
+ end
219
+
220
+ tokens[index]
221
+ end
222
+
223
+ # Finds the next position in the source string that begins a statement. This
224
+ # is used to bind statements lists and make sure they don't include a
225
+ # preceding comment. For example, we want the following comment to be
226
+ # attached to the class node and not the statement node:
227
+ #
228
+ # class Foo # :nodoc:
229
+ # ...
230
+ # end
231
+ #
232
+ # By finding the next non-space character, we can make sure that the bounds
233
+ # of the statement list are correct.
234
+ def find_next_statement_start(position)
235
+ remaining = source[position..-1]
236
+
237
+ if remaining.sub(/\A +/, "")[0] == "#"
238
+ return position + remaining.index("\n")
239
+ end
240
+
241
+ position
242
+ end
243
+
244
+ # --------------------------------------------------------------------------
245
+ # :section: Ripper event handlers
246
+ # The following methods all handle a dispatched ripper event.
247
+ # --------------------------------------------------------------------------
248
+
249
+ # :call-seq:
250
+ # on_BEGIN: (Statements statements) -> BEGINBlock
251
+ def on_BEGIN(statements)
252
+ lbrace = find_token(LBrace)
253
+ rbrace = find_token(RBrace)
254
+
255
+ statements.bind(
256
+ find_next_statement_start(lbrace.location.end_char),
257
+ rbrace.location.start_char
258
+ )
259
+
260
+ keyword = find_token(Kw, "BEGIN")
261
+
262
+ BEGINBlock.new(
263
+ lbrace: lbrace,
264
+ statements: statements,
265
+ location: keyword.location.to(rbrace.location)
266
+ )
267
+ end
268
+
269
+ # :call-seq:
270
+ # on_CHAR: (String value) -> CHAR
271
+ def on_CHAR(value)
272
+ CHAR.new(
273
+ value: value,
274
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
275
+ )
276
+ end
277
+
278
+ # :call-seq:
279
+ # on_END: (Statements statements) -> ENDBlock
280
+ def on_END(statements)
281
+ lbrace = find_token(LBrace)
282
+ rbrace = find_token(RBrace)
283
+
284
+ statements.bind(
285
+ find_next_statement_start(lbrace.location.end_char),
286
+ rbrace.location.start_char
287
+ )
288
+
289
+ keyword = find_token(Kw, "END")
290
+
291
+ ENDBlock.new(
292
+ lbrace: lbrace,
293
+ statements: statements,
294
+ location: keyword.location.to(rbrace.location)
295
+ )
296
+ end
297
+
298
+ # :call-seq:
299
+ # on___end__: (String value) -> EndContent
300
+ def on___end__(value)
301
+ @__end__ =
302
+ EndContent.new(
303
+ value: source[(char_pos + value.length)..-1],
304
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
305
+ )
306
+ end
307
+
308
+ # :call-seq:
309
+ # on_alias: (
310
+ # (DynaSymbol | SymbolLiteral) left,
311
+ # (DynaSymbol | SymbolLiteral) right
312
+ # ) -> Alias
313
+ def on_alias(left, right)
314
+ keyword = find_token(Kw, "alias")
315
+
316
+ Alias.new(
317
+ left: left,
318
+ right: right,
319
+ location: keyword.location.to(right.location)
320
+ )
321
+ end
322
+
323
+ # :call-seq:
324
+ # on_aref: (untyped collection, (nil | Args) index) -> ARef
325
+ def on_aref(collection, index)
326
+ find_token(LBracket)
327
+ rbracket = find_token(RBracket)
328
+
329
+ ARef.new(
330
+ collection: collection,
331
+ index: index,
332
+ location: collection.location.to(rbracket.location)
333
+ )
334
+ end
335
+
336
+ # :call-seq:
337
+ # on_aref_field: (
338
+ # untyped collection,
339
+ # (nil | Args) index
340
+ # ) -> ARefField
341
+ def on_aref_field(collection, index)
342
+ find_token(LBracket)
343
+ rbracket = find_token(RBracket)
344
+
345
+ ARefField.new(
346
+ collection: collection,
347
+ index: index,
348
+ location: collection.location.to(rbracket.location)
349
+ )
350
+ end
351
+
352
+ # def on_arg_ambiguous(value)
353
+ # value
354
+ # end
355
+
356
+ # :call-seq:
357
+ # on_arg_paren: (
358
+ # (nil | Args | ArgsForward) arguments
359
+ # ) -> ArgParen
360
+ def on_arg_paren(arguments)
361
+ lparen = find_token(LParen)
362
+ rparen = find_token(RParen)
363
+
364
+ # If the arguments exceed the ending of the parentheses, then we know we
365
+ # have a heredoc in the arguments, and we need to use the bounds of the
366
+ # arguments to determine how large the arg_paren is.
367
+ ending =
368
+ if arguments && arguments.location.end_line > rparen.location.end_line
369
+ arguments
370
+ else
371
+ rparen
372
+ end
373
+
374
+ ArgParen.new(
375
+ arguments: arguments,
376
+ location: lparen.location.to(ending.location)
377
+ )
378
+ end
379
+
380
+ # :call-seq:
381
+ # on_args_add: (Args arguments, untyped argument) -> Args
382
+ def on_args_add(arguments, argument)
383
+ if arguments.parts.empty?
384
+ # If this is the first argument being passed into the list of arguments,
385
+ # then we're going to use the bounds of the argument to override the
386
+ # parent node's location since this will be more accurate.
387
+ Args.new(parts: [argument], location: argument.location)
388
+ else
389
+ # Otherwise we're going to update the existing list with the argument
390
+ # being added as well as the new end bounds.
391
+ Args.new(
392
+ parts: arguments.parts << argument,
393
+ location: arguments.location.to(argument.location)
394
+ )
395
+ end
396
+ end
397
+
398
+ # :call-seq:
399
+ # on_args_add_block: (
400
+ # Args arguments,
401
+ # (false | untyped) block
402
+ # ) -> Args
403
+ def on_args_add_block(arguments, block)
404
+ operator = find_token(Op, "&", consume: false)
405
+
406
+ # If we can't find the & operator, then there's no block to add to the
407
+ # list, so we're just going to return the arguments as-is.
408
+ return arguments unless operator
409
+
410
+ # Now we know we have an & operator, so we're going to delete it from the
411
+ # list of tokens to make sure it doesn't get confused with anything else.
412
+ tokens.delete(operator)
413
+
414
+ # Construct the location that represents the block argument.
415
+ location = operator.location
416
+ location = operator.location.to(block.location) if block
417
+
418
+ # If there are any arguments and the operator we found from the list is
419
+ # not after them, then we're going to return the arguments as-is because
420
+ # we're looking at an & that occurs before the arguments are done.
421
+ if arguments.parts.any? && location.start_char < arguments.location.end_char
422
+ return arguments
423
+ end
424
+
425
+ # Otherwise, we're looking at an actual block argument (with or without a
426
+ # block, which could be missing because it could be a bare & since 3.1.0).
427
+ arg_block = ArgBlock.new(value: block, location: location)
428
+
429
+ Args.new(
430
+ parts: arguments.parts << arg_block,
431
+ location: arguments.location.to(location)
432
+ )
433
+ end
434
+
435
+ # :call-seq:
436
+ # on_args_add_star: (Args arguments, untyped star) -> Args
437
+ def on_args_add_star(arguments, argument)
438
+ beginning = find_token(Op, "*")
439
+ ending = argument || beginning
440
+
441
+ location =
442
+ if arguments.parts.empty?
443
+ ending.location
444
+ else
445
+ arguments.location.to(ending.location)
446
+ end
447
+
448
+ arg_star =
449
+ ArgStar.new(
450
+ value: argument,
451
+ location: beginning.location.to(ending.location)
452
+ )
453
+
454
+ Args.new(parts: arguments.parts << arg_star, location: location)
455
+ end
456
+
457
+ # :call-seq:
458
+ # on_args_forward: () -> ArgsForward
459
+ def on_args_forward
460
+ op = find_token(Op, "...")
461
+
462
+ ArgsForward.new(value: op.value, location: op.location)
463
+ end
464
+
465
+ # :call-seq:
466
+ # on_args_new: () -> Args
467
+ def on_args_new
468
+ Args.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
469
+ end
470
+
471
+ # :call-seq:
472
+ # on_array: ((nil | Args) contents) ->
473
+ # ArrayLiteral | QSymbols | QWords | Symbols | Words
474
+ def on_array(contents)
475
+ if !contents || contents.is_a?(Args)
476
+ lbracket = find_token(LBracket)
477
+ rbracket = find_token(RBracket)
478
+
479
+ ArrayLiteral.new(
480
+ lbracket: lbracket,
481
+ contents: contents,
482
+ location: lbracket.location.to(rbracket.location)
483
+ )
484
+ else
485
+ tstring_end =
486
+ find_token(TStringEnd, location: contents.beginning.location)
487
+
488
+ contents.class.new(
489
+ beginning: contents.beginning,
490
+ elements: contents.elements,
491
+ location: contents.location.to(tstring_end.location)
492
+ )
493
+ end
494
+ end
495
+
496
+ # :call-seq:
497
+ # on_aryptn: (
498
+ # (nil | VarRef) constant,
499
+ # (nil | Array[untyped]) requireds,
500
+ # (nil | VarField) rest,
501
+ # (nil | Array[untyped]) posts
502
+ # ) -> AryPtn
503
+ def on_aryptn(constant, requireds, rest, posts)
504
+ parts = [constant, *requireds, rest, *posts].compact
505
+
506
+ AryPtn.new(
507
+ constant: constant,
508
+ requireds: requireds || [],
509
+ rest: rest,
510
+ posts: posts || [],
511
+ location: parts[0].location.to(parts[-1].location)
512
+ )
513
+ end
514
+
515
+ # :call-seq:
516
+ # on_assign: (
517
+ # (ARefField | ConstPathField | Field | TopConstField | VarField) target,
518
+ # untyped value
519
+ # ) -> Assign
520
+ def on_assign(target, value)
521
+ Assign.new(
522
+ target: target,
523
+ value: value,
524
+ location: target.location.to(value.location)
525
+ )
526
+ end
527
+
528
+ # :call-seq:
529
+ # on_assoc_new: (untyped key, untyped value) -> Assoc
530
+ def on_assoc_new(key, value)
531
+ location = key.location
532
+ location = location.to(value.location) if value
533
+
534
+ Assoc.new(key: key, value: value, location: location)
535
+ end
536
+
537
+ # :call-seq:
538
+ # on_assoc_splat: (untyped value) -> AssocSplat
539
+ def on_assoc_splat(value)
540
+ operator = find_token(Op, "**")
541
+
542
+ AssocSplat.new(value: value, location: operator.location.to(value.location))
543
+ end
544
+
545
+ # def on_assoclist_from_args(assocs)
546
+ # assocs
547
+ # end
548
+
549
+ # :call-seq:
550
+ # on_backref: (String value) -> Backref
551
+ def on_backref(value)
552
+ Backref.new(
553
+ value: value,
554
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
555
+ )
556
+ end
557
+
558
+ # :call-seq:
559
+ # on_backtick: (String value) -> Backtick
560
+ def on_backtick(value)
561
+ node =
562
+ Backtick.new(
563
+ value: value,
564
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
565
+ )
566
+
567
+ tokens << node
568
+ node
569
+ end
570
+
571
+ # :call-seq:
572
+ # on_bare_assoc_hash: (Array[AssocNew | AssocSplat] assocs) -> BareAssocHash
573
+ def on_bare_assoc_hash(assocs)
574
+ BareAssocHash.new(
575
+ assocs: assocs,
576
+ location: assocs[0].location.to(assocs[-1].location)
577
+ )
578
+ end
579
+
580
+ # :call-seq:
581
+ # on_begin: (untyped bodystmt) -> Begin | PinnedBegin
582
+ def on_begin(bodystmt)
583
+ pin = find_token(Op, "^", consume: false)
584
+
585
+ if pin && pin.location.start_char < bodystmt.location.start_char
586
+ tokens.delete(pin)
587
+ find_token(LParen)
588
+
589
+ rparen = find_token(RParen)
590
+ location = pin.location.to(rparen.location)
591
+
592
+ PinnedBegin.new(statement: bodystmt, location: location)
593
+ else
594
+ keyword = find_token(Kw, "begin")
595
+ end_char =
596
+ if bodystmt.rescue_clause || bodystmt.ensure_clause ||
597
+ bodystmt.else_clause
598
+ bodystmt.location.end_char
599
+ else
600
+ find_token(Kw, "end").location.end_char
601
+ end
602
+
603
+ bodystmt.bind(keyword.location.end_char, end_char)
604
+ location = keyword.location.to(bodystmt.location)
605
+
606
+ Begin.new(bodystmt: bodystmt, location: location)
607
+ end
608
+ end
609
+
610
+ # :call-seq:
611
+ # on_binary: (untyped left, (Op | Symbol) operator, untyped right) -> Binary
612
+ def on_binary(left, operator, right)
613
+ if operator.is_a?(Symbol)
614
+ # Here, we're going to search backward for the token that's between the
615
+ # two operands that matches the operator so we can delete it from the
616
+ # list.
617
+ index =
618
+ tokens.rindex do |token|
619
+ location = token.location
620
+
621
+ token.is_a?(Op) && token.value == operator.to_s &&
622
+ location.start_char > left.location.end_char &&
623
+ location.end_char < right.location.start_char
624
+ end
625
+
626
+ tokens.delete_at(index) if index
627
+ else
628
+ # On most Ruby implementations, operator is a Symbol that represents
629
+ # that operation being performed. For instance in the example `1 < 2`,
630
+ # the `operator` object would be `:<`. However, on JRuby, it's an `@op`
631
+ # node, so here we're going to explicitly convert it into the same
632
+ # normalized form.
633
+ operator = tokens.delete(operator).value
634
+ end
635
+
636
+ Binary.new(
637
+ left: left,
638
+ operator: operator,
639
+ right: right,
640
+ location: left.location.to(right.location)
641
+ )
642
+ end
643
+
644
+ # :call-seq:
645
+ # on_block_var: (Params params, (nil | Array[Ident]) locals) -> BlockVar
646
+ def on_block_var(params, locals)
647
+ index =
648
+ tokens.rindex do |node|
649
+ node.is_a?(Op) && %w[| ||].include?(node.value) &&
650
+ node.location.start_char < params.location.start_char
651
+ end
652
+
653
+ beginning = tokens[index]
654
+ ending = tokens[-1]
655
+
656
+ BlockVar.new(
657
+ params: params,
658
+ locals: locals || [],
659
+ location: beginning.location.to(ending.location)
660
+ )
661
+ end
662
+
663
+ # :call-seq:
664
+ # on_blockarg: (Ident name) -> BlockArg
665
+ def on_blockarg(name)
666
+ operator = find_token(Op, "&")
667
+
668
+ location = operator.location
669
+ location = location.to(name.location) if name
670
+
671
+ BlockArg.new(name: name, location: location)
672
+ end
673
+
674
+ # :call-seq:
675
+ # on_bodystmt: (
676
+ # Statements statements,
677
+ # (nil | Rescue) rescue_clause,
678
+ # (nil | Statements) else_clause,
679
+ # (nil | Ensure) ensure_clause
680
+ # ) -> BodyStmt
681
+ def on_bodystmt(statements, rescue_clause, else_clause, ensure_clause)
682
+ BodyStmt.new(
683
+ statements: statements,
684
+ rescue_clause: rescue_clause,
685
+ else_clause: else_clause,
686
+ ensure_clause: ensure_clause,
687
+ location: Location.fixed(line: lineno, char: char_pos)
688
+ )
689
+ end
690
+
691
+ # :call-seq:
692
+ # on_brace_block: (
693
+ # (nil | BlockVar) block_var,
694
+ # Statements statements
695
+ # ) -> BraceBlock
696
+ def on_brace_block(block_var, statements)
697
+ lbrace = find_token(LBrace)
698
+ rbrace = find_token(RBrace)
699
+
700
+ statements.bind(
701
+ find_next_statement_start((block_var || lbrace).location.end_char),
702
+ rbrace.location.start_char
703
+ )
704
+
705
+ location =
706
+ Location.new(
707
+ start_line: lbrace.location.start_line,
708
+ start_char: lbrace.location.start_char,
709
+ end_line: [rbrace.location.end_line, statements.location.end_line].max,
710
+ end_char: rbrace.location.end_char
711
+ )
712
+
713
+ BraceBlock.new(
714
+ lbrace: lbrace,
715
+ block_var: block_var,
716
+ statements: statements,
717
+ location: location
718
+ )
719
+ end
720
+
721
+ # :call-seq:
722
+ # on_break: (Args arguments) -> Break
723
+ def on_break(arguments)
724
+ keyword = find_token(Kw, "break")
725
+
726
+ location = keyword.location
727
+ location = location.to(arguments.location) if arguments.parts.any?
728
+
729
+ Break.new(arguments: arguments, location: location)
730
+ end
731
+
732
+ # :call-seq:
733
+ # on_call: (
734
+ # untyped receiver,
735
+ # (:"::" | Op | Period) operator,
736
+ # (:call | Backtick | Const | Ident | Op) message
737
+ # ) -> Call
738
+ def on_call(receiver, operator, message)
739
+ ending = message
740
+ ending = operator if message == :call
741
+
742
+ Call.new(
743
+ receiver: receiver,
744
+ operator: operator,
745
+ message: message,
746
+ arguments: nil,
747
+ location: receiver.location.to(ending.location)
748
+ )
749
+ end
750
+
751
+ # :call-seq:
752
+ # on_case: (untyped value, untyped consequent) -> Case | RAssign
753
+ def on_case(value, consequent)
754
+ if keyword = find_token(Kw, "case", consume: false)
755
+ tokens.delete(keyword)
756
+
757
+ Case.new(
758
+ keyword: keyword,
759
+ value: value,
760
+ consequent: consequent,
761
+ location: keyword.location.to(consequent.location)
762
+ )
763
+ else
764
+ operator = find_token(Kw, "in", consume: false) || find_token(Op, "=>")
765
+
766
+ RAssign.new(
767
+ value: value,
768
+ operator: operator,
769
+ pattern: consequent,
770
+ location: value.location.to(consequent.location)
771
+ )
772
+ end
773
+ end
774
+
775
+ # :call-seq:
776
+ # on_class: (
777
+ # (ConstPathRef | ConstRef | TopConstRef) constant,
778
+ # untyped superclass,
779
+ # BodyStmt bodystmt
780
+ # ) -> ClassDeclaration
781
+ def on_class(constant, superclass, bodystmt)
782
+ beginning = find_token(Kw, "class")
783
+ ending = find_token(Kw, "end")
784
+
785
+ bodystmt.bind(
786
+ find_next_statement_start((superclass || constant).location.end_char),
787
+ ending.location.start_char
788
+ )
789
+
790
+ ClassDeclaration.new(
791
+ constant: constant,
792
+ superclass: superclass,
793
+ bodystmt: bodystmt,
794
+ location: beginning.location.to(ending.location)
795
+ )
796
+ end
797
+
798
+ # :call-seq:
799
+ # on_comma: (String value) -> Comma
800
+ def on_comma(value)
801
+ node =
802
+ Comma.new(
803
+ value: value,
804
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
805
+ )
806
+
807
+ tokens << node
808
+ node
809
+ end
810
+
811
+ # :call-seq:
812
+ # on_command: ((Const | Ident) message, Args arguments) -> Command
813
+ def on_command(message, arguments)
814
+ Command.new(
815
+ message: message,
816
+ arguments: arguments,
817
+ location: message.location.to(arguments.location)
818
+ )
819
+ end
820
+
821
+ # :call-seq:
822
+ # on_command_call: (
823
+ # untyped receiver,
824
+ # (:"::" | Op | Period) operator,
825
+ # (Const | Ident | Op) message,
826
+ # (nil | Args) arguments
827
+ # ) -> CommandCall
828
+ def on_command_call(receiver, operator, message, arguments)
829
+ ending = arguments || message
830
+
831
+ CommandCall.new(
832
+ receiver: receiver,
833
+ operator: operator,
834
+ message: message,
835
+ arguments: arguments,
836
+ location: receiver.location.to(ending.location)
837
+ )
838
+ end
839
+
840
+ # :call-seq:
841
+ # on_comment: (String value) -> Comment
842
+ def on_comment(value)
843
+ line = lineno
844
+ comment =
845
+ Comment.new(
846
+ value: value.chomp,
847
+ inline: value.strip != lines[line - 1].strip,
848
+ location:
849
+ Location.token(line: line, char: char_pos, size: value.size - 1)
850
+ )
851
+
852
+ @comments << comment
853
+ comment
854
+ end
855
+
856
+ # :call-seq:
857
+ # on_const: (String value) -> Const
858
+ def on_const(value)
859
+ Const.new(
860
+ value: value,
861
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
862
+ )
863
+ end
864
+
865
+ # :call-seq:
866
+ # on_const_path_field: (untyped parent, Const constant) -> ConstPathField
867
+ def on_const_path_field(parent, constant)
868
+ ConstPathField.new(
869
+ parent: parent,
870
+ constant: constant,
871
+ location: parent.location.to(constant.location)
872
+ )
873
+ end
874
+
875
+ # :call-seq:
876
+ # on_const_path_ref: (untyped parent, Const constant) -> ConstPathRef
877
+ def on_const_path_ref(parent, constant)
878
+ ConstPathRef.new(
879
+ parent: parent,
880
+ constant: constant,
881
+ location: parent.location.to(constant.location)
882
+ )
883
+ end
884
+
885
+ # :call-seq:
886
+ # on_const_ref: (Const constant) -> ConstRef
887
+ def on_const_ref(constant)
888
+ ConstRef.new(constant: constant, location: constant.location)
889
+ end
890
+
891
+ # :call-seq:
892
+ # on_cvar: (String value) -> CVar
893
+ def on_cvar(value)
894
+ CVar.new(
895
+ value: value,
896
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
897
+ )
898
+ end
899
+
900
+ # :call-seq:
901
+ # on_def: (
902
+ # (Backtick | Const | Ident | Kw | Op) name,
903
+ # (nil | Params | Paren) params,
904
+ # untyped bodystmt
905
+ # ) -> Def | DefEndless
906
+ def on_def(name, params, bodystmt)
907
+ # Make sure to delete this token in case you're defining something like
908
+ # def class which would lead to this being a kw and causing all kinds of
909
+ # trouble
910
+ tokens.delete(name)
911
+
912
+ # Find the beginning of the method definition, which works for single-line
913
+ # and normal method definitions.
914
+ beginning = find_token(Kw, "def")
915
+
916
+ # If there aren't any params then we need to correct the params node
917
+ # location information
918
+ if params.is_a?(Params) && params.empty?
919
+ end_char = name.location.end_char
920
+ location =
921
+ Location.new(
922
+ start_line: params.location.start_line,
923
+ start_char: end_char,
924
+ end_line: params.location.end_line,
925
+ end_char: end_char
926
+ )
927
+
928
+ params = Params.new(location: location)
929
+ end
930
+
931
+ ending = find_token(Kw, "end", consume: false)
932
+
933
+ if ending
934
+ tokens.delete(ending)
935
+ bodystmt.bind(
936
+ find_next_statement_start(params.location.end_char),
937
+ ending.location.start_char
938
+ )
939
+
940
+ Def.new(
941
+ name: name,
942
+ params: params,
943
+ bodystmt: bodystmt,
944
+ location: beginning.location.to(ending.location)
945
+ )
946
+ else
947
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
948
+ # the statements list. Before, it was just the individual statement.
949
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
950
+
951
+ DefEndless.new(
952
+ target: nil,
953
+ operator: nil,
954
+ name: name,
955
+ paren: params,
956
+ statement: statement,
957
+ location: beginning.location.to(bodystmt.location)
958
+ )
959
+ end
960
+ end
961
+
962
+ # :call-seq:
963
+ # on_defined: (untyped value) -> Defined
964
+ def on_defined(value)
965
+ beginning = find_token(Kw, "defined?")
966
+ ending = value
967
+
968
+ range = beginning.location.end_char...value.location.start_char
969
+ if source[range].include?("(")
970
+ find_token(LParen)
971
+ ending = find_token(RParen)
972
+ end
973
+
974
+ Defined.new(value: value, location: beginning.location.to(ending.location))
975
+ end
976
+
977
+ # :call-seq:
978
+ # on_defs: (
979
+ # untyped target,
980
+ # (Op | Period) operator,
981
+ # (Backtick | Const | Ident | Kw | Op) name,
982
+ # (Params | Paren) params,
983
+ # BodyStmt bodystmt
984
+ # ) -> Defs
985
+ def on_defs(target, operator, name, params, bodystmt)
986
+ # Make sure to delete this token in case you're defining something
987
+ # like def class which would lead to this being a kw and causing all kinds
988
+ # of trouble
989
+ tokens.delete(name)
990
+
991
+ # If there aren't any params then we need to correct the params node
992
+ # location information
993
+ if params.is_a?(Params) && params.empty?
994
+ end_char = name.location.end_char
995
+ location =
996
+ Location.new(
997
+ start_line: params.location.start_line,
998
+ start_char: end_char,
999
+ end_line: params.location.end_line,
1000
+ end_char: end_char
1001
+ )
1002
+
1003
+ params = Params.new(location: location)
1004
+ end
1005
+
1006
+ beginning = find_token(Kw, "def")
1007
+ ending = find_token(Kw, "end", consume: false)
1008
+
1009
+ if ending
1010
+ tokens.delete(ending)
1011
+ bodystmt.bind(
1012
+ find_next_statement_start(params.location.end_char),
1013
+ ending.location.start_char
1014
+ )
1015
+
1016
+ Defs.new(
1017
+ target: target,
1018
+ operator: operator,
1019
+ name: name,
1020
+ params: params,
1021
+ bodystmt: bodystmt,
1022
+ location: beginning.location.to(ending.location)
1023
+ )
1024
+ else
1025
+ # In Ruby >= 3.1.0, this is a BodyStmt that wraps a single statement in
1026
+ # the statements list. Before, it was just the individual statement.
1027
+ statement = bodystmt.is_a?(BodyStmt) ? bodystmt.statements : bodystmt
1028
+
1029
+ DefEndless.new(
1030
+ target: target,
1031
+ operator: operator,
1032
+ name: name,
1033
+ paren: params,
1034
+ statement: statement,
1035
+ location: beginning.location.to(bodystmt.location)
1036
+ )
1037
+ end
1038
+ end
1039
+
1040
+ # :call-seq:
1041
+ # on_do_block: (BlockVar block_var, BodyStmt bodystmt) -> DoBlock
1042
+ def on_do_block(block_var, bodystmt)
1043
+ beginning = find_token(Kw, "do")
1044
+ ending = find_token(Kw, "end")
1045
+
1046
+ bodystmt.bind(
1047
+ find_next_statement_start((block_var || beginning).location.end_char),
1048
+ ending.location.start_char
1049
+ )
1050
+
1051
+ DoBlock.new(
1052
+ keyword: beginning,
1053
+ block_var: block_var,
1054
+ bodystmt: bodystmt,
1055
+ location: beginning.location.to(ending.location)
1056
+ )
1057
+ end
1058
+
1059
+ # :call-seq:
1060
+ # on_dot2: ((nil | untyped) left, (nil | untyped) right) -> Dot2
1061
+ def on_dot2(left, right)
1062
+ operator = find_token(Op, "..")
1063
+
1064
+ beginning = left || operator
1065
+ ending = right || operator
1066
+
1067
+ Dot2.new(
1068
+ left: left,
1069
+ right: right,
1070
+ location: beginning.location.to(ending.location)
1071
+ )
1072
+ end
1073
+
1074
+ # :call-seq:
1075
+ # on_dot3: ((nil | untyped) left, (nil | untyped) right) -> Dot3
1076
+ def on_dot3(left, right)
1077
+ operator = find_token(Op, "...")
1078
+
1079
+ beginning = left || operator
1080
+ ending = right || operator
1081
+
1082
+ Dot3.new(
1083
+ left: left,
1084
+ right: right,
1085
+ location: beginning.location.to(ending.location)
1086
+ )
1087
+ end
1088
+
1089
+ # :call-seq:
1090
+ # on_dyna_symbol: (StringContent string_content) -> DynaSymbol
1091
+ def on_dyna_symbol(string_content)
1092
+ if find_token(SymBeg, consume: false)
1093
+ # A normal dynamic symbol
1094
+ symbeg = find_token(SymBeg)
1095
+ tstring_end = find_token(TStringEnd, location: symbeg.location)
1096
+
1097
+ DynaSymbol.new(
1098
+ quote: symbeg.value,
1099
+ parts: string_content.parts,
1100
+ location: symbeg.location.to(tstring_end.location)
1101
+ )
1102
+ else
1103
+ # A dynamic symbol as a hash key
1104
+ tstring_beg = find_token(TStringBeg)
1105
+ label_end = find_token(LabelEnd)
1106
+
1107
+ DynaSymbol.new(
1108
+ parts: string_content.parts,
1109
+ quote: label_end.value[0],
1110
+ location: tstring_beg.location.to(label_end.location)
1111
+ )
1112
+ end
1113
+ end
1114
+
1115
+ # :call-seq:
1116
+ # on_else: (Statements statements) -> Else
1117
+ def on_else(statements)
1118
+ beginning = find_token(Kw, "else")
1119
+
1120
+ # else can either end with an end keyword (in which case we'll want to
1121
+ # consume that event) or it can end with an ensure keyword (in which case
1122
+ # we'll leave that to the ensure to handle).
1123
+ index =
1124
+ tokens.rindex do |token|
1125
+ token.is_a?(Kw) && %w[end ensure].include?(token.value)
1126
+ end
1127
+
1128
+ node = tokens[index]
1129
+ ending = node.value == "end" ? tokens.delete_at(index) : node
1130
+ # ending = node
1131
+
1132
+ statements.bind(beginning.location.end_char, ending.location.start_char)
1133
+
1134
+ Else.new(
1135
+ statements: statements,
1136
+ location: beginning.location.to(ending.location)
1137
+ )
1138
+ end
1139
+
1140
+ # :call-seq:
1141
+ # on_elsif: (
1142
+ # untyped predicate,
1143
+ # Statements statements,
1144
+ # (nil | Elsif | Else) consequent
1145
+ # ) -> Elsif
1146
+ def on_elsif(predicate, statements, consequent)
1147
+ beginning = find_token(Kw, "elsif")
1148
+ ending = consequent || find_token(Kw, "end")
1149
+
1150
+ statements.bind(predicate.location.end_char, ending.location.start_char)
1151
+
1152
+ Elsif.new(
1153
+ predicate: predicate,
1154
+ statements: statements,
1155
+ consequent: consequent,
1156
+ location: beginning.location.to(ending.location)
1157
+ )
1158
+ end
1159
+
1160
+ # :call-seq:
1161
+ # on_embdoc: (String value) -> EmbDoc
1162
+ def on_embdoc(value)
1163
+ @embdoc.value << value
1164
+ @embdoc
1165
+ end
1166
+
1167
+ # :call-seq:
1168
+ # on_embdoc_beg: (String value) -> EmbDoc
1169
+ def on_embdoc_beg(value)
1170
+ @embdoc =
1171
+ EmbDoc.new(
1172
+ value: value,
1173
+ location: Location.fixed(line: lineno, char: char_pos)
1174
+ )
1175
+ end
1176
+
1177
+ # :call-seq:
1178
+ # on_embdoc_end: (String value) -> EmbDoc
1179
+ def on_embdoc_end(value)
1180
+ location = @embdoc.location
1181
+ embdoc =
1182
+ EmbDoc.new(
1183
+ value: @embdoc.value << value.chomp,
1184
+ location:
1185
+ Location.new(
1186
+ start_line: location.start_line,
1187
+ start_char: location.start_char,
1188
+ end_line: lineno,
1189
+ end_char: char_pos + value.length - 1
1190
+ )
1191
+ )
1192
+
1193
+ @comments << embdoc
1194
+ @embdoc = nil
1195
+
1196
+ embdoc
1197
+ end
1198
+
1199
+ # :call-seq:
1200
+ # on_embexpr_beg: (String value) -> EmbExprBeg
1201
+ def on_embexpr_beg(value)
1202
+ node =
1203
+ EmbExprBeg.new(
1204
+ value: value,
1205
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1206
+ )
1207
+
1208
+ tokens << node
1209
+ node
1210
+ end
1211
+
1212
+ # :call-seq:
1213
+ # on_embexpr_end: (String value) -> EmbExprEnd
1214
+ def on_embexpr_end(value)
1215
+ node =
1216
+ EmbExprEnd.new(
1217
+ value: value,
1218
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1219
+ )
1220
+
1221
+ tokens << node
1222
+ node
1223
+ end
1224
+
1225
+ # :call-seq:
1226
+ # on_embvar: (String value) -> EmbVar
1227
+ def on_embvar(value)
1228
+ node =
1229
+ EmbVar.new(
1230
+ value: value,
1231
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1232
+ )
1233
+
1234
+ tokens << node
1235
+ node
1236
+ end
1237
+
1238
+ # :call-seq:
1239
+ # on_ensure: (Statements statements) -> Ensure
1240
+ def on_ensure(statements)
1241
+ keyword = find_token(Kw, "ensure")
1242
+
1243
+ # We don't want to consume the :@kw event, because that would break
1244
+ # def..ensure..end chains.
1245
+ ending = find_token(Kw, "end", consume: false)
1246
+ statements.bind(
1247
+ find_next_statement_start(keyword.location.end_char),
1248
+ ending.location.start_char
1249
+ )
1250
+
1251
+ Ensure.new(
1252
+ keyword: keyword,
1253
+ statements: statements,
1254
+ location: keyword.location.to(ending.location)
1255
+ )
1256
+ end
1257
+
1258
+ # The handler for this event accepts no parameters (though in previous
1259
+ # versions of Ruby it accepted a string literal with a value of ",").
1260
+ #
1261
+ # :call-seq:
1262
+ # on_excessed_comma: () -> ExcessedComma
1263
+ def on_excessed_comma(*)
1264
+ comma = find_token(Comma)
1265
+
1266
+ ExcessedComma.new(value: comma.value, location: comma.location)
1267
+ end
1268
+
1269
+ # :call-seq:
1270
+ # on_fcall: ((Const | Ident) value) -> FCall
1271
+ def on_fcall(value)
1272
+ FCall.new(value: value, arguments: nil, location: value.location)
1273
+ end
1274
+
1275
+ # :call-seq:
1276
+ # on_field: (
1277
+ # untyped parent,
1278
+ # (:"::" | Op | Period) operator
1279
+ # (Const | Ident) name
1280
+ # ) -> Field
1281
+ def on_field(parent, operator, name)
1282
+ Field.new(
1283
+ parent: parent,
1284
+ operator: operator,
1285
+ name: name,
1286
+ location: parent.location.to(name.location)
1287
+ )
1288
+ end
1289
+
1290
+ # :call-seq:
1291
+ # on_float: (String value) -> FloatLiteral
1292
+ def on_float(value)
1293
+ FloatLiteral.new(
1294
+ value: value,
1295
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1296
+ )
1297
+ end
1298
+
1299
+ # :call-seq:
1300
+ # on_fndptn: (
1301
+ # (nil | untyped) constant,
1302
+ # VarField left,
1303
+ # Array[untyped] values,
1304
+ # VarField right
1305
+ # ) -> FndPtn
1306
+ def on_fndptn(constant, left, values, right)
1307
+ beginning = constant || find_token(LBracket)
1308
+ ending = find_token(RBracket)
1309
+
1310
+ FndPtn.new(
1311
+ constant: constant,
1312
+ left: left,
1313
+ values: values,
1314
+ right: right,
1315
+ location: beginning.location.to(ending.location)
1316
+ )
1317
+ end
1318
+
1319
+ # :call-seq:
1320
+ # on_for: (
1321
+ # (MLHS | VarField) value,
1322
+ # untyped collection,
1323
+ # Statements statements
1324
+ # ) -> For
1325
+ def on_for(index, collection, statements)
1326
+ beginning = find_token(Kw, "for")
1327
+ in_keyword = find_token(Kw, "in")
1328
+ ending = find_token(Kw, "end")
1329
+
1330
+ # Consume the do keyword if it exists so that it doesn't get confused for
1331
+ # some other block
1332
+ keyword = find_token(Kw, "do", consume: false)
1333
+ if keyword && keyword.location.start_char > collection.location.end_char &&
1334
+ keyword.location.end_char < ending.location.start_char
1335
+ tokens.delete(keyword)
1336
+ end
1337
+
1338
+ statements.bind(
1339
+ (keyword || collection).location.end_char,
1340
+ ending.location.start_char
1341
+ )
1342
+
1343
+ if index.is_a?(MLHS)
1344
+ comma_range = index.location.end_char...in_keyword.location.start_char
1345
+ index.comma = true if source[comma_range].strip.start_with?(",")
1346
+ end
1347
+
1348
+ For.new(
1349
+ index: index,
1350
+ collection: collection,
1351
+ statements: statements,
1352
+ location: beginning.location.to(ending.location)
1353
+ )
1354
+ end
1355
+
1356
+ # :call-seq:
1357
+ # on_gvar: (String value) -> GVar
1358
+ def on_gvar(value)
1359
+ GVar.new(
1360
+ value: value,
1361
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1362
+ )
1363
+ end
1364
+
1365
+ # :call-seq:
1366
+ # on_hash: ((nil | Array[AssocNew | AssocSplat]) assocs) -> HashLiteral
1367
+ def on_hash(assocs)
1368
+ lbrace = find_token(LBrace)
1369
+ rbrace = find_token(RBrace)
1370
+
1371
+ HashLiteral.new(
1372
+ lbrace: lbrace,
1373
+ assocs: assocs || [],
1374
+ location: lbrace.location.to(rbrace.location)
1375
+ )
1376
+ end
1377
+
1378
+ # :call-seq:
1379
+ # on_heredoc_beg: (String value) -> HeredocBeg
1380
+ def on_heredoc_beg(value)
1381
+ location =
1382
+ Location.token(line: lineno, char: char_pos, size: value.size + 1)
1383
+
1384
+ # Here we're going to artificially create an extra node type so that if
1385
+ # there are comments after the declaration of a heredoc, they get printed.
1386
+ beginning = HeredocBeg.new(value: value, location: location)
1387
+ @heredocs << Heredoc.new(beginning: beginning, location: location)
1388
+
1389
+ beginning
1390
+ end
1391
+
1392
+ # :call-seq:
1393
+ # on_heredoc_dedent: (StringContent string, Integer width) -> Heredoc
1394
+ def on_heredoc_dedent(string, width)
1395
+ heredoc = @heredocs[-1]
1396
+
1397
+ @heredocs[-1] = Heredoc.new(
1398
+ beginning: heredoc.beginning,
1399
+ ending: heredoc.ending,
1400
+ parts: string.parts,
1401
+ location: heredoc.location
1402
+ )
1403
+ end
1404
+
1405
+ # :call-seq:
1406
+ # on_heredoc_end: (String value) -> Heredoc
1407
+ def on_heredoc_end(value)
1408
+ heredoc = @heredocs[-1]
1409
+
1410
+ @heredocs[-1] = Heredoc.new(
1411
+ beginning: heredoc.beginning,
1412
+ ending: value.chomp,
1413
+ parts: heredoc.parts,
1414
+ location:
1415
+ Location.new(
1416
+ start_line: heredoc.location.start_line,
1417
+ start_char: heredoc.location.start_char,
1418
+ end_line: lineno,
1419
+ end_char: char_pos
1420
+ )
1421
+ )
1422
+ end
1423
+
1424
+ # :call-seq:
1425
+ # on_hshptn: (
1426
+ # (nil | untyped) constant,
1427
+ # Array[[Label, untyped]] keywords,
1428
+ # (nil | VarField) keyword_rest
1429
+ # ) -> HshPtn
1430
+ def on_hshptn(constant, keywords, keyword_rest)
1431
+ parts = [constant, keywords, keyword_rest].flatten(2).compact
1432
+
1433
+ HshPtn.new(
1434
+ constant: constant,
1435
+ keywords: keywords,
1436
+ keyword_rest: keyword_rest,
1437
+ location: parts[0].location.to(parts[-1].location)
1438
+ )
1439
+ end
1440
+
1441
+ # :call-seq:
1442
+ # on_ident: (String value) -> Ident
1443
+ def on_ident(value)
1444
+ Ident.new(
1445
+ value: value,
1446
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1447
+ )
1448
+ end
1449
+
1450
+ # :call-seq:
1451
+ # on_if: (
1452
+ # untyped predicate,
1453
+ # Statements statements,
1454
+ # (nil | Elsif | Else) consequent
1455
+ # ) -> If
1456
+ def on_if(predicate, statements, consequent)
1457
+ beginning = find_token(Kw, "if")
1458
+ ending = consequent || find_token(Kw, "end")
1459
+
1460
+ statements.bind(predicate.location.end_char, ending.location.start_char)
1461
+
1462
+ If.new(
1463
+ predicate: predicate,
1464
+ statements: statements,
1465
+ consequent: consequent,
1466
+ location: beginning.location.to(ending.location)
1467
+ )
1468
+ end
1469
+
1470
+ # :call-seq:
1471
+ # on_ifop: (untyped predicate, untyped truthy, untyped falsy) -> IfOp
1472
+ def on_ifop(predicate, truthy, falsy)
1473
+ IfOp.new(
1474
+ predicate: predicate,
1475
+ truthy: truthy,
1476
+ falsy: falsy,
1477
+ location: predicate.location.to(falsy.location)
1478
+ )
1479
+ end
1480
+
1481
+ # :call-seq:
1482
+ # on_if_mod: (untyped predicate, untyped statement) -> IfMod
1483
+ def on_if_mod(predicate, statement)
1484
+ find_token(Kw, "if")
1485
+
1486
+ IfMod.new(
1487
+ statement: statement,
1488
+ predicate: predicate,
1489
+ location: statement.location.to(predicate.location)
1490
+ )
1491
+ end
1492
+
1493
+ # def on_ignored_nl(value)
1494
+ # value
1495
+ # end
1496
+
1497
+ # def on_ignored_sp(value)
1498
+ # value
1499
+ # end
1500
+
1501
+ # :call-seq:
1502
+ # on_imaginary: (String value) -> Imaginary
1503
+ def on_imaginary(value)
1504
+ Imaginary.new(
1505
+ value: value,
1506
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1507
+ )
1508
+ end
1509
+
1510
+ # :call-seq:
1511
+ # on_in: (RAssign pattern, nil statements, nil consequent) -> RAssign
1512
+ # | (
1513
+ # untyped pattern,
1514
+ # Statements statements,
1515
+ # (nil | In | Else) consequent
1516
+ # ) -> In
1517
+ def on_in(pattern, statements, consequent)
1518
+ # Here we have a rightward assignment
1519
+ return pattern unless statements
1520
+
1521
+ beginning = find_token(Kw, "in")
1522
+ ending = consequent || find_token(Kw, "end")
1523
+
1524
+ statements_start = pattern
1525
+ if token = find_token(Kw, "then", consume: false)
1526
+ tokens.delete(token)
1527
+ statements_start = token
1528
+ end
1529
+
1530
+ statements.bind(
1531
+ find_next_statement_start(statements_start.location.end_char),
1532
+ ending.location.start_char
1533
+ )
1534
+
1535
+ In.new(
1536
+ pattern: pattern,
1537
+ statements: statements,
1538
+ consequent: consequent,
1539
+ location: beginning.location.to(ending.location)
1540
+ )
1541
+ end
1542
+
1543
+ # :call-seq:
1544
+ # on_int: (String value) -> Int
1545
+ def on_int(value)
1546
+ Int.new(
1547
+ value: value,
1548
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1549
+ )
1550
+ end
1551
+
1552
+ # :call-seq:
1553
+ # on_ivar: (String value) -> IVar
1554
+ def on_ivar(value)
1555
+ IVar.new(
1556
+ value: value,
1557
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1558
+ )
1559
+ end
1560
+
1561
+ # :call-seq:
1562
+ # on_kw: (String value) -> Kw
1563
+ def on_kw(value)
1564
+ node =
1565
+ Kw.new(
1566
+ value: value,
1567
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1568
+ )
1569
+
1570
+ tokens << node
1571
+ node
1572
+ end
1573
+
1574
+ # :call-seq:
1575
+ # on_kwrest_param: ((nil | Ident) name) -> KwRestParam
1576
+ def on_kwrest_param(name)
1577
+ location = find_token(Op, "**").location
1578
+ location = location.to(name.location) if name
1579
+
1580
+ KwRestParam.new(name: name, location: location)
1581
+ end
1582
+
1583
+ # :call-seq:
1584
+ # on_label: (String value) -> Label
1585
+ def on_label(value)
1586
+ Label.new(
1587
+ value: value,
1588
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1589
+ )
1590
+ end
1591
+
1592
+ # :call-seq:
1593
+ # on_label_end: (String value) -> LabelEnd
1594
+ def on_label_end(value)
1595
+ node =
1596
+ LabelEnd.new(
1597
+ value: value,
1598
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1599
+ )
1600
+
1601
+ tokens << node
1602
+ node
1603
+ end
1604
+
1605
+ # :call-seq:
1606
+ # on_lambda: (
1607
+ # (Params | Paren) params,
1608
+ # (BodyStmt | Statements) statements
1609
+ # ) -> Lambda
1610
+ def on_lambda(params, statements)
1611
+ beginning = find_token(TLambda)
1612
+
1613
+ if tokens.any? { |token|
1614
+ token.is_a?(TLamBeg) &&
1615
+ token.location.start_char > beginning.location.start_char
1616
+ }
1617
+ opening = find_token(TLamBeg)
1618
+ closing = find_token(RBrace)
1619
+ else
1620
+ opening = find_token(Kw, "do")
1621
+ closing = find_token(Kw, "end")
1622
+ end
1623
+
1624
+ statements.bind(opening.location.end_char, closing.location.start_char)
1625
+
1626
+ Lambda.new(
1627
+ params: params,
1628
+ statements: statements,
1629
+ location: beginning.location.to(closing.location)
1630
+ )
1631
+ end
1632
+
1633
+ # :call-seq:
1634
+ # on_lbrace: (String value) -> LBrace
1635
+ def on_lbrace(value)
1636
+ node =
1637
+ LBrace.new(
1638
+ value: value,
1639
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1640
+ )
1641
+
1642
+ tokens << node
1643
+ node
1644
+ end
1645
+
1646
+ # :call-seq:
1647
+ # on_lbracket: (String value) -> LBracket
1648
+ def on_lbracket(value)
1649
+ node =
1650
+ LBracket.new(
1651
+ value: value,
1652
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1653
+ )
1654
+
1655
+ tokens << node
1656
+ node
1657
+ end
1658
+
1659
+ # :call-seq:
1660
+ # on_lparen: (String value) -> LParen
1661
+ def on_lparen(value)
1662
+ node =
1663
+ LParen.new(
1664
+ value: value,
1665
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1666
+ )
1667
+
1668
+ tokens << node
1669
+ node
1670
+ end
1671
+
1672
+ # def on_magic_comment(key, value)
1673
+ # [key, value]
1674
+ # end
1675
+
1676
+ # :call-seq:
1677
+ # on_massign: ((MLHS | MLHSParen) target, untyped value) -> MAssign
1678
+ def on_massign(target, value)
1679
+ comma_range = target.location.end_char...value.location.start_char
1680
+ target.comma = true if source[comma_range].strip.start_with?(",")
1681
+
1682
+ MAssign.new(
1683
+ target: target,
1684
+ value: value,
1685
+ location: target.location.to(value.location)
1686
+ )
1687
+ end
1688
+
1689
+ # :call-seq:
1690
+ # on_method_add_arg: (
1691
+ # (Call | FCall) call,
1692
+ # (ArgParen | Args) arguments
1693
+ # ) -> Call | FCall
1694
+ def on_method_add_arg(call, arguments)
1695
+ location = call.location
1696
+ location = location.to(arguments.location) if arguments.is_a?(ArgParen)
1697
+
1698
+ if call.is_a?(FCall)
1699
+ FCall.new(value: call.value, arguments: arguments, location: location)
1700
+ else
1701
+ Call.new(
1702
+ receiver: call.receiver,
1703
+ operator: call.operator,
1704
+ message: call.message,
1705
+ arguments: arguments,
1706
+ location: location
1707
+ )
1708
+ end
1709
+ end
1710
+
1711
+ # :call-seq:
1712
+ # on_method_add_block: (
1713
+ # (Call | Command | CommandCall | FCall) call,
1714
+ # (BraceBlock | DoBlock) block
1715
+ # ) -> MethodAddBlock
1716
+ def on_method_add_block(call, block)
1717
+ MethodAddBlock.new(
1718
+ call: call,
1719
+ block: block,
1720
+ location: call.location.to(block.location)
1721
+ )
1722
+ end
1723
+
1724
+ # :call-seq:
1725
+ # on_mlhs_add: (
1726
+ # MLHS mlhs,
1727
+ # (ARefField | Field | Ident | MLHSParen | VarField) part
1728
+ # ) -> MLHS
1729
+ def on_mlhs_add(mlhs, part)
1730
+ location =
1731
+ mlhs.parts.empty? ? part.location : mlhs.location.to(part.location)
1732
+
1733
+ MLHS.new(parts: mlhs.parts << part, location: location)
1734
+ end
1735
+
1736
+ # :call-seq:
1737
+ # on_mlhs_add_post: (MLHS left, MLHS right) -> MLHS
1738
+ def on_mlhs_add_post(left, right)
1739
+ MLHS.new(
1740
+ parts: left.parts + right.parts,
1741
+ location: left.location.to(right.location)
1742
+ )
1743
+ end
1744
+
1745
+ # :call-seq:
1746
+ # on_mlhs_add_star: (
1747
+ # MLHS mlhs,
1748
+ # (nil | ARefField | Field | Ident | VarField) part
1749
+ # ) -> MLHS
1750
+ def on_mlhs_add_star(mlhs, part)
1751
+ beginning = find_token(Op, "*")
1752
+ ending = part || beginning
1753
+
1754
+ location = beginning.location.to(ending.location)
1755
+ arg_star = ArgStar.new(value: part, location: location)
1756
+
1757
+ location = mlhs.location.to(location) unless mlhs.parts.empty?
1758
+ MLHS.new(parts: mlhs.parts << arg_star, location: location)
1759
+ end
1760
+
1761
+ # :call-seq:
1762
+ # on_mlhs_new: () -> MLHS
1763
+ def on_mlhs_new
1764
+ MLHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1765
+ end
1766
+
1767
+ # :call-seq:
1768
+ # on_mlhs_paren: ((MLHS | MLHSParen) contents) -> MLHSParen
1769
+ def on_mlhs_paren(contents)
1770
+ lparen = find_token(LParen)
1771
+ rparen = find_token(RParen)
1772
+
1773
+ comma_range = lparen.location.end_char...rparen.location.start_char
1774
+ contents.comma = true if source[comma_range].strip.end_with?(",")
1775
+
1776
+ MLHSParen.new(
1777
+ contents: contents,
1778
+ location: lparen.location.to(rparen.location)
1779
+ )
1780
+ end
1781
+
1782
+ # :call-seq:
1783
+ # on_module: (
1784
+ # (ConstPathRef | ConstRef | TopConstRef) constant,
1785
+ # BodyStmt bodystmt
1786
+ # ) -> ModuleDeclaration
1787
+ def on_module(constant, bodystmt)
1788
+ beginning = find_token(Kw, "module")
1789
+ ending = find_token(Kw, "end")
1790
+
1791
+ bodystmt.bind(
1792
+ find_next_statement_start(constant.location.end_char),
1793
+ ending.location.start_char
1794
+ )
1795
+
1796
+ ModuleDeclaration.new(
1797
+ constant: constant,
1798
+ bodystmt: bodystmt,
1799
+ location: beginning.location.to(ending.location)
1800
+ )
1801
+ end
1802
+
1803
+ # :call-seq:
1804
+ # on_mrhs_new: () -> MRHS
1805
+ def on_mrhs_new
1806
+ MRHS.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
1807
+ end
1808
+
1809
+ # :call-seq:
1810
+ # on_mrhs_add: (MRHS mrhs, untyped part) -> MRHS
1811
+ def on_mrhs_add(mrhs, part)
1812
+ location =
1813
+ if mrhs.parts.empty?
1814
+ mrhs.location
1815
+ else
1816
+ mrhs.location.to(part.location)
1817
+ end
1818
+
1819
+ MRHS.new(parts: mrhs.parts << part, location: location)
1820
+ end
1821
+
1822
+ # :call-seq:
1823
+ # on_mrhs_add_star: (MRHS mrhs, untyped value) -> MRHS
1824
+ def on_mrhs_add_star(mrhs, value)
1825
+ beginning = find_token(Op, "*")
1826
+ ending = value || beginning
1827
+
1828
+ arg_star =
1829
+ ArgStar.new(
1830
+ value: value,
1831
+ location: beginning.location.to(ending.location)
1832
+ )
1833
+
1834
+ location =
1835
+ if mrhs.parts.empty?
1836
+ arg_star.location
1837
+ else
1838
+ mrhs.location.to(arg_star.location)
1839
+ end
1840
+
1841
+ MRHS.new(parts: mrhs.parts << arg_star, location: location)
1842
+ end
1843
+
1844
+ # :call-seq:
1845
+ # on_mrhs_new_from_args: (Args arguments) -> MRHS
1846
+ def on_mrhs_new_from_args(arguments)
1847
+ MRHS.new(parts: arguments.parts, location: arguments.location)
1848
+ end
1849
+
1850
+ # :call-seq:
1851
+ # on_next: (Args arguments) -> Next
1852
+ def on_next(arguments)
1853
+ keyword = find_token(Kw, "next")
1854
+
1855
+ location = keyword.location
1856
+ location = location.to(arguments.location) if arguments.parts.any?
1857
+
1858
+ Next.new(arguments: arguments, location: location)
1859
+ end
1860
+
1861
+ # def on_nl(value)
1862
+ # value
1863
+ # end
1864
+
1865
+ # def on_nokw_param(value)
1866
+ # value
1867
+ # end
1868
+
1869
+ # :call-seq:
1870
+ # on_op: (String value) -> Op
1871
+ def on_op(value)
1872
+ node =
1873
+ Op.new(
1874
+ value: value,
1875
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
1876
+ )
1877
+
1878
+ tokens << node
1879
+ node
1880
+ end
1881
+
1882
+ # :call-seq:
1883
+ # on_opassign: (
1884
+ # (ARefField | ConstPathField | Field | TopConstField | VarField) target,
1885
+ # Op operator,
1886
+ # untyped value
1887
+ # ) -> OpAssign
1888
+ def on_opassign(target, operator, value)
1889
+ OpAssign.new(
1890
+ target: target,
1891
+ operator: operator,
1892
+ value: value,
1893
+ location: target.location.to(value.location)
1894
+ )
1895
+ end
1896
+
1897
+ # def on_operator_ambiguous(value)
1898
+ # value
1899
+ # end
1900
+
1901
+ # :call-seq:
1902
+ # on_params: (
1903
+ # (nil | Array[Ident]) requireds,
1904
+ # (nil | Array[[Ident, untyped]]) optionals,
1905
+ # (nil | ArgsForward | ExcessedComma | RestParam) rest,
1906
+ # (nil | Array[Ident]) posts,
1907
+ # (nil | Array[[Ident, nil | untyped]]) keywords,
1908
+ # (nil | :nil | ArgsForward | KwRestParam) keyword_rest,
1909
+ # (nil | :& | BlockArg) block
1910
+ # ) -> Params
1911
+ def on_params(
1912
+ requireds,
1913
+ optionals,
1914
+ rest,
1915
+ posts,
1916
+ keywords,
1917
+ keyword_rest,
1918
+ block
1919
+ )
1920
+ parts = [
1921
+ *requireds,
1922
+ *optionals&.flatten(1),
1923
+ rest,
1924
+ *posts,
1925
+ *keywords&.flat_map { |(key, value)| [key, value || nil] },
1926
+ (keyword_rest if keyword_rest != :nil),
1927
+ (block if block != :&)
1928
+ ].compact
1929
+
1930
+ location =
1931
+ if parts.any?
1932
+ parts[0].location.to(parts[-1].location)
1933
+ else
1934
+ Location.fixed(line: lineno, char: char_pos)
1935
+ end
1936
+
1937
+ Params.new(
1938
+ requireds: requireds || [],
1939
+ optionals: optionals || [],
1940
+ rest: rest,
1941
+ posts: posts || [],
1942
+ keywords: keywords || [],
1943
+ keyword_rest: keyword_rest,
1944
+ block: (block if block != :&),
1945
+ location: location
1946
+ )
1947
+ end
1948
+
1949
+ # :call-seq:
1950
+ # on_paren: (untyped contents) -> Paren
1951
+ def on_paren(contents)
1952
+ lparen = find_token(LParen)
1953
+ rparen = find_token(RParen)
1954
+
1955
+ if contents && contents.is_a?(Params)
1956
+ location = contents.location
1957
+ location =
1958
+ Location.new(
1959
+ start_line: location.start_line,
1960
+ start_char: find_next_statement_start(lparen.location.end_char),
1961
+ end_line: location.end_line,
1962
+ end_char: rparen.location.start_char
1963
+ )
1964
+
1965
+ contents =
1966
+ Params.new(
1967
+ requireds: contents.requireds,
1968
+ optionals: contents.optionals,
1969
+ rest: contents.rest,
1970
+ posts: contents.posts,
1971
+ keywords: contents.keywords,
1972
+ keyword_rest: contents.keyword_rest,
1973
+ block: contents.block,
1974
+ location: location
1975
+ )
1976
+ end
1977
+
1978
+ Paren.new(
1979
+ lparen: lparen,
1980
+ contents: contents || nil,
1981
+ location: lparen.location.to(rparen.location)
1982
+ )
1983
+ end
1984
+
1985
+ # If we encounter a parse error, just immediately bail out so that our
1986
+ # runner can catch it.
1987
+ def on_parse_error(error, *)
1988
+ raise ParseError.new(error, lineno, column)
1989
+ end
1990
+ alias on_alias_error on_parse_error
1991
+ alias on_assign_error on_parse_error
1992
+ alias on_class_name_error on_parse_error
1993
+ alias on_param_error on_parse_error
1994
+
1995
+ # :call-seq:
1996
+ # on_period: (String value) -> Period
1997
+ def on_period(value)
1998
+ Period.new(
1999
+ value: value,
2000
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2001
+ )
2002
+ end
2003
+
2004
+ # :call-seq:
2005
+ # on_program: (Statements statements) -> Program
2006
+ def on_program(statements)
2007
+ location =
2008
+ Location.new(
2009
+ start_line: 1,
2010
+ start_char: 0,
2011
+ end_line: lines.length,
2012
+ end_char: source.length
2013
+ )
2014
+
2015
+ statements.body << @__end__ if @__end__
2016
+ statements.bind(0, source.length)
2017
+
2018
+ program = Program.new(statements: statements, location: location)
2019
+ attach_comments(program, @comments)
2020
+
2021
+ program
2022
+ end
2023
+
2024
+ # Attaches comments to the nodes in the tree that most closely correspond to
2025
+ # the location of the comments.
2026
+ def attach_comments(program, comments)
2027
+ comments.each do |comment|
2028
+ preceding, enclosing, following = nearest_nodes(program, comment)
2029
+
2030
+ if comment.inline?
2031
+ if preceding
2032
+ preceding.comments << comment
2033
+ comment.trailing!
2034
+ elsif following
2035
+ following.comments << comment
2036
+ comment.leading!
2037
+ elsif enclosing
2038
+ enclosing.comments << comment
2039
+ else
2040
+ program.comments << comment
2041
+ end
2042
+ else
2043
+ # If a comment exists on its own line, prefer a leading comment.
2044
+ if following
2045
+ following.comments << comment
2046
+ comment.leading!
2047
+ elsif preceding
2048
+ preceding.comments << comment
2049
+ comment.trailing!
2050
+ elsif enclosing
2051
+ enclosing.comments << comment
2052
+ else
2053
+ program.comments << comment
2054
+ end
2055
+ end
2056
+ end
2057
+ end
2058
+
2059
+ # Responsible for finding the nearest nodes to the given comment within the
2060
+ # context of the given encapsulating node.
2061
+ def nearest_nodes(node, comment)
2062
+ comment_start = comment.location.start_char
2063
+ comment_end = comment.location.end_char
2064
+
2065
+ child_nodes = node.child_nodes.compact
2066
+ preceding = nil
2067
+ following = nil
2068
+
2069
+ left = 0
2070
+ right = child_nodes.length
2071
+
2072
+ # This is a custom binary search that finds the nearest nodes to the given
2073
+ # comment. When it finds a node that completely encapsulates the comment,
2074
+ # it recursed downward into the tree.
2075
+ while left < right
2076
+ middle = (left + right) / 2
2077
+ child = child_nodes[middle]
2078
+
2079
+ node_start = child.location.start_char
2080
+ node_end = child.location.end_char
2081
+
2082
+ if node_start <= comment_start && comment_end <= node_end
2083
+ # The comment is completely contained by this child node. Abandon the
2084
+ # binary search at this level.
2085
+ return nearest_nodes(child, comment)
2086
+ end
2087
+
2088
+ if node_end <= comment_start
2089
+ # This child node falls completely before the comment. Because we will
2090
+ # never consider this node or any nodes before it again, this node
2091
+ # must be the closest preceding node we have encountered so far.
2092
+ preceding = child
2093
+ left = middle + 1
2094
+ next
2095
+ end
2096
+
2097
+ if comment_end <= node_start
2098
+ # This child node falls completely after the comment. Because we will
2099
+ # never consider this node or any nodes after it again, this node must
2100
+ # be the closest following node we have encountered so far.
2101
+ following = child
2102
+ right = middle
2103
+ next
2104
+ end
2105
+
2106
+ # This should only happen if there is a bug in this parser.
2107
+ raise "Comment location overlaps with node location"
2108
+ end
2109
+
2110
+ [preceding, node, following]
2111
+ end
2112
+
2113
+ # :call-seq:
2114
+ # on_qsymbols_add: (QSymbols qsymbols, TStringContent element) -> QSymbols
2115
+ def on_qsymbols_add(qsymbols, element)
2116
+ QSymbols.new(
2117
+ beginning: qsymbols.beginning,
2118
+ elements: qsymbols.elements << element,
2119
+ location: qsymbols.location.to(element.location)
2120
+ )
2121
+ end
2122
+
2123
+ # :call-seq:
2124
+ # on_qsymbols_beg: (String value) -> QSymbolsBeg
2125
+ def on_qsymbols_beg(value)
2126
+ node =
2127
+ QSymbolsBeg.new(
2128
+ value: value,
2129
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2130
+ )
2131
+
2132
+ tokens << node
2133
+ node
2134
+ end
2135
+
2136
+ # :call-seq:
2137
+ # on_qsymbols_new: () -> QSymbols
2138
+ def on_qsymbols_new
2139
+ beginning = find_token(QSymbolsBeg)
2140
+
2141
+ QSymbols.new(
2142
+ beginning: beginning,
2143
+ elements: [],
2144
+ location: beginning.location
2145
+ )
2146
+ end
2147
+
2148
+ # :call-seq:
2149
+ # on_qwords_add: (QWords qwords, TStringContent element) -> QWords
2150
+ def on_qwords_add(qwords, element)
2151
+ QWords.new(
2152
+ beginning: qwords.beginning,
2153
+ elements: qwords.elements << element,
2154
+ location: qwords.location.to(element.location)
2155
+ )
2156
+ end
2157
+
2158
+ # :call-seq:
2159
+ # on_qwords_beg: (String value) -> QWordsBeg
2160
+ def on_qwords_beg(value)
2161
+ node =
2162
+ QWordsBeg.new(
2163
+ value: value,
2164
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2165
+ )
2166
+
2167
+ tokens << node
2168
+ node
2169
+ end
2170
+
2171
+ # :call-seq:
2172
+ # on_qwords_new: () -> QWords
2173
+ def on_qwords_new
2174
+ beginning = find_token(QWordsBeg)
2175
+
2176
+ QWords.new(beginning: beginning, elements: [], location: beginning.location)
2177
+ end
2178
+
2179
+ # :call-seq:
2180
+ # on_rational: (String value) -> RationalLiteral
2181
+ def on_rational(value)
2182
+ RationalLiteral.new(
2183
+ value: value,
2184
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2185
+ )
2186
+ end
2187
+
2188
+ # :call-seq:
2189
+ # on_rbrace: (String value) -> RBrace
2190
+ def on_rbrace(value)
2191
+ node =
2192
+ RBrace.new(
2193
+ value: value,
2194
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2195
+ )
2196
+
2197
+ tokens << node
2198
+ node
2199
+ end
2200
+
2201
+ # :call-seq:
2202
+ # on_rbracket: (String value) -> RBracket
2203
+ def on_rbracket(value)
2204
+ node =
2205
+ RBracket.new(
2206
+ value: value,
2207
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2208
+ )
2209
+
2210
+ tokens << node
2211
+ node
2212
+ end
2213
+
2214
+ # :call-seq:
2215
+ # on_redo: () -> Redo
2216
+ def on_redo
2217
+ keyword = find_token(Kw, "redo")
2218
+
2219
+ Redo.new(value: keyword.value, location: keyword.location)
2220
+ end
2221
+
2222
+ # :call-seq:
2223
+ # on_regexp_add: (
2224
+ # RegexpContent regexp_content,
2225
+ # (StringDVar | StringEmbExpr | TStringContent) part
2226
+ # ) -> RegexpContent
2227
+ def on_regexp_add(regexp_content, part)
2228
+ RegexpContent.new(
2229
+ beginning: regexp_content.beginning,
2230
+ parts: regexp_content.parts << part,
2231
+ location: regexp_content.location.to(part.location)
2232
+ )
2233
+ end
2234
+
2235
+ # :call-seq:
2236
+ # on_regexp_beg: (String value) -> RegexpBeg
2237
+ def on_regexp_beg(value)
2238
+ node =
2239
+ RegexpBeg.new(
2240
+ value: value,
2241
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2242
+ )
2243
+
2244
+ tokens << node
2245
+ node
2246
+ end
2247
+
2248
+ # :call-seq:
2249
+ # on_regexp_end: (String value) -> RegexpEnd
2250
+ def on_regexp_end(value)
2251
+ RegexpEnd.new(
2252
+ value: value,
2253
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2254
+ )
2255
+ end
2256
+
2257
+ # :call-seq:
2258
+ # on_regexp_literal: (
2259
+ # RegexpContent regexp_content,
2260
+ # RegexpEnd ending
2261
+ # ) -> RegexpLiteral
2262
+ def on_regexp_literal(regexp_content, ending)
2263
+ RegexpLiteral.new(
2264
+ beginning: regexp_content.beginning,
2265
+ ending: ending.value,
2266
+ parts: regexp_content.parts,
2267
+ location: regexp_content.location.to(ending.location)
2268
+ )
2269
+ end
2270
+
2271
+ # :call-seq:
2272
+ # on_regexp_new: () -> RegexpContent
2273
+ def on_regexp_new
2274
+ regexp_beg = find_token(RegexpBeg)
2275
+
2276
+ RegexpContent.new(
2277
+ beginning: regexp_beg.value,
2278
+ parts: [],
2279
+ location: regexp_beg.location
2280
+ )
2281
+ end
2282
+
2283
+ # :call-seq:
2284
+ # on_rescue: (
2285
+ # (nil | [untyped] | MRHS | MRHSAddStar) exceptions,
2286
+ # (nil | Field | VarField) variable,
2287
+ # Statements statements,
2288
+ # (nil | Rescue) consequent
2289
+ # ) -> Rescue
2290
+ def on_rescue(exceptions, variable, statements, consequent)
2291
+ keyword = find_token(Kw, "rescue")
2292
+ exceptions = exceptions[0] if exceptions.is_a?(Array)
2293
+
2294
+ last_node = variable || exceptions || keyword
2295
+ statements.bind(
2296
+ find_next_statement_start(last_node.location.end_char),
2297
+ char_pos
2298
+ )
2299
+
2300
+ # We add an additional inner node here that ripper doesn't provide so that
2301
+ # we have a nice place to attach inline comments. But we only need it if
2302
+ # we have an exception or a variable that we're rescuing.
2303
+ rescue_ex =
2304
+ if exceptions || variable
2305
+ RescueEx.new(
2306
+ exceptions: exceptions,
2307
+ variable: variable,
2308
+ location:
2309
+ Location.new(
2310
+ start_line: keyword.location.start_line,
2311
+ start_char: keyword.location.end_char + 1,
2312
+ end_line: last_node.location.end_line,
2313
+ end_char: last_node.location.end_char
2314
+ )
2315
+ )
2316
+ end
2317
+
2318
+ Rescue.new(
2319
+ exception: rescue_ex,
2320
+ statements: statements,
2321
+ consequent: consequent,
2322
+ location:
2323
+ Location.new(
2324
+ start_line: keyword.location.start_line,
2325
+ start_char: keyword.location.start_char,
2326
+ end_line: lineno,
2327
+ end_char: char_pos
2328
+ )
2329
+ )
2330
+ end
2331
+
2332
+ # :call-seq:
2333
+ # on_rescue_mod: (untyped statement, untyped value) -> RescueMod
2334
+ def on_rescue_mod(statement, value)
2335
+ find_token(Kw, "rescue")
2336
+
2337
+ RescueMod.new(
2338
+ statement: statement,
2339
+ value: value,
2340
+ location: statement.location.to(value.location)
2341
+ )
2342
+ end
2343
+
2344
+ # :call-seq:
2345
+ # on_rest_param: ((nil | Ident) name) -> RestParam
2346
+ def on_rest_param(name)
2347
+ location = find_token(Op, "*").location
2348
+ location = location.to(name.location) if name
2349
+
2350
+ RestParam.new(name: name, location: location)
2351
+ end
2352
+
2353
+ # :call-seq:
2354
+ # on_retry: () -> Retry
2355
+ def on_retry
2356
+ keyword = find_token(Kw, "retry")
2357
+
2358
+ Retry.new(value: keyword.value, location: keyword.location)
2359
+ end
2360
+
2361
+ # :call-seq:
2362
+ # on_return: (Args arguments) -> Return
2363
+ def on_return(arguments)
2364
+ keyword = find_token(Kw, "return")
2365
+
2366
+ Return.new(
2367
+ arguments: arguments,
2368
+ location: keyword.location.to(arguments.location)
2369
+ )
2370
+ end
2371
+
2372
+ # :call-seq:
2373
+ # on_return0: () -> Return0
2374
+ def on_return0
2375
+ keyword = find_token(Kw, "return")
2376
+
2377
+ Return0.new(value: keyword.value, location: keyword.location)
2378
+ end
2379
+
2380
+ # :call-seq:
2381
+ # on_rparen: (String value) -> RParen
2382
+ def on_rparen(value)
2383
+ node =
2384
+ RParen.new(
2385
+ value: value,
2386
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2387
+ )
2388
+
2389
+ tokens << node
2390
+ node
2391
+ end
2392
+
2393
+ # :call-seq:
2394
+ # on_sclass: (untyped target, BodyStmt bodystmt) -> SClass
2395
+ def on_sclass(target, bodystmt)
2396
+ beginning = find_token(Kw, "class")
2397
+ ending = find_token(Kw, "end")
2398
+
2399
+ bodystmt.bind(
2400
+ find_next_statement_start(target.location.end_char),
2401
+ ending.location.start_char
2402
+ )
2403
+
2404
+ SClass.new(
2405
+ target: target,
2406
+ bodystmt: bodystmt,
2407
+ location: beginning.location.to(ending.location)
2408
+ )
2409
+ end
2410
+
2411
+ # def on_semicolon(value)
2412
+ # value
2413
+ # end
2414
+
2415
+ # def on_sp(value)
2416
+ # value
2417
+ # end
2418
+
2419
+ # stmts_add is a parser event that represents a single statement inside a
2420
+ # list of statements within any lexical block. It accepts as arguments the
2421
+ # parent stmts node as well as an stmt which can be any expression in
2422
+ # Ruby.
2423
+ def on_stmts_add(statements, statement)
2424
+ location =
2425
+ if statements.body.empty?
2426
+ statement.location
2427
+ else
2428
+ statements.location.to(statement.location)
2429
+ end
2430
+
2431
+ Statements.new(self, body: statements.body << statement, location: location)
2432
+ end
2433
+
2434
+ # :call-seq:
2435
+ # on_stmts_new: () -> Statements
2436
+ def on_stmts_new
2437
+ Statements.new(
2438
+ self,
2439
+ body: [],
2440
+ location: Location.fixed(line: lineno, char: char_pos)
2441
+ )
2442
+ end
2443
+
2444
+ # :call-seq:
2445
+ # on_string_add: (
2446
+ # String string,
2447
+ # (StringEmbExpr | StringDVar | TStringContent) part
2448
+ # ) -> StringContent
2449
+ def on_string_add(string, part)
2450
+ location =
2451
+ string.parts.any? ? string.location.to(part.location) : part.location
2452
+
2453
+ StringContent.new(parts: string.parts << part, location: location)
2454
+ end
2455
+
2456
+ # :call-seq:
2457
+ # on_string_concat: (
2458
+ # (StringConcat | StringLiteral) left,
2459
+ # StringLiteral right
2460
+ # ) -> StringConcat
2461
+ def on_string_concat(left, right)
2462
+ StringConcat.new(
2463
+ left: left,
2464
+ right: right,
2465
+ location: left.location.to(right.location)
2466
+ )
2467
+ end
2468
+
2469
+ # :call-seq:
2470
+ # on_string_content: () -> StringContent
2471
+ def on_string_content
2472
+ StringContent.new(
2473
+ parts: [],
2474
+ location: Location.fixed(line: lineno, char: char_pos)
2475
+ )
2476
+ end
2477
+
2478
+ # :call-seq:
2479
+ # on_string_dvar: ((Backref | VarRef) variable) -> StringDVar
2480
+ def on_string_dvar(variable)
2481
+ embvar = find_token(EmbVar)
2482
+
2483
+ StringDVar.new(
2484
+ variable: variable,
2485
+ location: embvar.location.to(variable.location)
2486
+ )
2487
+ end
2488
+
2489
+ # :call-seq:
2490
+ # on_string_embexpr: (Statements statements) -> StringEmbExpr
2491
+ def on_string_embexpr(statements)
2492
+ embexpr_beg = find_token(EmbExprBeg)
2493
+ embexpr_end = find_token(EmbExprEnd)
2494
+
2495
+ statements.bind(
2496
+ embexpr_beg.location.end_char,
2497
+ embexpr_end.location.start_char
2498
+ )
2499
+
2500
+ location =
2501
+ Location.new(
2502
+ start_line: embexpr_beg.location.start_line,
2503
+ start_char: embexpr_beg.location.start_char,
2504
+ end_line: [
2505
+ embexpr_end.location.end_line,
2506
+ statements.location.end_line
2507
+ ].max,
2508
+ end_char: embexpr_end.location.end_char
2509
+ )
2510
+
2511
+ StringEmbExpr.new(statements: statements, location: location)
2512
+ end
2513
+
2514
+ # :call-seq:
2515
+ # on_string_literal: (String string) -> Heredoc | StringLiteral
2516
+ def on_string_literal(string)
2517
+ heredoc = @heredocs[-1]
2518
+
2519
+ if heredoc && heredoc.ending
2520
+ heredoc = @heredocs.pop
2521
+
2522
+ Heredoc.new(
2523
+ beginning: heredoc.beginning,
2524
+ ending: heredoc.ending,
2525
+ parts: string.parts,
2526
+ location: heredoc.location
2527
+ )
2528
+ else
2529
+ tstring_beg = find_token(TStringBeg)
2530
+ tstring_end = find_token(TStringEnd, location: tstring_beg.location)
2531
+
2532
+ location =
2533
+ Location.new(
2534
+ start_line: tstring_beg.location.start_line,
2535
+ start_char: tstring_beg.location.start_char,
2536
+ end_line: [
2537
+ tstring_end.location.end_line,
2538
+ string.location.end_line
2539
+ ].max,
2540
+ end_char: tstring_end.location.end_char
2541
+ )
2542
+
2543
+ StringLiteral.new(
2544
+ parts: string.parts,
2545
+ quote: tstring_beg.value,
2546
+ location: location
2547
+ )
2548
+ end
2549
+ end
2550
+
2551
+ # :call-seq:
2552
+ # on_super: ((ArgParen | Args) arguments) -> Super
2553
+ def on_super(arguments)
2554
+ keyword = find_token(Kw, "super")
2555
+
2556
+ Super.new(
2557
+ arguments: arguments,
2558
+ location: keyword.location.to(arguments.location)
2559
+ )
2560
+ end
2561
+
2562
+ # symbeg is a token that represents the beginning of a symbol literal. In
2563
+ # most cases it will contain just ":" as in the value, but if its a dynamic
2564
+ # symbol being defined it will contain ":'" or ":\"".
2565
+ def on_symbeg(value)
2566
+ node =
2567
+ SymBeg.new(
2568
+ value: value,
2569
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2570
+ )
2571
+
2572
+ tokens << node
2573
+ node
2574
+ end
2575
+
2576
+ # :call-seq:
2577
+ # on_symbol: (
2578
+ # (Backtick | Const | CVar | GVar | Ident | IVar | Kw | Op) value
2579
+ # ) -> SymbolContent
2580
+ def on_symbol(value)
2581
+ tokens.delete(value)
2582
+
2583
+ SymbolContent.new(value: value, location: value.location)
2584
+ end
2585
+
2586
+ # :call-seq:
2587
+ # on_symbol_literal: (
2588
+ # (
2589
+ # Backtick | Const | CVar | GVar | Ident |
2590
+ # IVar | Kw | Op | SymbolContent
2591
+ # ) value
2592
+ # ) -> SymbolLiteral
2593
+ def on_symbol_literal(value)
2594
+ if value.is_a?(SymbolContent)
2595
+ symbeg = find_token(SymBeg)
2596
+
2597
+ SymbolLiteral.new(
2598
+ value: value.value,
2599
+ location: symbeg.location.to(value.location)
2600
+ )
2601
+ else
2602
+ tokens.delete(value)
2603
+ SymbolLiteral.new(value: value, location: value.location)
2604
+ end
2605
+ end
2606
+
2607
+ # :call-seq:
2608
+ # on_symbols_add: (Symbols symbols, Word word) -> Symbols
2609
+ def on_symbols_add(symbols, word)
2610
+ Symbols.new(
2611
+ beginning: symbols.beginning,
2612
+ elements: symbols.elements << word,
2613
+ location: symbols.location.to(word.location)
2614
+ )
2615
+ end
2616
+
2617
+ # :call-seq:
2618
+ # on_symbols_beg: (String value) -> SymbolsBeg
2619
+ def on_symbols_beg(value)
2620
+ node =
2621
+ SymbolsBeg.new(
2622
+ value: value,
2623
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2624
+ )
2625
+
2626
+ tokens << node
2627
+ node
2628
+ end
2629
+
2630
+ # :call-seq:
2631
+ # on_symbols_new: () -> Symbols
2632
+ def on_symbols_new
2633
+ beginning = find_token(SymbolsBeg)
2634
+
2635
+ Symbols.new(
2636
+ beginning: beginning,
2637
+ elements: [],
2638
+ location: beginning.location
2639
+ )
2640
+ end
2641
+
2642
+ # :call-seq:
2643
+ # on_tlambda: (String value) -> TLambda
2644
+ def on_tlambda(value)
2645
+ node =
2646
+ TLambda.new(
2647
+ value: value,
2648
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2649
+ )
2650
+
2651
+ tokens << node
2652
+ node
2653
+ end
2654
+
2655
+ # :call-seq:
2656
+ # on_tlambeg: (String value) -> TLamBeg
2657
+ def on_tlambeg(value)
2658
+ node =
2659
+ TLamBeg.new(
2660
+ value: value,
2661
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2662
+ )
2663
+
2664
+ tokens << node
2665
+ node
2666
+ end
2667
+
2668
+ # :call-seq:
2669
+ # on_top_const_field: (Const constant) -> TopConstRef
2670
+ def on_top_const_field(constant)
2671
+ operator = find_colon2_before(constant)
2672
+
2673
+ TopConstField.new(
2674
+ constant: constant,
2675
+ location: operator.location.to(constant.location)
2676
+ )
2677
+ end
2678
+
2679
+ # :call-seq:
2680
+ # on_top_const_ref: (Const constant) -> TopConstRef
2681
+ def on_top_const_ref(constant)
2682
+ operator = find_colon2_before(constant)
2683
+
2684
+ TopConstRef.new(
2685
+ constant: constant,
2686
+ location: operator.location.to(constant.location)
2687
+ )
2688
+ end
2689
+
2690
+ # :call-seq:
2691
+ # on_tstring_beg: (String value) -> TStringBeg
2692
+ def on_tstring_beg(value)
2693
+ node =
2694
+ TStringBeg.new(
2695
+ value: value,
2696
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2697
+ )
2698
+
2699
+ tokens << node
2700
+ node
2701
+ end
2702
+
2703
+ # :call-seq:
2704
+ # on_tstring_content: (String value) -> TStringContent
2705
+ def on_tstring_content(value)
2706
+ TStringContent.new(
2707
+ value: value,
2708
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2709
+ )
2710
+ end
2711
+
2712
+ # :call-seq:
2713
+ # on_tstring_end: (String value) -> TStringEnd
2714
+ def on_tstring_end(value)
2715
+ node =
2716
+ TStringEnd.new(
2717
+ value: value,
2718
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
2719
+ )
2720
+
2721
+ tokens << node
2722
+ node
2723
+ end
2724
+
2725
+ # :call-seq:
2726
+ # on_unary: (:not operator, untyped statement) -> Not
2727
+ # | (Symbol operator, untyped statement) -> Unary
2728
+ def on_unary(operator, statement)
2729
+ if operator == :not
2730
+ # We have somewhat special handling of the not operator since if it has
2731
+ # parentheses they don't get reported as a paren node for some reason.
2732
+
2733
+ beginning = find_token(Kw, "not")
2734
+ ending = statement
2735
+
2736
+ range = beginning.location.end_char...statement.location.start_char
2737
+ paren = source[range].include?("(")
2738
+
2739
+ if paren
2740
+ find_token(LParen)
2741
+ ending = find_token(RParen)
2742
+ end
2743
+
2744
+ Not.new(
2745
+ statement: statement,
2746
+ parentheses: paren,
2747
+ location: beginning.location.to(ending.location)
2748
+ )
2749
+ else
2750
+ # Special case instead of using find_token here. It turns out that
2751
+ # if you have a range that goes from a negative number to a negative
2752
+ # number then you can end up with a .. or a ... that's higher in the
2753
+ # stack. So we need to explicitly disallow those operators.
2754
+ index =
2755
+ tokens.rindex do |token|
2756
+ token.is_a?(Op) &&
2757
+ token.location.start_char < statement.location.start_char &&
2758
+ !%w[.. ...].include?(token.value)
2759
+ end
2760
+
2761
+ beginning = tokens.delete_at(index)
2762
+
2763
+ Unary.new(
2764
+ operator: operator[0], # :+@ -> "+"
2765
+ statement: statement,
2766
+ location: beginning.location.to(statement.location)
2767
+ )
2768
+ end
2769
+ end
2770
+
2771
+ # :call-seq:
2772
+ # on_undef: (Array[DynaSymbol | SymbolLiteral] symbols) -> Undef
2773
+ def on_undef(symbols)
2774
+ keyword = find_token(Kw, "undef")
2775
+
2776
+ Undef.new(
2777
+ symbols: symbols,
2778
+ location: keyword.location.to(symbols.last.location)
2779
+ )
2780
+ end
2781
+
2782
+ # :call-seq:
2783
+ # on_unless: (
2784
+ # untyped predicate,
2785
+ # Statements statements,
2786
+ # ((nil | Elsif | Else) consequent)
2787
+ # ) -> Unless
2788
+ def on_unless(predicate, statements, consequent)
2789
+ beginning = find_token(Kw, "unless")
2790
+ ending = consequent || find_token(Kw, "end")
2791
+
2792
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2793
+
2794
+ Unless.new(
2795
+ predicate: predicate,
2796
+ statements: statements,
2797
+ consequent: consequent,
2798
+ location: beginning.location.to(ending.location)
2799
+ )
2800
+ end
2801
+
2802
+ # :call-seq:
2803
+ # on_unless_mod: (untyped predicate, untyped statement) -> UnlessMod
2804
+ def on_unless_mod(predicate, statement)
2805
+ find_token(Kw, "unless")
2806
+
2807
+ UnlessMod.new(
2808
+ statement: statement,
2809
+ predicate: predicate,
2810
+ location: statement.location.to(predicate.location)
2811
+ )
2812
+ end
2813
+
2814
+ # :call-seq:
2815
+ # on_until: (untyped predicate, Statements statements) -> Until
2816
+ def on_until(predicate, statements)
2817
+ beginning = find_token(Kw, "until")
2818
+ ending = find_token(Kw, "end")
2819
+
2820
+ # Consume the do keyword if it exists so that it doesn't get confused for
2821
+ # some other block
2822
+ keyword = find_token(Kw, "do", consume: false)
2823
+ if keyword && keyword.location.start_char > predicate.location.end_char &&
2824
+ keyword.location.end_char < ending.location.start_char
2825
+ tokens.delete(keyword)
2826
+ end
2827
+
2828
+ # Update the Statements location information
2829
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2830
+
2831
+ Until.new(
2832
+ predicate: predicate,
2833
+ statements: statements,
2834
+ location: beginning.location.to(ending.location)
2835
+ )
2836
+ end
2837
+
2838
+ # :call-seq:
2839
+ # on_until_mod: (untyped predicate, untyped statement) -> UntilMod
2840
+ def on_until_mod(predicate, statement)
2841
+ find_token(Kw, "until")
2842
+
2843
+ UntilMod.new(
2844
+ statement: statement,
2845
+ predicate: predicate,
2846
+ location: statement.location.to(predicate.location)
2847
+ )
2848
+ end
2849
+
2850
+ # :call-seq:
2851
+ # on_var_alias: (GVar left, (Backref | GVar) right) -> VarAlias
2852
+ def on_var_alias(left, right)
2853
+ keyword = find_token(Kw, "alias")
2854
+
2855
+ VarAlias.new(
2856
+ left: left,
2857
+ right: right,
2858
+ location: keyword.location.to(right.location)
2859
+ )
2860
+ end
2861
+
2862
+ # :call-seq:
2863
+ # on_var_field: (
2864
+ # (nil | Const | CVar | GVar | Ident | IVar) value
2865
+ # ) -> VarField
2866
+ def on_var_field(value)
2867
+ location =
2868
+ if value
2869
+ value.location
2870
+ else
2871
+ # You can hit this pattern if you're assigning to a splat using
2872
+ # pattern matching syntax in Ruby 2.7+
2873
+ Location.fixed(line: lineno, char: char_pos)
2874
+ end
2875
+
2876
+ VarField.new(value: value, location: location)
2877
+ end
2878
+
2879
+ # :call-seq:
2880
+ # on_var_ref: ((Const | CVar | GVar | Ident | IVar | Kw) value) -> VarRef
2881
+ def on_var_ref(value)
2882
+ pin = find_token(Op, "^", consume: false)
2883
+
2884
+ if pin && pin.location.start_char == value.location.start_char - 1
2885
+ tokens.delete(pin)
2886
+ PinnedVarRef.new(value: value, location: pin.location.to(value.location))
2887
+ else
2888
+ VarRef.new(value: value, location: value.location)
2889
+ end
2890
+ end
2891
+
2892
+ # :call-seq:
2893
+ # on_vcall: (Ident ident) -> VCall
2894
+ def on_vcall(ident)
2895
+ VCall.new(value: ident, location: ident.location)
2896
+ end
2897
+
2898
+ # :call-seq:
2899
+ # on_void_stmt: () -> VoidStmt
2900
+ def on_void_stmt
2901
+ VoidStmt.new(location: Location.fixed(line: lineno, char: char_pos))
2902
+ end
2903
+
2904
+ # :call-seq:
2905
+ # on_when: (
2906
+ # Args arguments,
2907
+ # Statements statements,
2908
+ # (nil | Else | When) consequent
2909
+ # ) -> When
2910
+ def on_when(arguments, statements, consequent)
2911
+ beginning = find_token(Kw, "when")
2912
+ ending = consequent || find_token(Kw, "end")
2913
+
2914
+ statements_start = arguments
2915
+ if token = find_token(Kw, "then", consume: false)
2916
+ tokens.delete(token)
2917
+ statements_start = token
2918
+ end
2919
+
2920
+ statements.bind(
2921
+ find_next_statement_start(statements_start.location.end_char),
2922
+ ending.location.start_char
2923
+ )
2924
+
2925
+ When.new(
2926
+ arguments: arguments,
2927
+ statements: statements,
2928
+ consequent: consequent,
2929
+ location: beginning.location.to(ending.location)
2930
+ )
2931
+ end
2932
+
2933
+ # :call-seq:
2934
+ # on_while: (untyped predicate, Statements statements) -> While
2935
+ def on_while(predicate, statements)
2936
+ beginning = find_token(Kw, "while")
2937
+ ending = find_token(Kw, "end")
2938
+
2939
+ # Consume the do keyword if it exists so that it doesn't get confused for
2940
+ # some other block
2941
+ keyword = find_token(Kw, "do", consume: false)
2942
+ if keyword && keyword.location.start_char > predicate.location.end_char &&
2943
+ keyword.location.end_char < ending.location.start_char
2944
+ tokens.delete(keyword)
2945
+ end
2946
+
2947
+ # Update the Statements location information
2948
+ statements.bind(predicate.location.end_char, ending.location.start_char)
2949
+
2950
+ While.new(
2951
+ predicate: predicate,
2952
+ statements: statements,
2953
+ location: beginning.location.to(ending.location)
2954
+ )
2955
+ end
2956
+
2957
+ # :call-seq:
2958
+ # on_while_mod: (untyped predicate, untyped statement) -> WhileMod
2959
+ def on_while_mod(predicate, statement)
2960
+ find_token(Kw, "while")
2961
+
2962
+ WhileMod.new(
2963
+ statement: statement,
2964
+ predicate: predicate,
2965
+ location: statement.location.to(predicate.location)
2966
+ )
2967
+ end
2968
+
2969
+ # :call-seq:
2970
+ # on_word_add: (
2971
+ # Word word,
2972
+ # (StringEmbExpr | StringDVar | TStringContent) part
2973
+ # ) -> Word
2974
+ def on_word_add(word, part)
2975
+ location =
2976
+ word.parts.empty? ? part.location : word.location.to(part.location)
2977
+
2978
+ Word.new(parts: word.parts << part, location: location)
2979
+ end
2980
+
2981
+ # :call-seq:
2982
+ # on_word_new: () -> Word
2983
+ def on_word_new
2984
+ Word.new(parts: [], location: Location.fixed(line: lineno, char: char_pos))
2985
+ end
2986
+
2987
+ # :call-seq:
2988
+ # on_words_add: (Words words, Word word) -> Words
2989
+ def on_words_add(words, word)
2990
+ Words.new(
2991
+ beginning: words.beginning,
2992
+ elements: words.elements << word,
2993
+ location: words.location.to(word.location)
2994
+ )
2995
+ end
2996
+
2997
+ # :call-seq:
2998
+ # on_words_beg: (String value) -> WordsBeg
2999
+ def on_words_beg(value)
3000
+ node =
3001
+ WordsBeg.new(
3002
+ value: value,
3003
+ location: Location.token(line: lineno, char: char_pos, size: value.size)
3004
+ )
3005
+
3006
+ tokens << node
3007
+ node
3008
+ end
3009
+
3010
+ # :call-seq:
3011
+ # on_words_new: () -> Words
3012
+ def on_words_new
3013
+ beginning = find_token(WordsBeg)
3014
+
3015
+ Words.new(beginning: beginning, elements: [], location: beginning.location)
3016
+ end
3017
+
3018
+ # def on_words_sep(value)
3019
+ # value
3020
+ # end
3021
+
3022
+ # :call-seq:
3023
+ # on_xstring_add: (
3024
+ # XString xstring,
3025
+ # (StringEmbExpr | StringDVar | TStringContent) part
3026
+ # ) -> XString
3027
+ def on_xstring_add(xstring, part)
3028
+ XString.new(
3029
+ parts: xstring.parts << part,
3030
+ location: xstring.location.to(part.location)
3031
+ )
3032
+ end
3033
+
3034
+ # :call-seq:
3035
+ # on_xstring_new: () -> XString
3036
+ def on_xstring_new
3037
+ heredoc = @heredocs[-1]
3038
+
3039
+ location =
3040
+ if heredoc && heredoc.beginning.value.include?("`")
3041
+ heredoc.location
3042
+ else
3043
+ find_token(Backtick).location
3044
+ end
3045
+
3046
+ XString.new(parts: [], location: location)
3047
+ end
3048
+
3049
+ # :call-seq:
3050
+ # on_xstring_literal: (XString xstring) -> Heredoc | XStringLiteral
3051
+ def on_xstring_literal(xstring)
3052
+ heredoc = @heredocs[-1]
3053
+
3054
+ if heredoc && heredoc.beginning.value.include?("`")
3055
+ Heredoc.new(
3056
+ beginning: heredoc.beginning,
3057
+ ending: heredoc.ending,
3058
+ parts: xstring.parts,
3059
+ location: heredoc.location
3060
+ )
3061
+ else
3062
+ ending = find_token(TStringEnd, location: xstring.location)
3063
+
3064
+ XStringLiteral.new(
3065
+ parts: xstring.parts,
3066
+ location: xstring.location.to(ending.location)
3067
+ )
3068
+ end
3069
+ end
3070
+
3071
+ # :call-seq:
3072
+ # on_yield: ((Args | Paren) arguments) -> Yield
3073
+ def on_yield(arguments)
3074
+ keyword = find_token(Kw, "yield")
3075
+
3076
+ Yield.new(
3077
+ arguments: arguments,
3078
+ location: keyword.location.to(arguments.location)
3079
+ )
3080
+ end
3081
+
3082
+ # :call-seq:
3083
+ # on_yield0: () -> Yield0
3084
+ def on_yield0
3085
+ keyword = find_token(Kw, "yield")
3086
+
3087
+ Yield0.new(value: keyword.value, location: keyword.location)
3088
+ end
3089
+
3090
+ # :call-seq:
3091
+ # on_zsuper: () -> ZSuper
3092
+ def on_zsuper
3093
+ keyword = find_token(Kw, "super")
3094
+
3095
+ ZSuper.new(value: keyword.value, location: keyword.location)
3096
+ end
3097
+ end
3098
+ end