syntax_tree 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/gh-pages.yml +26 -0
- data/.github/workflows/main.yml +1 -1
- data/.gitignore +1 -1
- data/CHANGELOG.md +37 -1
- data/Gemfile.lock +8 -7
- data/README.md +5 -1
- data/bin/bench +11 -11
- data/bin/console +3 -3
- data/bin/profile +3 -3
- data/doc/logo.svg +284 -0
- data/exe/stree +1 -0
- data/lib/syntax_tree/cli.rb +100 -41
- data/lib/syntax_tree/formatter.rb +71 -0
- data/lib/syntax_tree/language_server/inlay_hints.rb +93 -0
- data/lib/syntax_tree/language_server.rb +99 -0
- data/lib/syntax_tree/node.rb +11979 -0
- data/lib/syntax_tree/parser.rb +3098 -0
- data/lib/syntax_tree/prettyprint.rb +4 -2
- data/lib/syntax_tree/version.rb +2 -4
- data/lib/syntax_tree.rb +18 -13673
- metadata +10 -3
@@ -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
|