parseman 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,108 @@ 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
+ function shorthandRanges(ch) {
1403
+ return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1404
+ }
1346
1405
  function parseClassRanges(body) {
1347
1406
  const ranges = [];
1348
1407
  let i = 0;
1349
- const readCp = () => {
1408
+ const readAtom = () => {
1350
1409
  const ch = body[i];
1351
1410
  if (ch === void 0) return null;
1352
1411
  if (ch === "\\") {
1353
1412
  const e = body[i + 1];
1354
1413
  if (e === void 0) return null;
1355
1414
  i += 2;
1356
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1415
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1416
+ if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1417
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1418
+ return { cp: e.codePointAt(0) };
1357
1419
  }
1358
1420
  i += ch.length;
1359
- return ch.codePointAt(0);
1421
+ return { cp: ch.codePointAt(0) };
1360
1422
  };
1361
1423
  while (i < body.length) {
1362
- const lo = readCp();
1424
+ const lo = readAtom();
1363
1425
  if (lo === null) return null;
1426
+ if ("set" in lo) {
1427
+ ranges.push(...lo.set);
1428
+ continue;
1429
+ }
1364
1430
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1365
1431
  i += 1;
1366
- const hi = readCp();
1367
- if (hi === null) return null;
1368
- ranges.push([lo, hi]);
1432
+ const hi = readAtom();
1433
+ if (hi === null || "set" in hi) return null;
1434
+ ranges.push([lo.cp, hi.cp]);
1369
1435
  } else {
1370
- ranges.push([lo, lo]);
1436
+ ranges.push([lo.cp, lo.cp]);
1371
1437
  }
1372
1438
  }
1373
1439
  return ranges.length ? ranges : null;
1374
1440
  }
1441
+ function classToRanges(cls) {
1442
+ if (cls === "\\d") return shorthandRanges("d");
1443
+ if (cls === "\\w") return shorthandRanges("w");
1444
+ const body = cls.slice(1, -1);
1445
+ if (body.startsWith("^")) return null;
1446
+ return parseClassRanges(body);
1447
+ }
1375
1448
  function literalCodePoints(frag) {
1376
1449
  const out = [];
1377
1450
  let i = 0;
@@ -1390,13 +1463,53 @@ function literalCodePoints(frag) {
1390
1463
  }
1391
1464
  return out.length ? out : null;
1392
1465
  }
