@hyperfixi/core 2.0.0 → 2.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.
Files changed (76) hide show
  1. package/dist/api/hyperscript-api.d.ts +8 -1
  2. package/dist/ast-utils/index.d.ts +1 -1
  3. package/dist/ast-utils/index.js +11 -1
  4. package/dist/ast-utils/index.mjs +11 -1
  5. package/dist/ast-utils/interchange/index.d.ts +1 -1
  6. package/dist/ast-utils/interchange/lsp.d.ts +4 -1
  7. package/dist/behaviors/index.js +13 -5
  8. package/dist/behaviors/index.mjs +13 -5
  9. package/dist/bundle-generator/index.js +9 -0
  10. package/dist/bundle-generator/index.mjs +9 -0
  11. package/dist/chunks/{bridge-I6ceoWxV.js → bridge-D2DBo02Z.js} +2 -2
  12. package/dist/chunks/browser-modular-BA3JFmkq.js +2 -0
  13. package/dist/chunks/feature-eventsource-B5F2-H1r.js +2 -0
  14. package/dist/chunks/feature-sockets-ClOH7vk7.js +2 -0
  15. package/dist/chunks/feature-webworker-3bAp0ac9.js +2 -0
  16. package/dist/chunks/index-C6Fn0jGB.js +2 -0
  17. package/dist/commands/dom/toggle.d.ts +2 -0
  18. package/dist/commands/index.js +62 -6
  19. package/dist/commands/index.mjs +62 -6
  20. package/dist/debug/debug-controller.d.ts +62 -0
  21. package/dist/debug/index.d.ts +5 -0
  22. package/dist/expressions/index.js +16 -79
  23. package/dist/expressions/index.mjs +16 -79
  24. package/dist/hyperfixi-classic-i18n.js +1 -1
  25. package/dist/hyperfixi-hx.js +1 -1
  26. package/dist/hyperfixi-hybrid-complete.js +1 -1
  27. package/dist/hyperfixi-minimal.js +1 -1
  28. package/dist/hyperfixi-multilingual.js +1 -1
  29. package/dist/hyperfixi-standard.js +1 -1
  30. package/dist/hyperfixi.js +1 -1
  31. package/dist/hyperfixi.mjs +1 -1
  32. package/dist/i18n/error-catalog.d.ts +12 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +35334 -24306
  35. package/dist/index.min.js +1 -1
  36. package/dist/index.mjs +35333 -24307
  37. package/dist/lokascript-browser-classic-i18n.js +1 -1
  38. package/dist/lokascript-browser-minimal.js +1 -1
  39. package/dist/lokascript-browser-standard.js +1 -1
  40. package/dist/lokascript-browser.js +1 -1
  41. package/dist/lokascript-hybrid-complete.js +1 -1
  42. package/dist/lokascript-hybrid-hx.js +1 -1
  43. package/dist/lokascript-multilingual.js +1 -1
  44. package/dist/lse/index.d.ts +15 -0
  45. package/dist/lse/index.js +84 -0
  46. package/dist/lse/index.mjs +71 -0
  47. package/dist/metadata.d.ts +4 -4
  48. package/dist/metadata.js +4 -4
  49. package/dist/metadata.mjs +4 -4
  50. package/dist/parser/full-parser.js +608 -271
  51. package/dist/parser/full-parser.mjs +608 -271
  52. package/dist/parser/helpers/ast-helpers.d.ts +1 -0
  53. package/dist/parser/hybrid/index.js +35 -2
  54. package/dist/parser/hybrid/index.mjs +35 -2
  55. package/dist/parser/hybrid/parser-core.d.ts +1 -0
  56. package/dist/parser/hybrid/parser-core.js +35 -2
  57. package/dist/parser/hybrid/parser-core.mjs +35 -2
  58. package/dist/parser/hybrid/tokenizer.js +5 -0
  59. package/dist/parser/hybrid/tokenizer.mjs +5 -0
  60. package/dist/parser/hybrid-parser.js +35 -2
  61. package/dist/parser/hybrid-parser.mjs +35 -2
  62. package/dist/parser/parser-types.d.ts +22 -7
  63. package/dist/parser/parser.d.ts +7 -0
  64. package/dist/parser/pratt-parser.d.ts +42 -0
  65. package/dist/parser/token-consumer.d.ts +1 -2
  66. package/dist/parser/tokenizer.d.ts +0 -33
  67. package/dist/registry/index.js +16 -79
  68. package/dist/registry/index.mjs +16 -79
  69. package/dist/runtime/runtime-base.d.ts +4 -0
  70. package/dist/types/base-types.d.ts +11 -0
  71. package/dist/types/core.d.ts +1 -0
  72. package/package.json +15 -6
  73. package/dist/chunks/browser-modular-Dv6PAV3c.js +0 -2
  74. package/dist/chunks/feature-eventsource-DWb514fy.js +0 -2
  75. package/dist/chunks/feature-sockets-3PFuvCVY.js +0 -2
  76. package/dist/chunks/feature-webworker-DTm_eh-E.js +0 -2
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const KEYWORDS$1 = {
3
+ const KEYWORDS = {
4
4
  THEN: 'then',
5
5
  ELSE: 'else',
6
6
  END: 'end',
@@ -29,11 +29,11 @@ const KEYWORDS$1 = {
29
29
  THE: 'the',
30
30
  START: 'start'};
31
31
  const COMMAND_TERMINATORS = [
32
- KEYWORDS$1.THEN,
33
- KEYWORDS$1.AND,
34
- KEYWORDS$1.ELSE,
35
- KEYWORDS$1.END,
36
- KEYWORDS$1.ON,
32
+ KEYWORDS.THEN,
33
+ KEYWORDS.AND,
34
+ KEYWORDS.ELSE,
35
+ KEYWORDS.END,
36
+ KEYWORDS.ON,
37
37
  ];
38
38
  const COMMANDS = new Set([
39
39
  'add',
@@ -348,74 +348,6 @@ var TokenKind;
348
348
  TokenKind["SYMBOL"] = "symbol";
349
349
  TokenKind["UNKNOWN"] = "unknown";
350
350
  })(TokenKind || (TokenKind = {}));
351
- function getTokenKind(tokenType) {
352
- switch (tokenType) {
353
- case TokenType.KEYWORD:
354
- case TokenType.COMMAND:
355
- case TokenType.EXPRESSION:
356
- case TokenType.CONTEXT_VAR:
357
- case TokenType.GLOBAL_VAR:
358
- case TokenType.EVENT:
359
- case TokenType.IDENTIFIER:
360
- return TokenKind.IDENTIFIER;
361
- case TokenType.STRING:
362
- return TokenKind.STRING;
363
- case TokenType.NUMBER:
364
- return TokenKind.NUMBER;
365
- case TokenType.BOOLEAN:
366
- return TokenKind.IDENTIFIER;
367
- case TokenType.TEMPLATE_LITERAL:
368
- return TokenKind.TEMPLATE;
369
- case TokenType.CSS_SELECTOR:
370
- case TokenType.ID_SELECTOR:
371
- case TokenType.CLASS_SELECTOR:
372
- case TokenType.QUERY_REFERENCE:
373
- return TokenKind.SELECTOR;
374
- case TokenType.OPERATOR:
375
- case TokenType.LOGICAL_OPERATOR:
376
- case TokenType.COMPARISON_OPERATOR:
377
- return TokenKind.OPERATOR;
378
- case TokenType.TIME_EXPRESSION:
379
- return TokenKind.TIME;
380
- case TokenType.COMMENT:
381
- return TokenKind.COMMENT;
382
- case TokenType.SYMBOL:
383
- return TokenKind.SYMBOL;
384
- case TokenType.OBJECT_LITERAL:
385
- case TokenType.ARRAY_LITERAL:
386
- case TokenType.UNKNOWN:
387
- default:
388
- return TokenKind.UNKNOWN;
389
- }
390
- }
391
- var TokenType;
392
- (function (TokenType) {
393
- TokenType["KEYWORD"] = "keyword";
394
- TokenType["COMMAND"] = "command";
395
- TokenType["EXPRESSION"] = "expression";
396
- TokenType["STRING"] = "string";
397
- TokenType["NUMBER"] = "number";
398
- TokenType["BOOLEAN"] = "boolean";
399
- TokenType["TEMPLATE_LITERAL"] = "template_literal";
400
- TokenType["CSS_SELECTOR"] = "css_selector";
401
- TokenType["ID_SELECTOR"] = "id_selector";
402
- TokenType["CLASS_SELECTOR"] = "class_selector";
403
- TokenType["QUERY_REFERENCE"] = "query_reference";
404
- TokenType["CONTEXT_VAR"] = "context_var";
405
- TokenType["GLOBAL_VAR"] = "global_var";
406
- TokenType["EVENT"] = "event";
407
- TokenType["OPERATOR"] = "operator";
408
- TokenType["LOGICAL_OPERATOR"] = "logical_operator";
409
- TokenType["COMPARISON_OPERATOR"] = "comparison_operator";
410
- TokenType["TIME_EXPRESSION"] = "time_expression";
411
- TokenType["OBJECT_LITERAL"] = "object_literal";
412
- TokenType["ARRAY_LITERAL"] = "array_literal";
413
- TokenType["SYMBOL"] = "symbol";
414
- TokenType["COMMENT"] = "comment";
415
- TokenType["IDENTIFIER"] = "identifier";
416
- TokenType["UNKNOWN"] = "unknown";
417
- })(TokenType || (TokenType = {}));
418
- const KEYWORDS = TOKENIZER_KEYWORDS;
419
351
  const MATHEMATICAL_OPERATORS = new Set(['+', '-', '*', '/', 'mod']);
420
352
  const TIME_UNITS = new Set(['ms', 's', 'seconds', 'minutes', 'hours', 'days']);
421
353
  function createTokenizer() {
@@ -458,7 +390,7 @@ function tokenize(input) {
458
390
  const start = tokenizer.position;
459
391
  advance(tokenizer);
460
392
  advance(tokenizer);
461
- addToken(tokenizer, TokenType.OPERATOR, "'s", start);
393
+ addToken(tokenizer, TokenKind.OPERATOR, "'s", start);
462
394
  }
463
395
  else {
464
396
  tokenizeString(tokenizer);
@@ -488,7 +420,7 @@ function tokenize(input) {
488
420
  const start = tokenizer.position;
489
421
  advance(tokenizer);
490
422
  advance(tokenizer);
491
- addToken(tokenizer, TokenType.OPERATOR, '..', start);
423
+ addToken(tokenizer, TokenKind.OPERATOR, '..', start);
492
424
  continue;
493
425
  }
494
426
  const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
@@ -533,46 +465,45 @@ function tokenize(input) {
533
465
  continue;
534
466
  }
535
467
  if (char === '{') {
536
- addToken(tokenizer, TokenType.OPERATOR, '{');
468
+ addToken(tokenizer, TokenKind.OPERATOR, '{');
537
469
  advance(tokenizer);
538
470
  continue;
539
471
  }
540
472
  if (char === '}') {
541
- addToken(tokenizer, TokenType.OPERATOR, '}');
473
+ addToken(tokenizer, TokenKind.OPERATOR, '}');
542
474
  advance(tokenizer);
543
475
  continue;
544
476
  }
545
477
  if (char === '[') {
546
478
  const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
547
479
  const isEventCondition = prevToken &&
548
- (prevToken.kind === TokenType.EVENT ||
549
- (prevToken.kind === TokenType.IDENTIFIER && DOM_EVENTS.has(prevToken.value))) &&
480
+ prevToken.kind === TokenKind.IDENTIFIER &&
481
+ DOM_EVENTS.has(prevToken.value) &&
550
482
  tokenizer.tokens.length >= 2 &&
551
483
  tokenizer.tokens[tokenizer.tokens.length - 2]?.value === 'on';
552
484
  const isMemberAccess = prevToken &&
553
- (prevToken.kind === TokenType.IDENTIFIER ||
554
- prevToken.kind === TokenType.CONTEXT_VAR ||
485
+ (prevToken.kind === TokenKind.IDENTIFIER ||
555
486
  prevToken.value === ')' ||
556
487
  prevToken.value === ']') &&
557
488
  !isEventCondition;
558
489
  if (isMemberAccess) {
559
- addToken(tokenizer, TokenType.OPERATOR, '[');
490
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
560
491
  advance(tokenizer);
561
492
  }
562
493
  else {
563
494
  if (isEventCondition) {
564
- addToken(tokenizer, TokenType.SYMBOL, '[');
495
+ addToken(tokenizer, TokenKind.SYMBOL, '[');
565
496
  advance(tokenizer);
566
497
  }
567
498
  else {
568
- addToken(tokenizer, TokenType.OPERATOR, '[');
499
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
569
500
  advance(tokenizer);
570
501
  }
571
502
  }
572
503
  continue;
573
504
  }
574
505
  if (char === ']') {
575
- addToken(tokenizer, TokenType.OPERATOR, ']');
506
+ addToken(tokenizer, TokenKind.OPERATOR, ']');
576
507
  advance(tokenizer);
577
508
  continue;
578
509
  }
@@ -597,7 +528,7 @@ function tokenize(input) {
597
528
  tokenizeIdentifier(tokenizer);
598
529
  continue;
599
530
  }
600
- addToken(tokenizer, TokenType.UNKNOWN, char);
531
+ addToken(tokenizer, TokenKind.UNKNOWN, char);
601
532
  advance(tokenizer);
602
533
  }
603
534
  return tokenizer.tokens;
@@ -639,7 +570,7 @@ function skipWhitespace(tokenizer) {
639
570
  }
640
571
  }
641
572
  }
642
- function addToken(tokenizer, type, value, start, end) {
573
+ function addToken(tokenizer, kind, value, start, end) {
643
574
  const tokenStart = start ?? tokenizer.position - value.length;
644
575
  const tokenEnd = tokenizer.position;
645
576
  let tokenColumn = tokenizer.column - value.length;
@@ -654,7 +585,7 @@ function addToken(tokenizer, type, value, start, end) {
654
585
  tokenColumn = start - lastNewlinePos;
655
586
  }
656
587
  const token = {
657
- kind: getTokenKind(type),
588
+ kind,
658
589
  value,
659
590
  start: tokenStart,
660
591
  end: tokenEnd,
@@ -690,7 +621,7 @@ function tokenizeComment(tokenizer) {
690
621
  break;
691
622
  value += advance(tokenizer);
692
623
  }
693
- addToken(tokenizer, TokenType.COMMENT, '--' + value, start);
624
+ addToken(tokenizer, TokenKind.COMMENT, '--' + value, start);
694
625
  }
695
626
  function tokenizeString(tokenizer) {
696
627
  const start = tokenizer.position;
@@ -707,7 +638,7 @@ function tokenizeString(tokenizer) {
707
638
  }
708
639
  }
709
640
  }
710
- addToken(tokenizer, TokenType.STRING, value, start);
641
+ addToken(tokenizer, TokenKind.STRING, value, start);
711
642
  }
712
643
  function tokenizeTemplateLiteral(tokenizer) {
713
644
  const start = tokenizer.position;
@@ -751,7 +682,7 @@ function tokenizeTemplateLiteral(tokenizer) {
751
682
  if (tokenizer.position >= tokenizer.input.length && !tokenizer.input.endsWith('`')) {
752
683
  throw new Error(`Unterminated template literal at line ${tokenizer.line}, column ${tokenizer.column - value.length}`);
753
684
  }
754
- addToken(tokenizer, TokenType.TEMPLATE_LITERAL, value, start);
685
+ addToken(tokenizer, TokenKind.TEMPLATE, value, start);
755
686
  }
756
687
  function tokenizeCSSSelector(tokenizer) {
757
688
  const start = tokenizer.position;
@@ -766,8 +697,7 @@ function tokenizeCSSSelector(tokenizer) {
766
697
  break;
767
698
  }
768
699
  }
769
- const type = prefix === '#' ? TokenType.ID_SELECTOR : TokenType.CLASS_SELECTOR;
770
- addToken(tokenizer, type, value, start);
700
+ addToken(tokenizer, TokenKind.SELECTOR, value, start);
771
701
  }
772
702
  function tokenizeQueryReference(tokenizer) {
773
703
  const start = tokenizer.position;
@@ -782,7 +712,7 @@ function tokenizeQueryReference(tokenizer) {
782
712
  break;
783
713
  }
784
714
  }
785
- addToken(tokenizer, TokenType.QUERY_REFERENCE, value, start);
715
+ addToken(tokenizer, TokenKind.SELECTOR, value, start);
786
716
  }
787
717
  function tokenizeSymbol(tokenizer) {
788
718
  const start = tokenizer.position;
@@ -796,7 +726,7 @@ function tokenizeSymbol(tokenizer) {
796
726
  break;
797
727
  }
798
728
  }
799
- addToken(tokenizer, TokenType.SYMBOL, value, start);
729
+ addToken(tokenizer, TokenKind.SYMBOL, value, start);
800
730
  }
