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