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