1466
+ function parseStringShape(source) {
1467
+ const q = source[0];
1468
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1469
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1470
+ const inner = source.slice(1, -1);
1471
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1472
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1473
+ let body;
1474
+ let esc;
1475
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1476
+ if (m) {
1477
+ body = m[1];
1478
+ esc = m[2];
1479
+ } else {
1480
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1481
+ if (m) {
1482
+ esc = m[1];
1483
+ body = m[2];
1484
+ }
1485
+ }
1486
+ if (body === void 0 || esc === void 0) return null;
1487
+ const excluded = parseClassRanges(body);
1488
+ if (!excluded) return null;
1489
+ const qcp = q.codePointAt(0);
1490
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1491
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1492
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1493
+ }
1393
1494
  function parseScanShape(source) {
1394
1495
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1395
1496
  if (m) {
1396
1497
  if (m[1].startsWith("^")) return null;
1397
1498
  const ranges = parseClassRanges(m[1]);
1398
- return ranges ? { kind: "chars", ranges } : null;
1499
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1500
+ }
1501
+ m = /^\\([dw])([+*])$/.exec(source);
1502
+ if (m) {
1503
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1504
+ }
1505
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
1506
+ if (m) {
1507
+ const head = classToRanges(m[1]);
1508
+ const tail = classToRanges(m[2]);
1509
+ return head && tail ? { kind: "ident", head, tail } : null;
1399
1510
  }
1511
+ const str = parseStringShape(source);
1512
+ if (str) return str;
1400
1513
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1401
1514
  if (m) {
1402
1515
  const open = literalCodePoints(m[1]);
@@ -1414,66 +1527,147 @@ function parseScanShape(source) {
1414
1527
  }
1415
1528
  return null;
1416
1529
  }
1530
+ function scanShapeFromRegex(source, flags) {
1531
+ if (/[imsu]/.test(flags)) return null;
1532
+ return parseScanShape(source);
1533
+ }
1417
1534
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1418
1535
  var litCond = (base, cps, firstVar) => cps.map(
1419
1536
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1420
1537
  ).join(" && ");
1421
- function advanceExpr(shape) {
1538
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
1539
+ function stringHardStop(shape) {
1540
+ return shape.excluded.filter(
1541
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
1542
+ );
1543
+ }
1544
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1545
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1422
1546
  if (shape.kind === "chars") {
1547
+ const cur = mint("_e");
1548
+ return {
1549
+ setup: [
1550
+ `${ind2}let ${cur} = ${start}`,
1551
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1552
+ ],
1553
+ // `*` always matches (possibly empty); `+` needs at least one char.
1554
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
1555
+ end: cur
1556
+ };
1557
+ }
1558
+ if (shape.kind === "ident") {
1559
+ const cur = mint("_e");
1423
1560
  return {
1424
1561
  setup: [
1425
- ` _e++`,
1426
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1562
+ `${ind2}let ${cur} = ${start}`,
1563
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
1564
+ `${ind2} ${cur} = ${start} + 1`,
1565
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
1566
+ `${ind2}}`
1427
1567
  ],
1428
- endVar: "_e"
1568
+ ok: `${cur} > ${start}`,
1569
+ end: cur
1429
1570
  };
1430
1571
  }
1431
1572
  if (shape.kind === "until") {
1573
+ const j2 = mint("_j");
1574
+ const openLen2 = shape.open.length;
1575
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1576
+ return {
1577
+ setup: [
1578
+ `${ind2}let ${j2} = ${start}`,
1579
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
1580
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
1581
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
1582
+ `${ind2}}`
1583
+ ],
1584
+ // An open-until-terminator always completes (stop char or EOF), so a matched
1585
+ // open literal is a full match; `end === start` iff the open didn't match.
1586
+ ok: `${j2} > ${start}`,
1587
+ end: j2
1588
+ };
1589
+ }
1590
+ if (shape.kind === "string") {
1591
+ const okV = mint("_ok");
1592
+ const endV2 = mint("_end");
1593
+ const i = mint("_i");
1594
+ const c2 = mint("_c");
1595
+ const hard = stringHardStop(shape);
1596
+ const ltLines = shape.escLineTerm ? [] : (() => {
1597
+ const c3 = mint("_c");
1598
+ return [
1599
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
1600
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
1601
+ ];
1602
+ })();
1432
1603
  return {
1433
1604
  setup: [
1434
- ` let j = _e + ${shape.open.length}`,
1435
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1605
+ `${ind2}let ${okV} = false`,
1606
+ `${ind2}let ${endV2} = ${start}`,
1607
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
1608
+ `${ind2} let ${i} = ${start} + 1`,
1609
+ `${ind2} while (${i} < input.length) {`,
1610
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
1611
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
1612
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
1613
+ `${ind2} if (${i} + 1 >= input.length) break`,
1614
+ ...ltLines,
1615
+ `${ind2} ${i} += 2`,
1616
+ `${ind2} continue`,
1617
+ `${ind2} }`,
1618
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
1619
+ `${ind2} ${i}++`,
1620
+ `${ind2} }`,
1621
+ `${ind2}}`
1436
1622
  ],
1437
- endVar: "j"
1623
+ ok: okV,
1624
+ end: endV2
1438
1625
  };
1439
1626
  }
1440
- const n = shape.close.length;
1627
+ const j = mint("_j");
1628
+ const endV = mint("_end");
1629
+ const openLen = shape.open.length;
1630
+ const closeLen = shape.close.length;
1631
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1632
+ const closeChk = litCond(j, shape.close);
1441
1633
  return {
1442
1634
  setup: [
1443
- ` let j = _e + ${shape.open.length}`,
1444
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1635
+ `${ind2}let ${j} = ${start}`,
1636
+ `${ind2}let ${endV} = ${start}`,
1637
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
1638
+ `${ind2} ${j} = ${start} + ${openLen}`,
1639
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
1640
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
1641
+ `${ind2}}`
1445
1642
  ],
1446
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1643
+ ok: `${endV} > ${start}`,
1644
+ end: endV
1447
1645
  };
1448
1646
  }
1449
- function guardLine(shape) {
1450
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1451
- return ` if (${cond}) {`;
1647
+ function scanBranch(shape, mint) {
1648
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1649
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1452
1650
  }
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) {
1651
+ function scanBranchLabeled(shape, kindIndex, mint) {
1652
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1461
1653
  return [
1654
+ ...m.setup,
1655
+ ` if (${m.end} > _e) {`,
1462
1656
  ` 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");
1657
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
1658
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1659
+ ` }`,
1660
+ ` _e = ${m.end}`,
1661
+ ` continue`,
1662
+ ` }`
1663
+ ].join("\n");
1474
1664
  }
1475
1665
 
1476
1666
  // src/compiler/trivia-fast-path.ts
1667
+ function makeMint() {
1668
+ let n = 0;
1669
+ return (prefix = "_v") => `${prefix}${n++}`;
1670
+ }
1477
1671
  function unwrapTrivia2(p) {
1478
1672
  let cur = p;
1479
1673
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1490,14 +1684,14 @@ function choiceArms(p) {
1490
1684
  return null;
1491
1685
  }
1492
1686
  function armShape(arm) {
1493
- const src = getCoreRegexDef(arm)?.source;
1494
- return src ? parseScanShape(src) : null;
1687
+ const d = getCoreRegexDef(arm);
1688
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1495
1689
  }
1496
1690
  function analyzeTriviaFastPath(trivia2) {
1497
1691
  const core = unwrapTrivia2(trivia2);
1498
- const directSrc = getCoreRegexDef(core)?.source;
1499
- if (directSrc) {
1500
- const shape = parseScanShape(directSrc);
1692
+ const directDef = getCoreRegexDef(core);
1693
+ if (directDef) {
1694
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1501
1695
  return shape && shape.kind === "chars" ? [shape] : null;
1502
1696
  }
1503
1697
  const inner = oneOrMoreInner(core);
@@ -1521,10 +1715,11 @@ var CAP_RECORD = [
1521
1715
  ` }`
1522
1716
  ].join("\n");
1523
1717
  function composeFastLoop(shapes) {
1718
+ const mint = makeMint();
1524
1719
  return [
1525
1720
  ` while (_e < input.length) {`,
1526
1721
  ` const c = input.charCodeAt(_e)`,
1527
- ...shapes.map(scanBranch),
1722
+ ...shapes.map((s) => scanBranch(s, mint)),
1528
1723
  ` break`,
1529
1724
  ` }`
1530
1725
  ].join("\n");
@@ -1544,20 +1739,21 @@ function analyzeLabeledScannableRun(trivia2) {
1544
1739
  if (!spec) return null;
1545
1740
  const out = [];
1546
1741
  for (const arm of spec.arms) {
1547
- const src = getCoreRegexDef(arm.parser)?.source;
1548
- const shape = src ? parseScanShape(src) : null;
1742
+ const d = getCoreRegexDef(arm.parser);
1743
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1549
1744
  if (!shape) return null;
1550
1745
  out.push({ shape, kindIndex: arm.kindIndex });
1551
1746
  }
1552
1747
  return out;
1553
1748
  }
1554
1749
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1750
+ const mint = makeMint();
1555
1751
  return [
1556
1752
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1557
1753
  ` let _e = _pos`,
1558
1754
  ` while (_e < input.length) {`,
1559
1755
  ` const c = input.charCodeAt(_e)`,
1560
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1756
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1561
1757
  ` break`,
1562
1758
  ` }`,
1563
1759
  ` return _e`,
@@ -1634,6 +1830,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1634
1830
  ].join("\n");
1635
1831
  }
1636
1832
 
1833
+ // src/compiler/scannable-terminal.ts
1834
+ function emitScannableTerminal(shape, c) {
1835
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
1836
+ const stmts = [...m.setup];
1837
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
1838
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
1839
+ return { stmts, endVar: m.end };
1840
+ }
1841
+
1637
1842
  // src/compiler/inline-build.ts
1638
1843
  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
1844
  function escapeRegExp(s) {
@@ -1755,14 +1960,45 @@ function failReturn(expected, posExpr) {
1755
1960
  function failReturnArr(expectedArr, posExpr) {
1756
1961
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1757
1962
  }
1963
+ function hoistExpected(ctx, constArrSource) {
1964
+ let name = ctx.expectedMap.get(constArrSource);
1965
+ if (name === void 0) {
1966
+ name = `_fx${ctx.expectedDecls.length}`;
1967
+ ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
1968
+ ctx.expectedMap.set(constArrSource, name);
1969
+ }
1970
+ return name;
1971
+ }
1758
1972
  function failBody(ctx, expected, posExpr) {
1759
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
1973
+ if (ctx.failLabel) {
1974
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1975
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
1976
+ }
1760
1977
  return failReturn(expected, posExpr);
1761
1978
  }
1762
- function failArrBody(ctx, expectedArr, posExpr) {
1763
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
1979
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
1980
+ if (ctx.failLabel) {
1981
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1982
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
1983
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
1984
+ }
1985
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1764
1986
  return failReturnArr(expectedArr, posExpr);
1765
1987
  }
1988
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
1989
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
1990
+ }
1991
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
1992
+ if (srcCtx !== "_ctx") {
1993
+ if (ctx.failLabel) {
1994
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1995
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
1996
+ }
1997
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
1998
+ }
1999
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
2000
+ return resultFromRecorded();
2001
+ }
1766
2002
  function emitIfFail(ctx, cond, body) {
1767
2003
  return [
1768
2004
  `${ind(ctx)}if (${cond}) {`,
@@ -1798,13 +2034,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1798
2034
  `}`
1799
2035
  ].join("\n"));
1800
2036
  }
1801
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
2037
+ function emitNamedFnCall(ctx, fnName, pos) {
1802
2038
  const vv = v(ctx, "_pfv");
1803
2039
  const ev = v(ctx, "_pfe");
1804
2040
  return {
1805
2041
  stmts: [
1806
2042
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1807
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
2043
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
2044
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
2045
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1808
2046
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1809
2047
  ],
1810
2048
  valueVar: vv,
@@ -1914,6 +2152,27 @@ function firstSetCond(codeVar, fs) {
1914
2152
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1915
2153
  ).join(" || ");
1916
2154
  }
2155
+ var SWITCH_RANGE_LIMIT = 4;
2156
+ var SWITCH_MAX_CASES = 48;
2157
+ var SWITCH_MIN_CASES = 3;
2158
+ var _forceDisjointIf = false;
2159
+ function planDisjointDispatch(parsers) {
2160
+ const cases = [];
2161
+ let total = 0;
2162
+ for (const p of parsers) {
2163
+ const fs = p._meta.firstSet;
2164
+ if (fs.kind !== "ranges") return { kind: "if" };
2165
+ const pts = [];
2166
+ for (const r of fs.ranges) {
2167
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2168
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2169
+ }
2170
+ total += pts.length;
2171
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2172
+ cases.push(pts);
2173
+ }
2174
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2175
+ }
1917
2176
  function captureRestoreBody(mL, mR, mTl, mLg) {
1918
2177
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1919
2178
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -2130,6 +2389,22 @@ function emitKeywords(def, ctx, pos) {
2130
2389
  return { stmts, valueVar: vv, endVar };
2131
2390
  }
2132
2391
  function emitRegex(def, ctx, pos) {
2392
+ const expectedStr = JSON.stringify(`/${def.source}/`);
2393
+ const shape = scanShapeFromRegex(def.source, def.flags);
2394
+ if (shape) {
2395
+ const vv2 = v(ctx);
2396
+ const scanned = emitScannableTerminal(shape, {
2397
+ ind: ind(ctx),
2398
+ pos,
2399
+ valueVar: vv2,
2400
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
2401
+ fresh: (prefix) => v(ctx, prefix)
2402
+ });
2403
+ if (scanned) {
2404
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
2405
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
2406
+ }
2407
+ }
2133
2408
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2134
2409
  const key = `${def.optimizedSource}/${flags}`;
2135
2410
  let rName = ctx.regexMap.get(key);
@@ -2140,7 +2415,6 @@ function emitRegex(def, ctx, pos) {
2140
2415
  }
2141
2416
  const mv = v(ctx, "_m");
2142
2417
  const vv = v(ctx);
2143
- const expectedStr = JSON.stringify(`/${def.source}/`);
2144
2418
  const stmts = [
2145
2419
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2146
2420
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2199,15 +2473,43 @@ function emitSeq(def, ctx, pos) {
2199
2473
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2200
2474
  return { stmts, valueVar: arrV, endVar };
2201
2475
  }
2476
+ function staticExpectedArr(parsers) {
2477
+ return JSON.stringify(parsers.flatMap((p) => {
2478
+ const e = staticExpected(p);
2479
+ return e.length > 0 ? e : [p._tag];
2480
+ }));
2481
+ }
2482
+ function failsAtStart(p) {
2483
+ const d = p._def;
2484
+ switch (d.tag) {
2485
+ case "literal":
2486
+ case "regex":
2487
+ case "keywords":
2488
+ case "guard":
2489
+ case "not":
2490
+ return true;
2491
+ case "transform":
2492
+ case "label":
2493
+ return failsAtStart(d.parser);
2494
+ default:
2495
+ return false;
2496
+ }
2497
+ }
2498
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
2499
+ if (failsAtStart(p)) {
2500
+ const r = emit(p, ctx, pos);
2501
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
2502
+ }
2503
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
2504
+ return [
2505
+ ...stmts,
2506
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
2507
+ `${ind(ctx)}${valV} = ${valVar}`,
2508
+ `${ind(ctx)}${endV} = ${endVar}`
2509
+ ];
2510
+ }
2202
2511
  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
- );
2512
+ const allExpected = staticExpectedArr(def.parsers);
2211
2513
  if (def.disjoint) {
2212
2514
  const codeV = v(ctx, "_code");
2213
2515
  const valV = v(ctx, "_chv");
@@ -2216,6 +2518,27 @@ function emitChoice(def, ctx, pos) {
2216
2518
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2217
2519
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2218
2520
  ];
2521
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
2522
+ if (plan.kind === "switch") {
2523
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
2524
+ ctx.indent++;
2525
+ for (let i = 0; i < def.parsers.length; i++) {
2526
+ const p = def.parsers[i];
2527
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
2528
+ stmts.push(`${ind(ctx)}{`);
2529
+ ctx.indent++;
2530
+ stmts.push(
2531
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
2532
+ `${ind(ctx)}break`
2533
+ );
2534
+ ctx.indent--;
2535
+ stmts.push(`${ind(ctx)}}`);
2536
+ }
2537
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
2538
+ ctx.indent--;
2539
+ stmts.push(`${ind(ctx)}}`);
2540
+ return { stmts, valueVar: valV, endVar: endV };
2541
+ }
2219
2542
  let first = true;
2220
2543
  for (const p of def.parsers) {
2221
2544
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2223,10 +2546,7 @@ function emitChoice(def, ctx, pos) {
2223
2546
  first = false;
2224
2547
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2225
2548
  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}`);
