@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
@@ -33,39 +33,39 @@ const DEBUG = {
33
33
  const debug = {
34
34
  command: (...args) => {
35
35
  if (DEBUG.commands)
36
- console.log('🔧', ...args);
36
+ console.log('\ud83d\udd27', ...args);
37
37
  },
38
38
  event: (...args) => {
39
39
  if (DEBUG.events)
40
- console.log('🎯', ...args);
40
+ console.log('\ud83c\udfaf', ...args);
41
41
  },
42
42
  parse: (...args) => {
43
43
  if (DEBUG.parsing)
44
- console.log('📝', ...args);
44
+ console.log('\ud83d\udcdd', ...args);
45
45
  },
46
46
  expr: (...args) => {
47
47
  if (DEBUG.expressions)
48
- console.log('🔍', ...args);
48
+ console.log('\ud83d\udd0d', ...args);
49
49
  },
50
50
  expressions: (...args) => {
51
51
  if (DEBUG.expressions)
52
- console.log('🔍', ...args);
52
+ console.log('\ud83d\udd0d', ...args);
53
53
  },
54
54
  style: (...args) => {
55
55
  if (DEBUG.styles)
56
- console.log('🎨', ...args);
56
+ console.log('\ud83c\udfa8', ...args);
57
57
  },
58
58
  runtime: (...args) => {
59
59
  if (DEBUG.runtime)
60
- console.log('🚀', ...args);
60
+ console.log('\ud83d\ude80', ...args);
61
61
  },
62
62
  loop: (...args) => {
63
63
  if (DEBUG.loops)
64
- console.log('🔁', ...args);
64
+ console.log('\ud83d\udd01', ...args);
65
65
  },
66
66
  async: (...args) => {
67
67
  if (DEBUG.async)
68
- console.log('⏳', ...args);
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(`✅ createProgramNode: Called with ${statements.length} statements`);
5776
+ debug.parse(`\u2705 createProgramNode: Called with ${statements.length} statements`);
5776
5777
  if (statements.length === 0) {
5777
- debug.parse(`✅ createProgramNode: Returning error node (0 statements)`);
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(`✅ createProgramNode: Returning single statement (type=${statements[0].type})`);
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(`✅ createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
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
- finalArgs.push(ctx.parsePrimary());
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('📍 Parsing event name, current token:', {
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('✅ Got event name:', eventName, 'Next token:', ctx.peek().value);
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('🔍 Checking for "from", current token:', ctx.peek().value);
6206
+ debug.parse('\ud83d\udd0d Checking for "from", current token:', ctx.peek().value);
6201
6207
  if (ctx.check(KEYWORDS.FROM)) {
6202
- debug.parse('✅ Found "from", advancing...');
6208
+ debug.parse('\u2705 Found "from", advancing...');
6203
6209
  ctx.advance();
6204
- debug.parse('📍 After consuming "from", current token:', ctx.peek().value);
6210
+ debug.parse('\ud83d\udccd After consuming "from", current token:', ctx.peek().value);
6205
6211
  if (consumeOptionalKeyword(ctx, KEYWORDS.THE)) {
6206
- debug.parse('✅ Found "the", advancing...');
6212
+ debug.parse('\u2705 Found "the", advancing...');
6207
6213
  }
6208
6214
  const beforePrimary = ctx.peek();
6209
- debug.parse('🔍 Before parsePrimary for event target:', {
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('✅ After parsePrimary, eventTarget:', eventTarget);
6221
+ debug.parse('\u2705 After parsePrimary, eventTarget:', eventTarget);
6216
6222
  }
6217
6223
  else {
6218
- debug.parse('❌ No "from" found, skipping target parsing');
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.checkIsCommand() || ctx.isCommand(tokenValue)) {
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
- if (hasThen) {
6420
- ctx.advance();
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, isGoURLTerminator);
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 isFetchURLTerminator(ctx) {
7481
- if (ctx.isAtEnd())
7506
+ function isNakedURLStart(ctx) {
7507
+ const tok = ctx.peek();
7508
+ if (tok.value === '/')
7482
7509
  return true;
7483
- const val = ctx.peek().value;
7484
- return (['as', 'with', '{', 'then', 'end', 'else', 'otherwise', ')'].includes(val) ||
7485
- isCommandBoundary(ctx));
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
- function parseBareURLPath(ctx, isTerminator = isFetchURLTerminator) {
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
- while (!ctx.isAtEnd() && !isTerminator(ctx)) {
7491
- path += ctx.advance().value;
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
- return {
7498
- type: 'literal',
7499
- value: path,
7500
- raw: path,
7501
- start: startPos,
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.check('/')) {
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.THEN) || ctx.check(KEYWORDS.ELSE) || ctx.check(KEYWORDS.END)) {
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('✅ PARSER: Found event handlers after command sequence, parsing as program');
8131
+ debug.parse('\u2705 PARSER: Found event handlers after command sequence, parsing as program');
8075
8132
  const statements = [commandSequence];
8076
- debug.parse(`✅ PARSER: Starting with ${statements.length} statement(s) from command sequence`);
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(`✅ PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
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(`✅ PARSER: parseEventHandler returned:`, eventHandler
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(`✅ PARSER: Added event handler, now have ${statements.length} statements total`);
8145
+ debug.parse(`\u2705 PARSER: Added event handler, now have ${statements.length} statements total`);
8089
8146
  }
8090
- debug.parse(`✅ PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
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(`⚠️ PARSER: Unexpected token after event handlers: ${this.peek().value}`);
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(`✅ PARSER: No more event handlers, at end of input`);
8159
+ debug.parse(`\u2705 PARSER: No more event handlers, at end of input`);
8103
8160
  break;
8104
8161
  }
8105
8162
  }
8106
- debug.parse(`✅ PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
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('🔄 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
8558
+ debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
8502
8559
  while (!this.isAtEnd() && !isStop()) {
8503
- debug.parse('📍 Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
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 ? '✅ Found COMMAND token:' : '✅ Found IDENTIFIER that is a command:', this.peek().value);
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('⚠️ parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
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('✅ Parsed command:', cmd.name);
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('⚠️ parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
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('❌ IDENTIFIER is not a command:', this.peek().value);
8586
+ debug.parse('\u274c IDENTIFIER is not a command:', this.peek().value);
8530
8587
  }
8531
8588
  if (!parsedCommand) {
8532
- debug.parse('❌ No command parsed, breaking. Current token:', this.peek().value);
8589
+ debug.parse('\u274c No command parsed, breaking. Current token:', this.peek().value);
8533
8590
  break;
8534
8591
  }
8535
- debug.parse('📍 After parsing command, current token:', this.peek().value);
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('⚠️ Skipping unexpected token:', this.peek().value);
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('✅ Found separator, continuing');
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('✅ Next token is a command, continuing without separator');
8608
+ debug.parse('\u2705 Next token is a command, continuing without separator');
8552
8609
  continue;
8553
8610
  }
8554
8611
  else {
8555
- debug.parse('📍 No separator and no command, breaking. Current token:', this.peek().value);
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('✅ parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
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('🔍 After loop, checking for "end". Current token:', this.peek().value);
8631
+ debug.parse('\ud83d\udd0d After loop, checking for "end". Current token:', this.peek().value);
8575
8632
  if (this.check('end')) {
8576
- debug.parse('✅ Found "end", consuming it');
8633
+ debug.parse('\u2705 Found "end", consuming it');
8577
8634
  this.advance();
8578
8635
  }
8579
8636
  else {
8580
- debug.parse('❌ ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
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('⚠️ Command parsing added error, restoring error state. Error was:', this.error.message);
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('⚠️ Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
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(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
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(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
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(`🔧 parseEventHandler: ENTRY - parsing event handler`);
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(`🔧 parseEventHandler: Parsed 'first' as .once alias`);
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(`🔧 parseEventHandler: Parsed first event name: ${event}`);
9542
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed first event name: ${event}`);
9478
9543
  while (this.check('or')) {
9479
9544
  this.advance();
9480
- debug.parse(`🔧 parseEventHandler: Found 'or', parsing additional event name`);
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(`🔧 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
9548
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
9484
9549
  }
9485
- debug.parse(`🔧 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
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(`🔧 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
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(`🔧 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
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(`🔧 parseEventHandler: Parsed modifier '.once'`);
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(`🔧 parseEventHandler: Parsed modifier '.prevent'`);
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(`🔧 parseEventHandler: Parsed modifier '.stop'`);
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(`🔧 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
9598
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
9534
9599
  }
9535
9600
  else {
9536
9601
  modifiers.throttle = delayMs;
9537
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
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(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
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(`🔧 parseEventHandler: Parsed modifiers:`, modifiers);
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(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
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(`🔧 parseEventHandler: Parsed attribute name: ${attributeName}`);
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(`🔧 parseEventHandler: Found 'in' keyword, parsing watch target`);
9646
+ debug.parse(`\ud83d\udd27 parseEventHandler: Found 'in' keyword, parsing watch target`);
9582
9647
  watchTarget = this.parseExpression();
9583
- debug.parse(`🔧 parseEventHandler: Parsed watch target expression`);
9648
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed watch target expression`);
9584
9649
  }
9585
9650
  const commands = [];
9586
- debug.parse(`✅ parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
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(`✅ parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
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(`✅ parseEventHandler: Stopping command parsing, found next event handler 'on'`);
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(`✅ parseEventHandler: Skipping comment token: ${this.peek().value}`);
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(`✅ parseEventHandler: Stopping command parsing, found 'end' keyword`);
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(`✅ parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
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('⚠️ Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
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(`🔧 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
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.parseCommandListUntilEnd();
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('⚠️ parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
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('⚠️ parseCommandSequence: Skipping unexpected token:', this.peek().value);
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('✅ parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
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(`💡 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
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('⚠️ synchronize: Skipping token:', token.value);
10719
+ debug.parse('\u26a0\ufe0f synchronize: Skipping token:', token.value);
10612
10720
  this.advance();
10613
10721
  }
10614
10722
  }