tree-sitter-ucode 0.6.1 → 0.7.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/README.md CHANGED
@@ -33,9 +33,17 @@ Ucode is an ECMAScript subset with OpenWrt-specific extensions. Key differences:
33
33
  | Removed features | Destructuring, `for...of`, `do-while`, generators, forward declarations, dynamic `import()` | All supported |
34
34
  | Added number literals | `0177` (C octal), `0x1.8` (hex float), `0B`/`0O` prefixes | Standard only |
35
35
  | Added escape sequences | `\e` (ESC), `\a` (BEL), octal `\177` | Standard only |
36
+ | String unicode escapes | `\uXXXX` only (no `\u{…}`); no `\u` escapes in identifiers | `\uXXXX` and `\u{…}` |
36
37
  | Regex flags | `g`, `i`, `s` only | Full set |
37
38
  | Module system | Static `import`/`export` only; no `from` on re-exports | Full ES modules |
38
39
 
40
+ The grammar tracks ucode's parser closely, with one deliberate exception: **automatic
41
+ semicolon insertion is kept ECMAScript-style (more lenient than the compiler).** ucode
42
+ only lets you drop a statement's `;` before `}`, end-of-file, a template tag close, or an
43
+ alt-syntax end keyword (`endif`/`endfor`/`endwhile`/`endfunction`/`elif`/`else`), whereas
44
+ the grammar also tolerates a bare newline between statements so that in-progress edits are
45
+ not flagged as errors.
46
+
39
47
  ## Doc comment grammar (ucdocs)
40
48
 
41
49
  `/** */` blocks are parsed by the `ucdocs` grammar and injected into the host parse tree.
