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