tree-sitter-ucode 0.2.0 → 0.4.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.
Files changed (45) hide show
  1. package/README.md +49 -58
  2. package/grammar.js +214 -28
  3. package/markup/grammar.js +1057 -0
  4. package/markup/queries/folds.scm +20 -0
  5. package/markup/queries/highlights.scm +38 -0
  6. package/markup/queries/indents.scm +51 -0
  7. package/markup/queries/injections.scm +40 -0
  8. package/markup/queries/locals.scm +107 -0
  9. package/markup/queries/tags.scm +65 -0
  10. package/markup/queries/textobjects.scm +56 -0
  11. package/markup/src/grammar.json +5786 -0
  12. package/markup/src/node-types.json +3211 -0
  13. package/markup/src/parser.c +134461 -0
  14. package/markup/src/scanner.c +22 -0
  15. package/package.json +8 -7
  16. package/prebuilds/darwin-arm64/tree-sitter-ucode.node +0 -0
  17. package/prebuilds/linux-arm64/tree-sitter-ucode.node +0 -0
  18. package/prebuilds/linux-x64/tree-sitter-ucode.node +0 -0
  19. package/prebuilds/win32-x64/tree-sitter-ucode.node +0 -0
  20. package/queries/folds.scm +38 -0
  21. package/queries/highlights.scm +6 -0
  22. package/queries/indents.scm +63 -0
  23. package/queries/locals.scm +1 -0
  24. package/queries/textobjects.scm +84 -0
  25. package/scripts/generate-markup-grammar.js +93 -0
  26. package/src/grammar.json +1069 -226
  27. package/src/node-types.json +662 -8
  28. package/src/parser.c +106401 -25117
  29. package/src/scanner.c +16 -193
  30. package/src/scanner_impl.h +494 -0
  31. package/tree-sitter-ucode.wasm +0 -0
  32. package/tree-sitter-ucode_markup.wasm +0 -0
  33. package/tree-sitter.json +24 -12
  34. package/tmpl/grammar.js +0 -67
  35. package/tmpl/queries/highlights.scm +0 -23
  36. package/tmpl/queries/injections.scm +0 -8
  37. package/tmpl/queries/locals.scm +0 -3
  38. package/tmpl/src/grammar.json +0 -243
  39. package/tmpl/src/node-types.json +0 -230
  40. package/tmpl/src/parser.c +0 -707
  41. package/tmpl/src/scanner.c +0 -169
  42. package/tree-sitter-ucode_tmpl.wasm +0 -0
  43. /package/{tmpl → markup}/src/tree_sitter/alloc.h +0 -0
  44. /package/{tmpl → markup}/src/tree_sitter/array.h +0 -0
  45. /package/{tmpl → markup}/src/tree_sitter/parser.h +0 -0
