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.js CHANGED
@@ -746,12 +746,15 @@ function scanFastWsCommentsChunks(input, cur, wsKind, commentKind) {
746
746
  continue;
747
747
  }
748
748
  if (c === 47 && input.charCodeAt(pos + 1) === 42) {
749
- const start = pos;
750
749
  let j = pos + 2;
751
750
  while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++;
752
- pos = j + 2 <= input.length ? j + 2 : input.length;
753
- chunks.push({ start, end: pos, kindIndex: commentKind });
754
- continue;
751
+ if (j + 1 < input.length && input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47) {
752
+ const start = pos;
753
+ pos = j + 2;
754
+ chunks.push({ start, end: pos, kindIndex: commentKind });
755
+ continue;
756
+ }
757
+ break;
755
758
  }
756
759
  break;
757
760
  }
@@ -1340,38 +1343,140 @@ function parse(combinator, input, opts = {}) {
1340
1343
  return withErrors;
1341
1344
  }
1342
1345
 
1346
+ // src/combinators/expect.ts
1347
+ function staticExpected(c) {
1348
+ const def = c._def;
1349
+ switch (def.tag) {
1350
+ case "literal":
1351
+ return [JSON.stringify(def.value)];
1352
+ case "regex":
1353
+ return [`/${def.source}/`];
1354
+ case "keywords":
1355
+ return def.words.map((w) => JSON.stringify(w));
1356
+ case "label":
1357
+ return [def.label];
1358
+ case "choice":
1359
+ return def.parsers.flatMap(staticExpected);
1360
+ case "sequence":
1361
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1362
+ case "node":
1363
+ case "grammar":
1364
+ case "trivia":
1365
+ case "optional":
1366
+ case "many":
1367
+ case "oneOrMore":
1368
+ case "transform":
1369
+ case "not":
1370
+ return staticExpected(def.parser);
1371
+ case "lazy":
1372
+ return staticExpected(def.thunk());
1373
+ default:
1374
+ return [];
1375
+ }
1376
+ }
1377
+ function expect(combinator, label2) {
1378
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1379
+ const meta = {
1380
+ firstSet: combinator._meta.firstSet,
1381
+ canMatchNewline: combinator._meta.canMatchNewline,
1382
+ isTrivia: false
1383
+ };
1384
+ return {
1385
+ _tag: "expect",
1386
+ _meta: meta,
1387
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1388
+ parse(input, pos, ctx) {
1389
+ const result = combinator.parse(input, pos, ctx);
1390
+ if (result.ok) return result;
1391
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1392
+ ctx._errors?.push(error);
1393
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1394
+ }
1395
+ };
1396
+ }
1397
+
1343
1398
  // src/compiler/scannable-run.ts
1399
+ var BACKSLASH = 92;
1344
1400
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1345
1401
  var META = new Set("()[]{}*+?|^$.".split(""));
1402
+ var SPACE_RANGES = [
1403
+ [9, 13],
1404
+ [32, 32],
1405
+ [160, 160],
1406
+ [5760, 5760],
1407
+ [8192, 8202],
1408
+ [8232, 8232],
1409
+ [8233, 8233],
1410
+ [8239, 8239],
1411
+ [8287, 8287],
1412
+ [12288, 12288],
1413
+ [65279, 65279]
1414
+ ];
1415
+ function shorthandRanges(ch) {
1416
+ if (ch === "d") return [[48, 57]];
1417
+ if (ch === "s") return SPACE_RANGES;
1418
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1419
+ }
1420
+ function readUnicodeEscape(body, i) {
1421
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1422
+ const hex = body.slice(i + 2, i + 6);
1423
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1424
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1425
+ }
1346
1426
  function parseClassRanges(body) {
1347
1427
  const ranges = [];
1348
1428
  let i = 0;
1349
- const readCp = () => {
1429
+ const readAtom = () => {
1350
1430
  const ch = body[i];
1351
1431
  if (ch === void 0) return null;
1352
1432
  if (ch === "\\") {
1433
+ const uni = readUnicodeEscape(body, i);
1434
+ if (uni) {
1435
+ i = uni.next;
1436
+ return { cp: uni.cp };
1437
+ }
1353
1438
  const e = body[i + 1];
1354
1439
  if (e === void 0) return null;
1355
1440
  i += 2;
1356
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1441
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1442
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1443
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1444
+ return { cp: e.codePointAt(0) };
1357
1445
  }
1358
1446
  i += ch.length;
1359
- return ch.codePointAt(0);
1447
+ return { cp: ch.codePointAt(0) };
1360
1448
  };
1361
1449
  while (i < body.length) {
1362
- const lo = readCp();
1450
+ const lo = readAtom();
1363
1451
  if (lo === null) return null;
1452
+ if ("set" in lo) {
1453
+ ranges.push(...lo.set);
1454
+ continue;
1455
+ }
1364
1456
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1365
1457
  i += 1;
1366
- const hi = readCp();
1367
- if (hi === null) return null;
1368
- ranges.push([lo, hi]);
1458
+ const hi = readAtom();
1459
+ if (hi === null || "set" in hi) return null;
1460
+ ranges.push([lo.cp, hi.cp]);
1369
1461
  } else {
1370
- ranges.push([lo, lo]);
1462
+ ranges.push([lo.cp, lo.cp]);
1371
1463
  }
1372
1464
  }
1373
1465
  return ranges.length ? ranges : null;
1374
1466
  }
1467
+ function classToRanges(cls) {
1468
+ if (cls === "\\d") return shorthandRanges("d");
1469
+ if (cls === "\\w") return shorthandRanges("w");
1470
+ if (cls === "\\s") return shorthandRanges("s");
1471
+ const body = cls.slice(1, -1);
1472
+ if (body.startsWith("^")) return null;
1473
+ return parseClassRanges(body);
1474
+ }
1475
+ var foldEq = (cVar, cp) => {
1476
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1477
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1478
+ return `${cVar} === ${cp}`;
1479
+ };
1375
1480
  function literalCodePoints(frag) {
1376
1481
  const out = [];
1377
1482
  let i = 0;
@@ -1390,19 +1495,482 @@ function literalCodePoints(frag) {
1390
1495
  }
1391
1496
  return out.length ? out : null;
1392
1497
  }
