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