@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.
Files changed (65) hide show
  1. package/dist/ast-utils/index.js +905 -882
  2. package/dist/ast-utils/index.mjs +905 -882
  3. package/dist/bundle-generator/index.js +6 -0
  4. package/dist/bundle-generator/index.mjs +6 -0
  5. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  6. package/dist/chunks/{bridge-D9JLmPkk.js → bridge-DwtNLKK3.js} +2 -2
  7. package/dist/chunks/browser-modular-6HT1r2AH.js +2 -0
  8. package/dist/chunks/feature-eventsource-Bxg_UNZo.js +2 -0
  9. package/dist/chunks/feature-sockets-CcTxD5sT.js +2 -0
  10. package/dist/chunks/feature-webworker-DoqZ0Grg.js +2 -0
  11. package/dist/chunks/{index-6DUg7Qjm.js → index-B8jwvuj_.js} +2 -2
  12. package/dist/commands/index.js +299 -184
  13. package/dist/commands/index.mjs +299 -184
  14. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  15. package/dist/hyperfixi-browser-minimal.js +1 -1
  16. package/dist/hyperfixi-browser-standard.js +1 -1
  17. package/dist/hyperfixi-browser.js +1 -1
  18. package/dist/hyperfixi-classic-i18n.js +1 -1
  19. package/dist/hyperfixi-hx-v4.js +1 -1
  20. package/dist/hyperfixi-hx.js +1 -1
  21. package/dist/hyperfixi-hybrid-complete.js +1 -1
  22. package/dist/hyperfixi-hybrid-hx.js +1 -1
  23. package/dist/hyperfixi-minimal.js +1 -1
  24. package/dist/hyperfixi-multilingual.js +1 -1
  25. package/dist/hyperfixi-standard.js +1 -1
  26. package/dist/hyperfixi.js +1 -1
  27. package/dist/hyperfixi.mjs +1 -1
  28. package/dist/index.js +1731 -1474
  29. package/dist/index.min.js +1 -1
  30. package/dist/index.mjs +1731 -1474
  31. package/dist/lokascript-browser-classic-i18n.js +1 -1
  32. package/dist/lokascript-browser-minimal.js +1 -1
  33. package/dist/lokascript-browser-standard.js +1 -1
  34. package/dist/lokascript-browser.js +1 -1
  35. package/dist/lokascript-hybrid-complete.js +1 -1
  36. package/dist/lokascript-hybrid-hx.js +1 -1
  37. package/dist/lokascript-multilingual.js +1 -1
  38. package/dist/lsp-metadata.js +5 -5
  39. package/dist/lsp-metadata.mjs +5 -5
  40. package/dist/metadata.js +25 -25
  41. package/dist/metadata.mjs +25 -25
  42. package/dist/multilingual/index.js +17 -17
  43. package/dist/multilingual/index.mjs +17 -17
  44. package/dist/parser/command-parsers/utility-commands.d.ts +2 -1
  45. package/dist/parser/full-parser.js +286 -178
  46. package/dist/parser/full-parser.mjs +286 -178
  47. package/dist/parser/hybrid/index.js +3 -0
  48. package/dist/parser/hybrid/index.mjs +3 -0
  49. package/dist/parser/hybrid/parser-core.js +3 -0
  50. package/dist/parser/hybrid/parser-core.mjs +3 -0
  51. package/dist/parser/hybrid-parser.js +3 -0
  52. package/dist/parser/hybrid-parser.mjs +3 -0
  53. package/dist/parser/parser.d.ts +1 -0
  54. package/dist/reference/index.js +2 -0
  55. package/dist/reference/index.mjs +2 -0
  56. package/dist/registry/index.js +286 -178
  57. package/dist/registry/index.mjs +286 -178
  58. package/dist/runtime/runtime-base.d.ts +2 -1
  59. package/dist/types/base-types.d.ts +3 -0
  60. package/dist/types/core.d.ts +1 -0
  61. package/package.json +6 -6
  62. package/dist/chunks/browser-modular-CPiVQXM0.js +0 -2
  63. package/dist/chunks/feature-eventsource-BpZvPy_K.js +0 -2
  64. package/dist/chunks/feature-sockets-CrYvjZ4j.js +0 -2
  65. package/dist/chunks/feature-webworker-BSYguEIW.js +0 -2