1498
+ function parseStringShape(source) {
1499
+ const q = source[0];
1500
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1501
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1502
+ const inner = source.slice(1, -1);
1503
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1504
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1505
+ let body;
1506
+ let esc;
1507
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1508
+ if (m) {
1509
+ body = m[1];
1510
+ esc = m[2];
1511
+ } else {
1512
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1513
+ if (m) {
1514
+ esc = m[1];
1515
+ body = m[2];
1516
+ }
1517
+ }
1518
+ if (body === void 0 || esc === void 0) return null;
1519
+ const excluded = parseClassRanges(body);
1520
+ if (!excluded) return null;
1521
+ const qcp = q.codePointAt(0);
1522
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1523
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1524
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1525
+ }
1526
+ function parseSeqParts(source) {
1527
+ const parts = [];
1528
+ let lit = [];
1529
+ const flush = () => {
1530
+ if (lit.length) {
1531
+ parts.push({ part: "lit", cps: lit, optional: false });
1532
+ lit = [];
1533
+ }
1534
+ };
1535
+ const runFrom = (ranges, negated, q) => {
1536
+ if (q === "+") {
1537
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1538
+ return 1;
1539
+ }
1540
+ if (q === "*") {
1541
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1542
+ return 1;
1543
+ }
1544
+ if (q === "?") {
1545
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1546
+ return 1;
1547
+ }
1548
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1549
+ return 0;
1550
+ };
1551
+ let i = 0;
1552
+ while (i < source.length) {
1553
+ const ch = source[i];
1554
+ if (ch === "[") {
1555
+ let k = i + 1;
1556
+ const neg = source[k] === "^";
1557
+ if (neg) k++;
1558
+ let body = "";
1559
+ while (k < source.length && source[k] !== "]") {
1560
+ if (source[k] === "\\") {
1561
+ body += source[k] + (source[k + 1] ?? "");
1562
+ k += 2;
1563
+ } else {
1564
+ body += source[k];
1565
+ k++;
1566
+ }
1567
+ }
1568
+ if (source[k] !== "]") return null;
1569
+ const ranges = parseClassRanges(body);
1570
+ if (!ranges) return null;
1571
+ flush();
1572
+ i = k + 1;
1573
+ i += runFrom(ranges, neg, source[i]);
1574
+ continue;
1575
+ }
1576
+ if (ch === "\\") {
1577
+ const e = source[i + 1];
1578
+ if (e === "d" || e === "w" || e === "s") {
1579
+ flush();
1580
+ i += 2;
1581
+ i += runFrom(shorthandRanges(e), false, source[i]);
1582
+ continue;
1583
+ }
1584
+ const uni = readUnicodeEscape(source, i);
1585
+ let cp2;
1586
+ if (uni) {
1587
+ cp2 = uni.cp;
1588
+ i = uni.next;
1589
+ } else if (e === void 0) return null;
1590
+ else if (e in CLASS_ESCAPES) {
1591
+ cp2 = CLASS_ESCAPES[e];
1592
+ i += 2;
1593
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1594
+ else {
1595
+ cp2 = e.codePointAt(0);
1596
+ i += 2;
1597
+ }
1598
+ if (source[i] === "?") {
1599
+ flush();
1600
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1601
+ i++;
1602
+ } else lit.push(cp2);
1603
+ continue;
1604
+ }
1605
+ if (ch === "(") {
1606
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1607
+ let k = i + 3;
1608
+ let depth = 1;
1609
+ while (k < source.length && depth > 0) {
1610
+ const c2 = source[k];
1611
+ if (c2 === "\\") {
1612
+ k += 2;
1613
+ continue;
1614
+ }
1615
+ if (c2 === "[") {
1616
+ let j = k + 1;
1617
+ if (source[j] === "^") j++;
1618
+ while (j < source.length && source[j] !== "]") {
1619
+ if (source[j] === "\\") j += 2;
1620
+ else j++;
1621
+ }
1622
+ if (source[j] !== "]") return null;
1623
+ k = j + 1;
1624
+ continue;
1625
+ }
1626
+ if (c2 === "(") depth++;
1627
+ else if (c2 === ")") depth--;
1628
+ k++;
1629
+ }
1630
+ if (depth !== 0) return null;
1631
+ const body = source.slice(i + 3, k - 1);
1632
+ const inner = parseScanShape(body);
1633
+ if (!inner || !groupInnerSafe(inner)) return null;
1634
+ const q = source[k];
1635
+ if (q === "{") return null;
1636
+ flush();
1637
+ if (q === "+") {
1638
+ if (shapeCanBeEmpty(inner)) return null;
1639
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1640
+ i = k + 1;
1641
+ } else if (q === "*") {
1642
+ if (shapeCanBeEmpty(inner)) return null;
1643
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1644
+ i = k + 1;
1645
+ } else if (q === "?") {
1646
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1647
+ i = k + 1;
1648
+ } else {
1649
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1650
+ i = k;
1651
+ }
1652
+ continue;
1653
+ }
1654
+ if (META.has(ch)) return null;
1655
+ const cp = ch.codePointAt(0);
1656
+ i += ch.length;
1657
+ if (source[i] === "?") {
1658
+ flush();
1659
+ parts.push({ part: "lit", cps: [cp], optional: true });
1660
+ i++;
1661
+ } else lit.push(cp);
1662
+ }
1663
+ flush();
1664
+ return parts.length ? parts : null;
1665
+ }
1666
+ function seqIsUnambiguous(parts) {
1667
+ let hasMandatory = false;
1668
+ for (let k = 0; k < parts.length; k++) {
1669
+ const p = parts[k];
1670
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1671
+ if (mandatory) hasMandatory = true;
1672
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1673
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1674
+ if (skippableSingle || isUnboundedRepeat) {
1675
+ const rest = seqFirstAccept(parts.slice(k + 1));
1676
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1677
+ }
1678
+ }
1679
+ return hasMandatory;
1680
+ }
1681
+ function rangesOverlap(a, b) {
1682
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1683
+ }
1684
+ function rangesSubset(sub, sup) {
1685
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1686
+ }
1687
+ function parseClassOperand(body) {
1688
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1689
+ return { ranges: shorthandRanges(body[1]), negated: false };
1690
+ }
1691
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1692
+ let inner = body.slice(1, -1);
1693
+ const negated = inner.startsWith("^");
1694
+ if (negated) inner = inner.slice(1);
1695
+ const ranges = parseClassRanges(inner);
1696
+ return ranges ? { ranges, negated } : null;
1697
+ }
1698
+ const cps = literalCodePoints(body);
1699
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1700
+ return null;
1701
+ }
1702
+ function classSubset(t, u) {
1703
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1704
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1705
+ if (t.negated && !u.negated) return false;
1706
+ return rangesSubset(u.ranges, t.ranges);
1707
+ }
1708
+ function classDisjoint(t, u) {
1709
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1710
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1711
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1712
+ return false;
1713
+ }
1714
+ function firstSetsDisjoint(a, b) {
1715
+ if (a === "any" || b === "any") return false;
1716
+ return classDisjoint(a, b);
1717
+ }
1718
+ function trailingBacktrackClass(shape) {
1719
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1720
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1721
+ if (shape.kind === "litFold") return null;
1722
+ if (shape.kind === "seq") {
1723
+ const last = shape.parts[shape.parts.length - 1];
1724
+ if (last.part === "lit") return null;
1725
+ if (last.part === "run") {
1726
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1727
+ return null;
1728
+ }
1729
+ const innerWiggle = trailingBacktrackClass(last.inner);
1730
+ if (innerWiggle === "unsupported") return "unsupported";
1731
+ const requiredOnce = !last.unbounded && last.min === 1;
1732
+ if (requiredOnce) return innerWiggle;
1733
+ const dropExposed = shapeFirstAccept(last.inner);
1734
+ if (dropExposed === "any") return "unsupported";
1735
+ if (innerWiggle === null) return dropExposed;
1736
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1737
+ return unioned === "any" ? "unsupported" : unioned;
1738
+ }
1739
+ if (shape.kind === "alt") {
1740
+ if (!shape.disjoint) return "unsupported";
1741
+ const classes = shape.arms.map(trailingBacktrackClass);
1742
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1743
+ const concrete = classes.filter((c) => c !== null);
1744
+ if (concrete.length === 0) return null;
1745
+ const unioned = unionCharSets(concrete);
1746
+ return unioned === "any" ? "unsupported" : unioned;
1747
+ }
1748
+ return "unsupported";
1749
+ }
1750
+ function lookaheadUnambiguous(inner, operand, negative) {
1751
+ const tail = trailingBacktrackClass(inner);
1752
+ if (tail === "unsupported") return false;
1753
+ if (tail === null) return true;
1754
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1755
+ }
1756
+ function stripTrailingLookahead(source) {
1757
+ if (!source.endsWith(")")) return null;
1758
+ for (const negative of [true, false]) {
1759
+ const lead = negative ? "(?!" : "(?=";
1760
+ const idx = source.lastIndexOf(lead);
1761
+ if (idx === -1) continue;
1762
+ const body = source.slice(idx + lead.length, -1);
1763
+ const operand = body.length ? parseClassOperand(body) : null;
1764
+ if (!operand) continue;
1765
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1766
+ }
1767
+ return null;
1768
+ }
1769
+ function unwrapOuterGroup(source) {
1770
+ while (source.startsWith("(?:")) {
1771
+ let depth = 1;
1772
+ let i = 3;
1773
+ let ok = true;
1774
+ while (i < source.length && depth > 0) {
1775
+ const ch = source[i];
1776
+ if (ch === "\\") {
1777
+ i += 2;
1778
+ continue;
1779
+ }
1780
+ if (ch === "[") {
1781
+ let j = i + 1;
1782
+ if (source[j] === "^") j++;
1783
+ while (j < source.length && source[j] !== "]") {
1784
+ if (source[j] === "\\") j += 2;
1785
+ else j++;
1786
+ }
1787
+ if (source[j] !== "]") {
1788
+ ok = false;
1789
+ break;
1790
+ }
1791
+ i = j + 1;
1792
+ continue;
1793
+ }
1794
+ if (ch === "(") depth++;
1795
+ else if (ch === ")") depth--;
1796
+ i++;
1797
+ }
1798
+ if (!ok || depth !== 0 || i !== source.length) break;
1799
+ source = source.slice(3, -1);
1800
+ }
1801
+ return source;
1802
+ }
1803
+ function splitTopLevelAlternation(source) {
1804
+ const body = unwrapOuterGroup(source);
1805
+ const parts = [];
1806
+ let depth = 0;
1807
+ let last = 0;
1808
+ let i = 0;
1809
+ while (i < body.length) {
1810
+ const ch = body[i];
1811
+ if (ch === "\\") {
1812
+ i += 2;
1813
+ continue;
1814
+ }
1815
+ if (ch === "[") {
1816
+ let j = i + 1;
1817
+ if (body[j] === "^") j++;
1818
+ while (j < body.length && body[j] !== "]") {
1819
+ if (body[j] === "\\") j += 2;
1820
+ else j++;
1821
+ }
1822
+ if (body[j] !== "]") return null;
1823
+ i = j + 1;
1824
+ continue;
1825
+ }
1826
+ if (ch === "(") {
1827
+ depth++;
1828
+ i++;
1829
+ continue;
1830
+ }
1831
+ if (ch === ")") {
1832
+ depth--;
1833
+ if (depth < 0) return null;
1834
+ i++;
1835
+ continue;
1836
+ }
1837
+ if (ch === "|" && depth === 0) {
1838
+ parts.push(body.slice(last, i));
1839
+ last = i + 1;
1840
+ i++;
1841
+ continue;
1842
+ }
1843
+ i++;
1844
+ }
1845
+ if (depth !== 0) return null;
1846
+ parts.push(body.slice(last));
1847
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1848
+ return parts;
1849
+ }
1850
+ function shapeCanBeEmpty(shape) {
1851
+ if (shape.kind === "chars") return !shape.minOne;
1852
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
1853
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
1854
+ return false;
1855
+ }
1856
+ function unionCharSets(sets) {
1857
+ if (sets.some((s) => s === "any")) return "any";
1858
+ const concrete = sets;
1859
+ if (concrete.length === 1) return concrete[0];
1860
+ if (concrete.some((s) => s.negated)) return "any";
1861
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
1862
+ }
1863
+ function partFirstAccept(part) {
1864
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
1865
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
1866
+ return shapeFirstAccept(part.inner);
1867
+ }
1868
+ function seqFirstAccept(parts) {
1869
+ const sets = [];
1870
+ for (const p of parts) {
1871
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1872
+ sets.push(partFirstAccept(p));
1873
+ if (mandatory) break;
1874
+ }
1875
+ return unionCharSets(sets);
1876
+ }
1877
+ function groupInnerSafe(shape) {
1878
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1879
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1880
+ if (shape.kind === "alt") return shape.disjoint;
1881
+ return false;
1882
+ }
1883
+ function shapeFirstAccept(shape) {
1884
+ if (shapeCanBeEmpty(shape)) return "any";
1885
+ switch (shape.kind) {
1886
+ case "chars":
1887
+ return { ranges: shape.ranges, negated: false };
1888
+ case "ident":
1889
+ return { ranges: shape.head, negated: false };
1890
+ case "until":
1891
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1892
+ case "delimited":
1893
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1894
+ case "string":
1895
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
1896
+ case "litFold": {
1897
+ const cp = shape.open[0];
1898
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
1899
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
1900
+ return { ranges: [[cp, cp]], negated: false };
1901
+ }
1902
+ case "seq":
1903
+ return seqFirstAccept(shape.parts);
1904
+ case "lookahead":
1905
+ return shapeFirstAccept(shape.inner);
1906
+ // zero-width — doesn't change first-char acceptance
1907
+ case "alt": {
1908
+ if (!shape.disjoint) return "any";
1909
+ const sets = shape.firsts.filter((f) => f !== null);
1910
+ return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1911
+ }
1912
+ }
1913
+ }
1914
+ function allPairsDisjoint(sets) {
1915
+ for (let i = 0; i < sets.length; i++) {
1916
+ const a = sets[i];
1917
+ if (a === "any") return false;
1918
+ for (let j = i + 1; j < sets.length; j++) {
1919
+ const b = sets[j];
1920
+ if (b === "any" || !classDisjoint(a, b)) return false;
1921
+ }
1922
+ }
1923
+ return true;
1924
+ }
1925
+ function parseAlternation(source) {
1926
+ const parts = splitTopLevelAlternation(source);
1927
+ if (!parts) return null;
1928
+ const arms = [];
1929
+ for (const p of parts) {
1930
+ const arm = parseScanShape(p);
1931
+ if (!arm) return null;
1932
+ arms.push(arm);
1933
+ }
1934
+ const firstSets = arms.map(shapeFirstAccept);
1935
+ const disjoint = allPairsDisjoint(firstSets);
1936
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
1937
+ }
1393
1938
  function parseScanShape(source) {
1939
+ const alt = parseAlternation(source);
1940
+ if (alt) return alt;
1941
+ const la = stripTrailingLookahead(source);
1942
+ if (la) {
1943
+ const inner = parseScanShapeCore(la.base);
1944
+ if (!inner) return null;
1945
+ const operand = { ranges: la.ranges, negated: la.classNegated };
1946
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
1947
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
1948
+ }
1949
+ return parseScanShapeCore(source);
1950
+ }
1951
+ function parseScanShapeCore(source) {
1394
1952
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1395
- if (m) {
1396
- if (m[1].startsWith("^")) return null;
1953
+ if (m && !m[1].startsWith("^")) {
1397
1954
  const ranges = parseClassRanges(m[1]);
1398
- return ranges ? { kind: "chars", ranges } : null;
1955
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1399
1956
  }
1957
+ m = /^\\([dws])([+*])$/.exec(source);
1958
+ if (m) {
1959
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1960
+ }
1961
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
1962
+ if (m) {
1963
+ const head = classToRanges(m[1]);
1964
+ const tail = classToRanges(m[2]);
1965
+ if (head && tail) return { kind: "ident", head, tail };
1966
+ }
1967
+ const str = parseStringShape(source);
1968
+ if (str) return str;
1400
1969
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1401
1970
  if (m) {
1402
1971
  const open = literalCodePoints(m[1]);
1403
1972
  const stop = parseClassRanges(m[2]);
1404
1973
  if (open && stop) return { kind: "until", open, stop };
1405
- return null;
1406
1974
  }
1407
1975
  if (!source.includes("\\\\")) {
1408
1976
  m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
@@ -1412,68 +1980,278 @@ function parseScanShape(source) {
1412
1980
  if (open && close) return { kind: "delimited", open, close };
1413
1981
  }
1414
1982
  }
1983
+ const parts = parseSeqParts(source);
1984
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1415
1985
  return null;
1416
1986
  }
1987
+ function scanShapeFromRegex(source, flags) {
1988
+ if (/[msu]/.test(flags)) return null;
1989
+ if (/i/.test(flags)) {
1990
+ const open = literalCodePoints(source);
1991
+ return open ? { kind: "litFold", open } : null;
1992
+ }
1993
+ return parseScanShape(source);
1994
+ }
1417
1995
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1418
1996
  var litCond = (base, cps, firstVar) => cps.map(
1419
1997
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1420
1998
  ).join(" && ");
1421
- function advanceExpr(shape) {
1999
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
2000
+ function stringHardStop(shape) {
2001
+ return shape.excluded.filter(
2002
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
2003
+ );
2004
+ }
2005
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2006
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2007
+ if (shape.kind === "lookahead") {
2008
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
2009
+ const okV = mint("_ok");
2010
+ const endV2 = mint("_end");
2011
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
2012
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
2013
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
2014
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
2015
+ return {
2016
+ setup: [
2017
+ ...inner.setup,
2018
+ `${ind2}let ${okV} = ${inner.ok}`,
2019
+ `${ind2}let ${endV2} = ${inner.end}`,
2020
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
2021
+ ],
2022
+ ok: okV,
2023
+ end: endV2
2024
+ };
2025
+ }
2026
+ if (shape.kind === "alt") {
2027
+ const okV = mint("_ok");
2028
+ const endV2 = mint("_end");
2029
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2030
+ if (shape.disjoint) {
2031
+ const c0 = codeAt(start, firstChar);
2032
+ shape.arms.forEach((arm, k) => {
2033
+ const fs = shape.firsts[k];
2034
+ const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2035
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2036
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2037
+ lines.push(...m.setup);
2038
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2039
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2040
+ lines.push(`${ind2}}`);
2041
+ });
2042
+ } else {
2043
+ const lbl = mint("_alt");
2044
+ lines.push(`${ind2}${lbl}: {`);
2045
+ for (const arm of shape.arms) {
2046
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2047
+ lines.push(...m.setup);
2048
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
2049
+ }
2050
+ lines.push(`${ind2}}`);
2051
+ }
2052
+ return { setup: lines, ok: okV, end: endV2 };
2053
+ }
1422
2054
  if (shape.kind === "chars") {
2055
+ const cur = mint("_e");
1423
2056
  return {
1424
2057
  setup: [
1425
- ` _e++`,
1426
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
2058
+ `${ind2}let ${cur} = ${start}`,
2059
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1427
2060
  ],
1428
- endVar: "_e"
2061
+ // `*` always matches (possibly empty); `+` needs at least one char.
2062
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
2063
+ end: cur
2064
+ };
2065
+ }
2066
+ if (shape.kind === "ident") {
2067
+ const cur = mint("_e");
2068
+ return {
2069
+ setup: [
2070
+ `${ind2}let ${cur} = ${start}`,
2071
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
2072
+ `${ind2} ${cur} = ${start} + 1`,
2073
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
2074
+ `${ind2}}`
2075
+ ],
2076
+ ok: `${cur} > ${start}`,
2077
+ end: cur
2078
+ };
2079
+ }
2080
+ if (shape.kind === "seq") {
2081
+ const endV2 = mint("_e");
2082
+ const okV = mint("_ok");
2083
+ const runCond = (p, at) => {
2084
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2085
+ return p.negated ? `!(${c})` : `(${c})`;
2086
+ };
2087
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2088
+ let first = true;
2089
+ for (const p of shape.parts) {
2090
+ if (p.part === "lit") {
2091
+ const L = p.cps.length;
2092
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2093
+ if (p.optional) {
2094
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2095
+ } else {
2096
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2097
+ lines.push(`${ind2} ${endV2} += ${L}`);
2098
+ }
2099
+ } else if (p.part === "run" && !p.unbounded) {
2100
+ if (p.min === 1) {
2101
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2102
+ lines.push(`${ind2} ${endV2}++`);
2103
+ } else {
2104
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2105
+ }
2106
+ } else if (p.part === "run") {
2107
+ const s = mint("_s");
2108
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2109
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2110
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2111
+ } else {
2112
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2113
+ if (!p.unbounded) {
2114
+ lines.push(...inner.setup);
2115
+ if (p.min === 1) {
2116
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2117
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2118
+ } else {
2119
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2120
+ }
2121
+ } else {
2122
+ const progV = mint("_prog");
2123
+ lines.push(`${ind2} let ${progV} = false`);
2124
+ lines.push(`${ind2} while (true) {`);
2125
+ lines.push(...inner.setup);
2126
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2127
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2128
+ lines.push(`${ind2} ${progV} = true`);
2129
+ lines.push(`${ind2} }`);
2130
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2131
+ }
2132
+ }
2133
+ first = false;
2134
+ }
2135
+ lines.push(`${ind2} ${okV} = true`);
2136
+ lines.push(`${ind2}} while (false)`);
2137
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2138
+ return { setup: lines, ok: okV, end: endV2 };
2139
+ }
2140
+ if (shape.kind === "litFold") {
2141
+ const openLen2 = shape.open.length;
2142
+ const endV2 = mint("_end");
2143
+ const checks = shape.open.map((cp, k) => {
2144
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2145
+ return foldEq(cVar, cp);
2146
+ });
2147
+ return {
2148
+ setup: [
2149
+ `${ind2}let ${endV2} = ${start}`,
2150
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2151
+ ],
2152
+ ok: `${endV2} > ${start}`,
2153
+ end: endV2
1429
2154
  };
1430
2155
  }
1431
2156
  if (shape.kind === "until") {
2157
+ const j2 = mint("_j");
2158
+ const openLen2 = shape.open.length;
2159
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1432
2160
  return {
1433
2161
  setup: [
1434
- ` let j = _e + ${shape.open.length}`,
1435
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
2162
+ `${ind2}let ${j2} = ${start}`,
2163
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
2164
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
2165
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
2166
+ `${ind2}}`
1436
2167
  ],
1437
- endVar: "j"
2168
+ // An open-until-terminator always completes (stop char or EOF), so a matched
2169
+ // open literal is a full match; `end === start` iff the open didn't match.
2170
+ ok: `${j2} > ${start}`,
2171
+ end: j2
1438
2172
  };
1439
2173
  }
1440
- const n = shape.close.length;
2174
+ if (shape.kind === "string") {
2175
+ const okV = mint("_ok");
2176
+ const endV2 = mint("_end");
2177
+ const i = mint("_i");
2178
+ const c2 = mint("_c");
2179
+ const hard = stringHardStop(shape);
2180
+ const ltLines = shape.escLineTerm ? [] : (() => {
2181
+ const c3 = mint("_c");
2182
+ return [
2183
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
2184
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
2185
+ ];
2186
+ })();
2187
+ return {
2188
+ setup: [
2189
+ `${ind2}let ${okV} = false`,
2190
+ `${ind2}let ${endV2} = ${start}`,
2191
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
2192
+ `${ind2} let ${i} = ${start} + 1`,
2193
+ `${ind2} while (${i} < input.length) {`,
2194
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
2195
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
2196
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
2197
+ `${ind2} if (${i} + 1 >= input.length) break`,
2198
+ ...ltLines,
2199
+ `${ind2} ${i} += 2`,
2200
+ `${ind2} continue`,
2201
+ `${ind2} }`,
2202
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
2203
+ `${ind2} ${i}++`,
2204
+ `${ind2} }`,
2205
+ `${ind2}}`
2206
+ ],
2207
+ ok: okV,
2208
+ end: endV2
2209
+ };
2210
+ }
2211
+ const j = mint("_j");
2212
+ const endV = mint("_end");
2213
+ const openLen = shape.open.length;
2214
+ const closeLen = shape.close.length;
2215
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
2216
+ const closeChk = litCond(j, shape.close);
1441
2217
  return {
1442
2218
  setup: [
1443
- ` let j = _e + ${shape.open.length}`,
1444
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
2219
+ `${ind2}let ${j} = ${start}`,
2220
+ `${ind2}let ${endV} = ${start}`,
2221
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
2222
+ `${ind2} ${j} = ${start} + ${openLen}`,
2223
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
2224
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
2225
+ `${ind2}}`
1445
2226
  ],
1446
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
2227
+ ok: `${endV} > ${start}`,
2228
+ end: endV
1447
2229
  };
1448
2230
  }
1449
- function guardLine(shape) {
1450
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1451
- return ` if (${cond}) {`;
2231
+ function scanBranch(shape, mint) {
2232
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
2233
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1452
2234
  }
1453
- function scanBranch(shape) {
1454
- if (shape.kind === "chars") {
1455
- return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1456
- }
1457
- const { setup, endVar } = advanceExpr(shape);
1458
- return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1459
- }
1460
- function captureChunk(startVar, kindIndex) {
2235
+ function scanBranchLabeled(shape, kindIndex, mint) {
2236
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1461
2237
  return [
2238
+ ...m.setup,
2239
+ ` if (${m.end} > _e) {`,
1462
2240
  ` if (_cap) {`,
1463
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1464
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1465
- ` }`
1466
- ];
1467
- }
1468
- function scanBranchLabeled(shape, kindIndex) {
1469
- const { setup, endVar } = advanceExpr(shape);
1470
- const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1471
- if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1472
- lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1473
- return lines.join("\n");
2241
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
2242
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
2243
+ ` }`,
2244
+ ` _e = ${m.end}`,
2245
+ ` continue`,
2246
+ ` }`
2247
+ ].join("\n");
1474
2248
  }
1475
2249
 
1476
2250
  // src/compiler/trivia-fast-path.ts
2251
+ function makeMint() {
2252
+ let n = 0;
2253
+ return (prefix = "_v") => `${prefix}${n++}`;
2254
+ }
1477
2255
  function unwrapTrivia2(p) {
1478
2256
  let cur = p;
1479
2257
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1490,14 +2268,14 @@ function choiceArms(p) {
1490
2268
  return null;
1491
2269
  }
1492
2270
  function armShape(arm) {
1493
- const src = getCoreRegexDef(arm)?.source;
1494
- return src ? parseScanShape(src) : null;
2271
+ const d = getCoreRegexDef(arm);
2272
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1495
2273
  }
1496
2274
  function analyzeTriviaFastPath(trivia2) {
1497
2275
  const core = unwrapTrivia2(trivia2);
1498
- const directSrc = getCoreRegexDef(core)?.source;
1499
- if (directSrc) {
1500
- const shape = parseScanShape(directSrc);
2276
+ const directDef = getCoreRegexDef(core);
2277
+ if (directDef) {
2278
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1501
2279
  return shape && shape.kind === "chars" ? [shape] : null;
1502
2280
  }
1503
2281
  const inner = oneOrMoreInner(core);
@@ -1521,10 +2299,11 @@ var CAP_RECORD = [
1521
2299
  ` }`
1522
2300
  ].join("\n");
1523
2301
  function composeFastLoop(shapes) {
2302
+ const mint = makeMint();
1524
2303
  return [
1525
2304
  ` while (_e < input.length) {`,
1526
2305
  ` const c = input.charCodeAt(_e)`,
1527
- ...shapes.map(scanBranch),
2306
+ ...shapes.map((s) => scanBranch(s, mint)),
1528
2307
  ` break`,
1529
2308
  ` }`
1530
2309
  ].join("\n");
@@ -1544,20 +2323,21 @@ function analyzeLabeledScannableRun(trivia2) {
1544
2323
  if (!spec) return null;
1545
2324
  const out = [];
1546
2325
  for (const arm of spec.arms) {
1547
- const src = getCoreRegexDef(arm.parser)?.source;
1548
- const shape = src ? parseScanShape(src) : null;
2326
+ const d = getCoreRegexDef(arm.parser);
2327
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1549
2328
  if (!shape) return null;
1550
2329
  out.push({ shape, kindIndex: arm.kindIndex });
1551
2330
  }
1552
2331
  return out;
1553
2332
  }
1554
2333
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
2334
+ const mint = makeMint();
1555
2335
  return [
1556
2336
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1557
2337
  ` let _e = _pos`,
1558
2338
  ` while (_e < input.length) {`,
1559
2339
  ` const c = input.charCodeAt(_e)`,
1560
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
2340
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1561
2341
  ` break`,
1562
2342
  ` }`,
1563
2343
  ` return _e`,
@@ -1634,6 +2414,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1634
2414
  ].join("\n");
1635
2415
  }
1636
2416
 
2417
+ // src/compiler/scannable-terminal.ts
2418
+ function emitScannableTerminal(shape, c) {
2419
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
2420
+ const stmts = [...m.setup];
2421
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
2422
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
2423
+ return { stmts, endVar: m.end };
2424
+ }
2425
+
1637
2426
  // src/compiler/inline-build.ts
1638
2427
  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*$/;
1639
2428
  function escapeRegExp(s) {
@@ -1755,14 +2544,45 @@ function failReturn(expected, posExpr) {
1755
2544
  function failReturnArr(expectedArr, posExpr) {
1756
2545
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1757
2546
  }
2547
+ function hoistExpected(ctx, constArrSource) {
2548
+ let name = ctx.expectedMap.get(constArrSource);
2549
+ if (name === void 0) {
2550
+ name = `_fx${ctx.expectedDecls.length}`;
2551
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2552
+ ctx.expectedMap.set(constArrSource, name);
2553
+ }
2554
+ return name;
2555
+ }
1758
2556
  function failBody(ctx, expected, posExpr) {
1759
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2557
+ if (ctx.failLabel) {
2558
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2559
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
2560
+ }
1760
2561
  return failReturn(expected, posExpr);
1761
2562
  }
1762
- function failArrBody(ctx, expectedArr, posExpr) {
1763
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2563
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
2564
+ if (ctx.failLabel) {
2565
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2566
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
2567
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
2568
+ }
2569
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1764
2570
  return failReturnArr(expectedArr, posExpr);
1765
2571
  }
2572
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
2573
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
2574
+ }
2575
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
2576
+ if (srcCtx !== "_ctx") {
2577
+ if (ctx.failLabel) {
2578
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2579
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
2580
+ }
2581
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
2582
+ }
2583
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
2584
+ return resultFromRecorded();
2585
+ }
1766
2586
  function emitIfFail(ctx, cond, body) {
1767
2587
  return [
1768
2588
  `${ind(ctx)}if (${cond}) {`,
@@ -1798,13 +2618,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1798
2618
  `}`
1799
2619
  ].join("\n"));
1800
2620
  }
1801
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
2621
+ function emitNamedFnCall(ctx, fnName, pos) {
1802
2622
  const vv = v(ctx, "_pfv");
1803
2623
  const ev = v(ctx, "_pfe");
1804
2624
  return {
1805
2625
  stmts: [
1806
2626
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1807
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
2627
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
2628
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
2629
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1808
2630
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1809
2631
  ],
1810
2632
  valueVar: vv,
@@ -1914,6 +2736,27 @@ function firstSetCond(codeVar, fs) {
1914
2736
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1915
2737
  ).join(" || ");
1916
2738
  }
2739
+ var SWITCH_RANGE_LIMIT = 4;
2740
+ var SWITCH_MAX_CASES = 48;
2741
+ var SWITCH_MIN_CASES = 3;
2742
+ var _forceDisjointIf = false;
2743
+ function planDisjointDispatch(parsers) {
2744
+ const cases = [];
2745
+ let total = 0;
2746
+ for (const p of parsers) {
2747
+ const fs = p._meta.firstSet;
2748
+ if (fs.kind !== "ranges") return { kind: "if" };
2749
+ const pts = [];
2750
+ for (const r of fs.ranges) {
2751
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2752
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2753
+ }
2754
+ total += pts.length;
2755
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2756
+ cases.push(pts);
2757
+ }
2758
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2759
+ }
1917
2760
  function captureRestoreBody(mL, mR, mTl, mLg) {
1918
2761
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1919
2762
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -2027,18 +2870,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
2027
2870
  return true;
2028
2871
  }
2029
2872
  }
2030
- function emitFallible(inner, ctx, pos) {
2873
+ function emitFallible(inner, ctx, pos, swallow = false) {
2031
2874
  const lbl = v(ctx, "_lbl");
2032
2875
  const okV = `${lbl}ok`;
2033
2876
  const valV = `${lbl}v`;
2034
2877
  const endV = `${lbl}e`;
2035
2878
  const savedLabel = ctx.failLabel;
2036
2879
  const savedIndent = ctx.indent;
2880
+ const savedRecord = ctx.recordFail;
2037
2881
  ctx.failLabel = lbl;
2038
2882
  ctx.indent = savedIndent + 1;
2883
+ if (swallow) ctx.recordFail = false;
2039
2884
  const r = emit(inner, ctx, pos);
2040
2885
  ctx.failLabel = savedLabel;
2041
2886
  ctx.indent = savedIndent;
2887
+ ctx.recordFail = savedRecord;
2042
2888
  const ind0 = ind(ctx);
2043
2889
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
2044
2890
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2061,6 +2907,7 @@ function emitFallible(inner, ctx, pos) {
2061
2907
  ];
2062
2908
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2063
2909
  }
2910
+ var CHARCODE_CHAIN_MAX = 16;
2064
2911
  function emitLit(def, ctx, pos) {
2065
2912
  const { value, caseInsensitive } = def;
2066
2913
  const len = value.length;
@@ -2083,7 +2930,7 @@ function emitLit(def, ctx, pos) {
2083
2930
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2084
2931
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2085
2932
  );
2086
- } else if (len <= 4) {
2933
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2087
2934
  const checks = Array.from(
2088
2935
  { length: len },
2089
2936
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2105,7 +2952,55 @@ function emitLit(def, ctx, pos) {
2105
2952
  function escapeKeywordRe(s) {
2106
2953
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2107
2954
  }
2955
+ function emitKeywordsFast(def, ctx, pos) {
2956
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
2957
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
2958
+ if (def.caseInsensitive && def.boundary) return null;
2959
+ let boundary = null;
2960
+ if (def.boundary) {
2961
+ let body = def.boundary;
2962
+ const negated = body.startsWith("^");
2963
+ if (negated) body = body.slice(1);
2964
+ const ranges = parseClassRanges(body);
2965
+ if (!ranges) return null;
2966
+ boundary = { ranges, negated };
2967
+ }
2968
+ const mint = (prefix = "_v") => v(ctx, prefix);
2969
+ const lbl = v(ctx, "_kwLbl");
2970
+ const valV = v(ctx, "_kwv");
2971
+ const endV = v(ctx, "_kwe");
2972
+ const bodyInd = ind(ctx) + " ";
2973
+ const tries = [];
2974
+ for (const w of def.words) {
2975
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
2976
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
2977
+ if (boundary) {
2978
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
2979
+ }
2980
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
2981
+ tries.push(
2982
+ ...m.setup,
2983
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
2984
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
2985
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
2986
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
2987
+ );
2988
+ }
2989
+ const stmts = [
2990
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
2991
+ `${ind(ctx)}${lbl}: {`,
2992
+ ...tries,
2993
+ `${ind(ctx)}}`,
2994
+ // Every word has length >= 1 (checked above), so a real match always
2995
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
2996
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
2997
+ ];
2998
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
2999
+ return { stmts, valueVar: valV, endVar: endV };
3000
+ }
2108
3001
  function emitKeywords(def, ctx, pos) {
3002
+ const fast = emitKeywordsFast(def, ctx, pos);
3003
+ if (fast) return fast;
2109
3004
  const alt = def.words.map(escapeKeywordRe).join("|");
2110
3005
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2111
3006
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2130,6 +3025,22 @@ function emitKeywords(def, ctx, pos) {
2130
3025
  return { stmts, valueVar: vv, endVar };
2131
3026
  }
2132
3027
  function emitRegex(def, ctx, pos) {
3028
+ const expectedStr = JSON.stringify(`/${def.source}/`);
3029
+ const shape = scanShapeFromRegex(def.source, def.flags);
3030
+ if (shape) {
3031
+ const vv2 = v(ctx);
3032
+ const scanned = emitScannableTerminal(shape, {
3033
+ ind: ind(ctx),
3034
+ pos,
3035
+ valueVar: vv2,
3036
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
3037
+ fresh: (prefix) => v(ctx, prefix)
3038
+ });
3039
+ if (scanned) {
3040
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
3041
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3042
+ }
3043
+ }
2133
3044
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2134
3045
  const key = `${def.optimizedSource}/${flags}`;
2135
3046
  let rName = ctx.regexMap.get(key);
@@ -2140,7 +3051,6 @@ function emitRegex(def, ctx, pos) {
2140
3051
  }
2141
3052
  const mv = v(ctx, "_m");
2142
3053
  const vv = v(ctx);
2143
- const expectedStr = JSON.stringify(`/${def.source}/`);
2144
3054
  const stmts = [
2145
3055
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2146
3056
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2199,15 +3109,68 @@ function emitSeq(def, ctx, pos) {
2199
3109
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2200
3110
  return { stmts, valueVar: arrV, endVar };
2201
3111
  }
3112
+ function staticExpectedArr(parsers) {
3113
+ return JSON.stringify(parsers.flatMap((p) => {
3114
+ const e = staticExpected(p);
3115
+ return e.length > 0 ? e : [p._tag];
3116
+ }));
3117
+ }
3118
+ function failsAtStart(p) {
3119
+ const d = p._def;
3120
+ switch (d.tag) {
3121
+ case "literal":
3122
+ case "regex":
3123
+ case "keywords":
3124
+ case "guard":
3125
+ case "not":
3126
+ return true;
3127
+ case "transform":
3128
+ case "label":
3129
+ return failsAtStart(d.parser);
3130
+ default:
3131
+ return false;
3132
+ }
3133
+ }
3134
+ function armStaticExpected(ctx, p) {
3135
+ return hoistExpected(ctx, staticExpectedArr([p]));
3136
+ }
3137
+ function canMatchEmptyAtStart(p) {
3138
+ const d = p._def;
3139
+ switch (d.tag) {
3140
+ case "regex":
3141
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3142
+ case "optional":
3143
+ return true;
3144
+ case "many":
3145
+ return true;
3146
+ case "transform":
3147
+ case "label":
3148
+ return canMatchEmptyAtStart(d.parser);
3149
+ case "literal":
3150
+ return d.value.length === 0;
3151
+ default:
3152
+ return false;
3153
+ }
3154
+ }
3155
+ function needsFirstSetGuard(p) {
3156
+ const fs = p._meta.firstSet;
3157
+ return fs.kind !== "any" && !canMatchEmptyAtStart(p);
3158
+ }
3159
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
3160
+ if (failsAtStart(p)) {
3161
+ const r = emit(p, ctx, pos);
3162
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
3163
+ }
3164
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3165
+ return [
3166
+ ...stmts,
3167
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
3168
+ `${ind(ctx)}${valV} = ${valVar}`,
3169
+ `${ind(ctx)}${endV} = ${endVar}`
3170
+ ];
3171
+ }
2202
3172
  function emitChoice(def, ctx, pos) {
2203
- const allExpected = JSON.stringify(
2204
- def.parsers.map((p) => {
2205
- const d = p._def;
2206
- if (d.tag === "literal") return JSON.stringify(d.value);
2207
- if (d.tag === "regex") return `/${d.source}/`;
2208
- return p._tag;
2209
- })
2210
- );
3173
+ const allExpected = staticExpectedArr(def.parsers);
2211
3174
  if (def.disjoint) {
2212
3175
  const codeV = v(ctx, "_code");
2213
3176
  const valV = v(ctx, "_chv");
@@ -2216,6 +3179,27 @@ function emitChoice(def, ctx, pos) {
2216
3179
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2217
3180
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2218
3181
  ];
3182
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
3183
+ if (plan.kind === "switch") {
3184
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
3185
+ ctx.indent++;
3186
+ for (let i = 0; i < def.parsers.length; i++) {
3187
+ const p = def.parsers[i];
3188
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
3189
+ stmts.push(`${ind(ctx)}{`);
3190
+ ctx.indent++;
3191
+ stmts.push(
3192
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
3193
+ `${ind(ctx)}break`
3194
+ );
3195
+ ctx.indent--;
3196
+ stmts.push(`${ind(ctx)}}`);
3197
+ }
3198
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
3199
+ ctx.indent--;
3200
+ stmts.push(`${ind(ctx)}}`);
3201
+ return { stmts, valueVar: valV, endVar: endV };
3202
+ }
2219
3203
  let first = true;
2220
3204
  for (const p of def.parsers) {
2221
3205
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2223,10 +3207,7 @@ function emitChoice(def, ctx, pos) {
2223
3207
  first = false;
2224
3208
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2225
3209
  ctx.indent++;
2226
- const r = emit(p, ctx, pos);
2227
- stmts.push(...r.stmts);
2228
- stmts.push(`${ind(ctx)}${valV} = ${r.valueVar}`);
2229
- stmts.push(`${ind(ctx)}${endV} = ${r.endVar}`);
3210
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2230
3211
  ctx.indent--;
2231
3212
  stmts.push(`${ind(ctx)}}`);
2232
3213
  }
@@ -2235,7 +3216,7 @@ function emitChoice(def, ctx, pos) {
2235
3216
  }
2236
3217
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2237
3218
  }
2238
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
3219
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2239
3220
  const superParser = def.parsers[superIndex];
2240
3221
  const regexDef = getCoreRegexDef(superParser);
2241
3222
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2250,10 +3231,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2250
3231
  const wordV = v(ctx, "_gw");
2251
3232
  const endV = v(ctx, "_ge");
2252
3233
  const valV = v(ctx, "_gcv");
3234
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2253
3235
  const stmts = [
2254
3236
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2255
3237
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2256
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
3238
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2257
3239
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2258
3240
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2259
3241
  `${ind(ctx)}let ${valV}`
@@ -2311,18 +3293,25 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2311
3293
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2312
3294
  return { stmts, valueVar: valV, endVar: endV };
2313
3295
  }
2314
- function emitFirstMatch(def, allExpected, ctx, pos) {
3296
+ function emitFirstMatch(def, ctx, pos) {
2315
3297
  const resValV = v(ctx, "_crv");
2316
3298
  const resEndV = v(ctx, "_cre");
2317
3299
  const resOkV = v(ctx, "_crok");
3300
+ const codeV = v(ctx, "_chcode");
3301
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2318
3302
  const ind0 = ind(ctx);
2319
3303
  const stmts = [
2320
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
3304
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
3305
+ `${ind0}let ${slots.join(", ")}`,
3306
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2321
3307
  ];
2322
3308
  for (let i = 0; i < def.parsers.length; i++) {
2323
3309
  const p = def.parsers[i];
2324
3310
  const gate = def.gates[i];
2325
3311
  const autoNot = def.autoNot[i];
3312
+ const atStart = failsAtStart(p);
3313
+ const staticFx = armStaticExpected(ctx, p);
3314
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2326
3315
  let gateCond = null;
2327
3316
  if (gate) {
2328
3317
  const gateIdx = ctx.mapFns.length;
@@ -2337,49 +3326,53 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2337
3326
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2338
3327
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2339
3328
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3329
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2340
3330
  stmts.push(`${ind0}if (${skipCond}) {`);
2341
- ctx.indent++;
2342
- const ind1 = ind(ctx);
3331
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3332
+ ctx.indent += fsGuard ? 2 : 1;
2343
3333
  if (markLeaves) {
2344
3334
  stmts.push(
2345
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2346
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2347
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2348
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3335
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3336
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3337
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3338
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2349
3339
  );
2350
3340
  }
2351
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3341
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2352
3342
  stmts.push(...armStmts);
2353
- ctx.indent--;
2354
3343
  if (autoNot && autoNot.length > 0) {
2355
3344
  const anCode = v(ctx, "_anc");
2356
3345
  const rejectCond = autoNot.map(
2357
3346
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2358
3347
  ).join(" || ");
2359
- stmts.push(`${ind0} if (${okVar}) {`);
2360
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2361
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2362
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2363
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2364
- stmts.push(`${ind0} ${resOkV} = true`);
2365
- stmts.push(`${ind0} }`);
2366
- stmts.push(`${ind0} }`);
2367
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3348
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3349
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3350
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3351
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3352
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3353
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3354
+ stmts.push(`${ind(ctx)} }`);
3355
+ stmts.push(`${ind(ctx)}}`);
3356
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3357
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2368
3358
  } else {
2369
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2370
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
3359
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3360
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2371
3361
  }
3362
+ ctx.indent -= fsGuard ? 2 : 1;
3363
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2372
3364
  stmts.push(`${ind0}}`);
2373
3365
  }
2374
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
3366
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
3367
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2375
3368
  return { stmts, valueVar: resValV, endVar: resEndV };
2376
3369
  }
2377
3370
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2378
3371
  if (strategy.tag === "greedyClassify")
2379
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
3372
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2380
3373
  if (strategy.tag === "literalsLongestFirst")
2381
3374
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2382
- return emitFirstMatch(def, allExpected, ctx, pos);
3375
+ return emitFirstMatch(def, ctx, pos);
2383
3376
  }
2384
3377
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2385
3378
  const def = p._def;
@@ -2400,7 +3393,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2400
3393
  function emitLiteralCondition(litVal, pos) {
2401
3394
  const len = litVal.length;
2402
3395
  if (len === 0) return "true";
2403
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3396
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2404
3397
  const checks = [`${pos} + ${len} <= input.length`];
2405
3398
  for (let i = 0; i < len; i++) {
2406
3399
  const code = litVal.codePointAt(i);
@@ -2449,7 +3442,7 @@ function emitMany(def, ctx, pos) {
2449
3442
  itemPos = npV;
2450
3443
  }
2451
3444
  }
2452
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3445
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2453
3446
  stmts.push(
2454
3447
  ...iterStmts,
2455
3448
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2463,7 +3456,7 @@ function emitMany(def, ctx, pos) {
2463
3456
  function emitOptional(def, ctx, pos) {
2464
3457
  const valV = v(ctx, "_opt");
2465
3458
  const endV = v(ctx, "_opte");
2466
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3459
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2467
3460
  const ind0 = ind(ctx);
2468
3461
  const stmts = [
2469
3462
  ...lblStmts,
@@ -2475,7 +3468,7 @@ function emitOptional(def, ctx, pos) {
2475
3468
  function emitSepBy(_p, def, ctx, pos) {
2476
3469
  const arrV = v(ctx, "_arr");
2477
3470
  const curV = v(ctx, "_cur");
2478
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3471
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2479
3472
  const stmts = [
2480
3473
  `${ind(ctx)}const ${arrV} = []`,
2481
3474
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2507,11 +3500,11 @@ function emitSepBy(_p, def, ctx, pos) {
2507
3500
  );
2508
3501
  sepAtPos = spV;
2509
3502
  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}; `;
2510
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3503
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2511
3504
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2512
3505
  const npV = v(ctx, "_np");
2513
3506
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2514
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3507
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2515
3508
  stmts.push(
2516
3509
  ...nextStmts,
2517
3510
  // item failed → unwind the separator too, back to the end of the last item
@@ -2524,11 +3517,11 @@ function emitSepBy(_p, def, ctx, pos) {
2524
3517
  const spV = v(ctx, "_sp");
2525
3518
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2526
3519
  sepAtPos = spV;
2527
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3520
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2528
3521
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2529
3522
  const npV = v(ctx, "_np");
2530
3523
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2531
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3524
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2532
3525
  stmts.push(
2533
3526
  ...nextStmts,
2534
3527
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2545,10 +3538,10 @@ function emitSepBy(_p, def, ctx, pos) {
2545
3538
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2546
3539
  );
2547
3540
  }
2548
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3541
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2549
3542
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2550
3543
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2551
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3544
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2552
3545
  stmts.push(
2553
3546
  ...nextStmts,
2554
3547
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -2606,11 +3599,12 @@ function emitScanTo(def, ctx, pos) {
2606
3599
  return { stmts, valueVar: valV, endVar: curV };
2607
3600
  }
2608
3601
  function emitNot(def, ctx, pos) {
2609
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3602
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
3603
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2610
3604
  return {
2611
3605
  stmts: [
2612
3606
  ...stmts,
2613
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
3607
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2614
3608
  ],
2615
3609
  valueVar: "null",
2616
3610
  endVar: pos
@@ -2642,7 +3636,7 @@ function emitNode(def, ctx, pos) {
2642
3636
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2643
3637
  stmts.push(...innerStmts);
2644
3638
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
2645
- stmts.push(...emitIfFail(ctx, `!${okVar}`, failBody(ctx, '"node"', pos)));
3639
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2646
3640
  let stV = "undefined";
2647
3641
  if (clonesState) {
2648
3642
  stV = v(ctx, "_nst");
@@ -2664,9 +3658,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2664
3658
  const rv = v(ctx, "_rt");
2665
3659
  const vv = v(ctx, "_rtv");
2666
3660
  const ev = v(ctx, "_rte");
3661
+ 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 }`;
2667
3662
  const stmts = [
2668
3663
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2669
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
3664
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2670
3665
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2671
3666
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2672
3667
  ];
@@ -2695,15 +3690,18 @@ function emitLazy(p, def, ctx, pos) {
2695
3690
  }
2696
3691
  const savedIndent = ctx.indent;
2697
3692
  const savedFailLabel = ctx.failLabel;
3693
+ const savedRecord = ctx.recordFail;
2698
3694
  ctx.indent = 1;
2699
3695
  ctx.failLabel = "_pfail";
3696
+ ctx.recordFail = true;
2700
3697
  const r = emit(resolved, ctx, "_pos");
2701
3698
  ctx.indent = savedIndent;
2702
3699
  ctx.failLabel = savedFailLabel;
3700
+ ctx.recordFail = savedRecord;
2703
3701
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2704
3702
  }
2705
3703
  const fnName = ctx.namedParsers.get(p);
2706
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
3704
+ return emitNamedFnCall(ctx, fnName, pos);
2707
3705
  }
2708
3706
  function emitRecover(def, ctx, pos) {
2709
3707
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2721,7 +3719,7 @@ function emitRecover(def, ctx, pos) {
2721
3719
  `${ind0} if (${sentOk}) break`,
2722
3720
  `${ind0} ${scanV}++`,
2723
3721
  `${ind0} }`,
2724
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
3722
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2725
3723
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2726
3724
  `${ind0} ${valVar} = ${errV}`,
2727
3725
  `${ind0} ${endVar} = ${scanV}`,
@@ -2879,11 +3877,14 @@ function emit(p, ctx, pos) {
2879
3877
  ctx.namedParsers.set(innerParser, fnName);
2880
3878
  const savedIndent = ctx.indent;
2881
3879
  const savedFailLabel = ctx.failLabel;
3880
+ const savedRecord = ctx.recordFail;
2882
3881
  ctx.indent = 1;
2883
3882
  ctx.failLabel = "_pfail";
3883
+ ctx.recordFail = true;
2884
3884
  const innerR = emit(innerParser, ctx, "_pos");
2885
3885
  ctx.indent = savedIndent;
2886
3886
  ctx.failLabel = savedFailLabel;
3887
+ ctx.recordFail = savedRecord;
2887
3888
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
2888
3889
  }
2889
3890
  const fn = ctx.namedParsers.get(innerParser);
@@ -2894,7 +3895,9 @@ function emit(p, ctx, pos) {
2894
3895
  stmts: [
2895
3896
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2896
3897
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2897
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3898
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3899
+ // and propagate the inner failure verbatim (interpreter parity).
3900
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2898
3901
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2899
3902
  ],
2900
3903
  valueVar: vv,
@@ -2995,7 +3998,7 @@ function analyzeLazyUsageMulti(roots) {
2995
3998
  const recursive = /* @__PURE__ */ new Set();
2996
3999
  const descended = /* @__PURE__ */ new Set();
2997
4000
  const active = /* @__PURE__ */ new Set();
2998
- function walk(p) {
4001
+ function walk2(p) {
2999
4002
  const def = p._def;
3000
4003
  if (def.tag === "lazy") {
3001
4004
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -3012,13 +4015,13 @@ function analyzeLazyUsageMulti(roots) {
3012
4015
  return;
3013
4016
  }
3014
4017
  active.add(p);
3015
- walk(resolved);
4018
+ walk2(resolved);
3016
4019
  active.delete(p);
3017
4020
  return;
3018
4021
  }
3019
- for (const child of childrenOf(def)) walk(child);
4022
+ for (const child of childrenOf(def)) walk2(child);
3020
4023
  }
3021
- for (const root of roots) walk(root);
4024
+ for (const root of roots) walk2(root);
3022
4025
  return { counts, recursive };
3023
4026
  }
3024
4027
  function compile(combinator, mapFnSources) {
@@ -3027,6 +4030,9 @@ function compile(combinator, mapFnSources) {
3027
4030
  indent: 1,
3028
4031
  regexDecls: [],
3029
4032
  regexMap: /* @__PURE__ */ new Map(),
4033
+ expectedDecls: [],
4034
+ expectedMap: /* @__PURE__ */ new Map(),
4035
+ recordFail: true,
3030
4036
  mapFns: [],
3031
4037
  mapFnSrcs: [],
3032
4038
  buildFns: [],
@@ -3048,6 +4054,7 @@ function compile(combinator, mapFnSources) {
3048
4054
  const source = [
3049
4055
  ...emptyTlDecls,
3050
4056
  ...ctx.regexDecls,
4057
+ ...ctx.expectedDecls,
3051
4058
  "",
3052
4059
  ...namedPrelude,
3053
4060
  ctx.namedFnDecls.join("\n\n"),
@@ -3060,6 +4067,7 @@ function compile(combinator, mapFnSources) {
3060
4067
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3061
4068
  ...emptyTlDecls,
3062
4069
  ...ctx.regexDecls,
4070
+ ...ctx.expectedDecls,
3063
4071
  collatorDecl,
3064
4072
  ...namedPrelude,
3065
4073
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3113,12 +4121,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3113
4121
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3114
4122
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3115
4123
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3116
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4124
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3117
4125
  if (!needsWrapper) return innerFn;
3118
4126
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3119
4127
  const hoistedDecls = [
3120
4128
  emptyTlDecl,
3121
4129
  ...ctx.regexDecls.map((d) => ` ${d}`),
4130
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3122
4131
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3123
4132
  mfDecl,
3124
4133
  buildDecl,
@@ -3179,6 +4188,7 @@ function recover(combinator, sentinel) {
3179
4188
  canMatchNewline: true,
3180
4189
  isTrivia: false
3181
4190
  };
4191
+ const expected = staticExpected(combinator);
3182
4192
  return {
3183
4193
  _tag: "recover",
3184
4194
  _meta: meta,
@@ -3194,7 +4204,7 @@ function recover(combinator, sentinel) {
3194
4204
  const error = {
3195
4205
  _tag: "parseError",
3196
4206
  span: { start: pos, end: scanPos },
3197
- expected: result.expected
4207
+ expected
3198
4208
  };
3199
4209
  ctx._errors?.push(error);
3200
4210
  return { ok: true, value: error, span: { start: pos, end: scanPos } };
@@ -3205,58 +4215,6 @@ function isParseError(value) {
3205
4215
  return typeof value === "object" && value !== null && value._tag === "parseError";
3206
4216
  }
3207
4217
 
3208
- // src/combinators/expect.ts
3209
- function staticExpected(c) {
3210
- const def = c._def;
3211
- switch (def.tag) {
3212
- case "literal":
3213
- return [JSON.stringify(def.value)];
3214
- case "regex":
3215
- return [`/${def.source}/`];
3216
- case "keywords":
3217
- return def.words.map((w) => JSON.stringify(w));
3218
- case "label":
3219
- return [def.label];
3220
- case "choice":
3221
- return def.parsers.flatMap(staticExpected);
3222
- case "sequence":
3223
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
3224
- case "node":
3225
- case "grammar":
3226
- case "trivia":
3227
- case "optional":
3228
- case "many":
3229
- case "oneOrMore":
3230
- case "transform":
3231
- case "not":
3232
- return staticExpected(def.parser);
3233
- case "lazy":
3234
- return staticExpected(def.thunk());
3235
- default:
3236
- return [];
3237
- }
3238
- }
3239
- function expect(combinator, label2) {
3240
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
3241
- const meta = {
3242
- firstSet: combinator._meta.firstSet,
3243
- canMatchNewline: combinator._meta.canMatchNewline,
3244
- isTrivia: false
3245
- };
3246
- return {
3247
- _tag: "expect",
3248
- _meta: meta,
3249
- _def: { tag: "expect", parser: combinator, label: label2, expected },
3250
- parse(input, pos, ctx) {
3251
- const result = combinator.parse(input, pos, ctx);
3252
- if (result.ok) return result;
3253
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
3254
- ctx._errors?.push(error);
3255
- return { ok: true, value: error, span: { start: pos, end: pos } };
3256
- }
3257
- };
3258
- }
3259
-
3260
4218
  // src/combinators/completions.ts
3261
4219
  function completionsAt(combinator, input, offset) {
3262
4220
  const probe = { offset, best: null };
@@ -3366,6 +4324,23 @@ function escapeClassChar(ch) {
3366
4324
  return ch.replace(/[\\\]^-]/g, "\\$&");
3367
4325
  }
3368
4326
 
4327
+ // src/combinators/recover-list.ts
4328
+ function tolerantElement(item, sentinel, until) {
4329
+ const el = transform(
4330
+ sequence(not(until), recover(item, sentinel)),
4331
+ ([, value]) => value
4332
+ );
4333
+ if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
4334
+ return el;
4335
+ }
4336
+ function sepByRecover(item, separator, until) {
4337
+ const sentinel = choice(separator, until);
4338
+ return sepBy(tolerantElement(item, sentinel, until), separator);
4339
+ }
4340
+ function manyRecover(item, until) {
4341
+ return many(tolerantElement(item, until, until));
4342
+ }
4343
+
3369
4344
  // src/functional/doc.ts
3370
4345
  function isNode(x) {
3371
4346
  return typeof x === "object" && x !== null && x._tag === "node";
@@ -3427,7 +4402,7 @@ function graftAndShift(root, path, newNode, delta) {
3427
4402
  children: newChildren
3428
4403
  };
3429
4404
  }
3430
- var FunctionalDocImpl = class _FunctionalDocImpl {
4405
+ var ParseDocImpl = class _ParseDocImpl {
3431
4406
  _registry;
3432
4407
  _rootRule;
3433
4408
  _opts;
@@ -3444,7 +4419,7 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3444
4419
  }
3445
4420
  edit(from, to, replacement) {
3446
4421
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
3447
- const reparse = () => makeFunctionalDoc(this._registry, this._rootRule, newInput, this._opts);
4422
+ const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
3448
4423
  if (!this.tree) return reparse();
3449
4424
  const delta = replacement.length - (to - from);
3450
4425
  const found = findContaining(this.tree, from);
@@ -3466,25 +4441,25 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3466
4441
  if (r.span.end === node2.span.end + delta) {
3467
4442
  if (delta === 0) {
3468
4443
  const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
3469
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
4444
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3470
4445
  }
3471
4446
  if (this._opts.rebuild) return reparse();
3472
4447
  const newTree = graftAndShift(this.tree, path, r.value, delta);
3473
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
4448
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
3474
4449
  }
3475
4450
  }
3476
4451
  return reparse();
3477
4452
  }
3478
4453
  };
3479
- function makeFunctionalDoc(registry, rootRule, input, opts = {}) {
4454
+ function parseDoc(registry, rootRule, input, opts = {}) {
3480
4455
  const ruleFn = registry[rootRule];
3481
4456
  if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
3482
4457
  const ctx = { trackLines: false, state: opts.state };
3483
4458
  const r = ruleFn(input, 0, ctx);
3484
4459
  if (r.ok) {
3485
- return new FunctionalDocImpl(registry, rootRule, opts, r.value, [], input);
4460
+ return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
3486
4461
  }
3487
- return new FunctionalDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
4462
+ return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
3488
4463
  }
3489
4464
 
3490
4465
  // src/cst/trivia-index.ts
@@ -3553,6 +4528,41 @@ function buildTriviaIndex(root, input = "", opts) {
3553
4528
  return { before, after };
3554
4529
  }
3555
4530
 
4531
+ // src/cst/walk.ts
4532
+ function walk(root, visitor, ctx = void 0) {
4533
+ const go = (node2, parent) => {
4534
+ const descend = visitor.enter ? visitor.enter(node2, parent, ctx) : void 0;
4535
+ const children = node2.children;
4536
+ if (descend !== false && Array.isArray(children)) {
4537
+ for (const child of children) go(child, node2);
4538
+ }
4539
+ visitor.leave?.(node2, parent, ctx);
4540
+ };
4541
+ go(root, null);
4542
+ }
4543
+ function createVisitor(handlers) {
4544
+ const api = {
4545
+ visit(node2) {
4546
+ const handler = node2.type !== void 0 ? handlers[node2.type] : void 0;
4547
+ if (handler) return handler(node2, api);
4548
+ api.visitChildren(node2);
4549
+ return void 0;
4550
+ },
4551
+ visitChildren(node2) {
4552
+ const out = [];
4553
+ const children = node2.children;
4554
+ if (Array.isArray(children)) {
4555
+ for (const child of children) {
4556
+ const r = api.visit(child);
4557
+ if (r !== void 0) out.push(r);
4558
+ }
4559
+ }
4560
+ return out;
4561
+ }
4562
+ };
4563
+ return (node2) => api.visit(node2);
4564
+ }
4565
+
3556
4566
  // src/cst/trivia-entries.ts
3557
4567
  function entryOffset(i, stride) {
3558
4568
  return i * stride;
@@ -3594,15 +4604,16 @@ export {
3594
4604
  choice,
3595
4605
  compile,
3596
4606
  completionsAt,
4607
+ createVisitor,
3597
4608
  expect,
3598
4609
  guard,
3599
4610
  isParseError,
3600
4611
  keywords,
3601
4612
  label,
3602
4613
  literal,
3603
- makeFunctionalDoc,
3604
4614
  makeWord,
3605
4615
  many,
4616
+ manyRecover,
3606
4617
  noTrivia,
3607
4618
  node,
3608
4619
  not,
@@ -3610,6 +4621,7 @@ export {
3610
4621
  oneOrMore,
3611
4622
  optional,
3612
4623
  parse,
4624
+ parseDoc,
3613
4625
  parser,
3614
4626
  recover,
3615
4627
  ref,
@@ -3617,12 +4629,14 @@ export {
3617
4629
  rules,
3618
4630
  scanTo,
3619
4631
  sepBy,
4632
+ sepByRecover,
3620
4633
  sequence,
3621
4634
  skip,
3622
4635
  staticExpected,
3623
4636
  transform,
3624
4637
  trivia,
3625
4638
  triviaEntries,
4639
+ walk,
3626
4640
  withCtx,
3627
4641
  word
3628
4642
  };