@dekobon/tree-sitter-groovy 0.2.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,1607 @@
1
+ /**
2
+ * @file Apache Groovy grammar for tree-sitter
3
+ * @author Elijah Zupancic <elijah@zupancic.name>
4
+ * @license MIT
5
+ *
6
+ * The authoritative design document for this grammar is
7
+ * SPECIFICATION.md at the repository root. Every rule below maps to
8
+ * a section there. Read the spec before making non-trivial changes.
9
+ *
10
+ * The external scanner (src/scanner.c) handles four context-sensitive
11
+ * lexing situations that pure grammar rules cannot disambiguate:
12
+ * - Slashy-string-start vs. division
13
+ * - Automatic-semicolon (newline-as-terminator)
14
+ * - Labelled-statement colon
15
+ * - Block-comment / Groovydoc single-token emit
16
+ * See SPECIFICATION.md §6.
17
+ */
18
+
19
+ /// <reference types="tree-sitter-cli/dsl" />
20
+ // @ts-check
21
+
22
+ /* eslint-disable no-multi-spaces --
23
+ * The aligned trailing comments make the Apache-mirror precedence
24
+ * ladder readable at a glance.
25
+ */
26
+ const PREC = {
27
+ // Higher number = tighter binding in tree-sitter.
28
+ // Mirrors apache/groovy:GroovyParser.g4 `expression` rule.
29
+ // See SPECIFICATION.md §3.1.
30
+ ASSIGN: 1, // = += -= *= /= %= **= <<= >>= >>>= &= ^= |= ?=
31
+ CONDITIONAL: 2, // ?: (Elvis) and ? : (ternary) — same level
32
+ IMPLICATION: 3, // ==>
33
+ LOGICAL_OR: 4, // ||
34
+ LOGICAL_AND: 5, // &&
35
+ BITWISE_OR: 6, // |
36
+ BITWISE_XOR: 7, // ^
37
+ BITWISE_AND: 8, // &
38
+ EQUALITY: 9, // == != <=> === !== =~ ==~
39
+ RELATIONAL: 10, // < <= > >= in !in instanceof !instanceof as
40
+ SHIFT_OR_RANGE: 11, // << >> >>> .. ..< <.. <..<
41
+ ADDITIVE: 12, // + -
42
+ MULTIPLICATIVE: 13, // * / %
43
+ UNARY: 14, // prefix ++ -- + - (Apache level 3 UNARY_ADD)
44
+ POWER: 15, // ** (Apache level 2)
45
+ UNARY_NOT: 16, // ! ~ (Apache level 1, tighter than POWER)
46
+ POSTFIX: 17, // postfix ++ --, []
47
+ ACCESS: 18, // . ?. ??. *. .& .@ ::
48
+ PRIMARY: 19, // literals, identifiers, ()
49
+ // Type-context only — never competes with the expression ladder
50
+ // above. Shares CONDITIONAL's numeric value by accident; the two
51
+ // do not collide because `_type` and `_expression` reach the
52
+ // resolver through disjoint parse stacks.
53
+ ARRAY_TYPE: 2,
54
+ };
55
+ /* eslint-enable no-multi-spaces */
56
+
57
+ /**
58
+ * GString body segment choice shared between `"..."` and `"""..."""`.
59
+ * The text rule differs (single-quoted bans newlines, triple-quoted
60
+ * allows them) but every other alternative is identical.
61
+ *
62
+ * @param {GrammarSymbols<string>} $ Grammar reference symbols.
63
+ * @param {RuleOrLiteral} textRule The flavour-specific literal-text rule.
64
+ * @returns {ChoiceRule} The shared body-segment choice.
65
+ */
66
+ function gstringPart($, textRule) {
67
+ return choice(
68
+ alias(textRule, $.string_fragment),
69
+ $.gstring_dollar_interpolation,
70
+ $.gstring_brace_interpolation,
71
+ alias($._gstring_literal_dollar, $.string_fragment),
72
+ );
73
+ }
74
+
75
+ module.exports = grammar({
76
+ name: 'groovy',
77
+
78
+ // Placeholder for the real implementation. SPECIFICATION.md §3-§7
79
+ // is the contract this rule set will fulfil.
80
+ word: $ => $.identifier,
81
+
82
+ extras: $ => [
83
+ /\s/,
84
+ $.line_comment,
85
+ $.block_comment,
86
+ $.groovydoc_comment,
87
+ ],
88
+
89
+ // Externals listed here must have a matching branch in
90
+ // `src/scanner.c` AND be referenced by at least one rule below.
91
+ // Tokens the spec §6 proposes as external scanner branches but
92
+ // that this grammar implements in-grammar (GString interpolation
93
+ // via regex tokens, slashy body via in-grammar `_slashy_text`,
94
+ // statement boundaries via `repeat($._statement)`, label colon
95
+ // via grammar precedence) are intentionally NOT listed — listing
96
+ // an unwired external token would let dormant scanner branches
97
+ // mis-fire in error-recovery states.
98
+ externals: $ => [
99
+ $._slashy_string_start,
100
+ $.line_comment,
101
+ $.block_comment,
102
+ $.groovydoc_comment,
103
+ ],
104
+
105
+ conflicts: $ => [
106
+ // See SPECIFICATION.md §7. Populated as rules accumulate.
107
+ // (list_literal vs map_literal — tree-sitter resolves without
108
+ // an explicit conflict because `:` after the first element
109
+ // always disambiguates within one token of lookahead.)
110
+ //
111
+ // §5.15 — inside `( … )` an identifier could be either an
112
+ // _expression (parenthesized_expression) or a _type
113
+ // (parenthesized_type_cast). Tree-sitter explores both via
114
+ // this conflict; the `)` plus the next token disambiguates.
115
+ [$._expression, $._type],
116
+
117
+ // §4 — `a.b.c` could be a field_access chain (expression
118
+ // context) or a qualified_type (type context). Same family
119
+ // as the _expression / _type conflict above but the implicit
120
+ // qualified_type_repeat1 aux symbol forces a separate
121
+ // declaration.
122
+ [$._expression, $.qualified_type],
123
+
124
+ // §3.2.2 — `_cast_type` overlaps `_type` on identifier / array /
125
+ // generic alternatives; only the dotted path diverges. The
126
+ // companion three-way conflict pulls in `_dotted_type` so the
127
+ // dotted shape can win the trailing `.identifier` from the
128
+ // surrounding `field_access` reduction.
129
+ [$._cast_type, $._type],
130
+ [$._cast_type, $._dotted_type, $.qualified_type],
131
+
132
+ // §4 — `for ( identifier …` could continue as a for_statement
133
+ // (identifier is part of the init expression) or as a
134
+ // for_in_statement (identifier is the loop variable). The
135
+ // token after the identifier (`;` / operator vs `in`)
136
+ // disambiguates.
137
+ [$.for_in_statement, $._expression],
138
+
139
+ // §5.2 — `def identifier` could either be a
140
+ // local_variable_declaration (variable_declarator with no
141
+ // initializer) or the start of a method_declaration
142
+ // (_property_name + parameters). The `(` after the name
143
+ // decides; the conflict keeps both alternatives alive until
144
+ // then.
145
+ [$.variable_declarator, $._property_name],
146
+ ],
147
+
148
+ rules: {
149
+ // Top-level. Real rule set lands incrementally per
150
+ // SPECIFICATION.md §4 (statement and declaration coverage).
151
+ source_file: $ => seq(
152
+ optional($.shebang),
153
+ repeat($._statement),
154
+ ),
155
+
156
+ // §4 — `#!/usr/bin/env groovy` line at file start. Single
157
+ // token consumed by the in-grammar lexer.
158
+ shebang: _ => token(seq('#!', /[^\n]*/)),
159
+
160
+ // §4 — statement-level alternatives. Declarations and the rest
161
+ // of control flow fill out this choice in subsequent iterations.
162
+ // `block` is intentionally NOT here — a top-level `{ ... }`
163
+ // continues to parse as an expression_statement-wrapped closure
164
+ // (the historically intended form for Groovy scripts). Blocks
165
+ // appear explicitly in the body slot of `if` / `while` / etc.
166
+ _statement: $ => choice(
167
+ $.expression_statement,
168
+ $.if_statement,
169
+ $.while_statement,
170
+ $.do_while_statement,
171
+ $.for_statement,
172
+ $.for_in_statement,
173
+ $.return_statement,
174
+ $.break_statement,
175
+ $.continue_statement,
176
+ $.throw_statement,
177
+ $.assert_statement,
178
+ $.yield_statement,
179
+ $.try_statement,
180
+ $.class_declaration,
181
+ $.trait_declaration,
182
+ $.interface_declaration,
183
+ $.annotation_type_declaration,
184
+ $.enum_declaration,
185
+ $.record_declaration,
186
+ $.package_declaration,
187
+ $.import_declaration,
188
+ $.local_variable_declaration,
189
+ $.multi_assignment_declaration,
190
+ $.pipeline_statement,
191
+ $.method_declaration,
192
+ $.labeled_statement,
193
+ $.command_chain,
194
+ ),
195
+
196
+ // §5.5 — command_chain is the parenthesis-free method-call
197
+ // form (`println 'hello'`, `events 'a', 'b', 'c'`). v1 is
198
+ // intentionally conservative: receiver is a bare identifier
199
+ // and arguments are restricted to literal-shaped expressions
200
+ // and closures (no risk of mis-parsing `foo bar` as a command
201
+ // call on a bare identifier). Multi-argument lists are supported
202
+ // (comma-separated literal arguments per Gradle / Jenkins DSL
203
+ // idioms). Chained `foo bar baz` and complex argument shapes
204
+ // land later. Closes a subset of murtaza64 #5.
205
+ command_chain: $ => prec(2, seq(
206
+ field('receiver', $.identifier),
207
+ field('argument', $._command_argument),
208
+ repeat(seq(',', field('argument', $._command_argument))),
209
+ )),
210
+
211
+ _command_argument: $ => choice(
212
+ $.string_literal,
213
+ $.number_literal,
214
+ $.boolean_literal,
215
+ $.null_literal,
216
+ $.closure,
217
+ ),
218
+
219
+ // §5.3 — `label: stmt`. Required only at statement-start
220
+ // position; map_entries (which share `:`) are inside `[…]`
221
+ // and don't collide here. `prec(1, ...)` keeps both
222
+ // labeled_statement and expression_statement alive after
223
+ // `identifier`; the `:` decides. A scanner-token form
224
+ // (`_label_colon`) was attempted but interacted with
225
+ // not-yet-implemented named-argument command chains
226
+ // (`archiveArtifacts artifacts: 'x'`); see divergence #5.
227
+ labeled_statement: $ => prec(1, seq(
228
+ field('label', $.identifier),
229
+ ':',
230
+ field('statement', $._statement),
231
+ )),
232
+
233
+ // §4 — block of statements, used in control-flow bodies and
234
+ // (eventually) method bodies. Shares `{ ... }` syntax with
235
+ // closure (§5.8). A `prec(1, …)` keeps block preferred at use
236
+ // sites where both `block` and a closure-bearing `_statement`
237
+ // are valid alternatives (e.g. the body slot of `if` / `while`).
238
+ block: $ => prec(1, seq(
239
+ '{',
240
+ repeat($._statement),
241
+ '}',
242
+ )),
243
+
244
+ // §4 — if / else. `prec.right` resolves the dangling-else
245
+ // ambiguity in favour of the closest `if`. Body accepts either
246
+ // an explicit block (preferred) or a single statement.
247
+ if_statement: $ => prec.right(seq(
248
+ 'if',
249
+ '(',
250
+ field('condition', $._expression),
251
+ ')',
252
+ field('consequence', choice($.block, $._statement)),
253
+ optional(seq(
254
+ 'else',
255
+ field('alternative', choice($.block, $._statement)),
256
+ )),
257
+ )),
258
+
259
+ // §4 — while loop. do-while lands separately.
260
+ while_statement: $ => seq(
261
+ 'while',
262
+ '(',
263
+ field('condition', $._expression),
264
+ ')',
265
+ field('body', choice($.block, $._statement)),
266
+ ),
267
+
268
+ // §4 — C-style for. init / condition / update slots all
269
+ // optional; bare `for (;;)` is the infinite-loop idiom.
270
+ // Typed variable declarations in init (`int i = 0`) land with
271
+ // local_variable_declaration in a later iteration.
272
+ for_statement: $ => seq(
273
+ 'for',
274
+ '(',
275
+ optional(field('init', $._expression)),
276
+ ';',
277
+ optional(field('condition', $._expression)),
278
+ ';',
279
+ optional(field('update', $._expression)),
280
+ ')',
281
+ field('body', choice($.block, $._statement)),
282
+ ),
283
+
284
+ // §4 — do-while. Body is required; condition follows the body.
285
+ do_while_statement: $ => seq(
286
+ 'do',
287
+ field('body', choice($.block, $._statement)),
288
+ 'while',
289
+ '(',
290
+ field('condition', $._expression),
291
+ ')',
292
+ ),
293
+
294
+ // §4 — return / break / continue / throw / assert. Until
295
+ // AUTOMATIC_SEMICOLON lands the parser will greedily consume
296
+ // a trailing expression onto `return` / `throw` / `assert`;
297
+ // explicit `;` or statement boundary is recommended in source.
298
+ return_statement: $ => prec.right(seq(
299
+ 'return',
300
+ optional($._expression),
301
+ )),
302
+
303
+ break_statement: $ => prec.right(seq(
304
+ 'break',
305
+ optional(field('label', $.identifier)),
306
+ )),
307
+
308
+ continue_statement: $ => prec.right(seq(
309
+ 'continue',
310
+ optional(field('label', $.identifier)),
311
+ )),
312
+
313
+ throw_statement: $ => seq('throw', $._expression),
314
+
315
+ // §4 — `yield expr` in a Groovy 4 switch expression's case
316
+ // body. `yield` is a contextual keyword (§5.2) so it remains
317
+ // a usable identifier elsewhere — tree-sitter's word:
318
+ // directive resolves the ambiguity.
319
+ yield_statement: $ => prec.right(seq(
320
+ 'yield',
321
+ $._expression,
322
+ )),
323
+
324
+ // §4 — power-assert: `assert e` or `assert e : msg`. The colon
325
+ // separator may collide with map_entry in later iterations, but
326
+ // assert is a statement-only construct so the context resolves
327
+ // it.
328
+ assert_statement: $ => prec.right(seq(
329
+ 'assert',
330
+ $._expression,
331
+ optional(seq(':', field('message', $._expression))),
332
+ )),
333
+
334
+ // §4 — try / catch / finally. At least one of catch/finally is
335
+ // required per Java/Groovy semantics; the grammar relaxes that
336
+ // to allow a bare `try { … }` and lets downstream tooling
337
+ // surface the error if needed. Try-with-resources lands later
338
+ // with local variable declarations.
339
+ try_statement: $ => seq(
340
+ 'try',
341
+ optional(field('resources', $.resource_specification)),
342
+ field('body', $.block),
343
+ repeat($.catch_clause),
344
+ optional($.finally_clause),
345
+ ),
346
+
347
+ // §4 — `try (Foo f = expr; Bar b = expr) { … }`. Resources
348
+ // are typed or `def`-style declarators with required `=`
349
+ // initializer (per Java-style try-with-resources). Multiple
350
+ // resources separated by `;`.
351
+ resource_specification: $ => seq(
352
+ '(',
353
+ $.resource,
354
+ repeat(seq(';', $.resource)),
355
+ optional(';'),
356
+ ')',
357
+ ),
358
+
359
+ resource: $ => seq(
360
+ optional(field('type', $._type)),
361
+ field('name', $.identifier),
362
+ '=',
363
+ field('value', $._expression),
364
+ ),
365
+
366
+ catch_clause: $ => seq(
367
+ 'catch',
368
+ '(',
369
+ field('parameter', $.catch_formal_parameter),
370
+ ')',
371
+ field('body', $.block),
372
+ ),
373
+
374
+ // §4 — `catch_formal_parameter` accepts either a single _type
375
+ // or a multi_type for Java 7+ / Groovy multi-catch. Closes
376
+ // murtaza64 #39.
377
+ catch_formal_parameter: $ => seq(
378
+ field('type', choice($._type, $.multi_type)),
379
+ field('name', $.identifier),
380
+ ),
381
+
382
+ multi_type: $ => seq(
383
+ $._type,
384
+ repeat1(seq('|', $._type)),
385
+ ),
386
+
387
+ finally_clause: $ => seq(
388
+ 'finally',
389
+ field('body', $.block),
390
+ ),
391
+
392
+ // §4 — class / trait / interface declarations. v1 supports the
393
+ // shape `[keyword] Name { body }`. extends, implements, generics,
394
+ // modifiers, and sealed / permits clauses land in later
395
+ // iterations. trait closes the dekobon #247 trait row.
396
+ // §4 — class declaration with optional Groovy 4 sealed modifier
397
+ // and `permits` clause. The `non-sealed` form is a single
398
+ // anonymous token (token('non-sealed')) so the dash doesn't
399
+ // get lexed as subtraction.
400
+ class_declaration: $ => seq(
401
+ repeat($.annotation),
402
+ repeat($._modifier),
403
+ optional(choice('sealed', token('non-sealed'))),
404
+ 'class',
405
+ field('name', $.identifier),
406
+ optional(field('type_parameters', $.type_parameters)),
407
+ optional($.superclass),
408
+ optional($.super_interfaces),
409
+ optional($.permits_clause),
410
+ field('body', $.class_body),
411
+ ),
412
+
413
+ trait_declaration: $ => seq(
414
+ repeat($.annotation),
415
+ repeat($._modifier),
416
+ optional(choice('sealed', token('non-sealed'))),
417
+ 'trait',
418
+ field('name', $.identifier),
419
+ optional(field('type_parameters', $.type_parameters)),
420
+ optional($.extends_interfaces),
421
+ optional($.permits_clause),
422
+ field('body', $.class_body),
423
+ ),
424
+
425
+ interface_declaration: $ => seq(
426
+ repeat($.annotation),
427
+ repeat($._modifier),
428
+ optional(choice('sealed', token('non-sealed'))),
429
+ 'interface',
430
+ field('name', $.identifier),
431
+ optional(field('type_parameters', $.type_parameters)),
432
+ optional($.extends_interfaces),
433
+ optional($.permits_clause),
434
+ field('body', $.class_body),
435
+ ),
436
+
437
+ // §4 / §5.14 — generic type parameter list on a class /
438
+ // interface / trait / method declaration. The `<` is
439
+ // `token.immediate` so the parser only treats it as a type
440
+ // parameter opener when it follows the declaration name with
441
+ // no intervening whitespace.
442
+ type_parameters: $ => seq(
443
+ token.immediate('<'),
444
+ $.type_parameter,
445
+ repeat(seq(',', $.type_parameter)),
446
+ '>',
447
+ ),
448
+
449
+ type_parameter: $ => seq(
450
+ field('name', alias($.identifier, $.type_identifier)),
451
+ optional(seq(
452
+ 'extends',
453
+ $._type,
454
+ repeat(seq('&', $._type)),
455
+ )),
456
+ ),
457
+
458
+ // §4 — class has a single super and zero-or-more interfaces.
459
+ superclass: $ => seq('extends', $._type),
460
+
461
+ super_interfaces: $ => seq(
462
+ 'implements',
463
+ $._type,
464
+ repeat(seq(',', $._type)),
465
+ ),
466
+
467
+ // §4 — trait / interface extend multiple interfaces (no
468
+ // `implements` keyword in that position).
469
+ extends_interfaces: $ => seq(
470
+ 'extends',
471
+ $._type,
472
+ repeat(seq(',', $._type)),
473
+ ),
474
+
475
+ // §4 — Java-style modifiers. Each one is a hard reserved
476
+ // keyword (§5.2) so the `word: $.identifier` directive keeps
477
+ // them from accidentally being treated as identifiers.
478
+ _modifier: _ => choice(
479
+ 'public',
480
+ 'private',
481
+ 'protected',
482
+ 'static',
483
+ 'final',
484
+ 'abstract',
485
+ 'synchronized',
486
+ 'native',
487
+ 'transient',
488
+ 'volatile',
489
+ 'strictfp',
490
+ ),
491
+
492
+ permits_clause: $ => seq(
493
+ 'permits',
494
+ $._type,
495
+ repeat(seq(',', $._type)),
496
+ ),
497
+
498
+ // §4 — `@interface Foo { … }` declares an annotation type.
499
+ // `@` followed by the `interface` keyword is unambiguous —
500
+ // `interface` is a hard reserved keyword (§5.2) so the
501
+ // alternative parse `annotation` (which needs identifier)
502
+ // cannot fire here.
503
+ annotation_type_declaration: $ => seq(
504
+ repeat($.annotation),
505
+ '@',
506
+ 'interface',
507
+ field('name', $.identifier),
508
+ field('body', $.class_body),
509
+ ),
510
+
511
+ // §4 — `{ method* }` for v1. Field declarations, static
512
+ // initialisers, and inner classes land later.
513
+ class_body: $ => seq(
514
+ '{',
515
+ repeat($._class_member),
516
+ '}',
517
+ ),
518
+
519
+ _class_member: $ => choice(
520
+ $.method_declaration,
521
+ $.field_declaration,
522
+ $.constructor_declaration,
523
+ $.static_initializer,
524
+ ),
525
+
526
+ // §4 — constructors share the method_declaration shape but have
527
+ // no return type and the name matches the enclosing class. We
528
+ // don't enforce the name match (downstream tooling can), but we
529
+ // emit a distinct node kind so IDE features (rename, jump) can
530
+ // tell constructors apart from methods. `prec(3, …)` wins over
531
+ // both field and method when the identifier is followed by `(`
532
+ // and no return type precedes it.
533
+ constructor_declaration: $ => prec.right(3, seq(
534
+ repeat($.annotation),
535
+ repeat($._modifier),
536
+ field('name', $.identifier),
537
+ field('parameters', $.formal_parameters),
538
+ optional($.throws_clause),
539
+ field('body', $.block),
540
+ )),
541
+
542
+ // §4 — `static { … }` class-body initializer block. Runs once
543
+ // when the class is loaded. Distinct node kind so downstream
544
+ // tools can index it.
545
+ static_initializer: $ => seq(
546
+ 'static',
547
+ field('body', $.block),
548
+ ),
549
+
550
+ // §4 — class / interface / trait field. Java-style shape: optional
551
+ // annotations, optional modifiers, `def` or a `_type`, then one or
552
+ // more declarators (each with optional initializer). Method
553
+ // declarations share the same prefix; `prec(2, …)` on
554
+ // `method_declaration` and the trailing `(` discriminate.
555
+ field_declaration: $ => seq(
556
+ repeat($.annotation),
557
+ repeat($._modifier),
558
+ choice('def', field('type', $._type)),
559
+ $.variable_declarator,
560
+ repeat(seq(',', $.variable_declarator)),
561
+ ),
562
+
563
+ // §4 — `def name(params) body`. Typed return type (`String foo()
564
+ // { … }`) lands once we can disambiguate from local variable
565
+ // declaration. Modifiers and throws clause similarly defer.
566
+ // prec(1) wins over local_variable_declaration when `(` follows
567
+ // the name, so `def f() { … }` at script-top-level parses as a
568
+ // method rather than a `def f` variable followed by `(…) { … }`.
569
+ method_declaration: $ => prec.right(2, seq(
570
+ repeat($.annotation),
571
+ repeat($._modifier),
572
+ optional(field('type_parameters', $.method_type_parameters)),
573
+ choice('def', field('return_type', $._type)),
574
+ field('name', $._property_name),
575
+ field('parameters', $.formal_parameters),
576
+ optional($.throws_clause),
577
+ optional(field('body', $.block)),
578
+ )),
579
+
580
+ // §4 / §5.14 — method-level generic type parameters
581
+ // (`<T> T identity(T x) { x }`). Distinct from `type_parameters`
582
+ // (used on class/trait/interface) because here the `<` cannot be
583
+ // `token.immediate` — it appears at the start of the declaration
584
+ // with optional whitespace from preceding modifiers / annotations.
585
+ method_type_parameters: $ => seq(
586
+ '<',
587
+ $.type_parameter,
588
+ repeat(seq(',', $.type_parameter)),
589
+ '>',
590
+ ),
591
+
592
+ // §4 — `throws E1, E2, …` after a method signature.
593
+ throws_clause: $ => prec.left(seq(
594
+ 'throws',
595
+ $._type,
596
+ repeat(seq(',', $._type)),
597
+ )),
598
+
599
+ formal_parameters: $ => seq(
600
+ '(',
601
+ optional(seq(
602
+ $.formal_parameter,
603
+ repeat(seq(',', $.formal_parameter)),
604
+ )),
605
+ ')',
606
+ ),
607
+
608
+ // §5.13 — formal parameter with optional type, optional
609
+ // varargs marker (`Type... name`), and optional default value
610
+ // (`name = expr`).
611
+ formal_parameter: $ => seq(
612
+ optional(field('type', choice($._type, $.varargs_type))),
613
+ field('name', $.identifier),
614
+ optional(seq('=', field('default', $._expression))),
615
+ ),
616
+
617
+ varargs_type: $ => seq($._type, '...'),
618
+
619
+ // §4 — enum. Closes murtaza64 #36 first half. Constants list
620
+ // (optionally trailing-comma) optionally followed by `;` and
621
+ // further class members. Constants may carry constructor
622
+ // arguments (`RED(0xff0000)`); v1 supports that via the
623
+ // optional argument_list.
624
+ enum_declaration: $ => seq(
625
+ repeat($.annotation),
626
+ 'enum',
627
+ field('name', $.identifier),
628
+ field('body', $.enum_body),
629
+ ),
630
+
631
+ enum_body: $ => seq(
632
+ '{',
633
+ optional(seq(
634
+ $.enum_constant,
635
+ repeat(seq(',', $.enum_constant)),
636
+ optional(','),
637
+ )),
638
+ optional(seq(';', repeat($._class_member))),
639
+ '}',
640
+ ),
641
+
642
+ enum_constant: $ => seq(
643
+ field('name', $.identifier),
644
+ optional(field('arguments', $.argument_list)),
645
+ ),
646
+
647
+ // §4 — record (Groovy 4+). `record Name(Type a, Type b) [{ body }]`.
648
+ // `record` is a contextual keyword — tree-sitter's word: directive
649
+ // keeps `record` usable as a plain identifier elsewhere.
650
+ record_declaration: $ => prec.right(seq(
651
+ repeat($.annotation),
652
+ 'record',
653
+ field('name', $.identifier),
654
+ field('components', $.record_components),
655
+ optional(field('body', $.class_body)),
656
+ )),
657
+
658
+ record_components: $ => seq(
659
+ '(',
660
+ optional(seq(
661
+ $.record_component,
662
+ repeat(seq(',', $.record_component)),
663
+ )),
664
+ ')',
665
+ ),
666
+
667
+ record_component: $ => seq(
668
+ field('type', $._type),
669
+ field('name', $.identifier),
670
+ ),
671
+
672
+ // §4 — package and import. Trailing `;` is optional in Groovy;
673
+ // we accept both forms by not requiring it.
674
+ package_declaration: $ => seq(
675
+ 'package',
676
+ field('name', $.qualified_name),
677
+ ),
678
+
679
+ // §4 — `import [static] qualified.Name [.*] [as Alias]`.
680
+ // `.*` is a single anonymous token so it lexes distinctly from
681
+ // a continuation of qualified_name (`.identifier`). Wildcard
682
+ // imports only appear in this position, so the lexer-wide
683
+ // longest-match is safe.
684
+ import_declaration: $ => seq(
685
+ 'import',
686
+ optional('static'),
687
+ field('name', $.qualified_name),
688
+ optional(token('.*')),
689
+ optional(seq('as', field('alias', $.identifier))),
690
+ ),
691
+
692
+ qualified_name: $ => seq(
693
+ $.identifier,
694
+ repeat(seq('.', $.identifier)),
695
+ ),
696
+
697
+ // §4 / §5.12 — `def x = …`, `var x = …`, and `Type x = …`.
698
+ // The typed form requires every declarator to carry an
699
+ // `= value` initializer; bare `Type x` without initializer
700
+ // would conflict with method_invocation `Type(x)` and is
701
+ // disallowed in v1. Multiple declarators per statement
702
+ // (`def a = 1, b = 2`) are supported in all three forms.
703
+ local_variable_declaration: $ => prec.right(choice(
704
+ seq(
705
+ choice('def', 'var'),
706
+ $.variable_declarator,
707
+ repeat(seq(',', $.variable_declarator)),
708
+ ),
709
+ seq(
710
+ field('type', $._type),
711
+ $._initialized_declarator,
712
+ repeat(seq(',', $._initialized_declarator)),
713
+ ),
714
+ )),
715
+
716
+ _initialized_declarator: $ => seq(
717
+ field('name', $.identifier),
718
+ '=',
719
+ field('value', $._expression),
720
+ ),
721
+
722
+ variable_declarator: $ => seq(
723
+ field('name', $.identifier),
724
+ optional(seq('=', field('value', $._expression))),
725
+ ),
726
+
727
+ // §4 — `def (a, b) = expr`. Closes murtaza64 #22. v1 requires
728
+ // the leading `def`; the no-`def` form `(a, b) = expr` lands
729
+ // later (it shares a prefix with parenthesized_expression and
730
+ // needs a conflict declaration).
731
+ multi_assignment_declaration: $ => seq(
732
+ 'def',
733
+ '(',
734
+ $.variable_declarator,
735
+ repeat(seq(',', $.variable_declarator)),
736
+ ')',
737
+ '=',
738
+ field('value', $._expression),
739
+ ),
740
+
741
+ // §4 / §10 row #37 — Jenkins-style `pipeline { … }` block.
742
+ // Closes murtaza64 #37 (pipeline as a regular statement, not
743
+ // a file-trailer-only construct). The body is a closure, since
744
+ // that's how Jenkins's DSL is shaped — `pipeline` is followed
745
+ // by a closure literal with nested stages, steps, etc.
746
+ pipeline_statement: $ => seq(
747
+ 'pipeline',
748
+ field('body', $.closure),
749
+ ),
750
+
751
+ // §5.9 — annotation usage. Lands on its own here; class /
752
+ // method / record / enum declarations gain an optional
753
+ // leading `repeat($.annotation)` so a `@Foo @Bar class X { }`
754
+ // attaches the annotations to the class node.
755
+ annotation: $ => seq(
756
+ '@',
757
+ field('name', $.qualified_name),
758
+ optional(field('arguments', $.argument_list)),
759
+ ),
760
+
761
+ // §4 — `for (x in xs)`. Untyped variable for v1. Typed
762
+ // (`for (Type x in xs)`) lands later. The `in` keyword here
763
+ // is a structural delimiter, not the membership_expression
764
+ // operator — context disambiguates because the for-header is
765
+ // not an expression position.
766
+ for_in_statement: $ => seq(
767
+ 'for',
768
+ '(',
769
+ field('variable', $.identifier),
770
+ 'in',
771
+ field('value', $._expression),
772
+ ')',
773
+ field('body', choice($.block, $._statement)),
774
+ ),
775
+
776
+ expression_statement: $ => $._expression,
777
+
778
+ // §3.2 PRIMARY — minimal alternative set for now. Every primary
779
+ // expression form from §3.2 / §5 will land in this choice over
780
+ // subsequent iterations.
781
+ _expression: $ => choice(
782
+ $.identifier,
783
+ $.number_literal,
784
+ $.string_literal,
785
+ $.boolean_literal,
786
+ $.null_literal,
787
+ $.parenthesized_expression,
788
+ $.unary_expression,
789
+ $.list_literal,
790
+ $.map_literal,
791
+ $.closure,
792
+ $.binary_expression,
793
+ $.range_expression,
794
+ $.identity_expression,
795
+ $.spaceship_expression,
796
+ $.regex_find_expression,
797
+ $.regex_match_expression,
798
+ $.ternary_expression,
799
+ $.elvis_expression,
800
+ $.logical_implication_expression,
801
+ $.assignment_expression,
802
+ $.power_expression,
803
+ $.subscript_expression,
804
+ $.safe_subscript_expression,
805
+ $.method_invocation,
806
+ $.update_expression,
807
+ $.unary_update_expression,
808
+ $.field_access,
809
+ $.safe_navigation_expression,
810
+ $.safe_chain_dot_expression,
811
+ $.spread_dot_expression,
812
+ $.method_pointer_expression,
813
+ $.direct_field_access_expression,
814
+ $.method_reference_expression,
815
+ $.object_creation_expression,
816
+ $.parenthesized_type_cast,
817
+ $.cast_expression,
818
+ $.membership_expression,
819
+ $.instanceof_expression,
820
+ $.switch_expression,
821
+ ),
822
+
823
+ // §4 — Groovy 4 switch-expression. Single rule for both classic
824
+ // `case … :` and arrow `case … -> …` branches; arrow form
825
+ // closes murtaza64 #36 (second half). Used as a statement via
826
+ // expression_statement, or as an expression directly per the
827
+ // §8.2 #36 regression.
828
+ switch_expression: $ => seq(
829
+ 'switch',
830
+ '(',
831
+ field('value', $._expression),
832
+ ')',
833
+ field('body', $.switch_block),
834
+ ),
835
+
836
+ switch_block: $ => seq(
837
+ '{',
838
+ repeat(choice(
839
+ $.switch_case,
840
+ $.switch_arrow_case,
841
+ $.switch_default,
842
+ )),
843
+ '}',
844
+ ),
845
+
846
+ switch_case: $ => seq(
847
+ 'case',
848
+ field('value', $._expression),
849
+ ':',
850
+ repeat($._statement),
851
+ ),
852
+
853
+ switch_arrow_case: $ => seq(
854
+ 'case',
855
+ field('value', $._expression),
856
+ '->',
857
+ field('body', choice($.block, $._statement)),
858
+ ),
859
+
860
+ switch_default: $ => seq(
861
+ 'default',
862
+ choice(
863
+ seq(':', repeat($._statement)),
864
+ seq('->', field('body', choice($.block, $._statement))),
865
+ ),
866
+ ),
867
+
868
+ // §3.2 — `x as Type`. RELATIONAL level (10), left-associative.
869
+ // RHS is `_cast_type`, a `_type`-shaped sibling that swaps in
870
+ // `_dotted_type` for `qualified_type` so the trailing
871
+ // `.identifier` of `x as java.util.Map` extends the type
872
+ // through every dot segment (instead of being claimed by
873
+ // `field_access` at higher precedence than `cast_expression`).
874
+ cast_expression: $ => prec.left(PREC.RELATIONAL, seq(
875
+ field('value', $._expression),
876
+ 'as',
877
+ field('type', $._cast_type),
878
+ )),
879
+
880
+ // §3.2.2 — RHS type of `as`, `instanceof`, and `!instanceof`.
881
+ // Same alternatives as `_type` except `qualified_type` is
882
+ // replaced by `_dotted_type`, whose tail shifts each dot at a
883
+ // precedence tighter than `field_access`.
884
+ _cast_type: $ => choice(
885
+ alias($.identifier, $.type_identifier),
886
+ $.array_type,
887
+ alias($._dotted_type, $.qualified_type),
888
+ $.generic_type,
889
+ ),
890
+
891
+ _dotted_type: $ => seq(
892
+ alias($.identifier, $.type_identifier),
893
+ $._dotted_type_tail,
894
+ ),
895
+
896
+ _dotted_type_tail: $ => prec.right(PREC.ACCESS + 1, seq(
897
+ '.',
898
+ alias($.identifier, $.type_identifier),
899
+ optional($._dotted_type_tail),
900
+ )),
901
+
902
+ // §3.2 — `x in xs` and `x !in xs`. RELATIONAL level. Both
903
+ // operators share one node kind; the `operator` field
904
+ // distinguishes them. `!in` is wrapped in `token(prec(1, …))`
905
+ // so it lexes as a single token despite the embedded space
906
+ // requirement implied by §3.2.2's trailing-context rule —
907
+ // Apache's `NOT_IN` predicate is approximated by requiring at
908
+ // least one trailing whitespace, `[`, `(`, or `{` after `!in`.
909
+ // §3.2 / §3.2.2 — `x in xs` and `x !in xs`. `!in` is a single
910
+ // token whose match requires a trailing whitespace / bracket
911
+ // character (Apache `NOT_IN` predicate) so `a !inXs` does not
912
+ // mis-tokenise as `!in` followed by `Xs`.
913
+ membership_expression: $ => {
914
+ const ops = [
915
+ 'in',
916
+ token(prec(1, seq('!in', /[ \t\r\n\[({]/))),
917
+ ];
918
+ return choice(...ops.map(op => prec.left(PREC.RELATIONAL, seq(
919
+ field('element', $._expression),
920
+ field('operator', op),
921
+ field('collection', $._expression),
922
+ ))));
923
+ },
924
+
925
+ // §3.2 / §3.2.2 — `x instanceof T` and `x !instanceof T`.
926
+ // `!instanceof` is a single token whose match requires a
927
+ // trailing whitespace (Apache `NOT_INSTANCEOF` predicate) so
928
+ // `a !instanceofX` does not mis-tokenise as `!instanceof`
929
+ // followed by identifier `X`.
930
+ instanceof_expression: $ => {
931
+ const ops = [
932
+ 'instanceof',
933
+ token(prec(1, seq('!instanceof', /[ \t\r\n]/))),
934
+ ];
935
+ return choice(...ops.map(op => prec.left(PREC.RELATIONAL, seq(
936
+ field('value', $._expression),
937
+ field('operator', op),
938
+ field('type', $._cast_type),
939
+ ))));
940
+ },
941
+
942
+ // §3.2 / §5.15 — `new Foo(args)`. We only handle the plain
943
+ // constructor-call form here; array creation `new Foo[10]`,
944
+ // generic types, and array initialisers land later. PREC.PRIMARY
945
+ // so it composes with the access tier from the left
946
+ // (`new Foo().bar`).
947
+ object_creation_expression: $ => prec.right(PREC.PRIMARY, seq(
948
+ 'new',
949
+ field('type', $._type),
950
+ field('arguments', $.argument_list),
951
+ )),
952
+
953
+ // §5.15 — C-style cast `(Foo) x`. Distinct from
954
+ // `parenthesized_expression` followed by something (resolved
955
+ // by tree-sitter's GLR; declared as a conflict if needed).
956
+ // PREC.UNARY right-associative so `(Foo) -x` parses as
957
+ // `(Foo)(-x)` and the type binds to the rightward expression.
958
+ parenthesized_type_cast: $ => prec.right(PREC.UNARY, seq(
959
+ '(',
960
+ field('type', $._type),
961
+ ')',
962
+ field('value', $._expression),
963
+ )),
964
+
965
+ // §4 — `_type`. Supports unqualified type identifiers,
966
+ // qualified types (`java.util.List`), array types
967
+ // (`int[]`, `String[][]`), and generic types (`List<String>`,
968
+ // `Map<String, Integer>`). `generic_type` always carries explicit
969
+ // angle brackets, so it never competes with comparison operators
970
+ // in expression context (which only reach `_type` through a
971
+ // separate parse stack).
972
+ _type: $ => choice(
973
+ alias($.identifier, $.type_identifier),
974
+ $.array_type,
975
+ $.qualified_type,
976
+ $.generic_type,
977
+ ),
978
+
979
+ // Near-twin of `_dotted_type` below; the two cannot be merged
980
+ // without breaking either field-access (which needs
981
+ // `qualified_type` to NOT outrank `field_access`) or
982
+ // cast / instanceof RHS (which needs `_dotted_type` to outrank
983
+ // `field_access`). Edits here should be mirrored in
984
+ // `_dotted_type` / `_dotted_type_tail`.
985
+ qualified_type: $ => prec.left(seq(
986
+ alias($.identifier, $.type_identifier),
987
+ repeat1(seq('.', alias($.identifier, $.type_identifier))),
988
+ )),
989
+
990
+ array_type: $ => prec(PREC.ARRAY_TYPE, seq($._type, '[', ']')),
991
+
992
+ // §4 / §5.14 — Java-style generic type arguments. Allows nested
993
+ // generics (`Map<String, List<Integer>>`) and bounded wildcards
994
+ // (`? extends Foo`, `? super Bar`). The `<` is `token.immediate`
995
+ // so it only matches when the angle bracket directly follows the
996
+ // base name with no intervening whitespace — `a < b` still parses
997
+ // as a binary expression because of the space before `<`.
998
+ generic_type: $ => prec.right(PREC.ARRAY_TYPE, seq(
999
+ field('base', choice(
1000
+ alias($.identifier, $.type_identifier),
1001
+ $.qualified_type,
1002
+ )),
1003
+ field('arguments', $.type_arguments),
1004
+ )),
1005
+
1006
+ type_arguments: $ => seq(
1007
+ token.immediate('<'),
1008
+ $._type_argument,
1009
+ repeat(seq(',', $._type_argument)),
1010
+ '>',
1011
+ ),
1012
+
1013
+ _type_argument: $ => choice(
1014
+ $._type,
1015
+ $.wildcard,
1016
+ ),
1017
+
1018
+ wildcard: $ => seq(
1019
+ '?',
1020
+ optional(seq(
1021
+ choice('extends', 'super'),
1022
+ $._type,
1023
+ )),
1024
+ ),
1025
+
1026
+ // §3.1 ACCESS tier / §3.2 — seven distinct node kinds for the
1027
+ // dot family. All at PREC.ACCESS left-associative so chains like
1028
+ // `a.b.c` parse `(a.b).c`. Right operand is the property /
1029
+ // field / method name (an identifier; method_reference also
1030
+ // accepts the `new` keyword per §5.14). Each operator emits a
1031
+ // unique node kind so downstream tooling (`get_op_type()`) can
1032
+ // discriminate without tree-shape heuristics.
1033
+
1034
+ field_access: $ => prec.left(PREC.ACCESS, seq(
1035
+ field('object', $._expression),
1036
+ '.',
1037
+ field('field', $._property_name),
1038
+ )),
1039
+
1040
+ safe_navigation_expression: $ => prec.left(PREC.ACCESS, seq(
1041
+ field('object', $._expression),
1042
+ '?.',
1043
+ field('property', $._property_name),
1044
+ )),
1045
+
1046
+ // Groovy 4+ — see SPECIFICATION.md §3.2. Unlike `?.`, which only
1047
+ // short-circuits when the immediate receiver is null, `??.`
1048
+ // propagates null through the whole chain.
1049
+ safe_chain_dot_expression: $ => prec.left(PREC.ACCESS, seq(
1050
+ field('object', $._expression),
1051
+ '??.',
1052
+ field('property', $._property_name),
1053
+ )),
1054
+
1055
+ spread_dot_expression: $ => prec.left(PREC.ACCESS, seq(
1056
+ field('object', $._expression),
1057
+ '*.',
1058
+ field('property', $._property_name),
1059
+ )),
1060
+
1061
+ method_pointer_expression: $ => prec.left(PREC.ACCESS, seq(
1062
+ field('object', $._expression),
1063
+ '.&',
1064
+ field('method', $._property_name),
1065
+ )),
1066
+
1067
+ direct_field_access_expression: $ => prec.left(PREC.ACCESS, seq(
1068
+ field('object', $._expression),
1069
+ '.@',
1070
+ field('field', $._property_name),
1071
+ )),
1072
+
1073
+ // §5.2 — property / method names after a dot operator (or as a
1074
+ // method declaration name) can be either an identifier or a
1075
+ // *quoted identifier*: a string literal that escapes spaces,
1076
+ // keywords, or special chars. `map."key with spaces"` and
1077
+ // `def "abstract"() { … }` both rely on this.
1078
+ _property_name: $ => choice(
1079
+ $.identifier,
1080
+ alias($.string_literal, $.quoted_identifier),
1081
+ ),
1082
+
1083
+ // §5.14 — method reference. Groovy 3+. The RHS is either an
1084
+ // identifier (`String::length`) or the `new` keyword
1085
+ // (`Foo::new`).
1086
+ method_reference_expression: $ => prec.left(PREC.ACCESS, seq(
1087
+ field('target', $._expression),
1088
+ '::',
1089
+ field('name', choice($.identifier, 'new')),
1090
+ )),
1091
+
1092
+ // §3.2 — postfix `++` / `--`. Distinct node kind from the
1093
+ // prefix form (`unary_update_expression`) per the spec table.
1094
+ // PREC.POSTFIX, left-associative.
1095
+ update_expression: $ => prec.left(PREC.POSTFIX, seq(
1096
+ field('operand', $._expression),
1097
+ field('operator', choice('++', '--')),
1098
+ )),
1099
+
1100
+ // §3.2 — prefix `++` / `--`. Apache groups these with UNARY_ADD
1101
+ // (level 3); we use PREC.UNARY to match.
1102
+ unary_update_expression: $ => prec.right(PREC.UNARY, seq(
1103
+ field('operator', choice('++', '--')),
1104
+ field('operand', $._expression),
1105
+ )),
1106
+
1107
+ // §3.2 POSTFIX / §3.2.1 — `f(args)` and the subscript-yielding-
1108
+ // callable composition `arr[i](args)`. The callee slot is any
1109
+ // expression so subscript / future field_access / future
1110
+ // safe_navigation chains compose naturally. PREC.POSTFIX
1111
+ // matches subscript so chains like `obj[i](x)[j]` work.
1112
+ method_invocation: $ => prec.left(PREC.POSTFIX, seq(
1113
+ field('function', $._expression),
1114
+ field('arguments', $.argument_list),
1115
+ )),
1116
+
1117
+ // §3.2 / §5.11 — argument list. Positional args are `_expression`;
1118
+ // named args (Groovy's `k: v` in call position) are `map_entry`
1119
+ // aliased to `named_argument` so downstream tools can tell them
1120
+ // apart from positional values. Spread (`*expr`) lands later.
1121
+ argument_list: $ => seq(
1122
+ '(',
1123
+ optional(seq(
1124
+ $._argument,
1125
+ repeat(seq(',', $._argument)),
1126
+ optional(','),
1127
+ )),
1128
+ ')',
1129
+ ),
1130
+
1131
+ _argument: $ => choice(
1132
+ $._expression,
1133
+ alias($.map_entry, $.named_argument),
1134
+ $.spread_arguments,
1135
+ ),
1136
+
1137
+ // §5.11 — `*expr` only inside argument list / list literal.
1138
+ // Distinct from a binary `*` because the LHS is absent.
1139
+ spread_arguments: $ => seq(
1140
+ '*',
1141
+ field('value', $._expression),
1142
+ ),
1143
+
1144
+ // §3.1 POSTFIX / §3.2 — subscript and safe-subscript. The
1145
+ // distinction `arr?[k]` (safe) vs `arr[k]` (regular) is the
1146
+ // explicit ask of dekobon #247 for the `?[]` row. Both at
1147
+ // PREC.POSTFIX, left-associative — `a[b][c]` parses as
1148
+ // `(a[b])[c]`.
1149
+ subscript_expression: $ => prec.left(PREC.POSTFIX, seq(
1150
+ field('object', $._expression),
1151
+ '[',
1152
+ field('index', $._expression),
1153
+ ']',
1154
+ )),
1155
+
1156
+ // `?[` is one token. `a ? [b] : c` (with space) still parses
1157
+ // as ternary plus list because tree-sitter's lexer never
1158
+ // crosses a whitespace boundary to extend a multi-char token.
1159
+ safe_subscript_expression: $ => prec.left(PREC.POSTFIX, seq(
1160
+ field('object', $._expression),
1161
+ '?[',
1162
+ field('index', $._expression),
1163
+ ']',
1164
+ )),
1165
+
1166
+ // §3.1 level 2 / §3.2 — `**`. Distinct node kind so metric tools
1167
+ // count power separately from multiplicative ops. Right-assoc
1168
+ // per Apache: `1 ** 2 ** 3` parses as `1 ** (2 ** 3)`. POWER
1169
+ // binds tighter than UNARY (+ -) but looser than UNARY_NOT
1170
+ // (! ~) — see PREC table comments.
1171
+ power_expression: $ => prec.right(PREC.POWER, seq(
1172
+ field('left', $._expression),
1173
+ '**',
1174
+ field('right', $._expression),
1175
+ )),
1176
+
1177
+ // §3.1 level 13.5 / §3.2 — `==>`. Right-associative per Apache
1178
+ // (`<assoc=right>`), distinct node kind so downstream tools can
1179
+ // discriminate from `=>` (lambda arrow, not yet wired).
1180
+ logical_implication_expression: $ => prec.right(PREC.IMPLICATION, seq(
1181
+ field('left', $._expression),
1182
+ '==>',
1183
+ field('right', $._expression),
1184
+ )),
1185
+
1186
+ // §3.1 level 15 / §3.2 — single `assignment_expression` node
1187
+ // covers every augmented form (`+=`, `-=`, `*=`, `/=`, `%=`,
1188
+ // `**=`, `<<=`, `>>=`, `>>>=`, `&=`, `^=`, `|=`) plus plain `=`
1189
+ // and Elvis-assign `?=`. The operator field distinguishes them.
1190
+ // Right-associative per Apache. ASSIGN is the loosest precedence
1191
+ // tier and binds looser than CONDITIONAL above it.
1192
+ assignment_expression: $ => {
1193
+ const ops = [
1194
+ '=',
1195
+ '+=', '-=', '*=', '/=', '%=', '**=',
1196
+ '<<=', '>>=', '>>>=',
1197
+ '&=', '^=', '|=',
1198
+ '?=',
1199
+ ];
1200
+ return choice(...ops.map(op => prec.right(PREC.ASSIGN, seq(
1201
+ field('left', $._expression),
1202
+ field('operator', op),
1203
+ field('right', $._expression),
1204
+ ))));
1205
+ },
1206
+
1207
+ // §3.1 level 14 / §3.2 — ternary and Elvis share the CONDITIONAL
1208
+ // precedence level and are both right-associative. Apache groups
1209
+ // them in one `conditionalExprAlt` so `a ? b ?: c : d` parses as
1210
+ // `a ? (b ?: c) : d` because the middle slot recursively re-enters
1211
+ // the same precedence level. We replicate this with two distinct
1212
+ // rules at PREC.CONDITIONAL; right-associativity + recursion via
1213
+ // `$._expression` give the same result.
1214
+ //
1215
+ // elvis_expression is the headline ask of
1216
+ // dekobon/big-code-analysis #246: downstream tools need to
1217
+ // discriminate elvis from ternary by node kind alone.
1218
+ ternary_expression: $ => prec.right(PREC.CONDITIONAL, seq(
1219
+ field('condition', $._expression),
1220
+ '?',
1221
+ field('consequence', $._expression),
1222
+ ':',
1223
+ field('alternative', $._expression),
1224
+ )),
1225
+
1226
+ elvis_expression: $ => prec.right(PREC.CONDITIONAL, seq(
1227
+ field('value', $._expression),
1228
+ '?:',
1229
+ field('default', $._expression),
1230
+ )),
1231
+
1232
+ // §3.2 — distinct-node ops that share precedence with existing
1233
+ // binary_expression tiers but emit unique node kinds per the
1234
+ // operator-coverage matrix. The integration target (`get_op_type()`
1235
+ // in dekobon/big-code-analysis) needs to discriminate these by
1236
+ // node kind alone, not by tree-shape heuristics.
1237
+
1238
+ // §3.2 range_expression — inclusive `..`, exclusive `..<` (right),
1239
+ // exclusive `<..` (left), exclusive `<..<` (both). SHIFT_OR_RANGE
1240
+ // precedence level, left-associative.
1241
+ range_expression: $ => {
1242
+ const ranges = ['..', '..<', '<..', '<..<'];
1243
+ return choice(...ranges.map(op => prec.left(PREC.SHIFT_OR_RANGE, seq(
1244
+ field('start', $._expression),
1245
+ field('operator', op),
1246
+ field('end', $._expression),
1247
+ ))));
1248
+ },
1249
+
1250
+ // §3.2 identity_expression — `===` (identity) and `!==`
1251
+ // (non-identity), EQUALITY level. Per Apache, these live in
1252
+ // the equality alternative.
1253
+ identity_expression: $ => {
1254
+ const ops = ['===', '!=='];
1255
+ return choice(...ops.map(op => prec.left(PREC.EQUALITY, seq(
1256
+ field('left', $._expression),
1257
+ field('operator', op),
1258
+ field('right', $._expression),
1259
+ ))));
1260
+ },
1261
+
1262
+ // §3.2 spaceship_expression — `<=>`, EQUALITY level. Three-way
1263
+ // comparison; no `operator` field since there is only one form.
1264
+ spaceship_expression: $ => prec.left(PREC.EQUALITY, seq(
1265
+ field('left', $._expression),
1266
+ '<=>',
1267
+ field('right', $._expression),
1268
+ )),
1269
+
1270
+ // §3.2 regex_find_expression — `=~`, EQUALITY level. Matches a
1271
+ // pattern at any position. Fields are `subject` / `pattern` so
1272
+ // downstream tools can tell the regex-bearing operand from the
1273
+ // input expression.
1274
+ regex_find_expression: $ => prec.left(PREC.EQUALITY, seq(
1275
+ field('subject', $._expression),
1276
+ '=~',
1277
+ field('pattern', $._expression),
1278
+ )),
1279
+
1280
+ // §3.2 regex_match_expression — `==~`, EQUALITY level. Anchored
1281
+ // whole-string match. Same field shape as regex_find_expression.
1282
+ regex_match_expression: $ => prec.left(PREC.EQUALITY, seq(
1283
+ field('subject', $._expression),
1284
+ '==~',
1285
+ field('pattern', $._expression),
1286
+ )),
1287
+
1288
+ // §3.2 — single binary_expression node with an `operator` field
1289
+ // for every Java-shaped binary operator (per §3.2 table row 1).
1290
+ // Apache groups arithmetic operators across two precedence
1291
+ // levels (MUL: * / %, ADD: + -); we keep them on one rule with
1292
+ // distinct precedence numbers so the *node kind* stays uniform
1293
+ // while the precedence ladder matches Apache exactly.
1294
+ //
1295
+ // Currently covers ADDITIVE and MULTIPLICATIVE. Shift/range,
1296
+ // relational, equality, bitwise, and logical levels join this
1297
+ // rule in later iterations — each is a single line addition.
1298
+ binary_expression: $ => {
1299
+ /* eslint-disable no-multi-spaces --
1300
+ * Aligned columns make the precedence ladder readable.
1301
+ */
1302
+ const ops = [
1303
+ // ADDITIVE — §3.1 level 5
1304
+ ['+', PREC.ADDITIVE],
1305
+ ['-', PREC.ADDITIVE],
1306
+ // MULTIPLICATIVE — §3.1 level 4
1307
+ ['*', PREC.MULTIPLICATIVE],
1308
+ ['/', PREC.MULTIPLICATIVE],
1309
+ ['%', PREC.MULTIPLICATIVE],
1310
+ // SHIFT — §3.1 level 6. Ranges (`..`, `..<`, etc.) share
1311
+ // this precedence level but use distinct node kinds, so
1312
+ // they live in their own rule (lands later).
1313
+ ['<<', PREC.SHIFT_OR_RANGE],
1314
+ ['>>', PREC.SHIFT_OR_RANGE],
1315
+ ['>>>', PREC.SHIFT_OR_RANGE],
1316
+ // RELATIONAL — §3.1 level 7. `in`, `!in`, `instanceof`,
1317
+ // `!instanceof`, and `as` share this level but emit
1318
+ // distinct node kinds (`membership_expression`,
1319
+ // `instanceof_expression`, `cast_expression`), so they
1320
+ // land in their own rules later.
1321
+ ['<', PREC.RELATIONAL],
1322
+ ['<=', PREC.RELATIONAL],
1323
+ ['>', PREC.RELATIONAL],
1324
+ ['>=', PREC.RELATIONAL],
1325
+ // EQUALITY — §3.1 level 8. `===`, `!==`, `<=>`, `=~`,
1326
+ // `==~` share this level but emit distinct node kinds.
1327
+ ['==', PREC.EQUALITY],
1328
+ ['!=', PREC.EQUALITY],
1329
+ // BITWISE — §3.1 levels 9, 10, 11 (each tighter than the
1330
+ // next: & < ^ < | by precedence).
1331
+ ['&', PREC.BITWISE_AND],
1332
+ ['^', PREC.BITWISE_XOR],
1333
+ ['|', PREC.BITWISE_OR],
1334
+ // LOGICAL — §3.1 levels 12, 13 (&& tighter than ||).
1335
+ ['&&', PREC.LOGICAL_AND],
1336
+ ['||', PREC.LOGICAL_OR],
1337
+ ];
1338
+ /* eslint-enable no-multi-spaces */
1339
+ return choice(...ops.map(([op, p]) => prec.left(p, seq(
1340
+ field('left', $._expression),
1341
+ field('operator', op),
1342
+ field('right', $._expression),
1343
+ ))));
1344
+ },
1345
+
1346
+ // §5.8 — closure expression. `{ -> }` is the explicit no-parameter
1347
+ // form; `{ a, b -> body }` has identifier parameters;
1348
+ // `{ String a, int b -> body }` has typed parameters; `{ body }`
1349
+ // has no `->` and an implicit `it` parameter (we do NOT emit a
1350
+ // synthetic `it` per §5.8). Default values and varargs land later.
1351
+ closure: $ => seq(
1352
+ '{',
1353
+ optional($.closure_parameters),
1354
+ repeat($._statement),
1355
+ '}',
1356
+ ),
1357
+
1358
+ closure_parameters: $ => seq(
1359
+ optional(seq(
1360
+ $.closure_parameter,
1361
+ repeat(seq(',', $.closure_parameter)),
1362
+ )),
1363
+ '->',
1364
+ ),
1365
+
1366
+ closure_parameter: $ => seq(
1367
+ optional(field('type', $._type)),
1368
+ field('name', $.identifier),
1369
+ ),
1370
+
1371
+ // §3.2 PRIMARY / §5.13 — list literal with trailing-comma support.
1372
+ // Empty list is `[]`; the empty map literal is the distinct `[:]`
1373
+ // form (see map_literal). Tree-sitter splits the parse between
1374
+ // list_literal and map_literal until a `:` or `]` disambiguates;
1375
+ // the conflict declaration above keeps both alternatives alive.
1376
+ list_literal: $ => seq(
1377
+ '[',
1378
+ optional(seq(
1379
+ $._expression,
1380
+ repeat(seq(',', $._expression)),
1381
+ optional(','),
1382
+ )),
1383
+ ']',
1384
+ ),
1385
+
1386
+ // §3.2 PRIMARY / §5.3 — map literal. `[:]` is the empty map;
1387
+ // anything else is one-or-more `key: value` entries. The
1388
+ // distinction between map_entry and a labelled statement is
1389
+ // contextual — labels are statement-start-only (see §5.3 and
1390
+ // the LABEL_COLON scanner branch), so inside `[ … ]` a `:` after
1391
+ // an identifier is always a map key separator.
1392
+ map_literal: $ => {
1393
+ const entry = choice($.map_entry, $.spread_map_entry);
1394
+ return seq(
1395
+ '[',
1396
+ choice(
1397
+ ':',
1398
+ seq(
1399
+ entry,
1400
+ repeat(seq(',', entry)),
1401
+ optional(','),
1402
+ ),
1403
+ ),
1404
+ ']',
1405
+ );
1406
+ },
1407
+
1408
+ map_entry: $ => seq(
1409
+ field('key', $._expression),
1410
+ ':',
1411
+ field('value', $._expression),
1412
+ ),
1413
+
1414
+ // §5.11 — `*: expr` only inside a map literal. `*:` is a single
1415
+ // anonymous token (longest-match keeps it intact); a bare `*`
1416
+ // followed by a `:` colon outside this context would have to be
1417
+ // syntactically impossible by the surrounding rules.
1418
+ spread_map_entry: $ => seq(
1419
+ '*:',
1420
+ field('value', $._expression),
1421
+ ),
1422
+
1423
+ // §3.2 PRIMARY — `( expr )`. Distinct from `parenthesized_type_cast`
1424
+ // (§5.15), which lands once we have `_type`. The parser disambiguates
1425
+ // by what's inside the parens.
1426
+ parenthesized_expression: $ => seq('(', $._expression, ')'),
1427
+
1428
+ // §3.2 — single `unary_expression` node kind but two precedence
1429
+ // alternatives, matching Apache §3.1:
1430
+ // UNARY_NOT (`!` `~`) — level 16, tighter than POWER (15),
1431
+ // so `~2 ** 3` parses as `(~2) ** 3`.
1432
+ // UNARY (`+` `-`) — level 14, looser than POWER (15),
1433
+ // so `-2 ** 3` parses as `-(2 ** 3)`.
1434
+ // Both alternatives produce the same node kind, so downstream
1435
+ // tools see one consistent shape and discriminate by operator.
1436
+ unary_expression: $ => choice(
1437
+ prec.right(PREC.UNARY_NOT, seq(
1438
+ field('operator', choice('!', '~')),
1439
+ field('operand', $._expression),
1440
+ )),
1441
+ prec.right(PREC.UNARY, seq(
1442
+ field('operator', choice('+', '-')),
1443
+ field('operand', $._expression),
1444
+ )),
1445
+ ),
1446
+
1447
+ // §3.2 PRIMARY — boolean and null are keywords in Groovy. The
1448
+ // `word: $.identifier` directive above lets tree-sitter resolve
1449
+ // the keyword-vs-identifier ambiguity automatically: a literal
1450
+ // 'true' / 'false' / 'null' that matches the identifier regex
1451
+ // is reclassified as the keyword token when one of these rules
1452
+ // is the only legal next reduction.
1453
+ boolean_literal: _ => choice('true', 'false'),
1454
+
1455
+ null_literal: _ => 'null',
1456
+
1457
+ // §5.7 — string flavours. Single and triple-single never
1458
+ // interpolate. Double-quoted and triple-double interpolate
1459
+ // `$identifier(.identifier)*` and `${expression}` as structured
1460
+ // children, so consumers can walk into the GString's expression
1461
+ // segments. Slashy bodies and dollar-slashy bodies remain flat
1462
+ // tokens (their internal `$` is rare in practice and the
1463
+ // scanner cannot interrupt a slashy mid-body without per-flavor
1464
+ // state).
1465
+ string_literal: $ => choice(
1466
+ $._single_quoted_string,
1467
+ $._triple_single_quoted_string,
1468
+ $._double_quoted_string,
1469
+ $._triple_double_quoted_string,
1470
+ $._dollar_slashy_string,
1471
+ $._slashy_string,
1472
+ ),
1473
+
1474
+ // §5.7 — slashy regex `/.../`. The opening `/` is emitted by
1475
+ // the external scanner (after the context guard from §5.4 / §6.2
1476
+ // and a lookahead that confirms a closing `/` exists on the same
1477
+ // line). The body is parsed as `gstringPart` so `$identifier` and
1478
+ // `${expr}` segments expose the same structured children as
1479
+ // double-quoted GStrings. The closing `/` is `token.immediate`
1480
+ // so it cannot skip whitespace between the body and the closer.
1481
+ _slashy_string: $ => seq(
1482
+ $._slashy_string_start,
1483
+ repeat(gstringPart($, $._slashy_text)),
1484
+ token.immediate('/'),
1485
+ ),
1486
+
1487
+ _slashy_text: _ => token.immediate(repeat1(choice(
1488
+ /[^\/\\$\n\r]/,
1489
+ /\\[\s\S]/,
1490
+ ))),
1491
+
1492
+ _single_quoted_string: _ => token(seq(
1493
+ '\'',
1494
+ repeat(choice(
1495
+ /[^'\\\n\r]/,
1496
+ /\\[\s\S]/,
1497
+ )),
1498
+ '\'',
1499
+ )),
1500
+
1501
+ _triple_single_quoted_string: _ => token(seq(
1502
+ '\'\'\'',
1503
+ repeat(choice(
1504
+ /[^'\\]/,
1505
+ /\\[\s\S]/,
1506
+ /'[^']/,
1507
+ /''[^']/,
1508
+ )),
1509
+ '\'\'\'',
1510
+ )),
1511
+
1512
+ // §5.7 / §6.3 — double-quoted GString. The opening `"` is
1513
+ // tokenised separately from the body so that `$identifier` and
1514
+ // `${expr}` segments parse as proper child nodes. The text
1515
+ // fragment stops before any `$`, so `$identifier` and `${expr}`
1516
+ // get a chance to match; literal `$` (followed by `"` or a
1517
+ // digit etc.) is recovered as `$` plus the fallback shape.
1518
+ _double_quoted_string: $ => seq(
1519
+ '"',
1520
+ repeat(gstringPart($, $._gstring_text)),
1521
+ token.immediate('"'),
1522
+ ),
1523
+
1524
+ _gstring_text: _ => token.immediate(repeat1(choice(
1525
+ /[^"\\$\n\r]/,
1526
+ /\\[\s\S]/,
1527
+ ))),
1528
+
1529
+ // Bare `$` inside a GString body that does not start a valid
1530
+ // interpolation (`$"`, `$5`, `$$`, `$ `) is matched as a one-
1531
+ // char literal fragment. This rule must have lower precedence
1532
+ // than `_gstring_dollar_path` so `$name` still wins.
1533
+ _gstring_literal_dollar: _ => token.immediate(prec(-1, /\$/)),
1534
+
1535
+ // §5.7 — triple-double GString. Like `_double_quoted_string`
1536
+ // but newlines and embedded `"` / `""` are part of the body.
1537
+ _triple_double_quoted_string: $ => seq(
1538
+ '"""',
1539
+ repeat(gstringPart($, $._triple_gstring_text)),
1540
+ token.immediate('"""'),
1541
+ ),
1542
+
1543
+ _triple_gstring_text: _ => token.immediate(repeat1(choice(
1544
+ /[^"\\$]/,
1545
+ /\\[\s\S]/,
1546
+ /"[^"]/,
1547
+ /""[^"]/,
1548
+ ))),
1549
+
1550
+ // §5.7 — `$identifier(.identifier)*` interpolation. The whole
1551
+ // `$identifier...` shape is matched as a single immediate token
1552
+ // so the `$` only consumes when followed by an identifier start;
1553
+ // `_gstring_literal_dollar` handles the lone-`$` fallback above.
1554
+ gstring_dollar_interpolation: $ => seq(
1555
+ field('value', alias(
1556
+ $._gstring_dollar_path,
1557
+ $.identifier,
1558
+ )),
1559
+ ),
1560
+
1561
+ _gstring_dollar_path: _ => token.immediate(
1562
+ /\$[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*/,
1563
+ ),
1564
+
1565
+ // §5.7 / §6.3 — `${expression}` interpolation.
1566
+ gstring_brace_interpolation: $ => seq(
1567
+ token.immediate('${'),
1568
+ field('value', $._expression),
1569
+ '}',
1570
+ ),
1571
+
1572
+ // §5.7 — `$/...$/` dollar-slashy strings. The body is parsed
1573
+ // through `gstringPart` so `$identifier` and `${expr}` segments
1574
+ // expose structured children. `_dollar_slashy_text` consumes
1575
+ // every literal-text alternative: `/X` where X is not `$`,
1576
+ // `$$` (literal `$`), `$/` (literal `/`), and any other
1577
+ // non-`/` non-`$` char. The closer `/$` is `token.immediate`
1578
+ // so it always wins over text when `/` is followed by `$`.
1579
+ _dollar_slashy_string: $ => seq(
1580
+ '$/',
1581
+ repeat(gstringPart($, $._dollar_slashy_text)),
1582
+ token.immediate('/$'),
1583
+ ),
1584
+
1585
+ _dollar_slashy_text: _ => token.immediate(repeat1(choice(
1586
+ /[^/$]/,
1587
+ /\/[^$]/,
1588
+ /\$\$/,
1589
+ /\$\//,
1590
+ ))),
1591
+
1592
+ // §5.1 — hex (0x…), binary (0b…), decimal int / float / scientific,
1593
+ // each with optional type suffix from { G g L l I i D d F f }.
1594
+ // Underscores between digits are accepted permissively (the spec
1595
+ // forbids leading/trailing `_`, but that is a semantic check,
1596
+ // not a grammar one — see §1.2).
1597
+ number_literal: _ => token(choice(
1598
+ /0[xX][0-9a-fA-F_]+[GgLlIi]?/,
1599
+ /0[bB][01_]+[GgLlIi]?/,
1600
+ /[0-9][0-9_]*\.[0-9][0-9_]*([eE][+-]?[0-9][0-9_]*)?[GgFfDd]?/,
1601
+ /[0-9][0-9_]*[eE][+-]?[0-9][0-9_]*[GgFfDd]?/,
1602
+ /[0-9][0-9_]*[GgLlIiDdFf]?/,
1603
+ )),
1604
+
1605
+ identifier: _ => /[A-Za-z_$][A-Za-z0-9_$]*/,
1606
+ },
1607
+ });