arpaka 0.1.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.
@@ -0,0 +1,1371 @@
1
+ # Lexer for Ruby source input. This deliberately does not use Prism:
2
+ # the parser needs the same small amount of lexical context that CRuby
3
+ # keeps in its parser (EXPR_BEG/EXPR_END and delimiter nesting).
4
+ class LexicalContext
5
+ attr_accessor :begin_expression, :condition_do, :condition_line,
6
+ :ternary_depth, :lambda_pending, :alias_context, :argument_label,
7
+ :lex_state, :command_start, :label_pending, :singleton_class_depth, :control_depth,
8
+ :method_header, :method_body_start
9
+ attr_reader :delimiter_stack, :cmdarg_stack, :condition_stack,
10
+ :token_history, :state_history, :block_stack, :scope_stack, :parser_events,
11
+ :parser_delimiter_stack, :parser_cmdarg_stack, :parser_block_stack,
12
+ :parser_condition_stack, :parser_scope_stack
13
+
14
+ def initialize
15
+ @begin_expression = true
16
+ @condition_do = false
17
+ @condition_line = false
18
+ @ternary_depth = 0
19
+ @lambda_pending = false
20
+ @alias_context = false
21
+ @argument_label = nil
22
+ @label_pending = false
23
+ @singleton_class_depth = 0
24
+ @control_depth = 0
25
+ @method_header = false
26
+ @method_body_start = false
27
+ @lex_state = :expr_beg
28
+ @command_start = true
29
+ @delimiter_stack = []
30
+ @cmdarg_stack = []
31
+ @condition_stack = []
32
+ @token_history = []
33
+ @state_history = []
34
+ @block_stack = []
35
+ @block_delimiter_depths = []
36
+ @scope_stack = []
37
+ @parser_events = []
38
+ @parser_delimiter_stack = []
39
+ @parser_cmdarg_stack = []
40
+ @parser_block_stack = []
41
+ @parser_condition_stack = []
42
+ @parser_scope_stack = []
43
+ end
44
+
45
+ def delimiter_depth
46
+ @delimiter_stack.length
47
+ end
48
+
49
+ def push_delimiter(value)
50
+ @delimiter_stack << value
51
+ end
52
+
53
+ def pop_delimiter(value)
54
+ openers = case value
55
+ when ")", "(" then ["(", :tLPAREN, :tLPAREN_ARG]
56
+ when "]" then ["[", :tLBRACK]
57
+ when "}" then ["{"]
58
+ else [value]
59
+ end
60
+ @delimiter_stack.pop if openers.include?(@delimiter_stack.last)
61
+ end
62
+
63
+ def push_cmdarg(value)
64
+ @cmdarg_stack << value
65
+ end
66
+
67
+ def pop_cmdarg
68
+ @cmdarg_stack.pop
69
+ end
70
+
71
+ def cmdarg?
72
+ @cmdarg_stack.last == true
73
+ end
74
+
75
+ def push_condition(value = true)
76
+ @condition_stack << value
77
+ end
78
+
79
+ def pop_condition
80
+ @condition_stack.pop
81
+ end
82
+
83
+ def condition?
84
+ @condition_stack.any?
85
+ end
86
+
87
+ def push_block(value, delimiter_depth: self.delimiter_depth)
88
+ @block_stack << value
89
+ @block_delimiter_depths << delimiter_depth
90
+ end
91
+
92
+ def pop_block
93
+ @block_delimiter_depths.pop
94
+ @block_stack.pop
95
+ end
96
+
97
+ def block_depth
98
+ @block_stack.length
99
+ end
100
+
101
+ def block_body_at_current_delimiter?
102
+ @block_delimiter_depths.last == delimiter_depth
103
+ end
104
+
105
+ def push_scope(value)
106
+ @scope_stack << value
107
+ end
108
+
109
+ def pop_scope
110
+ @scope_stack.pop
111
+ end
112
+
113
+ def scope_depth
114
+ @scope_stack.length
115
+ end
116
+
117
+ def remember_token(token)
118
+ @token_history << token
119
+ @token_history.shift while @token_history.length > 8
120
+ end
121
+
122
+ def remember_state(token)
123
+ @state_history << {
124
+ token: token,
125
+ lex_state: @lex_state,
126
+ begin_expression: @begin_expression,
127
+ command_start: @command_start,
128
+ delimiter_depth: delimiter_depth,
129
+ cmdarg_depth: @cmdarg_stack.length,
130
+ condition_depth: @condition_stack.length,
131
+ block_depth: @block_stack.length,
132
+ scope_depth: @scope_stack.length
133
+ }.freeze
134
+ end
135
+
136
+ def parser_shift(token, value, state, action)
137
+ @parser_events << [:shift, token, state, action].freeze
138
+ if ["(", "[", :tLPAREN_ARG].include?(token)
139
+ @parser_delimiter_stack << token
140
+ @parser_cmdarg_stack << (token == :tLPAREN_ARG)
141
+ elsif [")", "]", "}"].include?(token)
142
+ opener = {")" => "(", "]" => "[", "}" => "{"
143
+ }.fetch(token)
144
+ parser_openers = {
145
+ "(" => ["(", :tLPAREN, :tLPAREN_ARG],
146
+ "[" => ["[", :tLBRACK],
147
+ "{" => ["{"]
148
+ }
149
+ @parser_delimiter_stack.pop if parser_openers.fetch(opener).include?(@parser_delimiter_stack.last)
150
+ @parser_cmdarg_stack.pop
151
+ end
152
+ if [:keyword_do, :keyword_do_block, :keyword_do_LAMBDA, :tLAMBEG].include?(token) ||
153
+ token == "{" || token == :tLBRACE_ARG
154
+ @parser_block_stack << token
155
+ elsif token == "}" && @parser_block_stack.last
156
+ @parser_block_stack.pop
157
+ elsif token == :keyword_end && @parser_block_stack.last
158
+ @parser_block_stack.pop
159
+ end
160
+ if [:keyword_while, :keyword_until, :keyword_for].include?(token)
161
+ @parser_condition_stack << token
162
+ elsif token == :keyword_do_cond && @parser_condition_stack.last
163
+ @parser_condition_stack.pop
164
+ elsif token == :keyword_end && @parser_condition_stack.last
165
+ @parser_condition_stack.pop
166
+ end
167
+ if token == :keyword_def
168
+ @parser_scope_stack << :method
169
+ elsif [:keyword_class, :keyword_module].include?(token)
170
+ @parser_scope_stack << token
171
+ elsif token == :tLAMBDA
172
+ @parser_scope_stack << :lambda
173
+ elsif token == "}" && @parser_scope_stack.last == :lambda
174
+ @parser_scope_stack.pop
175
+ elsif token == :keyword_end && @parser_scope_stack.last
176
+ @parser_scope_stack.pop
177
+ end
178
+ end
179
+
180
+ def parser_reduce(rule, state)
181
+ @parser_events << [:reduce, rule, state].freeze
182
+ end
183
+ end
184
+
185
+ class Lexer
186
+ Error = LexerError
187
+
188
+ attr_reader :context
189
+
190
+ KEYWORDS = {
191
+ "class" => :keyword_class, "module" => :keyword_module,
192
+ "def" => :keyword_def, "undef" => :keyword_undef, "begin" => :keyword_begin,
193
+ "rescue" => :keyword_rescue, "ensure" => :keyword_ensure,
194
+ "end" => :keyword_end, "if" => :keyword_if,
195
+ "unless" => :keyword_unless, "then" => :keyword_then,
196
+ "elsif" => :keyword_elsif, "else" => :keyword_else,
197
+ "case" => :keyword_case, "when" => :keyword_when,
198
+ "while" => :keyword_while, "until" => :keyword_until,
199
+ "for" => :keyword_for, "break" => :keyword_break,
200
+ "next" => :keyword_next, "redo" => :keyword_redo,
201
+ "retry" => :keyword_retry, "return" => :keyword_return,
202
+ "yield" => :keyword_yield, "super" => :keyword_super,
203
+ "self" => :keyword_self, "nil" => :keyword_nil,
204
+ "true" => :keyword_true, "false" => :keyword_false,
205
+ "and" => :keyword_and, "or" => :keyword_or,
206
+ "not" => :keyword_not, "alias" => :keyword_alias,
207
+ "defined?" => :keyword_defined, "in" => :keyword_in,
208
+ "BEGIN" => :keyword_BEGIN, "END" => :keyword_END,
209
+ "__LINE__" => :keyword__LINE__, "__FILE__" => :keyword__FILE__,
210
+ "__ENCODING__" => :keyword__ENCODING__
211
+ }.freeze
212
+
213
+ OPERATORS = %w[[]= [] ... .. <=> === == != =~ !~ >= <= && || << >> ** => :: &. -> += -= *= /= %= **= <<= >>= &&= ||= |= &= ^=].freeze
214
+ OP_TOKENS = {
215
+ "**" => :tPOW, "<=>" => :tCMP, "==" => :tEQ, "===" => :tEQQ,
216
+ "!=" => :tNEQ, ">=" => :tGEQ, "<=" => :tLEQ, "&&" => :tANDOP,
217
+ "||" => :tOROP, "=~" => :tMATCH, "!~" => :tNMATCH,
218
+ ".." => :tDOT2, "..." => :tDOT3, "<<" => :tLSHFT,
219
+ ">>" => :tRSHFT, "&." => :tANDDOT, "::" => :tCOLON2,
220
+ "=>" => :tASSOC, "->" => :tLAMBDA, "[]" => :tAREF, "[]=" => :tASET
221
+ }.freeze
222
+
223
+ def initialize(source, filename: "(ruby)")
224
+ unless source.is_a?(String)
225
+ raise ArgumentError, "source must be a String"
226
+ end
227
+ unless [Encoding::UTF_8, Encoding::US_ASCII].include?(source.encoding) && source.valid_encoding?
228
+ raise Error, "#{filename}:1:0: invalid UTF-8 source"
229
+ end
230
+ @source = source.b
231
+ @filename = filename
232
+ @index = 0
233
+ @line = 1
234
+ @column = 0
235
+ @previous_previous = nil
236
+ @previous = nil
237
+ @previous_value = nil
238
+ @context = LexicalContext.new
239
+ @pending = []
240
+ @heredoc_queue = []
241
+ @deferred_suffix = nil
242
+ @heredoc_newline_pending = false
243
+ @class_superclass = false
244
+ @local_variables = {}
245
+ end
246
+
247
+ def each
248
+ return enum_for(__method__) unless block_given?
249
+ until eof? && @pending.empty? && @heredoc_queue.empty? && @deferred_suffix.nil?
250
+ token = next_token
251
+ yield token if token
252
+ end
253
+ yield [0, nil]
254
+ end
255
+
256
+ private
257
+
258
+ def eof?
259
+ @index >= @source.bytesize
260
+ end
261
+
262
+ def byte(offset = 0)
263
+ @source.getbyte(@index + offset)
264
+ end
265
+
266
+ def advance(count = 1)
267
+ count.times do
268
+ value = byte
269
+ @index += 1
270
+ if value == 10
271
+ @line += 1
272
+ @column = 0
273
+ else
274
+ @column += 1
275
+ end
276
+ end
277
+ end
278
+
279
+ def fail!(message, index = @index, line = @line, column = @column)
280
+ raise Error, "#{@filename}:#{line}:#{column}: #{message}"
281
+ end
282
+
283
+ def next_token
284
+ if @pending.empty? && @heredoc_queue.any?
285
+ load_queued_heredoc
286
+ elsif @pending.empty? && @deferred_suffix
287
+ suffix = @deferred_suffix
288
+ @deferred_suffix = nil
289
+ @pending.concat(heredoc_suffix_tokens(suffix))
290
+ @pending.pop if @pending.last == [0, nil]
291
+ @pending << ["\n", nil]
292
+ @heredoc_newline_pending = false
293
+ elsif @pending.empty? && @heredoc_newline_pending
294
+ @pending << ["\n", nil]
295
+ @heredoc_newline_pending = false
296
+ end
297
+ unless @pending.empty?
298
+ value = @pending.shift
299
+ return next_token if value[0] == "\n" && newline_ignored?
300
+ @previous_previous = @previous
301
+ @previous = value[0]
302
+ @previous_value = value[1]
303
+ update_pending_delimiter(value[0])
304
+ update_argument_label(value[0], value[1])
305
+ @context.alias_context = false if value[0] == ";"
306
+ @context.remember_token(value[0])
307
+ @context.begin_expression = expression_begin_after(value[0])
308
+ @context.lex_state = lexical_state_after(value[0])
309
+ @context.command_start = command_start_after(value[0])
310
+ update_block_context(value[0])
311
+ @context.remember_state(value[0])
312
+ return value
313
+ end
314
+ skip_space_and_comments
315
+ return nil if eof?
316
+
317
+ start = @index
318
+ value = case byte
319
+ when 10
320
+ advance
321
+ return next_token if @previous == "\n"
322
+ return next_token if next_chain_byte == 46
323
+ if @context.alias_context && [:tEQ, :tUMINUS, :tUPLUS].include?(@previous)
324
+ @context.alias_context = false
325
+ ["\n", nil]
326
+ elsif @class_superclass && next_word_is_terminator?
327
+ @class_superclass = false
328
+ return [";", nil]
329
+ else
330
+ @class_superclass = false
331
+ return next_token if newline_ignored? || @previous == ";"
332
+ @context.pop_condition if @context.condition? && @context.condition_do
333
+ @context.condition_do = false
334
+ ["\n", nil]
335
+ end
336
+ when 39, 34, 96
337
+ if byte == 96 && @previous == :tSYMBEG
338
+ operator_or_punctuation(start)
339
+ else
340
+ string_token(byte, start)
341
+ end
342
+ when 47
343
+ [:keyword_def, :keyword_alias, :tSYMBEG].include?(@previous) ? operator_or_punctuation(start) : regexp_or_operator(start)
344
+ when 37
345
+ [:keyword_def, :keyword_alias, :tSYMBEG].include?(@previous) ? operator_or_punctuation(start) : percent_token(start)
346
+ when 60
347
+ heredoc_or_operator(start)
348
+ when 48..57
349
+ number_token(start)
350
+ when 36, 64
351
+ variable_token(start)
352
+ when 63
353
+ character_or_question(start)
354
+ when 58
355
+ symbol_or_colon(start)
356
+ when 65..90, 95, 97..122, 128..255
357
+ identifier_token(start)
358
+ else
359
+ operator_or_punctuation(start)
360
+ end
361
+ @previous_previous = @previous
362
+ @previous = value && value[0]
363
+ @previous_value = value && value[1]
364
+ update_argument_label(value && value[0], value && value[1])
365
+ @context.alias_context = false if value && value[0] == ";"
366
+ @context.remember_token(value && value[0])
367
+ @context.begin_expression = expression_begin_after(value && value[0])
368
+ @context.lex_state = lexical_state_after(value && value[0])
369
+ @context.command_start = command_start_after(value && value[0])
370
+ update_block_context(value && value[0])
371
+ @context.remember_state(value && value[0])
372
+ value
373
+ end
374
+
375
+ def skip_space_and_comments
376
+ loop do
377
+ while [9, 11, 12, 13, 32].include?(byte)
378
+ advance
379
+ end
380
+ if byte == 92 && byte(1) == 10
381
+ advance(2)
382
+ next
383
+ end
384
+ break unless byte == 35
385
+ advance
386
+ advance while !eof? && byte != 10
387
+ end
388
+ end
389
+
390
+ def newline_ignored?
391
+ ignored = (@context.delimiter_depth.positive? &&
392
+ ["(", "[", "{", :tLPAREN, :tLPAREN_ARG, :tLBRACK, :tLBRACE, :tLBRACE_ARG].include?(@previous)) ||
393
+ (["[", :tLBRACK].include?(@context.delimiter_stack.last) && next_non_space_byte == 93) ||
394
+ @previous == "\n" ||
395
+ ["+", "-", "*", "/", "%", "=", "?", ":", ",", ".", "&", "|", "^", "!", "<<", ">>", "&&", "||", "=>", "<", ">", :keyword_and, :keyword_or, :keyword_not, :tAMPER, :tPIPE, :tSTAR, :tDSTAR, :tUMINUS, :tUPLUS, :tDOT2, :tDOT3, :tPOW, :tCMP, :tEQ, :tEQQ, :tNEQ, :tGEQ, :tLEQ, :tANDOP, :tOROP, :tMATCH, :tNMATCH, :tLSHFT, :tRSHFT, :tASSOC, :tLAMBDA, :tCOLON2, :tCOLON3, :tANDDOT].include?(@previous) ||
396
+ @previous == :tLABEL ||
397
+ [:modifier_if, :modifier_unless, :modifier_while, :modifier_until].include?(@previous) ||
398
+ [:tANDOP, :tOROP, :tMATCH, :tNMATCH, :tASSOC, :tOP_ASGN].include?(@previous) ||
399
+ (@previous == "{" && next_non_space_byte == 124) ||
400
+ (@context.ternary_depth.positive? && next_non_space_byte == 58) ||
401
+ @previous == :keyword_def ||
402
+ next_chain_byte == 46
403
+ ignored = false if @previous_previous == :tSYMBEG &&
404
+ [:tLSHFT, :tRSHFT, :tSTAR, :tDSTAR, :tPOW, :tEQ, :tEQQ, :tNEQ, "&",
405
+ :tCMP, :tGEQ, :tLEQ, :tANDOP, :tOROP, :tMATCH, :tNMATCH,
406
+ :tASSOC, :tOP_ASGN, :tUMINUS, :tUPLUS, "<", ">"].include?(@previous)
407
+ ignored = false if @previous_previous == :keyword_def &&
408
+ [:tUMINUS, :tUPLUS].include?(@previous)
409
+ @context.condition_line = false if ignored && @context.condition_line
410
+ ignored
411
+ end
412
+
413
+ def next_word_is_terminator?
414
+ position = @index
415
+ position += 1 while [9, 11, 12, 13, 32].include?(@source.getbyte(position))
416
+ %w[else elsif end when].any? do |word|
417
+ @source.byteslice(position, word.bytesize) == word &&
418
+ !identifier_byte?(@source.getbyte(position + word.bytesize))
419
+ end
420
+ end
421
+
422
+ def next_non_space_byte
423
+ position = @index
424
+ position += 1 while [9, 11, 12, 13, 32].include?(@source.getbyte(position))
425
+ @source.getbyte(position)
426
+ end
427
+
428
+ def next_chain_byte
429
+ position = @index
430
+ loop do
431
+ position += 1 while [9, 11, 12, 13, 32, 10].include?(@source.getbyte(position))
432
+ break unless @source.getbyte(position) == 35
433
+ position += 1
434
+ position += 1 while @source.getbyte(position) && @source.getbyte(position) != 10
435
+ end
436
+ @source.getbyte(position)
437
+ end
438
+
439
+ def update_pending_delimiter(token)
440
+ if ["(", "[", :tLPAREN_ARG].include?(token)
441
+ @context.push_delimiter(token)
442
+ @context.push_cmdarg(token == :tLPAREN_ARG)
443
+ elsif [")", "]", "}"].include?(token)
444
+ opener = {")" => "(", "]" => "[", "}" => "{"
445
+ }.fetch(token)
446
+ @context.pop_delimiter(opener)
447
+ @context.pop_cmdarg
448
+ @context.method_body_start = true if token == ")" && @context.method_header
449
+ @context.method_header = false if token == ")"
450
+ end
451
+ end
452
+
453
+ def update_argument_label(token, value)
454
+ if token == :tLABEL
455
+ @context.argument_label = value
456
+ @context.label_pending = true
457
+ elsif ["\n", ";"].include?(token)
458
+ @context.argument_label = nil
459
+ end
460
+ end
461
+
462
+ def expression_begin_after(token)
463
+ return true if token.nil? || token == "\n"
464
+ return false if receiver_operator?(token)
465
+ return false if token == ")" || token == "]" || token == "}"
466
+ return false if token == :keyword_end
467
+ return false if token == :tINTEGER || token == :tFLOAT || token == :tRATIONAL || token == :tIMAGINARY || token == :tCHAR
468
+ return false if @previous_previous == :tSYMBEG && [:tASET, "&"].include?(token)
469
+ return false if token == :tIDENTIFIER || token == :tCONSTANT || token == :tFID || token == :tSTRING_END || token == :tREGEXP_END
470
+ return false if token == :keyword_true || token == :keyword_false || token == :keyword_nil || token == :keyword_self
471
+ return false if [:keyword__LINE__, :keyword__FILE__, :keyword__ENCODING__].include?(token)
472
+ return false if token == :tIVAR || token == :tGVAR || token == :tCVAR || token == :tNTH_REF
473
+ return false if token == :keyword_redo
474
+ true
475
+ end
476
+
477
+ def lexical_state_after(token)
478
+ return :expr_beg if token.nil? || token == "\n"
479
+ return :expr_arg if receiver_operator?(token)
480
+ return :expr_fname if @previous_previous == :keyword_def || @context.alias_context
481
+ return :expr_end if token == 0
482
+ if @context.label_pending && token != :tLABEL
483
+ @context.label_pending = false
484
+ return :expr_labeled
485
+ end
486
+ return :expr_end if [")", "]", "}", :keyword_end, :tSTRING_END, :tREGEXP_END,
487
+ :tINTEGER, :tFLOAT, :tRATIONAL, :tIMAGINARY, :tCHAR, :tIDENTIFIER,
488
+ :tCONSTANT, :tFID, :tIVAR, :tGVAR, :tCVAR, :tNTH_REF,
489
+ :keyword_true, :keyword_false, :keyword_nil, :keyword_self,
490
+ :keyword__LINE__, :keyword__FILE__, :keyword__ENCODING__].include?(token)
491
+ return :expr_end if @previous_previous == :tSYMBEG && [:tASET, "&"].include?(token)
492
+ return :expr_label if token == :tLABEL
493
+ :expr_beg
494
+ end
495
+
496
+ def command_start_after(token)
497
+ [nil, 0, "\n", ";"].include?(token)
498
+ end
499
+
500
+ def update_block_context(token)
501
+ case token
502
+ when :keyword_if, :keyword_unless, :keyword_case, :keyword_begin,
503
+ :keyword_while, :keyword_until, :keyword_for
504
+ @context.control_depth += 1
505
+ when :keyword_def
506
+ @context.method_header = true
507
+ @context.push_scope(:method)
508
+ when :keyword_do, :keyword_do_block
509
+ @context.push_block(:do_block)
510
+ when :keyword_do_LAMBDA
511
+ @context.push_block(:lambda)
512
+ when :tLAMBDA
513
+ @context.push_scope(:lambda)
514
+ when :tLSHFT
515
+ @context.singleton_class_depth += 1 if @previous_previous == :keyword_class
516
+ when :tLAMBEG
517
+ @context.push_block(:lambda)
518
+ when "{", :tLBRACE_ARG
519
+ @context.push_block(:brace_block)
520
+ when "}"
521
+ @context.pop_block if [:brace_block, :lambda].include?(@context.block_stack.last)
522
+ @context.pop_scope if @context.scope_stack.last == :lambda
523
+ when :keyword_end
524
+ if @context.control_depth.positive?
525
+ @context.control_depth -= 1
526
+ elsif @context.singleton_class_depth.positive?
527
+ @context.singleton_class_depth -= 1
528
+ @context.pop_scope if @context.scope_stack.last == :keyword_class
529
+ elsif [:method, :keyword_class, :keyword_module].include?(@context.scope_stack.last)
530
+ @context.pop_scope
531
+ else
532
+ @context.pop_block if [:do_block, :lambda].include?(@context.block_stack.last)
533
+ @context.pop_scope if @context.scope_stack.last == :lambda
534
+ end
535
+ end
536
+ if @context.method_header && token != :keyword_def && token != "(" &&
537
+ ![".", :tCOLON2, :tANDDOT].include?(token) &&
538
+ !(@context.method_header && [".", :tCOLON2, :tANDDOT].include?(@previous_previous)) &&
539
+ !@context.delimiter_stack.include?("(") &&
540
+ !(@previous_previous == :keyword_def &&
541
+ [:keyword_self, :tIDENTIFIER, :tCONSTANT, :tIVAR, :tCVAR, :tGVAR, :tFID].include?(token))
542
+ @context.method_header = false
543
+ end
544
+ @context.method_body_start = false unless [")", "\n"].include?(token)
545
+ end
546
+
547
+ def identifier_token(start)
548
+ while identifier_byte?(byte)
549
+ advance
550
+ end
551
+ word = @source.byteslice(start, @index - start).force_encoding(Encoding::UTF_8)
552
+ if byte == 63 && word == "defined"
553
+ advance
554
+ word = "defined?"
555
+ end
556
+ if @previous == :keyword_undef && (KEYWORDS.key?(word) || word == "do")
557
+ return [:tFID, word.to_sym]
558
+ end
559
+ if byte == 63 && byte(1) == 58
560
+ advance(2)
561
+ return [:tLABEL, (word + "?").to_sym]
562
+ end
563
+ if [33, 63].include?(byte) && byte(1) != 61
564
+ suffix = byte.chr
565
+ advance
566
+ return [:tFID, (word + suffix).to_sym]
567
+ end
568
+ if byte == 61 && byte(1) != 61 &&
569
+ (@previous == :keyword_def ||
570
+ @context.method_header && [".", :tCOLON2, :tANDDOT].include?(@previous))
571
+ advance
572
+ return [:tFID, (word + "=").to_sym]
573
+ end
574
+ if byte == 61 && @context.alias_context
575
+ advance
576
+ return [:tFID, (word + "=").to_sym]
577
+ end
578
+ if byte == 61 && byte(1) != 61 && @previous == :tSYMBEG
579
+ advance
580
+ return [:tIDENTIFIER, (word + "=").to_sym]
581
+ end
582
+ @local_variables[word.to_sym] = true if byte == 61 && byte(1) != 61 &&
583
+ ![".", :tCOLON2, :tANDDOT].include?(@previous)
584
+ label_in_ternary = ["(", "[", :tLPAREN, :tLPAREN_ARG, :tLBRACK, ","].include?(@previous)
585
+ if byte == 58 && byte(1) != 58 &&
586
+ (@context.ternary_depth.zero? || label_in_ternary) &&
587
+ (byte(1).nil? || [9, 10, 11, 12, 13, 32, 44, 41, 93, 125, 124].include?(byte(1)) || @context.delimiter_stack.any?)
588
+ advance
589
+ return [:tLABEL, word.to_sym]
590
+ end
591
+ if word == "do" && [".", :tCOLON2, :tANDDOT].include?(@previous)
592
+ return [:tFID, word.to_sym]
593
+ end
594
+ if word == "do" && @context.lambda_pending
595
+ return [do_token, word.to_sym]
596
+ end
597
+ if word == "do" && !@context.condition_do && no_argument_block?
598
+ return [:keyword_do, word.to_sym]
599
+ end
600
+ token = KEYWORDS[word]
601
+ if token && [".", :tCOLON2, :tANDDOT].include?(@previous)
602
+ token = next_non_space_byte == 61 ? :tIDENTIFIER : :tFID
603
+ elsif token && @previous == :keyword_def && word != "self"
604
+ token = :tFID
605
+ elsif (!@context.begin_expression || @context.alias_context && @previous == :keyword_for || [:keyword_return, :keyword_break, :keyword_next, :keyword_end, :keyword_yield, :keyword_super].include?(@previous)) && { "if" => :modifier_if, "unless" => :modifier_unless,
606
+ "while" => :modifier_while, "until" => :modifier_until,
607
+ "rescue" => :modifier_rescue }.key?(word)
608
+ token = { "if" => :modifier_if, "unless" => :modifier_unless,
609
+ "while" => :modifier_while, "until" => :modifier_until,
610
+ "rescue" => :modifier_rescue }.fetch(word, token)
611
+ end
612
+ token ||= word == "do" ? do_token : (word.getbyte(0).between?(65, 90) ? :tCONSTANT : :tIDENTIFIER)
613
+ if [:keyword_while, :keyword_until, :keyword_for].include?(token)
614
+ @context.condition_do = true
615
+ @context.push_condition
616
+ end
617
+ @context.alias_context = true if token == :keyword_alias
618
+ [token, word.to_sym]
619
+ rescue EncodingError
620
+ fail!("invalid UTF-8 identifier", start)
621
+ end
622
+
623
+ def identifier_byte?(value)
624
+ value && (value == 95 || value.between?(65, 90) || value.between?(97, 122) || value >= 128 || value.between?(48, 57))
625
+ end
626
+
627
+ def do_token
628
+ if @context.condition_do && @context.condition?
629
+ @context.pop_condition
630
+ @context.condition_do = false
631
+ :keyword_do_cond
632
+ elsif @context.lambda_pending
633
+ @context.lambda_pending = false
634
+ :keyword_do_LAMBDA
635
+ elsif @previous == :tIDENTIFIER && @previous_previous == :tLABEL && @previous_value == :lambda
636
+ :keyword_do
637
+ elsif @previous == ")" && @context.token_history.each_cons(2).any? { |left, right| left == :tUMINUS && right == :tLPAREN }
638
+ :keyword_do_block
639
+ elsif [")", :keyword_super, :keyword_yield].include?(@previous)
640
+ :keyword_do
641
+ elsif @previous == :tLAMBDA
642
+ :keyword_do_LAMBDA
643
+ else
644
+ :keyword_do_block
645
+ end
646
+ end
647
+
648
+ def no_argument_block?
649
+ return false unless [:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous)
650
+ return false if @context.argument_label == :at && [".", :tCOLON2, :tANDDOT].include?(@previous_previous)
651
+ return false if @context.token_history == ["+", :tINTEGER, ".", :tIDENTIFIER]
652
+ return false if [:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous_previous)
653
+ return false if [:tSYMBEG, :tLABEL, :tCOLON2].include?(@previous_previous)
654
+ return true if @previous_value == :lambda && @previous_previous == :tLSHFT
655
+ return false if [:tLSHFT, :tLAMBDA].include?(@previous_previous)
656
+ true
657
+ end
658
+
659
+ def no_argument_brace_block?
660
+ return false if [".", :tCOLON2].include?(@previous_previous)
661
+ return false if [:tLABEL, :tLSHFT, :tOROP, :tANDOP, :tASSOC, :tAMPER, "=", "(", ",", ":", "?"].include?(@previous_previous)
662
+ no_argument_block?
663
+ end
664
+
665
+ def number_token(start)
666
+ base = 10
667
+ if byte == 48 && [120, 88, 98, 66, 111, 79].include?(byte(1))
668
+ base = { 120 => 16, 88 => 16, 98 => 2, 66 => 2, 111 => 8, 79 => 8 }.fetch(byte(1))
669
+ advance(2)
670
+ digit_start = @index
671
+ advance while digit_byte?(byte, base) || byte == 95
672
+ fail!("invalid numeric literal", start) if @index == digit_start
673
+ text = @source.byteslice(digit_start, @index - digit_start).delete("_")
674
+ return [:tINTEGER, text.to_i(base)]
675
+ end
676
+ advance while digit_byte?(byte, 10) || byte == 95
677
+ float = false
678
+ if byte == 46 && digit_byte?(byte(1), 10)
679
+ float = true
680
+ advance
681
+ advance while digit_byte?(byte, 10) || byte == 95
682
+ end
683
+ if byte == 101 || byte == 69
684
+ float = true
685
+ advance
686
+ advance if byte == 43 || byte == 45
687
+ fail!("invalid numeric literal", start) unless digit_byte?(byte, 10)
688
+ advance while digit_byte?(byte, 10) || byte == 95
689
+ end
690
+ suffix = byte
691
+ advance if [105, 114].include?(suffix)
692
+ text = @source.byteslice(start, @index - start).delete("_")
693
+ return [:tRATIONAL, Rational(text.delete_suffix("r"))] if suffix == 114
694
+ return [:tIMAGINARY, Complex(0, text.delete_suffix("i").to_f)] if suffix == 105
695
+ [float ? :tFLOAT : :tINTEGER, float ? text.to_f : text.to_i]
696
+ rescue ArgumentError, ZeroDivisionError
697
+ fail!("invalid numeric literal", start)
698
+ end
699
+
700
+ def digit_byte?(value, base)
701
+ return false unless value
702
+ value.between?(48, 57) && value - 48 < base || base == 16 && value.between?(65, 70) || base == 16 && value.between?(97, 102)
703
+ end
704
+
705
+ def variable_token(start)
706
+ marker = byte
707
+ advance
708
+ advance if marker == 64 && byte == 64
709
+ if marker == 36 && [33, 38, 39, 42, 43, 46, 47, 58, 60, 62, 61, 63, 96, 126].include?(byte)
710
+ advance
711
+ text = @source.byteslice(start, @index - start)
712
+ return [:tGVAR, text.to_sym]
713
+ end
714
+ if marker == 36 && byte == 36
715
+ advance
716
+ return [:tGVAR, :"$$"]
717
+ end
718
+ if marker == 36 && byte && byte.between?(48, 57)
719
+ advance while byte && byte.between?(48, 57)
720
+ return [:tNTH_REF, @source.byteslice(start + 1, @index - start - 1).to_i]
721
+ end
722
+ advance while identifier_byte?(byte)
723
+ text = @source.byteslice(start, @index - start)
724
+ token = marker == 36 ? :tGVAR : (text.start_with?("@@") ? :tCVAR : :tIVAR)
725
+ [token, text.to_sym]
726
+ end
727
+
728
+ def character_or_question(start)
729
+ # Since Ruby 2.4, `?""` is the ternary operator followed by an empty
730
+ # string, rather than a character literal. Keep the question mark as
731
+ # punctuation in this one ambiguous case.
732
+ if [39, 34].include?(byte(1)) && byte(1) == byte(2)
733
+ advance(3)
734
+ @pending = [[:tSTRING_BEG, nil], [:tSTRING_END, nil]]
735
+ return ["?", nil]
736
+ end
737
+ command_character = !@context.begin_expression &&
738
+ [:tIDENTIFIER, :tCONSTANT, :tFID, :tCHAR].include?(@previous) &&
739
+ start.positive? && [9, 32].include?(@source.getbyte(start - 1))
740
+ command_character = true if @previous == :tFID && @previous_value.to_s.end_with?("!")
741
+ command_character = true if @previous == :tCHAR && start.positive? && [9, 32].include?(@source.getbyte(start - 1))
742
+ command_character = false if command_character && ternary_question?(start)
743
+ return operator_or_punctuation(start) unless (@context.begin_expression || command_character) &&
744
+ byte(1) && byte(1) != 32 && byte(1) != 10
745
+ advance
746
+ value = if byte == 92
747
+ escape_sequence(start)
748
+ else
749
+ length = utf8_character_length(byte)
750
+ character = @source.byteslice(@index, length).force_encoding(Encoding::UTF_8)
751
+ advance(length)
752
+ character
753
+ end
754
+ [:tCHAR, value]
755
+ end
756
+
757
+ def ternary_question?(start)
758
+ depth = 0
759
+ quote = nil
760
+ escaped = false
761
+ index = start + 1
762
+ while index < @source.bytesize
763
+ value = @source.getbyte(index)
764
+ if quote
765
+ if escaped
766
+ escaped = false
767
+ elsif value == 92
768
+ escaped = true
769
+ elsif value == quote
770
+ quote = nil
771
+ end
772
+ else
773
+ case value
774
+ when 39, 34, 96
775
+ quote = value
776
+ when 40, 91, 123
777
+ depth += 1
778
+ when 41, 93, 125
779
+ depth -= 1 if depth.positive?
780
+ when 58
781
+ return true if depth.zero?
782
+ when 10, 59
783
+ return false if depth.zero?
784
+ end
785
+ end
786
+ index += 1
787
+ end
788
+ false
789
+ end
790
+
791
+ def utf8_character_length(first_byte)
792
+ return 1 if first_byte < 0x80
793
+ return 2 if first_byte.between?(0xC2, 0xDF)
794
+ return 3 if first_byte.between?(0xE0, 0xEF)
795
+ return 4 if first_byte.between?(0xF0, 0xF4)
796
+ fail!("invalid UTF-8 character")
797
+ end
798
+
799
+ def symbol_or_colon(start)
800
+ symbol_position = @context.begin_expression || ([:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous) && @context.ternary_depth.zero?)
801
+ return operator_or_punctuation(start) unless symbol_position && byte(1) &&
802
+ ![9, 10, 11, 12, 13, 32].include?(byte(1)) && byte(1) != 58
803
+ advance
804
+ if byte == 39 || byte == 34
805
+ quote = byte
806
+ advance
807
+ @pending = read_interpolated_quoted(quote, start)
808
+ @pending << [:tSTRING_END, nil]
809
+ end
810
+ [:tSYMBEG, nil]
811
+ end
812
+
813
+ def string_token(quote, start)
814
+ advance
815
+ content_tokens = read_interpolated_quoted(quote, start)
816
+ label = byte == 58 && @context.ternary_depth.zero?
817
+ advance if label
818
+ terminator = label ? :tLABEL_END : :tSTRING_END
819
+ @pending = content_tokens
820
+ @pending << [terminator, nil]
821
+ [quote == 96 ? :tXSTRING_BEG : :tSTRING_BEG, nil]
822
+ end
823
+
824
+ def read_interpolated_quoted(quote, start)
825
+ return [[:tSTRING_CONTENT, read_quoted(quote, interpolate: false, start: start)]] if quote == 39
826
+ content = +""
827
+ tokens = []
828
+ until eof?
829
+ value = byte
830
+ if value == quote
831
+ advance
832
+ tokens << [:tSTRING_CONTENT, content] unless content.empty?
833
+ return tokens
834
+ elsif value == 92
835
+ content << escape_sequence(start)
836
+ elsif value == 35 && byte(1) == 123
837
+ tokens << [:tSTRING_CONTENT, content] unless content.empty?
838
+ content = +""
839
+ advance(2)
840
+ expression = read_interpolation_source(start)
841
+ inner = self.class.new(expression, filename: @filename).each.to_a
842
+ inner.pop if inner.last == [0, nil]
843
+ tokens << [:tSTRING_DBEG, nil]
844
+ tokens.concat(inner)
845
+ tokens << [:tSTRING_DEND, nil]
846
+ else
847
+ content << value.chr
848
+ advance
849
+ end
850
+ end
851
+ fail!("unterminated literal", start)
852
+ end
853
+
854
+ def read_interpolation_source(start)
855
+ begin_index = @index
856
+ depth = 1
857
+ quote = nil
858
+ escaped = false
859
+ while !eof?
860
+ value = byte
861
+ if quote
862
+ if escaped
863
+ escaped = false
864
+ elsif value == 92
865
+ escaped = true
866
+ elsif value == quote
867
+ quote = nil
868
+ end
869
+ elsif [39, 34, 96].include?(value) && @source.getbyte(@index - 1) != 36
870
+ quote = value
871
+ elsif value == 123
872
+ depth += 1
873
+ elsif value == 125
874
+ depth -= 1
875
+ if depth.zero?
876
+ expression = @source.byteslice(begin_index, @index - begin_index)
877
+ advance
878
+ return expression.force_encoding(Encoding::UTF_8)
879
+ end
880
+ end
881
+ advance
882
+ end
883
+ fail!("unterminated string interpolation", start)
884
+ end
885
+
886
+ def read_quoted(quote, interpolate:, start:)
887
+ result = +""
888
+ until eof?
889
+ value = byte
890
+ if value == quote
891
+ advance
892
+ return result
893
+ elsif value == 92
894
+ result << escape_sequence(start)
895
+ elsif value == 35 && interpolate && byte(1) == 123
896
+ fail!("string interpolation cannot be represented by the AST", start)
897
+ else
898
+ result << value
899
+ advance
900
+ end
901
+ end
902
+ fail!("unterminated literal", start)
903
+ end
904
+
905
+ def escape_sequence(start)
906
+ advance
907
+ fail!("unterminated escape", start) if eof?
908
+ value = byte
909
+ advance
910
+ return "\n" if value == 110
911
+ return "\t" if value == 116
912
+ return "\r" if value == 114
913
+ return "\f" if value == 102
914
+ return "\a" if value == 97
915
+ return "\e" if value == 101
916
+ return value.chr if value == 92 || value == 34 || value == 39
917
+ value.chr
918
+ end
919
+
920
+ def regexp_or_operator(start)
921
+ return operator_or_punctuation(start) if [".", :tCOLON2, :tANDDOT, :keyword_def, :tSYMBEG].include?(@previous)
922
+ if @context.begin_expression
923
+ advance
924
+ value = read_regexp(start)
925
+ content = value.is_a?(Array) ? value : [[:tSTRING_CONTENT, value]]
926
+ @pending = content + [[:tREGEXP_END, nil]]
927
+ return [:tREGEXP_BEG, nil]
928
+ end
929
+ operator_or_punctuation(start)
930
+ end
931
+
932
+ def read_regexp(start)
933
+ result = +""
934
+ tokens = []
935
+ interpolated = false
936
+ in_class = false
937
+ until eof?
938
+ value = byte
939
+ if value == 92
940
+ result << value.chr
941
+ advance
942
+ fail!("unterminated regexp", start) if eof?
943
+ result << byte.chr
944
+ advance
945
+ elsif value == 91
946
+ in_class = true
947
+ result << value.chr
948
+ advance
949
+ elsif value == 93
950
+ in_class = false
951
+ result << value.chr
952
+ advance
953
+ elsif value == 35 && byte(1) == 123 && !in_class
954
+ tokens << [:tSTRING_CONTENT, result] unless result.empty?
955
+ result = +""
956
+ interpolated = true
957
+ advance(2)
958
+ expression = read_interpolation_source(start)
959
+ inner = self.class.new(expression, filename: @filename).each.to_a
960
+ inner.pop if inner.last == [0, nil]
961
+ tokens << [:tSTRING_DBEG, nil]
962
+ tokens.concat(inner)
963
+ tokens << [:tSTRING_DEND, nil]
964
+ elsif value == 47 && !in_class
965
+ advance
966
+ advance while byte && byte.between?(97, 122)
967
+ tokens << [:tSTRING_CONTENT, result] unless result.empty?
968
+ return interpolated ? tokens : result
969
+ else
970
+ result << value.chr
971
+ advance
972
+ end
973
+ end
974
+ fail!("unterminated regexp", start)
975
+ end
976
+
977
+ def percent_token(start)
978
+ return operator_or_punctuation(start) if [".", :tCOLON2, :tANDDOT].include?(@previous)
979
+ literal_kind = [113, 81, 119, 87, 105, 73, 114, 115, 120, 88].include?(byte(1))
980
+ command_argument = start.positive? && [9, 32].include?(@source.getbyte(start - 1)) &&
981
+ [:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous) &&
982
+ ![:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous_previous)
983
+ shorthand_delimiter = byte(1) && !identifier_byte?(byte(1)) &&
984
+ ![9, 10, 11, 12, 13, 32, 61].include?(byte(1))
985
+ return operator_or_punctuation(start) unless @context.begin_expression ||
986
+ ((literal_kind || shorthand_delimiter) && command_argument)
987
+ advance
988
+ kind = byte
989
+ if [113, 81, 119, 87, 105, 73, 114, 115, 120, 88].include?(kind)
990
+ advance
991
+ else
992
+ kind = 81
993
+ end
994
+ delimiter = byte
995
+ fail!("invalid percent literal", start) unless delimiter
996
+ advance
997
+ closing = { 40 => 41, 91 => 93, 123 => 125, 60 => 62 }.fetch(delimiter, delimiter)
998
+ interpolate = [81, 87, 73, 114, 120, 88].include?(kind)
999
+ content = interpolate ?
1000
+ read_interpolated_delimited(closing, start) :
1001
+ read_delimited(closing, start, interpolate: interpolate)
1002
+ advance while kind == 114 && byte && byte.between?(97, 122)
1003
+ token = { 113 => :tSTRING_BEG, 81 => :tSTRING_BEG, 119 => :tWORDS_BEG, 87 => :tQWORDS_BEG,
1004
+ 105 => :tSYMBOLS_BEG, 73 => :tQSYMBOLS_BEG, 114 => :tREGEXP_BEG,
1005
+ 115 => :tSYMBEG, 120 => :tXSTRING_BEG, 88 => :tXSTRING_BEG }.fetch(kind)
1006
+ terminator = kind == 114 ? :tREGEXP_END : :tSTRING_END
1007
+ @pending = if [119, 87, 105, 73].include?(kind)
1008
+ content = content.map { |token, value| value.to_s }.join if content.is_a?(Array)
1009
+ word_tokens(content, symbols: [105, 73].include?(kind)) + [[terminator, nil]]
1010
+ elsif content.is_a?(Array)
1011
+ content + [[terminator, nil]]
1012
+ else
1013
+ [[:tSTRING_CONTENT, content], [terminator, nil]]
1014
+ end
1015
+ [token, nil]
1016
+ end
1017
+
1018
+ def word_tokens(content, symbols:)
1019
+ words = content.split(/[\t\n\f\r ]+/).reject(&:empty?)
1020
+ tokens = [[" ", nil]]
1021
+ words.each do |word|
1022
+ tokens << [:tSTRING_CONTENT, symbols ? word.to_sym : word]
1023
+ tokens << [" ", nil]
1024
+ end
1025
+ tokens
1026
+ end
1027
+
1028
+ def read_delimited(closing, start, interpolate: false)
1029
+ result = +""
1030
+ depth = 0
1031
+ until eof?
1032
+ value = byte
1033
+ if value == 92
1034
+ result << escape_sequence(start)
1035
+ elsif value == 35 && interpolate && byte(1) == 123
1036
+ fail!("string interpolation cannot be represented by the AST", start)
1037
+ elsif value == closing && depth.zero?
1038
+ advance
1039
+ return result
1040
+ else
1041
+ depth += 1 if value == ({ 41 => 40, 93 => 91, 125 => 123, 62 => 60 }.fetch(closing, -1))
1042
+ depth -= 1 if value == closing && depth.positive?
1043
+ result << value.chr
1044
+ advance
1045
+ end
1046
+ end
1047
+ fail!("unterminated percent literal", start)
1048
+ end
1049
+
1050
+ def read_interpolated_delimited(closing, start)
1051
+ result = +""
1052
+ tokens = []
1053
+ depth = 0
1054
+ opening = { 41 => 40, 93 => 91, 125 => 123, 62 => 60 }.fetch(closing, -1)
1055
+ until eof?
1056
+ value = byte
1057
+ if value == 92
1058
+ result << escape_sequence(start)
1059
+ elsif value == 35 && byte(1) == 123
1060
+ tokens << [:tSTRING_CONTENT, result] unless result.empty?
1061
+ result = +""
1062
+ advance(2)
1063
+ expression = read_interpolation_source(start)
1064
+ inner = self.class.new(expression, filename: @filename).each.to_a
1065
+ inner.pop if inner.last == [0, nil]
1066
+ tokens << [:tSTRING_DBEG, nil]
1067
+ tokens.concat(inner)
1068
+ tokens << [:tSTRING_DEND, nil]
1069
+ elsif value == closing && depth.zero?
1070
+ advance
1071
+ tokens << [:tSTRING_CONTENT, result] unless result.empty?
1072
+ return tokens
1073
+ else
1074
+ depth += 1 if value == opening
1075
+ depth -= 1 if value == closing && depth.positive?
1076
+ result << value.chr
1077
+ advance
1078
+ end
1079
+ end
1080
+ fail!("unterminated percent literal", start)
1081
+ end
1082
+
1083
+ def interpolated_content_tokens(source, start)
1084
+ return [[:tSTRING_CONTENT, source]] unless source.include?("#" + "{")
1085
+ tokens = []
1086
+ literal = +""
1087
+ index = 0
1088
+ while index < source.bytesize
1089
+ if source.getbyte(index) == 35 && source.getbyte(index + 1) == 123
1090
+ tokens << [:tSTRING_CONTENT, literal] unless literal.empty?
1091
+ depth = 1
1092
+ expr_start = index + 2
1093
+ index = expr_start
1094
+ while index < source.bytesize && depth.positive?
1095
+ depth += 1 if source.getbyte(index) == 123
1096
+ depth -= 1 if source.getbyte(index) == 125
1097
+ index += 1
1098
+ end
1099
+ fail!("unterminated string interpolation", start) unless depth.zero?
1100
+ expression = source.byteslice(expr_start, index - expr_start - 1)
1101
+ inner = self.class.new(expression, filename: @filename).each.to_a
1102
+ inner.pop if inner.last == [0, nil]
1103
+ tokens << [:tSTRING_DBEG, nil]
1104
+ tokens.concat(inner)
1105
+ tokens << [:tSTRING_DEND, nil]
1106
+ literal = +""
1107
+ else
1108
+ literal << source.getbyte(index).chr
1109
+ index += 1
1110
+ end
1111
+ end
1112
+ tokens << [:tSTRING_CONTENT, literal] unless literal.empty?
1113
+ tokens
1114
+ end
1115
+
1116
+ def heredoc_or_operator(start)
1117
+ return operator_or_punctuation(start) if [".", :keyword_class].include?(@previous)
1118
+ if byte(1) == 60 && heredoc_prefix?
1119
+ heredoc_after_command = @previous == :tIDENTIFIER && start.positive? &&
1120
+ [9, 32].include?(@source.getbyte(start - 1))
1121
+ return heredoc_token(start) if @context.begin_expression || heredoc_after_command
1122
+ end
1123
+ operator_or_punctuation(start)
1124
+ end
1125
+
1126
+ def heredoc_prefix?
1127
+ value = byte(2)
1128
+ return false unless value
1129
+ if [45, 126].include?(value)
1130
+ value = byte(3)
1131
+ end
1132
+ if [39, 34, 96].include?(value)
1133
+ modifier = [45, 126].include?(@source.getbyte(@index + 2))
1134
+ value = byte(modifier ? 4 : 3)
1135
+ end
1136
+ identifier_byte?(value)
1137
+ end
1138
+
1139
+ def heredoc_token(start)
1140
+ advance(2)
1141
+ indent = byte == 45 || byte == 126
1142
+ squiggly = byte == 126
1143
+ advance if indent
1144
+ quote = byte
1145
+ if quote == 39 || quote == 34 || quote == 96
1146
+ advance
1147
+ delimiter_start = @index
1148
+ advance while byte && byte != quote
1149
+ fail!("unterminated heredoc identifier", start) if eof?
1150
+ delimiter = @source.byteslice(delimiter_start, @index - delimiter_start)
1151
+ advance
1152
+ else
1153
+ delimiter_start = @index
1154
+ advance while identifier_byte?(byte)
1155
+ delimiter = @source.byteslice(delimiter_start, @index - delimiter_start)
1156
+ end
1157
+ fail!("invalid heredoc identifier", start) if delimiter.empty?
1158
+ suffix_start = @index
1159
+ advance while byte && byte != 10
1160
+ suffix = @source.byteslice(suffix_start, @index - suffix_start).force_encoding(Encoding::UTF_8)
1161
+ advance if byte == 10
1162
+ interpolate = quote != 39
1163
+ body = read_heredoc_body(delimiter, indent, squiggly, start)
1164
+ content_tokens = interpolate ? interpolated_content_tokens(body, start) : [[:tSTRING_CONTENT, body]]
1165
+ @pending = content_tokens + [[:tSTRING_END, nil]]
1166
+ prefix, headers, suffix = extract_heredoc_headers(suffix)
1167
+ unless prefix.empty?
1168
+ prefix_tokens = heredoc_suffix_tokens(prefix)
1169
+ @pending.concat(prefix_tokens)
1170
+ end
1171
+ @heredoc_queue.concat(headers)
1172
+ @deferred_suffix = suffix unless suffix.empty?
1173
+ @heredoc_newline_pending = true
1174
+ [quote == 96 ? :tXSTRING_BEG : :tSTRING_BEG, nil]
1175
+ end
1176
+
1177
+ def read_heredoc_body(delimiter, indent, squiggly, start)
1178
+ body = +""
1179
+ loop do
1180
+ line_start = @index
1181
+ advance while !eof? && byte != 10
1182
+ line = @source.byteslice(line_start, @index - line_start)
1183
+ line_without_cr = line.delete_suffix("\r")
1184
+ terminator = indent ? line_without_cr.sub(/\A[ \t]*/, "") : line_without_cr
1185
+ if terminator == delimiter
1186
+ advance if byte == 10
1187
+ break
1188
+ end
1189
+ body << line << "\n"
1190
+ advance if byte == 10
1191
+ fail!("unterminated heredoc", start) if eof?
1192
+ end
1193
+ body = body.force_encoding(Encoding::UTF_8)
1194
+ squiggly ? dedent_heredoc(body) : body
1195
+ end
1196
+
1197
+ def extract_heredoc_headers(suffix)
1198
+ pattern = /<<(\-|~)?([A-Za-z_][A-Za-z0-9_]*)/
1199
+ match = pattern.match(suffix)
1200
+ return [suffix, [], ""] unless match
1201
+
1202
+ headers = [{delimiter: match[2], indent: !match[1].nil?, squiggly: match[1] == "~", quote: nil}]
1203
+ trailing = suffix[match.end(0)..].to_s.gsub(pattern) do
1204
+ headers << {delimiter: Regexp.last_match(2), indent: !Regexp.last_match(1).nil?, squiggly: Regexp.last_match(1) == "~", quote: nil}
1205
+ ""
1206
+ end
1207
+ [suffix[0...match.begin(0)], headers, trailing]
1208
+ end
1209
+
1210
+ def heredoc_suffix_tokens(source)
1211
+ suffix_lexer = self.class.new(source, filename: @filename)
1212
+ suffix_lexer.context.begin_expression = false
1213
+ suffix_lexer.context.lex_state = :expr_end
1214
+ suffix_lexer.instance_variable_set(:@previous, :tSTRING_END)
1215
+ tokens = suffix_lexer.each.to_a
1216
+ tokens.pop if tokens.last == [0, nil]
1217
+ if [:keyword_if, :keyword_unless, :keyword_while, :keyword_until, :keyword_rescue].include?(tokens.first&.first)
1218
+ token = {keyword_if: :modifier_if, keyword_unless: :modifier_unless,
1219
+ keyword_while: :modifier_while, keyword_until: :modifier_until,
1220
+ keyword_rescue: :modifier_rescue}.fetch(tokens.first.first)
1221
+ tokens[0] = [token, tokens.first.last]
1222
+ end
1223
+ tokens
1224
+ end
1225
+
1226
+ def load_queued_heredoc
1227
+ descriptor = @heredoc_queue.shift
1228
+ body = read_heredoc_body(descriptor.fetch(:delimiter), descriptor.fetch(:indent), descriptor.fetch(:squiggly), @index)
1229
+ content = descriptor.fetch(:quote) == 39 ? [[:tSTRING_CONTENT, body]] : interpolated_content_tokens(body, @index)
1230
+ @pending.concat([descriptor.fetch(:quote) == 96 ? [:tXSTRING_BEG, nil] : [:tSTRING_BEG, nil], *content, [:tSTRING_END, nil]])
1231
+ end
1232
+
1233
+ def dedent_heredoc(body)
1234
+ indents = body.lines.filter_map do |line|
1235
+ next if line.strip.empty?
1236
+ line[/\A[ \t]*/].bytesize
1237
+ end
1238
+ return body if indents.empty?
1239
+
1240
+ width = indents.min
1241
+ body.lines.map { |line| line.sub(/\A[ \t]{0,#{width}}/, "") }.join
1242
+ end
1243
+
1244
+ def operator_or_punctuation(start)
1245
+ if @previous == :keyword_def && @source.getbyte(@index) == 38
1246
+ advance
1247
+ return ["&", nil]
1248
+ end
1249
+ if @previous == :keyword_def && ["-@", "+@"].include?(@source.byteslice(@index, 2))
1250
+ value = @source.byteslice(@index, 2)
1251
+ advance(2)
1252
+ return [value == "-@" ? :tUMINUS : :tUPLUS, nil]
1253
+ end
1254
+ if (@previous == :tSYMBEG || @context.alias_context) && ["-@", "+@"].include?(@source.byteslice(@index, 2))
1255
+ value = @source.byteslice(@index, 2)
1256
+ advance(2)
1257
+ return [value == "-@" ? :tUMINUS : :tUPLUS, nil]
1258
+ end
1259
+ text = OPERATORS.sort_by { |operator| -operator.bytesize }.find do |operator|
1260
+ next false if ["[]", "[]="].include?(operator) && start.positive? &&
1261
+ [9, 10, 11, 12, 13, 32].include?(@source.getbyte(start - 1)) &&
1262
+ ![:keyword_def, :keyword_alias].include?(@previous) &&
1263
+ @previous_previous != :keyword_alias
1264
+ next false if ["[]", "[]="].include?(operator) &&
1265
+ [:tIDENTIFIER, :tCONSTANT, :tFID, :tGVAR, :tIVAR, :tCVAR, :tNTH_REF,
1266
+ :tINTEGER, :tSTRING_END, :tREGEXP_END, "]", "}"].include?(@previous) &&
1267
+ @previous_previous != :keyword_alias
1268
+ @source.byteslice(@index, operator.bytesize) == operator
1269
+ end
1270
+ operator_method = [:keyword_def, :keyword_alias, :keyword_undef, ".", :tCOLON2, :tANDDOT, :tSYMBEG].include?(@previous) ||
1271
+ @previous_previous == :keyword_alias
1272
+ if text && !(begin_expression? && !operator_method && ["[]", "[]="].include?(text))
1273
+ advance(text.bytesize)
1274
+ token = if text == "**" && @context.begin_expression
1275
+ :tDSTAR
1276
+ elsif text == ".." && @context.begin_expression
1277
+ :tBDOT2
1278
+ elsif text == "..." && (@context.begin_expression || [:tLPAREN, "(", ","].include?(@previous))
1279
+ :tBDOT3
1280
+ elsif text == "::" && (@context.begin_expression ||
1281
+ (start.positive? && [9, 32].include?(@source.getbyte(start - 1)) &&
1282
+ [:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous)))
1283
+ :tCOLON3
1284
+ elsif text == "&" && @previous == :keyword_def
1285
+ "&"
1286
+ else
1287
+ OP_TOKENS.fetch(text, :tOP_ASGN)
1288
+ end
1289
+ @context.lambda_pending = true if token == :tLAMBDA
1290
+ return [token, nil]
1291
+ end
1292
+ value = byte.chr
1293
+ advance
1294
+ if value == "(" || value == "[" || value == "{"
1295
+ brace_block = value == "{" && @previous != "(" &&
1296
+ ([:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous) ||
1297
+ (@previous == ")" && !(@previous_previous == :tSTAR && @context.scope_stack.last == :method)) ||
1298
+ [".", :tCOLON2].include?(@previous_previous) ||
1299
+ [:keyword_super, :keyword_yield].include?(@previous) ||
1300
+ [:proc, :lambda].include?(@previous_value) ||
1301
+ [:keyword_BEGIN, :keyword_END].include?(@previous))
1302
+ lambda_block = value == "{" && @context.lambda_pending
1303
+ command_arg = value == "(" && !@context.begin_expression && start.positive? && [9, 32].include?(@source.getbyte(start - 1)) &&
1304
+ [:tIDENTIFIER, :tCONSTANT, :tFID].include?(@previous) &&
1305
+ !@context.method_header
1306
+ unless brace_block || lambda_block
1307
+ @context.push_delimiter(value)
1308
+ @context.push_cmdarg(command_arg)
1309
+ end
1310
+ if value == "("
1311
+ if command_arg
1312
+ return [:tLPAREN_ARG, nil]
1313
+ end
1314
+ method_definition = @previous == :keyword_def || @previous_previous == :keyword_def
1315
+ return [@context.begin_expression && !method_definition &&
1316
+ ![".", :tCOLON2, :tANDDOT, :keyword_super, :keyword_yield, :tLAMBDA, :tAREF, :tASET].include?(@previous) ? :tLPAREN : "(", nil]
1317
+ end
1318
+ if value == "{" && @context.lambda_pending
1319
+ @context.lambda_pending = false
1320
+ return [:tLAMBEG, nil]
1321
+ end
1322
+ array_argument = value == "[" && start.positive? &&
1323
+ [9, 10, 11, 12, 13, 32].include?(@source.getbyte(start - 1)) &&
1324
+ [:tIDENTIFIER, :tFID, :tCONSTANT].include?(@previous)
1325
+ array_argument ||= value == "[" && @previous == ")" &&
1326
+ @previous_previous == "(" && @context.scope_stack.last == :method
1327
+ array_argument ||= value == "[" && @context.method_body_start
1328
+ array_argument = false if array_argument && @local_variables[@previous_value]
1329
+ array_argument = false if array_argument && [",", "*"].include?(@previous_previous) &&
1330
+ @context.delimiter_stack.any? { |delimiter| ["[", :tLBRACK].include?(delimiter) }
1331
+ return [value == "[" && (@context.begin_expression || array_argument) ? :tLBRACK : (value == "[" ? "[" : (brace_block ? "{" : :tLBRACE)), nil]
1332
+ elsif value == ")" || value == "]" || value == "}"
1333
+ opener = { ")" => "(", "]" => "[", "}" => "{" }.fetch(value)
1334
+ @context.pop_delimiter(opener)
1335
+ @context.pop_cmdarg
1336
+ if value == ")" && @context.method_header
1337
+ @context.method_body_start = true
1338
+ @context.method_header = false
1339
+ end
1340
+ elsif value == "-" && @context.begin_expression
1341
+ return [:tUMINUS, nil]
1342
+ elsif value == "+" && @context.begin_expression
1343
+ return [:tUPLUS, nil]
1344
+ elsif value == "*" && @context.begin_expression
1345
+ return [:tSTAR, nil]
1346
+ elsif value == "&" && @previous == :tSYMBEG
1347
+ return [value, nil]
1348
+ elsif value == "&" && @context.begin_expression
1349
+ return [:tAMPER, nil]
1350
+ elsif value == "?" && !@context.begin_expression
1351
+ @context.ternary_depth += 1
1352
+ elsif value == ":" && @context.ternary_depth.positive?
1353
+ @context.ternary_depth -= 1
1354
+ elsif value == "<" && @previous == :tCONSTANT && @previous_previous == :keyword_class
1355
+ @class_superclass = true
1356
+ end
1357
+ [value, nil]
1358
+ rescue EncodingError
1359
+ fail!("invalid byte", start)
1360
+ end
1361
+
1362
+ def begin_expression?
1363
+ @context.begin_expression
1364
+ end
1365
+
1366
+ def receiver_operator?(token)
1367
+ [".", :tCOLON2, :tANDDOT].include?(@previous_previous) &&
1368
+ ["/", :tLSHFT].include?(token)
1369
+ end
1370
+ end
1371
+ private_constant :Lexer, :LexicalContext