package/grammar.js CHANGED
@@ -77,7 +77,6 @@ module.exports = grammar({
77
77
 
78
78
  inline: $ => [
79
79
  $._call_signature,
80
- $._formal_parameter,
81
80
  $._expressions,
82
81
  $._semicolon,
83
82
  $._identifier,
@@ -125,7 +124,7 @@ module.exports = grammar({
125
124
  [$.primary_expression, $._for_header],
126
125
  [$.variable_declarator, $._for_header],
127
126
  [$.assignment_expression, $.pattern],
128
- [$.labeled_statement, $._property_name],
127
+ [$.primary_expression, $.delete_expression],
129
128
  ],
130
129
 
131
130
  word: $ => $.identifier,
@@ -208,18 +207,22 @@ module.exports = grammar({
208
207
  field('clause', $.export_clause),
209
208
  $._semicolon,
210
209
  ),
210
+ // `export let/const …;` already carries its own terminating semicolon.
211
211
  seq(
212
212
  'export',
213
- choice(
214
- field('declaration', $.declaration),
215
- seq(
216
- 'default',
217
- seq(
218
- field('value', $.expression),
219
- ';',
220
- ),
221
- ),
222
- ),
213
+ field('declaration', $.lexical_declaration),
214
+ ),
215
+ // `export function f() {}` requires an explicit trailing `;` in ucode.
216
+ seq(
217
+ 'export',
218
+ field('declaration', $.function_declaration),
219
+ ';',
220
+ ),
221
+ seq(
222
+ 'export',
223
+ 'default',
224
+ field('value', $.expression),
225
+ ';',
223
226
  ),
224
227
  ),
225
228
 
@@ -322,7 +325,6 @@ module.exports = grammar({
322
325
  $.continue_statement,
323
326
  $.return_statement,
324
327
  $.empty_statement,
325
- $.labeled_statement,
326
328
  ),
327
329
 
328
330
  expression_statement: $ => seq(
@@ -330,10 +332,19 @@ module.exports = grammar({
330
332
  $._semicolon,
331
333
  ),
332
334
 
333
- lexical_declaration: $ => seq(
334
- field('kind', choice('let', 'const')),
335
- commaSep1($.variable_declarator),
336
- $._semicolon,
335
+ // `let` declarators may omit the initializer; `const` requires one.
336
+ // ucode rejects `const a;` ("Expecting initializer expression").
337
+ lexical_declaration: $ => choice(
338
+ seq(
339
+ field('kind', 'let'),
340
+ commaSep1($.variable_declarator),
341
+ $._semicolon,
342
+ ),
343
+ seq(
344
+ field('kind', 'const'),
345
+ commaSep1(alias($._const_declarator, $.variable_declarator)),
346
+ $._semicolon,
347
+ ),
337
348
  ),
338
349
 
339
350
  variable_declarator: $ => seq(
@@ -341,6 +352,11 @@ module.exports = grammar({
341
352
  optional($._initializer),
342
353
  ),
343
354
 
355
+ _const_declarator: $ => seq(
356
+ field('name', $.identifier),
357
+ $._initializer,
358
+ ),
359
+
344
360
  statement_block: $ => prec.right(seq(
345
361
  '{',
346
362
  repeat($.statement),
@@ -512,17 +528,19 @@ module.exports = grammar({
512
528
  ),
513
529
  ),
514
530
 
515
- // Supports both `for (k in obj)` and `for (k, v in obj)` (ucode two-variable form)
531
+ // Supports both `for (k in obj)` and `for (k, v in obj)` (ucode two-variable form).
532
+ // The loop target must be a plain identifier: ucode rejects member/subscript
533
+ // targets (`for (a.x in o)`) and only `let` (never `const`) may declare it.
516
534
  _for_header: $ => seq(
517
535
  '(',
518
536
  choice(
519
537
  seq(
520
- field('kind', choice('let', 'const')),
538
+ field('kind', 'let'),
521
539
  field('left', $.identifier),
522
540
  optional(seq(',', field('value', $.identifier))),
523
541
  ),
524
542
  seq(
525
- field('left', $._lhs_expression),
543
+ field('left', $.identifier),
526
544
  optional(seq(',', field('value', $.identifier))),
527
545
  ),
528
546
  ),
@@ -565,15 +583,14 @@ module.exports = grammar({
565
583
  optional(field('handler', $.catch_clause)),
566
584
  ),
567
585
 
586
+ // ucode has no labeled statements, so `break`/`continue` take no label.
568
587
  break_statement: $ => seq(
569
588
  'break',
570
- field('label', optional(alias($.identifier, $.statement_identifier))),
571
589
  $._semicolon,
572
590
  ),
573
591
 
574
592
  continue_statement: $ => seq(
575
593
  'continue',
576
- field('label', optional(alias($.identifier, $.statement_identifier))),
577
594
  $._semicolon,
578
595
  ),
579
596
 
@@ -585,12 +602,6 @@ module.exports = grammar({
585
602
 
586
603
  empty_statement: _ => ';',
587
604
 
588
- labeled_statement: $ => prec.dynamic(-1, seq(
589
- field('label', alias(choice($.identifier, $._reserved_identifier), $.statement_identifier)),
590
- ':',
591
- field('body', $.statement),
592
- )),
593
-
594
605
  //
595
606
  // Statement components
596
607
  //
@@ -640,6 +651,7 @@ module.exports = grammar({
640
651
  $.assignment_expression,
641
652
  $.augmented_assignment_expression,
642
653
  $.unary_expression,
654
+ $.delete_expression,
643
655
  $.binary_expression,
644
656
  $.ternary_expression,
645
657
  $.update_expression,
@@ -666,25 +678,33 @@ module.exports = grammar({
666
678
  $.call_expression,
667
679
  ),
668
680
 
681
+ // ucode allows a trailing comma but NOT interior elision (`{a:1,,b:2}`).
669
682
  object: $ => prec('object', seq(
670
683
  '{',
671
- commaSep(optional(choice(
672
- $.pair,
673
- $.spread_element,
674
- alias(
675
- choice($.identifier, $._reserved_identifier),
676
- $.shorthand_property_identifier,
677
- ),
678
- ))),
684
+ optional(seq(
685
+ commaSep1(choice(
686
+ $.pair,
687
+ $.spread_element,
688
+ alias(
689
+ choice($.identifier, $._reserved_identifier),
690
+ $.shorthand_property_identifier,
691
+ ),
692
+ )),
693
+ optional(','),
694
+ )),
679
695
  '}',
680
696
  )),
681
697
 
698
+ // ucode allows a trailing comma but NOT interior elision (`[1,,2]`).
682
699
  array: $ => seq(
683
700
  '[',
684
- commaSep(optional(choice(
685
- $.expression,
686
- $.spread_element,
687
- ))),
701
+ optional(seq(
702
+ commaSep1(choice(
703
+ $.expression,
704
+ $.spread_element,
705
+ )),
706
+ optional(','),
707
+ )),
688
708
  ']',
689
709
  ),
690
710
 
@@ -795,10 +815,17 @@ module.exports = grammar({
795
815
  ),
796
816
 
797
817
  unary_expression: $ => prec.left('unary_void', seq(
798
- field('operator', choice('!', '~', '-', '+', 'delete')),
818
+ field('operator', choice('!', '~', '-', '+')),
799
819
  field('argument', $.expression),
800
820
  )),
801
821
 
822
+ // ucode's `delete` only accepts a property access (member or subscript)
823
+ // expression: `delete o.k` / `delete o["k"]`. `delete x` is a syntax error.
824
+ delete_expression: $ => prec.left('unary_void', seq(
825
+ field('operator', 'delete'),
826
+ field('argument', choice($.member_expression, $.subscript_expression)),
827
+ )),
828
+
802
829
  update_expression: $ => prec.left(choice(
803
830
  seq(
804
831
  field('argument', $.expression),
@@ -851,13 +878,16 @@ module.exports = grammar({
851
878
  ),
852
879
 
853
880
  _call_signature: $ => field('parameters', $.formal_parameters),
854
- _formal_parameter: $ => choice($.identifier, $.rest_element),
855
881
 
882
+ // A rest element (`...name`) must be the final parameter, and there can be
883
+ // only one. ucode rejects a rest param followed by anything — including a
884
+ // trailing comma. A trailing comma is allowed only after plain parameters.
856
885
  formal_parameters: $ => seq(
857
886
  '(',
858
- optional(seq(
859
- commaSep1($._formal_parameter),
860
- optional(','),
887
+ optional(choice(
888
+ $.rest_element,
889
+ seq(commaSep1($.identifier), ',', $.rest_element),
890
+ seq(commaSep1($.identifier), optional(',')),
861
891
  )),
862
892
  ')',
863
893
  ),
@@ -892,7 +922,8 @@ module.exports = grammar({
892
922
  unescaped_double_string_fragment: _ => token.immediate(prec(1, /[^"\\\r\n]+/)),
893
923
  unescaped_single_string_fragment: _ => token.immediate(prec(1, /[^'\\\r\n]+/)),
894
924
 
895
- // Ucode extends JS escapes with \e (ESC), \a (BEL), and octal sequences
925
+ // Ucode extends JS escapes with \e (ESC), \a (BEL), and octal sequences.
926
+ // Unlike JS, ucode supports only the 4-hex `\uXXXX` form \u2014 not `\u{...}`.
896
927
  escape_sequence: _ => token.immediate(seq(
897
928
  '\\',
898
929
  choice(
@@ -900,7 +931,6 @@ module.exports = grammar({
900
931
  /[0-7]{1,3}/,
901
932
  /x[0-9a-fA-F]{2}/,
902
933
  /u[0-9a-fA-F]{4}/,
903
- /u\{[0-9a-fA-F]+\}/,
904
934
  /\r[\n\u2028\u2029]/,
905
935
  ),
906
936
  )),
@@ -948,27 +978,30 @@ module.exports = grammar({
948
978
  // - C-style legacy octal: 0177
949
979
  // - Hex float: 0x1.8
950
980
  // - Uppercase prefixes: 0O, 0B (already in JS grammar)
981
+ //
982
+ // Unlike JS, ucode does NOT support:
983
+ // - numeric underscore separators (1_000)
984
+ // - leading-dot floats (.5) — a digit is required before the dot
951
985
  number: _ => {
952
- const hexDigits = /[\da-fA-F](_?[\da-fA-F])*/;
986
+ const hexDigits = /[\da-fA-F]+/;
953
987
  const hexLiteral = seq(choice('0x', '0X'), hexDigits);
954
988
  const hexFloat = seq(choice('0x', '0X'), hexDigits, '.', optional(hexDigits));
955
989
 
956
- const decimalDigits = /\d(_?\d)*/;
990
+ const decimalDigits = /\d+/;
957
991
  const signedInteger = seq(optional(choice('-', '+')), decimalDigits);
958
992
  const exponentPart = seq(choice('e', 'E'), signedInteger);
959
993
 
960
- const binaryLiteral = seq(choice('0b', '0B'), /[0-1](_?[0-1])*/);
961
- const octalLiteral = seq(choice('0o', '0O'), /[0-7](_?[0-7])*/);
994
+ const binaryLiteral = seq(choice('0b', '0B'), /[0-1]+/);
995
+ const octalLiteral = seq(choice('0o', '0O'), /[0-7]+/);
962
996
  const legacyOctalLiteral = seq('0', /[0-7]+/);
963
997
 
964
998
  const decimalIntegerLiteral = choice(
965
999
  '0',
966
- seq(optional('0'), /[1-9]/, optional(seq(optional('_'), decimalDigits))),
1000
+ seq(optional('0'), /[1-9]/, optional(decimalDigits)),
967
1001
  );
968
1002
 
969
1003
  const decimalLiteral = choice(
970
1004
  seq(decimalIntegerLiteral, '.', optional(decimalDigits), optional(exponentPart)),
971
- seq('.', decimalDigits, optional(exponentPart)),
972
1005
  seq(decimalIntegerLiteral, exponentPart),
973
1006
  decimalDigits,
974
1007
  );
@@ -985,9 +1018,12 @@ module.exports = grammar({
985
1018
 
986
1019
  _identifier: $ => $.identifier,
987
1020
 
1021
+ // ucode does NOT support unicode escape sequences in identifiers (unlike JS);
1022
+ // a literal `\u...` is an "Unexpected character". Non-ASCII letters are still
1023
+ // allowed directly via the negated character class.
988
1024
  identifier: _ => {
989
- 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]+\}/;
990
- const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
1025
+ const alpha = /[^\x00-\x1F\s\p{Zs}0-9:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]/;
1026
+ const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]/;
991
1027
  return token(seq(alpha, repeat(alphanumeric)));
992
1028
  },
993
1029
 
@@ -996,9 +1032,11 @@ module.exports = grammar({
996
1032
  false: _ => 'false',
997
1033
  null: _ => 'null',
998
1034
 
1035
+ // Unlike arrays/objects, ucode call arguments allow neither interior
1036
+ // elision (`f(1,,2)`) nor a trailing comma (`f(1,2,)`).
999
1037
  arguments: $ => seq(
1000
1038
  '(',
1001
- commaSep(optional(choice($.expression, $.spread_element))),
1039
+ commaSep(choice($.expression, $.spread_element)),
1002
1040
  ')',
1003
1041
  ),
1004
1042
 
package/markup/grammar.js CHANGED
@@ -78,7 +78,6 @@ module.exports = grammar({
78
78
 
79
79
  inline: $ => [
80
80
  $._call_signature,
81
- $._formal_parameter,
82
81
  $._expressions,
83
82
  $._semicolon,
84
83
  $._identifier,
@@ -126,7 +125,7 @@ module.exports = grammar({
126
125
  [$.primary_expression, $._for_header],
127
126
  [$.variable_declarator, $._for_header],
128
127
  [$.assignment_expression, $.pattern],
129
- [$.labeled_statement, $._property_name],
128
+ [$.primary_expression, $.delete_expression],
130
129
  ],
131
130
 
132
131
  word: $ => $.identifier,
@@ -209,18 +208,22 @@ module.exports = grammar({
209
208
  field('clause', $.export_clause),
210
209
  $._semicolon,
211
210
  ),
211
+ // `export let/const …;` already carries its own terminating semicolon.
212
212
  seq(
213
213
  'export',
214
- choice(
215
- field('declaration', $.declaration),
216
- seq(
217
- 'default',
218
- seq(
219
- field('value', $.expression),
220
- ';',
221
- ),
222
- ),
223
- ),
214
+ field('declaration', $.lexical_declaration),
215
+ ),
216
+ // `export function f() {}` requires an explicit trailing `;` in ucode.
217
+ seq(
218
+ 'export',
219
+ field('declaration', $.function_declaration),
220
+ ';',
221
+ ),
222
+ seq(
223
+ 'export',
224
+ 'default',
225
+ field('value', $.expression),
226
+ ';',
224
227
  ),
225
228
  ),
226
229
 
@@ -323,7 +326,6 @@ module.exports = grammar({
323
326
  $.continue_statement,
324
327
  $.return_statement,
325
328
  $.empty_statement,
326
- $.labeled_statement,
327
329
  ),
328
330
 
329
331
  expression_statement: $ => seq(
@@ -331,10 +333,19 @@ module.exports = grammar({
331
333
  $._semicolon,
332
334
  ),
333
335
 
334
- lexical_declaration: $ => seq(
335
- field('kind', choice('let', 'const')),
336
- commaSep1($.variable_declarator),
337
- $._semicolon,
336
+ // `let` declarators may omit the initializer; `const` requires one.
337
+ // ucode rejects `const a;` ("Expecting initializer expression").
338
+ lexical_declaration: $ => choice(
339
+ seq(
340
+ field('kind', 'let'),
341
+ commaSep1($.variable_declarator),
342
+ $._semicolon,
343
+ ),
344
+ seq(
345
+ field('kind', 'const'),
346
+ commaSep1(alias($._const_declarator, $.variable_declarator)),
347
+ $._semicolon,
348
+ ),
338
349
  ),
339
350
 
340
351
  variable_declarator: $ => seq(
@@ -342,6 +353,11 @@ module.exports = grammar({
342
353
  optional($._initializer),
343
354
  ),
344
355
 
356
+ _const_declarator: $ => seq(
357
+ field('name', $.identifier),
358
+ $._initializer,
359
+ ),
360
+
345
361
  statement_block: $ => prec.right(seq(
346
362
  '{',
347
363
  repeat($.statement),
@@ -513,17 +529,19 @@ module.exports = grammar({
513
529
  ),
514
530
  ),
515
531
 
516
- // Supports both `for (k in obj)` and `for (k, v in obj)` (ucode two-variable form)
532
+ // Supports both `for (k in obj)` and `for (k, v in obj)` (ucode two-variable form).
533
+ // The loop target must be a plain identifier: ucode rejects member/subscript
534
+ // targets (`for (a.x in o)`) and only `let` (never `const`) may declare it.
517
535
  _for_header: $ => seq(
518
536
  '(',
519
537
  choice(
520
538
  seq(
521
- field('kind', choice('let', 'const')),
539
+ field('kind', 'let'),
522
540
  field('left', $.identifier),
523
541
  optional(seq(',', field('value', $.identifier))),
524
542
  ),
525
543
  seq(
526
- field('left', $._lhs_expression),
544
+ field('left', $.identifier),
527
545
  optional(seq(',', field('value', $.identifier))),
528
546
  ),
529
547
  ),
@@ -566,15 +584,14 @@ module.exports = grammar({
566
584
  optional(field('handler', $.catch_clause)),
567
585
  ),
568
586
 
587
+ // ucode has no labeled statements, so `break`/`continue` take no label.
569
588
  break_statement: $ => seq(
570
589
  'break',
571
- field('label', optional(alias($.identifier, $.statement_identifier))),
572
590
  $._semicolon,
573
591
  ),
574
592
 
575
593
  continue_statement: $ => seq(
576
594
  'continue',
577
- field('label', optional(alias($.identifier, $.statement_identifier))),
578
595
  $._semicolon,
579
596
  ),
580
597
 
@@ -586,12 +603,6 @@ module.exports = grammar({
586
603
 
587
604
  empty_statement: _ => ';',
588
605
 
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
606
  //
596
607
  // Statement components
597
608
  //
@@ -641,6 +652,7 @@ module.exports = grammar({
641
652
  $.assignment_expression,
642
653
  $.augmented_assignment_expression,
643
654
  $.unary_expression,
655
+ $.delete_expression,
644
656
  $.binary_expression,
645
657
  $.ternary_expression,
646
658
  $.update_expression,
@@ -667,25 +679,33 @@ module.exports = grammar({
667
679
  $.call_expression,
668
680
  ),
669
681
 
682
+ // ucode allows a trailing comma but NOT interior elision (`{a:1,,b:2}`).
670
683
  object: $ => prec('object', seq(
671
684
  '{',
672
- commaSep(optional(choice(
673
- $.pair,
674
- $.spread_element,
675
- alias(
676
- choice($.identifier, $._reserved_identifier),
677
- $.shorthand_property_identifier,
678
- ),
679
- ))),
685
+ optional(seq(
686
+ commaSep1(choice(
687
+ $.pair,
688
+ $.spread_element,
689
+ alias(
690
+ choice($.identifier, $._reserved_identifier),
691
+ $.shorthand_property_identifier,
692
+ ),
693
+ )),
694
+ optional(','),
695
+ )),
680
696
  '}',
681
697
  )),
682
698
 
699
+ // ucode allows a trailing comma but NOT interior elision (`[1,,2]`).
683
700
  array: $ => seq(
684
701
  '[',
685
- commaSep(optional(choice(
686
- $.expression,
687
- $.spread_element,
688
- ))),
702
+ optional(seq(
703
+ commaSep1(choice(
704
+ $.expression,
705
+ $.spread_element,
706
+ )),
707
+ optional(','),
708
+ )),
689
709
  ']',
690
710
  ),
691
711
 
@@ -796,10 +816,17 @@ module.exports = grammar({
796
816
  ),
797
817
 
798
818
  unary_expression: $ => prec.left('unary_void', seq(
799
- field('operator', choice('!', '~', '-', '+', 'delete')),
819
+ field('operator', choice('!', '~', '-', '+')),
800
820
  field('argument', $.expression),
801
821
  )),
802
822
 
823
+ // ucode's `delete` only accepts a property access (member or subscript)
824
+ // expression: `delete o.k` / `delete o["k"]`. `delete x` is a syntax error.
825
+ delete_expression: $ => prec.left('unary_void', seq(
826
+ field('operator', 'delete'),
827
+ field('argument', choice($.member_expression, $.subscript_expression)),
828
+ )),
829
+
803
830
  update_expression: $ => prec.left(choice(
804
831
  seq(
805
832
  field('argument', $.expression),
@@ -852,13 +879,16 @@ module.exports = grammar({
852
879
  ),
853
880
 
854
881
  _call_signature: $ => field('parameters', $.formal_parameters),
855
- _formal_parameter: $ => choice($.identifier, $.rest_element),
856
882
 
883
+ // A rest element (`...name`) must be the final parameter, and there can be
884
+ // only one. ucode rejects a rest param followed by anything — including a
885
+ // trailing comma. A trailing comma is allowed only after plain parameters.
857
886
  formal_parameters: $ => seq(
858
887
  '(',
859
- optional(seq(
860
- commaSep1($._formal_parameter),
861
- optional(','),
888
+ optional(choice(
889
+ $.rest_element,
890
+ seq(commaSep1($.identifier), ',', $.rest_element),
891
+ seq(commaSep1($.identifier), optional(',')),
862
892
  )),
863
893
  ')',
864
894
  ),
@@ -893,7 +923,8 @@ module.exports = grammar({
893
923
  unescaped_double_string_fragment: _ => token.immediate(prec(1, /[^"\\\r\n]+/)),
894
924
  unescaped_single_string_fragment: _ => token.immediate(prec(1, /[^'\\\r\n]+/)),
895
925
 
896
- // Ucode extends JS escapes with \e (ESC), \a (BEL), and octal sequences
926
+ // Ucode extends JS escapes with \e (ESC), \a (BEL), and octal sequences.
927
+ // Unlike JS, ucode supports only the 4-hex `\uXXXX` form \u2014 not `\u{...}`.
897
928
  escape_sequence: _ => token.immediate(seq(
898
929
  '\\',
899
930
  choice(
@@ -901,7 +932,6 @@ module.exports = grammar({
901
932
  /[0-7]{1,3}/,
902
933
  /x[0-9a-fA-F]{2}/,
903
934
  /u[0-9a-fA-F]{4}/,
904
- /u\{[0-9a-fA-F]+\}/,
905
935
  /\r[\n\u2028\u2029]/,
906
936
  ),
907
937
  )),
@@ -949,27 +979,30 @@ module.exports = grammar({
949
979
  // - C-style legacy octal: 0177
950
980
  // - Hex float: 0x1.8
951
981
  // - Uppercase prefixes: 0O, 0B (already in JS grammar)
982
+ //
983
+ // Unlike JS, ucode does NOT support:
984
+ // - numeric underscore separators (1_000)
985
+ // - leading-dot floats (.5) — a digit is required before the dot
952
986
  number: _ => {
953
- const hexDigits = /[\da-fA-F](_?[\da-fA-F])*/;
987
+ const hexDigits = /[\da-fA-F]+/;
954
988
  const hexLiteral = seq(choice('0x', '0X'), hexDigits);
955
989
  const hexFloat = seq(choice('0x', '0X'), hexDigits, '.', optional(hexDigits));
956
990
 
957
- const decimalDigits = /\d(_?\d)*/;
991
+ const decimalDigits = /\d+/;
958
992
  const signedInteger = seq(optional(choice('-', '+')), decimalDigits);
959
993
  const exponentPart = seq(choice('e', 'E'), signedInteger);
960
994
 
961
- const binaryLiteral = seq(choice('0b', '0B'), /[0-1](_?[0-1])*/);
962
- const octalLiteral = seq(choice('0o', '0O'), /[0-7](_?[0-7])*/);
995
+ const binaryLiteral = seq(choice('0b', '0B'), /[0-1]+/);
996
+ const octalLiteral = seq(choice('0o', '0O'), /[0-7]+/);
963
997
  const legacyOctalLiteral = seq('0', /[0-7]+/);
964
998
 
965
999
  const decimalIntegerLiteral = choice(
966
1000
  '0',
967
- seq(optional('0'), /[1-9]/, optional(seq(optional('_'), decimalDigits))),
1001
+ seq(optional('0'), /[1-9]/, optional(decimalDigits)),
968
1002
  );
969
1003
 
970
1004
  const decimalLiteral = choice(
971
1005
  seq(decimalIntegerLiteral, '.', optional(decimalDigits), optional(exponentPart)),
972
- seq('.', decimalDigits, optional(exponentPart)),
973
1006
  seq(decimalIntegerLiteral, exponentPart),
974
1007
  decimalDigits,
975
1008
  );
@@ -986,9 +1019,12 @@ module.exports = grammar({
986
1019
 
987
1020
  _identifier: $ => $.identifier,
988
1021
 
1022
+ // ucode does NOT support unicode escape sequences in identifiers (unlike JS);
1023
+ // a literal `\u...` is an "Unexpected character". Non-ASCII letters are still
1024
+ // allowed directly via the negated character class.
989
1025
  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]+\}/;
1026
+ const alpha = /[^\x00-\x1F\s\p{Zs}0-9:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]/;
1027
+ const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B\u2028\u2029]/;
992
1028
  return token(seq(alpha, repeat(alphanumeric)));
993
1029
  },
994
1030
 
@@ -997,9 +1033,11 @@ module.exports = grammar({
997
1033
  false: _ => 'false',
998
1034
  null: _ => 'null',
999
1035
 
1036
+ // Unlike arrays/objects, ucode call arguments allow neither interior
1037
+ // elision (`f(1,,2)`) nor a trailing comma (`f(1,2,)`).
1000
1038
  arguments: $ => seq(
1001
1039
  '(',
1002
- commaSep(optional(choice($.expression, $.spread_element))),
1040
+ commaSep(choice($.expression, $.spread_element)),
1003
1041
  ')',
1004
1042
  ),
1005
1043