@@ -31,39 +31,39 @@ const DEBUG = {
31
31
  const debug = {
32
32
  command: (...args) => {
33
33
  if (DEBUG.commands)
34
- console.log('🔧', ...args);
34
+ console.log('\ud83d\udd27', ...args);
35
35
  },
36
36
  event: (...args) => {
37
37
  if (DEBUG.events)
38
- console.log('🎯', ...args);
38
+ console.log('\ud83c\udfaf', ...args);
39
39
  },
40
40
  parse: (...args) => {
41
41
  if (DEBUG.parsing)
42
- console.log('📝', ...args);
42
+ console.log('\ud83d\udcdd', ...args);
43
43
  },
44
44
  expr: (...args) => {
45
45
  if (DEBUG.expressions)
46
- console.log('🔍', ...args);
46
+ console.log('\ud83d\udd0d', ...args);
47
47
  },
48
48
  expressions: (...args) => {
49
49
  if (DEBUG.expressions)
50
- console.log('🔍', ...args);
50
+ console.log('\ud83d\udd0d', ...args);
51
51
  },
52
52
  style: (...args) => {
53
53
  if (DEBUG.styles)
54
- console.log('🎨', ...args);
54
+ console.log('\ud83c\udfa8', ...args);
55
55
  },
56
56
  runtime: (...args) => {
57
57
  if (DEBUG.runtime)
58
- console.log('🚀', ...args);
58
+ console.log('\ud83d\ude80', ...args);
59
59
  },
60
60
  loop: (...args) => {
61
61
  if (DEBUG.loops)
62
- console.log('🔁', ...args);
62
+ console.log('\ud83d\udd01', ...args);
63
63
  },
64
64
  async: (...args) => {
65
65
  if (DEBUG.async)
66
- console.log('⏳', ...args);
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(`✅ createProgramNode: Called with ${statements.length} statements`);
5774
+ debug.parse(`\u2705 createProgramNode: Called with ${statements.length} statements`);
5774
5775
  if (statements.length === 0) {
5775
- debug.parse(`✅ createProgramNode: Returning error node (0 statements)`);
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(`✅ createProgramNode: Returning single statement (type=${statements[0].type})`);
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(`✅ createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
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
- finalArgs.push(ctx.parsePrimary());
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('📍 Parsing event name, current token:', {
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('✅ Got event name:', eventName, 'Next token:', ctx.peek().value);
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('🔍 Checking for "from", current token:', ctx.peek().value);
6204
+ debug.parse('\ud83d\udd0d Checking for "from", current token:', ctx.peek().value);
6199
6205
  if (ctx.check(KEYWORDS.FROM)) {
6200
- debug.parse('✅ Found "from", advancing...');
6206
+ debug.parse('\u2705 Found "from", advancing...');
6201
6207
  ctx.advance();
6202
- debug.parse('📍 After consuming "from", current token:', ctx.peek().value);
6208
+ debug.parse('\ud83d\udccd After consuming "from", current token:', ctx.peek().value);
6203
6209
  if (consumeOptionalKeyword(ctx, KEYWORDS.THE)) {
6204
- debug.parse('✅ Found "the", advancing...');
6210
+ debug.parse('\u2705 Found "the", advancing...');
6205
6211
  }
6206
6212
  const beforePrimary = ctx.peek();
6207
- debug.parse('🔍 Before parsePrimary for event target:', {
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('✅ After parsePrimary, eventTarget:', eventTarget);
6219
+ debug.parse('\u2705 After parsePrimary, eventTarget:', eventTarget);
6214
6220
  }
6215
6221
  else {
6216
- debug.parse('❌ No "from" found, skipping target parsing');
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.checkIsCommand() || ctx.isCommand(tokenValue)) {
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
- if (hasThen) {
6418
- ctx.advance();
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, isGoURLTerminator);
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 isFetchURLTerminator(ctx) {
7479
- if (ctx.isAtEnd())
7504
+ function isNakedURLStart(ctx) {
7505
+ const tok = ctx.peek();
7506
+ if (tok.value === '/')
7480
7507
  return true;
7481
- const val = ctx.peek().value;
7482
- return (['as', 'with', '{', 'then', 'end', 'else', 'otherwise', ')'].includes(val) ||
7483
- isCommandBoundary(ctx));
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
- function parseBareURLPath(ctx, isTerminator = isFetchURLTerminator) {
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
- while (!ctx.isAtEnd() && !isTerminator(ctx)) {
7489
- path += ctx.advance().value;
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
- return {
7496
- type: 'literal',
7497
- value: path,
7498
- raw: path,
7499
- start: startPos,
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.check('/')) {
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.THEN) || ctx.check(KEYWORDS.ELSE) || ctx.check(KEYWORDS.END)) {
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('✅ PARSER: Found event handlers after command sequence, parsing as program');
8129
+ debug.parse('\u2705 PARSER: Found event handlers after command sequence, parsing as program');
8073
8130
  const statements = [commandSequence];
8074
- debug.parse(`✅ PARSER: Starting with ${statements.length} statement(s) from command sequence`);
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(`✅ PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
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(`✅ PARSER: parseEventHandler returned:`, eventHandler
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(`✅ PARSER: Added event handler, now have ${statements.length} statements total`);
8143
+ debug.parse(`\u2705 PARSER: Added event handler, now have ${statements.length} statements total`);
8087
8144
  }
8088
- debug.parse(`✅ PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
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(`⚠️ PARSER: Unexpected token after event handlers: ${this.peek().value}`);
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(`✅ PARSER: No more event handlers, at end of input`);
8157
+ debug.parse(`\u2705 PARSER: No more event handlers, at end of input`);
8101
8158
  break;
8102
8159
  }
8103
8160
  }
8104
- debug.parse(`✅ PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
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('🔄 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
8556
+ debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
8500
8557
  while (!this.isAtEnd() && !isStop()) {
8501
- debug.parse('📍 Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
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 ? '✅ Found COMMAND token:' : '✅ Found IDENTIFIER that is a command:', this.peek().value);
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('⚠️ parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
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('✅ Parsed command:', cmd.name);
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('⚠️ parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
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('❌ IDENTIFIER is not a command:', this.peek().value);
8584
+ debug.parse('\u274c IDENTIFIER is not a command:', this.peek().value);
8528
8585
  }
8529
8586
  if (!parsedCommand) {
8530
- debug.parse('❌ No command parsed, breaking. Current token:', this.peek().value);
8587
+ debug.parse('\u274c No command parsed, breaking. Current token:', this.peek().value);
8531
8588
  break;
8532
8589
  }
8533
- debug.parse('📍 After parsing command, current token:', this.peek().value);
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('⚠️ Skipping unexpected token:', this.peek().value);
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('✅ Found separator, continuing');
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('✅ Next token is a command, continuing without separator');
8606
+ debug.parse('\u2705 Next token is a command, continuing without separator');
8550
8607
  continue;
8551
8608
  }
8552
8609
  else {
8553
- debug.parse('📍 No separator and no command, breaking. Current token:', this.peek().value);
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('✅ parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
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('🔍 After loop, checking for "end". Current token:', this.peek().value);
8629
+ debug.parse('\ud83d\udd0d After loop, checking for "end". Current token:', this.peek().value);
8573
8630
  if (this.check('end')) {
8574
- debug.parse('✅ Found "end", consuming it');
8631
+ debug.parse('\u2705 Found "end", consuming it');
8575
8632
  this.advance();
8576
8633
  }
8577
8634
  else {
8578
- debug.parse('❌ ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
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('⚠️ Command parsing added error, restoring error state. Error was:', this.error.message);
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('⚠️ Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
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(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
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(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
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(`🔧 parseEventHandler: ENTRY - parsing event handler`);
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(`🔧 parseEventHandler: Parsed 'first' as .once alias`);
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(`🔧 parseEventHandler: Parsed first event name: ${event}`);
9540
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed first event name: ${event}`);
9476
9541
  while (this.check('or')) {
9477
9542
  this.advance();
9478
- debug.parse(`🔧 parseEventHandler: Found 'or', parsing additional event name`);
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(`🔧 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
9546
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
9482
9547
  }
9483
- debug.parse(`🔧 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
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(`🔧 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
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(`🔧 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
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(`🔧 parseEventHandler: Parsed modifier '.once'`);
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(`🔧 parseEventHandler: Parsed modifier '.prevent'`);
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(`🔧 parseEventHandler: Parsed modifier '.stop'`);
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(`🔧 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
9596
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
9532
9597
  }
9533
9598
  else {
9534
9599
  modifiers.throttle = delayMs;
9535
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
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(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
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(`🔧 parseEventHandler: Parsed modifiers:`, modifiers);
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(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
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(`🔧 parseEventHandler: Parsed attribute name: ${attributeName}`);
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(`🔧 parseEventHandler: Found 'in' keyword, parsing watch target`);
9644
+ debug.parse(`\ud83d\udd27 parseEventHandler: Found 'in' keyword, parsing watch target`);
9580
9645
  watchTarget = this.parseExpression();
9581
- debug.parse(`🔧 parseEventHandler: Parsed watch target expression`);
9646
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed watch target expression`);
9582
9647
  }
9583
9648
  const commands = [];
9584
- debug.parse(`✅ parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
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(`✅ parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
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(`✅ parseEventHandler: Stopping command parsing, found next event handler 'on'`);
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(`✅ parseEventHandler: Skipping comment token: ${this.peek().value}`);
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(`✅ parseEventHandler: Stopping command parsing, found 'end' keyword`);
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(`✅ parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
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('⚠️ Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
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(`🔧 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
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.parseCommandListUntilEnd();
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('⚠️ parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
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('⚠️ parseCommandSequence: Skipping unexpected token:', this.peek().value);
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('✅ parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
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(`💡 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
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('⚠️ synchronize: Skipping token:', token.value);
10717
+ debug.parse('\u26a0\ufe0f synchronize: Skipping token:', token.value);
10610
10718
  this.advance();
10611
10719
  }
10612
10720
  }