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