2549
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2230
2550
  ctx.indent--;
2231
2551
  stmts.push(`${ind(ctx)}}`);
2232
2552
  }
@@ -2235,7 +2555,7 @@ function emitChoice(def, ctx, pos) {
2235
2555
  }
2236
2556
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2237
2557
  }
2238
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2558
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2239
2559
  const superParser = def.parsers[superIndex];
2240
2560
  const regexDef = getCoreRegexDef(superParser);
2241
2561
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2250,10 +2570,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2250
2570
  const wordV = v(ctx, "_gw");
2251
2571
  const endV = v(ctx, "_ge");
2252
2572
  const valV = v(ctx, "_gcv");
2573
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2253
2574
  const stmts = [
2254
2575
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2255
2576
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2256
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
2577
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2257
2578
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2258
2579
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2259
2580
  `${ind(ctx)}let ${valV}`
@@ -2311,13 +2632,15 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2311
2632
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2312
2633
  return { stmts, valueVar: valV, endVar: endV };
2313
2634
  }
2314
- function emitFirstMatch(def, allExpected, ctx, pos) {
2635
+ function emitFirstMatch(def, ctx, pos) {
2315
2636
  const resValV = v(ctx, "_crv");
2316
2637
  const resEndV = v(ctx, "_cre");
2317
2638
  const resOkV = v(ctx, "_crok");
2639
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2318
2640
  const ind0 = ind(ctx);
2319
2641
  const stmts = [
2320
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
2642
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2643
+ `${ind0}let ${slots.join(", ")}`
2321
2644
  ];
2322
2645
  for (let i = 0; i < def.parsers.length; i++) {
2323
2646
  const p = def.parsers[i];
@@ -2364,22 +2687,24 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2364
2687
  stmts.push(`${ind0} ${resOkV} = true`);
2365
2688
  stmts.push(`${ind0} }`);
2366
2689
  stmts.push(`${ind0} }`);
2690
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2367
2691
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2368
2692
  } else {
2369
2693
  stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2370
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2694
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
2371
2695
  }
2372
2696
  stmts.push(`${ind0}}`);
2373
2697
  }
2374
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
2698
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
2699
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2375
2700
  return { stmts, valueVar: resValV, endVar: resEndV };
2376
2701
  }