@@ -0,0 +1,1057 @@
1
+ // GENERATED by scripts/generate-markup-grammar.js — do not edit by hand.
2
+ /**
3
+ * @file Ucode grammar for tree-sitter
4
+ * @license MIT
5
+ *
6
+ * Based on tree-sitter-javascript (MIT, Max Brunsfeld, Amaan Qureshi).
7
+ * Ucode is an ECMAScript-like language for OpenWrt system scripting.
8
+ * See README.md for a full list of syntax differences from JavaScript.
9
+ */
10
+
11
+ /// <reference types="tree-sitter-cli/dsl" />
12
+ // @ts-check
13
+
14
+ module.exports = grammar({
15
+ name: 'ucode_markup',
16
+
17
+ externals: $ => [
18
+ $._automatic_semicolon, // 0
19
+ $._template_chars, // 1
20
+ $._ternary_qmark, // 2
21
+ $.raw_text, // 3 literal text outside tags
22
+ $.statement_tag_open, // 4 {%
23
+ $.statement_tag_trim_open, // 5 {%-
24
+ $.statement_tag_lstrip_open, // 6 {%+
25
+ $.statement_tag_close, // 7 %}
26
+ $.statement_tag_trim_close, // 8 -%}
27
+ $.expression_tag_open, // 9 {{
28
+ $.expression_tag_trim_open, // 10 {{-
29
+ $.expression_tag_close, // 11 }}
30
+ $.expression_tag_trim_close, // 12 -}}
31
+ ],
32
+
33
+ extras: $ => [
34
+ $.comment,
35
+ /[\s\p{Zs}\uFEFF\u2028\u2029\u2060\u200B]/,
36
+ ],
37
+
38
+ reserved: {
39
+ global: $ => [
40
+ 'break',
41
+ 'case',
42
+ 'catch',
43
+ 'const',
44
+ 'continue',
45
+ 'default',
46
+ 'delete',
47
+ 'else',
48
+ 'elif',
49
+ 'endif',
50
+ 'endfor',
51
+ 'endwhile',
52
+ 'endfunction',
53
+ 'export',
54
+ 'false',
55
+ 'for',
56
+ 'function',
57
+ 'if',
58
+ 'import',
59
+ 'in',
60
+ 'let',
61
+ 'null',
62
+ 'return',
63
+ 'switch',
64
+ 'this',
65
+ 'true',
66
+ 'try',
67
+ 'while',
68
+ ],
69
+ properties: _ => [],
70
+ },
71
+
72
+ supertypes: $ => [
73
+ $.statement,
74
+ $.declaration,
75
+ $.expression,
76
+ $.primary_expression,
77
+ ],
78
+
79
+ inline: $ => [
80
+ $._call_signature,
81
+ $._formal_parameter,
82
+ $._expressions,
83
+ $._semicolon,
84
+ $._identifier,
85
+ $._reserved_identifier,
86
+ $._lhs_expression,
87
+ $._markup_node,
88
+ $._if_markup_node,
89
+ $._stmt_open,
90
+ $._stmt_close,
91
+ $._expr_open,
92
+ $._expr_close,
93
+ ],
94
+
95
+ precedences: $ => [
96
+ [
97
+ 'member',
98
+ 'call',
99
+ $.update_expression,
100
+ 'unary_void',
101
+ 'binary_exp',
102
+ 'binary_times',
103
+ 'binary_plus',
104
+ 'binary_shift',
105
+ 'binary_relation',
106
+ 'binary_equality',
107
+ 'bitwise_and',
108
+ 'bitwise_xor',
109
+ 'bitwise_or',
110
+ 'logical_and',
111
+ 'logical_or',
112
+ 'ternary',
113
+ $.sequence_expression,
114
+ $.arrow_function,
115
+ ],
116
+ ['assign', $.primary_expression],
117
+ ['member', 'call', $.expression],
118
+ ['declaration', 'literal'],
119
+ [$.primary_expression, $.statement_block, 'object'],
120
+ [$.export_statement, $.primary_expression],
121
+ [$.lexical_declaration, $.primary_expression],
122
+ ],
123
+
124
+ conflicts: $ => [
125
+ [$.primary_expression, $.formal_parameters],
126
+ [$.primary_expression, $._for_header],
127
+ [$.variable_declarator, $._for_header],
128
+ [$.assignment_expression, $.pattern],
129
+ [$.labeled_statement, $._property_name],
130
+ ],
131
+
132
+ word: $ => $.identifier,
133
+
134
+ rules: {
135
+ markup: $ => seq(
136
+ optional($.hash_bang_line),
137
+ repeat($._markup_node),
138
+ ),
139
+
140
+ program: $ => seq(
141
+ optional($.hash_bang_line),
142
+ repeat($.statement),
143
+ ),
144
+
145
+ //
146
+ // Markup entry point
147
+ //
148
+ // A .uc.tmpl document is a flat sequence of markup nodes: raw
149
+ // text, comment tags, expression tags, statement tags, and the
150
+ // alt-syntax constructs that span multiple tags.
151
+ //
152
+ // Statement tags that contain only simple (non-spanning) code are
153
+ // wrapped in `statement_tag`. Alt-syntax constructs that span tag
154
+ // boundaries appear directly as markup nodes with explicit tag-open /
155
+ // tag-close fields, giving a pristine tree with no empty-statement noise.
156
+ //
157
+ _markup_node: $ => choice(
158
+ $.raw_text,
159
+ $.expression_tag,
160
+ $.comment_tag,
161
+ $.statement_tag,
162
+ // Alt-syntax constructs that span tag boundaries:
163
+ $.if_alt_statement,
164
+ $.for_alt_statement,
165
+ $.for_in_alt_statement,
166
+ $.while_alt_statement,
167
+ ),
168
+
169
+ // -----------------------------------------------------------------------
170
+ // Simple tag wrappers
171
+ // -----------------------------------------------------------------------
172
+
173
+ // A statement_tag wraps non-spanning code: {% stmt; stmt; %}
174
+ statement_tag: $ => seq(
175
+ field('open', $._stmt_open),
176
+ repeat($.statement),
177
+ field('close', $._stmt_close),
178
+ ),
179
+
180
+ // {{ expr }} or {{- expr -}}
181
+ expression_tag: $ => seq(
182
+ field('open', $._expr_open),
183
+ optional($._expressions),
184
+ field('close', $._expr_close),
185
+ ),
186
+
187
+ // {# ... #} with optional whitespace-stripping markers
188
+ comment_tag: $ => seq(
189
+ field('open', choice('{#-', '{#')),
190
+ optional(field('content', $.comment_content)),
191
+ field('close', choice('-#}', '#}')),
192
+ ),
193
+
194
+ // Matches everything up to but not including #} or -#}
195
+ comment_content: _ => /([^#-]|#[^}]|-(?:[^#]|#[^}]))+/,
196
+
197
+ hash_bang_line: _ => /#!.*/,
198
+
199
+ //
200
+ // Export declarations
201
+ // Ucode export is simpler than JS: no `from`, no namespace re-exports.
202
+ // `export default` always takes an expression (requires trailing semicolon).
203
+ // `export function name() {}` does NOT require a semicolon.
204
+ //
205
+
206
+ export_statement: $ => choice(
207
+ seq(
208
+ 'export',
209
+ $.export_clause,
210
+ $._semicolon,
211
+ ),
212
+ seq(
213
+ 'export',
214
+ choice(
215
+ field('declaration', $.declaration),
216
+ seq(
217
+ 'default',
218
+ seq(
219
+ field('value', $.expression),
220
+ ';',
221
+ ),
222
+ ),
223
+ ),
224
+ ),
225
+ ),
226
+
227
+ export_clause: $ => seq(
228
+ '{',
229
+ commaSep($.export_specifier),
230
+ optional(','),
231
+ '}',
232
+ ),
233
+
234
+ export_specifier: $ => seq(
235
+ field('name', $._module_export_name),
236
+ optional(seq(
237
+ 'as',
238
+ field('alias', $._module_export_name),
239
+ )),
240
+ ),
241
+
242
+ _module_export_name: $ => choice(
243
+ $.identifier,
244
+ $.string,
245
+ 'default',
246
+ ),
247
+
248
+ declaration: $ => choice(
249
+ $.function_declaration,
250
+ $.lexical_declaration,
251
+ ),
252
+
253
+ //
254
+ // Import declarations
255
+ // Ucode keeps full ES module static import syntax.
256
+ //
257
+
258
+ import_statement: $ => seq(
259
+ 'import',
260
+ choice(
261
+ seq($.import_clause, 'from', field('source', $.string)),
262
+ field('source', $.string),
263
+ ),
264
+ $._semicolon,
265
+ ),
266
+
267
+ import_clause: $ => choice(
268
+ $.namespace_import,
269
+ $.named_imports,
270
+ seq(
271
+ $.identifier,
272
+ optional(seq(
273
+ ',',
274
+ choice(
275
+ $.namespace_import,
276
+ $.named_imports,
277
+ ),
278
+ )),
279
+ ),
280
+ ),
281
+
282
+ namespace_import: $ => seq('*', 'as', $.identifier),
283
+
284
+ named_imports: $ => seq(
285
+ '{',
286
+ commaSep($.import_specifier),
287
+ optional(','),
288
+ '}',
289
+ ),
290
+
291
+ import_specifier: $ => choice(
292
+ field('name', $.identifier),
293
+ seq(
294
+ field('name', $._module_export_name),
295
+ 'as',
296
+ field('alias', $.identifier),
297
+ ),
298
+ ),
299
+
300
+ //
301
+ // Statements
302
+ //
303
+
304
+ statement: $ => choice(
305
+ $.export_statement,
306
+ $.import_statement,
307
+ $.expression_statement,
308
+ $.declaration,
309
+ $.statement_block,
310
+
311
+ $.if_statement,
312
+ $.if_alt_statement,
313
+ $.switch_statement,
314
+ $.for_statement,
315
+ $.for_alt_statement,
316
+ $.for_in_statement,
317
+ $.for_in_alt_statement,
318
+ $.while_statement,
319
+ $.while_alt_statement,
320
+ $.try_statement,
321
+
322
+ $.break_statement,
323
+ $.continue_statement,
324
+ $.return_statement,
325
+ $.empty_statement,
326
+ $.labeled_statement,
327
+ ),
328
+
329
+ expression_statement: $ => seq(
330
+ $._expressions,
331
+ $._semicolon,
332
+ ),
333
+
334
+ lexical_declaration: $ => seq(
335
+ field('kind', choice('let', 'const')),
336
+ commaSep1($.variable_declarator),
337
+ $._semicolon,
338
+ ),
339
+
340
+ variable_declarator: $ => seq(
341
+ field('name', $.identifier),
342
+ optional($._initializer),
343
+ ),
344
+
345
+ statement_block: $ => prec.right(seq(
346
+ '{',
347
+ repeat($.statement),
348
+ '}',
349
+ optional($._automatic_semicolon),
350
+ )),
351
+
352
+ else_clause: $ => seq('else', $.statement),
353
+
354
+ // Standard brace-based if
355
+ if_statement: $ => prec.right(seq(
356
+ 'if',
357
+ field('condition', $.parenthesized_expression),
358
+ field('consequence', $.statement),
359
+ optional(field('alternative', $.else_clause)),
360
+ )),
361
+
362
+ // Alternative colon/endif syntax — two forms:
363
+ // code form: if (cond): stmts … endif (used in program / statement_tag)
364
+ // markup form: {% if (cond): %} … {% endif %} (spans tag boundaries in markup)
365
+ //
366
+ // The markup form uses a flat content repeat (_if_markup_node) rather than
367
+ // nested elif/else bodies. elif_clause_tag and else_alt_clause_tag are pure
368
+ // header tags that appear as regular items inside that repeat. This avoids
369
+ // the shift/reduce conflict that arises when a nested repeat($._markup_node)
370
+ // can't decide whether statement_tag_open starts another body node or the
371
+ // enclosing end tag.
372
+ if_alt_statement: $ => choice(
373
+ seq(
374
+ 'if',
375
+ field('condition', $.parenthesized_expression),
376
+ ':',
377
+ field('body', repeat($.statement)),
378
+ repeat(field('elif_clause', $.elif_clause)),
379
+ optional(field('else_body', $.else_alt_clause)),
380
+ 'endif',
381
+ ),
382
+ seq(
383
+ field('open', $._stmt_open),
384
+ 'if',
385
+ field('condition', $.parenthesized_expression),
386
+ ':',
387
+ repeat($.statement),
388
+ field('close', $._stmt_close),
389
+ repeat($._if_markup_node),
390
+ field('end_open', $._stmt_open),
391
+ 'endif',
392
+ field('end_close', $._stmt_close),
393
+ ),
394
+ ),
395
+
396
+ // Flat content node for if_alt_statement markup bodies.
397
+ // elif_clause_tag and else_alt_clause_tag are plain header tags here;
398
+ // the actual body content between them is expressed as sibling nodes.
399
+ _if_markup_node: $ => choice(
400
+ $._markup_node,
401
+ $.elif_clause_tag,
402
+ $.else_alt_clause_tag,
403
+ ),
404
+
405
+ // Inline wrappers for tag delimiter tokens.
406
+ // Each groups all variants (plain / trim / lstrip) so grammar rules stay
407
+ // concise while still surfacing distinct node types for highlight queries.
408
+ _stmt_open: $ => choice($.statement_tag_open, $.statement_tag_trim_open, $.statement_tag_lstrip_open),
409
+ _stmt_close: $ => choice($.statement_tag_close, $.statement_tag_trim_close),
410
+ _expr_open: $ => choice($.expression_tag_open, $.expression_tag_trim_open),
411
+ _expr_close: $ => choice($.expression_tag_close, $.expression_tag_trim_close),
412
+
413
+ elif_clause: $ => seq(
414
+ 'elif',
415
+ field('condition', $.parenthesized_expression),
416
+ ':',
417
+ field('body', repeat($.statement)),
418
+ ),
419
+
420
+ // Markup form of elif: just the header tag; body is sibling _if_markup_nodes
421
+ elif_clause_tag: $ => seq(
422
+ field('open', $._stmt_open),
423
+ 'elif',
424
+ field('condition', $.parenthesized_expression),
425
+ ':',
426
+ field('close', $._stmt_close),
427
+ ),
428
+
429
+ else_alt_clause: $ => seq(
430
+ 'else',
431
+ field('body', repeat($.statement)),
432
+ ),
433
+
434
+ // Markup form of else: just the header tag; body is sibling _if_markup_nodes
435
+ else_alt_clause_tag: $ => seq(
436
+ field('open', $._stmt_open),
437
+ 'else',
438
+ field('close', $._stmt_close),
439
+ ),
440
+
441
+ switch_statement: $ => seq(
442
+ 'switch',
443
+ field('value', $.parenthesized_expression),
444
+ field('body', $.switch_body),
445
+ ),
446
+
447
+ for_statement: $ => seq(
448
+ forHeader($),
449
+ field('body', $.statement),
450
+ ),
451
+
452
+ for_alt_statement: $ => choice(
453
+ seq(
454
+ forHeader($),
455
+ ':',
456
+ field('body', repeat($.statement)),
457
+ 'endfor',
458
+ ),
459
+ seq(
460
+ field('open', $._stmt_open),
461
+ forHeader($),
462
+ ':',
463
+ repeat($.statement),
464
+ field('close', $._stmt_close),
465
+ field('body', repeat($._markup_node)),
466
+ field('end_open', $._stmt_open),
467
+ 'endfor',
468
+ field('end_close', $._stmt_close),
469
+ ),
470
+ ),
471
+
472
+ for_in_statement: $ => seq(
473
+ 'for',
474
+ $._for_header,
475
+ field('body', $.statement),
476
+ ),
477
+
478
+ for_in_alt_statement: $ => choice(
479
+ seq(
480
+ 'for',
481
+ $._for_header,
482
+ ':',
483
+ field('body', repeat($.statement)),
484
+ 'endfor',
485
+ ),
486
+ seq(
487
+ field('open', $._stmt_open),
488
+ 'for',
489
+ $._for_header,
490
+ ':',
491
+ repeat($.statement),
492
+ field('close', $._stmt_close),
493
+ field('body', repeat($._markup_node)),
494
+ field('end_open', $._stmt_open),
495
+ 'endfor',
496
+ field('end_close', $._stmt_close),
497
+ ),
498
+ // Compact double-nested form: {% for (outer): for (inner): %} body {% endfor; endfor %}
499
+ // Both iterables contribute `right` fields; both loop vars contribute `left` fields.
500
+ seq(
501
+ field('open', $._stmt_open),
502
+ 'for',
503
+ $._for_header,
504
+ ':',
505
+ 'for',
506
+ $._for_header,
507
+ ':',
508
+ field('close', $._stmt_close),
509
+ field('body', repeat($._markup_node)),
510
+ field('end_open', $._stmt_open),
511
+ 'endfor', ';', 'endfor',
512
+ field('end_close', $._stmt_close),
513
+ ),
514
+ ),
515
+
516
+ // Supports both `for (k in obj)` and `for (k, v in obj)` (ucode two-variable form)
517
+ _for_header: $ => seq(
518
+ '(',
519
+ choice(
520
+ seq(
521
+ field('kind', choice('let', 'const')),
522
+ field('left', $.identifier),
523
+ optional(seq(',', field('value', $.identifier))),
524
+ ),
525
+ seq(
526
+ field('left', $._lhs_expression),
527
+ optional(seq(',', field('value', $.identifier))),
528
+ ),
529
+ ),
530
+ 'in',
531
+ field('right', $._expressions),
532
+ ')',
533
+ ),
534
+
535
+ while_statement: $ => seq(
536
+ 'while',
537
+ field('condition', $.parenthesized_expression),
538
+ field('body', $.statement),
539
+ ),
540
+
541
+ while_alt_statement: $ => choice(
542
+ seq(
543
+ 'while',
544
+ field('condition', $.parenthesized_expression),
545
+ ':',
546
+ field('body', repeat($.statement)),
547
+ 'endwhile',
548
+ ),
549
+ seq(
550
+ field('open', $._stmt_open),
551
+ 'while',
552
+ field('condition', $.parenthesized_expression),
553
+ ':',
554
+ repeat($.statement),
555
+ field('close', $._stmt_close),
556
+ field('body', repeat($._markup_node)),
557
+ field('end_open', $._stmt_open),
558
+ 'endwhile',
559
+ field('end_close', $._stmt_close),
560
+ ),
561
+ ),
562
+
563
+ try_statement: $ => seq(
564
+ 'try',
565
+ field('body', $.statement_block),
566
+ optional(field('handler', $.catch_clause)),
567
+ ),
568
+
569
+ break_statement: $ => seq(
570
+ 'break',
571
+ field('label', optional(alias($.identifier, $.statement_identifier))),
572
+ $._semicolon,
573
+ ),
574
+
575
+ continue_statement: $ => seq(
576
+ 'continue',
577
+ field('label', optional(alias($.identifier, $.statement_identifier))),
578
+ $._semicolon,
579
+ ),
580
+
581
+ return_statement: $ => seq(
582
+ 'return',
583
+ optional($._expressions),
584
+ $._semicolon,
585
+ ),
586
+
587
+ empty_statement: _ => ';',
588
+
589
+ labeled_statement: $ => prec.dynamic(-1, seq(
590
+ field('label', alias(choice($.identifier, $._reserved_identifier), $.statement_identifier)),
591
+ ':',
592
+ field('body', $.statement),
593
+ )),
594
+
595
+ //
596
+ // Statement components
597
+ //
598
+
599
+ switch_body: $ => seq(
600
+ '{',
601
+ repeat(choice($.switch_case, $.switch_default)),
602
+ '}',
603
+ ),
604
+
605
+ switch_case: $ => seq(
606
+ 'case',
607
+ field('value', $._expressions),
608
+ ':',
609
+ field('body', repeat($.statement)),
610
+ ),
611
+
612
+ switch_default: $ => seq(
613
+ 'default',
614
+ ':',
615
+ field('body', repeat($.statement)),
616
+ ),
617
+
618
+ catch_clause: $ => seq(
619
+ 'catch',
620
+ optional(seq('(', field('parameter', $.identifier), ')')),
621
+ field('body', $.statement_block),
622
+ ),
623
+
624
+ parenthesized_expression: $ => seq(
625
+ '(',
626
+ $._expressions,
627
+ ')',
628
+ ),
629
+
630
+ //
631
+ // Expressions
632
+ //
633
+
634
+ _expressions: $ => choice(
635
+ $.expression,
636
+ $.sequence_expression,
637
+ ),
638
+
639
+ expression: $ => choice(
640
+ $.primary_expression,
641
+ $.assignment_expression,
642
+ $.augmented_assignment_expression,
643
+ $.unary_expression,
644
+ $.binary_expression,
645
+ $.ternary_expression,
646
+ $.update_expression,
647
+ ),
648
+
649
+ primary_expression: $ => choice(
650
+ $.subscript_expression,
651
+ $.member_expression,
652
+ $.parenthesized_expression,
653
+ $._identifier,
654
+ alias($._reserved_identifier, $.identifier),
655
+ $.this,
656
+ $.number,
657
+ $.string,
658
+ $.template_string,
659
+ $.regex,
660
+ $.true,
661
+ $.false,
662
+ $.null,
663
+ $.object,
664
+ $.array,
665
+ $.function_expression,
666
+ $.arrow_function,
667
+ $.call_expression,
668
+ ),
669
+
670
+ object: $ => prec('object', seq(
671
+ '{',
672
+ commaSep(optional(choice(
673
+ $.pair,
674
+ $.spread_element,
675
+ alias(
676
+ choice($.identifier, $._reserved_identifier),
677
+ $.shorthand_property_identifier,
678
+ ),
679
+ ))),
680
+ '}',
681
+ )),
682
+
683
+ array: $ => seq(
684
+ '[',
685
+ commaSep(optional(choice(
686
+ $.expression,
687
+ $.spread_element,
688
+ ))),
689
+ ']',
690
+ ),
691
+
692
+ optional_chain: _ => '?.',
693
+
694
+ call_expression: $ => choice(
695
+ prec('call', seq(
696
+ field('function', $.expression),
697
+ field('arguments', $.arguments),
698
+ )),
699
+ prec('member', seq(
700
+ field('function', $.primary_expression),
701
+ field('optional_chain', $.optional_chain),
702
+ field('arguments', $.arguments),
703
+ )),
704
+ ),
705
+
706
+ member_expression: $ => prec('member', seq(
707
+ field('object', choice($.expression, $.primary_expression)),
708
+ choice('.', field('optional_chain', $.optional_chain)),
709
+ field('property', reserved('properties', alias($.identifier, $.property_identifier))),
710
+ )),
711
+
712
+ subscript_expression: $ => prec.right('member', seq(
713
+ field('object', choice($.expression, $.primary_expression)),
714
+ optional(field('optional_chain', $.optional_chain)),
715
+ '[', field('index', $._expressions), ']',
716
+ )),
717
+
718
+ _lhs_expression: $ => choice(
719
+ $.member_expression,
720
+ $.subscript_expression,
721
+ $._identifier,
722
+ alias($._reserved_identifier, $.identifier),
723
+ ),
724
+
725
+ assignment_expression: $ => prec.right('assign', seq(
726
+ field('left', choice($.parenthesized_expression, $._lhs_expression)),
727
+ '=',
728
+ field('right', $.expression),
729
+ )),
730
+
731
+ _augmented_assignment_lhs: $ => choice(
732
+ $.member_expression,
733
+ $.subscript_expression,
734
+ alias($._reserved_identifier, $.identifier),
735
+ $.identifier,
736
+ $.parenthesized_expression,
737
+ ),
738
+
739
+ augmented_assignment_expression: $ => prec.right('assign', seq(
740
+ field('left', $._augmented_assignment_lhs),
741
+ field('operator', choice(
742
+ '+=', '-=', '*=', '/=', '%=', '**=',
743
+ '^=', '&=', '|=', '>>=', '<<=',
744
+ '&&=', '||=', '??=',
745
+ )),
746
+ field('right', $.expression),
747
+ )),
748
+
749
+ _initializer: $ => seq(
750
+ '=',
751
+ field('value', $.expression),
752
+ ),
753
+
754
+ spread_element: $ => seq('...', $.expression),
755
+
756
+ ternary_expression: $ => prec.right('ternary', seq(
757
+ field('condition', $.expression),
758
+ alias($._ternary_qmark, '?'),
759
+ field('consequence', $.expression),
760
+ ':',
761
+ field('alternative', $.expression),
762
+ )),
763
+
764
+ binary_expression: $ => choice(
765
+ ...[
766
+ ['&&', 'logical_and'],
767
+ ['||', 'logical_or'],
768
+ ['>>', 'binary_shift'],
769
+ ['<<', 'binary_shift'],
770
+ ['&', 'bitwise_and'],
771
+ ['^', 'bitwise_xor'],
772
+ ['|', 'bitwise_or'],
773
+ ['+', 'binary_plus'],
774
+ ['-', 'binary_plus'],
775
+ ['*', 'binary_times'],
776
+ ['/', 'binary_times'],
777
+ ['%', 'binary_times'],
778
+ ['**', 'binary_exp', 'right'],
779
+ ['<', 'binary_relation'],
780
+ ['<=', 'binary_relation'],
781
+ ['==', 'binary_equality'],
782
+ ['===', 'binary_equality'],
783
+ ['!=', 'binary_equality'],
784
+ ['!==', 'binary_equality'],
785
+ ['>=', 'binary_relation'],
786
+ ['>', 'binary_relation'],
787
+ ['??', 'logical_or'], // same level as ||, freely mixable
788
+ ['in', 'binary_relation'],
789
+ ].map(([operator, precedence, associativity]) =>
790
+ (associativity === 'right' ? prec.right : prec.left)(precedence, seq(
791
+ field('left', $.expression),
792
+ field('operator', operator),
793
+ field('right', $.expression),
794
+ )),
795
+ ),
796
+ ),
797
+
798
+ unary_expression: $ => prec.left('unary_void', seq(
799
+ field('operator', choice('!', '~', '-', '+', 'delete')),
800
+ field('argument', $.expression),
801
+ )),
802
+
803
+ update_expression: $ => prec.left(choice(
804
+ seq(
805
+ field('argument', $.expression),
806
+ field('operator', choice('++', '--')),
807
+ ),
808
+ seq(
809
+ field('operator', choice('++', '--')),
810
+ field('argument', $.expression),
811
+ ),
812
+ )),
813
+
814
+ sequence_expression: $ => prec.right(commaSep1($.expression)),
815
+
816
+ //
817
+ // Functions
818
+ //
819
+
820
+ function_expression: $ => prec('literal', seq(
821
+ 'function',
822
+ field('name', optional($.identifier)),
823
+ $._call_signature,
824
+ field('body', $.statement_block),
825
+ )),
826
+
827
+ // Function declaration — brace body or alternative colon/endfunction syntax
828
+ function_declaration: $ => prec.right('declaration', seq(
829
+ 'function',
830
+ field('name', $.identifier),
831
+ $._call_signature,
832
+ field('body', choice(
833
+ $.statement_block,
834
+ seq(':', repeat($.statement), 'endfunction'),
835
+ )),
836
+ optional($._automatic_semicolon),
837
+ )),
838
+
839
+ arrow_function: $ => seq(
840
+ choice(
841
+ field('parameter', choice(
842
+ alias($._reserved_identifier, $.identifier),
843
+ $.identifier,
844
+ )),
845
+ $._call_signature,
846
+ ),
847
+ '=>',
848
+ field('body', choice(
849
+ $.expression,
850
+ $.statement_block,
851
+ )),
852
+ ),
853
+
854
+ _call_signature: $ => field('parameters', $.formal_parameters),
855
+ _formal_parameter: $ => choice($.identifier, $.rest_element),
856
+
857
+ formal_parameters: $ => seq(
858
+ '(',
859
+ optional(seq(
860
+ commaSep1($._formal_parameter),
861
+ optional(','),
862
+ )),
863
+ ')',
864
+ ),
865
+
866
+ rest_element: $ => seq('...', $.identifier),
867
+
868
+ pattern: $ => prec.dynamic(-1, $._lhs_expression),
869
+
870
+ //
871
+ // Primitives
872
+ //
873
+
874
+ string: $ => choice(
875
+ seq(
876
+ '"',
877
+ repeat(choice(
878
+ alias($.unescaped_double_string_fragment, $.string_fragment),
879
+ $.escape_sequence,
880
+ )),
881
+ '"',
882
+ ),
883
+ seq(
884
+ '\'',
885
+ repeat(choice(
886
+ alias($.unescaped_single_string_fragment, $.string_fragment),
887
+ $.escape_sequence,
888
+ )),
889
+ '\'',
890
+ ),
891
+ ),
892
+
893
+ unescaped_double_string_fragment: _ => token.immediate(prec(1, /[^"\\\r\n]+/)),
894
+ unescaped_single_string_fragment: _ => token.immediate(prec(1, /[^'\\\r\n]+/)),
895
+
896
+ // Ucode extends JS escapes with \e (ESC), \a (BEL), and octal sequences
897
+ escape_sequence: _ => token.immediate(seq(
898
+ '\\',
899
+ choice(
900
+ /[^xu0-7]/,
901
+ /[0-7]{1,3}/,
902
+ /x[0-9a-fA-F]{2}/,
903
+ /u[0-9a-fA-F]{4}/,
904
+ /u\{[0-9a-fA-F]+\}/,
905
+ /\r[\n\u2028\u2029]/,
906
+ ),
907
+ )),
908
+
909
+ comment: _ => token(choice(
910
+ seq("//", /[^\r\n\u2028\u2029]*/),
911
+ seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'),
912
+ )),
913
+
914
+ template_string: $ => seq(
915
+ '`',
916
+ repeat(choice(
917
+ alias($._template_chars, $.string_fragment),
918
+ $.escape_sequence,
919
+ $.template_substitution,
920
+ )),
921
+ '`',
922
+ ),
923
+
924
+ template_substitution: $ => seq(
925
+ '${',
926
+ $._expressions,
927
+ '}',
928
+ ),
929
+
930
+ regex: $ => seq(
931
+ '/',
932
+ field('pattern', $.regex_pattern),
933
+ token.immediate(prec(1, '/')),
934
+ optional(field('flags', $.regex_flags)),
935
+ ),
936
+
937
+ regex_pattern: _ => token.immediate(prec(-1,
938
+ repeat1(choice(
939
+ seq('[', repeat(choice(seq('\\', /./), /[^\]\n\\]/)), ']'),
940
+ seq('\\', /./),
941
+ /[^/\\\[\n]/,
942
+ )),
943
+ )),
944
+
945
+ // Ucode supports only g, i, s flags (not m, u, y, d)
946
+ regex_flags: _ => token.immediate(/[gis]+/),
947
+
948
+ // Ucode number literals extend JS with:
949
+ // - C-style legacy octal: 0177
950
+ // - Hex float: 0x1.8
951
+ // - Uppercase prefixes: 0O, 0B (already in JS grammar)
952
+ number: _ => {
953
+ const hexDigits = /[\da-fA-F](_?[\da-fA-F])*/;
954
+ const hexLiteral = seq(choice('0x', '0X'), hexDigits);
955
+ const hexFloat = seq(choice('0x', '0X'), hexDigits, '.', optional(hexDigits));
956
+
957
+ const decimalDigits = /\d(_?\d)*/;
958
+ const signedInteger = seq(optional(choice('-', '+')), decimalDigits);
959
+ const exponentPart = seq(choice('e', 'E'), signedInteger);
960
+
961
+ const binaryLiteral = seq(choice('0b', '0B'), /[0-1](_?[0-1])*/);
962
+ const octalLiteral = seq(choice('0o', '0O'), /[0-7](_?[0-7])*/);
963
+ const legacyOctalLiteral = seq('0', /[0-7]+/);
964
+
965
+ const decimalIntegerLiteral = choice(
966
+ '0',
967
+ seq(optional('0'), /[1-9]/, optional(seq(optional('_'), decimalDigits))),
968
+ );
969
+
970
+ const decimalLiteral = choice(
971
+ seq(decimalIntegerLiteral, '.', optional(decimalDigits), optional(exponentPart)),
972
+ seq('.', decimalDigits, optional(exponentPart)),
973
+ seq(decimalIntegerLiteral, exponentPart),
974
+ decimalDigits,
975
+ );
976
+
977
+ return token(choice(
978
+ hexFloat,
979
+ hexLiteral,
980
+ decimalLiteral,
981
+ binaryLiteral,
982
+ octalLiteral,
983
+ legacyOctalLiteral,
984
+ ));
985
+ },
986
+
987
+ _identifier: $ => $.identifier,
988
+
989
+ identifier: _ => {
990
+ const alpha = /[^\x00-\x1F\s\p{Zs}0-9:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
991
+ const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
992
+ return token(seq(alpha, repeat(alphanumeric)));
993
+ },
994
+
995
+ this: _ => 'this',
996
+ true: _ => 'true',
997
+ false: _ => 'false',
998
+ null: _ => 'null',
999
+
1000
+ arguments: $ => seq(
1001
+ '(',
1002
+ commaSep(optional(choice($.expression, $.spread_element))),
1003
+ ')',
1004
+ ),
1005
+
1006
+ _property_name: $ => reserved('properties', choice(
1007
+ alias(
1008
+ choice($.identifier, $._reserved_identifier),
1009
+ $.property_identifier,
1010
+ ),
1011
+ $.string,
1012
+ $.number,
1013
+ $.computed_property_name,
1014
+ )),
1015
+
1016
+ computed_property_name: $ => seq('[', $.expression, ']'),
1017
+
1018
+ pair: $ => seq(
1019
+ field('key', $._property_name),
1020
+ ':',
1021
+ field('value', $.expression),
1022
+ ),
1023
+
1024
+ _reserved_identifier: _ => choice(
1025
+ 'get',
1026
+ 'set',
1027
+ ),
1028
+
1029
+ _semicolon: $ => choice($._automatic_semicolon, ';'),
1030
+ },
1031
+ });
1032
+
1033
+ function forHeader($) {
1034
+ return seq(
1035
+ 'for',
1036
+ '(',
1037
+ choice(
1038
+ field('initializer', $.lexical_declaration),
1039
+ seq(field('initializer', $._expressions), ';'),
1040
+ field('initializer', $.empty_statement),
1041
+ ),
1042
+ field('condition', choice(
1043
+ seq($._expressions, ';'),
1044
+ $.empty_statement,
1045
+ )),
1046
+ field('increment', optional($._expressions)),
1047
+ ')',
1048
+ );
1049
+ }
1050
+
1051
+ function commaSep1(rule) {
1052
+ return seq(rule, repeat(seq(',', rule)));
1053
+ }
1054
+
1055
+ function commaSep(rule) {
1056
+ return optional(commaSep1(rule));
1057
+ }