@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
package/dist/registry/index.mjs
CHANGED
|
@@ -31,39 +31,39 @@ const DEBUG = {
|
|
|
31
31
|
const debug = {
|
|
32
32
|
command: (...args) => {
|
|
33
33
|
if (DEBUG.commands)
|
|
34
|
-
console.log('
|
|
34
|
+
console.log('\ud83d\udd27', ...args);
|
|
35
35
|
},
|
|
36
36
|
event: (...args) => {
|
|
37
37
|
if (DEBUG.events)
|
|
38
|
-
console.log('
|
|
38
|
+
console.log('\ud83c\udfaf', ...args);
|
|
39
39
|
},
|
|
40
40
|
parse: (...args) => {
|
|
41
41
|
if (DEBUG.parsing)
|
|
42
|
-
console.log('
|
|
42
|
+
console.log('\ud83d\udcdd', ...args);
|
|
43
43
|
},
|
|
44
44
|
expr: (...args) => {
|
|
45
45
|
if (DEBUG.expressions)
|
|
46
|
-
console.log('
|
|
46
|
+
console.log('\ud83d\udd0d', ...args);
|
|
47
47
|
},
|
|
48
48
|
expressions: (...args) => {
|
|
49
49
|
if (DEBUG.expressions)
|
|
50
|
-
console.log('
|
|
50
|
+
console.log('\ud83d\udd0d', ...args);
|
|
51
51
|
},
|
|
52
52
|
style: (...args) => {
|
|
53
53
|
if (DEBUG.styles)
|
|
54
|
-
console.log('
|
|
54
|
+
console.log('\ud83c\udfa8', ...args);
|
|
55
55
|
},
|
|
56
56
|
runtime: (...args) => {
|
|
57
57
|
if (DEBUG.runtime)
|
|
58
|
-
console.log('
|
|
58
|
+
console.log('\ud83d\ude80', ...args);
|
|
59
59
|
},
|
|
60
60
|
loop: (...args) => {
|
|
61
61
|
if (DEBUG.loops)
|
|
62
|
-
console.log('
|
|
62
|
+
console.log('\ud83d\udd01', ...args);
|
|
63
63
|
},
|
|
64
64
|
async: (...args) => {
|
|
65
65
|
if (DEBUG.async)
|
|
66
|
-
console.log('
|
|
66
|
+
console.log('\u23f3', ...args);
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
|
|
@@ -4046,6 +4046,7 @@ function tokenize(input) {
|
|
|
4046
4046
|
const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
|
|
4047
4047
|
const isPossessive = nextChar === 's' &&
|
|
4048
4048
|
prevToken &&
|
|
4049
|
+
!/\s/.test(tokenizer.input[tokenizer.position - 1] ?? ' ') &&
|
|
4049
4050
|
(prevToken.kind === TokenKind.IDENTIFIER ||
|
|
4050
4051
|
prevToken.kind === TokenKind.SELECTOR ||
|
|
4051
4052
|
prevToken.value === ']' ||
|
|
@@ -4137,13 +4138,13 @@ function tokenize(input) {
|
|
|
4137
4138
|
continue;
|
|
4138
4139
|
}
|
|
4139
4140
|
if (char === '{') {
|
|
4140
|
-
addToken(tokenizer, TokenKind.OPERATOR, '{');
|
|
4141
4141
|
advance(tokenizer);
|
|
4142
|
+
addToken(tokenizer, TokenKind.OPERATOR, '{');
|
|
4142
4143
|
continue;
|
|
4143
4144
|
}
|
|
4144
4145
|
if (char === '}') {
|
|
4145
|
-
addToken(tokenizer, TokenKind.OPERATOR, '}');
|
|
4146
4146
|
advance(tokenizer);
|
|
4147
|
+
addToken(tokenizer, TokenKind.OPERATOR, '}');
|
|
4147
4148
|
continue;
|
|
4148
4149
|
}
|
|
4149
4150
|
if (char === '[') {
|
|
@@ -4159,24 +4160,24 @@ function tokenize(input) {
|
|
|
4159
4160
|
prevToken.value === ']') &&
|
|
4160
4161
|
!isEventCondition;
|
|
4161
4162
|
if (isMemberAccess) {
|
|
4162
|
-
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
4163
4163
|
advance(tokenizer);
|
|
4164
|
+
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
4164
4165
|
}
|
|
4165
4166
|
else {
|
|
4166
4167
|
if (isEventCondition) {
|
|
4167
|
-
addToken(tokenizer, TokenKind.SYMBOL, '[');
|
|
4168
4168
|
advance(tokenizer);
|
|
4169
|
+
addToken(tokenizer, TokenKind.SYMBOL, '[');
|
|
4169
4170
|
}
|
|
4170
4171
|
else {
|
|
4171
|
-
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
4172
4172
|
advance(tokenizer);
|
|
4173
|
+
addToken(tokenizer, TokenKind.OPERATOR, '[');
|
|
4173
4174
|
}
|
|
4174
4175
|
}
|
|
4175
4176
|
continue;
|
|
4176
4177
|
}
|
|
4177
4178
|
if (char === ']') {
|
|
4178
|
-
addToken(tokenizer, TokenKind.OPERATOR, ']');
|
|
4179
4179
|
advance(tokenizer);
|
|
4180
|
+
addToken(tokenizer, TokenKind.OPERATOR, ']');
|
|
4180
4181
|
continue;
|
|
4181
4182
|
}
|
|
4182
4183
|
if (char === '$') {
|
|
@@ -5770,9 +5771,9 @@ function createPartialCommandNode(name, pos) {
|
|
|
5770
5771
|
};
|
|
5771
5772
|
}
|
|
5772
5773
|
function createProgramNode(statements) {
|
|
5773
|
-
debug.parse(
|
|
5774
|
+
debug.parse(`\u2705 createProgramNode: Called with ${statements.length} statements`);
|
|
5774
5775
|
if (statements.length === 0) {
|
|
5775
|
-
debug.parse(
|
|
5776
|
+
debug.parse(`\u2705 createProgramNode: Returning error node (0 statements)`);
|
|
5776
5777
|
return {
|
|
5777
5778
|
type: 'identifier',
|
|
5778
5779
|
name: '__ERROR__',
|
|
@@ -5783,7 +5784,7 @@ function createProgramNode(statements) {
|
|
|
5783
5784
|
};
|
|
5784
5785
|
}
|
|
5785
5786
|
if (statements.length === 1) {
|
|
5786
|
-
debug.parse(
|
|
5787
|
+
debug.parse(`\u2705 createProgramNode: Returning single statement (type=${statements[0].type})`);
|
|
5787
5788
|
return statements[0];
|
|
5788
5789
|
}
|
|
5789
5790
|
const programNode = {
|
|
@@ -5794,7 +5795,7 @@ function createProgramNode(statements) {
|
|
|
5794
5795
|
line: statements[0]?.line || 1,
|
|
5795
5796
|
column: statements[0]?.column || 1,
|
|
5796
5797
|
};
|
|
5797
|
-
debug.parse(
|
|
5798
|
+
debug.parse(`\u2705 createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
|
|
5798
5799
|
return programNode;
|
|
5799
5800
|
}
|
|
5800
5801
|
|
|
@@ -5805,7 +5806,6 @@ const MULTI_WORD_PATTERNS = [
|
|
|
5805
5806
|
keywords: ['a', 'an', 'from', 'called'],
|
|
5806
5807
|
syntax: 'make (a|an) <type> [from <args>] [called <name>]',
|
|
5807
5808
|
},
|
|
5808
|
-
{ command: 'send', keywords: ['to'], syntax: 'send <event> to <target>' },
|
|
5809
5809
|
{ command: 'throw', keywords: [], syntax: 'throw <error>' },
|
|
5810
5810
|
];
|
|
5811
5811
|
function getMultiWordPattern(commandName) {
|
|
@@ -6106,7 +6106,13 @@ function parseTriggerCommand(ctx, identifierNode) {
|
|
|
6106
6106
|
finalArgs.push(ctx.createIdentifier(keyword));
|
|
6107
6107
|
continue;
|
|
6108
6108
|
}
|
|
6109
|
-
|
|
6109
|
+
const before = ctx.savePosition();
|
|
6110
|
+
const expr = ctx.parsePrimary();
|
|
6111
|
+
if (expr)
|
|
6112
|
+
finalArgs.push(expr);
|
|
6113
|
+
if (ctx.savePosition() === before) {
|
|
6114
|
+
ctx.advance();
|
|
6115
|
+
}
|
|
6110
6116
|
}
|
|
6111
6117
|
return CommandNodeBuilder.fromIdentifier(identifierNode)
|
|
6112
6118
|
.withArgs(...finalArgs)
|
|
@@ -6183,37 +6189,37 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6183
6189
|
ctx.advance();
|
|
6184
6190
|
loopType = 'until-event';
|
|
6185
6191
|
const eventToken = ctx.peek();
|
|
6186
|
-
debug.parse('
|
|
6192
|
+
debug.parse('\ud83d\udccd Parsing event name, current token:', {
|
|
6187
6193
|
value: eventToken.value,
|
|
6188
6194
|
kind: eventToken.kind,
|
|
6189
6195
|
});
|
|
6190
6196
|
if (isIdentifierLike(eventToken) || isEvent(eventToken)) {
|
|
6191
6197
|
eventName = eventToken.value;
|
|
6192
6198
|
ctx.advance();
|
|
6193
|
-
debug.parse('
|
|
6199
|
+
debug.parse('\u2705 Got event name:', eventName, 'Next token:', ctx.peek().value);
|
|
6194
6200
|
}
|
|
6195
6201
|
else {
|
|
6196
6202
|
throw new Error('Expected event name after "event"');
|
|
6197
6203
|
}
|
|
6198
|
-
debug.parse('
|
|
6204
|
+
debug.parse('\ud83d\udd0d Checking for "from", current token:', ctx.peek().value);
|
|
6199
6205
|
if (ctx.check(KEYWORDS.FROM)) {
|
|
6200
|
-
debug.parse('
|
|
6206
|
+
debug.parse('\u2705 Found "from", advancing...');
|
|
6201
6207
|
ctx.advance();
|
|
6202
|
-
debug.parse('
|
|
6208
|
+
debug.parse('\ud83d\udccd After consuming "from", current token:', ctx.peek().value);
|
|
6203
6209
|
if (consumeOptionalKeyword(ctx, KEYWORDS.THE)) {
|
|
6204
|
-
debug.parse('
|
|
6210
|
+
debug.parse('\u2705 Found "the", advancing...');
|
|
6205
6211
|
}
|
|
6206
6212
|
const beforePrimary = ctx.peek();
|
|
6207
|
-
debug.parse('
|
|
6213
|
+
debug.parse('\ud83d\udd0d Before parsePrimary for event target:', {
|
|
6208
6214
|
value: beforePrimary.value,
|
|
6209
6215
|
kind: beforePrimary.kind,
|
|
6210
6216
|
position: beforePrimary.start,
|
|
6211
6217
|
});
|
|
6212
6218
|
eventTarget = ctx.parsePrimary();
|
|
6213
|
-
debug.parse('
|
|
6219
|
+
debug.parse('\u2705 After parsePrimary, eventTarget:', eventTarget);
|
|
6214
6220
|
}
|
|
6215
6221
|
else {
|
|
6216
|
-
debug.parse('
|
|
6222
|
+
debug.parse('\u274c No "from" found, skipping target parsing');
|
|
6217
6223
|
}
|
|
6218
6224
|
}
|
|
6219
6225
|
else {
|
|
@@ -6325,11 +6331,64 @@ function parseRepeatCommand(ctx, commandToken) {
|
|
|
6325
6331
|
}
|
|
6326
6332
|
return builder.endingAt(ctx.getPosition()).build();
|
|
6327
6333
|
}
|
|
6334
|
+
function parseIfBranchCommands(ctx, stopAtElse) {
|
|
6335
|
+
const commands = [];
|
|
6336
|
+
const atTerminator = () => ctx.check(KEYWORDS.END) || (stopAtElse && ctx.check(KEYWORDS.ELSE));
|
|
6337
|
+
while (!ctx.isAtEnd() && !atTerminator()) {
|
|
6338
|
+
if (isComment(ctx.peek())) {
|
|
6339
|
+
ctx.advance();
|
|
6340
|
+
continue;
|
|
6341
|
+
}
|
|
6342
|
+
if (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))
|
|
6343
|
+
break;
|
|
6344
|
+
ctx.advance();
|
|
6345
|
+
const cmd = ctx.parseCommand();
|
|
6346
|
+
if (cmd) {
|
|
6347
|
+
commands.push(cmd);
|
|
6348
|
+
}
|
|
6349
|
+
if (ctx.check(KEYWORDS.THEN) || ctx.check(KEYWORDS.AND) || ctx.check(',')) {
|
|
6350
|
+
ctx.advance();
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
return commands;
|
|
6354
|
+
}
|
|
6355
|
+
const OPERAND_INTRODUCERS = new Set([
|
|
6356
|
+
'is',
|
|
6357
|
+
'am',
|
|
6358
|
+
'are',
|
|
6359
|
+
'not',
|
|
6360
|
+
'no',
|
|
6361
|
+
'and',
|
|
6362
|
+
'or',
|
|
6363
|
+
'==',
|
|
6364
|
+
'===',
|
|
6365
|
+
'!=',
|
|
6366
|
+
'!==',
|
|
6367
|
+
'<',
|
|
6368
|
+
'>',
|
|
6369
|
+
'<=',
|
|
6370
|
+
'>=',
|
|
6371
|
+
'+',
|
|
6372
|
+
'-',
|
|
6373
|
+
'*',
|
|
6374
|
+
'/',
|
|
6375
|
+
'mod',
|
|
6376
|
+
]);
|
|
6377
|
+
function isBodyCommandStart(ctx, prevToken, token) {
|
|
6378
|
+
if (prevToken === null) {
|
|
6379
|
+
return false;
|
|
6380
|
+
}
|
|
6381
|
+
if (OPERAND_INTRODUCERS.has(prevToken.value?.toLowerCase?.() ?? '')) {
|
|
6382
|
+
return false;
|
|
6383
|
+
}
|
|
6384
|
+
return ctx.checkIsCommand() || ctx.isCommand(token.value?.toLowerCase());
|
|
6385
|
+
}
|
|
6328
6386
|
function parseIfCommand(ctx, commandToken) {
|
|
6329
6387
|
const args = [];
|
|
6330
6388
|
let hasThen = false;
|
|
6331
6389
|
const savedPosForThen = ctx.savePosition();
|
|
6332
6390
|
const maxThenLookahead = 500;
|
|
6391
|
+
let prevForThen = null;
|
|
6333
6392
|
for (let i = 0; i < maxThenLookahead && !ctx.isAtEnd(); i++) {
|
|
6334
6393
|
const token = ctx.peek();
|
|
6335
6394
|
if (token.value === KEYWORDS.THEN) {
|
|
@@ -6342,6 +6401,14 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6342
6401
|
token.value === KEYWORDS.ON) {
|
|
6343
6402
|
break;
|
|
6344
6403
|
}
|
|
6404
|
+
if (token.line !== undefined &&
|
|
6405
|
+
token.line !== commandToken.line &&
|
|
6406
|
+
isBodyCommandStart(ctx, prevForThen, token)) {
|
|
6407
|
+
break;
|
|
6408
|
+
}
|
|
6409
|
+
if (!isComment(token)) {
|
|
6410
|
+
prevForThen = token;
|
|
6411
|
+
}
|
|
6345
6412
|
ctx.advance();
|
|
6346
6413
|
}
|
|
6347
6414
|
ctx.restorePosition(savedPosForThen);
|
|
@@ -6350,9 +6417,14 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6350
6417
|
const savedPosition = ctx.savePosition();
|
|
6351
6418
|
const ifStatementLine = commandToken.line;
|
|
6352
6419
|
const maxLookahead = 100;
|
|
6420
|
+
let firstCommandOnIfLine = false;
|
|
6421
|
+
let prevToken = null;
|
|
6353
6422
|
while (!ctx.isAtEnd() && ctx.current - savedPosition < maxLookahead) {
|
|
6354
6423
|
const token = ctx.peek();
|
|
6355
6424
|
const tokenValue = token.value?.toLowerCase();
|
|
6425
|
+
if (firstCommandOnIfLine && token.line !== undefined && token.line !== ifStatementLine) {
|
|
6426
|
+
break;
|
|
6427
|
+
}
|
|
6356
6428
|
if (tokenValue === KEYWORDS.BEHAVIOR ||
|
|
6357
6429
|
tokenValue === KEYWORDS.DEF ||
|
|
6358
6430
|
tokenValue === KEYWORDS.ON) {
|
|
@@ -6364,11 +6436,15 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6364
6436
|
}
|
|
6365
6437
|
break;
|
|
6366
6438
|
}
|
|
6367
|
-
if (ctx
|
|
6439
|
+
if (isBodyCommandStart(ctx, prevToken, token)) {
|
|
6368
6440
|
if (token.line !== undefined && token.line !== ifStatementLine) {
|
|
6369
6441
|
hasImplicitMultiLineEnd = true;
|
|
6370
6442
|
break;
|
|
6371
6443
|
}
|
|
6444
|
+
firstCommandOnIfLine = true;
|
|
6445
|
+
}
|
|
6446
|
+
if (!isComment(token)) {
|
|
6447
|
+
prevToken = token;
|
|
6372
6448
|
}
|
|
6373
6449
|
ctx.advance();
|
|
6374
6450
|
}
|
|
@@ -6383,11 +6459,12 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6383
6459
|
const conditionTokens = [];
|
|
6384
6460
|
const maxIterations = 20;
|
|
6385
6461
|
let iterations = 0;
|
|
6462
|
+
let firstConditionParse = true;
|
|
6386
6463
|
while (!ctx.isAtEnd() &&
|
|
6387
|
-
!ctx.checkIsCommand() &&
|
|
6388
|
-
!ctx.isCommand(ctx.peek().value) &&
|
|
6389
6464
|
!ctx.check(KEYWORDS.THEN) &&
|
|
6465
|
+
(firstConditionParse || (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))) &&
|
|
6390
6466
|
iterations < maxIterations) {
|
|
6467
|
+
firstConditionParse = false;
|
|
6391
6468
|
const beforePos = ctx.savePosition();
|
|
6392
6469
|
conditionTokens.push(ctx.parseLogicalAnd());
|
|
6393
6470
|
if (ctx.savePosition() === beforePos) {
|
|
@@ -6414,22 +6491,8 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6414
6491
|
}
|
|
6415
6492
|
args.push(condition);
|
|
6416
6493
|
if (isMultiLine) {
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
}
|
|
6420
|
-
const thenCommands = [];
|
|
6421
|
-
while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.ELSE) && !ctx.check(KEYWORDS.END)) {
|
|
6422
|
-
if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
6423
|
-
ctx.advance();
|
|
6424
|
-
const cmd = ctx.parseCommand();
|
|
6425
|
-
if (cmd) {
|
|
6426
|
-
thenCommands.push(cmd);
|
|
6427
|
-
}
|
|
6428
|
-
}
|
|
6429
|
-
else {
|
|
6430
|
-
break;
|
|
6431
|
-
}
|
|
6432
|
-
}
|
|
6494
|
+
consumeOptionalKeyword(ctx, KEYWORDS.THEN);
|
|
6495
|
+
const thenCommands = parseIfBranchCommands(ctx, true);
|
|
6433
6496
|
if (thenCommands.length === 0 && ctx.isAtEnd()) {
|
|
6434
6497
|
throw new Error("Expected command after 'then' in if statement - incomplete conditional");
|
|
6435
6498
|
}
|
|
@@ -6455,19 +6518,7 @@ function parseIfCommand(ctx, commandToken) {
|
|
|
6455
6518
|
consumedElseIf = true;
|
|
6456
6519
|
}
|
|
6457
6520
|
else {
|
|
6458
|
-
const elseCommands =
|
|
6459
|
-
while (!ctx.isAtEnd() && !ctx.check(KEYWORDS.END)) {
|
|
6460
|
-
if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
|
|
6461
|
-
ctx.advance();
|
|
6462
|
-
const cmd = ctx.parseCommand();
|
|
6463
|
-
if (cmd) {
|
|
6464
|
-
elseCommands.push(cmd);
|
|
6465
|
-
}
|
|
6466
|
-
}
|
|
6467
|
-
else {
|
|
6468
|
-
break;
|
|
6469
|
-
}
|
|
6470
|
-
}
|
|
6521
|
+
const elseCommands = parseIfBranchCommands(ctx, false);
|
|
6471
6522
|
args.push(createBlock(elseCommands, {
|
|
6472
6523
|
start: commandToken.start,
|
|
6473
6524
|
end: ctx.getPosition().end,
|
|
@@ -7289,21 +7340,6 @@ const GO_KEYWORDS = new Set([
|
|
|
7289
7340
|
'back',
|
|
7290
7341
|
'forward',
|
|
7291
7342
|
]);
|
|
7292
|
-
const GO_URL_STOP = new Set([
|
|
7293
|
-
'in',
|
|
7294
|
-
'then',
|
|
7295
|
-
'and',
|
|
7296
|
-
'else',
|
|
7297
|
-
'end',
|
|
7298
|
-
'otherwise',
|
|
7299
|
-
'when',
|
|
7300
|
-
'where',
|
|
7301
|
-
'catch',
|
|
7302
|
-
'finally',
|
|
7303
|
-
]);
|
|
7304
|
-
function isGoURLTerminator(ctx) {
|
|
7305
|
-
return ctx.isAtEnd() || GO_URL_STOP.has(ctx.peek().value);
|
|
7306
|
-
}
|
|
7307
7343
|
function stringNode(value, tok) {
|
|
7308
7344
|
return {
|
|
7309
7345
|
type: 'string',
|
|
@@ -7314,16 +7350,6 @@ function stringNode(value, tok) {
|
|
|
7314
7350
|
column: tok.column,
|
|
7315
7351
|
};
|
|
7316
7352
|
}
|
|
7317
|
-
function isNakedURLStart(ctx) {
|
|
7318
|
-
const tok = ctx.peek();
|
|
7319
|
-
if (tok.value === '/')
|
|
7320
|
-
return true;
|
|
7321
|
-
if (ctx.checkIdentifierLike()) {
|
|
7322
|
-
const next = ctx.peekAt(1);
|
|
7323
|
-
return !!next && next.value === ':' && next.start === tok.end;
|
|
7324
|
-
}
|
|
7325
|
-
return false;
|
|
7326
|
-
}
|
|
7327
7353
|
function parseGoCommand(ctx, identifierNode) {
|
|
7328
7354
|
const args = [];
|
|
7329
7355
|
while (!isCommandBoundary(ctx, ['when', 'where', 'catch', 'finally'])) {
|
|
@@ -7335,7 +7361,7 @@ function parseGoCommand(ctx, identifierNode) {
|
|
|
7335
7361
|
continue;
|
|
7336
7362
|
}
|
|
7337
7363
|
if (isNakedURLStart(ctx)) {
|
|
7338
|
-
const url = parseBareURLPath(ctx
|
|
7364
|
+
const url = parseBareURLPath(ctx);
|
|
7339
7365
|
if (url) {
|
|
7340
7366
|
args.push(url);
|
|
7341
7367
|
continue;
|
|
@@ -7475,35 +7501,65 @@ function parseMultiWordCommand(ctx, commandToken, commandName) {
|
|
|
7475
7501
|
}
|
|
7476
7502
|
return builder.build();
|
|
7477
7503
|
}
|
|
7478
|
-
function
|
|
7479
|
-
|
|
7504
|
+
function isNakedURLStart(ctx) {
|
|
7505
|
+
const tok = ctx.peek();
|
|
7506
|
+
if (tok.value === '/')
|
|
7480
7507
|
return true;
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7508
|
+
if (ctx.checkIdentifierLike()) {
|
|
7509
|
+
const next = ctx.peekAt(1);
|
|
7510
|
+
return !!next && next.value === ':' && next.start === tok.end;
|
|
7511
|
+
}
|
|
7512
|
+
return false;
|
|
7484
7513
|
}
|
|
7485
|
-
|
|
7514
|
+
const URL_HARD_STOPS = new Set(['{', ')']);
|
|
7515
|
+
function parseBareURLPath(ctx) {
|
|
7486
7516
|
const startPos = ctx.savePosition();
|
|
7517
|
+
const firstTok = ctx.peek();
|
|
7487
7518
|
let path = '';
|
|
7488
|
-
|
|
7489
|
-
|
|
7519
|
+
let prev = null;
|
|
7520
|
+
while (!ctx.isAtEnd()) {
|
|
7521
|
+
const tok = ctx.peek();
|
|
7522
|
+
if (prev && tok.start !== prev.end)
|
|
7523
|
+
break;
|
|
7524
|
+
if (tok.value === '$' && ctx.peekAt(1)?.value === '{') {
|
|
7525
|
+
ctx.advance();
|
|
7526
|
+
let last = ctx.advance();
|
|
7527
|
+
let depth = 1;
|
|
7528
|
+
while (!ctx.isAtEnd() && depth > 0) {
|
|
7529
|
+
last = ctx.advance();
|
|
7530
|
+
if (last.value === '{')
|
|
7531
|
+
depth++;
|
|
7532
|
+
else if (last.value === '}')
|
|
7533
|
+
depth--;
|
|
7534
|
+
}
|
|
7535
|
+
path += ctx.getInputSlice(tok.start, last.end);
|
|
7536
|
+
prev = last;
|
|
7537
|
+
continue;
|
|
7538
|
+
}
|
|
7539
|
+
if (URL_HARD_STOPS.has(tok.value))
|
|
7540
|
+
break;
|
|
7541
|
+
prev = ctx.advance();
|
|
7542
|
+
path += prev.value;
|
|
7490
7543
|
}
|
|
7491
7544
|
if (!path || path === '/') {
|
|
7492
7545
|
ctx.restorePosition(startPos);
|
|
7493
7546
|
return null;
|
|
7494
7547
|
}
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
end: ctx.savePosition(),
|
|
7548
|
+
const span = {
|
|
7549
|
+
start: firstTok.start,
|
|
7550
|
+
end: prev ? prev.end : firstTok.end,
|
|
7551
|
+
line: firstTok.line,
|
|
7552
|
+
column: firstTok.column,
|
|
7501
7553
|
};
|
|
7554
|
+
if (path.includes('${')) {
|
|
7555
|
+
return { type: 'templateLiteral', value: path, ...span };
|
|
7556
|
+
}
|
|
7557
|
+
return { type: 'literal', value: path, raw: path, ...span };
|
|
7502
7558
|
}
|
|
7503
7559
|
function parseFetchCommand(ctx, commandToken) {
|
|
7504
7560
|
const modifiers = {};
|
|
7505
7561
|
let url = null;
|
|
7506
|
-
if (!ctx.isAtEnd() && ctx
|
|
7562
|
+
if (!ctx.isAtEnd() && isNakedURLStart(ctx)) {
|
|
7507
7563
|
url = parseBareURLPath(ctx);
|
|
7508
7564
|
}
|
|
7509
7565
|
if (!url) {
|
|
@@ -7729,10 +7785,10 @@ function parseTellCommand(ctx, identifierNode) {
|
|
|
7729
7785
|
catch {
|
|
7730
7786
|
break;
|
|
7731
7787
|
}
|
|
7732
|
-
if (ctx.match(KEYWORDS.AND)) {
|
|
7788
|
+
if (ctx.match(KEYWORDS.AND) || ctx.match(KEYWORDS.THEN)) {
|
|
7733
7789
|
continue;
|
|
7734
7790
|
}
|
|
7735
|
-
if (ctx.check(KEYWORDS.
|
|
7791
|
+
if (ctx.check(KEYWORDS.ELSE) || ctx.check(KEYWORDS.END)) {
|
|
7736
7792
|
break;
|
|
7737
7793
|
}
|
|
7738
7794
|
if (ctx.checkIsCommand()) {
|
|
@@ -7935,6 +7991,7 @@ class Parser {
|
|
|
7935
7991
|
const result = this.parseInternal();
|
|
7936
7992
|
if (this.errors.length > 0) {
|
|
7937
7993
|
result.errors = [...this.errors];
|
|
7994
|
+
result.recovered = true;
|
|
7938
7995
|
}
|
|
7939
7996
|
return result;
|
|
7940
7997
|
}
|
|
@@ -8069,26 +8126,26 @@ class Parser {
|
|
|
8069
8126
|
const commandSequence = this.parseCommandSequence();
|
|
8070
8127
|
if (commandSequence) {
|
|
8071
8128
|
if (!this.isAtEnd() && this.check('on')) {
|
|
8072
|
-
debug.parse('
|
|
8129
|
+
debug.parse('\u2705 PARSER: Found event handlers after command sequence, parsing as program');
|
|
8073
8130
|
const statements = [commandSequence];
|
|
8074
|
-
debug.parse(
|
|
8131
|
+
debug.parse(`\u2705 PARSER: Starting with ${statements.length} statement(s) from command sequence`);
|
|
8075
8132
|
let eventHandlerCount = 0;
|
|
8076
8133
|
while (!this.isAtEnd() && this.check('on')) {
|
|
8077
|
-
debug.parse(
|
|
8134
|
+
debug.parse(`\u2705 PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
|
|
8078
8135
|
this.advance();
|
|
8079
8136
|
const eventHandler = this.parseEventHandler();
|
|
8080
|
-
debug.parse(
|
|
8137
|
+
debug.parse(`\u2705 PARSER: parseEventHandler returned:`, eventHandler
|
|
8081
8138
|
? `type=${eventHandler.type}, event=${eventHandler.event}`
|
|
8082
8139
|
: 'null');
|
|
8083
8140
|
if (eventHandler) {
|
|
8084
8141
|
statements.push(eventHandler);
|
|
8085
8142
|
eventHandlerCount++;
|
|
8086
|
-
debug.parse(
|
|
8143
|
+
debug.parse(`\u2705 PARSER: Added event handler, now have ${statements.length} statements total`);
|
|
8087
8144
|
}
|
|
8088
|
-
debug.parse(
|
|
8145
|
+
debug.parse(`\u2705 PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
|
|
8089
8146
|
if (!this.isAtEnd() && !this.check('on')) {
|
|
8090
8147
|
if (!this.isAtEnd()) {
|
|
8091
|
-
debug.parse(
|
|
8148
|
+
debug.parse(`\u26a0\ufe0f PARSER: Unexpected token after event handlers: ${this.peek().value}`);
|
|
8092
8149
|
this.addError(`Unexpected token after event handlers: ${this.peek().value}`);
|
|
8093
8150
|
return {
|
|
8094
8151
|
success: false,
|
|
@@ -8097,11 +8154,11 @@ class Parser {
|
|
|
8097
8154
|
error: this.error,
|
|
8098
8155
|
};
|
|
8099
8156
|
}
|
|
8100
|
-
debug.parse(
|
|
8157
|
+
debug.parse(`\u2705 PARSER: No more event handlers, at end of input`);
|
|
8101
8158
|
break;
|
|
8102
8159
|
}
|
|
8103
8160
|
}
|
|
8104
|
-
debug.parse(
|
|
8161
|
+
debug.parse(`\u2705 PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
|
|
8105
8162
|
if (this.error) {
|
|
8106
8163
|
return {
|
|
8107
8164
|
success: false,
|
|
@@ -8496,41 +8553,41 @@ class Parser {
|
|
|
8496
8553
|
parseCommandListUntilTerminator(extraStops) {
|
|
8497
8554
|
const commands = [];
|
|
8498
8555
|
const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
|
|
8499
|
-
debug.parse('
|
|
8556
|
+
debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
|
|
8500
8557
|
while (!this.isAtEnd() && !isStop()) {
|
|
8501
|
-
debug.parse('
|
|
8558
|
+
debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
|
|
8502
8559
|
let parsedCommand = false;
|
|
8503
8560
|
const isCommandToken = this.checkIsCommand();
|
|
8504
8561
|
const isCommandIdentifier = !isCommandToken && this.checkIdentifier() && this.isCommand(this.peek().value);
|
|
8505
8562
|
if (isCommandToken || isCommandIdentifier) {
|
|
8506
|
-
debug.parse(isCommandToken ? '
|
|
8563
|
+
debug.parse(isCommandToken ? '\u2705 Found COMMAND token:' : '\u2705 Found IDENTIFIER that is a command:', this.peek().value);
|
|
8507
8564
|
this.advance();
|
|
8508
8565
|
const savedError = this.error;
|
|
8509
8566
|
try {
|
|
8510
8567
|
const cmd = this.parseCommand();
|
|
8511
8568
|
if (this.error && this.error !== savedError) {
|
|
8512
|
-
debug.parse('
|
|
8569
|
+
debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
|
|
8513
8570
|
this.error = savedError;
|
|
8514
8571
|
}
|
|
8515
8572
|
if (cmd) {
|
|
8516
|
-
debug.parse('
|
|
8573
|
+
debug.parse('\u2705 Parsed command:', cmd.name);
|
|
8517
8574
|
commands.push(cmd);
|
|
8518
8575
|
parsedCommand = true;
|
|
8519
8576
|
}
|
|
8520
8577
|
}
|
|
8521
8578
|
catch (error) {
|
|
8522
|
-
debug.parse('
|
|
8579
|
+
debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
|
|
8523
8580
|
this.error = savedError;
|
|
8524
8581
|
}
|
|
8525
8582
|
}
|
|
8526
8583
|
else if (this.checkIdentifier()) {
|
|
8527
|
-
debug.parse('
|
|
8584
|
+
debug.parse('\u274c IDENTIFIER is not a command:', this.peek().value);
|
|
8528
8585
|
}
|
|
8529
8586
|
if (!parsedCommand) {
|
|
8530
|
-
debug.parse('
|
|
8587
|
+
debug.parse('\u274c No command parsed, breaking. Current token:', this.peek().value);
|
|
8531
8588
|
break;
|
|
8532
8589
|
}
|
|
8533
|
-
debug.parse('
|
|
8590
|
+
debug.parse('\ud83d\udccd After parsing command, current token:', this.peek().value);
|
|
8534
8591
|
while (!this.isAtEnd() &&
|
|
8535
8592
|
!isStop() &&
|
|
8536
8593
|
!this.checkIsCommand() &&
|
|
@@ -8538,19 +8595,19 @@ class Parser {
|
|
|
8538
8595
|
!this.check('then') &&
|
|
8539
8596
|
!this.check('and') &&
|
|
8540
8597
|
!this.check(',')) {
|
|
8541
|
-
debug.parse('
|
|
8598
|
+
debug.parse('\u26a0\ufe0f Skipping unexpected token:', this.peek().value);
|
|
8542
8599
|
this.advance();
|
|
8543
8600
|
}
|
|
8544
8601
|
if (this.match('then', 'and', ',')) {
|
|
8545
|
-
debug.parse('
|
|
8602
|
+
debug.parse('\u2705 Found separator, continuing');
|
|
8546
8603
|
continue;
|
|
8547
8604
|
}
|
|
8548
8605
|
else if (this.checkIsCommand() || this.isCommand(this.peek().value)) {
|
|
8549
|
-
debug.parse('
|
|
8606
|
+
debug.parse('\u2705 Next token is a command, continuing without separator');
|
|
8550
8607
|
continue;
|
|
8551
8608
|
}
|
|
8552
8609
|
else {
|
|
8553
|
-
debug.parse('
|
|
8610
|
+
debug.parse('\ud83d\udccd No separator and no command, breaking. Current token:', this.peek().value);
|
|
8554
8611
|
break;
|
|
8555
8612
|
}
|
|
8556
8613
|
}
|
|
@@ -8564,18 +8621,18 @@ class Parser {
|
|
|
8564
8621
|
if (!this.check('end') && !extraStops.some(s => this.check(s))) {
|
|
8565
8622
|
terminator = '';
|
|
8566
8623
|
}
|
|
8567
|
-
debug.parse('
|
|
8624
|
+
debug.parse('\u2705 parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
|
|
8568
8625
|
return { commands, terminator };
|
|
8569
8626
|
}
|
|
8570
8627
|
parseCommandListUntilEnd() {
|
|
8571
8628
|
const { commands } = this.parseCommandListUntilTerminator([]);
|
|
8572
|
-
debug.parse('
|
|
8629
|
+
debug.parse('\ud83d\udd0d After loop, checking for "end". Current token:', this.peek().value);
|
|
8573
8630
|
if (this.check('end')) {
|
|
8574
|
-
debug.parse('
|
|
8631
|
+
debug.parse('\u2705 Found "end", consuming it');
|
|
8575
8632
|
this.advance();
|
|
8576
8633
|
}
|
|
8577
8634
|
else {
|
|
8578
|
-
debug.parse('
|
|
8635
|
+
debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
|
|
8579
8636
|
throw new Error('Expected "end" to close repeat block');
|
|
8580
8637
|
}
|
|
8581
8638
|
return commands;
|
|
@@ -9248,6 +9305,23 @@ class Parser {
|
|
|
9248
9305
|
this.consume(')', "Expected ')' after parameter list");
|
|
9249
9306
|
}
|
|
9250
9307
|
const bodyCommands = this.parseCommandBlock(['end', 'catch', 'finally']);
|
|
9308
|
+
const { errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks();
|
|
9309
|
+
this.consume('end', "Expected 'end' after function definition");
|
|
9310
|
+
return {
|
|
9311
|
+
type: 'def',
|
|
9312
|
+
name: funcName,
|
|
9313
|
+
params,
|
|
9314
|
+
body: bodyCommands,
|
|
9315
|
+
...(errorSymbol !== undefined && { errorSymbol }),
|
|
9316
|
+
...(errorHandler !== undefined && { errorHandler }),
|
|
9317
|
+
...(finallyHandler !== undefined && { finallyHandler }),
|
|
9318
|
+
start: pos.start,
|
|
9319
|
+
end: this.getPosition().end,
|
|
9320
|
+
line: pos.line,
|
|
9321
|
+
column: pos.column,
|
|
9322
|
+
};
|
|
9323
|
+
}
|
|
9324
|
+
parseErrorAndFinallyBlocks() {
|
|
9251
9325
|
let errorSymbol;
|
|
9252
9326
|
let errorHandler;
|
|
9253
9327
|
if (this.check('catch')) {
|
|
@@ -9262,19 +9336,10 @@ class Parser {
|
|
|
9262
9336
|
this.advance();
|
|
9263
9337
|
finallyHandler = this.parseCommandBlock(['end']);
|
|
9264
9338
|
}
|
|
9265
|
-
this.consume('end', "Expected 'end' after function definition");
|
|
9266
9339
|
return {
|
|
9267
|
-
type: 'def',
|
|
9268
|
-
name: funcName,
|
|
9269
|
-
params,
|
|
9270
|
-
body: bodyCommands,
|
|
9271
9340
|
...(errorSymbol !== undefined && { errorSymbol }),
|
|
9272
9341
|
...(errorHandler !== undefined && { errorHandler }),
|
|
9273
9342
|
...(finallyHandler !== undefined && { finallyHandler }),
|
|
9274
|
-
start: pos.start,
|
|
9275
|
-
end: this.getPosition().end,
|
|
9276
|
-
line: pos.line,
|
|
9277
|
-
column: pos.column,
|
|
9278
9343
|
};
|
|
9279
9344
|
}
|
|
9280
9345
|
parseEventNameWithNamespace(errorMessage) {
|
|
@@ -9419,13 +9484,13 @@ class Parser {
|
|
|
9419
9484
|
try {
|
|
9420
9485
|
const cmd = this.parseCommand();
|
|
9421
9486
|
if (this.error && this.error !== savedError) {
|
|
9422
|
-
debug.parse('
|
|
9487
|
+
debug.parse('\u26a0\ufe0f Command parsing added error, restoring error state. Error was:', this.error.message);
|
|
9423
9488
|
this.error = savedError;
|
|
9424
9489
|
}
|
|
9425
9490
|
return cmd;
|
|
9426
9491
|
}
|
|
9427
9492
|
catch (error) {
|
|
9428
|
-
debug.parse('
|
|
9493
|
+
debug.parse('\u26a0\ufe0f Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
|
|
9429
9494
|
this.error = savedError;
|
|
9430
9495
|
return null;
|
|
9431
9496
|
}
|
|
@@ -9442,12 +9507,12 @@ class Parser {
|
|
|
9442
9507
|
const timeToken = this.advance();
|
|
9443
9508
|
const delayMs = parseTimeToMs(timeToken.value);
|
|
9444
9509
|
modifiers[modifierKey] = delayMs;
|
|
9445
|
-
debug.parse(
|
|
9510
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
|
|
9446
9511
|
}
|
|
9447
9512
|
else if (this.matchNumber()) {
|
|
9448
9513
|
const delayMs = parseInt(this.previous().value, 10);
|
|
9449
9514
|
modifiers[modifierKey] = delayMs;
|
|
9450
|
-
debug.parse(
|
|
9515
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
|
|
9451
9516
|
}
|
|
9452
9517
|
else {
|
|
9453
9518
|
this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
|
|
@@ -9458,7 +9523,7 @@ class Parser {
|
|
|
9458
9523
|
}
|
|
9459
9524
|
}
|
|
9460
9525
|
parseEventHandler() {
|
|
9461
|
-
debug.parse(
|
|
9526
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: ENTRY - parsing event handler`);
|
|
9462
9527
|
const eventNames = [];
|
|
9463
9528
|
let firstOnceAlias = false;
|
|
9464
9529
|
if (this.check('first') && !this.checkIsCommand() && this.current + 1 < this.tokens.length) {
|
|
@@ -9467,27 +9532,27 @@ class Parser {
|
|
|
9467
9532
|
if (peek2Value && peek2Value !== 'of' && peek2Value !== 'in' && peek2Value !== 'from') {
|
|
9468
9533
|
this.advance();
|
|
9469
9534
|
firstOnceAlias = true;
|
|
9470
|
-
debug.parse(
|
|
9535
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'first' as .once alias`);
|
|
9471
9536
|
}
|
|
9472
9537
|
}
|
|
9473
9538
|
const event = this.parseEventNameWithNamespace("Expected event name after 'on'");
|
|
9474
9539
|
eventNames.push(event);
|
|
9475
|
-
debug.parse(
|
|
9540
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed first event name: ${event}`);
|
|
9476
9541
|
while (this.check('or')) {
|
|
9477
9542
|
this.advance();
|
|
9478
|
-
debug.parse(
|
|
9543
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Found 'or', parsing additional event name`);
|
|
9479
9544
|
const additionalEvent = this.parseEventNameWithNamespace("Expected event name after 'or'");
|
|
9480
9545
|
eventNames.push(additionalEvent);
|
|
9481
|
-
debug.parse(
|
|
9546
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
|
|
9482
9547
|
}
|
|
9483
|
-
debug.parse(
|
|
9548
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
|
|
9484
9549
|
let customEventSource;
|
|
9485
9550
|
if (this.registryIntegration) {
|
|
9486
9551
|
const firstEvent = eventNames[0];
|
|
9487
9552
|
if (this.registryIntegration.hasEventSource(firstEvent)) {
|
|
9488
9553
|
const source = this.registryIntegration.getEventSource(firstEvent);
|
|
9489
9554
|
customEventSource = source?.name;
|
|
9490
|
-
debug.parse(
|
|
9555
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
|
|
9491
9556
|
}
|
|
9492
9557
|
}
|
|
9493
9558
|
const eventParams = [];
|
|
@@ -9499,7 +9564,7 @@ class Parser {
|
|
|
9499
9564
|
} while (this.match(','));
|
|
9500
9565
|
}
|
|
9501
9566
|
this.consume(')', "Expected ')' after event parameters");
|
|
9502
|
-
debug.parse(
|
|
9567
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
|
|
9503
9568
|
}
|
|
9504
9569
|
const modifiers = {};
|
|
9505
9570
|
while (this.check('.')) {
|
|
@@ -9508,15 +9573,15 @@ class Parser {
|
|
|
9508
9573
|
const modName = modToken.value.toLowerCase();
|
|
9509
9574
|
if (modName === 'once') {
|
|
9510
9575
|
modifiers.once = true;
|
|
9511
|
-
debug.parse(
|
|
9576
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.once'`);
|
|
9512
9577
|
}
|
|
9513
9578
|
else if (modName === 'prevent') {
|
|
9514
9579
|
modifiers.prevent = true;
|
|
9515
|
-
debug.parse(
|
|
9580
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.prevent'`);
|
|
9516
9581
|
}
|
|
9517
9582
|
else if (modName === 'stop') {
|
|
9518
9583
|
modifiers.stop = true;
|
|
9519
|
-
debug.parse(
|
|
9584
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.stop'`);
|
|
9520
9585
|
}
|
|
9521
9586
|
else if (modName === 'debounce' || modName === 'throttle') {
|
|
9522
9587
|
if (this.check('(')) {
|
|
@@ -9528,11 +9593,11 @@ class Parser {
|
|
|
9528
9593
|
}
|
|
9529
9594
|
if (modName === 'debounce') {
|
|
9530
9595
|
modifiers.debounce = delayMs;
|
|
9531
|
-
debug.parse(
|
|
9596
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
|
|
9532
9597
|
}
|
|
9533
9598
|
else {
|
|
9534
9599
|
modifiers.throttle = delayMs;
|
|
9535
|
-
debug.parse(
|
|
9600
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
|
|
9536
9601
|
}
|
|
9537
9602
|
this.consume(')', `Expected ')' after ${modName} delay`);
|
|
9538
9603
|
}
|
|
@@ -9541,7 +9606,7 @@ class Parser {
|
|
|
9541
9606
|
}
|
|
9542
9607
|
}
|
|
9543
9608
|
else {
|
|
9544
|
-
debug.parse(
|
|
9609
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Warning - unknown modifier '.${modName}'`);
|
|
9545
9610
|
}
|
|
9546
9611
|
}
|
|
9547
9612
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
@@ -9549,7 +9614,7 @@ class Parser {
|
|
|
9549
9614
|
modifiers.once = true;
|
|
9550
9615
|
}
|
|
9551
9616
|
if (Object.keys(modifiers).length > 0) {
|
|
9552
|
-
debug.parse(
|
|
9617
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifiers:`, modifiers);
|
|
9553
9618
|
}
|
|
9554
9619
|
let condition;
|
|
9555
9620
|
if (this.match('[')) {
|
|
@@ -9560,7 +9625,7 @@ class Parser {
|
|
|
9560
9625
|
if (this.match('from')) {
|
|
9561
9626
|
const targetToken = this.advance();
|
|
9562
9627
|
target = targetToken.value;
|
|
9563
|
-
debug.parse(
|
|
9628
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
|
|
9564
9629
|
this.parseTemporalKeywordModifiers(modifiers);
|
|
9565
9630
|
}
|
|
9566
9631
|
let attributeName;
|
|
@@ -9572,32 +9637,36 @@ class Parser {
|
|
|
9572
9637
|
else {
|
|
9573
9638
|
attributeName = attrToken.value;
|
|
9574
9639
|
}
|
|
9575
|
-
debug.parse(
|
|
9640
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed attribute name: ${attributeName}`);
|
|
9576
9641
|
}
|
|
9577
9642
|
let watchTarget;
|
|
9578
9643
|
if (this.match('in')) {
|
|
9579
|
-
debug.parse(
|
|
9644
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Found 'in' keyword, parsing watch target`);
|
|
9580
9645
|
watchTarget = this.parseExpression();
|
|
9581
|
-
debug.parse(
|
|
9646
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Parsed watch target expression`);
|
|
9582
9647
|
}
|
|
9583
9648
|
const commands = [];
|
|
9584
|
-
debug.parse(
|
|
9649
|
+
debug.parse(`\u2705 parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
|
|
9585
9650
|
while (!this.isAtEnd()) {
|
|
9586
|
-
debug.parse(
|
|
9651
|
+
debug.parse(`\u2705 parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
|
|
9587
9652
|
if (this.check('on')) {
|
|
9588
|
-
debug.parse(
|
|
9653
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found next event handler 'on'`);
|
|
9589
9654
|
break;
|
|
9590
9655
|
}
|
|
9591
9656
|
if (this.checkComment()) {
|
|
9592
|
-
debug.parse(
|
|
9657
|
+
debug.parse(`\u2705 parseEventHandler: Skipping comment token: ${this.peek().value}`);
|
|
9593
9658
|
this.advance();
|
|
9594
9659
|
continue;
|
|
9595
9660
|
}
|
|
9596
9661
|
if (this.check('end')) {
|
|
9597
|
-
debug.parse(
|
|
9662
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found 'end' keyword`);
|
|
9598
9663
|
this.advance();
|
|
9599
9664
|
break;
|
|
9600
9665
|
}
|
|
9666
|
+
if (this.check('catch') || this.check('finally')) {
|
|
9667
|
+
debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found '${this.peek().value}'`);
|
|
9668
|
+
break;
|
|
9669
|
+
}
|
|
9601
9670
|
if (this.checkIsCommand()) {
|
|
9602
9671
|
const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
|
|
9603
9672
|
const commandName = this.peek().value.toLowerCase();
|
|
@@ -9626,7 +9695,7 @@ class Parser {
|
|
|
9626
9695
|
const cmd = this.parseCommandWithErrorRecovery();
|
|
9627
9696
|
if (cmd) {
|
|
9628
9697
|
commands.push(cmd);
|
|
9629
|
-
debug.parse(
|
|
9698
|
+
debug.parse(`\u2705 parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
|
|
9630
9699
|
}
|
|
9631
9700
|
}
|
|
9632
9701
|
}
|
|
@@ -9644,7 +9713,7 @@ class Parser {
|
|
|
9644
9713
|
expr = this.parseExpression();
|
|
9645
9714
|
}
|
|
9646
9715
|
catch (error) {
|
|
9647
|
-
debug.parse('
|
|
9716
|
+
debug.parse('\u26a0\ufe0f Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
|
|
9648
9717
|
this.error = savedError;
|
|
9649
9718
|
break;
|
|
9650
9719
|
}
|
|
@@ -9691,7 +9760,9 @@ class Parser {
|
|
|
9691
9760
|
!this.check('then') &&
|
|
9692
9761
|
!this.check('and') &&
|
|
9693
9762
|
!this.check(',') &&
|
|
9694
|
-
!this.check('on')
|
|
9763
|
+
!this.check('on') &&
|
|
9764
|
+
!this.check('catch') &&
|
|
9765
|
+
!this.check('finally')) {
|
|
9695
9766
|
this.advance();
|
|
9696
9767
|
}
|
|
9697
9768
|
if (this.match('then', 'and', ',')) {
|
|
@@ -9704,12 +9775,24 @@ class Parser {
|
|
|
9704
9775
|
break;
|
|
9705
9776
|
}
|
|
9706
9777
|
}
|
|
9778
|
+
let errorSymbol;
|
|
9779
|
+
let errorHandler;
|
|
9780
|
+
let finallyHandler;
|
|
9781
|
+
if (!this.isAtEnd() && (this.check('catch') || this.check('finally'))) {
|
|
9782
|
+
({ errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks());
|
|
9783
|
+
if (this.check('end')) {
|
|
9784
|
+
this.advance();
|
|
9785
|
+
}
|
|
9786
|
+
}
|
|
9707
9787
|
const pos = this.getPosition();
|
|
9708
9788
|
const node = {
|
|
9709
9789
|
type: 'eventHandler',
|
|
9710
9790
|
event: eventNames.length === 1 ? eventNames[0] : eventNames.join('|'),
|
|
9711
9791
|
events: eventNames,
|
|
9712
9792
|
commands,
|
|
9793
|
+
...(errorSymbol !== undefined && { errorSymbol }),
|
|
9794
|
+
...(errorHandler !== undefined && { errorHandler }),
|
|
9795
|
+
...(finallyHandler !== undefined && { finallyHandler }),
|
|
9713
9796
|
...(eventParams.length > 0 && { args: eventParams }),
|
|
9714
9797
|
...(condition && { condition }),
|
|
9715
9798
|
...(target && { target }),
|
|
@@ -9722,7 +9805,7 @@ class Parser {
|
|
|
9722
9805
|
line: pos.line,
|
|
9723
9806
|
column: pos.column,
|
|
9724
9807
|
};
|
|
9725
|
-
debug.parse(
|
|
9808
|
+
debug.parse(`\ud83d\udd27 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
|
|
9726
9809
|
return node;
|
|
9727
9810
|
}
|
|
9728
9811
|
parseBehaviorDefinition() {
|
|
@@ -9787,11 +9870,33 @@ class Parser {
|
|
|
9787
9870
|
}
|
|
9788
9871
|
}
|
|
9789
9872
|
}
|
|
9790
|
-
const handlerCommands = this.
|
|
9873
|
+
const { commands: handlerCommands, terminator } = this.parseCommandListUntilTerminator([
|
|
9874
|
+
'catch',
|
|
9875
|
+
'finally',
|
|
9876
|
+
]);
|
|
9877
|
+
let handlerErrorSymbol;
|
|
9878
|
+
let handlerErrorHandler;
|
|
9879
|
+
let handlerFinallyHandler;
|
|
9880
|
+
if (terminator === 'catch' || terminator === 'finally') {
|
|
9881
|
+
({
|
|
9882
|
+
errorSymbol: handlerErrorSymbol,
|
|
9883
|
+
errorHandler: handlerErrorHandler,
|
|
9884
|
+
finallyHandler: handlerFinallyHandler,
|
|
9885
|
+
} = this.parseErrorAndFinallyBlocks());
|
|
9886
|
+
}
|
|
9887
|
+
if (this.check('end')) {
|
|
9888
|
+
this.advance();
|
|
9889
|
+
}
|
|
9890
|
+
else {
|
|
9891
|
+
throw new Error('Expected "end" to close behavior event handler');
|
|
9892
|
+
}
|
|
9791
9893
|
const handlerNode = {
|
|
9792
9894
|
type: 'eventHandler',
|
|
9793
9895
|
event: eventName,
|
|
9794
9896
|
commands: handlerCommands,
|
|
9897
|
+
...(handlerErrorSymbol !== undefined && { errorSymbol: handlerErrorSymbol }),
|
|
9898
|
+
...(handlerErrorHandler !== undefined && { errorHandler: handlerErrorHandler }),
|
|
9899
|
+
...(handlerFinallyHandler !== undefined && { finallyHandler: handlerFinallyHandler }),
|
|
9795
9900
|
...(eventSource !== undefined && { target: eventSource }),
|
|
9796
9901
|
...(eventArgs.length > 0 && { args: eventArgs }),
|
|
9797
9902
|
start: handlerPos.start,
|
|
@@ -9846,7 +9951,7 @@ class Parser {
|
|
|
9846
9951
|
this.error.message.includes('unclosed parenthes') ||
|
|
9847
9952
|
this.error.message.includes('Unclosed parenthes');
|
|
9848
9953
|
if (!isCriticalError) {
|
|
9849
|
-
debug.parse('
|
|
9954
|
+
debug.parse('\u26a0\ufe0f parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
|
|
9850
9955
|
this.error = savedError;
|
|
9851
9956
|
}
|
|
9852
9957
|
}
|
|
@@ -9856,11 +9961,11 @@ class Parser {
|
|
|
9856
9961
|
!this.isCommand(this.peek().value) &&
|
|
9857
9962
|
!this.check('then') &&
|
|
9858
9963
|
!this.check('on')) {
|
|
9859
|
-
debug.parse('
|
|
9964
|
+
debug.parse('\u26a0\ufe0f parseCommandSequence: Skipping unexpected token:', this.peek().value);
|
|
9860
9965
|
this.advance();
|
|
9861
9966
|
}
|
|
9862
9967
|
if (this.check('on')) {
|
|
9863
|
-
debug.parse('
|
|
9968
|
+
debug.parse('\u2705 parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
|
|
9864
9969
|
break;
|
|
9865
9970
|
}
|
|
9866
9971
|
if (this.match('then')) {
|
|
@@ -9918,6 +10023,9 @@ class Parser {
|
|
|
9918
10023
|
this.advance();
|
|
9919
10024
|
const cmd = this.parseCommand();
|
|
9920
10025
|
commands.push(cmd);
|
|
10026
|
+
if (this.match('then', 'and', ',')) {
|
|
10027
|
+
continue;
|
|
10028
|
+
}
|
|
9921
10029
|
}
|
|
9922
10030
|
else {
|
|
9923
10031
|
break;
|
|
@@ -10304,7 +10412,7 @@ class Parser {
|
|
|
10304
10412
|
if (!this.check(')') && callee.type === 'identifier') {
|
|
10305
10413
|
const funcName = callee.name;
|
|
10306
10414
|
if (CommandClassification.isCSSFunction(funcName)) {
|
|
10307
|
-
debug.parse(
|
|
10415
|
+
debug.parse(`\ud83d\udca1 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
|
|
10308
10416
|
`Use '${funcName}(...)' or \`${funcName}(...)\` instead.`);
|
|
10309
10417
|
}
|
|
10310
10418
|
}
|
|
@@ -10606,7 +10714,7 @@ class Parser {
|
|
|
10606
10714
|
if (this.checkIsCommand() || (this.isCommand(token.value) && !this.isKeyword(token.value))) {
|
|
10607
10715
|
return;
|
|
10608
10716
|
}
|
|
10609
|
-
debug.parse('
|
|
10717
|
+
debug.parse('\u26a0\ufe0f synchronize: Skipping token:', token.value);
|
|
10610
10718
|
this.advance();
|
|
10611
10719
|
}
|
|
10612
10720
|
}
|