801
731
  function tokenizeOperator(tokenizer) {
802
732
  const start = tokenizer.position;
@@ -806,7 +736,7 @@ function tokenizeOperator(tokenizer) {
806
736
  value = "'s";
807
737
  advance(tokenizer);
808
738
  advance(tokenizer);
809
- addToken(tokenizer, TokenType.OPERATOR, value, start);
739
+ addToken(tokenizer, TokenKind.OPERATOR, value, start);
810
740
  return;
811
741
  }
812
742
  const twoChar = tokenizer.input.substring(tokenizer.position, tokenizer.position + 2);
@@ -825,17 +755,7 @@ function tokenizeOperator(tokenizer) {
825
755
  else {
826
756
  value = advance(tokenizer);
827
757
  }
828
- let type = TokenType.OPERATOR;
829
- if (COMPARISON_OPERATORS.has(value)) {
830
- type = TokenType.COMPARISON_OPERATOR;
831
- }
832
- else if (['&&', '||'].includes(value)) {
833
- type = TokenType.LOGICAL_OPERATOR;
834
- }
835
- else if (MATHEMATICAL_OPERATORS.has(value)) {
836
- type = TokenType.OPERATOR;
837
- }
838
- addToken(tokenizer, type, value, start);
758
+ addToken(tokenizer, TokenKind.OPERATOR, value, start);
839
759
  }
840
760
  function tokenizeNumberOrTime(tokenizer) {
841
761
  const start = tokenizer.position;
@@ -901,11 +821,11 @@ function tokenizeNumberOrTime(tokenizer) {
901
821
  }
902
822
  }
903
823
  if (TIME_UNITS.has(unit)) {
904
- addToken(tokenizer, TokenType.TIME_EXPRESSION, value + unit, start);
824
+ addToken(tokenizer, TokenKind.TIME, value + unit, start);
905
825
  }
906
826
  else {
907
827
  tokenizer.position = unitStart;
908
- addToken(tokenizer, TokenType.NUMBER, value, start);
828
+ addToken(tokenizer, TokenKind.NUMBER, value, start);
909
829
  }
910
830
  }
911
831
  function tokenizeIdentifier(tokenizer) {
@@ -951,7 +871,7 @@ function tokenizeGlobalVariable(tokenizer) {
951
871
  break;
952
872
  }
953
873
  }
954
- addToken(tokenizer, TokenType.GLOBAL_VAR, value, start);
874
+ addToken(tokenizer, TokenKind.IDENTIFIER, value, start);
955
875
  }
