parseman 0.11.0 → 0.13.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,15 +37,16 @@ __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,
43
44
  keywords: () => keywords,
44
45
  label: () => label,
45
46
  literal: () => literal,
46
- makeFunctionalDoc: () => makeFunctionalDoc,
47
47
  makeWord: () => makeWord,
48
48
  many: () => many,
49
+ manyRecover: () => manyRecover,
49
50
  noTrivia: () => noTrivia,
50
51
  node: () => node,
51
52
  not: () => not,
@@ -53,6 +54,7 @@ __export(index_exports, {
53
54
  oneOrMore: () => oneOrMore,
54
55
  optional: () => optional,
55
56
  parse: () => parse,
57
+ parseDoc: () => parseDoc,
56
58
  parser: () => parser,
57
59
  recover: () => recover,
58
60
  ref: () => ref,
@@ -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,140 @@ 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
+ var SPACE_RANGES = [
1480
+ [9, 13],
1481
+ [32, 32],
1482
+ [160, 160],
1483
+ [5760, 5760],
1484
+ [8192, 8202],
1485
+ [8232, 8232],
1486
+ [8233, 8233],
1487
+ [8239, 8239],
1488
+ [8287, 8287],
1489
+ [12288, 12288],
1490
+ [65279, 65279]
1491
+ ];
1492
+ function shorthandRanges(ch) {
1493
+ if (ch === "d") return [[48, 57]];
1494
+ if (ch === "s") return SPACE_RANGES;
1495
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1496
+ }
1497
+ function readUnicodeEscape(body, i) {
1498
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1499
+ const hex = body.slice(i + 2, i + 6);
1500
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1501
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1502
+ }
1419
1503
  function parseClassRanges(body) {
1420
1504
  const ranges = [];
1421
1505
  let i = 0;
1422
- const readCp = () => {
1506
+ const readAtom = () => {
1423
1507
  const ch = body[i];
1424
1508
  if (ch === void 0) return null;
1425
1509
  if (ch === "\\") {
1510
+ const uni = readUnicodeEscape(body, i);
1511
+ if (uni) {
1512
+ i = uni.next;
1513
+ return { cp: uni.cp };
1514
+ }
1426
1515
  const e = body[i + 1];
1427
1516
  if (e === void 0) return null;
1428
1517
  i += 2;
1429
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1518
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1519
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1520
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1521
+ return { cp: e.codePointAt(0) };
1430
1522
  }
1431
1523
  i += ch.length;
1432
- return ch.codePointAt(0);
1524
+ return { cp: ch.codePointAt(0) };
1433
1525
  };
1434
1526
  while (i < body.length) {
1435
- const lo = readCp();
1527
+ const lo = readAtom();
1436
1528
  if (lo === null) return null;
1529
+ if ("set" in lo) {
1530
+ ranges.push(...lo.set);
1531
+ continue;
1532
+ }
1437
1533
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1438
1534
  i += 1;
1439
- const hi = readCp();
1440
- if (hi === null) return null;
1441
- ranges.push([lo, hi]);
1535
+ const hi = readAtom();
1536
+ if (hi === null || "set" in hi) return null;
1537
+ ranges.push([lo.cp, hi.cp]);
1442
1538
  } else {
1443
- ranges.push([lo, lo]);
1539
+ ranges.push([lo.cp, lo.cp]);
1444
1540
  }
1445
1541
  }
1446
1542
  return ranges.length ? ranges : null;
1447
1543
  }
1544
+ function classToRanges(cls) {
1545
+ if (cls === "\\d") return shorthandRanges("d");
1546
+ if (cls === "\\w") return shorthandRanges("w");
1547
+ if (cls === "\\s") return shorthandRanges("s");
1548
+ const body = cls.slice(1, -1);
1549
+ if (body.startsWith("^")) return null;
1550
+ return parseClassRanges(body);
1551
+ }
1552
+ var foldEq = (cVar, cp) => {
1553
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1554
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1555
+ return `${cVar} === ${cp}`;
1556
+ };
1448
1557
  function literalCodePoints(frag) {
1449
1558
  const out = [];
1450
1559
  let i = 0;
@@ -1463,19 +1572,482 @@ function literalCodePoints(frag) {
1463
1572
  }
1464
1573
  return out.length ? out : null;
1465
1574
  }
1575
+ function parseStringShape(source) {
1576
+ const q = source[0];
1577
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1578
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1579
+ const inner = source.slice(1, -1);
1580
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1581
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1582
+ let body;
1583
+ let esc;
1584
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1585
+ if (m) {
1586
+ body = m[1];
1587
+ esc = m[2];
1588
+ } else {
1589
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1590
+ if (m) {
1591
+ esc = m[1];
1592
+ body = m[2];
1593
+ }
1594
+ }
1595
+ if (body === void 0 || esc === void 0) return null;
1596
+ const excluded = parseClassRanges(body);
1597
+ if (!excluded) return null;
1598
+ const qcp = q.codePointAt(0);
1599
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1600
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1601
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1602
+ }
1603
+ function parseSeqParts(source) {
1604
+ const parts = [];
1605
+ let lit = [];
1606
+ const flush = () => {
1607
+ if (lit.length) {
1608
+ parts.push({ part: "lit", cps: lit, optional: false });
1609
+ lit = [];
1610
+ }
1611
+ };
1612
+ const runFrom = (ranges, negated, q) => {
1613
+ if (q === "+") {
1614
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1615
+ return 1;
1616
+ }
1617
+ if (q === "*") {
1618
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1619
+ return 1;
1620
+ }
1621
+ if (q === "?") {
1622
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1623
+ return 1;
1624
+ }
1625
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1626
+ return 0;
1627
+ };
1628
+ let i = 0;
1629
+ while (i < source.length) {
1630
+ const ch = source[i];
1631
+ if (ch === "[") {
1632
+ let k = i + 1;
1633
+ const neg = source[k] === "^";
1634
+ if (neg) k++;
1635
+ let body = "";
1636
+ while (k < source.length && source[k] !== "]") {
1637
+ if (source[k] === "\\") {
1638
+ body += source[k] + (source[k + 1] ?? "");
1639
+ k += 2;
1640
+ } else {
1641
+ body += source[k];
1642
+ k++;
1643
+ }
1644
+ }
1645
+ if (source[k] !== "]") return null;
1646
+ const ranges = parseClassRanges(body);
1647
+ if (!ranges) return null;
1648
+ flush();
1649
+ i = k + 1;
1650
+ i += runFrom(ranges, neg, source[i]);
1651
+ continue;
1652
+ }
1653
+ if (ch === "\\") {
1654
+ const e = source[i + 1];
1655
+ if (e === "d" || e === "w" || e === "s") {
1656
+ flush();
1657
+ i += 2;
1658
+ i += runFrom(shorthandRanges(e), false, source[i]);
1659
+ continue;
1660
+ }
1661
+ const uni = readUnicodeEscape(source, i);
1662
+ let cp2;
1663
+ if (uni) {
1664
+ cp2 = uni.cp;
1665
+ i = uni.next;
1666
+ } else if (e === void 0) return null;
1667
+ else if (e in CLASS_ESCAPES) {
1668
+ cp2 = CLASS_ESCAPES[e];
1669
+ i += 2;
1670
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1671
+ else {
1672
+ cp2 = e.codePointAt(0);
1673
+ i += 2;
1674
+ }
1675
+ if (source[i] === "?") {
1676
+ flush();
1677
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1678
+ i++;
1679
+ } else lit.push(cp2);
1680
+ continue;
1681
+ }
1682
+ if (ch === "(") {
1683
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1684
+ let k = i + 3;
1685
+ let depth = 1;
1686
+ while (k < source.length && depth > 0) {
1687
+ const c2 = source[k];
1688
+ if (c2 === "\\") {
1689
+ k += 2;
1690
+ continue;
1691
+ }
1692
+ if (c2 === "[") {
1693
+ let j = k + 1;
1694
+ if (source[j] === "^") j++;
1695
+ while (j < source.length && source[j] !== "]") {
1696
+ if (source[j] === "\\") j += 2;
1697
+ else j++;
1698
+ }
1699
+ if (source[j] !== "]") return null;
1700
+ k = j + 1;
1701
+ continue;
1702
+ }
1703
+ if (c2 === "(") depth++;
1704
+ else if (c2 === ")") depth--;
1705
+ k++;
1706
+ }
1707
+ if (depth !== 0) return null;
1708
+ const body = source.slice(i + 3, k - 1);
1709
+ const inner = parseScanShape(body);
1710
+ if (!inner || !groupInnerSafe(inner)) return null;
1711
+ const q = source[k];
1712
+ if (q === "{") return null;
1713
+ flush();
1714
+ if (q === "+") {
1715
+ if (shapeCanBeEmpty(inner)) return null;
1716
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1717
+ i = k + 1;
1718
+ } else if (q === "*") {
1719
+ if (shapeCanBeEmpty(inner)) return null;
1720
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1721
+ i = k + 1;
1722
+ } else if (q === "?") {
1723
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1724
+ i = k + 1;
1725
+ } else {
1726
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1727
+ i = k;
1728
+ }
1729
+ continue;
1730
+ }
1731
+ if (META.has(ch)) return null;
1732
+ const cp = ch.codePointAt(0);
1733
+ i += ch.length;
1734
+ if (source[i] === "?") {
1735
+ flush();
1736
+ parts.push({ part: "lit", cps: [cp], optional: true });
1737
+ i++;
1738
+ } else lit.push(cp);
1739
+ }
1740
+ flush();
1741
+ return parts.length ? parts : null;
1742
+ }
1743
+ function seqIsUnambiguous(parts) {
1744
+ let hasMandatory = false;
1745
+ for (let k = 0; k < parts.length; k++) {
1746
+ const p = parts[k];
1747
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1748
+ if (mandatory) hasMandatory = true;
1749
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1750
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1751
+ if (skippableSingle || isUnboundedRepeat) {
1752
+ const rest = seqFirstAccept(parts.slice(k + 1));
1753
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1754
+ }
1755
+ }
1756
+ return hasMandatory;
1757
+ }
1758
+ function rangesOverlap(a, b) {
1759
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1760
+ }
1761
+ function rangesSubset(sub, sup) {
1762
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1763
+ }
1764
+ function parseClassOperand(body) {
1765
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1766
+ return { ranges: shorthandRanges(body[1]), negated: false };
1767
+ }
1768
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1769
+ let inner = body.slice(1, -1);
1770
+ const negated = inner.startsWith("^");
1771
+ if (negated) inner = inner.slice(1);
1772
+ const ranges = parseClassRanges(inner);
1773
+ return ranges ? { ranges, negated } : null;
1774
+ }
1775
+ const cps = literalCodePoints(body);
1776
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1777
+ return null;
1778
+ }
1779
+ function classSubset(t, u) {
1780
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1781
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1782
+ if (t.negated && !u.negated) return false;
1783
+ return rangesSubset(u.ranges, t.ranges);
1784
+ }
1785
+ function classDisjoint(t, u) {
1786
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1787
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1788
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1789
+ return false;
1790
+ }
1791
+ function firstSetsDisjoint(a, b) {
1792
+ if (a === "any" || b === "any") return false;
1793
+ return classDisjoint(a, b);
1794
+ }
1795
+ function trailingBacktrackClass(shape) {
1796
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1797
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1798
+ if (shape.kind === "litFold") return null;
1799
+ if (shape.kind === "seq") {
1800
+ const last = shape.parts[shape.parts.length - 1];
1801
+ if (last.part === "lit") return null;
1802
+ if (last.part === "run") {
1803
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1804
+ return null;
1805
+ }
1806
+ const innerWiggle = trailingBacktrackClass(last.inner);
1807
+ if (innerWiggle === "unsupported") return "unsupported";
1808
+ const requiredOnce = !last.unbounded && last.min === 1;
1809
+ if (requiredOnce) return innerWiggle;
1810
+ const dropExposed = shapeFirstAccept(last.inner);
1811
+ if (dropExposed === "any") return "unsupported";
1812
+ if (innerWiggle === null) return dropExposed;
1813
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1814
+ return unioned === "any" ? "unsupported" : unioned;
1815
+ }
1816
+ if (shape.kind === "alt") {
1817
+ if (!shape.disjoint) return "unsupported";
1818
+ const classes = shape.arms.map(trailingBacktrackClass);
1819
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1820
+ const concrete = classes.filter((c) => c !== null);
1821
+ if (concrete.length === 0) return null;
1822
+ const unioned = unionCharSets(concrete);
1823
+ return unioned === "any" ? "unsupported" : unioned;
1824
+ }
1825
+ return "unsupported";
1826
+ }
1827
+ function lookaheadUnambiguous(inner, operand, negative) {
1828
+ const tail = trailingBacktrackClass(inner);
1829
+ if (tail === "unsupported") return false;
1830
+ if (tail === null) return true;
1831
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1832
+ }
1833
+ function stripTrailingLookahead(source) {
1834
+ if (!source.endsWith(")")) return null;
1835
+ for (const negative of [true, false]) {
1836
+ const lead = negative ? "(?!" : "(?=";
1837
+ const idx = source.lastIndexOf(lead);
1838
+ if (idx === -1) continue;
1839
+ const body = source.slice(idx + lead.length, -1);
1840
+ const operand = body.length ? parseClassOperand(body) : null;
1841
+ if (!operand) continue;
1842
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1843
+ }
1844
+ return null;
1845
+ }
1846
+ function unwrapOuterGroup(source) {
1847
+ while (source.startsWith("(?:")) {
1848
+ let depth = 1;
1849
+ let i = 3;
1850
+ let ok = true;
1851
+ while (i < source.length && depth > 0) {
1852
+ const ch = source[i];
1853
+ if (ch === "\\") {
1854
+ i += 2;
1855
+ continue;
1856
+ }
1857
+ if (ch === "[") {
1858
+ let j = i + 1;
1859
+ if (source[j] === "^") j++;
1860
+ while (j < source.length && source[j] !== "]") {
1861
+ if (source[j] === "\\") j += 2;
1862
+ else j++;
1863
+ }
1864
+ if (source[j] !== "]") {
1865
+ ok = false;
1866
+ break;
1867
+ }
1868
+ i = j + 1;
1869
+ continue;
1870
+ }
1871
+ if (ch === "(") depth++;
1872
+ else if (ch === ")") depth--;
1873
+ i++;
1874
+ }
1875
+ if (!ok || depth !== 0 || i !== source.length) break;
1876
+ source = source.slice(3, -1);
1877
+ }
1878
+ return source;
1879
+ }
1880
+ function splitTopLevelAlternation(source) {
1881
+ const body = unwrapOuterGroup(source);
1882
+ const parts = [];
1883
+ let depth = 0;
1884
+ let last = 0;
1885
+ let i = 0;
1886
+ while (i < body.length) {
1887
+ const ch = body[i];
1888
+ if (ch === "\\") {
1889
+ i += 2;
1890
+ continue;
1891
+ }
1892
+ if (ch === "[") {
1893
+ let j = i + 1;
1894
+ if (body[j] === "^") j++;
1895
+ while (j < body.length && body[j] !== "]") {
1896
+ if (body[j] === "\\") j += 2;
1897
+ else j++;
1898
+ }
1899
+ if (body[j] !== "]") return null;
1900
+ i = j + 1;
1901
+ continue;
1902
+ }
1903
+ if (ch === "(") {
1904
+ depth++;
1905
+ i++;
1906
+ continue;
1907
+ }
1908
+ if (ch === ")") {
1909
+ depth--;
1910
+ if (depth < 0) return null;
1911
+ i++;
1912
+ continue;
1913
+ }
1914
+ if (ch === "|" && depth === 0) {
1915
+ parts.push(body.slice(last, i));
1916
+ last = i + 1;
1917
+ i++;
1918
+ continue;
1919
+ }
1920
+ i++;
1921
+ }
1922
+ if (depth !== 0) return null;
1923
+ parts.push(body.slice(last));
1924
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1925
+ return parts;
1926
+ }
1927
+ function shapeCanBeEmpty(shape) {
1928
+ if (shape.kind === "chars") return !shape.minOne;
1929
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
1930
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
1931
+ return false;
1932
+ }
1933
+ function unionCharSets(sets) {
1934
+ if (sets.some((s) => s === "any")) return "any";
1935
+ const concrete = sets;
1936
+ if (concrete.length === 1) return concrete[0];
1937
+ if (concrete.some((s) => s.negated)) return "any";
1938
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
1939
+ }
1940
+ function partFirstAccept(part) {
1941
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
1942
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
1943
+ return shapeFirstAccept(part.inner);
1944
+ }
1945
+ function seqFirstAccept(parts) {
1946
+ const sets = [];
1947
+ for (const p of parts) {
1948
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1949
+ sets.push(partFirstAccept(p));
1950
+ if (mandatory) break;
1951
+ }
1952
+ return unionCharSets(sets);
1953
+ }
1954
+ function groupInnerSafe(shape) {
1955
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1956
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1957
+ if (shape.kind === "alt") return shape.disjoint;
1958
+ return false;
1959
+ }
1960
+ function shapeFirstAccept(shape) {
1961
+ if (shapeCanBeEmpty(shape)) return "any";
1962
+ switch (shape.kind) {
1963
+ case "chars":
1964
+ return { ranges: shape.ranges, negated: false };
1965
+ case "ident":
1966
+ return { ranges: shape.head, negated: false };
1967
+ case "until":
1968
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1969
+ case "delimited":
1970
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1971
+ case "string":
1972
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
1973
+ case "litFold": {
1974
+ const cp = shape.open[0];
1975
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
1976
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
1977
+ return { ranges: [[cp, cp]], negated: false };
1978
+ }
1979
+ case "seq":
1980
+ return seqFirstAccept(shape.parts);
1981
+ case "lookahead":
1982
+ return shapeFirstAccept(shape.inner);
1983
+ // zero-width — doesn't change first-char acceptance
1984
+ case "alt": {
1985
+ if (!shape.disjoint) return "any";
1986
+ const sets = shape.firsts.filter((f) => f !== null);
1987
+ return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1988
+ }
1989
+ }
1990
+ }
1991
+ function allPairsDisjoint(sets) {
1992
+ for (let i = 0; i < sets.length; i++) {
1993
+ const a = sets[i];
1994
+ if (a === "any") return false;
1995
+ for (let j = i + 1; j < sets.length; j++) {
1996
+ const b = sets[j];
1997
+ if (b === "any" || !classDisjoint(a, b)) return false;
1998
+ }
1999
+ }
2000
+ return true;
2001
+ }
2002
+ function parseAlternation(source) {
2003
+ const parts = splitTopLevelAlternation(source);
2004
+ if (!parts) return null;
2005
+ const arms = [];
2006
+ for (const p of parts) {
2007
+ const arm = parseScanShape(p);
2008
+ if (!arm) return null;
2009
+ arms.push(arm);
2010
+ }
2011
+ const firstSets = arms.map(shapeFirstAccept);
2012
+ const disjoint = allPairsDisjoint(firstSets);
2013
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
2014
+ }
1466
2015
  function parseScanShape(source) {
2016
+ const alt = parseAlternation(source);
2017
+ if (alt) return alt;
2018
+ const la = stripTrailingLookahead(source);
2019
+ if (la) {
2020
+ const inner = parseScanShapeCore(la.base);
2021
+ if (!inner) return null;
2022
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2023
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2024
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2025
+ }
2026
+ return parseScanShapeCore(source);
2027
+ }
2028
+ function parseScanShapeCore(source) {
1467
2029
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1468
- if (m) {
1469
- if (m[1].startsWith("^")) return null;
2030
+ if (m && !m[1].startsWith("^")) {
1470
2031
  const ranges = parseClassRanges(m[1]);
1471
- return ranges ? { kind: "chars", ranges } : null;
2032
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1472
2033
  }
2034
+ m = /^\\([dws])([+*])$/.exec(source);
2035
+ if (m) {
2036
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2037
+ }
2038
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2039
+ if (m) {
2040
+ const head = classToRanges(m[1]);
2041
+ const tail = classToRanges(m[2]);
2042
+ if (head && tail) return { kind: "ident", head, tail };
2043
+ }
2044
+ const str = parseStringShape(source);
2045
+ if (str) return str;
1473
2046
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1474
2047
  if (m) {
1475
2048
  const open = literalCodePoints(m[1]);
1476
2049
  const stop = parseClassRanges(m[2]);
1477
2050
  if (open && stop) return { kind: "until", open, stop };
1478
- return null;
1479
2051
  }
1480
2052
  if (!source.includes("\\\\")) {
1481
2053
  m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
@@ -1485,68 +2057,278 @@ function parseScanShape(source) {
1485
2057
  if (open && close) return { kind: "delimited", open, close };
1486
2058
  }
1487
2059
  }
2060
+ const parts = parseSeqParts(source);
2061
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1488
2062
  return null;
1489
2063
  }
2064
+ function scanShapeFromRegex(source, flags) {
2065
+ if (/[msu]/.test(flags)) return null;
2066
+ if (/i/.test(flags)) {
2067
+ const open = literalCodePoints(source);
2068
+ return open ? { kind: "litFold", open } : null;
2069
+ }
2070
+ return parseScanShape(source);
2071
+ }
1490
2072
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1491
2073
  var litCond = (base, cps, firstVar) => cps.map(
1492
2074
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1493
2075
  ).join(" && ");
1494
- function advanceExpr(shape) {
2076
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
2077
+ function stringHardStop(shape) {
2078
+ return shape.excluded.filter(
2079
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
2080
+ );
2081
+ }
2082
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2083
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2084
+ if (shape.kind === "lookahead") {
2085
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
2086
+ const okV = mint("_ok");
2087
+ const endV2 = mint("_end");
2088
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
2089
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
2090
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
2091
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
2092
+ return {
2093
+ setup: [
2094
+ ...inner.setup,
2095
+ `${ind2}let ${okV} = ${inner.ok}`,
2096
+ `${ind2}let ${endV2} = ${inner.end}`,
2097
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
2098
+ ],
2099
+ ok: okV,
2100
+ end: endV2
2101
+ };
2102
+ }
2103
+ if (shape.kind === "alt") {
2104
+ const okV = mint("_ok");
2105
+ const endV2 = mint("_end");
2106
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2107
+ if (shape.disjoint) {
2108
+ const c0 = codeAt(start, firstChar);
2109
+ shape.arms.forEach((arm, k) => {
2110
+ const fs = shape.firsts[k];
2111
+ const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2112
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2113
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2114
+ lines.push(...m.setup);
2115
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2116
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2117
+ lines.push(`${ind2}}`);
2118
+ });
2119
+ } else {
2120
+ const lbl = mint("_alt");
2121
+ lines.push(`${ind2}${lbl}: {`);
2122
+ for (const arm of shape.arms) {
2123
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2124
+ lines.push(...m.setup);
2125
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
2126
+ }
2127
+ lines.push(`${ind2}}`);
2128
+ }
2129
+ return { setup: lines, ok: okV, end: endV2 };
2130
+ }
1495
2131
  if (shape.kind === "chars") {
2132
+ const cur = mint("_e");
1496
2133
  return {
1497
2134
  setup: [
1498
- ` _e++`,
1499
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
2135
+ `${ind2}let ${cur} = ${start}`,
2136
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1500
2137
  ],
1501
- endVar: "_e"
2138
+ // `*` always matches (possibly empty); `+` needs at least one char.
2139
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
2140
+ end: cur
2141
+ };
2142
+ }
2143
+ if (shape.kind === "ident") {
2144
+ const cur = mint("_e");
2145
+ return {
2146
+ setup: [
2147
+ `${ind2}let ${cur} = ${start}`,
2148
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
2149
+ `${ind2} ${cur} = ${start} + 1`,
2150
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
2151
+ `${ind2}}`
2152
+ ],
2153
+ ok: `${cur} > ${start}`,
2154
+ end: cur
2155
+ };
2156
+ }
2157
+ if (shape.kind === "seq") {
2158
+ const endV2 = mint("_e");
2159
+ const okV = mint("_ok");
2160
+ const runCond = (p, at) => {
2161
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2162
+ return p.negated ? `!(${c})` : `(${c})`;
2163
+ };
2164
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2165
+ let first = true;
2166
+ for (const p of shape.parts) {
2167
+ if (p.part === "lit") {
2168
+ const L = p.cps.length;
2169
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2170
+ if (p.optional) {
2171
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2172
+ } else {
2173
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2174
+ lines.push(`${ind2} ${endV2} += ${L}`);
2175
+ }
2176
+ } else if (p.part === "run" && !p.unbounded) {
2177
+ if (p.min === 1) {
2178
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2179
+ lines.push(`${ind2} ${endV2}++`);
2180
+ } else {
2181
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2182
+ }
2183
+ } else if (p.part === "run") {
2184
+ const s = mint("_s");
2185
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2186
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2187
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2188
+ } else {
2189
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2190
+ if (!p.unbounded) {
2191
+ lines.push(...inner.setup);
2192
+ if (p.min === 1) {
2193
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2194
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2195
+ } else {
2196
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2197
+ }
2198
+ } else {
2199
+ const progV = mint("_prog");
2200
+ lines.push(`${ind2} let ${progV} = false`);
2201
+ lines.push(`${ind2} while (true) {`);
2202
+ lines.push(...inner.setup);
2203
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2204
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2205
+ lines.push(`${ind2} ${progV} = true`);
2206
+ lines.push(`${ind2} }`);
2207
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2208
+ }
2209
+ }
2210
+ first = false;
2211
+ }
2212
+ lines.push(`${ind2} ${okV} = true`);
2213
+ lines.push(`${ind2}} while (false)`);
2214
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2215
+ return { setup: lines, ok: okV, end: endV2 };
2216
+ }
2217
+ if (shape.kind === "litFold") {
2218
+ const openLen2 = shape.open.length;
2219
+ const endV2 = mint("_end");
2220
+ const checks = shape.open.map((cp, k) => {
2221
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2222
+ return foldEq(cVar, cp);
2223
+ });
2224
+ return {
2225
+ setup: [
2226
+ `${ind2}let ${endV2} = ${start}`,
2227
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2228
+ ],
2229
+ ok: `${endV2} > ${start}`,
2230
+ end: endV2
1502
2231
  };
1503
2232
  }
1504
2233
  if (shape.kind === "until") {
2234
+ const j2 = mint("_j");
2235
+ const openLen2 = shape.open.length;
2236
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1505
2237
  return {
1506
2238
  setup: [
1507
- ` let j = _e + ${shape.open.length}`,
1508
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
2239
+ `${ind2}let ${j2} = ${start}`,
2240
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
2241
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
2242
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
2243
+ `${ind2}}`
1509
2244
  ],
1510
- endVar: "j"
2245
+ // An open-until-terminator always completes (stop char or EOF), so a matched
2246
+ // open literal is a full match; `end === start` iff the open didn't match.
2247
+ ok: `${j2} > ${start}`,
2248
+ end: j2
1511
2249
  };
1512
2250
  }
1513
- const n = shape.close.length;
2251
+ if (shape.kind === "string") {
2252
+ const okV = mint("_ok");
2253
+ const endV2 = mint("_end");
2254
+ const i = mint("_i");
2255
+ const c2 = mint("_c");
2256
+ const hard = stringHardStop(shape);
2257
+ const ltLines = shape.escLineTerm ? [] : (() => {
2258
+ const c3 = mint("_c");
2259
+ return [
2260
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
2261
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
2262
+ ];
2263
+ })();
2264
+ return {
2265
+ setup: [
2266
+ `${ind2}let ${okV} = false`,
2267
+ `${ind2}let ${endV2} = ${start}`,
2268
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
2269
+ `${ind2} let ${i} = ${start} + 1`,
2270
+ `${ind2} while (${i} < input.length) {`,
2271
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
2272
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
2273
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
2274
+ `${ind2} if (${i} + 1 >= input.length) break`,
2275
+ ...ltLines,
2276
+ `${ind2} ${i} += 2`,
2277
+ `${ind2} continue`,
2278
+ `${ind2} }`,
2279
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
2280
+ `${ind2} ${i}++`,
2281
+ `${ind2} }`,
2282
+ `${ind2}}`
2283
+ ],
2284
+ ok: okV,
2285
+ end: endV2
2286
+ };
2287
+ }
2288
+ const j = mint("_j");
2289
+ const endV = mint("_end");
2290
+ const openLen = shape.open.length;
2291
+ const closeLen = shape.close.length;
2292
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
2293
+ const closeChk = litCond(j, shape.close);
1514
2294
  return {
1515
2295
  setup: [
1516
- ` let j = _e + ${shape.open.length}`,
1517
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
2296
+ `${ind2}let ${j} = ${start}`,
2297
+ `${ind2}let ${endV} = ${start}`,
2298
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
2299
+ `${ind2} ${j} = ${start} + ${openLen}`,
2300
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
2301
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
2302
+ `${ind2}}`
1518
2303
  ],
1519
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
2304
+ ok: `${endV} > ${start}`,
2305
+ end: endV
1520
2306
  };
1521
2307
  }
1522
- function guardLine(shape) {
1523
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1524
- return ` if (${cond}) {`;
2308
+ function scanBranch(shape, mint) {
2309
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
2310
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1525
2311
  }
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) {
2312
+ function scanBranchLabeled(shape, kindIndex, mint) {
2313
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1534
2314
  return [
2315
+ ...m.setup,
2316
+ ` if (${m.end} > _e) {`,
1535
2317
  ` 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");
2318
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
2319
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
2320
+ ` }`,
2321
+ ` _e = ${m.end}`,
2322
+ ` continue`,
2323
+ ` }`
2324
+ ].join("\n");
1547
2325
  }
1548
2326
 
1549
2327
  // src/compiler/trivia-fast-path.ts
2328
+ function makeMint() {
2329
+ let n = 0;
2330
+ return (prefix = "_v") => `${prefix}${n++}`;
2331
+ }
1550
2332
  function unwrapTrivia2(p) {
1551
2333
  let cur = p;
1552
2334
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1563,14 +2345,14 @@ function choiceArms(p) {
1563
2345
  return null;
1564
2346
  }
1565
2347
  function armShape(arm) {
1566
- const src = getCoreRegexDef(arm)?.source;
1567
- return src ? parseScanShape(src) : null;
2348
+ const d = getCoreRegexDef(arm);
2349
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1568
2350
  }
1569
2351
  function analyzeTriviaFastPath(trivia2) {
1570
2352
  const core = unwrapTrivia2(trivia2);
1571
- const directSrc = getCoreRegexDef(core)?.source;
1572
- if (directSrc) {
1573
- const shape = parseScanShape(directSrc);
2353
+ const directDef = getCoreRegexDef(core);
2354
+ if (directDef) {
2355
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1574
2356
  return shape && shape.kind === "chars" ? [shape] : null;
1575
2357
  }
1576
2358
  const inner = oneOrMoreInner(core);
@@ -1594,10 +2376,11 @@ var CAP_RECORD = [
1594
2376
  ` }`
1595
2377
  ].join("\n");
1596
2378
  function composeFastLoop(shapes) {
2379
+ const mint = makeMint();
1597
2380
  return [
1598
2381
  ` while (_e < input.length) {`,
1599
2382
  ` const c = input.charCodeAt(_e)`,
1600
- ...shapes.map(scanBranch),
2383
+ ...shapes.map((s) => scanBranch(s, mint)),
1601
2384
  ` break`,
1602
2385
  ` }`
1603
2386
  ].join("\n");
@@ -1617,20 +2400,21 @@ function analyzeLabeledScannableRun(trivia2) {
1617
2400
  if (!spec) return null;
1618
2401
  const out = [];
1619
2402
  for (const arm of spec.arms) {
1620
- const src = getCoreRegexDef(arm.parser)?.source;
1621
- const shape = src ? parseScanShape(src) : null;
2403
+ const d = getCoreRegexDef(arm.parser);
2404
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1622
2405
  if (!shape) return null;
1623
2406
  out.push({ shape, kindIndex: arm.kindIndex });
1624
2407
  }
1625
2408
  return out;
1626
2409
  }
1627
2410
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
2411
+ const mint = makeMint();
1628
2412
  return [
1629
2413
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1630
2414
  ` let _e = _pos`,
1631
2415
  ` while (_e < input.length) {`,
1632
2416
  ` const c = input.charCodeAt(_e)`,
1633
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
2417
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1634
2418
  ` break`,
1635
2419
  ` }`,
1636
2420
  ` return _e`,
@@ -1707,6 +2491,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1707
2491
  ].join("\n");
1708
2492
  }
1709
2493
 
2494
+ // src/compiler/scannable-terminal.ts
2495
+ function emitScannableTerminal(shape, c) {
2496
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
2497
+ const stmts = [...m.setup];
2498
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
2499
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
2500
+ return { stmts, endVar: m.end };
2501
+ }
2502
+
1710
2503
  // src/compiler/inline-build.ts
1711
2504
  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
2505
  function escapeRegExp(s) {
@@ -1828,14 +2621,45 @@ function failReturn(expected, posExpr) {
1828
2621
  function failReturnArr(expectedArr, posExpr) {
1829
2622
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1830
2623
  }
2624
+ function hoistExpected(ctx, constArrSource) {
2625
+ let name = ctx.expectedMap.get(constArrSource);
2626
+ if (name === void 0) {
2627
+ name = `_fx${ctx.expectedDecls.length}`;
2628
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2629
+ ctx.expectedMap.set(constArrSource, name);
2630
+ }
2631
+ return name;
2632
+ }
1831
2633
  function failBody(ctx, expected, posExpr) {
1832
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2634
+ if (ctx.failLabel) {
2635
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2636
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
2637
+ }
1833
2638
  return failReturn(expected, posExpr);
1834
2639
  }
1835
- function failArrBody(ctx, expectedArr, posExpr) {
1836
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2640
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
2641
+ if (ctx.failLabel) {
2642
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2643
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
2644
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
2645
+ }
2646
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1837
2647
  return failReturnArr(expectedArr, posExpr);
1838
2648
  }
2649
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
2650
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
2651
+ }
2652
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
2653
+ if (srcCtx !== "_ctx") {
2654
+ if (ctx.failLabel) {
2655
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2656
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
2657
+ }
2658
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
2659
+ }
2660
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
2661
+ return resultFromRecorded();
2662
+ }
1839
2663
  function emitIfFail(ctx, cond, body) {
1840
2664
  return [
1841
2665
  `${ind(ctx)}if (${cond}) {`,
@@ -1871,13 +2695,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1871
2695
  `}`
1872
2696
  ].join("\n"));
1873
2697
  }
1874
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
2698
+ function emitNamedFnCall(ctx, fnName, pos) {
1875
2699
  const vv = v(ctx, "_pfv");
1876
2700
  const ev = v(ctx, "_pfe");
1877
2701
  return {
1878
2702
  stmts: [
1879
2703
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1880
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
2704
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
2705
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
2706
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1881
2707
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1882
2708
  ],
1883
2709
  valueVar: vv,
@@ -1987,6 +2813,27 @@ function firstSetCond(codeVar, fs) {
1987
2813
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1988
2814
  ).join(" || ");
1989
2815
  }
2816
+ var SWITCH_RANGE_LIMIT = 4;
2817
+ var SWITCH_MAX_CASES = 48;
2818
+ var SWITCH_MIN_CASES = 3;
2819
+ var _forceDisjointIf = false;
2820
+ function planDisjointDispatch(parsers) {
2821
+ const cases = [];
2822
+ let total = 0;
2823
+ for (const p of parsers) {
2824
+ const fs = p._meta.firstSet;
2825
+ if (fs.kind !== "ranges") return { kind: "if" };
2826
+ const pts = [];
2827
+ for (const r of fs.ranges) {
2828
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2829
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2830
+ }
2831
+ total += pts.length;
2832
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2833
+ cases.push(pts);
2834
+ }
2835
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2836
+ }
1990
2837
  function captureRestoreBody(mL, mR, mTl, mLg) {
1991
2838
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1992
2839
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -2100,18 +2947,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
2100
2947
  return true;
2101
2948
  }
2102
2949
  }
2103
- function emitFallible(inner, ctx, pos) {
2950
+ function emitFallible(inner, ctx, pos, swallow = false) {
2104
2951
  const lbl = v(ctx, "_lbl");
2105
2952
  const okV = `${lbl}ok`;
2106
2953
  const valV = `${lbl}v`;
2107
2954
  const endV = `${lbl}e`;
2108
2955
  const savedLabel = ctx.failLabel;
2109
2956
  const savedIndent = ctx.indent;
2957
+ const savedRecord = ctx.recordFail;
2110
2958
  ctx.failLabel = lbl;
2111
2959
  ctx.indent = savedIndent + 1;
2960
+ if (swallow) ctx.recordFail = false;
2112
2961
  const r = emit(inner, ctx, pos);
2113
2962
  ctx.failLabel = savedLabel;
2114
2963
  ctx.indent = savedIndent;
2964
+ ctx.recordFail = savedRecord;
2115
2965
  const ind0 = ind(ctx);
2116
2966
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
2117
2967
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2134,6 +2984,7 @@ function emitFallible(inner, ctx, pos) {
2134
2984
  ];
2135
2985
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2136
2986
  }
2987
+ var CHARCODE_CHAIN_MAX = 16;
2137
2988
  function emitLit(def, ctx, pos) {
2138
2989
  const { value, caseInsensitive } = def;
2139
2990
  const len = value.length;
@@ -2156,7 +3007,7 @@ function emitLit(def, ctx, pos) {
2156
3007
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2157
3008
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2158
3009
  );
2159
- } else if (len <= 4) {
3010
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2160
3011
  const checks = Array.from(
2161
3012
  { length: len },
2162
3013
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2178,7 +3029,55 @@ function emitLit(def, ctx, pos) {
2178
3029
  function escapeKeywordRe(s) {
2179
3030
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2180
3031
  }
3032
+ function emitKeywordsFast(def, ctx, pos) {
3033
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
3034
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
3035
+ if (def.caseInsensitive && def.boundary) return null;
3036
+ let boundary = null;
3037
+ if (def.boundary) {
3038
+ let body = def.boundary;
3039
+ const negated = body.startsWith("^");
3040
+ if (negated) body = body.slice(1);
3041
+ const ranges = parseClassRanges(body);
3042
+ if (!ranges) return null;
3043
+ boundary = { ranges, negated };
3044
+ }
3045
+ const mint = (prefix = "_v") => v(ctx, prefix);
3046
+ const lbl = v(ctx, "_kwLbl");
3047
+ const valV = v(ctx, "_kwv");
3048
+ const endV = v(ctx, "_kwe");
3049
+ const bodyInd = ind(ctx) + " ";
3050
+ const tries = [];
3051
+ for (const w of def.words) {
3052
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
3053
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
3054
+ if (boundary) {
3055
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
3056
+ }
3057
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
3058
+ tries.push(
3059
+ ...m.setup,
3060
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
3061
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
3062
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
3063
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
3064
+ );
3065
+ }
3066
+ const stmts = [
3067
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
3068
+ `${ind(ctx)}${lbl}: {`,
3069
+ ...tries,
3070
+ `${ind(ctx)}}`,
3071
+ // Every word has length >= 1 (checked above), so a real match always
3072
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
3073
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
3074
+ ];
3075
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
3076
+ return { stmts, valueVar: valV, endVar: endV };
3077
+ }
2181
3078
  function emitKeywords(def, ctx, pos) {
3079
+ const fast = emitKeywordsFast(def, ctx, pos);
3080
+ if (fast) return fast;
2182
3081
  const alt = def.words.map(escapeKeywordRe).join("|");
2183
3082
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2184
3083
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2203,6 +3102,22 @@ function emitKeywords(def, ctx, pos) {
2203
3102
  return { stmts, valueVar: vv, endVar };
2204
3103
  }
2205
3104
  function emitRegex(def, ctx, pos) {
3105
+ const expectedStr = JSON.stringify(`/${def.source}/`);
3106
+ const shape = scanShapeFromRegex(def.source, def.flags);
3107
+ if (shape) {
3108
+ const vv2 = v(ctx);
3109
+ const scanned = emitScannableTerminal(shape, {
3110
+ ind: ind(ctx),
3111
+ pos,
3112
+ valueVar: vv2,
3113
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
3114
+ fresh: (prefix) => v(ctx, prefix)
3115
+ });
3116
+ if (scanned) {
3117
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
3118
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3119
+ }
3120
+ }
2206
3121
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2207
3122
  const key = `${def.optimizedSource}/${flags}`;
2208
3123
  let rName = ctx.regexMap.get(key);
@@ -2213,7 +3128,6 @@ function emitRegex(def, ctx, pos) {
2213
3128
  }
2214
3129
  const mv = v(ctx, "_m");
2215
3130
  const vv = v(ctx);
2216
- const expectedStr = JSON.stringify(`/${def.source}/`);
2217
3131
  const stmts = [
2218
3132
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2219
3133
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2272,15 +3186,68 @@ function emitSeq(def, ctx, pos) {
2272
3186
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2273
3187
  return { stmts, valueVar: arrV, endVar };
2274
3188
  }
3189
+ function staticExpectedArr(parsers) {
3190
+ return JSON.stringify(parsers.flatMap((p) => {
3191
+ const e = staticExpected(p);
3192
+ return e.length > 0 ? e : [p._tag];
3193
+ }));
3194
+ }
3195
+ function failsAtStart(p) {
3196
+ const d = p._def;
3197
+ switch (d.tag) {
3198
+ case "literal":
3199
+ case "regex":
3200
+ case "keywords":
3201
+ case "guard":
3202
+ case "not":
3203
+ return true;
3204
+ case "transform":
3205
+ case "label":
3206
+ return failsAtStart(d.parser);
3207
+ default:
3208
+ return false;
3209
+ }
3210
+ }
3211
+ function armStaticExpected(ctx, p) {
3212
+ return hoistExpected(ctx, staticExpectedArr([p]));
3213
+ }
3214
+ function canMatchEmptyAtStart(p) {
3215
+ const d = p._def;
3216
+ switch (d.tag) {
3217
+ case "regex":
3218
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3219
+ case "optional":
3220
+ return true;
3221
+ case "many":
3222
+ return true;
3223
+ case "transform":
3224
+ case "label":
3225
+ return canMatchEmptyAtStart(d.parser);
3226
+ case "literal":
3227
+ return d.value.length === 0;
3228
+ default:
3229
+ return false;
3230
+ }
3231
+ }
3232
+ function needsFirstSetGuard(p) {
3233
+ const fs = p._meta.firstSet;
3234
+ return fs.kind !== "any" && !canMatchEmptyAtStart(p);
3235
+ }
3236
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
3237
+ if (failsAtStart(p)) {
3238
+ const r = emit(p, ctx, pos);
3239
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
3240
+ }
3241
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3242
+ return [
3243
+ ...stmts,
3244
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
3245
+ `${ind(ctx)}${valV} = ${valVar}`,
3246
+ `${ind(ctx)}${endV} = ${endVar}`
3247
+ ];
3248
+ }
2275
3249
  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
- );
3250
+ const allExpected = staticExpectedArr(def.parsers);
2284
3251
  if (def.disjoint) {
2285
3252
  const codeV = v(ctx, "_code");
2286
3253
  const valV = v(ctx, "_chv");
@@ -2289,6 +3256,27 @@ function emitChoice(def, ctx, pos) {
2289
3256
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2290
3257
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2291
3258
  ];
3259
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
3260
+ if (plan.kind === "switch") {
3261
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
3262
+ ctx.indent++;
3263
+ for (let i = 0; i < def.parsers.length; i++) {
3264
+ const p = def.parsers[i];
3265
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
3266
+ stmts.push(`${ind(ctx)}{`);
3267
+ ctx.indent++;
3268
+ stmts.push(
3269
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
3270
+ `${ind(ctx)}break`
3271
+ );
3272
+ ctx.indent--;
3273
+ stmts.push(`${ind(ctx)}}`);
3274
+ }
3275
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
3276
+ ctx.indent--;
3277
+ stmts.push(`${ind(ctx)}}`);
3278
+ return { stmts, valueVar: valV, endVar: endV };
3279
+ }
2292
3280
  let first = true;
2293
3281
  for (const p of def.parsers) {
2294
3282
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2296,10 +3284,7 @@ function emitChoice(def, ctx, pos) {
2296
3284
  first = false;
2297
3285
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2298
3286
  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}`);
