@willbooster/tree-sitter-ruby 0.0.0-stage → 1.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.
package/grammar.js ADDED
@@ -0,0 +1,1117 @@
1
+ /// <reference types="tree-sitter-cli/dsl" />
2
+ // @ts-check
3
+
4
+ const PREC = {
5
+ COMMENT: -2,
6
+ CURLY_BLOCK: 1,
7
+ DO_BLOCK: -1,
8
+
9
+ AND: -2,
10
+ OR: -2,
11
+ NOT: 5,
12
+ DEFINED: 10,
13
+ ALIAS: 11,
14
+ ASSIGN: 15,
15
+ RESCUE: 16,
16
+ CONDITIONAL: 20,
17
+ RANGE: 25,
18
+ BOOLEAN_OR: 30,
19
+ BOOLEAN_AND: 35,
20
+ RELATIONAL: 40,
21
+ COMPARISON: 45,
22
+ BITWISE_OR: 50,
23
+ BITWISE_AND: 55,
24
+ CALL: 56,
25
+ SHIFT: 60,
26
+ ADDITIVE: 65,
27
+ MULTIPLICATIVE: 70,
28
+ UNARY_MINUS: 75,
29
+ EXPONENTIAL: 80,
30
+ COMPLEMENT: 85,
31
+ };
32
+
33
+ // Regex sources reach src/grammar.json as written, so they keep upstream's spelling, and tree-sitter's regex parser
34
+ // rejects an unescaped `[` in a character class.
35
+ // oxlint-disable no-control-regex, no-useless-escape, unicorn/no-hex-escape -- See above.
36
+ const IDENTIFIER_CHARS = /[^\x00-\x1F\s:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]*/;
37
+ const LOWER_ALPHA_CHAR = /[^\x00-\x1F\sA-Z0-9:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]/;
38
+ const ALPHA_CHAR = /[^\x00-\x1F\s0-9:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]/;
39
+ // oxlint-enable no-control-regex, no-useless-escape, unicorn/no-hex-escape
40
+
41
+ // oxlint-disable-next-line unicorn/prefer-module -- This package is CommonJS, so tree-sitter loads grammar.js as CommonJS.
42
+ module.exports = grammar({
43
+ name: 'ruby',
44
+ inline: ($) => [$._arg_rhs, $._call_operator],
45
+ externals: ($) => [
46
+ $._line_break,
47
+ $._no_line_break,
48
+
49
+ // Delimited literals
50
+ $.simple_symbol,
51
+ $._string_start,
52
+ $._symbol_start,
53
+ $._subshell_start,
54
+ $._regex_start,
55
+ $._string_array_start,
56
+ $._symbol_array_start,
57
+ $._heredoc_body_start,
58
+ $.string_content,
59
+ $.heredoc_content,
60
+ $._string_end,
61
+ $.heredoc_end,
62
+ $.heredoc_beginning,
63
+
64
+ // Tokens that require lookahead
65
+ '/',
66
+ $._block_ampersand,
67
+ $._splat_star,
68
+ $._unary_minus,
69
+ $._unary_minus_num,
70
+ $._binary_minus,
71
+ $._binary_star,
72
+ $._singleton_class_left_angle_left_langle,
73
+ $.hash_key_symbol,
74
+ $._identifier_suffix,
75
+ $._constant_suffix,
76
+ $._hash_splat_star_star,
77
+ $._binary_star_star,
78
+ $._element_reference_bracket,
79
+ $._short_interpolation,
80
+ ],
81
+
82
+ extras: ($) => [$.comment, $.heredoc_body, /\s/, /\\\r?\n/],
83
+
84
+ word: ($) => $.identifier,
85
+
86
+ supertypes: ($) => [
87
+ $._statement,
88
+ $._arg,
89
+ $._method_name,
90
+ $._expression,
91
+ $._variable,
92
+ $._primary,
93
+ $._simple_numeric,
94
+ $._lhs,
95
+ $._nonlocal_variable,
96
+ $._pattern_top_expr_body,
97
+ $._pattern_expr,
98
+ $._pattern_expr_basic,
99
+ $._pattern_primitive,
100
+ $._pattern_constant,
101
+ ],
102
+
103
+ rules: {
104
+ program: ($) => seq(optional($._statements), optional(choice(seq(/__END__/, $.uninterpreted)))),
105
+
106
+ uninterpreted: () => /(.|\s)*/,
107
+
108
+ block_body: ($) => $._statements,
109
+
110
+ _statements: ($) =>
111
+ choice(
112
+ seq(repeat1(choice(seq($._statement, $._terminator), $.empty_statement)), optional($._statement)),
113
+ $._statement
114
+ ),
115
+
116
+ begin_block: ($) => seq('BEGIN', '{', optional($._statements), '}'),
117
+ end_block: ($) => seq('END', '{', optional($._statements), '}'),
118
+
119
+ _statement: ($) =>
120
+ choice(
121
+ $.undef,
122
+ $.alias,
123
+ $.if_modifier,
124
+ $.unless_modifier,
125
+ $.while_modifier,
126
+ $.until_modifier,
127
+ $.rescue_modifier,
128
+ $.begin_block,
129
+ $.end_block,
130
+ $._expression
131
+ ),
132
+
133
+ method: ($) => seq('def', $._method_rest),
134
+
135
+ singleton_method: ($) =>
136
+ seq(
137
+ 'def',
138
+ seq(choice(field('object', $._variable), seq('(', field('object', $._arg), ')')), choice('.', '::')),
139
+ $._method_rest
140
+ ),
141
+
142
+ _method_rest: ($) =>
143
+ seq(
144
+ field('name', $._method_name),
145
+ choice(
146
+ $._body_expr,
147
+ seq(
148
+ field('parameters', alias($.parameters, $.method_parameters)),
149
+ choice(seq(optional($._terminator), optional(field('body', $.body_statement)), 'end'), $._body_expr)
150
+ ),
151
+ seq(
152
+ optional(field('parameters', alias($.bare_parameters, $.method_parameters))),
153
+ $._terminator,
154
+ optional(field('body', $.body_statement)),
155
+ 'end'
156
+ )
157
+ )
158
+ ),
159
+
160
+ rescue_modifier_arg: ($) => prec(PREC.RESCUE, seq(field('body', $._arg), 'rescue', field('handler', $._arg))),
161
+
162
+ rescue_modifier_expression: ($) =>
163
+ prec(PREC.RESCUE, seq(field('body', $._expression), 'rescue', field('handler', $._arg))),
164
+
165
+ _body_expr: ($) => seq('=', field('body', choice($._arg, alias($.rescue_modifier_arg, $.rescue_modifier)))),
166
+
167
+ parameters: ($) => seq('(', commaSep($._formal_parameter), ')'),
168
+
169
+ bare_parameters: ($) => seq($._simple_formal_parameter, repeat(seq(',', $._formal_parameter))),
170
+
171
+ block_parameters: ($) =>
172
+ seq(
173
+ '|',
174
+ seq(commaSep($._formal_parameter), optional(',')),
175
+ optional(seq(';', sep1(field('locals', $.identifier), ','))), // Block shadow args e.g. {|; a, b| ...}
176
+ '|'
177
+ ),
178
+
179
+ _formal_parameter: ($) => choice($._simple_formal_parameter, alias($.parameters, $.destructured_parameter)),
180
+
181
+ _simple_formal_parameter: ($) =>
182
+ choice(
183
+ $.identifier,
184
+ $.splat_parameter,
185
+ $.hash_splat_parameter,
186
+ $.hash_splat_nil,
187
+ $.forward_parameter,
188
+ $.block_parameter,
189
+ $.keyword_parameter,
190
+ $.optional_parameter
191
+ ),
192
+
193
+ forward_parameter: () => '...',
194
+
195
+ splat_parameter: ($) => prec.right(-2, seq('*', field('name', optional($.identifier)))),
196
+ hash_splat_parameter: ($) => seq('**', field('name', optional($.identifier))),
197
+ hash_splat_nil: () => seq('**', 'nil'),
198
+ block_parameter: ($) => seq('&', field('name', optional($.identifier))),
199
+ keyword_parameter: ($) =>
200
+ prec.right(
201
+ PREC.BITWISE_OR + 1,
202
+ seq(field('name', $.identifier), token.immediate(':'), field('value', optional($._arg)))
203
+ ),
204
+ optional_parameter: ($) => prec(PREC.BITWISE_OR + 1, seq(field('name', $.identifier), '=', field('value', $._arg))),
205
+
206
+ class: ($) =>
207
+ seq(
208
+ 'class',
209
+ field('name', choice($.constant, $.scope_resolution)),
210
+ choice(seq(field('superclass', $.superclass), $._terminator), optional($._terminator)),
211
+ optional(field('body', $.body_statement)),
212
+ 'end'
213
+ ),
214
+
215
+ superclass: ($) => seq('<', $._expression),
216
+
217
+ singleton_class: ($) =>
218
+ seq(
219
+ 'class',
220
+ alias($._singleton_class_left_angle_left_langle, '<<'),
221
+ field('value', $._arg),
222
+ $._terminator,
223
+ optional(field('body', $.body_statement)),
224
+ 'end'
225
+ ),
226
+
227
+ module: ($) =>
228
+ seq(
229
+ 'module',
230
+ field('name', choice($.constant, $.scope_resolution)),
231
+ optional($._terminator),
232
+ optional(field('body', $.body_statement)),
233
+ 'end'
234
+ ),
235
+
236
+ return_command: ($) => prec.left(seq('return', alias($.command_argument_list, $.argument_list))),
237
+ yield_command: ($) => prec.left(seq('yield', alias($.command_argument_list, $.argument_list))),
238
+ break_command: ($) => prec.left(seq('break', alias($.command_argument_list, $.argument_list))),
239
+ next_command: ($) => prec.left(seq('next', alias($.command_argument_list, $.argument_list))),
240
+ return: ($) => prec.left(seq('return', optional($.argument_list))),
241
+ yield: ($) => prec.left(seq('yield', optional($.argument_list))),
242
+ break: ($) => prec.left(seq('break', optional($.argument_list))),
243
+ next: ($) => prec.left(seq('next', optional($.argument_list))),
244
+ redo: ($) => prec.left(seq('redo', optional($.argument_list))),
245
+ retry: ($) => prec.left(seq('retry', optional($.argument_list))),
246
+
247
+ if_modifier: ($) => prec(PREC.RESCUE, seq(field('body', $._statement), 'if', field('condition', $._expression))),
248
+
249
+ unless_modifier: ($) =>
250
+ prec(PREC.RESCUE, seq(field('body', $._statement), 'unless', field('condition', $._expression))),
251
+
252
+ while_modifier: ($) =>
253
+ prec(PREC.RESCUE, seq(field('body', $._statement), 'while', field('condition', $._expression))),
254
+
255
+ until_modifier: ($) =>
256
+ prec(PREC.RESCUE, seq(field('body', $._statement), 'until', field('condition', $._expression))),
257
+
258
+ rescue_modifier: ($) =>
259
+ prec(PREC.RESCUE, seq(field('body', $._statement), 'rescue', field('handler', $._expression))),
260
+
261
+ while: ($) => seq('while', field('condition', $._statement), field('body', $.do)),
262
+
263
+ until: ($) => seq('until', field('condition', $._statement), field('body', $.do)),
264
+
265
+ for: ($) =>
266
+ seq('for', field('pattern', choice($._lhs, $.left_assignment_list)), field('value', $.in), field('body', $.do)),
267
+
268
+ in: ($) => seq('in', $._arg),
269
+ do: ($) => seq(choice('do', $._terminator), optional($._statements), 'end'),
270
+
271
+ case: ($) =>
272
+ seq(
273
+ 'case',
274
+ optional(seq(optional($._line_break), field('value', $._statement))),
275
+ optional($._terminator),
276
+ repeat($.when),
277
+ optional($.else),
278
+ 'end'
279
+ ),
280
+
281
+ case_match: ($) =>
282
+ seq(
283
+ 'case',
284
+ seq(optional($._line_break), field('value', $._statement)),
285
+ optional($._terminator),
286
+ repeat1(field('clauses', $.in_clause)),
287
+ optional(field('else', $.else)),
288
+ 'end'
289
+ ),
290
+
291
+ when: ($) => seq('when', commaSep1(field('pattern', $.pattern)), choice($._terminator, field('body', $.then))),
292
+
293
+ in_clause: ($) =>
294
+ seq(
295
+ 'in',
296
+ field('pattern', $._pattern_top_expr_body),
297
+ field('guard', optional($._guard)),
298
+ choice($._terminator, field('body', $.then))
299
+ ),
300
+
301
+ pattern: ($) => choice($._arg, $.splat_argument),
302
+
303
+ _guard: ($) => choice($.if_guard, $.unless_guard),
304
+
305
+ if_guard: ($) => seq('if', field('condition', $._expression)),
306
+
307
+ unless_guard: ($) => seq('unless', field('condition', $._expression)),
308
+
309
+ _pattern_top_expr_body: ($) =>
310
+ prec(
311
+ -1,
312
+ choice(
313
+ $._pattern_expr,
314
+ alias($._array_pattern_n, $.array_pattern),
315
+ alias($._find_pattern_body, $.find_pattern),
316
+ alias($._hash_pattern_body, $.hash_pattern)
317
+ )
318
+ ),
319
+
320
+ _array_pattern_n: ($) =>
321
+ prec.right(
322
+ choice(
323
+ seq($._pattern_expr, alias(',', $.splat_parameter)),
324
+ seq($._pattern_expr, ',', choice($._pattern_expr, $._array_pattern_n)),
325
+ seq($.splat_parameter, repeat(seq(',', $._pattern_expr)))
326
+ )
327
+ ),
328
+
329
+ _pattern_expr: ($) => choice($.as_pattern, $._pattern_expr_alt),
330
+
331
+ as_pattern: ($) => seq(field('value', $._pattern_expr), '=>', field('name', $.identifier)),
332
+
333
+ _pattern_expr_alt: ($) => choice($.alternative_pattern, $._pattern_expr_basic),
334
+
335
+ alternative_pattern: ($) =>
336
+ seq(
337
+ field('alternatives', $._pattern_expr_basic),
338
+ repeat1(seq('|', field('alternatives', $._pattern_expr_basic)))
339
+ ),
340
+
341
+ _array_pattern_body: ($) => choice($._pattern_expr, $._array_pattern_n),
342
+
343
+ array_pattern: ($) =>
344
+ prec.right(
345
+ -1,
346
+ choice(
347
+ seq('[', optional($._array_pattern_body), ']'),
348
+ seq(field('class', $._pattern_constant), token.immediate('['), optional($._array_pattern_body), ']'),
349
+ seq(field('class', $._pattern_constant), token.immediate('('), optional($._array_pattern_body), ')')
350
+ )
351
+ ),
352
+
353
+ _find_pattern_body: ($) => seq($.splat_parameter, repeat1(seq(',', $._pattern_expr)), ',', $.splat_parameter),
354
+ find_pattern: ($) =>
355
+ choice(
356
+ seq('[', $._find_pattern_body, ']'),
357
+ seq(field('class', $._pattern_constant), token.immediate('['), $._find_pattern_body, ']'),
358
+ seq(field('class', $._pattern_constant), token.immediate('('), $._find_pattern_body, ')')
359
+ ),
360
+
361
+ _hash_pattern_body: ($) =>
362
+ prec.right(
363
+ choice(
364
+ seq(commaSep1($.keyword_pattern), optional(',')),
365
+ seq(commaSep1($.keyword_pattern), ',', $._hash_pattern_any_rest),
366
+ $._hash_pattern_any_rest
367
+ )
368
+ ),
369
+
370
+ keyword_pattern: ($) =>
371
+ prec.right(
372
+ -1,
373
+ seq(
374
+ field(
375
+ 'key',
376
+ choice(
377
+ alias($.identifier, $.hash_key_symbol),
378
+ alias($.constant, $.hash_key_symbol),
379
+ alias($.identifier_suffix, $.hash_key_symbol),
380
+ alias($.constant_suffix, $.hash_key_symbol),
381
+ $.string
382
+ )
383
+ ),
384
+ token.immediate(':'),
385
+ optional(field('value', $._pattern_expr))
386
+ )
387
+ ),
388
+
389
+ _hash_pattern_any_rest: ($) => choice($.hash_splat_parameter, $.hash_splat_nil),
390
+
391
+ hash_pattern: ($) =>
392
+ prec.right(
393
+ -1,
394
+ choice(
395
+ seq('{', optional($._hash_pattern_body), '}'),
396
+ seq(field('class', $._pattern_constant), token.immediate('['), $._hash_pattern_body, ']'),
397
+ seq(field('class', $._pattern_constant), token.immediate('('), $._hash_pattern_body, ')')
398
+ )
399
+ ),
400
+
401
+ _pattern_expr_basic: ($) =>
402
+ prec.right(
403
+ -1,
404
+ choice($._pattern_value, $.identifier, $.array_pattern, $.find_pattern, $.hash_pattern, $.parenthesized_pattern)
405
+ ),
406
+
407
+ parenthesized_pattern: ($) => seq('(', $._pattern_expr, ')'),
408
+
409
+ _pattern_value: ($) =>
410
+ prec.right(
411
+ -1,
412
+ choice(
413
+ $._pattern_primitive,
414
+ alias($._pattern_range, $.range),
415
+ $.variable_reference_pattern,
416
+ $.expression_reference_pattern,
417
+ $._pattern_constant
418
+ )
419
+ ),
420
+
421
+ _pattern_range: ($) => {
422
+ const begin = field('begin', $._pattern_primitive);
423
+ const end = field('end', $._pattern_primitive);
424
+ const operator = field('operator', choice('..', '...'));
425
+ return choice(seq(begin, operator, end), seq(operator, end), seq(begin, operator));
426
+ },
427
+
428
+ _pattern_primitive: ($) => choice($._pattern_literal, $._pattern_lambda),
429
+
430
+ _pattern_lambda: ($) => prec.right(-1, $.lambda),
431
+
432
+ _pattern_literal: ($) =>
433
+ prec.right(
434
+ -1,
435
+ choice(
436
+ $._literal,
437
+ $.string,
438
+ $.subshell,
439
+ $.heredoc_beginning,
440
+ $.regex,
441
+ $.string_array,
442
+ $.symbol_array,
443
+ $._keyword_variable
444
+ )
445
+ ),
446
+
447
+ _keyword_variable: ($) => prec.right(-1, choice($.nil, $.self, $.true, $.false, $.line, $.file, $.encoding)),
448
+
449
+ line: () => '__LINE__',
450
+ file: () => '__FILE__',
451
+ encoding: () => '__ENCODING__',
452
+
453
+ variable_reference_pattern: ($) => seq('^', field('name', choice($.identifier, $._nonlocal_variable))),
454
+
455
+ expression_reference_pattern: ($) => seq('^', '(', field('value', $._expression), ')'),
456
+
457
+ _pattern_constant: ($) =>
458
+ prec.right(-1, choice($.constant, alias($._pattern_constant_resolution, $.scope_resolution))),
459
+
460
+ _pattern_constant_resolution: ($) =>
461
+ seq(optional(field('scope', $._pattern_constant)), '::', field('name', $.constant)),
462
+
463
+ if: ($) =>
464
+ seq(
465
+ 'if',
466
+ field('condition', $._statement),
467
+ choice($._terminator, field('consequence', $.then)),
468
+ field('alternative', optional(choice($.else, $.elsif))),
469
+ 'end'
470
+ ),
471
+
472
+ unless: ($) =>
473
+ seq(
474
+ 'unless',
475
+ field('condition', $._statement),
476
+ choice($._terminator, field('consequence', $.then)),
477
+ field('alternative', optional(choice($.else, $.elsif))),
478
+ 'end'
479
+ ),
480
+
481
+ elsif: ($) =>
482
+ seq(
483
+ 'elsif',
484
+ field('condition', $._statement),
485
+ choice($._terminator, field('consequence', $.then)),
486
+ field('alternative', optional(choice($.else, $.elsif)))
487
+ ),
488
+
489
+ else: ($) => seq('else', optional($._terminator), optional($._statements)),
490
+
491
+ // oxlint-disable-next-line unicorn/no-thenable -- `then` is a grammar rule, whose name is the node type that trees and queries expose.
492
+ then: ($) =>
493
+ choice(seq($._terminator, $._statements), seq(optional($._terminator), 'then', optional($._statements))),
494
+
495
+ begin: ($) => seq('begin', optional($._terminator), optional($._body_statement), 'end'),
496
+
497
+ ensure: ($) => seq('ensure', optional($._statements)),
498
+
499
+ rescue: ($) =>
500
+ seq(
501
+ 'rescue',
502
+ field('exceptions', optional($.exceptions)),
503
+ field('variable', optional($.exception_variable)),
504
+ choice($._terminator, field('body', $.then))
505
+ ),
506
+
507
+ exceptions: ($) => commaSep1(choice($._arg, $.splat_argument)),
508
+
509
+ exception_variable: ($) => seq('=>', $._lhs),
510
+
511
+ body_statement: ($) => $._body_statement,
512
+
513
+ _body_statement: ($) =>
514
+ choice(
515
+ seq($._statements, repeat(choice($.rescue, $.else, $.ensure))),
516
+ seq(optional($._statements), repeat1(choice($.rescue, $.else, $.ensure)))
517
+ ),
518
+
519
+ // Method calls without parentheses (aka "command calls") are only allowed
520
+ // in certain positions, like the top-level of a statement, the condition
521
+ // of a postfix control-flow operator like `if`, or as the value of a
522
+ // control-flow statement like `return`. In many other places, they're not
523
+ // allowed.
524
+ //
525
+ // Because of this distinction, a lot of rules have two variants: the
526
+ // normal variant, which can appear anywhere that an expression is valid,
527
+ // and the "command" varaint, which is only valid in a more limited set of
528
+ // positions, because it can contain "command calls".
529
+ //
530
+ // The `_expression` rule can appear in relatively few places, but can
531
+ // contain command calls. The `_arg` rule can appear in many more places,
532
+ // but cannot contain command calls (unless they are wrapped in parens).
533
+ // This naming convention is based on Ruby's standard grammar.
534
+ _expression: ($) =>
535
+ choice(
536
+ alias($.command_binary, $.binary),
537
+ alias($.command_unary, $.unary),
538
+ alias($.command_assignment, $.assignment),
539
+ alias($.command_operator_assignment, $.operator_assignment),
540
+ alias($.command_call, $.call),
541
+ alias($.command_call_with_block, $.call),
542
+ prec.left(alias($._chained_command_call, $.call)),
543
+ alias($.return_command, $.return),
544
+ alias($.yield_command, $.yield),
545
+ alias($.break_command, $.break),
546
+ alias($.next_command, $.next),
547
+ $.match_pattern,
548
+ $.test_pattern,
549
+ $._arg
550
+ ),
551
+
552
+ match_pattern: ($) => prec(100, seq(field('value', $._arg), '=>', field('pattern', $._pattern_top_expr_body))),
553
+
554
+ test_pattern: ($) => prec(100, seq(field('value', $._arg), 'in', field('pattern', $._pattern_top_expr_body))),
555
+
556
+ _arg: ($) =>
557
+ choice(
558
+ alias($._unary_minus_pow, $.unary),
559
+ $._primary,
560
+ $.assignment,
561
+ $.operator_assignment,
562
+ $.conditional,
563
+ $.range,
564
+ $.binary,
565
+ $.unary
566
+ ),
567
+
568
+ _unary_minus_pow: ($) =>
569
+ seq(field('operator', alias($._unary_minus_num, '-')), field('operand', alias($._pow, $.binary))),
570
+ _pow: ($) =>
571
+ prec.right(
572
+ PREC.EXPONENTIAL,
573
+ seq(
574
+ field('left', $._simple_numeric),
575
+ field('operator', alias($._binary_star_star, '**')),
576
+ field('right', $._arg)
577
+ )
578
+ ),
579
+
580
+ _primary: ($) =>
581
+ choice(
582
+ $.parenthesized_statements,
583
+ $._lhs,
584
+ alias($._function_identifier_call, $.call),
585
+ $.call,
586
+ $.array,
587
+ $.string_array,
588
+ $.symbol_array,
589
+ $.hash,
590
+ $.subshell,
591
+ $._literal,
592
+ $.string,
593
+ $.character,
594
+ $.chained_string,
595
+ $.regex,
596
+ $.lambda,
597
+ $.method,
598
+ $.singleton_method,
599
+ $.class,
600
+ $.singleton_class,
601
+ $.module,
602
+ $.begin,
603
+ $.while,
604
+ $.until,
605
+ $.if,
606
+ $.unless,
607
+ $.for,
608
+ $.case,
609
+ $.case_match,
610
+ $.return,
611
+ $.yield,
612
+ $.break,
613
+ $.next,
614
+ $.redo,
615
+ $.retry,
616
+ alias($.parenthesized_unary, $.unary),
617
+ $.heredoc_beginning
618
+ ),
619
+
620
+ parenthesized_statements: ($) => seq('(', optional($._statements), ')'),
621
+
622
+ element_reference: ($) =>
623
+ prec.left(
624
+ 1,
625
+ seq(
626
+ field('object', $._primary),
627
+ alias($._element_reference_bracket, '['),
628
+ optional($._argument_list_with_trailing_comma),
629
+ ']',
630
+ optional(field('block', choice($.block, $.do_block)))
631
+ )
632
+ ),
633
+
634
+ scope_resolution: ($) =>
635
+ prec.left(
636
+ PREC.CALL + 1,
637
+ seq(choice('::', seq(field('scope', $._primary), token.immediate('::'))), field('name', $.constant))
638
+ ),
639
+
640
+ _call_operator: () => choice('.', '&.', token.immediate('::')),
641
+ _call: ($) =>
642
+ prec.left(
643
+ PREC.CALL,
644
+ seq(
645
+ field('receiver', $._primary),
646
+ field('operator', $._call_operator),
647
+ field('method', choice($.identifier, $.operator, $.constant, $._function_identifier))
648
+ )
649
+ ),
650
+
651
+ command_call: ($) =>
652
+ seq(
653
+ choice($._call, $._chained_command_call, field('method', choice($._variable, $._function_identifier))),
654
+ field('arguments', alias($.command_argument_list, $.argument_list))
655
+ ),
656
+
657
+ command_call_with_block: ($) => {
658
+ const receiver = choice($._call, field('method', choice($._variable, $._function_identifier)));
659
+ const args = field('arguments', alias($.command_argument_list, $.argument_list));
660
+ const block = field('block', $.block);
661
+ const doBlock = field('block', $.do_block);
662
+ return choice(
663
+ seq(receiver, prec(PREC.CURLY_BLOCK, seq(args, block))),
664
+ seq(receiver, prec(PREC.DO_BLOCK, seq(args, doBlock)))
665
+ );
666
+ },
667
+
668
+ _chained_command_call: ($) =>
669
+ seq(
670
+ field('receiver', alias($.command_call_with_block, $.call)),
671
+ field('operator', $._call_operator),
672
+ field('method', choice($.identifier, $._function_identifier, $.operator, $.constant))
673
+ ),
674
+
675
+ call: ($) => {
676
+ const receiver = choice($._call, field('method', choice($._variable, $._function_identifier)));
677
+
678
+ const args = field('arguments', $.argument_list);
679
+ const receiverArguments = seq(
680
+ choice(receiver, prec.left(PREC.CALL, seq(field('receiver', $._primary), field('operator', $._call_operator)))),
681
+ args
682
+ );
683
+
684
+ const block = field('block', $.block);
685
+ const doBlock = field('block', $.do_block);
686
+ return choice(
687
+ receiverArguments,
688
+ prec(PREC.CURLY_BLOCK, seq(receiverArguments, block)),
689
+ prec(PREC.DO_BLOCK, seq(receiverArguments, doBlock)),
690
+ prec(PREC.CURLY_BLOCK, seq(receiver, block)),
691
+ prec(PREC.DO_BLOCK, seq(receiver, doBlock))
692
+ );
693
+ },
694
+
695
+ command_argument_list: ($) => prec.right(commaSep1($._argument)),
696
+
697
+ argument_list: ($) => prec.right(seq(token.immediate('('), optional($._argument_list_with_trailing_comma), ')')),
698
+
699
+ _argument_list_with_trailing_comma: ($) => prec.right(seq(commaSep1($._argument), optional(','))),
700
+
701
+ _argument: ($) =>
702
+ prec.left(
703
+ choice($._expression, $.splat_argument, $.hash_splat_argument, $.forward_argument, $.block_argument, $.pair)
704
+ ),
705
+
706
+ forward_argument: () => '...',
707
+ splat_argument: ($) => prec.right(seq(alias($._splat_star, '*'), optional($._arg))),
708
+ hash_splat_argument: ($) => prec.right(seq(alias($._hash_splat_star_star, '**'), optional($._arg))),
709
+ block_argument: ($) => prec.right(seq(alias($._block_ampersand, '&'), optional($._arg))),
710
+
711
+ do_block: ($) =>
712
+ seq(
713
+ 'do',
714
+ optional($._terminator),
715
+ optional(seq(field('parameters', $.block_parameters), optional($._terminator))),
716
+ optional(field('body', $.body_statement)),
717
+ 'end'
718
+ ),
719
+
720
+ block: ($) =>
721
+ prec(
722
+ PREC.CURLY_BLOCK,
723
+ seq('{', field('parameters', optional($.block_parameters)), optional(field('body', $.block_body)), '}')
724
+ ),
725
+
726
+ _arg_rhs: ($) => choice($._arg, alias($.rescue_modifier_arg, $.rescue_modifier)),
727
+ assignment: ($) =>
728
+ prec.right(
729
+ PREC.ASSIGN,
730
+ choice(
731
+ seq(
732
+ field('left', choice($._lhs, $.left_assignment_list)),
733
+ '=',
734
+ field('right', choice($._arg_rhs, $.splat_argument, $.right_assignment_list))
735
+ )
736
+ )
737
+ ),
738
+
739
+ command_assignment: ($) =>
740
+ prec.right(
741
+ PREC.ASSIGN,
742
+ seq(
743
+ field('left', choice($._lhs, $.left_assignment_list)),
744
+ '=',
745
+ field('right', choice($._expression, alias($.rescue_modifier_expression, $.rescue_modifier)))
746
+ )
747
+ ),
748
+
749
+ operator_assignment: ($) =>
750
+ prec.right(
751
+ PREC.ASSIGN,
752
+ seq(
753
+ field('left', $._lhs),
754
+ field('operator', choice('+=', '-=', '*=', '**=', '/=', '||=', '|=', '&&=', '&=', '%=', '>>=', '<<=', '^=')),
755
+ field('right', $._arg_rhs)
756
+ )
757
+ ),
758
+
759
+ command_operator_assignment: ($) =>
760
+ prec.right(
761
+ PREC.ASSIGN,
762
+ seq(
763
+ field('left', $._lhs),
764
+ field('operator', choice('+=', '-=', '*=', '**=', '/=', '||=', '|=', '&&=', '&=', '%=', '>>=', '<<=', '^=')),
765
+ field('right', choice($._expression, alias($.rescue_modifier_expression, $.rescue_modifier)))
766
+ )
767
+ ),
768
+
769
+ conditional: ($) =>
770
+ prec.right(
771
+ PREC.CONDITIONAL,
772
+ seq(field('condition', $._arg), '?', field('consequence', $._arg), ':', field('alternative', $._arg))
773
+ ),
774
+
775
+ range: ($) => {
776
+ const begin = field('begin', $._arg);
777
+ const end = field('end', $._arg);
778
+ const operator = field('operator', choice('..', '...'));
779
+ return prec.right(PREC.RANGE, choice(seq(begin, operator, end), seq(operator, end), seq(begin, operator)));
780
+ },
781
+
782
+ binary: ($) => {
783
+ // oxlint-disable typescript/unbound-method -- The DSL's `prec` functions do not use `this`.
784
+ /** @type {[(precedence: number, rule: RuleOrLiteral) => Rule, number, RuleOrLiteral][]} */
785
+ const operators = [
786
+ [prec.left, PREC.AND, 'and'],
787
+ [prec.left, PREC.OR, 'or'],
788
+ [prec.left, PREC.BOOLEAN_OR, '||'],
789
+ [prec.left, PREC.BOOLEAN_AND, '&&'],
790
+ [prec.left, PREC.SHIFT, choice('<<', '>>')],
791
+ [prec.left, PREC.COMPARISON, choice('<', '<=', '>', '>=')],
792
+ [prec.left, PREC.BITWISE_AND, '&'],
793
+ [prec.left, PREC.BITWISE_OR, choice('^', '|')],
794
+ [prec.left, PREC.ADDITIVE, choice('+', alias($._binary_minus, '-'))],
795
+ [prec.left, PREC.MULTIPLICATIVE, choice('/', '%', alias($._binary_star, '*'))],
796
+ [prec.right, PREC.RELATIONAL, choice('==', '!=', '===', '<=>', '=~', '!~')],
797
+ [prec.right, PREC.EXPONENTIAL, alias($._binary_star_star, '**')],
798
+ ];
799
+ // oxlint-enable typescript/unbound-method
800
+
801
+ return choice(
802
+ ...operators.map(([fn, precedence, operator]) =>
803
+ fn(precedence, seq(field('left', $._arg), field('operator', operator), field('right', $._arg)))
804
+ )
805
+ );
806
+ },
807
+
808
+ command_binary: ($) =>
809
+ prec.left(
810
+ seq(field('left', $._expression), field('operator', choice('or', 'and')), field('right', $._expression))
811
+ ),
812
+
813
+ unary: ($) => {
814
+ // oxlint-disable typescript/unbound-method -- The DSL's `prec` functions do not use `this`.
815
+ /** @type {[(precedence: number, rule: RuleOrLiteral) => Rule, number, RuleOrLiteral][]} */
816
+ const operators = [
817
+ [prec, PREC.DEFINED, 'defined?'],
818
+ [prec.right, PREC.NOT, 'not'],
819
+ [prec.right, PREC.UNARY_MINUS, choice(alias($._unary_minus, '-'), alias($._binary_minus, '-'), '+')],
820
+ [prec.right, PREC.COMPLEMENT, choice('!', '~')],
821
+ ];
822
+ // oxlint-enable typescript/unbound-method
823
+ return choice(
824
+ ...operators.map(([fn, precedence, operator]) =>
825
+ fn(precedence, seq(field('operator', operator), field('operand', $._arg)))
826
+ )
827
+ );
828
+ },
829
+
830
+ command_unary: ($) => {
831
+ // oxlint-disable typescript/unbound-method -- The DSL's `prec` functions do not use `this`.
832
+ /** @type {[(precedence: number, rule: RuleOrLiteral) => Rule, number, RuleOrLiteral][]} */
833
+ const operators = [
834
+ [prec, PREC.DEFINED, 'defined?'],
835
+ [prec.right, PREC.NOT, 'not'],
836
+ [prec.right, PREC.UNARY_MINUS, choice(alias($._unary_minus, '-'), '+')],
837
+ [prec.right, PREC.COMPLEMENT, choice('!', '~')],
838
+ ];
839
+ // oxlint-enable typescript/unbound-method
840
+ return choice(
841
+ ...operators.map(([fn, precedence, operator]) =>
842
+ fn(precedence, seq(field('operator', operator), field('operand', $._expression)))
843
+ )
844
+ );
845
+ },
846
+
847
+ parenthesized_unary: ($) =>
848
+ prec(PREC.CALL, seq(field('operator', choice('defined?', 'not')), field('operand', $.parenthesized_statements))),
849
+
850
+ unary_literal: ($) =>
851
+ prec.right(
852
+ PREC.UNARY_MINUS,
853
+ seq(field('operator', choice(alias($._unary_minus_num, '-'), '+')), field('operand', $._simple_numeric))
854
+ ),
855
+
856
+ _literal: ($) => choice($.simple_symbol, $.delimited_symbol, $._numeric),
857
+
858
+ _numeric: ($) => choice($._simple_numeric, alias($.unary_literal, $.unary)),
859
+
860
+ _simple_numeric: ($) => choice($.integer, $.float, $.complex, $.rational),
861
+
862
+ right_assignment_list: ($) => prec(-1, commaSep1(choice($._arg, $.splat_argument))),
863
+
864
+ left_assignment_list: ($) => $._mlhs,
865
+ _mlhs: ($) =>
866
+ prec.left(-1, seq(commaSep1(choice($._lhs, $.rest_assignment, $.destructured_left_assignment)), optional(','))),
867
+ destructured_left_assignment: ($) => prec(-1, seq('(', $._mlhs, ')')),
868
+
869
+ rest_assignment: ($) => prec(-1, seq('*', optional($._lhs))),
870
+
871
+ _function_identifier: ($) => choice(alias($.identifier_suffix, $.identifier), alias($.constant_suffix, $.constant)),
872
+ _function_identifier_call: ($) => prec.left(field('method', $._function_identifier)),
873
+ _lhs: ($) =>
874
+ prec.left(
875
+ choice($._variable, $.true, $.false, $.nil, $.scope_resolution, $.element_reference, alias($._call, $.call))
876
+ ),
877
+
878
+ _variable: ($) => prec.right(choice($.self, $.super, $._nonlocal_variable, $.identifier, $.constant)),
879
+
880
+ operator: () =>
881
+ choice(
882
+ '..',
883
+ '|',
884
+ '^',
885
+ '&',
886
+ '<=>',
887
+ '==',
888
+ '===',
889
+ '=~',
890
+ '>',
891
+ '>=',
892
+ '<',
893
+ '<=',
894
+ '+',
895
+ '!=',
896
+ '-',
897
+ '*',
898
+ '/',
899
+ '%',
900
+ '!',
901
+ '!~',
902
+ '**',
903
+ '<<',
904
+ '>>',
905
+ '~',
906
+ '+@',
907
+ '-@',
908
+ '~@',
909
+ '[]',
910
+ '[]=',
911
+ '`'
912
+ ),
913
+
914
+ _method_name: ($) =>
915
+ choice(
916
+ $.identifier,
917
+ $._function_identifier,
918
+ $.constant,
919
+ $.setter,
920
+ $.simple_symbol,
921
+ $.delimited_symbol,
922
+ $.operator,
923
+ $._nonlocal_variable
924
+ ),
925
+
926
+ _nonlocal_variable: ($) => choice($.instance_variable, $.class_variable, $.global_variable),
927
+
928
+ setter: ($) => seq(field('name', $.identifier), token.immediate('=')),
929
+
930
+ undef: ($) => seq('undef', commaSep1($._method_name)),
931
+ alias: ($) => seq('alias', field('name', $._method_name), field('alias', $._method_name)),
932
+
933
+ comment: () =>
934
+ token(
935
+ prec(
936
+ PREC.COMMENT,
937
+ choice(
938
+ seq('#', /.*/),
939
+ seq(/=begin.*\r?\n/, repeat(choice(/[^=]/, /=[^e]/, /=e[^n]/, /=en[^d]/)), /[\s*]*=end.*/)
940
+ )
941
+ )
942
+ ),
943
+
944
+ integer: () => /0[bB][01](_?[01])*|0[oO]?[0-7](_?[0-7])*|(0[dD])?\d(_?\d)*|0[xX][0-9a-fA-F](_?[0-9a-fA-F])*/,
945
+ _int_or_float: ($) => choice($.integer, $.float),
946
+ // oxlint-disable-next-line no-useless-escape -- The regex source reaches src/grammar.json as written, so it keeps upstream's spelling.
947
+ float: () => /\d(_?\d)*(\.\d)?(_?\d)*([eE][\+-]?\d(_?\d)*)?/,
948
+ complex: ($) =>
949
+ choice(
950
+ seq($._int_or_float, token.immediate('i')),
951
+ seq(alias($._int_or_float, $.rational), token.immediate('ri'))
952
+ ),
953
+ rational: ($) => seq($._int_or_float, token.immediate('r')),
954
+ super: () => 'super',
955
+ self: () => 'self',
956
+ true: () => 'true',
957
+ false: () => 'false',
958
+ nil: () => 'nil',
959
+
960
+ constant: () => token(seq(/[A-Z]/, IDENTIFIER_CHARS)),
961
+ constant_suffix: ($) => choice(token(seq(/[A-Z]/, IDENTIFIER_CHARS, /[?]/)), $._constant_suffix),
962
+ identifier: () => token(seq(LOWER_ALPHA_CHAR, IDENTIFIER_CHARS)),
963
+ identifier_suffix: ($) => choice(token(seq(LOWER_ALPHA_CHAR, IDENTIFIER_CHARS, /[?]/)), $._identifier_suffix),
964
+ instance_variable: () => token(seq('@', ALPHA_CHAR, IDENTIFIER_CHARS)),
965
+ class_variable: () => token(seq('@@', ALPHA_CHAR, IDENTIFIER_CHARS)),
966
+
967
+ global_variable: () => /\$(-[a-zA-Z0-9_]|[!@&`'+~=/\\,;.<>*$?:"]|[0-9]+|[a-zA-Z_][a-zA-Z0-9_]*)/,
968
+
969
+ chained_string: ($) => seq($.string, repeat1($.string)),
970
+
971
+ character: () => /\?(\\\S(\{[0-9A-Fa-f]*\}|[0-9A-Fa-f]*|-\S([MC]-\S)?)?|\S)/,
972
+
973
+ interpolation: ($) =>
974
+ choice(seq('#{', optional($._statements), '}'), seq($._short_interpolation, $._nonlocal_variable)),
975
+
976
+ string: ($) => seq(alias($._string_start, '"'), optional($._literal_contents), alias($._string_end, '"')),
977
+
978
+ subshell: ($) => seq(alias($._subshell_start, '`'), optional($._literal_contents), alias($._string_end, '`')),
979
+
980
+ string_array: ($) =>
981
+ seq(
982
+ alias($._string_array_start, '%w('),
983
+ optional(/\s+/),
984
+ sep(alias($._literal_contents, $.bare_string), /\s+/),
985
+ optional(/\s+/),
986
+ alias($._string_end, ')')
987
+ ),
988
+
989
+ symbol_array: ($) =>
990
+ seq(
991
+ alias($._symbol_array_start, '%i('),
992
+ optional(/\s+/),
993
+ sep(alias($._literal_contents, $.bare_symbol), /\s+/),
994
+ optional(/\s+/),
995
+ alias($._string_end, ')')
996
+ ),
997
+
998
+ delimited_symbol: ($) =>
999
+ seq(alias($._symbol_start, ':"'), optional($._literal_contents), alias($._string_end, '"')),
1000
+
1001
+ regex: ($) => seq(alias($._regex_start, '/'), optional($._literal_contents), alias($._string_end, '/')),
1002
+
1003
+ heredoc_body: ($) =>
1004
+ seq($._heredoc_body_start, repeat(choice($.heredoc_content, $.interpolation, $.escape_sequence)), $.heredoc_end),
1005
+
1006
+ _literal_contents: ($) => repeat1(choice($.string_content, $.interpolation, $.escape_sequence)),
1007
+
1008
+ // https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Strings
1009
+ escape_sequence: () =>
1010
+ token(
1011
+ seq(
1012
+ '\\',
1013
+ choice(
1014
+ /[^ux0-7]/, // single character
1015
+ /x[0-9a-fA-F]{1,2}/, // hex code
1016
+ /[0-7]{1,3}/, // octal
1017
+ /u[0-9a-fA-F]{4}/, // single unicode
1018
+ /u\{[0-9a-fA-F ]+\}/ // multiple unicode
1019
+ )
1020
+ )
1021
+ ),
1022
+
1023
+ array: ($) => seq('[', optional($._argument_list_with_trailing_comma), ']'),
1024
+
1025
+ hash: ($) => seq('{', optional(seq(commaSep1(choice($.pair, $.hash_splat_argument)), optional(','))), '}'),
1026
+
1027
+ pair: ($) =>
1028
+ prec.right(
1029
+ choice(
1030
+ seq(field('key', $._arg), '=>', field('value', $._arg)),
1031
+ seq(field('key', choice($.string)), token.immediate(':'), field('value', $._arg)),
1032
+ seq(
1033
+ field(
1034
+ 'key',
1035
+ choice(
1036
+ $.hash_key_symbol,
1037
+ alias($.identifier, $.hash_key_symbol),
1038
+ alias($.constant, $.hash_key_symbol),
1039
+ alias($.identifier_suffix, $.hash_key_symbol),
1040
+ alias($.constant_suffix, $.hash_key_symbol)
1041
+ )
1042
+ ),
1043
+ token.immediate(':'),
1044
+ choice(
1045
+ field('value', optional($._arg)),
1046
+ // This alternative never matches, because '_no_line_break' tokens do not exist.
1047
+ // The purpose is give a hint to the scanner that it should not produce any line-break
1048
+ // terminators at this point.
1049
+ $._no_line_break
1050
+ )
1051
+ )
1052
+ )
1053
+ ),
1054
+
1055
+ lambda: ($) =>
1056
+ seq(
1057
+ '->',
1058
+ field(
1059
+ 'parameters',
1060
+ optional(choice(alias($.parameters, $.lambda_parameters), alias($.bare_parameters, $.lambda_parameters)))
1061
+ ),
1062
+ field('body', choice($.block, $.do_block))
1063
+ ),
1064
+
1065
+ empty_statement: () => prec(-1, ';'),
1066
+
1067
+ _terminator: ($) => choice($._line_break, ';'),
1068
+ },
1069
+ });
1070
+
1071
+ /**
1072
+ * Creates a rule to optionally match one or more of the rules separated by `separator`
1073
+ *
1074
+ * @param {RuleOrLiteral} rule
1075
+ *
1076
+ * @param {RuleOrLiteral} separator
1077
+ *
1078
+ * @returns {ChoiceRule}
1079
+ */
1080
+ function sep(rule, separator) {
1081
+ return optional(sep1(rule, separator));
1082
+ }
1083
+
1084
+ /**
1085
+ * Creates a rule to match one or more of the rules separated by `separator`
1086
+ *
1087
+ * @param {RuleOrLiteral} rule
1088
+ *
1089
+ * @param {RuleOrLiteral} separator
1090
+ *
1091
+ * @returns {SeqRule}
1092
+ */
1093
+ function sep1(rule, separator) {
1094
+ return seq(rule, repeat(seq(separator, rule)));
1095
+ }
1096
+
1097
+ /**
1098
+ * Creates a rule to match one or more of the rules separated by a comma
1099
+ *
1100
+ * @param {RuleOrLiteral} rule
1101
+ *
1102
+ * @returns {SeqRule}
1103
+ */
1104
+ function commaSep1(rule) {
1105
+ return sep1(rule, ',');
1106
+ }
1107
+
1108
+ /**
1109
+ * Creates a rule to optionally match one or more of the rules separated by a comma
1110
+ *
1111
+ * @param {RuleOrLiteral} rule
1112
+ *
1113
+ * @returns {ChoiceRule}
1114
+ */
1115
+ function commaSep(rule) {
1116
+ return optional(commaSep1(rule));
1117
+ }