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