2377
2702
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2378
2703
  if (strategy.tag === "greedyClassify")
2379
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
2704
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2380
2705
  if (strategy.tag === "literalsLongestFirst")
2381
2706
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2382
- return emitFirstMatch(def, allExpected, ctx, pos);
2707
+ return emitFirstMatch(def, ctx, pos);
2383
2708
  }
2384
2709
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2385
2710
  const def = p._def;
@@ -2607,10 +2932,11 @@ function emitScanTo(def, ctx, pos) {
2607
2932
  }
2608
2933
  function emitNot(def, ctx, pos) {
2609
2934
  const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
2935
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2610
2936
  return {
2611
2937
  stmts: [
2612
2938
  ...stmts,
2613
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
2939
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2614
2940
  ],
2615
2941
  valueVar: "null",
2616
2942
  endVar: pos
@@ -2642,7 +2968,7 @@ function emitNode(def, ctx, pos) {
2642
2968
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2643
2969
  stmts.push(...innerStmts);
2644
2970
  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)));
2971
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2646
2972
  let stV = "undefined";
2647
2973
  if (clonesState) {
2648
2974
  stV = v(ctx, "_nst");
@@ -2664,9 +2990,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2664
2990
  const rv = v(ctx, "_rt");
2665
2991
  const vv = v(ctx, "_rtv");
2666
2992
  const ev = v(ctx, "_rte");
2993
+ 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
2994
  const stmts = [
2668
2995
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2669
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
2996
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2670
2997
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2671
2998
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2672
2999
  ];
@@ -2703,7 +3030,7 @@ function emitLazy(p, def, ctx, pos) {
2703
3030
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2704
3031
  }
2705
3032
  const fnName = ctx.namedParsers.get(p);
2706
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
3033
+ return emitNamedFnCall(ctx, fnName, pos);
2707
3034
  }