3287
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2303
3288
  ctx.indent--;
2304
3289
  stmts.push(`${ind(ctx)}}`);
2305
3290
  }
@@ -2308,7 +3293,7 @@ function emitChoice(def, ctx, pos) {
2308
3293
  }
2309
3294
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2310
3295
  }
2311
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
3296
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2312
3297
  const superParser = def.parsers[superIndex];
2313
3298
  const regexDef = getCoreRegexDef(superParser);
2314
3299
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2323,10 +3308,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2323
3308
  const wordV = v(ctx, "_gw");
2324
3309
  const endV = v(ctx, "_ge");
2325
3310
  const valV = v(ctx, "_gcv");
3311
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2326
3312
  const stmts = [
2327
3313
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2328
3314
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2329
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
3315
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2330
3316
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2331
3317
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2332
3318
  `${ind(ctx)}let ${valV}`
@@ -2384,18 +3370,25 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2384
3370
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2385
3371
  return { stmts, valueVar: valV, endVar: endV };
2386
3372
  }
2387
- function emitFirstMatch(def, allExpected, ctx, pos) {
3373
+ function emitFirstMatch(def, ctx, pos) {
2388
3374
  const resValV = v(ctx, "_crv");
2389
3375
  const resEndV = v(ctx, "_cre");
2390
3376
  const resOkV = v(ctx, "_crok");
3377
+ const codeV = v(ctx, "_chcode");
3378
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2391
3379
  const ind0 = ind(ctx);
2392
3380
  const stmts = [
2393
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
3381
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
3382
+ `${ind0}let ${slots.join(", ")}`,
3383
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2394
3384
  ];
2395
3385
  for (let i = 0; i < def.parsers.length; i++) {
2396
3386
  const p = def.parsers[i];
2397
3387
  const gate = def.gates[i];
2398
3388
  const autoNot = def.autoNot[i];
3389
+ const atStart = failsAtStart(p);
3390
+ const staticFx = armStaticExpected(ctx, p);
3391
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2399
3392
  let gateCond = null;
2400
3393
  if (gate) {
2401
3394
  const gateIdx = ctx.mapFns.length;
@@ -2410,49 +3403,53 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2410
3403
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2411
3404
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2412
3405
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3406
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2413
3407
  stmts.push(`${ind0}if (${skipCond}) {`);
2414
- ctx.indent++;
2415
- const ind1 = ind(ctx);
3408
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3409
+ ctx.indent += fsGuard ? 2 : 1;
2416
3410
  if (markLeaves) {
2417
3411
  stmts.push(
2418
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2419
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2420
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2421
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3412
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3413
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3414
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3415
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2422
3416
  );
2423
3417
  }
2424
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3418
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2425
3419
  stmts.push(...armStmts);
2426
- ctx.indent--;
2427
3420
  if (autoNot && autoNot.length > 0) {
2428
3421
  const anCode = v(ctx, "_anc");
2429
3422
  const rejectCond = autoNot.map(
2430
3423
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2431
3424
  ).join(" || ");
2432
- stmts.push(`${ind0} if (${okVar}) {`);
2433
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2434
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2435
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2436
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2437
- stmts.push(`${ind0} ${resOkV} = true`);
2438
- stmts.push(`${ind0} }`);
2439
- stmts.push(`${ind0} }`);
2440
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3425
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3426
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3427
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3428
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3429
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3430
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3431
+ stmts.push(`${ind(ctx)} }`);
3432
+ stmts.push(`${ind(ctx)}}`);
3433
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3434
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2441
3435
  } else {
2442
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2443
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
3436
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3437
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2444
3438
  }
3439
+ ctx.indent -= fsGuard ? 2 : 1;
3440
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2445
3441
  stmts.push(`${ind0}}`);
2446
3442
  }
2447
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
3443
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
3444
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2448
3445
  return { stmts, valueVar: resValV, endVar: resEndV };
2449
3446
  }
2450
3447
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2451
3448
  if (strategy.tag === "greedyClassify")
2452
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
3449
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2453
3450
  if (strategy.tag === "literalsLongestFirst")
2454
3451
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2455
- return emitFirstMatch(def, allExpected, ctx, pos);
3452
+ return emitFirstMatch(def, ctx, pos);
2456
3453
  }
2457
3454
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2458
3455
  const def = p._def;
@@ -2473,7 +3470,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2473
3470
  function emitLiteralCondition(litVal, pos) {
2474
3471
  const len = litVal.length;
2475
3472
  if (len === 0) return "true";
2476
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3473
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2477
3474
  const checks = [`${pos} + ${len} <= input.length`];
2478
3475
  for (let i = 0; i < len; i++) {
2479
3476
  const code = litVal.codePointAt(i);
@@ -2522,7 +3519,7 @@ function emitMany(def, ctx, pos) {
2522
3519
  itemPos = npV;
2523
3520
  }
2524
3521
  }
2525
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3522
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2526
3523
  stmts.push(
2527
3524
  ...iterStmts,
2528
3525
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2536,7 +3533,7 @@ function emitMany(def, ctx, pos) {
2536
3533
  function emitOptional(def, ctx, pos) {
2537
3534
  const valV = v(ctx, "_opt");
2538
3535
  const endV = v(ctx, "_opte");
2539
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3536
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2540
3537
  const ind0 = ind(ctx);
2541
3538
  const stmts = [
2542
3539
  ...lblStmts,
@@ -2548,7 +3545,7 @@ function emitOptional(def, ctx, pos) {
2548
3545
  function emitSepBy(_p, def, ctx, pos) {
2549
3546
  const arrV = v(ctx, "_arr");
2550
3547
  const curV = v(ctx, "_cur");
2551
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3548
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2552
3549
  const stmts = [
2553
3550
  `${ind(ctx)}const ${arrV} = []`,
2554
3551
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2580,11 +3577,11 @@ function emitSepBy(_p, def, ctx, pos) {
2580
3577
  );
2581
3578
  sepAtPos = spV;
2582
3579
  const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
2583
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3580
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2584
3581
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2585
3582
  const npV = v(ctx, "_np");
2586
3583
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2587
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3584
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2588
3585
  stmts.push(
2589
3586
  ...nextStmts,
2590
3587
  // item failed → unwind the separator too, back to the end of the last item
@@ -2597,11 +3594,11 @@ function emitSepBy(_p, def, ctx, pos) {
2597
3594
  const spV = v(ctx, "_sp");
2598
3595
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2599
3596
  sepAtPos = spV;
2600
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3597
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2601
3598
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2602
3599
  const npV = v(ctx, "_np");
2603
3600
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2604
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3601
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2605
3602
  stmts.push(
2606
3603
  ...nextStmts,
2607
3604
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2618,10 +3615,10 @@ function emitSepBy(_p, def, ctx, pos) {
2618
3615
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2619
3616
  );
2620
3617
  }
2621
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3618
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2622
3619
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2623
3620
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2624
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3621
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2625
3622
  stmts.push(
2626
3623
  ...nextStmts,
2627
3624
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -2679,11 +3676,12 @@ function emitScanTo(def, ctx, pos) {
2679
3676
  return { stmts, valueVar: valV, endVar: curV };
2680
3677
  }
2681
3678
  function emitNot(def, ctx, pos) {
2682
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3679
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
3680
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2683
3681
  return {
2684
3682
  stmts: [
2685
3683
  ...stmts,
2686
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
3684
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2687
3685
  ],
2688
3686
  valueVar: "null",
2689
3687
  endVar: pos
@@ -2715,7 +3713,7 @@ function emitNode(def, ctx, pos) {
2715
3713
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2716
3714
  stmts.push(...innerStmts);
2717
3715
  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)));
3716
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2719
3717
  let stV = "undefined";
2720
3718
  if (clonesState) {
2721
3719
  stV = v(ctx, "_nst");
@@ -2737,9 +3735,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2737
3735
  const rv = v(ctx, "_rt");
2738
3736
  const vv = v(ctx, "_rtv");
2739
3737
  const ev = v(ctx, "_rte");
3738
+ 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
3739
  const stmts = [
2741
3740
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2742
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
3741
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2743
3742
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2744
3743
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2745
3744
  ];
@@ -2768,15 +3767,18 @@ function emitLazy(p, def, ctx, pos) {
2768
3767
  }
2769
3768
  const savedIndent = ctx.indent;
2770
3769
  const savedFailLabel = ctx.failLabel;
3770
+ const savedRecord = ctx.recordFail;
2771
3771
  ctx.indent = 1;
2772
3772
  ctx.failLabel = "_pfail";
3773
+ ctx.recordFail = true;
2773
3774
  const r = emit(resolved, ctx, "_pos");
2774
3775
  ctx.indent = savedIndent;
2775
3776
  ctx.failLabel = savedFailLabel;
3777
+ ctx.recordFail = savedRecord;
2776
3778
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2777
3779
  }
2778
3780
  const fnName = ctx.namedParsers.get(p);
2779
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
3781
+ return emitNamedFnCall(ctx, fnName, pos);
2780
3782
  }
2781
3783
  function emitRecover(def, ctx, pos) {
2782
3784
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2794,7 +3796,7 @@ function emitRecover(def, ctx, pos) {
2794
3796
  `${ind0} if (${sentOk}) break`,
2795
3797
  `${ind0} ${scanV}++`,
2796
3798
  `${ind0} }`,
2797
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
3799
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2798
3800
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2799
3801
  `${ind0} ${valVar} = ${errV}`,
2800
3802
  `${ind0} ${endVar} = ${scanV}`,
@@ -2952,11 +3954,14 @@ function emit(p, ctx, pos) {
2952
3954
  ctx.namedParsers.set(innerParser, fnName);
2953
3955
  const savedIndent = ctx.indent;
2954
3956
  const savedFailLabel = ctx.failLabel;
3957
+ const savedRecord = ctx.recordFail;
2955
3958
  ctx.indent = 1;
2956
3959
  ctx.failLabel = "_pfail";
3960
+ ctx.recordFail = true;
2957
3961
  const innerR = emit(innerParser, ctx, "_pos");
2958
3962
  ctx.indent = savedIndent;
2959
3963
  ctx.failLabel = savedFailLabel;
3964
+ ctx.recordFail = savedRecord;
2960
3965
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
2961
3966
  }
2962
3967
  const fn = ctx.namedParsers.get(innerParser);
@@ -2967,7 +3972,9 @@ function emit(p, ctx, pos) {
2967
3972
  stmts: [
2968
3973
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2969
3974
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2970
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3975
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3976
+ // and propagate the inner failure verbatim (interpreter parity).
3977
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2971
3978
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2972
3979
  ],
2973
3980
  valueVar: vv,
@@ -3068,7 +4075,7 @@ function analyzeLazyUsageMulti(roots) {
3068
4075
  const recursive = /* @__PURE__ */ new Set();
3069
4076
  const descended = /* @__PURE__ */ new Set();
3070
4077
  const active = /* @__PURE__ */ new Set();
3071
- function walk(p) {
4078
+ function walk2(p) {
3072
4079
  const def = p._def;
3073
4080
  if (def.tag === "lazy") {
3074
4081
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -3085,13 +4092,13 @@ function analyzeLazyUsageMulti(roots) {
3085
4092
  return;
3086
4093
  }
3087
4094
  active.add(p);
3088
- walk(resolved);
4095
+ walk2(resolved);
3089
4096
  active.delete(p);
3090
4097
  return;
3091
4098
  }
3092
- for (const child of childrenOf(def)) walk(child);
4099
+ for (const child of childrenOf(def)) walk2(child);
3093
4100
  }
3094
- for (const root of roots) walk(root);
4101
+ for (const root of roots) walk2(root);
3095
4102
  return { counts, recursive };
3096
4103
  }
3097
4104
  function compile(combinator, mapFnSources) {
@@ -3100,6 +4107,9 @@ function compile(combinator, mapFnSources) {
3100
4107
  indent: 1,
3101
4108
  regexDecls: [],
3102
4109
  regexMap: /* @__PURE__ */ new Map(),
4110
+ expectedDecls: [],
4111
+ expectedMap: /* @__PURE__ */ new Map(),
4112
+ recordFail: true,
3103
4113
  mapFns: [],
3104
4114
  mapFnSrcs: [],
3105
4115
  buildFns: [],
@@ -3121,6 +4131,7 @@ function compile(combinator, mapFnSources) {
3121
4131
  const source = [
3122
4132
  ...emptyTlDecls,
3123
4133
  ...ctx.regexDecls,
4134
+ ...ctx.expectedDecls,
3124
4135
  "",
3125
4136
  ...namedPrelude,
3126
4137
  ctx.namedFnDecls.join("\n\n"),
@@ -3133,6 +4144,7 @@ function compile(combinator, mapFnSources) {
3133
4144
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3134
4145
  ...emptyTlDecls,
3135
4146
  ...ctx.regexDecls,
4147
+ ...ctx.expectedDecls,
3136
4148
  collatorDecl,
3137
4149
  ...namedPrelude,
3138
4150
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3186,12 +4198,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3186
4198
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3187
4199
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3188
4200
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3189
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4201
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3190
4202
  if (!needsWrapper) return innerFn;
3191
4203
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3192
4204
  const hoistedDecls = [
3193
4205
  emptyTlDecl,
3194
4206
  ...ctx.regexDecls.map((d) => ` ${d}`),
4207
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3195
4208
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3196
4209
  mfDecl,
3197
4210
  buildDecl,
@@ -3252,6 +4265,7 @@ function recover(combinator, sentinel) {
3252
4265
  canMatchNewline: true,
3253
4266
  isTrivia: false
3254
4267
  };
4268
+ const expected = staticExpected(combinator);
3255
4269
  return {
3256
4270
  _tag: "recover",
3257
4271
  _meta: meta,
@@ -3267,7 +4281,7 @@ function recover(combinator, sentinel) {
3267
4281
  const error = {
3268
4282
  _tag: "parseError",
3269
4283
  span: { start: pos, end: scanPos },
3270
- expected: result.expected
4284
+ expected
3271
4285
  };
3272
4286
  ctx._errors?.push(error);
3273
4287
  return { ok: true, value: error, span: { start: pos, end: scanPos } };
@@ -3278,58 +4292,6 @@ function isParseError(value) {
3278
4292
  return typeof value === "object" && value !== null && value._tag === "parseError";
3279
4293
  }
3280
4294
 
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
4295
  // src/combinators/completions.ts
3334
4296
  function completionsAt(combinator, input, offset) {
3335
4297
  const probe = { offset, best: null };
@@ -3439,6 +4401,23 @@ function escapeClassChar(ch) {
3439
4401
  return ch.replace(/[\\\]^-]/g, "\\$&");
3440
4402
  }
3441
4403
 
4404
+ // src/combinators/recover-list.ts
4405
+ function tolerantElement(item, sentinel, until) {
4406
+ const el = transform(
4407
+ sequence(not(until), recover(item, sentinel)),
4408
+ ([, value]) => value
4409
+ );
4410
+ if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
4411
+ return el;
4412
+ }
4413
+ function sepByRecover(item, separator, until) {
4414
+ const sentinel = choice(separator, until);
4415
+ return sepBy(tolerantElement(item, sentinel, until), separator);
4416
+ }
4417
+ function manyRecover(item, until) {
4418
+ return many(tolerantElement(item, until, until));
4419
+ }
4420
+
3442
4421
  // src/functional/doc.ts
3443
4422
  function isNode(x) {
3444
4423
  return typeof x === "object" && x !== null && x._tag === "node";
@@ -3500,7 +4479,7 @@ function graftAndShift(root, path, newNode, delta) {
3500
4479
  children: newChildren
3501
4480
  };
3502
4481
  }
3503
- var FunctionalDocImpl = class _FunctionalDocImpl {
4482
+ var ParseDocImpl = class _ParseDocImpl {
3504
4483
  _registry;
3505
4484
  _rootRule;
3506
4485
  _opts;
@@ -3517,7 +4496,7 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3517
4496
  }
3518
4497
  edit(from, to, replacement) {
3519
4498
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
3520
- const reparse = () => makeFunctionalDoc(this._registry, this._rootRule, newInput, this._opts);
4499
+ const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
3521
4500
  if (!this.tree) return reparse();
3522
4501
  const delta = replacement.length - (to - from);
3523
4502
  const found = findContaining(this.tree, from);
@@ -3539,25 +4518,25 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3539
4518
  if (r.span.end === node2.span.end + delta) {
3540
4519
  if (delta === 0) {
3541
4520
  const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
3542
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
4521
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3543
4522
  }
3544
4523
  if (this._opts.rebuild) return reparse();
3545
4524
  const newTree = graftAndShift(this.tree, path, r.value, delta);
3546
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
4525
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
3547
4526
  }
3548
4527
  }
3549
4528
  return reparse();
3550
4529
  }
3551
4530
  };
3552
- function makeFunctionalDoc(registry, rootRule, input, opts = {}) {
4531
+ function parseDoc(registry, rootRule, input, opts = {}) {
3553
4532
  const ruleFn = registry[rootRule];
3554
4533
  if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
3555
4534
  const ctx = { trackLines: false, state: opts.state };
3556
4535
  const r = ruleFn(input, 0, ctx);
3557
4536
  if (r.ok) {
3558
- return new FunctionalDocImpl(registry, rootRule, opts, r.value, [], input);
4537
+ return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
3559
4538
  }
3560
- return new FunctionalDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
4539
+ return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
3561
4540
  }
3562
4541
 
3563
4542
  // src/cst/trivia-index.ts
@@ -3626,6 +4605,41 @@ function buildTriviaIndex(root, input = "", opts) {
3626
4605
  return { before, after };
3627
4606
  }
3628
4607
 
4608
+ // src/cst/walk.ts
4609
+ function walk(root, visitor, ctx = void 0) {
4610
+ const go = (node2, parent) => {
4611
+ const descend = visitor.enter ? visitor.enter(node2, parent, ctx) : void 0;
4612
+ const children = node2.children;
4613
+ if (descend !== false && Array.isArray(children)) {
4614
+ for (const child of children) go(child, node2);
4615
+ }
4616
+ visitor.leave?.(node2, parent, ctx);
4617
+ };
4618
+ go(root, null);
4619
+ }
4620
+ function createVisitor(handlers) {
4621
+ const api = {
4622
+ visit(node2) {
4623
+ const handler = node2.type !== void 0 ? handlers[node2.type] : void 0;
4624
+ if (handler) return handler(node2, api);
4625
+ api.visitChildren(node2);
4626
+ return void 0;
4627
+ },
4628
+ visitChildren(node2) {
4629
+ const out = [];
4630
+ const children = node2.children;
4631
+ if (Array.isArray(children)) {
4632
+ for (const child of children) {
4633
+ const r = api.visit(child);
4634
+ if (r !== void 0) out.push(r);
4635
+ }
4636
+ }
4637
+ return out;
4638
+ }
4639
+ };
4640
+ return (node2) => api.visit(node2);
4641
+ }
4642
+
3629
4643
  // src/cst/trivia-entries.ts
3630
4644
  function entryOffset(i, stride) {
3631
4645
  return i * stride;