@hyperfixi/core 2.9.0 → 2.9.2
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/ast-utils/index.js +905 -882
- package/dist/ast-utils/index.mjs +905 -882
- package/dist/bundle-generator/index.js +6 -0
- package/dist/bundle-generator/index.mjs +6 -0
- package/dist/bundle-generator/parser-templates.d.ts +1 -1
- package/dist/chunks/{bridge-D9JLmPkk.js → bridge-DwtNLKK3.js} +2 -2
- package/dist/chunks/browser-modular-6HT1r2AH.js +2 -0
- package/dist/chunks/feature-eventsource-Bxg_UNZo.js +2 -0
- package/dist/chunks/feature-sockets-CcTxD5sT.js +2 -0
- package/dist/chunks/feature-webworker-DoqZ0Grg.js +2 -0
- package/dist/chunks/{index-6DUg7Qjm.js → index-B8jwvuj_.js} +2 -2
- package/dist/commands/index.js +299 -184
- package/dist/commands/index.mjs +299 -184
- package/dist/hyperfixi-browser-classic-i18n.js +1 -1
- package/dist/hyperfixi-browser-minimal.js +1 -1
- package/dist/hyperfixi-browser-standard.js +1 -1
- package/dist/hyperfixi-browser.js +1 -1
- package/dist/hyperfixi-classic-i18n.js +1 -1
- package/dist/hyperfixi-hx-v4.js +1 -1
- package/dist/hyperfixi-hx.js +1 -1
- package/dist/hyperfixi-hybrid-complete.js +1 -1
- package/dist/hyperfixi-hybrid-hx.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/index.js +1731 -1474
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +1731 -1474
- 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/lsp-metadata.js +5 -5
- package/dist/lsp-metadata.mjs +5 -5
- package/dist/metadata.js +25 -25
- package/dist/metadata.mjs +25 -25
- package/dist/multilingual/index.js +17 -17
- package/dist/multilingual/index.mjs +17 -17
- package/dist/parser/command-parsers/utility-commands.d.ts +2 -1
- package/dist/parser/full-parser.js +286 -178
- package/dist/parser/full-parser.mjs +286 -178
- package/dist/parser/hybrid/index.js +3 -0
- package/dist/parser/hybrid/index.mjs +3 -0
- package/dist/parser/hybrid/parser-core.js +3 -0
- package/dist/parser/hybrid/parser-core.mjs +3 -0
- package/dist/parser/hybrid-parser.js +3 -0
- package/dist/parser/hybrid-parser.mjs +3 -0
- package/dist/parser/parser.d.ts +1 -0
- package/dist/reference/index.js +2 -0
- package/dist/reference/index.mjs +2 -0
- package/dist/registry/index.js +286 -178
- package/dist/registry/index.mjs +286 -178
- package/dist/runtime/runtime-base.d.ts +2 -1
- package/dist/types/base-types.d.ts +3 -0
- package/dist/types/core.d.ts +1 -0
- package/package.json +6 -6
- package/dist/chunks/browser-modular-CPiVQXM0.js +0 -2
- package/dist/chunks/feature-eventsource-BpZvPy_K.js +0 -2
- package/dist/chunks/feature-sockets-CrYvjZ4j.js +0 -2
- package/dist/chunks/feature-webworker-BSYguEIW.js +0 -2
|
@@ -424,6 +424,7 @@ function tokenize(input) {
|
|
|
424
424
|
const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
|
|
425
425
|
const isPossessive = nextChar === 's' &&
|
|
426
426
|
prevToken &&
|
|
427
|
+
!/\s/.test(tokenizer.input[tokenizer.position - 1] ?? ' ') &&
|
|
427
428
|
(prevToken.kind === TokenKind.IDENTIFIER ||
|
|
428
429
|
prevToken.kind === TokenKind.SELECTOR ||
|
|
429
430
|
prevToken.value === ']' ||
|
|
@@ -515,13 +516,13 @@ function tokenize(input) {
|
|
|
515
516
|
continue;
|
|
516
517
|
}
|
|
517
518
|
if (char === '{') {
|
|
518
|
-
addToken(tokenizer, TokenKind.OPERATOR, '{');
|
|
519
519
|
advance(tokenizer);
|
|
520
|
+
addToken(tokenizer, TokenKind.OPERATOR, '{');
|
|
520
521
|
continue;
|
|
521
522
|
}
|
|
522
523
|
if (char === '}') {
|
|
523
|
-
addToken(tokenizer, TokenKind.OPERATOR, '}');
|
|
524
524
|
advance(tokenizer);
|
|
525
|
+
addToken(tokenizer, TokenKind.OPERATOR, '}');
|
|
525
526
|
continue;
|
|
526
527
|
}
|
|
527
528
|
if (char === '[') {
|
|
@@ -537,24 +538,24 @@ function tokenize(input) {
|
|
|
537
538
|
prevToken.value === ']') &&
|
|
538
539
|
!isEventCondition;
|
|
539
540
|
if (isMemberAccess) {
|
|
540
|
-
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
541
541
|
advance(tokenizer);
|
|
542
|
+
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
542
543
|
}
|
|
543
544
|
else {
|
|
544
545
|
if (isEventCondition) {
|
|
545
|
-
addToken(tokenizer, TokenKind.SYMBOL, '[');
|
|
546
546
|
advance(tokenizer);
|
|
547
|
+
addToken(tokenizer, TokenKind.SYMBOL, '[');
|
|
547
548
|
}
|
|
548
549
|
else {
|
|
549
|
-
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
550
550
|
advance(tokenizer);
|
|
551
|
+
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
551
552
|
}
|
|
552
553
|
}
|
|
553
554
|
continue;
|
|
554
555
|
}
|
|
555
556
|
if (char === ']') {
|
|
556
|
-
addToken(tokenizer, TokenKind.OPERATOR, ']');
|
|
557
557
|
advance(tokenizer);
|
|
558
|
+
addToken(tokenizer, TokenKind.OPERATOR, ']');
|
|
558
559
|
continue;
|
|
559
560
|
}
|
|
560
561
|
if (char === '$') {
|
|
@@ -1296,39 +1297,39 @@ const DEBUG = {
|
|
|
1296
1297
|
const debug = {
|
|
1297
1298
|
command: (...args) => {
|
|
1298
1299
|
if (DEBUG.commands)
|
|
1299
|
-
console.log('
|
|
1300
|
+
console.log('\ud83d\udd27', ...args);
|
|
1300
1301
|
},
|
|
1301
1302
|
event: (...args) => {
|
|
1302
1303
|
if (DEBUG.events)
|
|
1303
|
-
console.log('
|
|
1304
|
+
console.log('\ud83c\udfaf', ...args);
|
|
1304
1305
|
},
|
|
1305
1306
|
parse: (...args) => {
|
|
1306
1307
|
if (DEBUG.parsing)
|
|
1307
|
-
console.log('
|
|
1308
|
+
console.log('\ud83d\udcdd', ...args);
|
|
1308
1309
|
},
|
|
1309
1310
|
expr: (...args) => {
|
|
1310
1311
|
if (DEBUG.expressions)
|
|
1311
|
-
console.log('
|
|
1312
|
+
console.log('\ud83d\udd0d', ...args);
|
|
1312
1313
|
},
|
|
1313
1314
|
expressions: (...args) => {
|
|
1314
1315
|
if (DEBUG.expressions)
|
|
1315
|
-
console.log('
|
|
1316
|
+
console.log('\ud83d\udd0d', ...args);
|
|
1316
1317
|
},
|
|
1317
1318
|
style: (...args) => {
|
|
1318
1319
|
if (DEBUG.styles)
|
|
1319
|
-
console.log('
|
|
1320
|
+
console.log('\ud83c\udfa8', ...args);
|
|
1320
1321
|
},
|
|
1321
1322
|
runtime: (...args) => {
|
|
1322
1323
|
if (DEBUG.runtime)
|
|
1323
|
-
console.log('
|
|
1324
|
+
console.log('\ud83d\ude80', ...args);
|
|
1324
1325
|
},
|
|
1325
1326
|
loop: (...args) => {
|
|
1326
1327
|
if (DEBUG.loops)
|
|
1327
|
-
console.log('
|
|
1328
|
+
console.log('\ud83d\udd01', ...args);
|
|
1328
1329
|
},
|
|
1329
1330
|
async: (...args) => {
|
|
1330
1331
|
if (DEBUG.async)
|
|
1331
|
-
console.log('
|
|
1332
|
+
console.log('\u23f3', ...args);
|
|
1332
1333
|
},
|
|
1333
1334
|
};
|
|
1334
1335
|
|
|
@@ -2726,9 +2727,9 @@ function createPartialCommandNode(name, pos) {
|
|
|
2726
2727
|
};
|
|
2727
2728
|
}
|
|
2728
2729
|
function createProgramNode(statements) {
|
|
2729
|
-
debug.parse(
|
|
2730
|
+
debug.parse(`\u2705 createProgramNode: Called with ${statements.length} statements`);
|
|
2730
2731
|
if (statements.length === 0) {
|
|
2731
|
-
debug.parse(
|
|
2732
|
+
debug.parse(`\u2705 createProgramNode: Returning error node (0 statements)`);
|
|
2732
2733
|
return {
|
|
2733
2734
|
type: 'identifier',
|
|
2734
2735
|
name: '__ERROR__',
|
|
@@ -2739,7 +2740,7 @@ function createProgramNode(statements) {
|
|
|
2739
2740
|
};
|
|
2740
2741
|
}
|
|
2741
2742
|
if (statements.length === 1) {
|
|
2742
|
-
debug.parse(
|
|
2743
|
+
debug.parse(`\u2705 createProgramNode: Returning single statement (type=${statements[0].type})`);
|
|
2743
2744
|
return statements[0];
|
|
2744
2745
|
}
|
|
2745
2746
|
const programNode = {
|
|
@@ -2750,7 +2751,7 @@ function createProgramNode(statements) {
|
|
|
2750
2751
|
line: statements[0]?.line || 1,
|
|
2751
2752
|
column: statements[0]?.column || 1,
|
|
2752
2753
|
};
|
|
2753
|
-
debug.parse(
|
|
2754
|
+
debug.parse(`\u2705 createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
|
|
2754
2755
|
return programNode;
|
|
2755
2756
|
}
|
|
2756
2757
|
|
|
@@ -2761,7 +2762,6 @@ const MULTI_WORD_PATTERNS = [
|
|
|
2761
2762
|
keywords: ['a', 'an', 'from', 'called'],
|
|
2762
2763
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
2763
2764
|
},
|
|
2764
|
-
{ command: 'send', keywords: ['to'], syntax: 'send <event> to <target>' },
|
|
2765
2765
|
{ command: 'throw', keywords: [], syntax: 'throw <error>' },
|
|
2766
2766
|
];
|
|
2767
2767
|
function getMultiWordPattern(commandName) {
|
|
@@ -3062,7 +3062,13 @@ function parseTriggerCommand(ctx, identifierNode) {
|
|
|
3062
3062
|
finalArgs.push(ctx.createIdentifier(keyword));
|
|
3063
3063
|
continue;
|
|
3064
3064
|
}
|
|
3065
|
-
|
|
3065
|
+
const before = ctx.savePosition();
|
|
3066
|
+
const expr = ctx.parsePrimary();
|
|
3067
|
+
if (expr)
|
|
3068
|
+
finalArgs.push(expr);
|
|
3069
|
+
if (ctx.savePosition() === before) {
|
|
3070
|
+
ctx.advance();
|
|
3071
|
+
}
|
|
3066
3072
|
}
|
|
3067
3073
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
3068
3074
|
.withArgs(...finalArgs)
|
|
@@ -3139,37 +3145,37 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
3139
3145
|
ctx.advance();
|
|
3140
3146
|
loopType = 'until-event';
|
|
3141
3147
|
const eventToken = ctx.peek();
|
|
3142
|
-
debug.parse('
|
|
3148
|
+
debug.parse('\ud83d\udccd Parsing event name, current token:', {
|
|
3143
3149
|
value: eventToken.value,
|
|
3144
3150
|
kind: eventToken.kind,
|
|
3145
3151
|
});
|
|
3146
3152
|
if (isIdentifierLike(eventToken) || isEvent(eventToken)) {
|
|
3147
3153
|
eventName = eventToken.value;
|
|
3148
3154
|
ctx.advance();
|
|
3149
|
-
debug.parse('
|
|
3155
|
+
debug.parse('\u2705 Got event name:', eventName, 'Next token:', ctx.peek().value);
|
|
3150
3156
|
}
|
|
3151
3157
|
else {
|
|
3152
3158
|
throw new Error('Expected event name after "event"');
|
|
3153
3159
|
}
|
|
3154
|
-
debug.parse('
|
|
3160
|
+
debug.parse('\ud83d\udd0d Checking for "from", current token:', ctx.peek().value);
|
|
3155
3161
|
if (ctx.check(KEYWORDS.FROM)) {
|
|
3156
|
-
debug.parse('
|
|
3162
|
+
debug.parse('\u2705 Found "from", advancing...');
|
|
3157
3163
|
ctx.advance();
|
|
3158
|
-
debug.parse('
|
|
3164
|
+
debug.parse('\ud83d\udccd After consuming "from", current token:', ctx.peek().value);
|
|
3159
3165
|
if (consumeOptionalKeyword(ctx, KEYWORDS.THE)) {
|
|
3160
|
-
debug.parse('
|
|
3166
|
+
debug.parse('\u2705 Found "the", advancing...');
|
|
3161
3167
|
}
|
|
3162
3168
|
const beforePrimary = ctx.peek();
|
|
3163
|
-
debug.parse('
|
|
3169
|
+
debug.parse('\ud83d\udd0d Before parsePrimary for event target:', {
|
|
3164
3170
|
value: beforePrimary.value,
|
|
3165
3171
|
kind: beforePrimary.kind,
|
|
3166
3172
|
position: beforePrimary.start,
|
|
3167
3173
|
});
|
|
3168
3174
|
eventTarget = ctx.parsePrimary();
|
|
3169
|
-
debug.parse('
|
|
3175
|
+
debug.parse('\u2705 After parsePrimary, eventTarget:', eventTarget);
|
|
3170
3176
|
}
|
|
3171
3177
|
else {
|
|
3172
|
-
debug.parse('
|
|
3178
|
+
debug.parse('\u274c No "from" found, skipping target parsing');
|
|
3173
3179
|
}
|
|
3174
3180
|
}
|
|
3175
3181
|
else {
|
|
@@ -3281,11 +3287,64 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
3281
3287
|
}
|
|
3282
3288
|
return builder.endingAt(ctx.getPosition()).build();
|
|
3283
3289
|
}
|
|
3290
|
+
function parseIfBranchCommands(ctx, stopAtElse) {
|
|
3291
|
+
const commands = [];
|
|
3292
|
+
const atTerminator = () => ctx.check(KEYWORDS.END) || (stopAtElse && ctx.check(KEYWORDS.ELSE));
|
|
3293
|
+
while (!ctx.isAtEnd() && !atTerminator()) {
|
|
3294
|
+
if (isComment(ctx.peek())) {
|
|
3295
|
+
ctx.advance();
|
|
3296
|
+
continue;
|
|
3297
|
+
}
|
|
3298
|
+
if (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))
|
|
3299
|
+
break;
|
|
3300
|
+
ctx.advance();
|
|
3301
|
+
const cmd = ctx.parseCommand();
|
|
3302
|
+
if (cmd) {
|
|
3303
|
+
commands.push(cmd);
|
|
3304
|
+
}
|
|
3305
|
+
if (ctx.check(KEYWORDS.THEN) || ctx.check(KEYWORDS.AND) || ctx.check(',')) {
|
|
3306
|
+
ctx.advance();
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return commands;
|
|
3310
|
+
}
|
|
3311
|
+
const OPERAND_INTRODUCERS = new Set([
|
|
3312
|
+
'is',
|
|
3313
|
+
'am',
|
|
3314
|
+
'are',
|
|
3315
|
+
'not',
|
|
3316
|
+
'no',
|
|
3317
|
+
'and',
|
|
3318
|
+
'or',
|
|
3319
|
+
'==',
|
|
3320
|
+
'===',
|
|
3321
|
+
'!=',
|
|
3322
|
+
'!==',
|
|
3323
|
+
'<',
|
|
3324
|
+
'>',
|
|
3325
|
+
'<=',
|
|
3326
|
+
'>=',
|
|
3327
|
+
'+',
|
|
3328
|
+
'-',
|
|
3329
|
+
'*',
|
|
3330
|
+
'/',
|
|
3331
|
+
'mod',
|
|
3332
|
+
]);
|
|
3333
|
+
function isBodyCommandStart(ctx, prevToken, token) {
|
|
3334
|
+
if (prevToken === null) {
|
|
3335
|
+
return false;
|
|
3336
|
+
}
|
|
3337
|
+
if (OPERAND_INTRODUCERS.has(prevToken.value?.toLowerCase?.() ?? '')) {
|
|
3338
|
+
return false;
|
|
3339
|
+
}
|
|
3340
|
+
return ctx.checkIsCommand() || ctx.isCommand(token.value?.toLowerCase());
|
|
3341
|
+
}
|
|
3284
3342
|
function parseIfCommand(ctx, commandToken) {
|
|
3285
3343
|
const args = [];
|
|
3286
3344
|
let hasThen = false;
|
|
3287
3345
|
const savedPosForThen = ctx.savePosition();
|
|
3288
3346
|
const maxThenLookahead = 500;
|
|
3347
|
+
let prevForThen = null;
|
|
3289
3348
|
for (let i = 0; i < maxThenLookahead && !ctx.isAtEnd(); i++) {
|
|
3290
3349
|
const token = ctx.peek();
|
|
3291
3350
|
if (token.value === KEYWORDS.THEN) {
|
|
@@ -3298,6 +3357,14 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3298
3357
|
token.value === KEYWORDS.ON) {
|
|
3299
3358
|
break;
|
|
3300
3359
|
}
|
|
3360
|
+
if (token.line !== undefined &&
|
|
3361
|
+
token.line !== commandToken.line &&
|
|
3362
|
+
isBodyCommandStart(ctx, prevForThen, token)) {
|
|
3363
|
+
break;
|
|
3364
|
+
}
|
|
3365
|
+
if (!isComment(token)) {
|
|
3366
|
+
prevForThen = token;
|
|
3367
|
+
}
|
|
3301
3368
|
ctx.advance();
|
|
3302
3369
|
}
|
|
3303
3370
|
ctx.restorePosition(savedPosForThen);
|
|
@@ -3306,9 +3373,14 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3306
3373
|
const savedPosition = ctx.savePosition();
|
|
3307
3374
|
const ifStatementLine = commandToken.line;
|
|
3308
3375
|
const maxLookahead = 100;
|
|
3376
|
+
let firstCommandOnIfLine = false;
|
|
3377
|
+
let prevToken = null;
|
|
3309
3378
|
while (!ctx.isAtEnd() && ctx.current - savedPosition < maxLookahead) {
|
|
3310
3379
|
const token = ctx.peek();
|
|
3311
3380
|
const tokenValue = token.value?.toLowerCase();
|
|
3381
|
+
if (firstCommandOnIfLine && token.line !== undefined && token.line !== ifStatementLine) {
|
|
3382
|
+
break;
|
|
3383
|
+
}
|
|
3312
3384
|
if (tokenValue === KEYWORDS.BEHAVIOR ||
|
|
3313
3385
|
tokenValue === KEYWORDS.DEF ||
|
|
3314
3386
|
tokenValue === KEYWORDS.ON) {
|
|
@@ -3320,11 +3392,15 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3320
3392
|
}
|
|
3321
3393
|
break;
|
|
3322
3394
|
}
|
|
3323
|
-
if (ctx
|
|
3395
|
+
if (isBodyCommandStart(ctx, prevToken, token)) {
|
|
3324
3396
|
if (token.line !== undefined && token.line !== ifStatementLine) {
|
|
3325
3397
|
hasImplicitMultiLineEnd = true;
|
|
3326
3398
|
break;
|
|
3327
3399
|
}
|
|
3400
|
+
firstCommandOnIfLine = true;
|
|
3401
|
+
}
|
|
3402
|
+
if (!isComment(token)) {
|
|
3403
|
+
prevToken = token;
|
|
3328
3404
|
}
|
|
3329
3405
|
ctx.advance();
|
|
3330
3406
|
}
|
|
@@ -3339,11 +3415,12 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3339
3415
|
const conditionTokens = [];
|
|
3340
3416
|
const maxIterations = 20;
|
|
3341
3417
|
let iterations = 0;
|
|
3418
|
+
let firstConditionParse = true;
|
|
3342
3419
|
while (!ctx.isAtEnd() &&
|
|
3343
|
-
!ctx.checkIsCommand() &&
|
|
3344
|
-
!ctx.isCommand(ctx.peek().value) &&
|
|
3345
3420
|
!ctx.check(KEYWORDS.THEN) &&
|
|
3421
|
+
(firstConditionParse || (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))) &&
|
|
3346
3422
|
iterations < maxIterations) {
|
|
3423
|
+
firstConditionParse = false;
|
|
3347
3424
|
const beforePos = ctx.savePosition();
|
|
3348
3425
|
conditionTokens.push(ctx.parseLogicalAnd());
|
|
3349
3426
|
if (ctx.savePosition() === beforePos) {
|
|
@@ -3370,22 +3447,8 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3370
3447
|
}
|
|
3371
3448
|
args.push(condition);
|
|
3372
3449
|
if (isMultiLine) {
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
}
|
|
3376
|
-
const thenCommands = [];
|
|
3377
|
-
while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.ELSE) && !ctx.check(KEYWORDS.END)) {
|
|
3378
|
-
if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
3379
|
-
ctx.advance();
|
|
3380
|
-
const cmd = ctx.parseCommand();
|
|
3381
|
-
if (cmd) {
|
|
3382
|
-
thenCommands.push(cmd);
|
|
3383
|
-
}
|
|
3384
|
-
}
|
|
3385
|
-
else {
|
|
3386
|
-
break;
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3450
|
+
consumeOptionalKeyword(ctx, KEYWORDS.THEN);
|
|
3451
|
+
const thenCommands = parseIfBranchCommands(ctx, true);
|
|
3389
3452
|
if (thenCommands.length === 0 && ctx.isAtEnd()) {
|
|
3390
3453
|
throw new Error("Expected command after 'then' in if statement - incomplete conditional");
|
|
3391
3454
|
}
|
|
@@ -3411,19 +3474,7 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
3411
3474
|
consumedElseIf = true;
|
|
3412
3475
|
}
|
|
3413
3476
|
else {
|
|
3414
|
-
const elseCommands =
|
|
3415
|
-
while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.END)) {
|
|
3416
|
-
if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
3417
|
-
ctx.advance();
|
|
3418
|
-
const cmd = ctx.parseCommand();
|
|
3419
|
-
if (cmd) {
|
|
3420
|
-
elseCommands.push(cmd);
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
else {
|
|
3424
|
-
break;
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3477
|
+
const elseCommands = parseIfBranchCommands(ctx, false);
|
|
3427
3478
|
args.push(createBlock(elseCommands, {
|
|
3428
3479
|
start: commandToken.start,
|
|
3429
3480
|
end: ctx.getPosition().end,
|
|
@@ -4245,21 +4296,6 @@ const GO_KEYWORDS = new Set([
|
|
|
4245
4296
|
'back',
|
|
4246
4297
|
'forward',
|
|
4247
4298
|
]);
|
|
4248
|
-
const GO_URL_STOP = new Set([
|
|
4249
|
-
'in',
|
|
4250
|
-
'then',
|
|
4251
|
-
'and',
|
|
4252
|
-
'else',
|
|
4253
|
-
'end',
|
|
4254
|
-
'otherwise',
|
|
4255
|
-
'when',
|
|
4256
|
-
'where',
|
|
4257
|
-
'catch',
|
|
4258
|
-
'finally',
|
|
4259
|
-
]);
|
|
4260
|
-
function isGoURLTerminator(ctx) {
|
|
4261
|
-
return ctx.isAtEnd() || GO_URL_STOP.has(ctx.peek().value);
|
|
4262
|
-
}
|
|
4263
4299
|
function stringNode(value, tok) {
|
|
4264
4300
|
return {
|
|
4265
4301
|
type: 'string',
|
|
@@ -4270,16 +4306,6 @@ function stringNode(value, tok) {
|
|
|
4270
4306
|
column: tok.column,
|
|
4271
4307
|
};
|
|
4272
4308
|
}
|
|
4273
|
-
function isNakedURLStart(ctx) {
|
|
4274
|
-
const tok = ctx.peek();
|
|
4275
|
-
if (tok.value === '/')
|
|
4276
|
-
return true;
|
|
4277
|
-
if (ctx.checkIdentifierLike()) {
|
|
4278
|
-
const next = ctx.peekAt(1);
|
|
4279
|
-
return !!next && next.value === ':' && next.start === tok.end;
|
|
4280
|
-
}
|
|
4281
|
-
return false;
|
|
4282
|
-
}
|
|
4283
4309
|
function parseGoCommand(ctx, identifierNode) {
|
|
4284
4310
|
const args = [];
|
|
4285
4311
|
while (!isCommandBoundary(ctx, ['when', 'where', 'catch', 'finally'])) {
|
|
@@ -4291,7 +4317,7 @@ function parseGoCommand(ctx, identifierNode) {
|
|
|
4291
4317
|
continue;
|
|
4292
4318
|
}
|
|
4293
4319
|
if (isNakedURLStart(ctx)) {
|
|
4294
|
-
const url = parseBareURLPath(ctx
|
|
4320
|
+
const url = parseBareURLPath(ctx);
|
|
4295
4321
|
if (url) {
|
|
4296
4322
|
args.push(url);
|
|
4297
4323
|
continue;
|
|
@@ -4431,35 +4457,65 @@ function parseMultiWordCommand(ctx, commandToken, commandName) {
|
|
|
4431
4457
|
}
|
|
4432
4458
|
return builder.build();
|
|
4433
4459
|
}
|
|
4434
|
-
function
|
|
4435
|
-
|
|
4460
|
+
function isNakedURLStart(ctx) {
|
|
4461
|
+
const tok = ctx.peek();
|
|
4462
|
+
if (tok.value === '/')
|
|
4436
4463
|
return true;
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4464
|
+
if (ctx.checkIdentifierLike()) {
|
|
4465
|
+
const next = ctx.peekAt(1);
|
|
4466
|
+
return !!next && next.value === ':' && next.start === tok.end;
|
|
4467
|
+
}
|
|
4468
|
+
return false;
|
|
4440
4469
|
}
|
|
4441
|
-
|
|
4470
|
+
const URL_HARD_STOPS = new Set(['{', ')']);
|
|
4471
|
+
function parseBareURLPath(ctx) {
|
|
4442
4472
|
const startPos = ctx.savePosition();
|
|
4473
|
+
const firstTok = ctx.peek();
|
|
4443
4474
|
let path = '';
|
|
4444
|
-
|
|
4445
|
-
|
|
4475
|
+
let prev = null;
|
|
4476
|
+
while (!ctx.isAtEnd()) {
|
|
4477
|
+
const tok = ctx.peek();
|
|
4478
|
+
if (prev && tok.start !== prev.end)
|
|
4479
|
+
break;
|
|
4480
|
+
if (tok.value === '$' && ctx.peekAt(1)?.value === '{') {
|
|
4481
|
+
ctx.advance();
|
|
4482
|
+
let last = ctx.advance();
|
|
4483
|
+
let depth = 1;
|
|
4484
|
+
while (!ctx.isAtEnd() && depth > 0) {
|
|
4485
|
+
last = ctx.advance();
|
|
4486
|
+
if (last.value === '{')
|
|
4487
|
+
depth++;
|
|
4488
|
+
else if (last.value === '}')
|
|
4489
|
+
depth--;
|
|
4490
|
+
}
|
|
4491
|
+
path += ctx.getInputSlice(tok.start, last.end);
|
|
4492
|
+
prev = last;
|
|
4493
|
+
continue;
|
|
4494
|
+
}
|
|
4495
|
+
if (URL_HARD_STOPS.has(tok.value))
|
|
4496
|
+
break;
|
|
4497
|
+
prev = ctx.advance();
|
|
4498
|
+
path += prev.value;
|
|
4446
4499
|
}
|
|
4447
4500
|
if (!path || path === '/') {
|
|
4448
4501
|
ctx.restorePosition(startPos);
|
|
4449
4502
|
return null;
|
|
4450
4503
|
}
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
end: ctx.savePosition(),
|
|
4504
|
+
const span = {
|
|
4505
|
+
start: firstTok.start,
|
|
4506
|
+
end: prev ? prev.end : firstTok.end,
|
|
4507
|
+
line: firstTok.line,
|
|
4508
|
+
column: firstTok.column,
|
|
4457
4509
|
};
|
|
4510
|
+
if (path.includes('${')) {
|
|
4511
|
+
return { type: 'templateLiteral', value: path, ...span };
|
|
4512
|
+
}
|
|
4513
|
+
return { type: 'literal', value: path, raw: path, ...span };
|
|
4458
4514
|
}
|
|
4459
4515
|
function parseFetchCommand(ctx, commandToken) {
|
|
4460
4516
|
const modifiers = {};
|
|
4461
4517
|
let url = null;
|
|
4462
|
-
if (!ctx.isAtEnd() && ctx
|
|
4518
|
+
if (!ctx.isAtEnd() && isNakedURLStart(ctx)) {
|
|
4463
4519
|
url = parseBareURLPath(ctx);
|
|
4464
4520
|
}
|
|
4465
4521
|
if (!url) {
|
|
@@ -4685,10 +4741,10 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
4685
4741
|
catch {
|
|
4686
4742
|
break;
|
|
4687
4743
|
}
|
|
4688
|
-
if (ctx.match(KEYWORDS.AND)) {
|
|
4744
|
+
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
4689
4745
|
continue;
|
|
4690
4746
|
}
|
|
4691
|
-
if (ctx.check(KEYWORDS.
|
|
4747
|
+
if (ctx.check(KEYWORDS.ELSE) || ctx.check(KEYWORDS.END)) {
|
|
4692
4748
|
break;
|
|
4693
4749
|
}
|
|
4694
4750
|
if (ctx.checkIsCommand()) {
|
|
@@ -4891,6 +4947,7 @@ class Parser {
|
|
|
4891
4947
|
const result = this.parseInternal();
|
|
4892
4948
|
if (this.errors.length > 0) {
|
|
4893
4949
|
result.errors = [...this.errors];
|
|
4950
|
+
result.recovered = true;
|
|
4894
4951
|
}
|
|
4895
4952
|
return result;
|
|
4896
4953
|
}
|
|
@@ -5025,26 +5082,26 @@ class Parser {
|
|
|
5025
5082
|
const commandSequence = this.parseCommandSequence();
|
|
5026
5083
|
if (commandSequence) {
|
|
5027
5084
|
if (!this.isAtEnd() && this.check('on')) {
|
|
5028
|
-
debug.parse('
|
|
5085
|
+
debug.parse('\u2705 PARSER: Found event handlers after command sequence, parsing as program');
|
|
5029
5086
|
const statements = [commandSequence];
|
|
5030
|
-
debug.parse(
|
|
5087
|
+
debug.parse(`\u2705 PARSER: Starting with ${statements.length} statement(s) from command sequence`);
|
|
5031
5088
|
let eventHandlerCount = 0;
|
|
5032
5089
|
while (!this.isAtEnd() && this.check('on')) {
|
|
5033
|
-
debug.parse(
|
|
5090
|
+
debug.parse(`\u2705 PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
|
|
5034
5091
|
this.advance();
|
|
5035
5092
|
const eventHandler = this.parseEventHandler();
|
|
5036
|
-
debug.parse(
|
|
5093
|
+
debug.parse(`\u2705 PARSER: parseEventHandler returned:`, eventHandler
|
|
5037
5094
|
? `type=${eventHandler.type}, event=${eventHandler.event}`
|
|
5038
5095
|
: 'null');
|
|
5039
5096
|
if (eventHandler) {
|
|
5040
5097
|
statements.push(eventHandler);
|
|
5041
5098
|
eventHandlerCount++;
|
|
5042
|
-
debug.parse(
|
|
5099
|
+
debug.parse(`\u2705 PARSER: Added event handler, now have ${statements.length} statements total`);
|
|
5043
5100
|
}
|
|
5044
|
-
debug.parse(
|
|
5101
|
+
debug.parse(`\u2705 PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
|
|
5045
5102
|
if (!this.isAtEnd() && !this.check('on')) {
|
|
5046
5103
|
if (!this.isAtEnd()) {
|
|
5047
|
-
debug.parse(
|
|
5104
|
+
debug.parse(`\u26a0\ufe0f PARSER: Unexpected token after event handlers: ${this.peek().value}`);
|
|
5048
5105
|
this.addError(`Unexpected token after event handlers: ${this.peek().value}`);
|
|
5049
5106
|
return {
|
|
5050
5107
|
success: false,
|
|
@@ -5053,11 +5110,11 @@ class Parser {
|
|
|
5053
5110
|
error: this.error,
|
|
5054
5111
|
};
|
|
5055
5112
|
}
|
|
5056
|
-
debug.parse(
|
|
5113
|
+
debug.parse(`\u2705 PARSER: No more event handlers, at end of input`);
|
|
5057
5114
|
break;
|
|
5058
5115
|
}
|
|
5059
5116
|
}
|
|
5060
|
-
debug.parse(
|
|
5117
|
+
debug.parse(`\u2705 PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
|
|
5061
5118
|
if (this.error) {
|
|
5062
5119
|
return {
|
|
5063
5120
|
success: false,
|
|
@@ -5452,41 +5509,41 @@ class Parser {
|
|
|
5452
5509
|
parseCommandListUntilTerminator(extraStops) {
|
|
5453
5510
|
const commands = [];
|
|
5454
5511
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
5455
|
-
debug.parse('
|
|
5512
|
+
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
5456
5513
|
while (!this.isAtEnd() && !isStop()) {
|
|
5457
|
-
debug.parse('
|
|
5514
|
+
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
5458
5515
|
let parsedCommand = false;
|
|
5459
5516
|
const isCommandToken = this.checkIsCommand();
|
|
5460
5517
|
const isCommandIdentifier = !isCommandToken && this.checkIdentifier() && this.isCommand(this.peek().value);
|
|
5461
5518
|
if (isCommandToken || isCommandIdentifier) {
|
|
5462
|
-
debug.parse(isCommandToken ? '
|
|
5519
|
+
debug.parse(isCommandToken ? '\u2705 Found COMMAND token:' : '\u2705 Found IDENTIFIER that is a command:', this.peek().value);
|
|
5463
5520
|
this.advance();
|
|
5464
5521
|
const savedError = this.error;
|
|
5465
5522
|
try {
|
|
5466
5523
|
const cmd = this.parseCommand();
|
|
5467
5524
|
if (this.error && this.error !== savedError) {
|
|
5468
|
-
debug.parse('
|
|
5525
|
+
debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
|
|
5469
5526
|
this.error = savedError;
|
|
5470
5527
|
}
|
|
5471
5528
|
if (cmd) {
|
|
5472
|
-
debug.parse('
|
|
5529
|
+
debug.parse('\u2705 Parsed command:', cmd.name);
|
|
5473
5530
|
commands.push(cmd);
|
|
5474
5531
|
parsedCommand = true;
|
|
5475
5532
|
}
|
|
5476
5533
|
}
|
|
5477
5534
|
catch (error) {
|
|
5478
|
-
debug.parse('
|
|
5535
|
+
debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
|
|
5479
5536
|
this.error = savedError;
|
|
5480
5537
|
}
|
|
5481
5538
|
}
|
|
5482
5539
|
else if (this.checkIdentifier()) {
|
|
5483
|
-
debug.parse('
|
|
5540
|
+
debug.parse('\u274c IDENTIFIER is not a command:', this.peek().value);
|
|
5484
5541
|
}
|
|
5485
5542
|
if (!parsedCommand) {
|
|
5486
|
-
debug.parse('
|
|
5543
|
+
debug.parse('\u274c No command parsed, breaking. Current token:', this.peek().value);
|
|
5487
5544
|
break;
|
|
5488
5545
|
}
|
|
5489
|
-
debug.parse('
|
|
5546
|
+
debug.parse('\ud83d\udccd After parsing command, current token:', this.peek().value);
|
|
5490
5547
|
while (!this.isAtEnd() &&
|
|
5491
5548
|
!isStop() &&
|
|
5492
5549
|
!this.checkIsCommand() &&
|
|
@@ -5494,19 +5551,19 @@ class Parser {
|
|
|
5494
5551
|
!this.check('then') &&
|
|
5495
5552
|
!this.check('and') &&
|
|
5496
5553
|
!this.check(',')) {
|
|
5497
|
-
debug.parse('
|
|
5554
|
+
debug.parse('\u26a0\ufe0f Skipping unexpected token:', this.peek().value);
|
|
5498
5555
|
this.advance();
|
|
5499
5556
|
}
|
|
5500
5557
|
if (this.match('then', 'and', ',')) {
|
|
5501
|
-
debug.parse('
|
|
5558
|
+
debug.parse('\u2705 Found separator, continuing');
|
|
5502
5559
|
continue;
|
|
5503
5560
|
}
|
|
5504
5561
|
else if (this.checkIsCommand() || this.isCommand(this.peek().value)) {
|
|
5505
|
-
debug.parse('
|
|
5562
|
+
debug.parse('\u2705 Next token is a command, continuing without separator');
|
|
5506
5563
|
continue;
|
|
5507
5564
|
}
|
|
5508
5565
|
else {
|
|
5509
|
-
debug.parse('
|
|
5566
|
+
debug.parse('\ud83d\udccd No separator and no command, breaking. Current token:', this.peek().value);
|
|
5510
5567
|
break;
|
|
5511
5568
|
}
|
|
5512
5569
|
}
|
|
@@ -5520,18 +5577,18 @@ class Parser {
|
|
|
5520
5577
|
if (!this.check('end') && !extraStops.some(s => this.check(s))) {
|
|
5521
5578
|
terminator = '';
|
|
5522
5579
|
}
|
|
5523
|
-
debug.parse('
|
|
5580
|
+
debug.parse('\u2705 parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
|
|
5524
5581
|
return { commands, terminator };
|
|
5525
5582
|
}
|
|
5526
5583
|
parseCommandListUntilEnd() {
|
|
5527
5584
|
const { commands } = this.parseCommandListUntilTerminator([]);
|
|
5528
|
-
debug.parse('
|
|
5585
|
+
debug.parse('\ud83d\udd0d After loop, checking for "end". Current token:', this.peek().value);
|
|
5529
5586
|
if (this.check('end')) {
|
|
5530
|
-
debug.parse('
|
|
5587
|
+
debug.parse('\u2705 Found "end", consuming it');
|
|
5531
5588
|
this.advance();
|
|
5532
5589
|
}
|
|
5533
5590
|
else {
|
|
5534
|
-
debug.parse('
|
|
5591
|
+
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
5535
5592
|
throw new Error('Expected "end" to close repeat block');
|
|
5536
5593
|
}
|
|
5537
5594
|
return commands;
|
|
@@ -6204,6 +6261,23 @@ class Parser {
|
|
|
6204
6261
|
this.consume(')', "Expected ')' after parameter list");
|
|
6205
6262
|
}
|
|
6206
6263
|
const bodyCommands = this.parseCommandBlock(['end', 'catch', 'finally']);
|
|
6264
|
+
const { errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks();
|
|
6265
|
+
this.consume('end', "Expected 'end' after function definition");
|
|
6266
|
+
return {
|
|
6267
|
+
type: 'def',
|
|
6268
|
+
name: funcName,
|
|
6269
|
+
params,
|
|
6270
|
+
body: bodyCommands,
|
|
6271
|
+
...(errorSymbol !== undefined && { errorSymbol }),
|
|
6272
|
+
...(errorHandler !== undefined && { errorHandler }),
|
|
6273
|
+
...(finallyHandler !== undefined && { finallyHandler }),
|
|
6274
|
+
start: pos.start,
|
|
6275
|
+
end: this.getPosition().end,
|
|
6276
|
+
line: pos.line,
|
|
6277
|
+
column: pos.column,
|
|
6278
|
+
};
|
|
6279
|
+
}
|
|
6280
|
+
parseErrorAndFinallyBlocks() {
|
|
6207
6281
|
let errorSymbol;
|
|
6208
6282
|
let errorHandler;
|
|
6209
6283
|
if (this.check('catch')) {
|
|
@@ -6218,19 +6292,10 @@ class Parser {
|
|
|
6218
6292
|
this.advance();
|
|
6219
6293
|
finallyHandler = this.parseCommandBlock(['end']);
|
|
6220
6294
|
}
|
|
6221
|
-
this.consume('end', "Expected 'end' after function definition");
|
|
6222
6295
|
return {
|
|
6223
|
-
type: 'def',
|
|
6224
|
-
name: funcName,
|
|
6225
|
-
params,
|
|
6226
|
-
body: bodyCommands,
|
|
6227
6296
|
...(errorSymbol !== undefined && { errorSymbol }),
|
|
6228
6297
|
...(errorHandler !== undefined && { errorHandler }),
|
|
6229
6298
|
...(finallyHandler !== undefined && { finallyHandler }),
|
|
6230
|
-
start: pos.start,
|
|
6231
|
-
end: this.getPosition().end,
|
|
6232
|
-
line: pos.line,
|
|
6233
|
-
column: pos.column,
|
|
6234
6299
|
};
|
|
6235
6300
|
}
|
|
6236
6301
|
parseEventNameWithNamespace(errorMessage) {
|
|
@@ -6375,13 +6440,13 @@ class Parser {
|
|
|
6375
6440
|
try {
|
|
6376
6441
|
const cmd = this.parseCommand();
|
|
6377
6442
|
if (this.error && this.error !== savedError) {
|
|
6378
|
-
debug.parse('
|
|
6443
|
+
debug.parse('\u26a0\ufe0f Command parsing added error, restoring error state. Error was:', this.error.message);
|
|
6379
6444
|
this.error = savedError;
|
|
6380
6445
|
}
|
|
6381
6446
|
return cmd;
|
|
6382
6447
|
}
|
|
6383
6448
|
catch (error) {
|
|
6384
|
-
debug.parse('
|
|
6449
|
+
debug.parse('\u26a0\ufe0f Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
|
|
6385
6450
|
this.error = savedError;
|
|
6386
6451
|
return null;
|
|
6387
6452
|
}
|
|
@@ -6398,12 +6463,12 @@ class Parser {
|
|
|
6398
6463
|
const timeToken = this.advance();
|
|
6399
6464
|
const delayMs = parseTimeToMs(timeToken.value);
|
|
6400
6465
|
modifiers[modifierKey] = delayMs;
|
|
6401
|
-
debug.parse(
|
|
6466
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
|
|
6402
6467
|
}
|
|
6403
6468
|
else if (this.matchNumber()) {
|
|
6404
6469
|
const delayMs = parseInt(this.previous().value, 10);
|
|
6405
6470
|
modifiers[modifierKey] = delayMs;
|
|
6406
|
-
debug.parse(
|
|
6471
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
|
|
6407
6472
|
}
|
|
6408
6473
|
else {
|
|
6409
6474
|
this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
|
|
@@ -6414,7 +6479,7 @@ class Parser {
|
|
|
6414
6479
|
}
|
|
6415
6480
|
}
|
|
6416
6481
|
parseEventHandler() {
|
|
6417
|
-
debug.parse(
|
|
6482
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: ENTRY - parsing event handler`);
|
|
6418
6483
|
const eventNames = [];
|
|
6419
6484
|
let firstOnceAlias = false;
|
|
6420
6485
|
if (this.check('first') && !this.checkIsCommand() && this.current + 1 < this.tokens.length) {
|
|
@@ -6423,27 +6488,27 @@ class Parser {
|
|
|
6423
6488
|
if (peek2Value && peek2Value !== 'of' && peek2Value !== 'in' && peek2Value !== 'from') {
|
|
6424
6489
|
this.advance();
|
|
6425
6490
|
firstOnceAlias = true;
|
|
6426
|
-
debug.parse(
|
|
6491
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'first' as .once alias`);
|
|
6427
6492
|
}
|
|
6428
6493
|
}
|
|
6429
6494
|
const event = this.parseEventNameWithNamespace("Expected event name after 'on'");
|
|
6430
6495
|
eventNames.push(event);
|
|
6431
|
-
debug.parse(
|
|
6496
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed first event name: ${event}`);
|
|
6432
6497
|
while (this.check('or')) {
|
|
6433
6498
|
this.advance();
|
|
6434
|
-
debug.parse(
|
|
6499
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Found 'or', parsing additional event name`);
|
|
6435
6500
|
const additionalEvent = this.parseEventNameWithNamespace("Expected event name after 'or'");
|
|
6436
6501
|
eventNames.push(additionalEvent);
|
|
6437
|
-
debug.parse(
|
|
6502
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
|
|
6438
6503
|
}
|
|
6439
|
-
debug.parse(
|
|
6504
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
|
|
6440
6505
|
let customEventSource;
|
|
6441
6506
|
if (this.registryIntegration) {
|
|
6442
6507
|
const firstEvent = eventNames[0];
|
|
6443
6508
|
if (this.registryIntegration.hasEventSource(firstEvent)) {
|
|
6444
6509
|
const source = this.registryIntegration.getEventSource(firstEvent);
|
|
6445
6510
|
customEventSource = source?.name;
|
|
6446
|
-
debug.parse(
|
|
6511
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
|
|
6447
6512
|
}
|
|
6448
6513
|
}
|
|
6449
6514
|
const eventParams = [];
|
|
@@ -6455,7 +6520,7 @@ class Parser {
|
|
|
6455
6520
|
} while (this.match(','));
|
|
6456
6521
|
}
|
|
6457
6522
|
this.consume(')', "Expected ')' after event parameters");
|
|
6458
|
-
debug.parse(
|
|
6523
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
|
|
6459
6524
|
}
|
|
6460
6525
|
const modifiers = {};
|
|
6461
6526
|
while (this.check('.')) {
|
|
@@ -6464,15 +6529,15 @@ class Parser {
|
|
|
6464
6529
|
const modName = modToken.value.toLowerCase();
|
|
6465
6530
|
if (modName === 'once') {
|
|
6466
6531
|
modifiers.once = true;
|
|
6467
|
-
debug.parse(
|
|
6532
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.once'`);
|
|
6468
6533
|
}
|
|
6469
6534
|
else if (modName === 'prevent') {
|
|
6470
6535
|
modifiers.prevent = true;
|
|
6471
|
-
debug.parse(
|
|
6536
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.prevent'`);
|
|
6472
6537
|
}
|
|
6473
6538
|
else if (modName === 'stop') {
|
|
6474
6539
|
modifiers.stop = true;
|
|
6475
|
-
debug.parse(
|
|
6540
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.stop'`);
|
|
6476
6541
|
}
|
|
6477
6542
|
else if (modName === 'debounce' || modName === 'throttle') {
|
|
6478
6543
|
if (this.check('(')) {
|
|
@@ -6484,11 +6549,11 @@ class Parser {
|
|
|
6484
6549
|
}
|
|
6485
6550
|
if (modName === 'debounce') {
|
|
6486
6551
|
modifiers.debounce = delayMs;
|
|
6487
|
-
debug.parse(
|
|
6552
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
|
|
6488
6553
|
}
|
|
6489
6554
|
else {
|
|
6490
6555
|
modifiers.throttle = delayMs;
|
|
6491
|
-
debug.parse(
|
|
6556
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
|
|
6492
6557
|
}
|
|
6493
6558
|
this.consume(')', `Expected ')' after ${modName} delay`);
|
|
6494
6559
|
}
|
|
@@ -6497,7 +6562,7 @@ class Parser {
|
|
|
6497
6562
|
}
|
|
6498
6563
|
}
|
|
6499
6564
|
else {
|
|
6500
|
-
debug.parse(
|
|
6565
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Warning - unknown modifier '.${modName}'`);
|
|
6501
6566
|
}
|
|
6502
6567
|
}
|
|
6503
6568
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
@@ -6505,7 +6570,7 @@ class Parser {
|
|
|
6505
6570
|
modifiers.once = true;
|
|
6506
6571
|
}
|
|
6507
6572
|
if (Object.keys(modifiers).length > 0) {
|
|
6508
|
-
debug.parse(
|
|
6573
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifiers:`, modifiers);
|
|
6509
6574
|
}
|
|
6510
6575
|
let condition;
|
|
6511
6576
|
if (this.match('[')) {
|
|
@@ -6516,7 +6581,7 @@ class Parser {
|
|
|
6516
6581
|
if (this.match('from')) {
|
|
6517
6582
|
const targetToken = this.advance();
|
|
6518
6583
|
target = targetToken.value;
|
|
6519
|
-
debug.parse(
|
|
6584
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
|
|
6520
6585
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
6521
6586
|
}
|
|
6522
6587
|
let attributeName;
|
|
@@ -6528,32 +6593,36 @@ class Parser {
|
|
|
6528
6593
|
else {
|
|
6529
6594
|
attributeName = attrToken.value;
|
|
6530
6595
|
}
|
|
6531
|
-
debug.parse(
|
|
6596
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed attribute name: ${attributeName}`);
|
|
6532
6597
|
}
|
|
6533
6598
|
let watchTarget;
|
|
6534
6599
|
if (this.match('in')) {
|
|
6535
|
-
debug.parse(
|
|
6600
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Found 'in' keyword, parsing watch target`);
|
|
6536
6601
|
watchTarget = this.parseExpression();
|
|
6537
|
-
debug.parse(
|
|
6602
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed watch target expression`);
|
|
6538
6603
|
}
|
|
6539
6604
|
const commands = [];
|
|
6540
|
-
debug.parse(
|
|
6605
|
+
debug.parse(`\u2705 parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
|
|
6541
6606
|
while (!this.isAtEnd()) {
|
|
6542
|
-
debug.parse(
|
|
6607
|
+
debug.parse(`\u2705 parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
|
|
6543
6608
|
if (this.check('on')) {
|
|
6544
|
-
debug.parse(
|
|
6609
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found next event handler 'on'`);
|
|
6545
6610
|
break;
|
|
6546
6611
|
}
|
|
6547
6612
|
if (this.checkComment()) {
|
|
6548
|
-
debug.parse(
|
|
6613
|
+
debug.parse(`\u2705 parseEventHandler: Skipping comment token: ${this.peek().value}`);
|
|
6549
6614
|
this.advance();
|
|
6550
6615
|
continue;
|
|
6551
6616
|
}
|
|
6552
6617
|
if (this.check('end')) {
|
|
6553
|
-
debug.parse(
|
|
6618
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found 'end' keyword`);
|
|
6554
6619
|
this.advance();
|
|
6555
6620
|
break;
|
|
6556
6621
|
}
|
|
6622
|
+
if (this.check('catch') || this.check('finally')) {
|
|
6623
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found '${this.peek().value}'`);
|
|
6624
|
+
break;
|
|
6625
|
+
}
|
|
6557
6626
|
if (this.checkIsCommand()) {
|
|
6558
6627
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
6559
6628
|
const commandName = this.peek().value.toLowerCase();
|
|
@@ -6582,7 +6651,7 @@ class Parser {
|
|
|
6582
6651
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
6583
6652
|
if (cmd) {
|
|
6584
6653
|
commands.push(cmd);
|
|
6585
|
-
debug.parse(
|
|
6654
|
+
debug.parse(`\u2705 parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
|
|
6586
6655
|
}
|
|
6587
6656
|
}
|
|
6588
6657
|
}
|
|
@@ -6600,7 +6669,7 @@ class Parser {
|
|
|
6600
6669
|
expr = this.parseExpression();
|
|
6601
6670
|
}
|
|
6602
6671
|
catch (error) {
|
|
6603
|
-
debug.parse('
|
|
6672
|
+
debug.parse('\u26a0\ufe0f Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
|
|
6604
6673
|
this.error = savedError;
|
|
6605
6674
|
break;
|
|
6606
6675
|
}
|
|
@@ -6647,7 +6716,9 @@ class Parser {
|
|
|
6647
6716
|
!this.check('then') &&
|
|
6648
6717
|
!this.check('and') &&
|
|
6649
6718
|
!this.check(',') &&
|
|
6650
|
-
!this.check('on')
|
|
6719
|
+
!this.check('on') &&
|
|
6720
|
+
!this.check('catch') &&
|
|
6721
|
+
!this.check('finally')) {
|
|
6651
6722
|
this.advance();
|
|
6652
6723
|
}
|
|
6653
6724
|
if (this.match('then', 'and', ',')) {
|
|
@@ -6660,12 +6731,24 @@ class Parser {
|
|
|
6660
6731
|
break;
|
|
6661
6732
|
}
|
|
6662
6733
|
}
|
|
6734
|
+
let errorSymbol;
|
|
6735
|
+
let errorHandler;
|
|
6736
|
+
let finallyHandler;
|
|
6737
|
+
if (!this.isAtEnd() && (this.check('catch') || this.check('finally'))) {
|
|
6738
|
+
({ errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks());
|
|
6739
|
+
if (this.check('end')) {
|
|
6740
|
+
this.advance();
|
|
6741
|
+
}
|
|
6742
|
+
}
|
|
6663
6743
|
const pos = this.getPosition();
|
|
6664
6744
|
const node = {
|
|
6665
6745
|
type: 'eventHandler',
|
|
6666
6746
|
event: eventNames.length === 1 ? eventNames[0] : eventNames.join('|'),
|
|
6667
6747
|
events: eventNames,
|
|
6668
6748
|
commands,
|
|
6749
|
+
...(errorSymbol !== undefined && { errorSymbol }),
|
|
6750
|
+
...(errorHandler !== undefined && { errorHandler }),
|
|
6751
|
+
...(finallyHandler !== undefined && { finallyHandler }),
|
|
6669
6752
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
6670
6753
|
...(condition && { condition }),
|
|
6671
6754
|
...(target && { target }),
|
|
@@ -6678,7 +6761,7 @@ class Parser {
|
|
|
6678
6761
|
line: pos.line,
|
|
6679
6762
|
column: pos.column,
|
|
6680
6763
|
};
|
|
6681
|
-
debug.parse(
|
|
6764
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
|
|
6682
6765
|
return node;
|
|
6683
6766
|
}
|
|
6684
6767
|
parseBehaviorDefinition() {
|
|
@@ -6743,11 +6826,33 @@ class Parser {
|
|
|
6743
6826
|
}
|
|
6744
6827
|
}
|
|
6745
6828
|
}
|
|
6746
|
-
const handlerCommands = this.
|
|
6829
|
+
const { commands: handlerCommands, terminator } = this.parseCommandListUntilTerminator([
|
|
6830
|
+
'catch',
|
|
6831
|
+
'finally',
|
|
6832
|
+
]);
|
|
6833
|
+
let handlerErrorSymbol;
|
|
6834
|
+
let handlerErrorHandler;
|
|
6835
|
+
let handlerFinallyHandler;
|
|
6836
|
+
if (terminator === 'catch' || terminator === 'finally') {
|
|
6837
|
+
({
|
|
6838
|
+
errorSymbol: handlerErrorSymbol,
|
|
6839
|
+
errorHandler: handlerErrorHandler,
|
|
6840
|
+
finallyHandler: handlerFinallyHandler,
|
|
6841
|
+
} = this.parseErrorAndFinallyBlocks());
|
|
6842
|
+
}
|
|
6843
|
+
if (this.check('end')) {
|
|
6844
|
+
this.advance();
|
|
6845
|
+
}
|
|
6846
|
+
else {
|
|
6847
|
+
throw new Error('Expected "end" to close behavior event handler');
|
|
6848
|
+
}
|
|
6747
6849
|
const handlerNode = {
|
|
6748
6850
|
type: 'eventHandler',
|
|
6749
6851
|
event: eventName,
|
|
6750
6852
|
commands: handlerCommands,
|
|
6853
|
+
...(handlerErrorSymbol !== undefined && { errorSymbol: handlerErrorSymbol }),
|
|
6854
|
+
...(handlerErrorHandler !== undefined && { errorHandler: handlerErrorHandler }),
|
|
6855
|
+
...(handlerFinallyHandler !== undefined && { finallyHandler: handlerFinallyHandler }),
|
|
6751
6856
|
...(eventSource !== undefined && { target: eventSource }),
|
|
6752
6857
|
...(eventArgs.length > 0 && { args: eventArgs }),
|
|
6753
6858
|
start: handlerPos.start,
|
|
@@ -6802,7 +6907,7 @@ class Parser {
|
|
|
6802
6907
|
this.error.message.includes('unclosed parenthes') ||
|
|
6803
6908
|
this.error.message.includes('Unclosed parenthes');
|
|
6804
6909
|
if (!isCriticalError) {
|
|
6805
|
-
debug.parse('
|
|
6910
|
+
debug.parse('\u26a0\ufe0f parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
|
|
6806
6911
|
this.error = savedError;
|
|
6807
6912
|
}
|
|
6808
6913
|
}
|
|
@@ -6812,11 +6917,11 @@ class Parser {
|
|
|
6812
6917
|
!this.isCommand(this.peek().value) &&
|
|
6813
6918
|
!this.check('then') &&
|
|
6814
6919
|
!this.check('on')) {
|
|
6815
|
-
debug.parse('
|
|
6920
|
+
debug.parse('\u26a0\ufe0f parseCommandSequence: Skipping unexpected token:', this.peek().value);
|
|
6816
6921
|
this.advance();
|
|
6817
6922
|
}
|
|
6818
6923
|
if (this.check('on')) {
|
|
6819
|
-
debug.parse('
|
|
6924
|
+
debug.parse('\u2705 parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
|
|
6820
6925
|
break;
|
|
6821
6926
|
}
|
|
6822
6927
|
if (this.match('then')) {
|
|
@@ -6874,6 +6979,9 @@ class Parser {
|
|
|
6874
6979
|
this.advance();
|
|
6875
6980
|
const cmd = this.parseCommand();
|
|
6876
6981
|
commands.push(cmd);
|
|
6982
|
+
if (this.match('then', 'and', ',')) {
|
|
6983
|
+
continue;
|
|
6984
|
+
}
|
|
6877
6985
|
}
|
|
6878
6986
|
else {
|
|
6879
6987
|
break;
|
|
@@ -7260,7 +7368,7 @@ class Parser {
|
|
|
7260
7368
|
if (!this.check(')') && callee.type === 'identifier') {
|
|
7261
7369
|
const funcName = callee.name;
|
|
7262
7370
|
if (CommandClassification.isCSSFunction(funcName)) {
|
|
7263
|
-
debug.parse(
|
|
7371
|
+
debug.parse(`\ud83d\udca1 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
|
|
7264
7372
|
`Use '${funcName}(...)' or \`${funcName}(...)\` instead.`);
|
|
7265
7373
|
}
|
|
7266
7374
|
}
|
|
@@ -7562,7 +7670,7 @@ class Parser {
|
|
|
7562
7670
|
if (this.checkIsCommand() || (this.isCommand(token.value) && !this.isKeyword(token.value))) {
|
|
7563
7671
|
return;
|
|
7564
7672
|
}
|
|
7565
|
-
debug.parse('
|
|
7673
|
+
debug.parse('\u26a0\ufe0f synchronize: Skipping token:', token.value);
|
|
7566
7674
|
this.advance();
|
|
7567
7675
|
}
|
|
7568
7676
|
}
|