@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
@@ -151,7 +151,7 @@ function resolveElement(target, context, commandName) {
151
151
  if (!target) {
152
152
  const me = context.me;
153
153
  if (!me) {
154
- throw new Error(withCmd(commandName, 'No target element — provide explicit target or ensure context.me is set'));
154
+ throw new Error(withCmd(commandName, 'No target element \u2014 provide explicit target or ensure context.me is set'));
155
155
  }
156
156
  return asHTMLElement(me);
157
157
  }
@@ -177,7 +177,7 @@ function resolveElement(target, context, commandName) {
177
177
  }
178
178
  const doc = context.me?.ownerDocument ?? (typeof document !== 'undefined' ? document : null);
179
179
  if (!doc) {
180
- throw new Error(withCmd(commandName, 'DOM not available — cannot resolve element selector'));
180
+ throw new Error(withCmd(commandName, 'DOM not available \u2014 cannot resolve element selector'));
181
181
  }
182
182
  const element = doc.querySelector(trimmed);
183
183
  if (!element) {
@@ -633,39 +633,39 @@ const DEBUG = {
633
633
  const debug = {
634
634
  command: (...args) => {
635
635
  if (DEBUG.commands)
636
- console.log('🔧', ...args);
636
+ console.log('\ud83d\udd27', ...args);
637
637
  },
638
638
  event: (...args) => {
639
639
  if (DEBUG.events)
640
- console.log('🎯', ...args);
640
+ console.log('\ud83c\udfaf', ...args);
641
641
  },
642
642
  parse: (...args) => {
643
643
  if (DEBUG.parsing)
644
- console.log('📝', ...args);
644
+ console.log('\ud83d\udcdd', ...args);
645
645
  },
646
646
  expr: (...args) => {
647
647
  if (DEBUG.expressions)
648
- console.log('🔍', ...args);
648
+ console.log('\ud83d\udd0d', ...args);
649
649
  },
650
650
  expressions: (...args) => {
651
651
  if (DEBUG.expressions)
652
- console.log('🔍', ...args);
652
+ console.log('\ud83d\udd0d', ...args);
653
653
  },
654
654
  style: (...args) => {
655
655
  if (DEBUG.styles)
656
- console.log('🎨', ...args);
656
+ console.log('\ud83c\udfa8', ...args);
657
657
  },
658
658
  runtime: (...args) => {
659
659
  if (DEBUG.runtime)
660
- console.log('🚀', ...args);
660
+ console.log('\ud83d\ude80', ...args);
661
661
  },
662
662
  loop: (...args) => {
663
663
  if (DEBUG.loops)
664
- console.log('🔁', ...args);
664
+ console.log('\ud83d\udd01', ...args);
665
665
  },
666
666
  async: (...args) => {
667
667
  if (DEBUG.async)
668
- console.log('⏳', ...args);
668
+ console.log('\u23f3', ...args);
669
669
  },
670
670
  };
671
671
 
@@ -6218,6 +6218,7 @@ function tokenize(input) {
6218
6218
  const prevToken = tokenizer.tokens[tokenizer.tokens.length - 1];
6219
6219
  const isPossessive = nextChar === 's' &&
6220
6220
  prevToken &&
6221
+ !/\s/.test(tokenizer.input[tokenizer.position - 1] ?? ' ') &&
6221
6222
  (prevToken.kind === TokenKind.IDENTIFIER ||
6222
6223
  prevToken.kind === TokenKind.SELECTOR ||
6223
6224
  prevToken.value === ']' ||
@@ -6309,13 +6310,13 @@ function tokenize(input) {
6309
6310
  continue;
6310
6311
  }
6311
6312
  if (char === '{') {
6312
- addToken(tokenizer, TokenKind.OPERATOR, '{');
6313
6313
  advance(tokenizer);
6314
+ addToken(tokenizer, TokenKind.OPERATOR, '{');
6314
6315
  continue;
6315
6316
  }
6316
6317
  if (char === '}') {
6317
- addToken(tokenizer, TokenKind.OPERATOR, '}');
6318
6318
  advance(tokenizer);
6319
+ addToken(tokenizer, TokenKind.OPERATOR, '}');
6319
6320
  continue;
6320
6321
  }
6321
6322
  if (char === '[') {
@@ -6331,24 +6332,24 @@ function tokenize(input) {
6331
6332
  prevToken.value === ']') &&
6332
6333
  !isEventCondition;
6333
6334
  if (isMemberAccess) {
6334
- addToken(tokenizer, TokenKind.OPERATOR, '[');
6335
6335
  advance(tokenizer);
6336
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
6336
6337
  }
6337
6338
  else {
6338
6339
  if (isEventCondition) {
6339
- addToken(tokenizer, TokenKind.SYMBOL, '[');
6340
6340
  advance(tokenizer);
6341
+ addToken(tokenizer, TokenKind.SYMBOL, '[');
6341
6342
  }
6342
6343
  else {
6343
- addToken(tokenizer, TokenKind.OPERATOR, '[');
6344
6344
  advance(tokenizer);
6345
+ addToken(tokenizer, TokenKind.OPERATOR, '[');
6345
6346
  }
6346
6347
  }
6347
6348
  continue;
6348
6349
  }
6349
6350
  if (char === ']') {
6350
- addToken(tokenizer, TokenKind.OPERATOR, ']');
6351
6351
  advance(tokenizer);
6352
+ addToken(tokenizer, TokenKind.OPERATOR, ']');
6352
6353
  continue;
6353
6354
  }
6354
6355
  if (char === '$') {
@@ -7942,9 +7943,9 @@ function createPartialCommandNode(name, pos) {
7942
7943
  };
7943
7944
  }
7944
7945
  function createProgramNode(statements) {
7945
- debug.parse(`✅ createProgramNode: Called with ${statements.length} statements`);
7946
+ debug.parse(`\u2705 createProgramNode: Called with ${statements.length} statements`);
7946
7947
  if (statements.length === 0) {
7947
- debug.parse(`✅ createProgramNode: Returning error node (0 statements)`);
7948
+ debug.parse(`\u2705 createProgramNode: Returning error node (0 statements)`);
7948
7949
  return {
7949
7950
  type: 'identifier',
7950
7951
  name: '__ERROR__',
@@ -7955,7 +7956,7 @@ function createProgramNode(statements) {
7955
7956
  };
7956
7957
  }
7957
7958
  if (statements.length === 1) {
7958
- debug.parse(`✅ createProgramNode: Returning single statement (type=${statements[0].type})`);
7959
+ debug.parse(`\u2705 createProgramNode: Returning single statement (type=${statements[0].type})`);
7959
7960
  return statements[0];
7960
7961
  }
7961
7962
  const programNode = {
@@ -7966,7 +7967,7 @@ function createProgramNode(statements) {
7966
7967
  line: statements[0]?.line || 1,
7967
7968
  column: statements[0]?.column || 1,
7968
7969
  };
7969
- debug.parse(`✅ createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
7970
+ debug.parse(`\u2705 createProgramNode: Returning Program node with ${statements.length} statements, type=${programNode.type}`);
7970
7971
  return programNode;
7971
7972
  }
7972
7973
 
@@ -7977,7 +7978,6 @@ const MULTI_WORD_PATTERNS = [
7977
7978
  keywords: ['a', 'an', 'from', 'called'],
7978
7979
  syntax: 'make (a|an) <type> [from <args>] [called <name>]',
7979
7980
  },
7980
- { command: 'send', keywords: ['to'], syntax: 'send <event> to <target>' },
7981
7981
  { command: 'throw', keywords: [], syntax: 'throw <error>' },
7982
7982
  ];
7983
7983
  function getMultiWordPattern(commandName) {
@@ -8278,7 +8278,13 @@ function parseTriggerCommand(ctx, identifierNode) {
8278
8278
  finalArgs.push(ctx.createIdentifier(keyword));
8279
8279
  continue;
8280
8280
  }
8281
- finalArgs.push(ctx.parsePrimary());
8281
+ const before = ctx.savePosition();
8282
+ const expr = ctx.parsePrimary();
8283
+ if (expr)
8284
+ finalArgs.push(expr);
8285
+ if (ctx.savePosition() === before) {
8286
+ ctx.advance();
8287
+ }
8282
8288
  }
8283
8289
  return CommandNodeBuilder.fromIdentifier(identifierNode)
8284
8290
  .withArgs(...finalArgs)
@@ -8355,37 +8361,37 @@ function parseRepeatCommand(ctx, commandToken) {
8355
8361
  ctx.advance();
8356
8362
  loopType = 'until-event';
8357
8363
  const eventToken = ctx.peek();
8358
- debug.parse('📍 Parsing event name, current token:', {
8364
+ debug.parse('\ud83d\udccd Parsing event name, current token:', {
8359
8365
  value: eventToken.value,
8360
8366
  kind: eventToken.kind,
8361
8367
  });
8362
8368
  if (isIdentifierLike(eventToken) || isEvent(eventToken)) {
8363
8369
  eventName = eventToken.value;
8364
8370
  ctx.advance();
8365
- debug.parse('✅ Got event name:', eventName, 'Next token:', ctx.peek().value);
8371
+ debug.parse('\u2705 Got event name:', eventName, 'Next token:', ctx.peek().value);
8366
8372
  }
8367
8373
  else {
8368
8374
  throw new Error('Expected event name after "event"');
8369
8375
  }
8370
- debug.parse('🔍 Checking for "from", current token:', ctx.peek().value);
8376
+ debug.parse('\ud83d\udd0d Checking for "from", current token:', ctx.peek().value);
8371
8377
  if (ctx.check(KEYWORDS$1.FROM)) {
8372
- debug.parse('✅ Found "from", advancing...');
8378
+ debug.parse('\u2705 Found "from", advancing...');
8373
8379
  ctx.advance();
8374
- debug.parse('📍 After consuming "from", current token:', ctx.peek().value);
8380
+ debug.parse('\ud83d\udccd After consuming "from", current token:', ctx.peek().value);
8375
8381
  if (consumeOptionalKeyword(ctx, KEYWORDS$1.THE)) {
8376
- debug.parse('✅ Found "the", advancing...');
8382
+ debug.parse('\u2705 Found "the", advancing...');
8377
8383
  }
8378
8384
  const beforePrimary = ctx.peek();
8379
- debug.parse('🔍 Before parsePrimary for event target:', {
8385
+ debug.parse('\ud83d\udd0d Before parsePrimary for event target:', {
8380
8386
  value: beforePrimary.value,
8381
8387
  kind: beforePrimary.kind,
8382
8388
  position: beforePrimary.start,
8383
8389
  });
8384
8390
  eventTarget = ctx.parsePrimary();
8385
- debug.parse('✅ After parsePrimary, eventTarget:', eventTarget);
8391
+ debug.parse('\u2705 After parsePrimary, eventTarget:', eventTarget);
8386
8392
  }
8387
8393
  else {
8388
- debug.parse('❌ No "from" found, skipping target parsing');
8394
+ debug.parse('\u274c No "from" found, skipping target parsing');
8389
8395
  }
8390
8396
  }
8391
8397
  else {
@@ -8497,11 +8503,64 @@ function parseRepeatCommand(ctx, commandToken) {
8497
8503
  }
8498
8504
  return builder.endingAt(ctx.getPosition()).build();
8499
8505
  }
8506
+ function parseIfBranchCommands(ctx, stopAtElse) {
8507
+ const commands = [];
8508
+ const atTerminator = () => ctx.check(KEYWORDS$1.END) || (stopAtElse && ctx.check(KEYWORDS$1.ELSE));
8509
+ while (!ctx.isAtEnd() && !atTerminator()) {
8510
+ if (isComment(ctx.peek())) {
8511
+ ctx.advance();
8512
+ continue;
8513
+ }
8514
+ if (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))
8515
+ break;
8516
+ ctx.advance();
8517
+ const cmd = ctx.parseCommand();
8518
+ if (cmd) {
8519
+ commands.push(cmd);
8520
+ }
8521
+ if (ctx.check(KEYWORDS$1.THEN) || ctx.check(KEYWORDS$1.AND) || ctx.check(',')) {
8522
+ ctx.advance();
8523
+ }
8524
+ }
8525
+ return commands;
8526
+ }
8527
+ const OPERAND_INTRODUCERS = new Set([
8528
+ 'is',
8529
+ 'am',
8530
+ 'are',
8531
+ 'not',
8532
+ 'no',
8533
+ 'and',
8534
+ 'or',
8535
+ '==',
8536
+ '===',
8537
+ '!=',
8538
+ '!==',
8539
+ '<',
8540
+ '>',
8541
+ '<=',
8542
+ '>=',
8543
+ '+',
8544
+ '-',
8545
+ '*',
8546
+ '/',
8547
+ 'mod',
8548
+ ]);
8549
+ function isBodyCommandStart(ctx, prevToken, token) {
8550
+ if (prevToken === null) {
8551
+ return false;
8552
+ }
8553
+ if (OPERAND_INTRODUCERS.has(prevToken.value?.toLowerCase?.() ?? '')) {
8554
+ return false;
8555
+ }
8556
+ return ctx.checkIsCommand() || ctx.isCommand(token.value?.toLowerCase());
8557
+ }
8500
8558
  function parseIfCommand(ctx, commandToken) {
8501
8559
  const args = [];
8502
8560
  let hasThen = false;
8503
8561
  const savedPosForThen = ctx.savePosition();
8504
8562
  const maxThenLookahead = 500;
8563
+ let prevForThen = null;
8505
8564
  for (let i = 0; i < maxThenLookahead && !ctx.isAtEnd(); i++) {
8506
8565
  const token = ctx.peek();
8507
8566
  if (token.value === KEYWORDS$1.THEN) {
@@ -8514,6 +8573,14 @@ function parseIfCommand(ctx, commandToken) {
8514
8573
  token.value === KEYWORDS$1.ON) {
8515
8574
  break;
8516
8575
  }
8576
+ if (token.line !== undefined &&
8577
+ token.line !== commandToken.line &&
8578
+ isBodyCommandStart(ctx, prevForThen, token)) {
8579
+ break;
8580
+ }
8581
+ if (!isComment(token)) {
8582
+ prevForThen = token;
8583
+ }
8517
8584
  ctx.advance();
8518
8585
  }
8519
8586
  ctx.restorePosition(savedPosForThen);
@@ -8522,9 +8589,14 @@ function parseIfCommand(ctx, commandToken) {
8522
8589
  const savedPosition = ctx.savePosition();
8523
8590
  const ifStatementLine = commandToken.line;
8524
8591
  const maxLookahead = 100;
8592
+ let firstCommandOnIfLine = false;
8593
+ let prevToken = null;
8525
8594
  while (!ctx.isAtEnd() && ctx.current - savedPosition < maxLookahead) {
8526
8595
  const token = ctx.peek();
8527
8596
  const tokenValue = token.value?.toLowerCase();
8597
+ if (firstCommandOnIfLine && token.line !== undefined && token.line !== ifStatementLine) {
8598
+ break;
8599
+ }
8528
8600
  if (tokenValue === KEYWORDS$1.BEHAVIOR ||
8529
8601
  tokenValue === KEYWORDS$1.DEF ||
8530
8602
  tokenValue === KEYWORDS$1.ON) {
@@ -8536,11 +8608,15 @@ function parseIfCommand(ctx, commandToken) {
8536
8608
  }
8537
8609
  break;
8538
8610
  }
8539
- if (ctx.checkIsCommand() || ctx.isCommand(tokenValue)) {
8611
+ if (isBodyCommandStart(ctx, prevToken, token)) {
8540
8612
  if (token.line !== undefined && token.line !== ifStatementLine) {
8541
8613
  hasImplicitMultiLineEnd = true;
8542
8614
  break;
8543
8615
  }
8616
+ firstCommandOnIfLine = true;
8617
+ }
8618
+ if (!isComment(token)) {
8619
+ prevToken = token;
8544
8620
  }
8545
8621
  ctx.advance();
8546
8622
  }
@@ -8555,11 +8631,12 @@ function parseIfCommand(ctx, commandToken) {
8555
8631
  const conditionTokens = [];
8556
8632
  const maxIterations = 20;
8557
8633
  let iterations = 0;
8634
+ let firstConditionParse = true;
8558
8635
  while (!ctx.isAtEnd() &&
8559
- !ctx.checkIsCommand() &&
8560
- !ctx.isCommand(ctx.peek().value) &&
8561
8636
  !ctx.check(KEYWORDS$1.THEN) &&
8637
+ (firstConditionParse || (!ctx.checkIsCommand() && !ctx.isCommand(ctx.peek().value))) &&
8562
8638
  iterations < maxIterations) {
8639
+ firstConditionParse = false;
8563
8640
  const beforePos = ctx.savePosition();
8564
8641
  conditionTokens.push(ctx.parseLogicalAnd());
8565
8642
  if (ctx.savePosition() === beforePos) {
@@ -8586,22 +8663,8 @@ function parseIfCommand(ctx, commandToken) {
8586
8663
  }
8587
8664
  args.push(condition);
8588
8665
  if (isMultiLine) {
8589
- if (hasThen) {
8590
- ctx.advance();
8591
- }
8592
- const thenCommands = [];
8593
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.ELSE) && !ctx.check(KEYWORDS$1.END)) {
8594
- if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
8595
- ctx.advance();
8596
- const cmd = ctx.parseCommand();
8597
- if (cmd) {
8598
- thenCommands.push(cmd);
8599
- }
8600
- }
8601
- else {
8602
- break;
8603
- }
8604
- }
8666
+ consumeOptionalKeyword(ctx, KEYWORDS$1.THEN);
8667
+ const thenCommands = parseIfBranchCommands(ctx, true);
8605
8668
  if (thenCommands.length === 0 && ctx.isAtEnd()) {
8606
8669
  throw new Error("Expected command after 'then' in if statement - incomplete conditional");
8607
8670
  }
@@ -8627,19 +8690,7 @@ function parseIfCommand(ctx, commandToken) {
8627
8690
  consumedElseIf = true;
8628
8691
  }
8629
8692
  else {
8630
- const elseCommands = [];
8631
- while (!ctx.isAtEnd() && !ctx.check(KEYWORDS$1.END)) {
8632
- if (ctx.checkIsCommand() || ctx.isCommand(ctx.peek().value)) {
8633
- ctx.advance();
8634
- const cmd = ctx.parseCommand();
8635
- if (cmd) {
8636
- elseCommands.push(cmd);
8637
- }
8638
- }
8639
- else {
8640
- break;
8641
- }
8642
- }
8693
+ const elseCommands = parseIfBranchCommands(ctx, false);
8643
8694
  args.push(createBlock(elseCommands, {
8644
8695
  start: commandToken.start,
8645
8696
  end: ctx.getPosition().end,
@@ -9461,21 +9512,6 @@ const GO_KEYWORDS = new Set([
9461
9512
  'back',
9462
9513
  'forward',
9463
9514
  ]);
9464
- const GO_URL_STOP = new Set([
9465
- 'in',
9466
- 'then',
9467
- 'and',
9468
- 'else',
9469
- 'end',
9470
- 'otherwise',
9471
- 'when',
9472
- 'where',
9473
- 'catch',
9474
- 'finally',
9475
- ]);
9476
- function isGoURLTerminator(ctx) {
9477
- return ctx.isAtEnd() || GO_URL_STOP.has(ctx.peek().value);
9478
- }
9479
9515
  function stringNode(value, tok) {
9480
9516
  return {
9481
9517
  type: 'string',
@@ -9486,16 +9522,6 @@ function stringNode(value, tok) {
9486
9522
  column: tok.column,
9487
9523
  };
9488
9524
  }
9489
- function isNakedURLStart(ctx) {
9490
- const tok = ctx.peek();
9491
- if (tok.value === '/')
9492
- return true;
9493
- if (ctx.checkIdentifierLike()) {
9494
- const next = ctx.peekAt(1);
9495
- return !!next && next.value === ':' && next.start === tok.end;
9496
- }
9497
- return false;
9498
- }
9499
9525
  function parseGoCommand(ctx, identifierNode) {
9500
9526
  const args = [];
9501
9527
  while (!isCommandBoundary(ctx, ['when', 'where', 'catch', 'finally'])) {
@@ -9507,7 +9533,7 @@ function parseGoCommand(ctx, identifierNode) {
9507
9533
  continue;
9508
9534
  }
9509
9535
  if (isNakedURLStart(ctx)) {
9510
- const url = parseBareURLPath(ctx, isGoURLTerminator);
9536
+ const url = parseBareURLPath(ctx);
9511
9537
  if (url) {
9512
9538
  args.push(url);
9513
9539
  continue;
@@ -9647,35 +9673,65 @@ function parseMultiWordCommand(ctx, commandToken, commandName) {
9647
9673
  }
9648
9674
  return builder.build();
9649
9675
  }
9650
- function isFetchURLTerminator(ctx) {
9651
- if (ctx.isAtEnd())
9676
+ function isNakedURLStart(ctx) {
9677
+ const tok = ctx.peek();
9678
+ if (tok.value === '/')
9652
9679
  return true;
9653
- const val = ctx.peek().value;
9654
- return (['as', 'with', '{', 'then', 'end', 'else', 'otherwise', ')'].includes(val) ||
9655
- isCommandBoundary(ctx));
9680
+ if (ctx.checkIdentifierLike()) {
9681
+ const next = ctx.peekAt(1);
9682
+ return !!next && next.value === ':' && next.start === tok.end;
9683
+ }
9684
+ return false;
9656
9685
  }
9657
- function parseBareURLPath(ctx, isTerminator = isFetchURLTerminator) {
9686
+ const URL_HARD_STOPS = new Set(['{', ')']);
9687
+ function parseBareURLPath(ctx) {
9658
9688
  const startPos = ctx.savePosition();
9689
+ const firstTok = ctx.peek();
9659
9690
  let path = '';
9660
- while (!ctx.isAtEnd() && !isTerminator(ctx)) {
9661
- path += ctx.advance().value;
9691
+ let prev = null;
9692
+ while (!ctx.isAtEnd()) {
9693
+ const tok = ctx.peek();
9694
+ if (prev && tok.start !== prev.end)
9695
+ break;
9696
+ if (tok.value === '$' && ctx.peekAt(1)?.value === '{') {
9697
+ ctx.advance();
9698
+ let last = ctx.advance();
9699
+ let depth = 1;
9700
+ while (!ctx.isAtEnd() && depth > 0) {
9701
+ last = ctx.advance();
9702
+ if (last.value === '{')
9703
+ depth++;
9704
+ else if (last.value === '}')
9705
+ depth--;
9706
+ }
9707
+ path += ctx.getInputSlice(tok.start, last.end);
9708
+ prev = last;
9709
+ continue;
9710
+ }
9711
+ if (URL_HARD_STOPS.has(tok.value))
9712
+ break;
9713
+ prev = ctx.advance();
9714
+ path += prev.value;
9662
9715
  }
9663
9716
  if (!path || path === '/') {
9664
9717
  ctx.restorePosition(startPos);
9665
9718
  return null;
9666
9719
  }
9667
- return {
9668
- type: 'literal',
9669
- value: path,
9670
- raw: path,
9671
- start: startPos,
9672
- end: ctx.savePosition(),
9720
+ const span = {
9721
+ start: firstTok.start,
9722
+ end: prev ? prev.end : firstTok.end,
9723
+ line: firstTok.line,
9724
+ column: firstTok.column,
9673
9725
  };
9726
+ if (path.includes('${')) {
9727
+ return { type: 'templateLiteral', value: path, ...span };
9728
+ }
9729
+ return { type: 'literal', value: path, raw: path, ...span };
9674
9730
  }
9675
9731
  function parseFetchCommand(ctx, commandToken) {
9676
9732
  const modifiers = {};
9677
9733
  let url = null;
9678
- if (!ctx.isAtEnd() && ctx.check('/')) {
9734
+ if (!ctx.isAtEnd() && isNakedURLStart(ctx)) {
9679
9735
  url = parseBareURLPath(ctx);
9680
9736
  }
9681
9737
  if (!url) {
@@ -9901,10 +9957,10 @@ function parseTellCommand(ctx, identifierNode) {
9901
9957
  catch {
9902
9958
  break;
9903
9959
  }
9904
- if (ctx.match(KEYWORDS$1.AND)) {
9960
+ if (ctx.match(KEYWORDS$1.AND) || ctx.match(KEYWORDS$1.THEN)) {
9905
9961
  continue;
9906
9962
  }
9907
- if (ctx.check(KEYWORDS$1.THEN) || ctx.check(KEYWORDS$1.ELSE) || ctx.check(KEYWORDS$1.END)) {
9963
+ if (ctx.check(KEYWORDS$1.ELSE) || ctx.check(KEYWORDS$1.END)) {
9908
9964
  break;
9909
9965
  }
9910
9966
  if (ctx.checkIsCommand()) {
@@ -10107,6 +10163,7 @@ class Parser {
10107
10163
  const result = this.parseInternal();
10108
10164
  if (this.errors.length > 0) {
10109
10165
  result.errors = [...this.errors];
10166
+ result.recovered = true;
10110
10167
  }
10111
10168
  return result;
10112
10169
  }
@@ -10241,26 +10298,26 @@ class Parser {
10241
10298
  const commandSequence = this.parseCommandSequence();
10242
10299
  if (commandSequence) {
10243
10300
  if (!this.isAtEnd() && this.check('on')) {
10244
- debug.parse('✅ PARSER: Found event handlers after command sequence, parsing as program');
10301
+ debug.parse('\u2705 PARSER: Found event handlers after command sequence, parsing as program');
10245
10302
  const statements = [commandSequence];
10246
- debug.parse(`✅ PARSER: Starting with ${statements.length} statement(s) from command sequence`);
10303
+ debug.parse(`\u2705 PARSER: Starting with ${statements.length} statement(s) from command sequence`);
10247
10304
  let eventHandlerCount = 0;
10248
10305
  while (!this.isAtEnd() && this.check('on')) {
10249
- debug.parse(`✅ PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
10306
+ debug.parse(`\u2705 PARSER: Parsing event handler #${eventHandlerCount + 1}, current token: ${this.peek().value}`);
10250
10307
  this.advance();
10251
10308
  const eventHandler = this.parseEventHandler();
10252
- debug.parse(`✅ PARSER: parseEventHandler returned:`, eventHandler
10309
+ debug.parse(`\u2705 PARSER: parseEventHandler returned:`, eventHandler
10253
10310
  ? `type=${eventHandler.type}, event=${eventHandler.event}`
10254
10311
  : 'null');
10255
10312
  if (eventHandler) {
10256
10313
  statements.push(eventHandler);
10257
10314
  eventHandlerCount++;
10258
- debug.parse(`✅ PARSER: Added event handler, now have ${statements.length} statements total`);
10315
+ debug.parse(`\u2705 PARSER: Added event handler, now have ${statements.length} statements total`);
10259
10316
  }
10260
- debug.parse(`✅ PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
10317
+ debug.parse(`\u2705 PARSER: After parsing event handler, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd=${this.isAtEnd()}, check('on')=${this.check('on')}`);
10261
10318
  if (!this.isAtEnd() && !this.check('on')) {
10262
10319
  if (!this.isAtEnd()) {
10263
- debug.parse(`⚠️ PARSER: Unexpected token after event handlers: ${this.peek().value}`);
10320
+ debug.parse(`\u26a0\ufe0f PARSER: Unexpected token after event handlers: ${this.peek().value}`);
10264
10321
  this.addError(`Unexpected token after event handlers: ${this.peek().value}`);
10265
10322
  return {
10266
10323
  success: false,
@@ -10269,11 +10326,11 @@ class Parser {
10269
10326
  error: this.error,
10270
10327
  };
10271
10328
  }
10272
- debug.parse(`✅ PARSER: No more event handlers, at end of input`);
10329
+ debug.parse(`\u2705 PARSER: No more event handlers, at end of input`);
10273
10330
  break;
10274
10331
  }
10275
10332
  }
10276
- debug.parse(`✅ PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
10333
+ debug.parse(`\u2705 PARSER: Finished parsing, creating Program node with ${statements.length} statements`);
10277
10334
  if (this.error) {
10278
10335
  return {
10279
10336
  success: false,
@@ -10668,41 +10725,41 @@ class Parser {
10668
10725
  parseCommandListUntilTerminator(extraStops) {
10669
10726
  const commands = [];
10670
10727
  const isStop = () => this.check('end') || extraStops.some(s => this.check(s));
10671
- debug.parse('🔄 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
10728
+ debug.parse('\ud83d\udd04 parseCommandListUntilTerminator: Starting (extraStops:', extraStops.join(','), ')');
10672
10729
  while (!this.isAtEnd() && !isStop()) {
10673
- debug.parse('📍 Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
10730
+ debug.parse('\ud83d\udccd Loop iteration, current token:', this.peek().value, 'kind:', this.peek().kind);
10674
10731
  let parsedCommand = false;
10675
10732
  const isCommandToken = this.checkIsCommand();
10676
10733
  const isCommandIdentifier = !isCommandToken && this.checkIdentifier() && this.isCommand(this.peek().value);
10677
10734
  if (isCommandToken || isCommandIdentifier) {
10678
- debug.parse(isCommandToken ? '✅ Found COMMAND token:' : '✅ Found IDENTIFIER that is a command:', this.peek().value);
10735
+ debug.parse(isCommandToken ? '\u2705 Found COMMAND token:' : '\u2705 Found IDENTIFIER that is a command:', this.peek().value);
10679
10736
  this.advance();
10680
10737
  const savedError = this.error;
10681
10738
  try {
10682
10739
  const cmd = this.parseCommand();
10683
10740
  if (this.error && this.error !== savedError) {
10684
- debug.parse('⚠️ parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
10741
+ debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing added error, restoring. Error was:', this.error.message);
10685
10742
  this.error = savedError;
10686
10743
  }
10687
10744
  if (cmd) {
10688
- debug.parse('✅ Parsed command:', cmd.name);
10745
+ debug.parse('\u2705 Parsed command:', cmd.name);
10689
10746
  commands.push(cmd);
10690
10747
  parsedCommand = true;
10691
10748
  }
10692
10749
  }
10693
10750
  catch (error) {
10694
- debug.parse('⚠️ parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
10751
+ debug.parse('\u26a0\ufe0f parseCommandListUntilTerminator: Command parsing threw, restoring:', error instanceof Error ? error.message : String(error));
10695
10752
  this.error = savedError;
10696
10753
  }
10697
10754
  }
10698
10755
  else if (this.checkIdentifier()) {
10699
- debug.parse('❌ IDENTIFIER is not a command:', this.peek().value);
10756
+ debug.parse('\u274c IDENTIFIER is not a command:', this.peek().value);
10700
10757
  }
10701
10758
  if (!parsedCommand) {
10702
- debug.parse('❌ No command parsed, breaking. Current token:', this.peek().value);
10759
+ debug.parse('\u274c No command parsed, breaking. Current token:', this.peek().value);
10703
10760
  break;
10704
10761
  }
10705
- debug.parse('📍 After parsing command, current token:', this.peek().value);
10762
+ debug.parse('\ud83d\udccd After parsing command, current token:', this.peek().value);
10706
10763
  while (!this.isAtEnd() &&
10707
10764
  !isStop() &&
10708
10765
  !this.checkIsCommand() &&
@@ -10710,19 +10767,19 @@ class Parser {
10710
10767
  !this.check('then') &&
10711
10768
  !this.check('and') &&
10712
10769
  !this.check(',')) {
10713
- debug.parse('⚠️ Skipping unexpected token:', this.peek().value);
10770
+ debug.parse('\u26a0\ufe0f Skipping unexpected token:', this.peek().value);
10714
10771
  this.advance();
10715
10772
  }
10716
10773
  if (this.match('then', 'and', ',')) {
10717
- debug.parse('✅ Found separator, continuing');
10774
+ debug.parse('\u2705 Found separator, continuing');
10718
10775
  continue;
10719
10776
  }
10720
10777
  else if (this.checkIsCommand() || this.isCommand(this.peek().value)) {
10721
- debug.parse('✅ Next token is a command, continuing without separator');
10778
+ debug.parse('\u2705 Next token is a command, continuing without separator');
10722
10779
  continue;
10723
10780
  }
10724
10781
  else {
10725
- debug.parse('📍 No separator and no command, breaking. Current token:', this.peek().value);
10782
+ debug.parse('\ud83d\udccd No separator and no command, breaking. Current token:', this.peek().value);
10726
10783
  break;
10727
10784
  }
10728
10785
  }
@@ -10736,18 +10793,18 @@ class Parser {
10736
10793
  if (!this.check('end') && !extraStops.some(s => this.check(s))) {
10737
10794
  terminator = '';
10738
10795
  }
10739
- debug.parse('✅ parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
10796
+ debug.parse('\u2705 parseCommandListUntilTerminator: parsed', commands.length, 'commands (terminator:', terminator, ')');
10740
10797
  return { commands, terminator };
10741
10798
  }
10742
10799
  parseCommandListUntilEnd() {
10743
10800
  const { commands } = this.parseCommandListUntilTerminator([]);
10744
- debug.parse('🔍 After loop, checking for "end". Current token:', this.peek().value);
10801
+ debug.parse('\ud83d\udd0d After loop, checking for "end". Current token:', this.peek().value);
10745
10802
  if (this.check('end')) {
10746
- debug.parse('✅ Found "end", consuming it');
10803
+ debug.parse('\u2705 Found "end", consuming it');
10747
10804
  this.advance();
10748
10805
  }
10749
10806
  else {
10750
- debug.parse('❌ ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
10807
+ debug.parse('\u274c ERROR: Expected "end" but got:', this.peek().value, 'at position:', this.peek().start);
10751
10808
  throw new Error('Expected "end" to close repeat block');
10752
10809
  }
10753
10810
  return commands;
@@ -11420,6 +11477,23 @@ class Parser {
11420
11477
  this.consume(')', "Expected ')' after parameter list");
11421
11478
  }
11422
11479
  const bodyCommands = this.parseCommandBlock(['end', 'catch', 'finally']);
11480
+ const { errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks();
11481
+ this.consume('end', "Expected 'end' after function definition");
11482
+ return {
11483
+ type: 'def',
11484
+ name: funcName,
11485
+ params,
11486
+ body: bodyCommands,
11487
+ ...(errorSymbol !== undefined && { errorSymbol }),
11488
+ ...(errorHandler !== undefined && { errorHandler }),
11489
+ ...(finallyHandler !== undefined && { finallyHandler }),
11490
+ start: pos.start,
11491
+ end: this.getPosition().end,
11492
+ line: pos.line,
11493
+ column: pos.column,
11494
+ };
11495
+ }
11496
+ parseErrorAndFinallyBlocks() {
11423
11497
  let errorSymbol;
11424
11498
  let errorHandler;
11425
11499
  if (this.check('catch')) {
@@ -11434,19 +11508,10 @@ class Parser {
11434
11508
  this.advance();
11435
11509
  finallyHandler = this.parseCommandBlock(['end']);
11436
11510
  }
11437
- this.consume('end', "Expected 'end' after function definition");
11438
11511
  return {
11439
- type: 'def',
11440
- name: funcName,
11441
- params,
11442
- body: bodyCommands,
11443
11512
  ...(errorSymbol !== undefined && { errorSymbol }),
11444
11513
  ...(errorHandler !== undefined && { errorHandler }),
11445
11514
  ...(finallyHandler !== undefined && { finallyHandler }),
11446
- start: pos.start,
11447
- end: this.getPosition().end,
11448
- line: pos.line,
11449
- column: pos.column,
11450
11515
  };
11451
11516
  }
11452
11517
  parseEventNameWithNamespace(errorMessage) {
@@ -11591,13 +11656,13 @@ class Parser {
11591
11656
  try {
11592
11657
  const cmd = this.parseCommand();
11593
11658
  if (this.error && this.error !== savedError) {
11594
- debug.parse('⚠️ Command parsing added error, restoring error state. Error was:', this.error.message);
11659
+ debug.parse('\u26a0\ufe0f Command parsing added error, restoring error state. Error was:', this.error.message);
11595
11660
  this.error = savedError;
11596
11661
  }
11597
11662
  return cmd;
11598
11663
  }
11599
11664
  catch (error) {
11600
- debug.parse('⚠️ Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
11665
+ debug.parse('\u26a0\ufe0f Command parsing threw exception, restoring error state:', error instanceof Error ? error.message : String(error));
11601
11666
  this.error = savedError;
11602
11667
  return null;
11603
11668
  }
@@ -11614,12 +11679,12 @@ class Parser {
11614
11679
  const timeToken = this.advance();
11615
11680
  const delayMs = parseTimeToMs(timeToken.value);
11616
11681
  modifiers[modifierKey] = delayMs;
11617
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
11682
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${timeToken.value}' (${delayMs}ms)`);
11618
11683
  }
11619
11684
  else if (this.matchNumber()) {
11620
11685
  const delayMs = parseInt(this.previous().value, 10);
11621
11686
  modifiers[modifierKey] = delayMs;
11622
- debug.parse(`🔧 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
11687
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed '${modName} at ${delayMs}'`);
11623
11688
  }
11624
11689
  else {
11625
11690
  this.addError(`Expected time expression after '${modName} at', got: ${this.peek().value}`);
@@ -11630,7 +11695,7 @@ class Parser {
11630
11695
  }
11631
11696
  }
11632
11697
  parseEventHandler() {
11633
- debug.parse(`🔧 parseEventHandler: ENTRY - parsing event handler`);
11698
+ debug.parse(`\ud83d\udd27 parseEventHandler: ENTRY - parsing event handler`);
11634
11699
  const eventNames = [];
11635
11700
  let firstOnceAlias = false;
11636
11701
  if (this.check('first') && !this.checkIsCommand() && this.current + 1 < this.tokens.length) {
@@ -11639,27 +11704,27 @@ class Parser {
11639
11704
  if (peek2Value && peek2Value !== 'of' && peek2Value !== 'in' && peek2Value !== 'from') {
11640
11705
  this.advance();
11641
11706
  firstOnceAlias = true;
11642
- debug.parse(`🔧 parseEventHandler: Parsed 'first' as .once alias`);
11707
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'first' as .once alias`);
11643
11708
  }
11644
11709
  }
11645
11710
  const event = this.parseEventNameWithNamespace("Expected event name after 'on'");
11646
11711
  eventNames.push(event);
11647
- debug.parse(`🔧 parseEventHandler: Parsed first event name: ${event}`);
11712
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed first event name: ${event}`);
11648
11713
  while (this.check('or')) {
11649
11714
  this.advance();
11650
- debug.parse(`🔧 parseEventHandler: Found 'or', parsing additional event name`);
11715
+ debug.parse(`\ud83d\udd27 parseEventHandler: Found 'or', parsing additional event name`);
11651
11716
  const additionalEvent = this.parseEventNameWithNamespace("Expected event name after 'or'");
11652
11717
  eventNames.push(additionalEvent);
11653
- debug.parse(`🔧 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
11718
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed additional event name: ${additionalEvent}`);
11654
11719
  }
11655
- debug.parse(`🔧 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
11720
+ debug.parse(`\ud83d\udd27 parseEventHandler: Total events parsed: ${eventNames.join(', ')}`);
11656
11721
  let customEventSource;
11657
11722
  if (this.registryIntegration) {
11658
11723
  const firstEvent = eventNames[0];
11659
11724
  if (this.registryIntegration.hasEventSource(firstEvent)) {
11660
11725
  const source = this.registryIntegration.getEventSource(firstEvent);
11661
11726
  customEventSource = source?.name;
11662
- debug.parse(`🔧 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
11727
+ debug.parse(`\ud83d\udd27 parseEventHandler: Detected custom event source '${customEventSource}' for event '${firstEvent}'`);
11663
11728
  }
11664
11729
  }
11665
11730
  const eventParams = [];
@@ -11671,7 +11736,7 @@ class Parser {
11671
11736
  } while (this.match(','));
11672
11737
  }
11673
11738
  this.consume(')', "Expected ')' after event parameters");
11674
- debug.parse(`🔧 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
11739
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed event parameters: ${eventParams.join(', ')}`);
11675
11740
  }
11676
11741
  const modifiers = {};
11677
11742
  while (this.check('.')) {
@@ -11680,15 +11745,15 @@ class Parser {
11680
11745
  const modName = modToken.value.toLowerCase();
11681
11746
  if (modName === 'once') {
11682
11747
  modifiers.once = true;
11683
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.once'`);
11748
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.once'`);
11684
11749
  }
11685
11750
  else if (modName === 'prevent') {
11686
11751
  modifiers.prevent = true;
11687
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.prevent'`);
11752
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.prevent'`);
11688
11753
  }
11689
11754
  else if (modName === 'stop') {
11690
11755
  modifiers.stop = true;
11691
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.stop'`);
11756
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.stop'`);
11692
11757
  }
11693
11758
  else if (modName === 'debounce' || modName === 'throttle') {
11694
11759
  if (this.check('(')) {
@@ -11700,11 +11765,11 @@ class Parser {
11700
11765
  }
11701
11766
  if (modName === 'debounce') {
11702
11767
  modifiers.debounce = delayMs;
11703
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
11768
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.debounce(${delayMs})'`);
11704
11769
  }
11705
11770
  else {
11706
11771
  modifiers.throttle = delayMs;
11707
- debug.parse(`🔧 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
11772
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifier '.throttle(${delayMs})'`);
11708
11773
  }
11709
11774
  this.consume(')', `Expected ')' after ${modName} delay`);
11710
11775
  }
@@ -11713,7 +11778,7 @@ class Parser {
11713
11778
  }
11714
11779
  }
11715
11780
  else {
11716
- debug.parse(`🔧 parseEventHandler: Warning - unknown modifier '.${modName}'`);
11781
+ debug.parse(`\ud83d\udd27 parseEventHandler: Warning - unknown modifier '.${modName}'`);
11717
11782
  }
11718
11783
  }
11719
11784
  this.parseTemporalKeywordModifiers(modifiers);
@@ -11721,7 +11786,7 @@ class Parser {
11721
11786
  modifiers.once = true;
11722
11787
  }
11723
11788
  if (Object.keys(modifiers).length > 0) {
11724
- debug.parse(`🔧 parseEventHandler: Parsed modifiers:`, modifiers);
11789
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed modifiers:`, modifiers);
11725
11790
  }
11726
11791
  let condition;
11727
11792
  if (this.match('[')) {
@@ -11732,7 +11797,7 @@ class Parser {
11732
11797
  if (this.match('from')) {
11733
11798
  const targetToken = this.advance();
11734
11799
  target = targetToken.value;
11735
- debug.parse(`🔧 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
11800
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed 'from' target: ${target} (kind: ${targetToken.kind})`);
11736
11801
  this.parseTemporalKeywordModifiers(modifiers);
11737
11802
  }
11738
11803
  let attributeName;
@@ -11744,32 +11809,36 @@ class Parser {
11744
11809
  else {
11745
11810
  attributeName = attrToken.value;
11746
11811
  }
11747
- debug.parse(`🔧 parseEventHandler: Parsed attribute name: ${attributeName}`);
11812
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed attribute name: ${attributeName}`);
11748
11813
  }
11749
11814
  let watchTarget;
11750
11815
  if (this.match('in')) {
11751
- debug.parse(`🔧 parseEventHandler: Found 'in' keyword, parsing watch target`);
11816
+ debug.parse(`\ud83d\udd27 parseEventHandler: Found 'in' keyword, parsing watch target`);
11752
11817
  watchTarget = this.parseExpression();
11753
- debug.parse(`🔧 parseEventHandler: Parsed watch target expression`);
11818
+ debug.parse(`\ud83d\udd27 parseEventHandler: Parsed watch target expression`);
11754
11819
  }
11755
11820
  const commands = [];
11756
- debug.parse(`✅ parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
11821
+ debug.parse(`\u2705 parseEventHandler: About to parse commands, current token: ${this.isAtEnd() ? 'END' : this.peek().value}, isAtEnd: ${this.isAtEnd()}`);
11757
11822
  while (!this.isAtEnd()) {
11758
- debug.parse(`✅ parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
11823
+ debug.parse(`\u2705 parseEventHandler: Loop iteration, current token: ${this.peek().value}, kind: ${this.peek().kind}`);
11759
11824
  if (this.check('on')) {
11760
- debug.parse(`✅ parseEventHandler: Stopping command parsing, found next event handler 'on'`);
11825
+ debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found next event handler 'on'`);
11761
11826
  break;
11762
11827
  }
11763
11828
  if (this.checkComment()) {
11764
- debug.parse(`✅ parseEventHandler: Skipping comment token: ${this.peek().value}`);
11829
+ debug.parse(`\u2705 parseEventHandler: Skipping comment token: ${this.peek().value}`);
11765
11830
  this.advance();
11766
11831
  continue;
11767
11832
  }
11768
11833
  if (this.check('end')) {
11769
- debug.parse(`✅ parseEventHandler: Stopping command parsing, found 'end' keyword`);
11834
+ debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found 'end' keyword`);
11770
11835
  this.advance();
11771
11836
  break;
11772
11837
  }
11838
+ if (this.check('catch') || this.check('finally')) {
11839
+ debug.parse(`\u2705 parseEventHandler: Stopping command parsing, found '${this.peek().value}'`);
11840
+ break;
11841
+ }
11773
11842
  if (this.checkIsCommand()) {
11774
11843
  const nextIsOpenParen = this.tokens[this.current + 1]?.value === '(';
11775
11844
  const commandName = this.peek().value.toLowerCase();
@@ -11798,7 +11867,7 @@ class Parser {
11798
11867
  const cmd = this.parseCommandWithErrorRecovery();
11799
11868
  if (cmd) {
11800
11869
  commands.push(cmd);
11801
- debug.parse(`✅ parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
11870
+ debug.parse(`\u2705 parseEventHandler: Parsed command, next token: ${this.isAtEnd() ? 'END' : this.peek().value}`);
11802
11871
  }
11803
11872
  }
11804
11873
  }
@@ -11816,7 +11885,7 @@ class Parser {
11816
11885
  expr = this.parseExpression();
11817
11886
  }
11818
11887
  catch (error) {
11819
- debug.parse('⚠️ Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
11888
+ debug.parse('\u26a0\ufe0f Expression parsing error, restoring error state:', error instanceof Error ? error.message : String(error));
11820
11889
  this.error = savedError;
11821
11890
  break;
11822
11891
  }
@@ -11863,7 +11932,9 @@ class Parser {
11863
11932
  !this.check('then') &&
11864
11933
  !this.check('and') &&
11865
11934
  !this.check(',') &&
11866
- !this.check('on')) {
11935
+ !this.check('on') &&
11936
+ !this.check('catch') &&
11937
+ !this.check('finally')) {
11867
11938
  this.advance();
11868
11939
  }
11869
11940
  if (this.match('then', 'and', ',')) {
@@ -11876,12 +11947,24 @@ class Parser {
11876
11947
  break;
11877
11948
  }
11878
11949
  }
11950
+ let errorSymbol;
11951
+ let errorHandler;
11952
+ let finallyHandler;
11953
+ if (!this.isAtEnd() && (this.check('catch') || this.check('finally'))) {
11954
+ ({ errorSymbol, errorHandler, finallyHandler } = this.parseErrorAndFinallyBlocks());
11955
+ if (this.check('end')) {
11956
+ this.advance();
11957
+ }
11958
+ }
11879
11959
  const pos = this.getPosition();
11880
11960
  const node = {
11881
11961
  type: 'eventHandler',
11882
11962
  event: eventNames.length === 1 ? eventNames[0] : eventNames.join('|'),
11883
11963
  events: eventNames,
11884
11964
  commands,
11965
+ ...(errorSymbol !== undefined && { errorSymbol }),
11966
+ ...(errorHandler !== undefined && { errorHandler }),
11967
+ ...(finallyHandler !== undefined && { finallyHandler }),
11885
11968
  ...(eventParams.length > 0 && { args: eventParams }),
11886
11969
  ...(condition && { condition }),
11887
11970
  ...(target && { target }),
@@ -11894,7 +11977,7 @@ class Parser {
11894
11977
  line: pos.line,
11895
11978
  column: pos.column,
11896
11979
  };
11897
- debug.parse(`🔧 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
11980
+ debug.parse(`\ud83d\udd27 parseEventHandler: Created node with events: ${eventNames.join(', ')}, attributeName: ${attributeName || 'none'}, watchTarget: ${watchTarget ? 'yes' : 'none'}`);
11898
11981
  return node;
11899
11982
  }
11900
11983
  parseBehaviorDefinition() {
@@ -11959,11 +12042,33 @@ class Parser {
11959
12042
  }
11960
12043
  }
11961
12044
  }
11962
- const handlerCommands = this.parseCommandListUntilEnd();
12045
+ const { commands: handlerCommands, terminator } = this.parseCommandListUntilTerminator([
12046
+ 'catch',
12047
+ 'finally',
12048
+ ]);
12049
+ let handlerErrorSymbol;
12050
+ let handlerErrorHandler;
12051
+ let handlerFinallyHandler;
12052
+ if (terminator === 'catch' || terminator === 'finally') {
12053
+ ({
12054
+ errorSymbol: handlerErrorSymbol,
12055
+ errorHandler: handlerErrorHandler,
12056
+ finallyHandler: handlerFinallyHandler,
12057
+ } = this.parseErrorAndFinallyBlocks());
12058
+ }
12059
+ if (this.check('end')) {
12060
+ this.advance();
12061
+ }
12062
+ else {
12063
+ throw new Error('Expected "end" to close behavior event handler');
12064
+ }
11963
12065
  const handlerNode = {
11964
12066
  type: 'eventHandler',
11965
12067
  event: eventName,
11966
12068
  commands: handlerCommands,
12069
+ ...(handlerErrorSymbol !== undefined && { errorSymbol: handlerErrorSymbol }),
12070
+ ...(handlerErrorHandler !== undefined && { errorHandler: handlerErrorHandler }),
12071
+ ...(handlerFinallyHandler !== undefined && { finallyHandler: handlerFinallyHandler }),
11967
12072
  ...(eventSource !== undefined && { target: eventSource }),
11968
12073
  ...(eventArgs.length > 0 && { args: eventArgs }),
11969
12074
  start: handlerPos.start,
@@ -12018,7 +12123,7 @@ class Parser {
12018
12123
  this.error.message.includes('unclosed parenthes') ||
12019
12124
  this.error.message.includes('Unclosed parenthes');
12020
12125
  if (!isCriticalError) {
12021
- debug.parse('⚠️ parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
12126
+ debug.parse('\u26a0\ufe0f parseCommandSequence: Command parsing added non-critical error, restoring error state. Error was:', this.error.message);
12022
12127
  this.error = savedError;
12023
12128
  }
12024
12129
  }
@@ -12028,11 +12133,11 @@ class Parser {
12028
12133
  !this.isCommand(this.peek().value) &&
12029
12134
  !this.check('then') &&
12030
12135
  !this.check('on')) {
12031
- debug.parse('⚠️ parseCommandSequence: Skipping unexpected token:', this.peek().value);
12136
+ debug.parse('\u26a0\ufe0f parseCommandSequence: Skipping unexpected token:', this.peek().value);
12032
12137
  this.advance();
12033
12138
  }
12034
12139
  if (this.check('on')) {
12035
- debug.parse('✅ parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
12140
+ debug.parse('\u2705 parseCommandSequence: Found "on" token, stopping command sequence to allow event handler parsing');
12036
12141
  break;
12037
12142
  }
12038
12143
  if (this.match('then')) {
@@ -12090,6 +12195,9 @@ class Parser {
12090
12195
  this.advance();
12091
12196
  const cmd = this.parseCommand();
12092
12197
  commands.push(cmd);
12198
+ if (this.match('then', 'and', ',')) {
12199
+ continue;
12200
+ }
12093
12201
  }
12094
12202
  else {
12095
12203
  break;
@@ -12476,7 +12584,7 @@ class Parser {
12476
12584
  if (!this.check(')') && callee.type === 'identifier') {
12477
12585
  const funcName = callee.name;
12478
12586
  if (CommandClassification.isCSSFunction(funcName)) {
12479
- debug.parse(`💡 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
12587
+ debug.parse(`\ud83d\udca1 Tip: CSS functions like ${funcName}() should be quoted for clean parsing. ` +
12480
12588
  `Use '${funcName}(...)' or \`${funcName}(...)\` instead.`);
12481
12589
  }
12482
12590
  }
@@ -12778,7 +12886,7 @@ class Parser {
12778
12886
  if (this.checkIsCommand() || (this.isCommand(token.value) && !this.isKeyword(token.value))) {
12779
12887
  return;
12780
12888
  }
12781
- debug.parse('⚠️ synchronize: Skipping token:', token.value);
12889
+ debug.parse('\u26a0\ufe0f synchronize: Skipping token:', token.value);
12782
12890
  this.advance();
12783
12891
  }
12784
12892
  }
@@ -16765,8 +16873,15 @@ let FetchCommand = (() => {
16765
16873
  }
16766
16874
  async parseURL(arg, evaluator, context) {
16767
16875
  const v = await evaluator.evaluate(arg, context);
16768
- if (typeof v !== 'string' || !v)
16876
+ if (typeof v !== 'string' || !v) {
16877
+ const node = arg;
16878
+ if ((v === undefined || v === null) && node?.type === 'identifier' && node.name) {
16879
+ throw new Error(`fetch: URL expression "${node.name}" evaluated to ${String(v)} \u2014 ` +
16880
+ `check the variable is set, or quote it if you meant a literal URL ` +
16881
+ `(fetch 'https://example.com/path')`);
16882
+ }
16769
16883
  throw new Error('fetch: URL must be a non-empty string');
16884
+ }
16770
16885
  return v;
16771
16886
  }
16772
16887
  parseResponseType(asNode) {
@@ -16985,7 +17100,7 @@ let SetCommand = (() => {
16985
17100
  if (typeof firstValue !== 'string') {
16986
17101
  const isMember = firstArg?.type === 'memberExpression' || firstArg?.type === 'propertyAccess';
16987
17102
  const hint = isMember
16988
- ? ` (a property chain evaluated to ${firstValue === null ? 'null' : typeof firstValue} — check that all intermediate objects exist)`
17103
+ ? ` (a property chain evaluated to ${firstValue === null ? 'null' : typeof firstValue} \u2014 check that all intermediate objects exist)`
16989
17104
  : '';
16990
17105
  throw new Error(`set command target must be a string or object literal${hint}`);
16991
17106
  }
@@ -18063,7 +18178,7 @@ let BeepCommand = (() => {
18063
18178
  return { expressionCount: 0, debugged: true, outputs: [] };
18064
18179
  }
18065
18180
  const outputs = [];
18066
- console.group('🔔 beep! Debug Output');
18181
+ console.group('\ud83d\udd14 beep! Debug Output');
18067
18182
  for (const expression of expressions) {
18068
18183
  const output = this.debugExpression(expression);
18069
18184
  outputs.push(output);
@@ -18076,7 +18191,7 @@ let BeepCommand = (() => {
18076
18191
  return { expressionCount: expressions.length, debugged: true, outputs };
18077
18192
  }
18078
18193
  debugContext(context) {
18079
- console.group('🔔 beep! Context Debug');
18194
+ console.group('\ud83d\udd14 beep! Context Debug');
18080
18195
  console.log('me:', context.me);
18081
18196
  console.log('it:', context.it);
18082
18197
  console.log('you:', context.you);