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