2708
3035
  function emitRecover(def, ctx, pos) {
2709
3036
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2721,7 +3048,7 @@ function emitRecover(def, ctx, pos) {
2721
3048
  `${ind0} if (${sentOk}) break`,
2722
3049
  `${ind0} ${scanV}++`,
2723
3050
  `${ind0} }`,
2724
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
3051
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2725
3052
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2726
3053
  `${ind0} ${valVar} = ${errV}`,
2727
3054
  `${ind0} ${endVar} = ${scanV}`,
@@ -2894,7 +3221,9 @@ function emit(p, ctx, pos) {
2894
3221
  stmts: [
2895
3222
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2896
3223
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2897
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3224
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3225
+ // and propagate the inner failure verbatim (interpreter parity).
3226
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2898
3227
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2899
3228
  ],
2900
3229
  valueVar: vv,
@@ -2995,7 +3324,7 @@ function analyzeLazyUsageMulti(roots) {
2995
3324
  const recursive = /* @__PURE__ */ new Set();
2996
3325
  const descended = /* @__PURE__ */ new Set();
2997
3326
  const active = /* @__PURE__ */ new Set();
2998
- function walk(p) {
3327
+ function walk2(p) {
2999
3328
  const def = p._def;
3000
3329
  if (def.tag === "lazy") {
3001
3330
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -3012,13 +3341,13 @@ function analyzeLazyUsageMulti(roots) {
3012
3341
  return;
3013
3342
  }
3014
3343
  active.add(p);
3015
- walk(resolved);
3344
+ walk2(resolved);
3016
3345
  active.delete(p);
3017
3346
  return;
3018
3347
  }
3019
- for (const child of childrenOf(def)) walk(child);
3348
+ for (const child of childrenOf(def)) walk2(child);
3020
3349
  }
3021
- for (const root of roots) walk(root);
3350
+ for (const root of roots) walk2(root);
3022
3351
  return { counts, recursive };
3023
3352
  }
3024
3353
  function compile(combinator, mapFnSources) {
@@ -3027,6 +3356,8 @@ function compile(combinator, mapFnSources) {
3027
3356
  indent: 1,
3028
3357
  regexDecls: [],
3029
3358
  regexMap: /* @__PURE__ */ new Map(),
3359
+ expectedDecls: [],
3360
+ expectedMap: /* @__PURE__ */ new Map(),
3030
3361
  mapFns: [],
3031
3362
  mapFnSrcs: [],
3032
3363
  buildFns: [],
@@ -3048,6 +3379,7 @@ function compile(combinator, mapFnSources) {
3048
3379
  const source = [
3049
3380
  ...emptyTlDecls,
3050
3381
  ...ctx.regexDecls,
3382
+ ...ctx.expectedDecls,
3051
3383
  "",
3052
3384
  ...namedPrelude,
3053
3385
  ctx.namedFnDecls.join("\n\n"),
@@ -3060,6 +3392,7 @@ function compile(combinator, mapFnSources) {
3060
3392
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3061
3393
  ...emptyTlDecls,
3062
3394
  ...ctx.regexDecls,
3395
+ ...ctx.expectedDecls,
3063
3396
  collatorDecl,
3064
3397
  ...namedPrelude,
3065
3398
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3113,12 +3446,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3113
3446
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3114
3447
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3115
3448
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3116
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3449
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3117
3450
  if (!needsWrapper) return innerFn;
3118
3451
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3119
3452
  const hoistedDecls = [
3120
3453
  emptyTlDecl,
3121
3454
  ...ctx.regexDecls.map((d) => ` ${d}`),
3455
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3122
3456
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3123
3457
  mfDecl,
3124
3458
  buildDecl,
@@ -3179,6 +3513,7 @@ function recover(combinator, sentinel) {
3179
3513
  canMatchNewline: true,
3180
3514
  isTrivia: false
3181
3515
  };
3516
+ const expected = staticExpected(combinator);
3182
3517
  return {
3183
3518
  _tag: "recover",
3184
3519
  _meta: meta,
@@ -3194,7 +3529,7 @@ function recover(combinator, sentinel) {
3194
3529
  const error = {
3195
3530
  _tag: "parseError",
3196
3531
  span: { start: pos, end: scanPos },
3197
- expected: result.expected
3532
+ expected
3198
3533
  };
3199
3534
  ctx._errors?.push(error);
3200
3535
  return { ok: true, value: error, span: { start: pos, end: scanPos } };
@@ -3205,58 +3540,6 @@ function isParseError(value) {
3205
3540
  return typeof value === "object" && value !== null && value._tag === "parseError";
3206
3541
  }
3207
3542
 
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
3543
  // src/combinators/completions.ts
3261
3544
  function completionsAt(combinator, input, offset) {
3262
3545
  const probe = { offset, best: null };
@@ -3366,6 +3649,23 @@ function escapeClassChar(ch) {
3366
3649
  return ch.replace(/[\\\]^-]/g, "\\$&");
3367
3650
  }
3368
3651
 
3652
+ // src/combinators/recover-list.ts
3653
+ function tolerantElement(item, sentinel, until) {
3654
+ const el = transform(
3655
+ sequence(not(until), recover(item, sentinel)),
3656
+ ([, value]) => value
3657
+ );
3658
+ if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
3659
+ return el;
3660
+ }
3661
+ function sepByRecover(item, separator, until) {
3662
+ const sentinel = choice(separator, until);
3663
+ return sepBy(tolerantElement(item, sentinel, until), separator);
3664
+ }
3665
+ function manyRecover(item, until) {
3666
+ return many(tolerantElement(item, until, until));
3667
+ }
3668
+
3369
3669
  // src/functional/doc.ts
3370
3670
  function isNode(x) {
3371
3671
  return typeof x === "object" && x !== null && x._tag === "node";
@@ -3553,6 +3853,41 @@ function buildTriviaIndex(root, input = "", opts) {
3553
3853
  return { before, after };
3554
3854
  }
3555
3855
 
3856
+ // src/cst/walk.ts
3857
+ function walk(root, visitor, ctx = void 0) {
3858
+ const go = (node2, parent) => {
3859
+ const descend = visitor.enter ? visitor.enter(node2, parent, ctx) : void 0;
3860
+ const children = node2.children;
3861
+ if (descend !== false && Array.isArray(children)) {
3862
+ for (const child of children) go(child, node2);
3863
+ }
3864
+ visitor.leave?.(node2, parent, ctx);
3865
+ };
3866
+ go(root, null);
3867
+ }
3868
+ function createVisitor(handlers) {
3869
+ const api = {
3870
+ visit(node2) {
3871
+ const handler = node2.type !== void 0 ? handlers[node2.type] : void 0;
3872
+ if (handler) return handler(node2, api);
3873
+ api.visitChildren(node2);
3874
+ return void 0;
3875
+ },
3876
+ visitChildren(node2) {
3877
+ const out = [];
3878
+ const children = node2.children;
3879
+ if (Array.isArray(children)) {
3880
+ for (const child of children) {
3881
+ const r = api.visit(child);
3882
+ if (r !== void 0) out.push(r);
3883
+ }
3884
+ }
3885
+ return out;
3886
+ }
3887
+ };
3888
+ return (node2) => api.visit(node2);
3889
+ }
3890
+
3556
3891
  // src/cst/trivia-entries.ts
3557
3892
  function entryOffset(i, stride) {
3558
3893
  return i * stride;
@@ -3594,6 +3929,7 @@ export {
3594
3929
  choice,
3595
3930
  compile,
3596
3931
  completionsAt,
3932
+ createVisitor,
3597
3933
  expect,
3598
3934
  guard,
3599
3935
  isParseError,
@@ -3603,6 +3939,7 @@ export {
3603
3939
  makeFunctionalDoc,
3604
3940
  makeWord,
3605
3941
  many,
3942
+ manyRecover,
3606
3943
  noTrivia,
3607
3944
  node,
3608
3945
  not,
@@ -3617,12 +3954,14 @@ export {
3617
3954
  rules,
3618
3955
  scanTo,
3619
3956
  sepBy,
3957
+ sepByRecover,
3620
3958
  sequence,
3621
3959
  skip,
3622
3960
  staticExpected,
3623
3961
  transform,
3624
3962
  trivia,
3625
3963
  triviaEntries,
3964
+ walk,
3626
3965
  withCtx,
3627
3966
  word
3628
3967
  };