parseman 0.10.0 → 0.12.0

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.
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  choice: () => choice,
38
38
  compile: () => compile,
39
39
  completionsAt: () => completionsAt,
40
+ createVisitor: () => createVisitor,
40
41
  expect: () => expect,
41
42
  guard: () => guard,
42
43
  isParseError: () => isParseError,
@@ -46,6 +47,7 @@ __export(index_exports, {
46
47
  makeFunctionalDoc: () => makeFunctionalDoc,
47
48
  makeWord: () => makeWord,
48
49
  many: () => many,
50
+ manyRecover: () => manyRecover,
49
51
  noTrivia: () => noTrivia,
50
52
  node: () => node,
51
53
  not: () => not,
@@ -60,12 +62,14 @@ __export(index_exports, {
60
62
  rules: () => rules,
61
63
  scanTo: () => scanTo,
62
64
  sepBy: () => sepBy,
65
+ sepByRecover: () => sepByRecover,
63
66
  sequence: () => sequence,
64
67
  skip: () => skip,
65
68
  staticExpected: () => staticExpected,
66
69
  transform: () => transform,
67
70
  trivia: () => trivia,
68
71
  triviaEntries: () => triviaEntries,
72
+ walk: () => walk,
69
73
  withCtx: () => withCtx,
70
74
  word: () => word
71
75
  });
@@ -819,12 +823,15 @@ function scanFastWsCommentsChunks(input, cur, wsKind, commentKind) {
819
823
  continue;
820
824
  }
821
825
  if (c === 47 && input.charCodeAt(pos + 1) === 42) {
822
- const start = pos;
823
826
  let j = pos + 2;
824
827
  while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++;
825
- pos = j + 2 <= input.length ? j + 2 : input.length;
826
- chunks.push({ start, end: pos, kindIndex: commentKind });
827
- continue;
828
+ if (j + 1 < input.length && input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47) {
829
+ const start = pos;
830
+ pos = j + 2;
831
+ chunks.push({ start, end: pos, kindIndex: commentKind });
832
+ continue;
833
+ }
834
+ break;
828
835
  }
829
836
  break;
830
837
  }
@@ -1413,38 +1420,108 @@ function parse(combinator, input, opts = {}) {
1413
1420
  return withErrors;
1414
1421
  }
1415
1422
 
1423
+ // src/combinators/expect.ts
1424
+ function staticExpected(c) {
1425
+ const def = c._def;
1426
+ switch (def.tag) {
1427
+ case "literal":
1428
+ return [JSON.stringify(def.value)];
1429
+ case "regex":
1430
+ return [`/${def.source}/`];
1431
+ case "keywords":
1432
+ return def.words.map((w) => JSON.stringify(w));
1433
+ case "label":
1434
+ return [def.label];
1435
+ case "choice":
1436
+ return def.parsers.flatMap(staticExpected);
1437
+ case "sequence":
1438
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1439
+ case "node":
1440
+ case "grammar":
1441
+ case "trivia":
1442
+ case "optional":
1443
+ case "many":
1444
+ case "oneOrMore":
1445
+ case "transform":
1446
+ case "not":
1447
+ return staticExpected(def.parser);
1448
+ case "lazy":
1449
+ return staticExpected(def.thunk());
1450
+ default:
1451
+ return [];
1452
+ }
1453
+ }
1454
+ function expect(combinator, label2) {
1455
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1456
+ const meta = {
1457
+ firstSet: combinator._meta.firstSet,
1458
+ canMatchNewline: combinator._meta.canMatchNewline,
1459
+ isTrivia: false
1460
+ };
1461
+ return {
1462
+ _tag: "expect",
1463
+ _meta: meta,
1464
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1465
+ parse(input, pos, ctx) {
1466
+ const result = combinator.parse(input, pos, ctx);
1467
+ if (result.ok) return result;
1468
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1469
+ ctx._errors?.push(error);
1470
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1471
+ }
1472
+ };
1473
+ }
1474
+
1416
1475
  // src/compiler/scannable-run.ts
1476
+ var BACKSLASH = 92;
1417
1477
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1418
1478
  var META = new Set("()[]{}*+?|^$.".split(""));
1479
+ function shorthandRanges(ch) {
1480
+ return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1481
+ }
1419
1482
  function parseClassRanges(body) {
1420
1483
  const ranges = [];
1421
1484
  let i = 0;
1422
- const readCp = () => {
1485
+ const readAtom = () => {
1423
1486
  const ch = body[i];
1424
1487
  if (ch === void 0) return null;
1425
1488
  if (ch === "\\") {
1426
1489
  const e = body[i + 1];
1427
1490
  if (e === void 0) return null;
1428
1491
  i += 2;
1429
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1492
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1493
+ if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1494
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1495
+ return { cp: e.codePointAt(0) };
1430
1496
  }
1431
1497
  i += ch.length;
1432
- return ch.codePointAt(0);
1498
+ return { cp: ch.codePointAt(0) };
1433
1499
  };
1434
1500
  while (i < body.length) {
1435
- const lo = readCp();
1501
+ const lo = readAtom();
1436
1502
  if (lo === null) return null;
1503
+ if ("set" in lo) {
1504
+ ranges.push(...lo.set);
1505
+ continue;
1506
+ }
1437
1507
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1438
1508
  i += 1;
1439
- const hi = readCp();
1440
- if (hi === null) return null;
1441
- ranges.push([lo, hi]);
1509
+ const hi = readAtom();
1510
+ if (hi === null || "set" in hi) return null;
1511
+ ranges.push([lo.cp, hi.cp]);
1442
1512
  } else {
1443
- ranges.push([lo, lo]);
1513
+ ranges.push([lo.cp, lo.cp]);
1444
1514
  }
1445
1515
  }
1446
1516
  return ranges.length ? ranges : null;
1447
1517
  }
1518
+ function classToRanges(cls) {
1519
+ if (cls === "\\d") return shorthandRanges("d");
1520
+ if (cls === "\\w") return shorthandRanges("w");
1521
+ const body = cls.slice(1, -1);
1522
+ if (body.startsWith("^")) return null;
1523
+ return parseClassRanges(body);
1524
+ }
1448
1525
  function literalCodePoints(frag) {
1449
1526
  const out = [];
1450
1527
  let i = 0;
@@ -1463,13 +1540,53 @@ function literalCodePoints(frag) {
1463
1540
  }
1464
1541
  return out.length ? out : null;
1465
1542
  }
1543
+ function parseStringShape(source) {
1544
+ const q = source[0];
1545
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1546
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1547
+ const inner = source.slice(1, -1);
1548
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1549
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1550
+ let body;
1551
+ let esc;
1552
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1553
+ if (m) {
1554
+ body = m[1];
1555
+ esc = m[2];
1556
+ } else {
1557
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1558
+ if (m) {
1559
+ esc = m[1];
1560
+ body = m[2];
1561
+ }
1562
+ }
1563
+ if (body === void 0 || esc === void 0) return null;
1564
+ const excluded = parseClassRanges(body);
1565
+ if (!excluded) return null;
1566
+ const qcp = q.codePointAt(0);
1567
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1568
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1569
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1570
+ }
1466
1571
  function parseScanShape(source) {
1467
1572
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1468
1573
  if (m) {
1469
1574
  if (m[1].startsWith("^")) return null;
1470
1575
  const ranges = parseClassRanges(m[1]);
1471
- return ranges ? { kind: "chars", ranges } : null;
1576
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1577
+ }
1578
+ m = /^\\([dw])([+*])$/.exec(source);
1579
+ if (m) {
1580
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1581
+ }
1582
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
1583
+ if (m) {
1584
+ const head = classToRanges(m[1]);
1585
+ const tail = classToRanges(m[2]);
1586
+ return head && tail ? { kind: "ident", head, tail } : null;
1472
1587
  }
1588
+ const str = parseStringShape(source);
1589
+ if (str) return str;
1473
1590
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1474
1591
  if (m) {
1475
1592
  const open = literalCodePoints(m[1]);
@@ -1487,66 +1604,147 @@ function parseScanShape(source) {
1487
1604
  }
1488
1605
  return null;
1489
1606
  }
1607
+ function scanShapeFromRegex(source, flags) {
1608
+ if (/[imsu]/.test(flags)) return null;
1609
+ return parseScanShape(source);
1610
+ }
1490
1611
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1491
1612
  var litCond = (base, cps, firstVar) => cps.map(
1492
1613
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1493
1614
  ).join(" && ");
1494
- function advanceExpr(shape) {
1615
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
1616
+ function stringHardStop(shape) {
1617
+ return shape.excluded.filter(
1618
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
1619
+ );
1620
+ }
1621
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1622
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1495
1623
  if (shape.kind === "chars") {
1624
+ const cur = mint("_e");
1625
+ return {
1626
+ setup: [
1627
+ `${ind2}let ${cur} = ${start}`,
1628
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1629
+ ],
1630
+ // `*` always matches (possibly empty); `+` needs at least one char.
1631
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
1632
+ end: cur
1633
+ };
1634
+ }
1635
+ if (shape.kind === "ident") {
1636
+ const cur = mint("_e");
1496
1637
  return {
1497
1638
  setup: [
1498
- ` _e++`,
1499
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1639
+ `${ind2}let ${cur} = ${start}`,
1640
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
1641
+ `${ind2} ${cur} = ${start} + 1`,
1642
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
1643
+ `${ind2}}`
1500
1644
  ],
1501
- endVar: "_e"
1645
+ ok: `${cur} > ${start}`,
1646
+ end: cur
1502
1647
  };
1503
1648
  }
1504
1649
  if (shape.kind === "until") {
1650
+ const j2 = mint("_j");
1651
+ const openLen2 = shape.open.length;
1652
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1653
+ return {
1654
+ setup: [
1655
+ `${ind2}let ${j2} = ${start}`,
1656
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
1657
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
1658
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
1659
+ `${ind2}}`
1660
+ ],
1661
+ // An open-until-terminator always completes (stop char or EOF), so a matched
1662
+ // open literal is a full match; `end === start` iff the open didn't match.
1663
+ ok: `${j2} > ${start}`,
1664
+ end: j2
1665
+ };
1666
+ }
1667
+ if (shape.kind === "string") {
1668
+ const okV = mint("_ok");
1669
+ const endV2 = mint("_end");
1670
+ const i = mint("_i");
1671
+ const c2 = mint("_c");
1672
+ const hard = stringHardStop(shape);
1673
+ const ltLines = shape.escLineTerm ? [] : (() => {
1674
+ const c3 = mint("_c");
1675
+ return [
1676
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
1677
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
1678
+ ];
1679
+ })();
1505
1680
  return {
1506
1681
  setup: [
1507
- ` let j = _e + ${shape.open.length}`,
1508
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1682
+ `${ind2}let ${okV} = false`,
1683
+ `${ind2}let ${endV2} = ${start}`,
1684
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
1685
+ `${ind2} let ${i} = ${start} + 1`,
1686
+ `${ind2} while (${i} < input.length) {`,
1687
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
1688
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
1689
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
1690
+ `${ind2} if (${i} + 1 >= input.length) break`,
1691
+ ...ltLines,
1692
+ `${ind2} ${i} += 2`,
1693
+ `${ind2} continue`,
1694
+ `${ind2} }`,
1695
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
1696
+ `${ind2} ${i}++`,
1697
+ `${ind2} }`,
1698
+ `${ind2}}`
1509
1699
  ],
1510
- endVar: "j"
1700
+ ok: okV,
1701
+ end: endV2
1511
1702
  };
1512
1703
  }
1513
- const n = shape.close.length;
1704
+ const j = mint("_j");
1705
+ const endV = mint("_end");
1706
+ const openLen = shape.open.length;
1707
+ const closeLen = shape.close.length;
1708
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1709
+ const closeChk = litCond(j, shape.close);
1514
1710
  return {
1515
1711
  setup: [
1516
- ` let j = _e + ${shape.open.length}`,
1517
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1712
+ `${ind2}let ${j} = ${start}`,
1713
+ `${ind2}let ${endV} = ${start}`,
1714
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
1715
+ `${ind2} ${j} = ${start} + ${openLen}`,
1716
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
1717
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
1718
+ `${ind2}}`
1518
1719
  ],
1519
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1720
+ ok: `${endV} > ${start}`,
1721
+ end: endV
1520
1722
  };
1521
1723
  }
1522
- function guardLine(shape) {
1523
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1524
- return ` if (${cond}) {`;
1724
+ function scanBranch(shape, mint) {
1725
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1726
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1525
1727
  }
1526
- function scanBranch(shape) {
1527
- if (shape.kind === "chars") {
1528
- return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1529
- }
1530
- const { setup, endVar } = advanceExpr(shape);
1531
- return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1532
- }
1533
- function captureChunk(startVar, kindIndex) {
1728
+ function scanBranchLabeled(shape, kindIndex, mint) {
1729
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1534
1730
  return [
1731
+ ...m.setup,
1732
+ ` if (${m.end} > _e) {`,
1535
1733
  ` if (_cap) {`,
1536
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1537
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1538
- ` }`
1539
- ];
1540
- }
1541
- function scanBranchLabeled(shape, kindIndex) {
1542
- const { setup, endVar } = advanceExpr(shape);
1543
- const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1544
- if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1545
- lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1546
- return lines.join("\n");
1734
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
1735
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1736
+ ` }`,
1737
+ ` _e = ${m.end}`,
1738
+ ` continue`,
1739
+ ` }`
1740
+ ].join("\n");
1547
1741
  }
1548
1742
 
1549
1743
  // src/compiler/trivia-fast-path.ts
1744
+ function makeMint() {
1745
+ let n = 0;
1746
+ return (prefix = "_v") => `${prefix}${n++}`;
1747
+ }
1550
1748
  function unwrapTrivia2(p) {
1551
1749
  let cur = p;
1552
1750
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1563,14 +1761,14 @@ function choiceArms(p) {
1563
1761
  return null;
1564
1762
  }
1565
1763
  function armShape(arm) {
1566
- const src = getCoreRegexDef(arm)?.source;
1567
- return src ? parseScanShape(src) : null;
1764
+ const d = getCoreRegexDef(arm);
1765
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1568
1766
  }
1569
1767
  function analyzeTriviaFastPath(trivia2) {
1570
1768
  const core = unwrapTrivia2(trivia2);
1571
- const directSrc = getCoreRegexDef(core)?.source;
1572
- if (directSrc) {
1573
- const shape = parseScanShape(directSrc);
1769
+ const directDef = getCoreRegexDef(core);
1770
+ if (directDef) {
1771
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1574
1772
  return shape && shape.kind === "chars" ? [shape] : null;
1575
1773
  }
1576
1774
  const inner = oneOrMoreInner(core);
@@ -1594,10 +1792,11 @@ var CAP_RECORD = [
1594
1792
  ` }`
1595
1793
  ].join("\n");
1596
1794
  function composeFastLoop(shapes) {
1795
+ const mint = makeMint();
1597
1796
  return [
1598
1797
  ` while (_e < input.length) {`,
1599
1798
  ` const c = input.charCodeAt(_e)`,
1600
- ...shapes.map(scanBranch),
1799
+ ...shapes.map((s) => scanBranch(s, mint)),
1601
1800
  ` break`,
1602
1801
  ` }`
1603
1802
  ].join("\n");
@@ -1617,20 +1816,21 @@ function analyzeLabeledScannableRun(trivia2) {
1617
1816
  if (!spec) return null;
1618
1817
  const out = [];
1619
1818
  for (const arm of spec.arms) {
1620
- const src = getCoreRegexDef(arm.parser)?.source;
1621
- const shape = src ? parseScanShape(src) : null;
1819
+ const d = getCoreRegexDef(arm.parser);
1820
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1622
1821
  if (!shape) return null;
1623
1822
  out.push({ shape, kindIndex: arm.kindIndex });
1624
1823
  }
1625
1824
  return out;
1626
1825
  }
1627
1826
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1827
+ const mint = makeMint();
1628
1828
  return [
1629
1829
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1630
1830
  ` let _e = _pos`,
1631
1831
  ` while (_e < input.length) {`,
1632
1832
  ` const c = input.charCodeAt(_e)`,
1633
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1833
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1634
1834
  ` break`,
1635
1835
  ` }`,
1636
1836
  ` return _e`,
@@ -1707,6 +1907,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1707
1907
  ].join("\n");
1708
1908
  }
1709
1909
 
1910
+ // src/compiler/scannable-terminal.ts
1911
+ function emitScannableTerminal(shape, c) {
1912
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
1913
+ const stmts = [...m.setup];
1914
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
1915
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
1916
+ return { stmts, endVar: m.end };
1917
+ }
1918
+
1710
1919
  // src/compiler/inline-build.ts
1711
1920
  var MK_BUILD_RE = /^\(\s*(?:\w+\s*,\s*){3}\w+\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\1\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*\)\s*$/;
1712
1921
  function escapeRegExp(s) {
@@ -1828,14 +2037,45 @@ function failReturn(expected, posExpr) {
1828
2037
  function failReturnArr(expectedArr, posExpr) {
1829
2038
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1830
2039
  }
2040
+ function hoistExpected(ctx, constArrSource) {
2041
+ let name = ctx.expectedMap.get(constArrSource);
2042
+ if (name === void 0) {
2043
+ name = `_fx${ctx.expectedDecls.length}`;
2044
+ ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
2045
+ ctx.expectedMap.set(constArrSource, name);
2046
+ }
2047
+ return name;
2048
+ }
1831
2049
  function failBody(ctx, expected, posExpr) {
1832
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2050
+ if (ctx.failLabel) {
2051
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2052
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
2053
+ }
1833
2054
  return failReturn(expected, posExpr);
1834
2055
  }
1835
- function failArrBody(ctx, expectedArr, posExpr) {
1836
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2056
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
2057
+ if (ctx.failLabel) {
2058
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2059
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
2060
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
2061
+ }
2062
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1837
2063
  return failReturnArr(expectedArr, posExpr);
1838
2064
  }
2065
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
2066
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
2067
+ }
2068
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
2069
+ if (srcCtx !== "_ctx") {
2070
+ if (ctx.failLabel) {
2071
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2072
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
2073
+ }
2074
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
2075
+ }
2076
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
2077
+ return resultFromRecorded();
2078
+ }
1839
2079
  function emitIfFail(ctx, cond, body) {
1840
2080
  return [
1841
2081
  `${ind(ctx)}if (${cond}) {`,
@@ -1871,13 +2111,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1871
2111
  `}`
1872
2112
  ].join("\n"));
1873
2113
  }
1874
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
2114
+ function emitNamedFnCall(ctx, fnName, pos) {
1875
2115
  const vv = v(ctx, "_pfv");
1876
2116
  const ev = v(ctx, "_pfe");
1877
2117
  return {
1878
2118
  stmts: [
1879
2119
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1880
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
2120
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
2121
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
2122
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1881
2123
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1882
2124
  ],
1883
2125
  valueVar: vv,
@@ -1987,6 +2229,27 @@ function firstSetCond(codeVar, fs) {
1987
2229
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1988
2230
  ).join(" || ");
1989
2231
  }
2232
+ var SWITCH_RANGE_LIMIT = 4;
2233
+ var SWITCH_MAX_CASES = 48;
2234
+ var SWITCH_MIN_CASES = 3;
2235
+ var _forceDisjointIf = false;
2236
+ function planDisjointDispatch(parsers) {
2237
+ const cases = [];
2238
+ let total = 0;
2239
+ for (const p of parsers) {
2240
+ const fs = p._meta.firstSet;
2241
+ if (fs.kind !== "ranges") return { kind: "if" };
2242
+ const pts = [];
2243
+ for (const r of fs.ranges) {
2244
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2245
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2246
+ }
2247
+ total += pts.length;
2248
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2249
+ cases.push(pts);
2250
+ }
2251
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2252
+ }
1990
2253
  function captureRestoreBody(mL, mR, mTl, mLg) {
1991
2254
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1992
2255
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -2203,6 +2466,22 @@ function emitKeywords(def, ctx, pos) {
2203
2466
  return { stmts, valueVar: vv, endVar };
2204
2467
  }
2205
2468
  function emitRegex(def, ctx, pos) {
2469
+ const expectedStr = JSON.stringify(`/${def.source}/`);
2470
+ const shape = scanShapeFromRegex(def.source, def.flags);
2471
+ if (shape) {
2472
+ const vv2 = v(ctx);
2473
+ const scanned = emitScannableTerminal(shape, {
2474
+ ind: ind(ctx),
2475
+ pos,
2476
+ valueVar: vv2,
2477
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
2478
+ fresh: (prefix) => v(ctx, prefix)
2479
+ });
2480
+ if (scanned) {
2481
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
2482
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
2483
+ }
2484
+ }
2206
2485
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2207
2486
  const key = `${def.optimizedSource}/${flags}`;
2208
2487
  let rName = ctx.regexMap.get(key);
@@ -2213,7 +2492,6 @@ function emitRegex(def, ctx, pos) {
2213
2492
  }
2214
2493
  const mv = v(ctx, "_m");
2215
2494
  const vv = v(ctx);
2216
- const expectedStr = JSON.stringify(`/${def.source}/`);
2217
2495
  const stmts = [
2218
2496
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2219
2497
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2272,15 +2550,43 @@ function emitSeq(def, ctx, pos) {
2272
2550
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2273
2551
  return { stmts, valueVar: arrV, endVar };
2274
2552
  }
2553
+ function staticExpectedArr(parsers) {
2554
+ return JSON.stringify(parsers.flatMap((p) => {
2555
+ const e = staticExpected(p);
2556
+ return e.length > 0 ? e : [p._tag];
2557
+ }));
2558
+ }
2559
+ function failsAtStart(p) {
2560
+ const d = p._def;
2561
+ switch (d.tag) {
2562
+ case "literal":
2563
+ case "regex":
2564
+ case "keywords":
2565
+ case "guard":
2566
+ case "not":
2567
+ return true;
2568
+ case "transform":
2569
+ case "label":
2570
+ return failsAtStart(d.parser);
2571
+ default:
2572
+ return false;
2573
+ }
2574
+ }
2575
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
2576
+ if (failsAtStart(p)) {
2577
+ const r = emit(p, ctx, pos);
2578
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
2579
+ }
2580
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
2581
+ return [
2582
+ ...stmts,
2583
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
2584
+ `${ind(ctx)}${valV} = ${valVar}`,
2585
+ `${ind(ctx)}${endV} = ${endVar}`
2586
+ ];
2587
+ }
2275
2588
  function emitChoice(def, ctx, pos) {
2276
- const allExpected = JSON.stringify(
2277
- def.parsers.map((p) => {
2278
- const d = p._def;
2279
- if (d.tag === "literal") return JSON.stringify(d.value);
2280
- if (d.tag === "regex") return `/${d.source}/`;
2281
- return p._tag;
2282
- })
2283
- );
2589
+ const allExpected = staticExpectedArr(def.parsers);
2284
2590
  if (def.disjoint) {
2285
2591
  const codeV = v(ctx, "_code");
2286
2592
  const valV = v(ctx, "_chv");
@@ -2289,6 +2595,27 @@ function emitChoice(def, ctx, pos) {
2289
2595
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2290
2596
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2291
2597
  ];
2598
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
2599
+ if (plan.kind === "switch") {
2600
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
2601
+ ctx.indent++;
2602
+ for (let i = 0; i < def.parsers.length; i++) {
2603
+ const p = def.parsers[i];
2604
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
2605
+ stmts.push(`${ind(ctx)}{`);
2606
+ ctx.indent++;
2607
+ stmts.push(
2608
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
2609
+ `${ind(ctx)}break`
2610
+ );
2611
+ ctx.indent--;
2612
+ stmts.push(`${ind(ctx)}}`);
2613
+ }
2614
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
2615
+ ctx.indent--;
2616
+ stmts.push(`${ind(ctx)}}`);
2617
+ return { stmts, valueVar: valV, endVar: endV };
2618
+ }
2292
2619
  let first = true;
2293
2620
  for (const p of def.parsers) {
2294
2621
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2296,10 +2623,7 @@ function emitChoice(def, ctx, pos) {
2296
2623
  first = false;
2297
2624
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2298
2625
  ctx.indent++;
2299
- const r = emit(p, ctx, pos);
2300
- stmts.push(...r.stmts);
2301
- stmts.push(`${ind(ctx)}${valV} = ${r.valueVar}`);
2302
- stmts.push(`${ind(ctx)}${endV} = ${r.endVar}`);
2626
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2303
2627
  ctx.indent--;
2304
2628
  stmts.push(`${ind(ctx)}}`);
2305
2629
  }
@@ -2308,7 +2632,7 @@ function emitChoice(def, ctx, pos) {
2308
2632
  }
2309
2633
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2310
2634
  }
2311
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2635
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2312
2636
  const superParser = def.parsers[superIndex];
2313
2637
  const regexDef = getCoreRegexDef(superParser);
2314
2638
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2323,10 +2647,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2323
2647
  const wordV = v(ctx, "_gw");
2324
2648
  const endV = v(ctx, "_ge");
2325
2649
  const valV = v(ctx, "_gcv");
2650
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2326
2651
  const stmts = [
2327
2652
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2328
2653
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2329
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
2654
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2330
2655
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2331
2656
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2332
2657
  `${ind(ctx)}let ${valV}`
@@ -2384,13 +2709,15 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2384
2709
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2385
2710
  return { stmts, valueVar: valV, endVar: endV };
2386
2711
  }
2387
- function emitFirstMatch(def, allExpected, ctx, pos) {
2712
+ function emitFirstMatch(def, ctx, pos) {
2388
2713
  const resValV = v(ctx, "_crv");
2389
2714
  const resEndV = v(ctx, "_cre");
2390
2715
  const resOkV = v(ctx, "_crok");
2716
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2391
2717
  const ind0 = ind(ctx);
2392
2718
  const stmts = [
2393
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
2719
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2720
+ `${ind0}let ${slots.join(", ")}`
2394
2721
  ];
2395
2722
  for (let i = 0; i < def.parsers.length; i++) {
2396
2723
  const p = def.parsers[i];
@@ -2437,22 +2764,24 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2437
2764
  stmts.push(`${ind0} ${resOkV} = true`);
2438
2765
  stmts.push(`${ind0} }`);
2439
2766
  stmts.push(`${ind0} }`);
2767
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2440
2768
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2441
2769
  } else {
2442
2770
  stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2443
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2771
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
2444
2772
  }
2445
2773
  stmts.push(`${ind0}}`);
2446
2774
  }
2447
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
2775
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
2776
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2448
2777
  return { stmts, valueVar: resValV, endVar: resEndV };
2449
2778
  }
2450
2779
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2451
2780
  if (strategy.tag === "greedyClassify")
2452
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
2781
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2453
2782
  if (strategy.tag === "literalsLongestFirst")
2454
2783
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2455
- return emitFirstMatch(def, allExpected, ctx, pos);
2784
+ return emitFirstMatch(def, ctx, pos);
2456
2785
  }
2457
2786
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2458
2787
  const def = p._def;
@@ -2680,10 +3009,11 @@ function emitScanTo(def, ctx, pos) {
2680
3009
  }
2681
3010
  function emitNot(def, ctx, pos) {
2682
3011
  const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3012
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2683
3013
  return {
2684
3014
  stmts: [
2685
3015
  ...stmts,
2686
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
3016
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2687
3017
  ],
2688
3018
  valueVar: "null",
2689
3019
  endVar: pos
@@ -2715,7 +3045,7 @@ function emitNode(def, ctx, pos) {
2715
3045
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2716
3046
  stmts.push(...innerStmts);
2717
3047
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
2718
- stmts.push(...emitIfFail(ctx, `!${okVar}`, failBody(ctx, '"node"', pos)));
3048
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2719
3049
  let stV = "undefined";
2720
3050
  if (clonesState) {
2721
3051
  stV = v(ctx, "_nst");
@@ -2737,9 +3067,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2737
3067
  const rv = v(ctx, "_rt");
2738
3068
  const vv = v(ctx, "_rtv");
2739
3069
  const ev = v(ctx, "_rte");
3070
+ const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span }`;
2740
3071
  const stmts = [
2741
3072
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2742
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
3073
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2743
3074
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2744
3075
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2745
3076
  ];
@@ -2776,7 +3107,7 @@ function emitLazy(p, def, ctx, pos) {
2776
3107
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2777
3108
  }
2778
3109
  const fnName = ctx.namedParsers.get(p);
2779
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
3110
+ return emitNamedFnCall(ctx, fnName, pos);
2780
3111
  }
2781
3112
  function emitRecover(def, ctx, pos) {
2782
3113
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2794,7 +3125,7 @@ function emitRecover(def, ctx, pos) {
2794
3125
  `${ind0} if (${sentOk}) break`,
2795
3126
  `${ind0} ${scanV}++`,
2796
3127
  `${ind0} }`,
2797
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
3128
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2798
3129
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2799
3130
  `${ind0} ${valVar} = ${errV}`,
2800
3131
  `${ind0} ${endVar} = ${scanV}`,
@@ -2967,7 +3298,9 @@ function emit(p, ctx, pos) {
2967
3298
  stmts: [
2968
3299
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2969
3300
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2970
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3301
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3302
+ // and propagate the inner failure verbatim (interpreter parity).
3303
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2971
3304
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2972
3305
  ],
2973
3306
  valueVar: vv,
@@ -3068,7 +3401,7 @@ function analyzeLazyUsageMulti(roots) {
3068
3401
  const recursive = /* @__PURE__ */ new Set();
3069
3402
  const descended = /* @__PURE__ */ new Set();
3070
3403
  const active = /* @__PURE__ */ new Set();
3071
- function walk(p) {
3404
+ function walk2(p) {
3072
3405
  const def = p._def;
3073
3406
  if (def.tag === "lazy") {
3074
3407
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -3085,13 +3418,13 @@ function analyzeLazyUsageMulti(roots) {
3085
3418
  return;
3086
3419
  }
3087
3420
  active.add(p);
3088
- walk(resolved);
3421
+ walk2(resolved);
3089
3422
  active.delete(p);
3090
3423
  return;
3091
3424
  }
3092
- for (const child of childrenOf(def)) walk(child);
3425
+ for (const child of childrenOf(def)) walk2(child);
3093
3426
  }
3094
- for (const root of roots) walk(root);
3427
+ for (const root of roots) walk2(root);
3095
3428
  return { counts, recursive };
3096
3429
  }
3097
3430
  function compile(combinator, mapFnSources) {
@@ -3100,6 +3433,8 @@ function compile(combinator, mapFnSources) {
3100
3433
  indent: 1,
3101
3434
  regexDecls: [],
3102
3435
  regexMap: /* @__PURE__ */ new Map(),
3436
+ expectedDecls: [],
3437
+ expectedMap: /* @__PURE__ */ new Map(),
3103
3438
  mapFns: [],
3104
3439
  mapFnSrcs: [],
3105
3440
  buildFns: [],
@@ -3121,6 +3456,7 @@ function compile(combinator, mapFnSources) {
3121
3456
  const source = [
3122
3457
  ...emptyTlDecls,
3123
3458
  ...ctx.regexDecls,
3459
+ ...ctx.expectedDecls,
3124
3460
  "",
3125
3461
  ...namedPrelude,
3126
3462
  ctx.namedFnDecls.join("\n\n"),
@@ -3133,6 +3469,7 @@ function compile(combinator, mapFnSources) {
3133
3469
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3134
3470
  ...emptyTlDecls,
3135
3471
  ...ctx.regexDecls,
3472
+ ...ctx.expectedDecls,
3136
3473
  collatorDecl,
3137
3474
  ...namedPrelude,
3138
3475
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3186,12 +3523,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3186
3523
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3187
3524
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3188
3525
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3189
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3526
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3190
3527
  if (!needsWrapper) return innerFn;
3191
3528
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3192
3529
  const hoistedDecls = [
3193
3530
  emptyTlDecl,
3194
3531
  ...ctx.regexDecls.map((d) => ` ${d}`),
3532
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3195
3533
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3196
3534
  mfDecl,
3197
3535
  buildDecl,
@@ -3252,6 +3590,7 @@ function recover(combinator, sentinel) {
3252
3590
  canMatchNewline: true,
3253
3591
  isTrivia: false
3254
3592
  };
3593
+ const expected = staticExpected(combinator);
3255
3594
  return {
3256
3595
  _tag: "recover",
3257
3596
  _meta: meta,
@@ -3267,7 +3606,7 @@ function recover(combinator, sentinel) {
3267
3606
  const error = {
3268
3607
  _tag: "parseError",
3269
3608
  span: { start: pos, end: scanPos },
3270
- expected: result.expected
3609
+ expected
3271
3610
  };
3272
3611
  ctx._errors?.push(error);
3273
3612
  return { ok: true, value: error, span: { start: pos, end: scanPos } };
@@ -3278,58 +3617,6 @@ function isParseError(value) {
3278
3617
  return typeof value === "object" && value !== null && value._tag === "parseError";
3279
3618
  }
3280
3619
 
3281
- // src/combinators/expect.ts
3282
- function staticExpected(c) {
3283
- const def = c._def;
3284
- switch (def.tag) {
3285
- case "literal":
3286
- return [JSON.stringify(def.value)];
3287
- case "regex":
3288
- return [`/${def.source}/`];
3289
- case "keywords":
3290
- return def.words.map((w) => JSON.stringify(w));
3291
- case "label":
3292
- return [def.label];
3293
- case "choice":
3294
- return def.parsers.flatMap(staticExpected);
3295
- case "sequence":
3296
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
3297
- case "node":
3298
- case "grammar":
3299
- case "trivia":
3300
- case "optional":
3301
- case "many":
3302
- case "oneOrMore":
3303
- case "transform":
3304
- case "not":
3305
- return staticExpected(def.parser);
3306
- case "lazy":
3307
- return staticExpected(def.thunk());
3308
- default:
3309
- return [];
3310
- }
3311
- }
3312
- function expect(combinator, label2) {
3313
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
3314
- const meta = {
3315
- firstSet: combinator._meta.firstSet,
3316
- canMatchNewline: combinator._meta.canMatchNewline,
3317
- isTrivia: false
3318
- };
3319
- return {
3320
- _tag: "expect",
3321
- _meta: meta,
3322
- _def: { tag: "expect", parser: combinator, label: label2, expected },
3323
- parse(input, pos, ctx) {
3324
- const result = combinator.parse(input, pos, ctx);
3325
- if (result.ok) return result;
3326
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
3327
- ctx._errors?.push(error);
3328
- return { ok: true, value: error, span: { start: pos, end: pos } };
3329
- }
3330
- };
3331
- }
3332
-
3333
3620
  // src/combinators/completions.ts
3334
3621
  function completionsAt(combinator, input, offset) {
3335
3622
  const probe = { offset, best: null };
@@ -3439,6 +3726,23 @@ function escapeClassChar(ch) {
3439
3726
  return ch.replace(/[\\\]^-]/g, "\\$&");
3440
3727
  }
3441
3728
 
3729
+ // src/combinators/recover-list.ts
3730
+ function tolerantElement(item, sentinel, until) {
3731
+ const el = transform(
3732
+ sequence(not(until), recover(item, sentinel)),
3733
+ ([, value]) => value
3734
+ );
3735
+ if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
3736
+ return el;
3737
+ }
3738
+ function sepByRecover(item, separator, until) {
3739
+ const sentinel = choice(separator, until);
3740
+ return sepBy(tolerantElement(item, sentinel, until), separator);
3741
+ }
3742
+ function manyRecover(item, until) {
3743
+ return many(tolerantElement(item, until, until));
3744
+ }
3745
+
3442
3746
  // src/functional/doc.ts
3443
3747
  function isNode(x) {
3444
3748
  return typeof x === "object" && x !== null && x._tag === "node";
@@ -3626,6 +3930,41 @@ function buildTriviaIndex(root, input = "", opts) {
3626
3930
  return { before, after };
3627
3931
  }
3628
3932
 
3933
+ // src/cst/walk.ts
3934
+ function walk(root, visitor, ctx = void 0) {
3935
+ const go = (node2, parent) => {
3936
+ const descend = visitor.enter ? visitor.enter(node2, parent, ctx) : void 0;
3937
+ const children = node2.children;
3938
+ if (descend !== false && Array.isArray(children)) {
3939
+ for (const child of children) go(child, node2);
3940
+ }
3941
+ visitor.leave?.(node2, parent, ctx);
3942
+ };
3943
+ go(root, null);
3944
+ }
3945
+ function createVisitor(handlers) {
3946
+ const api = {
3947
+ visit(node2) {
3948
+ const handler = node2.type !== void 0 ? handlers[node2.type] : void 0;
3949
+ if (handler) return handler(node2, api);
3950
+ api.visitChildren(node2);
3951
+ return void 0;
3952
+ },
3953
+ visitChildren(node2) {
3954
+ const out = [];
3955
+ const children = node2.children;
3956
+ if (Array.isArray(children)) {
3957
+ for (const child of children) {
3958
+ const r = api.visit(child);
3959
+ if (r !== void 0) out.push(r);
3960
+ }
3961
+ }
3962
+ return out;
3963
+ }
3964
+ };
3965
+ return (node2) => api.visit(node2);
3966
+ }
3967
+
3629
3968
  // src/cst/trivia-entries.ts
3630
3969
  function entryOffset(i, stride) {
3631
3970
  return i * stride;