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