956
876
  function tryTokenizeCompoundOperator(tokenizer, firstWord, start) {
957
877
  const lowerFirst = firstWord.toLowerCase();
@@ -961,10 +881,7 @@ function tryTokenizeCompoundOperator(tokenizer, firstWord, start) {
961
881
  }
962
882
  const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
963
883
  if (prevToken &&
964
- (prevToken.kind === 'identifier' ||
965
- prevToken.kind === 'id_selector' ||
966
- prevToken.kind === 'class_selector' ||
967
- prevToken.kind === 'context_var')) {
884
+ (prevToken.kind === TokenKind.IDENTIFIER || prevToken.kind === TokenKind.SELECTOR)) {
968
885
  const nextChar = tokenizer.position < tokenizer.input.length ? tokenizer.input[tokenizer.position] : '';
969
886
  if (nextChar === "'" || nextChar === "'") {
970
887
  return false;
@@ -994,17 +911,17 @@ function tryTokenizeCompoundOperator(tokenizer, firstWord, start) {
994
911
  }
995
912
  const longestCompound = tryBuildLongestCompound(tokenizer, lowerFirst, lowerNext);
996
913
  if (longestCompound) {
997
- addToken(tokenizer, TokenType.COMPARISON_OPERATOR, longestCompound, start);
914
+ addToken(tokenizer, TokenKind.OPERATOR, longestCompound, start);
998
915
  return true;
999
916
  }
1000
917
  if (COMPARISON_OPERATORS.has(compound)) {
1001
- addToken(tokenizer, TokenType.COMPARISON_OPERATOR, compound, start);
918
+ addToken(tokenizer, TokenKind.OPERATOR, compound, start);
1002
919
  return true;
1003
920
  }
1004
921
  }
1005
922
  if (COMPARISON_OPERATORS.has(lowerFirst)) {
1006
923
  tokenizer.position = originalPosition;
1007
- addToken(tokenizer, TokenType.COMPARISON_OPERATOR, firstWord, start);
924
+ addToken(tokenizer, TokenKind.OPERATOR, firstWord, start);
1008
925
  return true;
1009
926
  }
1010
927
  tokenizer.position = originalPosition;
@@ -1051,7 +968,7 @@ function tryBuildCompoundPreposition(tokenizer, firstWord, secondWord, start) {
1051
968
  const lowerFourth = fourthWord.toLowerCase();
1052
969
  if (lowerFourth === 'of') {
1053
970
  const compound = `at the ${lowerThird} of`;
1054
- addToken(tokenizer, TokenType.KEYWORD, compound, start);
971
+ addToken(tokenizer, TokenKind.IDENTIFIER, compound, start);
1055
972
  return true;
1056
973
  }
1057
974
  }
@@ -1077,7 +994,7 @@ function tryBuildCompoundPreposition(tokenizer, firstWord, secondWord, start) {
1077
994
  const lowerThird = thirdWord.toLowerCase();
1078
995
  if (lowerThird === 'of') {
1079
996
  const compound = `at ${lowerSecond} of`;
1080
- addToken(tokenizer, TokenType.KEYWORD, compound, start);
997
+ addToken(tokenizer, TokenKind.IDENTIFIER, compound, start);
1081
998
  return true;
1082
999
  }
1083
1000
  tokenizer.position = afterSecondWord;
@@ -1089,36 +1006,18 @@ function tryBuildCompoundPreposition(tokenizer, firstWord, secondWord, start) {
1089
1006
  function classifyIdentifier(value) {
1090
1007
  const lowerValue = value.toLowerCase();
1091
1008
  if (lowerValue === 'include' || lowerValue === 'includes') {
1092
- return TokenType.COMPARISON_OPERATOR;
1009
+ return TokenKind.OPERATOR;
1093
1010
  }
1094
1011
  if (LOGICAL_OPERATORS.has(lowerValue)) {
1095
- return TokenType.LOGICAL_OPERATOR;
1012
+ return TokenKind.OPERATOR;
1096
1013
  }
1097
1014
  if (MATHEMATICAL_OPERATORS.has(value) || MATHEMATICAL_OPERATORS.has(lowerValue)) {
1098
- return TokenType.OPERATOR;
1015
+ return TokenKind.OPERATOR;
1099
1016
  }
1100
1017
  if (COMPARISON_OPERATORS.has(lowerValue)) {
1101
- return TokenType.COMPARISON_OPERATOR;
1102
- }
1103
- if (value === 'I') {
1104
- return TokenType.CONTEXT_VAR;
1018
+ return TokenKind.OPERATOR;
1105
1019
  }
1106
- if (CONTEXT_VARS.has(lowerValue)) {
1107
- return TokenType.CONTEXT_VAR;
1108
- }
1109
- if (COMMANDS.has(lowerValue)) {
1110
- return TokenType.COMMAND;
1111
- }
1112
- if (DOM_EVENTS.has(lowerValue)) {
1113
- return TokenType.EVENT;
1114
- }
1115
- if (['true', 'false', 'null', 'undefined'].includes(lowerValue)) {
1116
- return TokenType.BOOLEAN;
1117
- }
1118
- if (KEYWORDS.has(lowerValue)) {
1119
- return TokenType.KEYWORD;
1120
- }
1121
- return TokenType.IDENTIFIER;
1020
+ return TokenKind.IDENTIFIER;
1122
1021
  }
1123
1022
  function isAlpha(char) {
1124
1023
  return /[a-zA-Z]/.test(char);
@@ -1384,6 +1283,9 @@ class SemanticIntegrationAdapter {
1384
1283
  if (/\btell\b/.test(lowerInput)) {
1385
1284
  return true;
1386
1285
  }
1286
+ if (/\*[a-zA-Z]/.test(input)) {
1287
+ return true;
1288
+ }
1387
1289
  return false;
1388
1290
  }
1389
1291
  trySemanticParse(input) {
@@ -2134,6 +2036,26 @@ function createErrorNode(pos) {
2134
2036
  column: pos.column,
2135
2037
  };
2136
2038
  }
2039
+ function createErrorCommandNode(pos, message, source) {
2040
+ const diagnostic = {
2041
+ message,
2042
+ severity: 'error',
2043
+ code: 'parse-error',
2044
+ line: pos.line,
2045
+ column: pos.column,
2046
+ ...(source && { source }),
2047
+ };
2048
+ return {
2049
+ type: 'command',
2050
+ name: '__ERROR__',
2051
+ args: [],
2052
+ diagnostics: [diagnostic],
2053
+ start: pos.start,
2054
+ end: pos.end,
2055
+ line: pos.line,
2056
+ column: pos.column,
2057
+ };
2058
+ }
2137
2059
  function createProgramNode(statements) {
2138
2060
  debug.parse(`✅ createProgramNode: Called with ${statements.length} statements`);
2139
2061
  if (statements.length === 0) {
@@ -2178,10 +2100,10 @@ function isKeyword(token, keywords) {
2178
2100
  return keywords.some(kw => token.value === kw || token.value.toLowerCase() === kw);
2179
2101
  }
2180
2102
  const DEFAULT_BOUNDARY_KEYWORDS = [
2181
- KEYWORDS$1.THEN,
2182
- KEYWORDS$1.AND,
2183
- KEYWORDS$1.ELSE,
2184
- KEYWORDS$1.END,
2103
+ KEYWORDS.THEN,
2104
+ KEYWORDS.AND,
2105
+ KEYWORDS.ELSE,
2106
+ KEYWORDS.END,
2185
2107
  ];
2186
2108
  function isCommandBoundary(ctx, additionalBoundaries = []) {
2187
2109
  if (ctx.isAtEnd()) {
@@ -2491,21 +2413,21 @@ function parseTriggerCommand(ctx, identifierNode) {
2491
2413
 
2492
2414
  function parseHaltCommand(ctx, identifierNode) {
2493
2415
  const args = [];
2494
- if (ctx.check(KEYWORDS$1.THE)) {
2416
+ if (ctx.check(KEYWORDS.THE)) {
2495
2417
  const theToken = ctx.advance();
2496
2418
  args.push({
2497
2419
  type: 'identifier',
2498
- name: KEYWORDS$1.THE,
2420
+ name: KEYWORDS.THE,
2499
2421
  start: theToken.start,
2500
2422
  end: theToken.end,
2501
2423
  line: theToken.line,
2502
2424
  column: theToken.column,
2503
2425
  });
2504
- if (ctx.check(KEYWORDS$1.EVENT)) {
2426
+ if (ctx.check(KEYWORDS.EVENT)) {
2505
2427
  const eventToken = ctx.advance();
2506
2428
  args.push({
2507
2429
  type: 'identifier',
2508
- name: KEYWORDS$1.EVENT,
2430
+ name: KEYWORDS.EVENT,
2509
2431
  start: eventToken.start,
2510
2432
  end: eventToken.end,
2511
2433
  line: eventToken.line,
@@ -2527,34 +2449,34 @@ function parseRepeatCommand(ctx, commandToken) {
2527
2449
  let collection = null;
2528
2450
  let variable = null;
2529
2451
  let times = null;
2530
- if (ctx.check(KEYWORDS$1.FOR)) {
2452
+ if (ctx.check(KEYWORDS.FOR)) {
2531
2453
  ctx.advance();
2532
- loopType = KEYWORDS$1.FOR;
2454
+ loopType = KEYWORDS.FOR;
2533
2455
  const identToken = ctx.peek();
2534
2456
  if (isIdentifierLike(identToken)) {
2535
2457
  variable = identToken.value;
2536
2458
  ctx.advance();
2537
2459
  }
2538
- if (ctx.check(KEYWORDS$1.IN)) {
2460
+ if (ctx.check(KEYWORDS.IN)) {
2539
2461
  ctx.advance();
2540
2462
  collection = ctx.parseExpression();
2541
2463
  }
2542
2464
  }
2543
- else if (ctx.check(KEYWORDS$1.IN)) {
2465
+ else if (ctx.check(KEYWORDS.IN)) {
2544
2466
  ctx.advance();
2545
- loopType = KEYWORDS$1.FOR;
2467
+ loopType = KEYWORDS.FOR;
2546
2468
  variable = 'it';
2547
2469
  collection = ctx.parseExpression();
2548
2470
  }
2549
- else if (ctx.check(KEYWORDS$1.WHILE)) {
2471
+ else if (ctx.check(KEYWORDS.WHILE)) {
2550
2472
  ctx.advance();
2551
- loopType = KEYWORDS$1.WHILE;
2473
+ loopType = KEYWORDS.WHILE;
2552
2474
  condition = ctx.parseExpression();
2553
2475
  }
2554
- else if (ctx.check(KEYWORDS$1.UNTIL)) {
2476
+ else if (ctx.check(KEYWORDS.UNTIL)) {
2555
2477
  ctx.advance();
2556
- loopType = KEYWORDS$1.UNTIL;
2557
- if (ctx.check(KEYWORDS$1.EVENT)) {
2478
+ loopType = KEYWORDS.UNTIL;
2479
+ if (ctx.check(KEYWORDS.EVENT)) {
2558
2480
  ctx.advance();
2559
2481
  loopType = 'until-event';
2560
2482
  const eventToken = ctx.peek();
@@ -2571,11 +2493,11 @@ function parseRepeatCommand(ctx, commandToken) {
2571
2493
  throw new Error('Expected event name after "event"');
2572
2494
  }
2573
2495
  debug.parse('🔍 Checking for "from", current token:', ctx.peek().value);
2574
- if (ctx.check(KEYWORDS$1.FROM)) {
2496
+ if (ctx.check(KEYWORDS.FROM)) {
2575
2497
  debug.parse('✅ Found "from", advancing...');
2576
2498
  ctx.advance();
2577
2499
  debug.parse('📍 After consuming "from", current token:', ctx.peek().value);
2578
- if (consumeOptionalKeyword(ctx, KEYWORDS$1.THE)) {
2500
+ if (consumeOptionalKeyword(ctx, KEYWORDS.THE)) {
2579
2501
  debug.parse('✅ Found "the", advancing...');
2580
2502
  }
2581
2503
  const beforePrimary = ctx.peek();
@@ -2595,27 +2517,27 @@ function parseRepeatCommand(ctx, commandToken) {
2595
2517
  condition = ctx.parseExpression();
2596
2518
  }
2597
2519
  }
2598
- else if (ctx.check(KEYWORDS$1.FOREVER)) {
2520
+ else if (ctx.check(KEYWORDS.FOREVER)) {
2599
2521
  ctx.advance();
2600
- loopType = KEYWORDS$1.FOREVER;
2522
+ loopType = KEYWORDS.FOREVER;
2601
2523
  }
2602
2524
  else {
2603
2525
  times = ctx.parseExpression();
2604
- if (ctx.check(KEYWORDS$1.TIMES)) {
2526
+ if (ctx.check(KEYWORDS.TIMES)) {
2605
2527
  ctx.advance();
2606
- loopType = KEYWORDS$1.TIMES;
2528
+ loopType = KEYWORDS.TIMES;
2607
2529
  }
2608
2530
  }
2609
2531
  let indexVariable = null;
2610
- if (ctx.check(KEYWORDS$1.WITH)) {
2532
+ if (ctx.check(KEYWORDS.WITH)) {
2611
2533
  const nextToken = ctx.peekAt(1);
2612
- if (nextToken && nextToken.value.toLowerCase() === KEYWORDS$1.INDEX) {
2534
+ if (nextToken && nextToken.value.toLowerCase() === KEYWORDS.INDEX) {
2613
2535
  ctx.advance();
2614
2536
  ctx.advance();
2615
- indexVariable = KEYWORDS$1.INDEX;
2537
+ indexVariable = KEYWORDS.INDEX;
2616
2538
  }
2617
2539
  }
2618
- else if (ctx.check(KEYWORDS$1.INDEX)) {
2540
+ else if (ctx.check(KEYWORDS.INDEX)) {
2619
2541
  ctx.advance();
2620
2542
  const indexToken = ctx.peek();
2621
2543
  if (isIdentifierLike(indexToken)) {
@@ -2623,7 +2545,7 @@ function parseRepeatCommand(ctx, commandToken) {
2623
2545
  ctx.advance();
2624
2546
  }
2625
2547
  else {
2626
- indexVariable = KEYWORDS$1.INDEX;
2548
+ indexVariable = KEYWORDS.INDEX;
2627
2549
  }
2628
2550
  }
2629
2551
  const commands = ctx.parseCommandListUntilEnd();
@@ -2671,14 +2593,14 @@ function parseIfCommand(ctx, commandToken) {
2671
2593
  const maxThenLookahead = 500;
2672
2594
  for (let i = 0; i < maxThenLookahead && !ctx.isAtEnd(); i++) {
2673
2595
  const token = ctx.peek();
2674
- if (token.value === KEYWORDS$1.THEN) {
2596
+ if (token.value === KEYWORDS.THEN) {
2675
2597
  hasThen = true;
2676
2598
  break;
2677
2599
  }
2678
- if (token.value === KEYWORDS$1.END ||
2679
- token.value === KEYWORDS$1.BEHAVIOR ||
2680
- token.value === KEYWORDS$1.DEF ||
2681
- token.value === KEYWORDS$1.ON) {
2600
+ if (token.value === KEYWORDS.END ||
2601
+ token.value === KEYWORDS.BEHAVIOR ||
2602
+ token.value === KEYWORDS.DEF ||
2603
+ token.value === KEYWORDS.ON) {
2682
2604
  break;
2683
2605
  }
2684
2606
  ctx.advance();
@@ -2692,12 +2614,12 @@ function parseIfCommand(ctx, commandToken) {
2692
2614
  while (!ctx.isAtEnd() && ctx.current - savedPosition < maxLookahead) {
2693
2615
  const token = ctx.peek();
2694
2616
  const tokenValue = token.value?.toLowerCase();
2695
- if (tokenValue === KEYWORDS$1.BEHAVIOR ||
2696
- tokenValue === KEYWORDS$1.DEF ||
2697
- tokenValue === KEYWORDS$1.ON) {
2617
+ if (tokenValue === KEYWORDS.BEHAVIOR ||
2618
+ tokenValue === KEYWORDS.DEF ||
2619
+ tokenValue === KEYWORDS.ON) {
2698
2620
  break;
2699
2621
  }
2700
- if (tokenValue === KEYWORDS$1.ELSE || tokenValue === KEYWORDS$1.END) {
2622
+ if (tokenValue === KEYWORDS.ELSE || tokenValue === KEYWORDS.END) {
2701
2623
  if (token.line === ifStatementLine) {
2702
2624
  hasImplicitMultiLineEnd = true;
2703
2625
  }
@@ -2725,7 +2647,7 @@ function parseIfCommand(ctx, commandToken) {
2725
2647
  while (!ctx.isAtEnd() &&
2726
2648
  !ctx.checkIsCommand() &&
2727
2649
  !ctx.isCommand(ctx.peek().value) &&
2728
- !ctx.check(KEYWORDS$1.THEN) &&
2650
+ !ctx.check(KEYWORDS.THEN) &&
2729
2651
  iterations < maxIterations) {
2730
2652
  const beforePos = ctx.savePosition();
2731
2653
  conditionTokens.push(ctx.parseLogicalAnd());
@@ -2757,7 +2679,7 @@ function parseIfCommand(ctx, commandToken) {
2757
2679
  ctx.advance();
2758
2680
  }
2759
2681
  const thenCommands = [];
2760
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.ELSE) && !ctx.check(KEYWORDS$1.END)) {
2682
+ while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.ELSE) && !ctx.check(KEYWORDS.END)) {
2761
2683
  if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
2762
2684
  ctx.advance();
2763
2685
  const cmd = ctx.parseCommand();
@@ -2779,9 +2701,9 @@ function parseIfCommand(ctx, commandToken) {
2779
2701
  column: commandToken.column,
2780
2702
  }));
2781
2703
  let consumedElseIf = false;
2782
- if (ctx.check(KEYWORDS$1.ELSE)) {
2704
+ if (ctx.check(KEYWORDS.ELSE)) {
2783
2705
  ctx.advance();
2784
- if (ctx.check(KEYWORDS$1.IF)) {
2706
+ if (ctx.check(KEYWORDS.IF)) {
2785
2707
  const ifToken = ctx.peek();
2786
2708
  ctx.advance();
2787
2709
  const elseIfCommand = parseIfCommand(ctx, ifToken);
@@ -2795,7 +2717,7 @@ function parseIfCommand(ctx, commandToken) {
2795
2717
  }
2796
2718
  else {
2797
2719
  const elseCommands = [];
2798
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.END)) {
2720
+ while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.END)) {
2799
2721
  if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
2800
2722
  ctx.advance();
2801
2723
  const cmd = ctx.parseCommand();
@@ -2816,7 +2738,7 @@ function parseIfCommand(ctx, commandToken) {
2816
2738
  }
2817
2739
  }
2818
2740
  if (!consumedElseIf) {
2819
- ctx.consume(KEYWORDS$1.END, "Expected 'end' after if block");
2741
+ ctx.consume(KEYWORDS.END, "Expected 'end' after if block");
2820
2742
  }
2821
2743
  }
2822
2744
  else {
@@ -2843,7 +2765,7 @@ function parseForCommand(ctx, commandToken) {
2843
2765
  const args = [];
2844
2766
  let variable = null;
2845
2767
  let collection = null;
2846
- if (ctx.check(KEYWORDS$1.EACH)) {
2768
+ if (ctx.check(KEYWORDS.EACH)) {
2847
2769
  ctx.advance();
2848
2770
  }
2849
2771
  const identToken = ctx.peek();
@@ -2854,7 +2776,7 @@ function parseForCommand(ctx, commandToken) {
2854
2776
  else {
2855
2777
  throw new Error('Expected variable name after "for"');
2856
2778
  }
2857
- if (!ctx.check(KEYWORDS$1.IN)) {
2779
+ if (!ctx.check(KEYWORDS.IN)) {
2858
2780
  throw new Error('Expected "in" after variable name in for loop');
2859
2781
  }
2860
2782
  ctx.advance();
@@ -2863,15 +2785,15 @@ function parseForCommand(ctx, commandToken) {
2863
2785
  throw new Error('Expected collection expression after "in"');
2864
2786
  }
2865
2787
  let indexVariable = null;
2866
- if (ctx.check(KEYWORDS$1.WITH)) {
2788
+ if (ctx.check(KEYWORDS.WITH)) {
2867
2789
  const nextToken = ctx.peekAt(1);
2868
- if (nextToken && nextToken.value.toLowerCase() === KEYWORDS$1.INDEX) {
2790
+ if (nextToken && nextToken.value.toLowerCase() === KEYWORDS.INDEX) {
2869
2791
  ctx.advance();
2870
2792
  ctx.advance();
2871
- indexVariable = KEYWORDS$1.INDEX;
2793
+ indexVariable = KEYWORDS.INDEX;
2872
2794
  }
2873
2795
  }
2874
- else if (ctx.check(KEYWORDS$1.INDEX)) {
2796
+ else if (ctx.check(KEYWORDS.INDEX)) {
2875
2797
  ctx.advance();
2876
2798
  const indexToken = ctx.peek();
2877
2799
  if (isIdentifierLike(indexToken)) {
@@ -2879,7 +2801,7 @@ function parseForCommand(ctx, commandToken) {
2879
2801
  ctx.advance();
2880
2802
  }
2881
2803
  else {
2882
- indexVariable = KEYWORDS$1.INDEX;
2804
+ indexVariable = KEYWORDS.INDEX;
2883
2805
  }
2884
2806
  }
2885
2807
  const commands = ctx.parseCommandListUntilEnd();
@@ -2991,7 +2913,7 @@ function parseTransitionCommand(ctx, commandToken) {
2991
2913
  throw new Error('Transition command requires a CSS property');
2992
2914
  }
2993
2915
  args.push(property);
2994
- if (!ctx.check(KEYWORDS$1.TO)) {
2916
+ if (!ctx.check(KEYWORDS.TO)) {
2995
2917
  throw new Error('Expected "to" keyword after property in transition command');
2996
2918
  }
2997
2919
  ctx.advance();
@@ -3002,7 +2924,7 @@ function parseTransitionCommand(ctx, commandToken) {
3002
2924
  const duration = ctx.parsePrimary();
3003
2925
  modifiers['over'] = duration;
3004
2926
  }
3005
- if (ctx.check(KEYWORDS$1.WITH)) {
2927
+ if (ctx.check(KEYWORDS.WITH)) {
3006
2928
  ctx.advance();
3007
2929
  const timingFunction = ctx.parsePrimary();
3008
2930
  modifiers['with'] = timingFunction;
@@ -3016,11 +2938,11 @@ function parseTransitionCommand(ctx, commandToken) {
3016
2938
 
3017
2939
  function parseRemoveCommand(ctx, identifierNode) {
3018
2940
  const args = [];
3019
- const classArg = parseOneArgument(ctx, [KEYWORDS$1.FROM]);
2941
+ const classArg = parseOneArgument(ctx, [KEYWORDS.FROM]);
3020
2942
  if (classArg) {
3021
2943
  args.push(classArg);
3022
2944
  }
3023
- consumeKeywordToArgs(ctx, KEYWORDS$1.FROM, args);
2945
+ consumeKeywordToArgs(ctx, KEYWORDS.FROM, args);
3024
2946
  const targetArg = parseOneArgument(ctx);
3025
2947
  if (targetArg) {
3026
2948
  args.push(targetArg);
@@ -3032,19 +2954,19 @@ function parseRemoveCommand(ctx, identifierNode) {
3032
2954
  }
3033
2955
  function parseToggleCommand(ctx, identifierNode) {
3034
2956
  const args = [];
3035
- if (ctx.check(KEYWORDS$1.BETWEEN)) {
2957
+ if (ctx.check(KEYWORDS.BETWEEN)) {
3036
2958
  ctx.advance();
3037
2959
  args.push(ctx.createIdentifier('between'));
3038
- const firstArg = parseOneArgument(ctx, [KEYWORDS$1.AND]);
2960
+ const firstArg = parseOneArgument(ctx, [KEYWORDS.AND]);
3039
2961
  if (firstArg) {
3040
2962
  args.push(firstArg);
3041
2963
  }
3042
- consumeKeywordToArgs(ctx, KEYWORDS$1.AND, args);
3043
- const secondArg = parseOneArgument(ctx, [KEYWORDS$1.FROM, KEYWORDS$1.ON, KEYWORDS$1.FOR]);
2964
+ consumeKeywordToArgs(ctx, KEYWORDS.AND, args);
2965
+ const secondArg = parseOneArgument(ctx, [KEYWORDS.FROM, KEYWORDS.ON, KEYWORDS.FOR]);
3044
2966
  if (secondArg) {
3045
2967
  args.push(secondArg);
3046
2968
  }
3047
- const preposition = consumeOneOfKeywordsToArgs(ctx, [KEYWORDS$1.FROM, KEYWORDS$1.ON], args);
2969
+ const preposition = consumeOneOfKeywordsToArgs(ctx, [KEYWORDS.FROM, KEYWORDS.ON], args);
3048
2970
  if (preposition) {
3049
2971
  const targetArg = parseOneArgument(ctx);
3050
2972
  if (targetArg) {
@@ -3056,11 +2978,11 @@ function parseToggleCommand(ctx, identifierNode) {
3056
2978
  .endingAt(ctx.getPosition())
3057
2979
  .build();
3058
2980
  }
3059
- const classArg = parseOneArgument(ctx, [KEYWORDS$1.FROM, KEYWORDS$1.ON]);
2981
+ const classArg = parseOneArgument(ctx, [KEYWORDS.FROM, KEYWORDS.ON]);
3060
2982
  if (classArg) {
3061
2983
  args.push(classArg);
3062
2984
  }
3063
- const preposition = consumeOneOfKeywordsToArgs(ctx, [KEYWORDS$1.FROM, KEYWORDS$1.ON], args);
2985
+ const preposition = consumeOneOfKeywordsToArgs(ctx, [KEYWORDS.FROM, KEYWORDS.ON], args);
3064
2986
  if (preposition) {
3065
2987
  const targetArg = parseOneArgument(ctx);
3066
2988
  if (targetArg) {
@@ -3078,12 +3000,12 @@ function parseAddCommand(ctx, commandToken) {
3078
3000
  args.push(ctx.parseCSSObjectLiteral());
3079
3001
  }
3080
3002
  else {
3081
- const classArg = parseOneArgument(ctx, [KEYWORDS$1.TO]);
3003
+ const classArg = parseOneArgument(ctx, [KEYWORDS.TO]);
3082
3004
  if (classArg) {
3083
3005
  args.push(classArg);
3084
3006
  }
3085
3007
  }
3086
- if (ctx.check(KEYWORDS$1.TO)) {
3008
+ if (ctx.check(KEYWORDS.TO)) {
3087
3009
  ctx.advance();
3088
3010
  const targetArg = parseOneArgument(ctx);
3089
3011
  if (targetArg) {
@@ -3116,19 +3038,19 @@ function parsePutCommand(ctx, identifierNode) {
3116
3038
  operation = PUT_OPERATIONS.AT_END_OF;
3117
3039
  }
3118
3040
  else if (operation === PUT_OPERATIONS.AT) {
3119
- if (ctx.check(KEYWORDS$1.START) || ctx.check(KEYWORDS$1.THE)) {
3120
- consumeOptionalKeyword(ctx, KEYWORDS$1.THE);
3121
- if (ctx.check(KEYWORDS$1.START)) {
3041
+ if (ctx.check(KEYWORDS.START) || ctx.check(KEYWORDS.THE)) {
3042
+ consumeOptionalKeyword(ctx, KEYWORDS.THE);
3043
+ if (ctx.check(KEYWORDS.START)) {
3122
3044
  ctx.advance();
3123
- if (ctx.check(KEYWORDS$1.OF)) {
3045
+ if (ctx.check(KEYWORDS.OF)) {
3124
3046
  ctx.advance();
3125
3047
  operation = PUT_OPERATIONS.AT_START_OF;
3126
3048
  }
3127
3049
  }
3128
3050
  }
3129
- else if (ctx.check(KEYWORDS$1.END)) {
3051
+ else if (ctx.check(KEYWORDS.END)) {
3130
3052
  ctx.advance();
3131
- if (ctx.check(KEYWORDS$1.OF)) {
3053
+ if (ctx.check(KEYWORDS.OF)) {
3132
3054
  ctx.advance();
3133
3055
  operation = PUT_OPERATIONS.AT_END_OF;
3134
3056
  }
@@ -3178,7 +3100,7 @@ function parseSwapCommand(ctx, identifierNode) {
3178
3100
  .endingAt(ctx.getPosition())
3179
3101
  .build();
3180
3102
  }
3181
- if (!ctx.isAtEnd() && ctx.check(KEYWORDS$1.OF)) {
3103
+ if (!ctx.isAtEnd() && ctx.check(KEYWORDS.OF)) {
3182
3104
  ctx.advance();
3183
3105
  args.push(ctx.createIdentifier('of'));
3184
3106
  }
@@ -3186,7 +3108,7 @@ function parseSwapCommand(ctx, identifierNode) {
3186
3108
  if (targetExpr) {
3187
3109
  args.push(targetExpr);
3188
3110
  }
3189
- if (!ctx.isAtEnd() && ctx.check(KEYWORDS$1.WITH)) {
3111
+ if (!ctx.isAtEnd() && ctx.check(KEYWORDS.WITH)) {
3190
3112
  ctx.advance();
3191
3113
  args.push(ctx.createIdentifier('with'));
3192
3114
  const contentExpr = ctx.parseExpression();
@@ -3252,9 +3174,9 @@ function parseWaitCommand(ctx, commandToken) {
3252
3174
  }
3253
3175
  } while (!ctx.isAtEnd());
3254
3176
  let eventTarget = null;
3255
- if (ctx.check(KEYWORDS$1.FROM)) {
3177
+ if (ctx.check(KEYWORDS.FROM)) {
3256
3178
  ctx.advance();
3257
- consumeOptionalKeyword(ctx, KEYWORDS$1.THE);
3179
+ consumeOptionalKeyword(ctx, KEYWORDS.THE);
3258
3180
  eventTarget = ctx.parsePrimary();
3259
3181
  }
3260
3182
  const eventPos = {
@@ -3372,7 +3294,7 @@ function parseColonVariable(ctx) {
3372
3294
  };
3373
3295
  }
3374
3296
  function parseScopedVariable(ctx) {
3375
- if (!ctx.check(KEYWORDS$1.GLOBAL) && !ctx.check(KEYWORDS$1.LOCAL))
3297
+ if (!ctx.check(KEYWORDS.GLOBAL) && !ctx.check(KEYWORDS.LOCAL))
3376
3298
  return null;
3377
3299
  const scopeToken = ctx.advance();
3378
3300
  const variableToken = ctx.advance();
@@ -3385,15 +3307,15 @@ function parseScopedVariable(ctx) {
3385
3307
  };
3386
3308
  }
3387
3309
  function parsePropertyOfTarget(ctx, startPosition) {
3388
- if (!ctx.check(KEYWORDS$1.THE))
3310
+ if (!ctx.check(KEYWORDS.THE))
3389
3311
  return null;
3390
3312
  const thePosition = ctx.savePosition();
3391
3313
  ctx.advance();
3392
3314
  const nextToken = ctx.peek();
3393
3315
  const tokenAfterNext = ctx.peekAt(1);
3394
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS$1.OF) {
3316
+ if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.OF) {
3395
3317
  const propertyToken = ctx.advance();
3396
- if (ctx.check(KEYWORDS$1.OF)) {
3318
+ if (ctx.check(KEYWORDS.OF)) {
3397
3319
  ctx.advance();
3398
3320
  const targetToken = ctx.advance();
3399
3321
  const isIdSelector = targetToken.value.startsWith('#');
@@ -3418,7 +3340,7 @@ function parsePropertyOfTarget(ctx, startPosition) {
3418
3340
  ctx.restorePosition(startPosition);
3419
3341
  return null;
3420
3342
  }
3421
- if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS$1.TO) {
3343
+ if (nextToken && tokenAfterNext && tokenAfterNext.value === KEYWORDS.TO) {
3422
3344
  const variableToken = ctx.advance();
3423
3345
  return {
3424
3346
  type: 'identifier',
@@ -3446,11 +3368,11 @@ function parseTargetFallback(ctx) {
3446
3368
  }
3447
3369
  const targetTokens = [];
3448
3370
  while (!ctx.isAtEnd() &&
3449
- !ctx.check(KEYWORDS$1.TO) &&
3450
- !ctx.check(KEYWORDS$1.THEN) &&
3451
- !ctx.check(KEYWORDS$1.AND) &&
3452
- !ctx.check(KEYWORDS$1.ELSE) &&
3453
- !ctx.check(KEYWORDS$1.END)) {
3371
+ !ctx.check(KEYWORDS.TO) &&
3372
+ !ctx.check(KEYWORDS.THEN) &&
3373
+ !ctx.check(KEYWORDS.AND) &&
3374
+ !ctx.check(KEYWORDS.ELSE) &&
3375
+ !ctx.check(KEYWORDS.END)) {
3454
3376
  targetTokens.push(ctx.parsePrimary());
3455
3377
  }
3456
3378
  if (targetTokens.length === 0) {
@@ -3458,8 +3380,8 @@ function parseTargetFallback(ctx) {
3458
3380
  }
3459
3381
  if (targetTokens.length >= 4) {
3460
3382
  const nodeStr = (node, key) => node[key];
3461
- if (nodeStr(targetTokens[0], 'value') === KEYWORDS$1.THE &&
3462
- nodeStr(targetTokens[2], 'value') === KEYWORDS$1.OF) {
3383
+ if (nodeStr(targetTokens[0], 'value') === KEYWORDS.THE &&
3384
+ nodeStr(targetTokens[2], 'value') === KEYWORDS.OF) {
3463
3385
  const propToken = targetTokens[1];
3464
3386
  const targetToken = targetTokens[3];
3465
3387
  return {
@@ -3512,7 +3434,7 @@ function parseSetCommand(ctx, identifierNode) {
3512
3434
  targetExpression = fallback.expression;
3513
3435
  fallbackTokens = fallback.tokens;
3514
3436
  }
3515
- if (!ctx.check(KEYWORDS$1.TO)) {
3437
+ if (!ctx.check(KEYWORDS.TO)) {
3516
3438
  const found = ctx.isAtEnd() ? 'end of input' : ctx.peek().value;
3517
3439
  throw new Error(`Expected 'to' in set command, found: ${found}`);
3518
3440
  }
@@ -3525,7 +3447,7 @@ function parseSetCommand(ctx, identifierNode) {
3525
3447
  else if (fallbackTokens.length > 0) {
3526
3448
  finalArgs.push(...fallbackTokens);
3527
3449
  }
3528
- finalArgs.push(ctx.createIdentifier(KEYWORDS$1.TO));
3450
+ finalArgs.push(ctx.createIdentifier(KEYWORDS.TO));
3529
3451
  if (value) {
3530
3452
  finalArgs.push(value);
3531
3453
  }
@@ -3539,7 +3461,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
3539
3461
  const isIncrement = commandName === 'increment';
3540
3462
  const operator = isIncrement ? '+' : '-';
3541
3463
  let hasGlobal = false;
3542
- if (ctx.check(KEYWORDS$1.GLOBAL)) {
3464
+ if (ctx.check(KEYWORDS.GLOBAL)) {
3543
3465
  hasGlobal = true;
3544
3466
  ctx.advance();
3545
3467
  }
@@ -3548,7 +3470,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
3548
3470
  throw new Error(`Expected variable or expression after ${commandName}`);
3549
3471
  }
3550
3472
  let amount;
3551
- if (ctx.check(KEYWORDS$1.BY)) {
3473
+ if (ctx.check(KEYWORDS.BY)) {
3552
3474
  ctx.advance();
3553
3475
  const parsedAmount = ctx.parseExpression();
3554
3476
  if (!parsedAmount) {
@@ -3579,7 +3501,7 @@ function parseIncrementDecrementCommand(ctx, commandToken) {
3579
3501
  column: commandToken.column,
3580
3502
  };
3581
3503
  const binaryExpr = createBinaryExpression(operator, target, amount, pos);
3582
- const toIdentifier = createIdentifier(KEYWORDS$1.TO, pos);
3504
+ const toIdentifier = createIdentifier(KEYWORDS.TO, pos);
3583
3505
  const args = [targetWithScope, toIdentifier, binaryExpr];
3584
3506
  return CommandNodeBuilder.from(commandToken)
3585
3507
  .withName('set')
@@ -3796,12 +3718,12 @@ function parseJsCommand(ctx, identifierNode) {
3796
3718
  ctx.consume(')', 'Expected ) after js parameters');
3797
3719
  }
3798
3720
  const jsCodeStart = ctx.peek().start;
3799
- while (!ctx.check(KEYWORDS$1.END) && !ctx.isAtEnd()) {
3721
+ while (!ctx.check(KEYWORDS.END) && !ctx.isAtEnd()) {
3800
3722
  ctx.advance();
3801
3723
  }
3802
3724
  const endToken = ctx.peek();
3803
3725
  const jsCodeEnd = endToken.start;
3804
- ctx.consume(KEYWORDS$1.END, 'Expected end after js code body');
3726
+ ctx.consume(KEYWORDS.END, 'Expected end after js code body');
3805
3727
  const rawSlice = ctx.getInputSlice(jsCodeStart, jsCodeEnd);
3806
3728
  const code = rawSlice.trim();
3807
3729
  const codeNode = {
@@ -3847,10 +3769,10 @@ function parseTellCommand(ctx, identifierNode) {
3847
3769
  catch {
3848
3770
  break;
3849
3771
  }
3850
- if (ctx.match(KEYWORDS$1.AND)) {
3772
+ if (ctx.match(KEYWORDS.AND)) {
3851
3773
  continue;
3852
3774
  }
3853
- if (ctx.check(KEYWORDS$1.THEN) || ctx.check(KEYWORDS$1.ELSE) || ctx.check(KEYWORDS$1.END)) {
3775
+ if (ctx.check(KEYWORDS.THEN) || ctx.check(KEYWORDS.ELSE) || ctx.check(KEYWORDS.END)) {
3854
3776
  break;
3855
3777
  }
3856
3778
  if (ctx.checkIsCommand()) {
@@ -3871,6 +3793,292 @@ function parseTellCommand(ctx, identifierNode) {
3871
3793
  .build();
3872
3794
  }
3873
3795
 
3796
+ const STOP_TOKENS = new Set([
3797
+ 'then',
3798
+ 'end',
3799
+ 'to',
3800
+ 'into',
3801
+ 'on',
3802
+ 'with',
3803
+ 'from',
3804
+ 'in',
3805
+ 'by',
3806
+ 'for',
3807
+ 'while',
3808
+ 'until',
3809
+ 'unless',
3810
+ 'else',
3811
+ 'catch',
3812
+ 'finally',
3813
+ ]);
3814
+ const STOP_DELIMITERS = new Set([')', ']', '}', ',']);
3815
+ function mergeFragments(...fragments) {
3816
+ const merged = new Map();
3817
+ for (const fragment of fragments) {
3818
+ for (const [key, entry] of fragment) {
3819
+ const existing = merged.get(key);
3820
+ if (existing) {
3821
+ merged.set(key, {
3822
+ prefix: entry.prefix ?? existing.prefix,
3823
+ infix: entry.infix ?? existing.infix,
3824
+ });
3825
+ }
3826
+ else {
3827
+ merged.set(key, { ...entry });
3828
+ }
3829
+ }
3830
+ }
3831
+ return merged;
3832
+ }
3833
+ function leftAssoc(bp, handler) {
3834
+ return {
3835
+ infix: {
3836
+ bp: [bp, bp + 1],
3837
+ handler: handler ??
3838
+ ((left, token, ctx) => ({
3839
+ type: 'binaryExpression',
3840
+ operator: token.value,
3841
+ left,
3842
+ right: ctx.parseExpr(bp + 1),
3843
+ start: left.start,
3844
+ })),
3845
+ },
3846
+ };
3847
+ }
3848
+ function rightAssoc(bp, handler) {
3849
+ return {
3850
+ infix: {
3851
+ bp: [bp + 1, bp],
3852
+ handler: ((left, token, ctx) => ({
3853
+ type: 'binaryExpression',
3854
+ operator: token.value,
3855
+ left,
3856
+ right: ctx.parseExpr(bp),
3857
+ start: left.start,
3858
+ })),
3859
+ },
3860
+ };
3861
+ }
3862
+ function prefix(bp, handler) {
3863
+ return {
3864
+ prefix: {
3865
+ bp,
3866
+ handler: handler ??
3867
+ ((token, ctx) => ({
3868
+ type: 'unaryExpression',
3869
+ operator: token.value,
3870
+ operand: ctx.parseExpr(bp),
3871
+ start: token.start,
3872
+ })),
3873
+ },
3874
+ };
3875
+ }
3876
+ const CORE_FRAGMENT = new Map([
3877
+ ['or', leftAssoc(10)],
3878
+ ['||', leftAssoc(10)],
3879
+ ['and', leftAssoc(20)],
3880
+ ['&&', leftAssoc(20)],
3881
+ ['==', leftAssoc(30)],
3882
+ ['!=', leftAssoc(30)],
3883
+ ['<', leftAssoc(30)],
3884
+ ['>', leftAssoc(30)],
3885
+ ['<=', leftAssoc(30)],
3886
+ ['>=', leftAssoc(30)],
3887
+ ['is', leftAssoc(30)],
3888
+ ['matches', leftAssoc(30)],
3889
+ ['contains', leftAssoc(30)],
3890
+ ['+', { ...leftAssoc(40), ...prefix(80) }],
3891
+ ['-', { ...leftAssoc(40), ...prefix(80) }],
3892
+ ['*', leftAssoc(50)],
3893
+ ['/', leftAssoc(50)],
3894
+ ['%', leftAssoc(50)],
3895
+ ['mod', leftAssoc(50)],
3896
+ ['^', rightAssoc(60)],
3897
+ ['**', rightAssoc(60)],
3898
+ [
3899
+ 'as',
3900
+ leftAssoc(70, (left, _token, ctx) => ({
3901
+ type: 'asExpression',
3902
+ expression: left,
3903
+ targetType: ctx.parseExpr(71),
3904
+ start: left.start,
3905
+ })),
3906
+ ],
3907
+ ['not', prefix(80)],
3908
+ ['!', prefix(80)],
3909
+ ['no', prefix(80)],
3910
+ ]);
3911
+ const POSITIONAL_FRAGMENT = new Map([
3912
+ [
3913
+ 'first',
3914
+ prefix(85, (token, ctx) => ({
3915
+ type: 'positionalExpression',
3916
+ position: 'first',
3917
+ operand: ctx.parseExpr(85),
3918
+ start: token.start,
3919
+ })),
3920
+ ],
3921
+ [
3922
+ 'last',
3923
+ prefix(85, (token, ctx) => ({
3924
+ type: 'positionalExpression',
3925
+ position: 'last',
3926
+ operand: ctx.parseExpr(85),
3927
+ start: token.start,
3928
+ })),
3929
+ ],
3930
+ ]);
3931
+ const PROPERTY_FRAGMENT = new Map([
3932
+ [
3933
+ '.',
3934
+ {
3935
+ infix: {
3936
+ bp: [90, 91],
3937
+ handler: (left, _token, ctx) => {
3938
+ const propToken = ctx.advance();
3939
+ return {
3940
+ type: 'propertyAccess',
3941
+ object: left,
3942
+ property: propToken.value,
3943
+ start: left.start,
3944
+ };
3945
+ },
3946
+ },
3947
+ },
3948
+ ],
3949
+ [
3950
+ '?.',
3951
+ {
3952
+ infix: {
3953
+ bp: [90, 91],
3954
+ handler: (left, _token, ctx) => {
3955
+ const propToken = ctx.advance();
3956
+ return {
3957
+ type: 'optionalChain',
3958
+ object: left,
3959
+ property: propToken.value,
3960
+ start: left.start,
3961
+ };
3962
+ },
3963
+ },
3964
+ },
3965
+ ],
3966
+ [
3967
+ "'s",
3968
+ {
3969
+ infix: {
3970
+ bp: [95, 96],
3971
+ handler: (left, _token, ctx) => {
3972
+ const propToken = ctx.advance();
3973
+ return {
3974
+ type: 'possessiveExpression',
3975
+ object: left,
3976
+ property: propToken.value,
3977
+ start: left.start,
3978
+ };
3979
+ },
3980
+ },
3981
+ },
3982
+ ],
3983
+ ]);
3984
+ const PARSER_COMPARISON_FRAGMENT = new Map([
3985
+ ['===', leftAssoc(30)],
3986
+ ['!==', leftAssoc(30)],
3987
+ ['is not', leftAssoc(30)],
3988
+ ['is a', leftAssoc(30)],
3989
+ ['is an', leftAssoc(30)],
3990
+ ['is not a', leftAssoc(30)],
3991
+ ['is not an', leftAssoc(30)],
3992
+ ['is in', leftAssoc(30)],
3993
+ ['is not in', leftAssoc(30)],
3994
+ ['has', leftAssoc(30)],
3995
+ ['have', leftAssoc(30)],
3996
+ ['match', leftAssoc(30)],
3997
+ ['include', leftAssoc(30)],
3998
+ ['includes', leftAssoc(30)],
3999
+ ['equals', leftAssoc(30)],
4000
+ ['does not contain', leftAssoc(30)],
4001
+ ['does not include', leftAssoc(30)],
4002
+ ['in', leftAssoc(30)],
4003
+ ['of', leftAssoc(30)],
4004
+ ['really', leftAssoc(30)],
4005
+ [
4006
+ 'exists',
4007
+ {
4008
+ prefix: {
4009
+ bp: 80,
4010
+ handler: (token, ctx) => ({
4011
+ type: 'unaryExpression',
4012
+ operator: token.value,
4013
+ operand: ctx.parseExpr(80),
4014
+ start: token.start,
4015
+ }),
4016
+ },
4017
+ infix: {
4018
+ bp: [30, 31],
4019
+ handler: (left, token) => ({
4020
+ type: 'unaryExpression',
4021
+ operator: token.value,
4022
+ operand: left,
4023
+ prefix: false,
4024
+ start: left.start,
4025
+ }),
4026
+ },
4027
+ },
4028
+ ],
4029
+ [
4030
+ 'does not exist',
4031
+ {
4032
+ infix: {
4033
+ bp: [30, 31],
4034
+ handler: (left, token) => ({
4035
+ type: 'unaryExpression',
4036
+ operator: token.value,
4037
+ operand: left,
4038
+ prefix: false,
4039
+ start: left.start,
4040
+ }),
4041
+ },
4042
+ },
4043
+ ],
4044
+ [
4045
+ 'is empty',
4046
+ {
4047
+ infix: {
4048
+ bp: [30, 31],
4049
+ handler: (left, token) => ({
4050
+ type: 'unaryExpression',
4051
+ operator: token.value,
4052
+ operand: left,
4053
+ prefix: false,
4054
+ start: left.start,
4055
+ }),
4056
+ },
4057
+ },
4058
+ ],
4059
+ [
4060
+ 'is not empty',
4061
+ {
4062
+ infix: {
4063
+ bp: [30, 31],
4064
+ handler: (left, token) => ({
4065
+ type: 'unaryExpression',
4066
+ operator: token.value,
4067
+ operand: left,
4068
+ prefix: false,
4069
+ start: left.start,
4070
+ }),
4071
+ },
4072
+ },
4073
+ ],
4074
+ ['some', prefix(80)],
4075
+ ]);
4076
+ const ASSIGNMENT_FRAGMENT = new Map([
4077
+ ['=', rightAssoc(5)],
4078
+ ]);
4079
+ mergeFragments(CORE_FRAGMENT, POSITIONAL_FRAGMENT, PROPERTY_FRAGMENT);
4080
+ const PARSER_TABLE = mergeFragments(CORE_FRAGMENT, PARSER_COMPARISON_FRAGMENT, ASSIGNMENT_FRAGMENT);
4081
+
3874
4082
  function parseTimeToMs(timeStr) {
3875
4083
  if (timeStr.endsWith('ms'))
3876
4084
  return parseInt(timeStr, 10);
@@ -3889,7 +4097,9 @@ function parseTimeToMs(timeStr) {
3889
4097
  class Parser {
3890
4098
  constructor(tokens, options, originalInput) {
3891
4099
  this.current = 0;
4100
+ this.errors = [];
3892
4101
  this.warnings = [];
4102
+ this.prattCache = new Map();
3893
4103
  this.tokens = tokens;
3894
4104
  this.keywordResolver = options?.keywords;
3895
4105
  this.registryIntegration = options?.registryIntegration;
@@ -3912,6 +4122,14 @@ class Parser {
3912
4122
  this.warnings.push(warning);
3913
4123
  }
3914
4124
  parse() {
4125
+ this.prattCache.clear();
4126
+ const result = this.parseInternal();
4127
+ if (this.errors.length > 0) {
4128
+ result.errors = [...this.errors];
4129
+ }
4130
+ return result;
4131
+ }
4132
+ parseInternal() {
3915
4133
  try {
3916
4134
  if (this.tokens.length === 0) {
3917
4135
  this.addError('Cannot parse empty input');
@@ -4127,7 +4345,114 @@ class Parser {
4127
4345
  }
4128
4346
  }
4129
4347
  parseExpression() {
4130
- return this.parseAssignment();
4348
+ return this.parseExpressionPratt(0);
4349
+ }
4350
+ parseExpressionPratt(minBp) {
4351
+ const cacheKey = `${this.current}:${minBp}`;
4352
+ const cached = this.prattCache.get(cacheKey);
4353
+ if (cached) {
4354
+ this.current = cached.endPos;
4355
+ return cached.node;
4356
+ }
4357
+ this.current;
4358
+ if (this.isAtEnd()) {
4359
+ this.addError('Unexpected end of expression');
4360
+ return this.createErrorNode();
4361
+ }
4362
+ const firstToken = this.peek();
4363
+ const prefixEntry = Parser.PRATT_TABLE.get(firstToken.value);
4364
+ let left;
4365
+ if (prefixEntry?.prefix) {
4366
+ const token = this.advance();
4367
+ if (this.isAtEnd()) {
4368
+ this.addError(`Expected expression after '${token.value}' operator`);
4369
+ return this.createErrorNode();
4370
+ }
4371
+ left = prefixEntry.prefix.handler(token, this.makePrattContext());
4372
+ }
4373
+ else {
4374
+ left = this.parseCall();
4375
+ }
4376
+ while (!this.isAtEnd()) {
4377
+ const nextToken = this.peek();
4378
+ const infixEntry = Parser.PRATT_TABLE.get(nextToken.value);
4379
+ if (!infixEntry?.infix) {
4380
+ if (STOP_TOKENS.has(nextToken.value) || STOP_DELIMITERS.has(nextToken.value)) {
4381
+ break;
4382
+ }
4383
+ break;
4384
+ }
4385
+ const [leftBp] = infixEntry.infix.bp;
4386
+ if (leftBp < minBp)
4387
+ break;
4388
+ if (nextToken.value === '=') {
4389
+ if (this.current + 1 < this.tokens.length && this.tokens[this.current + 1].value === '>') {
4390
+ this.advance();
4391
+ this.advance();
4392
+ this.addError('Arrow functions (=>) are not supported in hyperscript. ' +
4393
+ 'Use "js ... end" blocks for JavaScript callbacks.');
4394
+ if (!this.isAtEnd()) {
4395
+ try {
4396
+ this.parseExpressionPratt(0);
4397
+ }
4398
+ catch {
4399
+ }
4400
+ }
4401
+ return this.createErrorNode();
4402
+ }
4403
+ }
4404
+ if (nextToken.value === '+' || nextToken.value === '-') {
4405
+ const afterOp = this.current + 1 < this.tokens.length ? this.tokens[this.current + 1] : null;
4406
+ if (afterOp && (afterOp.value === '+' || afterOp.value === '-')) {
4407
+ this.addError(`Invalid operator combination: ${nextToken.value}${afterOp.value}`);
4408
+ return left;
4409
+ }
4410
+ }
4411
+ if (nextToken.value === '*' || nextToken.value === '/' || nextToken.value === '%') {
4412
+ const afterOp = this.current + 1 < this.tokens.length ? this.tokens[this.current + 1] : null;
4413
+ if (afterOp &&
4414
+ (afterOp.value === '*' ||
4415
+ afterOp.value === '/' ||
4416
+ afterOp.value === '%' ||
4417
+ afterOp.value === '+' ||
4418
+ afterOp.value === '-')) {
4419
+ if (nextToken.value === '*' && afterOp.value === '*') {
4420
+ this.addError(`Unexpected token: ${afterOp.value}`);
4421
+ }
4422
+ else {
4423
+ this.addError(`Invalid operator combination: ${nextToken.value}${afterOp.value}`);
4424
+ }
4425
+ return left;
4426
+ }
4427
+ }
4428
+ const opToken = this.advance();
4429
+ if (this.isAtEnd() && !Parser.POSTFIX_UNARY_OPERATORS.has(opToken.value)) {
4430
+ this.addError(`Expected expression after '${opToken.value}' operator`);
4431
+ return left;
4432
+ }
4433
+ left = infixEntry.infix.handler(left, opToken, this.makePrattContext());
4434
+ }
4435
+ this.prattCache.set(cacheKey, { node: left, endPos: this.current });
4436
+ return left;
4437
+ }
4438
+ makePrattContext() {
4439
+ const self = this;
4440
+ return {
4441
+ peek: () => (self.current < self.tokens.length ? self.tokens[self.current] : undefined),
4442
+ advance: () => {
4443
+ const token = self.tokens[self.current];
4444
+ self.current++;
4445
+ return token;
4446
+ },
4447
+ parseExpr: (bp) => self.parseExpressionPratt(bp),
4448
+ isStopToken: () => {
4449
+ const t = self.tokens[self.current];
4450
+ if (!t)
4451
+ return true;
4452
+ return STOP_TOKENS.has(t.value) || STOP_DELIMITERS.has(t.value);
4453
+ },
4454
+ atEnd: () => self.current >= self.tokens.length,
4455
+ };
4131
4456
  }
4132
4457
  parseAssignment() {
4133
4458
  let expr = this.parseLogicalOr();
@@ -4744,6 +5069,17 @@ class Parser {
4744
5069
  if (token.value === 'the') {
4745
5070
  return this.parseTheXofY();
4746
5071
  }
5072
+ if (token.value === 'values' && this.check('of')) {
5073
+ this.advance();
5074
+ const target = this.parsePrimary();
5075
+ return {
5076
+ type: 'propertyOfExpression',
5077
+ property: { type: 'identifier', name: 'values' },
5078
+ target,
5079
+ line: token.line,
5080
+ column: token.column,
5081
+ };
5082
+ }
4747
5083
  return this.createIdentifier(token.value);
4748
5084
  }
4749
5085
  if (this.match('$')) {
@@ -5549,36 +5885,7 @@ class Parser {
5549
5885
  }
5550
5886
  }
5551
5887
  }
5552
- const handlerCommands = [];
5553
- while (!this.isAtEnd() && !this.check('end')) {
5554
- if (this.checkIsCommand() || this.isCommand(this.peek().value)) {
5555
- this.advance();
5556
- const savedError = this.error;
5557
- try {
5558
- const cmd = this.parseCommand();
5559
- if (this.error && this.error !== savedError) {
5560
- this.error = savedError;
5561
- }
5562
- handlerCommands.push(cmd);
5563
- while (!this.isAtEnd() &&
5564
- !this.check('end') &&
5565
- !this.checkIsCommand() &&
5566
- !this.isCommand(this.peek().value)) {
5567
- this.advance();
5568
- }
5569
- }
5570
- catch (error) {
5571
- this.error = savedError;
5572
- break;
5573
- }
5574
- }
5575
- else {
5576
- if (!this.check('end')) {
5577
- this.addError(`Unexpected token in event handler: ${this.peek().value}`);
5578
- }
5579
- break;
5580
- }
5581
- }
5888
+ const handlerCommands = this.parseCommandListUntilEnd();
5582
5889
  const handlerNode = {
5583
5890
  type: 'eventHandler',
5584
5891
  event: eventName,
@@ -5591,7 +5898,6 @@ class Parser {
5591
5898
  column: handlerPos.column,
5592
5899
  };
5593
5900
  eventHandlers.push(handlerNode);
5594
- this.consume('end', "Expected 'end' after event handler body");
5595
5901
  }
5596
5902
  else if (this.match('init')) {
5597
5903
  const initCommands = this.parseCommandBlock(['end']);
@@ -5665,7 +5971,23 @@ class Parser {
5665
5971
  break;
5666
5972
  }
5667
5973
  else {
5668
- this.addError(`Expected command, got: ${this.peek().value}`);
5974
+ const errorToken = this.peek();
5975
+ const errorPos = this.getPosition();
5976
+ const errorMessage = `Expected command, got: ${errorToken.value}`;
5977
+ this.addError(errorMessage);
5978
+ const errorNode = createErrorCommandNode(errorPos, errorMessage, errorToken.value);
5979
+ commands.push(errorNode);
5980
+ this.synchronizeToCommandBoundary();
5981
+ if (this.isAtEnd())
5982
+ break;
5983
+ if (this.match('then'))
5984
+ continue;
5985
+ if (this.check('end') || this.check('on'))
5986
+ break;
5987
+ if (this.checkIsCommand() ||
5988
+ (this.isCommand(this.peek().value) && !this.isKeyword(this.peek().value))) {
5989
+ continue;
5990
+ }
5669
5991
  break;
5670
5992
  }
5671
5993
  }
@@ -6296,6 +6618,20 @@ class Parser {
6296
6618
  column: Math.max(1, column),
6297
6619
  position: Math.max(0, position),
6298
6620
  };
6621
+ this.errors.push(this.error);
6622
+ }
6623
+ synchronizeToCommandBoundary() {
6624
+ while (!this.isAtEnd()) {
6625
+ const token = this.peek();
6626
+ if (token.value === 'then' || token.value === 'end' || token.value === 'on') {
6627
+ return;
6628
+ }
6629
+ if (this.checkIsCommand() || (this.isCommand(token.value) && !this.isKeyword(token.value))) {
6630
+ return;
6631
+ }
6632
+ debug.parse('⚠️ synchronize: Skipping token:', token.value);
6633
+ this.advance();
6634
+ }
6299
6635
  }
6300
6636
  parseAttributeOrArrayLiteral() {
6301
6637
  const startPos = this.previous().start;
@@ -6446,6 +6782,7 @@ Parser.POSTFIX_UNARY_OPERATORS = new Set([
6446
6782
  'is empty',
6447
6783
  'is not empty',
6448
6784
  ]);
6785
+ Parser.PRATT_TABLE = PARSER_TABLE;
6449
6786
  Parser.PSEUDO_COMMAND_PREPOSITIONS = ['from', 'on', 'with', 'into', 'at', 'to'];
6450
6787
 
6451
6788
  class FullParserImpl {