parseman 0.25.0 → 0.26.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.
Files changed (54) hide show
  1. package/dist/combinators/completions.d.ts +14 -4
  2. package/dist/combinators/completions.d.ts.map +1 -1
  3. package/dist/combinators/expect.d.ts +2 -0
  4. package/dist/combinators/expect.d.ts.map +1 -1
  5. package/dist/combinators/grammar.d.ts +5 -5
  6. package/dist/combinators/not.d.ts.map +1 -1
  7. package/dist/combinators/parser.d.ts +3 -2
  8. package/dist/combinators/parser.d.ts.map +1 -1
  9. package/dist/combinators/repeat.d.ts.map +1 -1
  10. package/dist/combinators/sequence.d.ts.map +1 -1
  11. package/dist/combinators/trivia-skip.d.ts +1 -0
  12. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  13. package/dist/compiler/codegen.d.ts +9 -4
  14. package/dist/compiler/codegen.d.ts.map +1 -1
  15. package/dist/compiler/scannable-run.d.ts +2 -2
  16. package/dist/compiler/scannable-run.d.ts.map +1 -1
  17. package/dist/cst/capture-buffer.d.ts +1 -0
  18. package/dist/cst/capture-buffer.d.ts.map +1 -1
  19. package/dist/cst/types.d.ts +7 -7
  20. package/dist/cst/types.d.ts.map +1 -1
  21. package/dist/functional/doc.d.ts +37 -2
  22. package/dist/functional/doc.d.ts.map +1 -1
  23. package/dist/functional/run.d.ts +12 -1
  24. package/dist/functional/run.d.ts.map +1 -1
  25. package/dist/index.cjs +505 -195
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +1 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +505 -195
  30. package/dist/index.js.map +4 -4
  31. package/dist/language-service/index.cjs +776 -0
  32. package/dist/language-service/index.cjs.map +7 -0
  33. package/dist/language-service/index.d.ts +94 -0
  34. package/dist/language-service/index.d.ts.map +1 -0
  35. package/dist/language-service/index.js +753 -0
  36. package/dist/language-service/index.js.map +7 -0
  37. package/dist/plugin/evaluator.d.ts.map +1 -1
  38. package/dist/plugin/index.cjs +568 -129
  39. package/dist/plugin/index.cjs.map +4 -4
  40. package/dist/plugin/index.d.ts +11 -1
  41. package/dist/plugin/index.d.ts.map +1 -1
  42. package/dist/plugin/index.js +568 -129
  43. package/dist/plugin/index.js.map +4 -4
  44. package/dist/recovery/scan.d.ts +54 -0
  45. package/dist/recovery/scan.d.ts.map +1 -0
  46. package/dist/spec/index.cjs.map +1 -1
  47. package/dist/spec/index.js.map +1 -1
  48. package/dist/types.d.ts +60 -6
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +6 -1
  51. package/dist/combinators/recover-list.d.ts +0 -41
  52. package/dist/combinators/recover-list.d.ts.map +0 -1
  53. package/dist/combinators/recover.d.ts +0 -20
  54. package/dist/combinators/recover.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -253,7 +253,13 @@ function saveCstMark(ctx) {
253
253
  raw: cstRawLen(ctx),
254
254
  tlog: cstTlLen(ctx),
255
255
  leaves: cstLeavesLen(ctx),
256
- fields: ctx._fields?.length ?? 0
256
+ fields: ctx._fields?.length ?? 0,
257
+ // Speculative rollback must truncate the flat recovery-error sink in lockstep
258
+ // with the CST leaves: a branch that recovered (pushing a ParseError to
259
+ // ctx._errors AND embedding it as a CST child via captureError) then failed
260
+ // must remove BOTH, or the flat error survives as a GHOST that a tree walk
261
+ // can't see (flat run().errors would disagree with the embedded set).
262
+ errors: ctx._errors?.length ?? 0
257
263
  };
258
264
  }
259
265
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -269,6 +275,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
269
275
  if (len === 0) b[keySingle] = void 0;
270
276
  }
271
277
  function rollbackCstCapture(ctx, mark) {
278
+ if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
272
279
  const b = ctx._cstBuf;
273
280
  if (b) {
274
281
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
@@ -1215,10 +1222,10 @@ function needsDeferredTriviaCommit(ctx) {
1215
1222
  }
1216
1223
  function saveTriviaMark(ctx) {
1217
1224
  const m = saveCstMark(ctx);
1218
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1225
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1219
1226
  }
1220
1227
  function rollbackTrivia(ctx, mark) {
1221
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1228
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1222
1229
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1223
1230
  }
1224
1231
  function scanWithLabels(input, cur, ctx) {
@@ -1452,6 +1459,94 @@ function scanBlockComment(input, cur) {
1452
1459
  return close === -1 ? cur : close + 2;
1453
1460
  }
1454
1461
 
1462
+ // src/recovery/scan.ts
1463
+ function probeAt(sentinel, input, pos, ctx) {
1464
+ const s = {
1465
+ _probe: ctx._probe,
1466
+ _errors: ctx._errors,
1467
+ _cstLeaves: ctx._cstLeaves,
1468
+ _cstChildren: ctx._cstChildren,
1469
+ _cstRawChildren: ctx._cstRawChildren,
1470
+ _cstTriviaLog: ctx._cstTriviaLog,
1471
+ _triviaLog: ctx._triviaLog,
1472
+ _fields: ctx._fields,
1473
+ _cstBuf: ctx._cstBuf,
1474
+ _tolerant: ctx._tolerant,
1475
+ _sync: ctx._sync
1476
+ };
1477
+ ctx._probe = void 0;
1478
+ ctx._errors = void 0;
1479
+ ctx._cstLeaves = void 0;
1480
+ ctx._cstChildren = void 0;
1481
+ ctx._cstRawChildren = void 0;
1482
+ ctx._cstTriviaLog = void 0;
1483
+ ctx._triviaLog = void 0;
1484
+ ctx._fields = void 0;
1485
+ ctx._cstBuf = void 0;
1486
+ ctx._tolerant = void 0;
1487
+ ctx._sync = void 0;
1488
+ try {
1489
+ return sentinel.parse(input, pos, ctx).ok;
1490
+ } finally {
1491
+ ctx._probe = s._probe;
1492
+ ctx._errors = s._errors;
1493
+ ctx._cstLeaves = s._cstLeaves;
1494
+ ctx._cstChildren = s._cstChildren;
1495
+ ctx._cstRawChildren = s._cstRawChildren;
1496
+ ctx._cstTriviaLog = s._cstTriviaLog;
1497
+ ctx._triviaLog = s._triviaLog;
1498
+ ctx._fields = s._fields;
1499
+ ctx._cstBuf = s._cstBuf;
1500
+ ctx._tolerant = s._tolerant;
1501
+ ctx._sync = s._sync;
1502
+ }
1503
+ }
1504
+ function orSentinel(a, b) {
1505
+ if (b === void 0) return a;
1506
+ return {
1507
+ _tag: "orSentinel",
1508
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
1509
+ _def: { tag: "unknown" },
1510
+ parse(input, pos, ctx) {
1511
+ const ra = a.parse(input, pos, ctx);
1512
+ if (ra.ok) return ra;
1513
+ return b.parse(input, pos, ctx);
1514
+ }
1515
+ };
1516
+ }
1517
+ function firstSetSentinel(fs) {
1518
+ if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
1519
+ const ranges = fs.ranges;
1520
+ return {
1521
+ _tag: "firstSetSentinel",
1522
+ _meta: { firstSet: fs, canMatchNewline: false, isTrivia: false },
1523
+ _def: { tag: "unknown" },
1524
+ parse(input, pos) {
1525
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
1526
+ if (code !== void 0) {
1527
+ for (const r of ranges) {
1528
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
1529
+ }
1530
+ }
1531
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
1532
+ }
1533
+ };
1534
+ }
1535
+ function recoverScan(input, from, ctx, sync, expected) {
1536
+ let scanPos = from;
1537
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
1538
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
1539
+ ctx._errors?.push(error);
1540
+ return { error, end: scanPos };
1541
+ }
1542
+ function matchesAt(sentinel, input, pos, ctx) {
1543
+ return probeAt(sentinel, input, pos, ctx);
1544
+ }
1545
+ function captureError(ctx, error) {
1546
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
1547
+ }
1548
+ var REC = { scan: recoverScan, at: matchesAt, or: orSentinel, sentinel: firstSetSentinel, capture: captureError };
1549
+
1455
1550
  // src/combinators/sequence.ts
1456
1551
  function sequence(...parsers) {
1457
1552
  const meta = {
@@ -1462,11 +1557,59 @@ function sequence(...parsers) {
1462
1557
  isTrivia: false
1463
1558
  };
1464
1559
  const def = { tag: "sequence", parsers };
1560
+ let followSentinels;
1561
+ function parseTolerant(input, pos, ctx) {
1562
+ followSentinels ??= parsers.map((_, i) => {
1563
+ const fs = parsers.slice(i + 1).reduce(
1564
+ (acc, p) => union(acc, firstSetOf(p)),
1565
+ { kind: "empty" }
1566
+ );
1567
+ return firstSetSentinel(fs);
1568
+ });
1569
+ const values = def.valueUnused ? void 0 : [];
1570
+ let cur = pos;
1571
+ const inheritedSync = ctx._sync;
1572
+ try {
1573
+ for (let i = 0; i < parsers.length; i++) {
1574
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1575
+ if (ctx.trivia && i > 0) {
1576
+ const mark = saveTriviaMark(ctx);
1577
+ let scanEnd;
1578
+ if (needsDeferredTriviaCommit(ctx)) {
1579
+ const scan = scanTrivia(input, cur, ctx);
1580
+ scan.commit();
1581
+ scanEnd = scan.end;
1582
+ } else {
1583
+ scanEnd = advanceTrivia(input, cur, ctx);
1584
+ }
1585
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1586
+ if (!result2.ok) return result2;
1587
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1588
+ else rollbackTrivia(ctx, mark);
1589
+ if (values !== void 0) values.push(result2.value);
1590
+ continue;
1591
+ }
1592
+ const result = parsers[i].parse(input, cur, ctx);
1593
+ if (!result.ok) return result;
1594
+ if (values !== void 0) values.push(result.value);
1595
+ cur = result.span.end;
1596
+ }
1597
+ } finally {
1598
+ ctx._sync = inheritedSync;
1599
+ }
1600
+ return {
1601
+ ok: true,
1602
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1603
+ value: values ?? void 0,
1604
+ span: { start: pos, end: cur }
1605
+ };
1606
+ }
1465
1607
  return {
1466
1608
  _tag: "sequence",
1467
1609
  _meta: meta,
1468
1610
  _def: def,
1469
1611
  parse(input, pos, ctx) {
1612
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1470
1613
  const values = def.valueUnused ? void 0 : [];
1471
1614
  let cur = pos;
1472
1615
  for (let i = 0; i < parsers.length; i++) {
@@ -1505,6 +1648,69 @@ function sequence(...parsers) {
1505
1648
  };
1506
1649
  }
1507
1650
 
1651
+ // src/combinators/expect.ts
1652
+ function deriveExpected(c) {
1653
+ const def = c._def;
1654
+ switch (def.tag) {
1655
+ case "literal":
1656
+ return [JSON.stringify(def.value)];
1657
+ case "regex":
1658
+ return [`/${def.source}/`];
1659
+ case "keywords":
1660
+ return def.words.map((w) => JSON.stringify(w));
1661
+ case "label":
1662
+ return [def.label];
1663
+ case "choice":
1664
+ return def.parsers.flatMap(deriveExpected);
1665
+ case "sequence":
1666
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1667
+ case "node":
1668
+ case "grammar":
1669
+ case "trivia":
1670
+ case "token":
1671
+ case "optional":
1672
+ case "many":
1673
+ case "oneOrMore":
1674
+ case "transform":
1675
+ case "not":
1676
+ return deriveExpected(def.parser);
1677
+ case "lazy": {
1678
+ const name = c._ruleName;
1679
+ try {
1680
+ return deriveExpected(def.thunk());
1681
+ } catch {
1682
+ return name ? [name] : [];
1683
+ }
1684
+ }
1685
+ default:
1686
+ return [];
1687
+ }
1688
+ }
1689
+ function expect(combinator, label2) {
1690
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1691
+ const meta = {
1692
+ firstSet: combinator._meta.firstSet,
1693
+ canMatchNewline: combinator._meta.canMatchNewline,
1694
+ isTrivia: false
1695
+ };
1696
+ return {
1697
+ _tag: "expect",
1698
+ _meta: meta,
1699
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1700
+ parse(input, pos, ctx) {
1701
+ const result = combinator.parse(input, pos, ctx);
1702
+ if (result.ok) return result;
1703
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1704
+ ctx._errors?.push(error);
1705
+ if (ctx._tolerant) captureError(ctx, error);
1706
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1707
+ }
1708
+ };
1709
+ }
1710
+ function isParseError(value) {
1711
+ return typeof value === "object" && value !== null && value._tag === "parseError";
1712
+ }
1713
+
1508
1714
  // src/combinators/repeat.ts
1509
1715
  function repItem(combinator, input, cur, ctx) {
1510
1716
  const mark = saveTriviaMark(ctx);
@@ -1525,7 +1731,7 @@ function repItem(combinator, input, cur, ctx) {
1525
1731
  const result = combinator.parse(input, pos, ctx);
1526
1732
  if (!result.ok) {
1527
1733
  rollbackTrivia(ctx, mark);
1528
- return { fail: result };
1734
+ return { fail: result, failPos: pos };
1529
1735
  }
1530
1736
  if (result.span.end === pos) {
1531
1737
  rollbackTrivia(ctx, mark);
@@ -1540,6 +1746,7 @@ function many(combinator) {
1540
1746
  isTrivia: false
1541
1747
  };
1542
1748
  const def = { tag: "many", parser: combinator, min: 0 };
1749
+ let expected;
1543
1750
  return {
1544
1751
  _tag: "many",
1545
1752
  _meta: meta,
@@ -1549,7 +1756,18 @@ function many(combinator) {
1549
1756
  let cur = pos;
1550
1757
  while (cur < input.length) {
1551
1758
  const item = repItem(combinator, input, cur, ctx);
1552
- if (item === "stop" || "fail" in item) break;
1759
+ if (item === "stop") break;
1760
+ if ("fail" in item) {
1761
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1762
+ if (sync === void 0) break;
1763
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1764
+ expected ??= deriveExpected(combinator);
1765
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1766
+ if (values !== void 0) values.push(error);
1767
+ captureError(ctx, error);
1768
+ cur = end;
1769
+ continue;
1770
+ }
1553
1771
  if (values !== void 0) values.push(item.value);
1554
1772
  cur = item.end;
1555
1773
  }
@@ -1564,6 +1782,7 @@ function oneOrMore(combinator) {
1564
1782
  isTrivia: false
1565
1783
  };
1566
1784
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1785
+ let expected;
1567
1786
  return {
1568
1787
  _tag: "oneOrMore",
1569
1788
  _meta: meta,
@@ -1575,7 +1794,18 @@ function oneOrMore(combinator) {
1575
1794
  let cur = first.span.end;
1576
1795
  while (cur < input.length) {
1577
1796
  const item = repItem(combinator, input, cur, ctx);
1578
- if (item === "stop" || "fail" in item) break;
1797
+ if (item === "stop") break;
1798
+ if ("fail" in item) {
1799
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1800
+ if (sync === void 0) break;
1801
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1802
+ expected ??= deriveExpected(combinator);
1803
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1804
+ if (values !== void 0) values.push(error);
1805
+ captureError(ctx, error);
1806
+ cur = end;
1807
+ continue;
1808
+ }
1579
1809
  if (values !== void 0) values.push(item.value);
1580
1810
  cur = item.end;
1581
1811
  }
@@ -1621,15 +1851,29 @@ function sepBy(combinator, separator) {
1621
1851
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1622
1852
  isTrivia: false
1623
1853
  };
1854
+ let expected;
1624
1855
  return {
1625
1856
  _tag: "sepBy",
1626
1857
  _meta: meta,
1627
1858
  _def: { tag: "sepBy", parser: combinator, separator },
1628
1859
  parse(input, pos, ctx) {
1629
1860
  const first = combinator.parse(input, pos, ctx);
1630
- if (!first.ok) return { ok: true, value: [], span: { start: pos, end: pos } };
1631
- const values = [first.value];
1632
- let cur = first.span.end;
1861
+ const values = [];
1862
+ let cur;
1863
+ if (first.ok) {
1864
+ values.push(first.value);
1865
+ cur = first.span.end;
1866
+ } else {
1867
+ const term = ctx._tolerant ? ctx._sync : void 0;
1868
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1869
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1870
+ }
1871
+ expected ??= deriveExpected(combinator);
1872
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1873
+ values.push(rec.error);
1874
+ captureError(ctx, rec.error);
1875
+ cur = rec.end;
1876
+ }
1633
1877
  while (cur < input.length) {
1634
1878
  const loopMark = saveTriviaMark(ctx);
1635
1879
  let sepPos = cur;
@@ -1659,6 +1903,15 @@ function sepBy(combinator, separator) {
1659
1903
  }
1660
1904
  const next = combinator.parse(input, nextPos, ctx);
1661
1905
  if (!next.ok) {
1906
+ const term = ctx._tolerant ? ctx._sync : void 0;
1907
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1908
+ expected ??= deriveExpected(combinator);
1909
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1910
+ values.push(rec.error);
1911
+ captureError(ctx, rec.error);
1912
+ cur = rec.end;
1913
+ continue;
1914
+ }
1662
1915
  rollbackTrivia(ctx, loopMark);
1663
1916
  break;
1664
1917
  }
@@ -1832,7 +2085,7 @@ function rules(a, b) {
1832
2085
  cache[key] = parser2;
1833
2086
  }
1834
2087
  }
1835
- if (options?.trivia !== void 0) {
2088
+ if (options?.trivia != null) {
1836
2089
  for (const key of Object.keys(definitions)) {
1837
2090
  const rule = cache[key];
1838
2091
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1862,7 +2115,9 @@ function not(combinator) {
1862
2115
  _meta: meta,
1863
2116
  _def: { tag: "not", parser: combinator },
1864
2117
  parse(input, pos, ctx) {
2118
+ const mark = saveCstMark(ctx);
1865
2119
  const result = combinator.parse(input, pos, ctx);
2120
+ rollbackCstCapture(ctx, mark);
1866
2121
  if (result.ok) {
1867
2122
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1868
2123
  }
@@ -1976,7 +2231,7 @@ function unwrapChild(child) {
1976
2231
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1977
2232
  }
1978
2233
  function isCstChild(value) {
1979
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2234
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
1980
2235
  }
1981
2236
  function missingInferredType() {
1982
2237
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2254,65 +2509,6 @@ function token(root) {
2254
2509
  };
2255
2510
  }
2256
2511
 
2257
- // src/combinators/expect.ts
2258
- function deriveExpected(c) {
2259
- const def = c._def;
2260
- switch (def.tag) {
2261
- case "literal":
2262
- return [JSON.stringify(def.value)];
2263
- case "regex":
2264
- return [`/${def.source}/`];
2265
- case "keywords":
2266
- return def.words.map((w) => JSON.stringify(w));
2267
- case "label":
2268
- return [def.label];
2269
- case "choice":
2270
- return def.parsers.flatMap(deriveExpected);
2271
- case "sequence":
2272
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
2273
- case "node":
2274
- case "grammar":
2275
- case "trivia":
2276
- case "token":
2277
- case "optional":
2278
- case "many":
2279
- case "oneOrMore":
2280
- case "transform":
2281
- case "not":
2282
- return deriveExpected(def.parser);
2283
- case "lazy": {
2284
- const name = c._ruleName;
2285
- try {
2286
- return deriveExpected(def.thunk());
2287
- } catch {
2288
- return name ? [name] : [];
2289
- }
2290
- }
2291
- default:
2292
- return [];
2293
- }
2294
- }
2295
- function expect(combinator, label2) {
2296
- const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
2297
- const meta = {
2298
- firstSet: combinator._meta.firstSet,
2299
- canMatchNewline: combinator._meta.canMatchNewline,
2300
- isTrivia: false
2301
- };
2302
- return {
2303
- _tag: "expect",
2304
- _meta: meta,
2305
- _def: { tag: "expect", parser: combinator, label: label2, expected },
2306
- parse(input, pos, ctx) {
2307
- const result = combinator.parse(input, pos, ctx);
2308
- if (result.ok) return result;
2309
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2310
- ctx._errors?.push(error);
2311
- return { ok: true, value: error, span: { start: pos, end: pos } };
2312
- }
2313
- };
2314
- }
2315
-
2316
2512
  // src/compiler/scannable-run.ts
2317
2513
  var BACKSLASH = 92;
2318
2514
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2384,20 +2580,39 @@ function parseSeqParts(source) {
2384
2580
  lit = [];
2385
2581
  }
2386
2582
  };
2387
- const runFrom = (ranges, negated, q) => {
2583
+ const braceQuant = (at) => {
2584
+ const close = source.indexOf("}", at);
2585
+ if (close === -1) return null;
2586
+ const body = source.slice(at + 1, close);
2587
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2588
+ if (!m) return null;
2589
+ const min = Number(m[1]);
2590
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2591
+ if (max < 1 || min > max) return null;
2592
+ return { min, max, len: close - at + 1 };
2593
+ };
2594
+ const runFrom = (ranges, negated, at) => {
2595
+ const q = source[at];
2388
2596
  if (q === "+") {
2389
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2597
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2390
2598
  return 1;
2391
2599
  }
2392
2600
  if (q === "*") {
2393
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2601
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2394
2602
  return 1;
2395
2603
  }
2396
2604
  if (q === "?") {
2397
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2605
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2398
2606
  return 1;
2399
2607
  }
2400
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2608
+ if (q === "{") {
2609
+ const b = braceQuant(at);
2610
+ if (b) {
2611
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2612
+ return b.len;
2613
+ }
2614
+ }
2615
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2401
2616
  return 0;
2402
2617
  };
2403
2618
  let i = 0;
@@ -2422,7 +2637,7 @@ function parseSeqParts(source) {
2422
2637
  if (!ranges) return null;
2423
2638
  flush();
2424
2639
  i = k + 1;
2425
- i += runFrom(ranges, neg, source[i]);
2640
+ i += runFrom(ranges, neg, i);
2426
2641
  continue;
2427
2642
  }
2428
2643
  if (ch === "\\") {
@@ -2430,7 +2645,7 @@ function parseSeqParts(source) {
2430
2645
  if (e === "d" || e === "w" || e === "s") {
2431
2646
  flush();
2432
2647
  i += 2;
2433
- i += runFrom(shorthandRanges(e), false, source[i]);
2648
+ i += runFrom(shorthandRanges(e), false, i);
2434
2649
  continue;
2435
2650
  }
2436
2651
  const uni = readUnicodeEscape(source, i);
@@ -2519,7 +2734,7 @@ function seqIsUnambiguous(parts) {
2519
2734
  let hasMandatory = false;
2520
2735
  for (let k = 0; k < parts.length; k++) {
2521
2736
  const p = parts[k];
2522
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2737
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2523
2738
  if (mandatory) hasMandatory = true;
2524
2739
  if (p.part === "group") {
2525
2740
  const exposure = groupPartExposure(p);
@@ -2530,9 +2745,8 @@ function seqIsUnambiguous(parts) {
2530
2745
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2531
2746
  }
2532
2747
  }
2533
- const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
2534
- const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
2535
- if (skippableSingle || isUnboundedRepeat) {
2748
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2749
+ if (needsDisjoint) {
2536
2750
  const rest = seqFirstAccept(parts.slice(k + 1));
2537
2751
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2538
2752
  }
@@ -2589,8 +2803,8 @@ function fixedClassSeq(shape) {
2589
2803
  if (p.part === "lit") {
2590
2804
  if (p.optional) return null;
2591
2805
  for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
2592
- } else if (p.part === "run" && !p.unbounded && p.min === 1) {
2593
- out.push({ ranges: p.ranges, negated: p.negated });
2806
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2807
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2594
2808
  } else {
2595
2809
  return null;
2596
2810
  }
@@ -2642,7 +2856,7 @@ function trailingBacktrackClass(shape) {
2642
2856
  const last = shape.parts[shape.parts.length - 1];
2643
2857
  if (last.part === "lit") return null;
2644
2858
  if (last.part === "run") {
2645
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2859
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2646
2860
  return null;
2647
2861
  }
2648
2862
  return groupPartExposure(last);
@@ -2779,7 +2993,7 @@ function partFirstAccept(part) {
2779
2993
  function seqFirstAccept(parts) {
2780
2994
  const sets = [];
2781
2995
  for (const p of parts) {
2782
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2996
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2783
2997
  sets.push(partFirstAccept(p));
2784
2998
  if (mandatory) break;
2785
2999
  }
@@ -3194,7 +3408,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3194
3408
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3195
3409
  lines.push(`${ind2} ${endV2} += ${L}`);
3196
3410
  }
3197
- } else if (p.part === "run" && !p.unbounded) {
3411
+ } else if (p.part === "run" && p.max === Infinity) {
3412
+ const s = mint("_s");
3413
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3414
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3415
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3416
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3417
+ } else if (p.part === "run" && p.max === 1) {
3198
3418
  if (p.min === 1) {
3199
3419
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3200
3420
  lines.push(`${ind2} ${endV2}++`);
@@ -3204,8 +3424,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3204
3424
  } else if (p.part === "run") {
3205
3425
  const s = mint("_s");
3206
3426
  lines.push(`${ind2} const ${s} = ${endV2}`);
3207
- lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3208
- if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3427
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3428
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3209
3429
  } else {
3210
3430
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3211
3431
  if (!p.unbounded) {
@@ -3664,21 +3884,29 @@ function hoistExpected(ctx, constArrSource) {
3664
3884
  }
3665
3885
  return name;
3666
3886
  }
3887
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3888
+ if (!ctx.recovery) return "";
3889
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3890
+ const pb = v(ctx, "_pb");
3891
+ return `if (_ctx._probe !== undefined && ${posExpr} <= _ctx._probe.offset) { const ${pb} = _ctx._probe.best; if (${pb} === null || ${posExpr} > ${pb}.span.start) _ctx._probe.best = { ok: false, expected: [...${fx}], span: { start: ${posExpr}, end: ${posExpr} } }; else if (${posExpr} === ${pb}.span.start) _ctx._probe.best = { ok: false, expected: [...${pb}.expected, ...${fx}], span: { start: ${posExpr}, end: ${posExpr} } } } `;
3892
+ }
3667
3893
  function failBody(ctx, expected, posExpr) {
3894
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3668
3895
  if (ctx.failLabel) {
3669
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3670
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3896
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3897
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3671
3898
  }
3672
- return failReturn(expected, posExpr);
3899
+ return probe + failReturn(expected, posExpr);
3673
3900
  }
3674
3901
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3902
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3675
3903
  if (ctx.failLabel) {
3676
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3904
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3677
3905
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3678
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3906
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3679
3907
  }
3680
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3681
- return failReturnArr(expectedArr, posExpr);
3908
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3909
+ return probe + failReturnArr(expectedArr, posExpr);
3682
3910
  }
3683
3911
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3684
3912
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4178,6 +4406,10 @@ function emitRegex(def, ctx, pos) {
4178
4406
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4179
4407
  return { stmts, valueVar: vv, endVar };
4180
4408
  }
4409
+ function syncSentinelExpr(fs) {
4410
+ if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
4411
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs.ranges)} })`;
4412
+ }
4181
4413
  function emitSeqValues(def, ctx, pos) {
4182
4414
  const startV = v(ctx, "_start");
4183
4415
  const curV = v(ctx, "_cur");
@@ -4186,7 +4418,17 @@ function emitSeqValues(def, ctx, pos) {
4186
4418
  `${ind(ctx)}let ${curV} = ${pos}`
4187
4419
  ];
4188
4420
  const valueVars = [];
4421
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4422
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4423
+ const publishSync = (i) => {
4424
+ if (!ctx.recovery) return "";
4425
+ const fs = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4426
+ const ref2 = syncSentinelExpr(fs);
4427
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4428
+ };
4189
4429
  for (let i = 0; i < def.parsers.length; i++) {
4430
+ const syncPub = publishSync(i);
4431
+ if (syncPub) stmts.push(syncPub);
4190
4432
  if (i > 0 && ctx.activeTrivia) {
4191
4433
  if (ctx.capturing) {
4192
4434
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4211,7 +4453,17 @@ function emitSeqValues(def, ctx, pos) {
4211
4453
  continue;
4212
4454
  } else {
4213
4455
  const trivFn = ensureTriviaFn(ctx);
4214
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4456
+ const scanEndV = v(ctx, "_sne");
4457
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4458
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4459
+ stmts.push(...r2.stmts);
4460
+ const endAfterV = v(ctx, "_sea");
4461
+ stmts.push(
4462
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4463
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4464
+ );
4465
+ valueVars.push(r2.valueVar);
4466
+ continue;
4215
4467
  }
4216
4468
  }
4217
4469
  const r = emit(def.parsers[i], ctx, curV);
@@ -4441,7 +4693,7 @@ function emitFirstMatch(def, ctx, pos) {
4441
4693
  if (gate) {
4442
4694
  const gateIdx = ctx.mapFns.length;
4443
4695
  ctx.mapFns.push(gate);
4444
- ctx.mapFnSrcs.push(null);
4696
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4445
4697
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4446
4698
  }
4447
4699
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4546,6 +4798,8 @@ function emitMany(def, ctx, pos) {
4546
4798
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4547
4799
  `${ind(ctx)}let ${curV} = ${pos}`
4548
4800
  ];
4801
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4802
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4549
4803
  if (def.min === 1) {
4550
4804
  const firstR = emit(def.parser, ctx, curV);
4551
4805
  stmts.push(...firstR.stmts);
@@ -4579,10 +4833,26 @@ function emitMany(def, ctx, pos) {
4579
4833
  }
4580
4834
  }
4581
4835
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
4582
- stmts.push(
4583
- ...iterStmts,
4584
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
4585
- );
4836
+ stmts.push(...iterStmts);
4837
+ if (ctx.recovery) {
4838
+ const rrV = v(ctx, "_rr");
4839
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4840
+ stmts.push(
4841
+ `${ind(ctx)}if (!${iterOk}) {`,
4842
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4843
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4844
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4845
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4846
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4847
+ `${ind(ctx)} continue`,
4848
+ `${ind(ctx)} }`,
4849
+ `${ind(ctx)} break`,
4850
+ `${ind(ctx)}}`,
4851
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4852
+ );
4853
+ } else {
4854
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4855
+ }
4586
4856
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4587
4857
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4588
4858
  ctx.indent--;
@@ -4604,20 +4874,56 @@ function emitOptional(def, ctx, pos) {
4604
4874
  function emitSepBy(_p, def, ctx, pos) {
4605
4875
  const arrV = v(ctx, "_arr");
4606
4876
  const curV = v(ctx, "_cur");
4877
+ const rec = !!ctx.recovery;
4878
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4879
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4880
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4881
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4607
4882
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4608
4883
  const stmts = [
4609
4884
  `${ind(ctx)}const ${arrV} = []`,
4610
4885
  `${ind(ctx)}let ${curV} = ${pos}`,
4611
- ...firstStmts,
4612
- `${ind(ctx)}if (${firstOk}) {`
4886
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4887
+ ...firstStmts
4613
4888
  ];
4614
- ctx.indent++;
4615
- stmts.push(
4616
- `${ind(ctx)}${arrV}.push(${firstVal})`,
4617
- `${ind(ctx)}${curV} = ${firstEnd}`,
4618
- `${ind(ctx)}while (${curV} < input.length) {`
4619
- );
4620
- ctx.indent++;
4889
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4890
+ if (!rec) return [originalFail];
4891
+ const rr = v(ctx, "_rr");
4892
+ return [
4893
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4894
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4895
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4896
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4897
+ `${ind(ctx)} }`,
4898
+ `${ind(ctx)} ${rb}break`,
4899
+ `${ind(ctx)}}`
4900
+ ];
4901
+ };
4902
+ if (rec) {
4903
+ const rr0 = v(ctx, "_rr0");
4904
+ const didV = v(ctx, "_did");
4905
+ stmts.push(
4906
+ `${ind(ctx)}let ${didV} = false`,
4907
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4908
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4909
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4910
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4911
+ `${ind(ctx)}}`,
4912
+ `${ind(ctx)}if (${didV}) {`
4913
+ );
4914
+ ctx.indent++;
4915
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
4916
+ ctx.indent++;
4917
+ } else {
4918
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
4919
+ ctx.indent++;
4920
+ stmts.push(
4921
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
4922
+ `${ind(ctx)}${curV} = ${firstEnd}`,
4923
+ `${ind(ctx)}while (${curV} < input.length) {`
4924
+ );
4925
+ ctx.indent++;
4926
+ }
4621
4927
  let sepAtPos = curV;
4622
4928
  if (ctx.activeTrivia) {
4623
4929
  if (ctx.capturing) {
@@ -4643,8 +4949,7 @@ function emitSepBy(_p, def, ctx, pos) {
4643
4949
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4644
4950
  stmts.push(
4645
4951
  ...nextStmts,
4646
- // item failed → unwind the separator too, back to the end of the last item
4647
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
4952
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4648
4953
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4649
4954
  `${ind(ctx)}${curV} = ${nextEnd}`
4650
4955
  );
@@ -4660,7 +4965,7 @@ function emitSepBy(_p, def, ctx, pos) {
4660
4965
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4661
4966
  stmts.push(
4662
4967
  ...nextStmts,
4663
- `${ind(ctx)}if (!${nextOk}) break`,
4968
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4664
4969
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4665
4970
  `${ind(ctx)}${curV} = ${nextEnd}`
4666
4971
  );
@@ -4680,7 +4985,7 @@ function emitSepBy(_p, def, ctx, pos) {
4680
4985
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4681
4986
  stmts.push(
4682
4987
  ...nextStmts,
4683
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
4988
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4684
4989
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4685
4990
  `${ind(ctx)}${curV} = ${nextEnd}`
4686
4991
  );
@@ -4937,7 +5242,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4937
5242
  stmts.push(
4938
5243
  `${i}const ${ndV} = ${finalExpr}`,
4939
5244
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4940
- `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'error')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
5245
+ `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'parseError')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
4941
5246
  );
4942
5247
  return { stmts, valueVar: ndV, endVar };
4943
5248
  }
@@ -5027,6 +5332,10 @@ function emitExpect(def, ctx, pos) {
5027
5332
  `${ind0}if (!${okVar}) {`,
5028
5333
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5029
5334
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5335
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5336
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5337
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5338
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5030
5339
  `${ind0} ${valVar} = ${errV}`,
5031
5340
  `${ind0} ${endVar} = ${pos}`,
5032
5341
  `${ind0} ${okVar} = true`,
@@ -5193,7 +5502,7 @@ function emitDispatch(p, ctx, pos) {
5193
5502
  case "guard": {
5194
5503
  const fnIdx = ctx.mapFns.length;
5195
5504
  ctx.mapFns.push(def.predicate);
5196
- ctx.mapFnSrcs.push(null);
5505
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5197
5506
  const vv = v(ctx);
5198
5507
  return {
5199
5508
  stmts: [
@@ -5208,7 +5517,7 @@ function emitDispatch(p, ctx, pos) {
5208
5517
  const evIdx = ctx.mapFns.length;
5209
5518
  const extra = def.extra;
5210
5519
  ctx.mapFns.push((() => extra));
5211
- ctx.mapFnSrcs.push(null);
5520
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5212
5521
  const innerParser = def.parser;
5213
5522
  if (!ctx.namedParsers.has(innerParser)) {
5214
5523
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5452,7 +5761,7 @@ function ruleDependencies(ruleMap) {
5452
5761
  }
5453
5762
  return deps;
5454
5763
  }
5455
- function compile(combinator, mapFnSources) {
5764
+ function compile(combinator, mapFnSources, opts) {
5456
5765
  markUnusedValues(combinator);
5457
5766
  const grammarTrivia = combinator._meta.grammarTrivia;
5458
5767
  const ctx = {
@@ -5473,6 +5782,7 @@ function compile(combinator, mapFnSources) {
5473
5782
  triviaFnNames: /* @__PURE__ */ new Map(),
5474
5783
  namedFnDecls: [],
5475
5784
  capturing: hasNodeDef(combinator),
5785
+ recovery: opts?.recovery ?? false,
5476
5786
  lazyUsage: analyzeLazyUsage(combinator),
5477
5787
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5478
5788
  };
@@ -5525,7 +5835,7 @@ function compile(combinator, mapFnSources) {
5525
5835
  parseWithContext(input, parseCtx, pos = 0) {
5526
5836
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5527
5837
  },
5528
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5838
+ // Note: collects expect() errors via _errors. Unlike interpreter
5529
5839
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5530
5840
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5531
5841
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5582,6 +5892,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5582
5892
  triviaFnNames: /* @__PURE__ */ new Map(),
5583
5893
  namedFnDecls: [],
5584
5894
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5895
+ recovery: opts?.recovery ?? false,
5585
5896
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5586
5897
  ns,
5587
5898
  deferFirstSetRefs: true,
@@ -6327,46 +6638,13 @@ function withCtx(extra, combinator) {
6327
6638
  };
6328
6639
  }
6329
6640
 
6330
- // src/combinators/recover.ts
6331
- function recover(combinator, sentinel) {
6332
- const meta = {
6333
- firstSet: { kind: "any" },
6334
- canMatchNewline: true,
6335
- isTrivia: false
6336
- };
6337
- const expected = deriveExpected(combinator);
6338
- return {
6339
- _tag: "recover",
6340
- _meta: meta,
6341
- _def: { tag: "recover", parser: combinator, sentinel },
6342
- parse(input, pos, ctx) {
6343
- const result = combinator.parse(input, pos, ctx);
6344
- if (result.ok) return result;
6345
- let scanPos = pos;
6346
- while (scanPos < input.length) {
6347
- if (sentinel.parse(input, scanPos, ctx).ok) break;
6348
- scanPos++;
6349
- }
6350
- const error = {
6351
- _tag: "parseError",
6352
- span: { start: pos, end: scanPos },
6353
- expected
6354
- };
6355
- ctx._errors?.push(error);
6356
- return { ok: true, value: error, span: { start: pos, end: scanPos } };
6357
- }
6358
- };
6359
- }
6360
- function isParseError(value) {
6361
- return typeof value === "object" && value !== null && value._tag === "parseError";
6362
- }
6363
-
6364
6641
  // src/combinators/completions.ts
6365
- function completionsAt(combinator, input, offset) {
6642
+ function completionsAt(target, input, offset, options = {}) {
6366
6643
  const probe = { offset, best: null };
6367
- const ctx = { trackLines: false, _probe: probe };
6368
- const result = combinator.parse(input.slice(0, offset), 0, ctx);
6369
- if (result.ok) return [];
6644
+ const ctx = { trackLines: false, _probe: probe, ...options.tolerant ? { _tolerant: true, _rec: REC } : {} };
6645
+ const sliced = input.slice(0, offset);
6646
+ const result = "parseWithContext" in target ? target.parseWithContext(sliced, ctx, 0) : target.parse(sliced, 0, ctx);
6647
+ if (result.ok) return options.tolerant ? probe.best?.expected ?? [] : [];
6370
6648
  const best = deeperFail(probe.best, result);
6371
6649
  return best?.expected ?? [];
6372
6650
  }
@@ -6376,23 +6654,6 @@ function deeperFail(a, b) {
6376
6654
  return a.span.start >= b.span.start ? a : b;
6377
6655
  }
6378
6656
 
6379
- // src/combinators/recover-list.ts
6380
- function tolerantElement(item, sentinel, until) {
6381
- const el = transform(
6382
- sequence(not(until), recover(item, sentinel)),
6383
- ([, value]) => value
6384
- );
6385
- if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
6386
- return el;
6387
- }
6388
- function sepByRecover(item, separator, until) {
6389
- const sentinel = choice(separator, until);
6390
- return sepBy(tolerantElement(item, sentinel, until), separator);
6391
- }
6392
- function manyRecover(item, until) {
6393
- return many(tolerantElement(item, until, until));
6394
- }
6395
-
6396
6657
  // src/cst/relative-spans.ts
6397
6658
  function relativize(node2, parentBase = 0) {
6398
6659
  return {
@@ -6487,6 +6748,9 @@ function editNode(node2, at, delta, parentBase, parentShift, stats) {
6487
6748
  }
6488
6749
 
6489
6750
  // src/functional/doc.ts
6751
+ function mkCtx(state, build, tolerant) {
6752
+ return tolerant ? { trackLines: false, state, build, _tolerant: true, _rec: REC, _errors: [] } : { trackLines: false, state, build };
6753
+ }
6490
6754
  function asRuleFn(entry) {
6491
6755
  return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
6492
6756
  }
@@ -6593,8 +6857,8 @@ function graftRelative(root, path, newNode, delta) {
6593
6857
  children: newChildren
6594
6858
  };
6595
6859
  }
6596
- function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build) {
6597
- const ctx = { trackLines: false, state: null, build };
6860
+ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, tolerant) {
6861
+ const ctx = mkCtx(null, build, tolerant);
6598
6862
  const out = [];
6599
6863
  let pos = reStart;
6600
6864
  let guard2 = 0;
@@ -6618,7 +6882,7 @@ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build) {
6618
6882
  }
6619
6883
  return pos === reEnd ? out : null;
6620
6884
  }
6621
- function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build) {
6885
+ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build, tolerant) {
6622
6886
  const kids = C.children;
6623
6887
  if (kids.length === 0) return null;
6624
6888
  let h = 0;
@@ -6659,7 +6923,7 @@ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registr
6659
6923
  const reStart = h > 0 ? cStart + kids[h - 1].span.end : cStart;
6660
6924
  const reEnd = cStart + kids[t].span.start + delta;
6661
6925
  if (reEnd < reStart) return null;
6662
- const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build);
6926
+ const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
6663
6927
  if (!middle) return null;
6664
6928
  if (middle.length > 0) {
6665
6929
  const last2 = middle[middle.length - 1];
@@ -6669,7 +6933,7 @@ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registr
6669
6933
  for (const sentinel of [" ", "\uFFFF"]) {
6670
6934
  if (newInput[reEnd] === sentinel) continue;
6671
6935
  const probed = newInput.slice(0, reEnd) + sentinel.repeat(newInput.length - reEnd);
6672
- const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build);
6936
+ const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
6673
6937
  if (!probe || probe.length !== middle.length) return null;
6674
6938
  for (let i = 0; i < middle.length; i++) if (!structurallyEqual(probe[i], middle[i])) return null;
6675
6939
  }
@@ -6697,6 +6961,11 @@ function structurallyEqual(a, b) {
6697
6961
  if (at === "leaf" || at === "trivia") {
6698
6962
  return a.value === b.value;
6699
6963
  }
6964
+ if (at === "parseError") {
6965
+ const ae = a.expected ?? [];
6966
+ const be = b.expected ?? [];
6967
+ return ae.length === be.length && ae.every((x, i) => x === be[i]);
6968
+ }
6700
6969
  if (a.type !== b.type) return false;
6701
6970
  const ac = a.children;
6702
6971
  const bc = b.children;
@@ -6706,13 +6975,13 @@ function structurallyEqual(a, b) {
6706
6975
  }
6707
6976
  return true;
6708
6977
  }
6709
- function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
6978
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced, tolerant) {
6710
6979
  if (!produced.ok) return false;
6711
6980
  if (boundary >= newInput.length) return true;
6712
6981
  for (const sentinel of [" ", "\uFFFF"]) {
6713
6982
  if (newInput[boundary] === sentinel) continue;
6714
6983
  const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
6715
- const ctx = { trackLines: false, state, build };
6984
+ const ctx = mkCtx(state, build, tolerant);
6716
6985
  let r;
6717
6986
  try {
6718
6987
  r = ruleFn(probed, start, ctx);
@@ -6744,8 +7013,9 @@ var ParseDocImpl = class _ParseDocImpl {
6744
7013
  _abs;
6745
7014
  _rel;
6746
7015
  errors;
7016
+ unconsumedFrom;
6747
7017
  input;
6748
- constructor(registry, rootRule, opts, trees, errors, input) {
7018
+ constructor(registry, rootRule, opts, trees, errors, unconsumedFrom, input) {
6749
7019
  this._registry = registry;
6750
7020
  this._fns = {};
6751
7021
  this._reps = /* @__PURE__ */ new Set();
@@ -6758,6 +7028,7 @@ var ParseDocImpl = class _ParseDocImpl {
6758
7028
  this._abs = trees.abs;
6759
7029
  this._rel = trees.rel;
6760
7030
  this.errors = errors;
7031
+ this.unconsumedFrom = unconsumedFrom;
6761
7032
  this.input = input;
6762
7033
  }
6763
7034
  /**
@@ -6780,6 +7051,19 @@ var ParseDocImpl = class _ParseDocImpl {
6780
7051
  if (!rel) throw new Error("spanAt on a failed parse (tree is null)");
6781
7052
  return absoluteSpanCST(rel, path);
6782
7053
  }
7054
+ /**
7055
+ * Wrap a reused (grafted/spliced) RELATIVE tree, recomputing `errors` and
7056
+ * `unconsumedFrom` from it so both stay consistent with the tree across the
7057
+ * edit (the flat errors are recovered errors embedded in the reused subtrees;
7058
+ * unconsumedFrom re-derives trailing junk over the new input).
7059
+ */
7060
+ wrapReuse(newTree, newInput) {
7061
+ const trivia2 = triviaOf(this._registry[this._rootRule], this._opts);
7062
+ const end = newTree.span.end;
7063
+ const errors = collectEmbeddedErrors(newTree, 0, []);
7064
+ const unconsumedFrom = unconsumedAfter(end, newInput, trivia2);
7065
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree }, errors, unconsumedFrom, newInput);
7066
+ }
6783
7067
  edit(from, to, replacement) {
6784
7068
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
6785
7069
  const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
@@ -6801,22 +7085,22 @@ var ParseDocImpl = class _ParseDocImpl {
6801
7085
  const ruleFn = this._fns[node2.type];
6802
7086
  if (!ruleFn) continue;
6803
7087
  if (!(absStart <= from && to <= absEnd)) continue;
6804
- const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
7088
+ const ctx = mkCtx(node2.state, this._opts.build, !!this._opts.tolerant);
6805
7089
  const r = ruleFn(newInput, absStart, ctx);
6806
7090
  if (!r.ok) continue;
6807
7091
  if (r.span.end !== absEnd + delta) continue;
6808
- if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r)) {
7092
+ if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r, !!this._opts.tolerant)) {
6809
7093
  continue;
6810
7094
  }
6811
7095
  const parentBase = absStart - node2.span.start;
6812
7096
  const newRel = relativizeCST(r.value, parentBase);
6813
7097
  if (delta === 0) {
6814
7098
  const newTree2 = replaceAtPath(rebuild, root, path, newRel);
6815
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree2 }, [], newInput);
7099
+ return this.wrapReuse(newTree2, newInput);
6816
7100
  }
6817
7101
  if (this._opts.rebuild) return reparse();
6818
7102
  const newTree = graftRelative(root, path, newRel, delta);
6819
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree }, [], newInput);
7103
+ return this.wrapReuse(newTree, newInput);
6820
7104
  }
6821
7105
  if (this._opts.structuralReuse && delta !== 0 && !this._opts.rebuild) {
6822
7106
  const spliceCandidates = [...candidates, { node: root, path: [] }];
@@ -6824,22 +7108,50 @@ var ParseDocImpl = class _ParseDocImpl {
6824
7108
  if (!this._reps.has(node2.type)) continue;
6825
7109
  const { start: cStart, end: cEnd } = absoluteSpanCST(root, path);
6826
7110
  if (!(cStart <= from && to <= cEnd)) continue;
6827
- const spliced = tryListSplice(root, path, node2, cStart, newInput, from, to, delta, this._fns, this._opts.build);
6828
- if (spliced) return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: spliced }, [], newInput);
7111
+ const spliced = tryListSplice(root, path, node2, cStart, newInput, from, to, delta, this._fns, this._opts.build, !!this._opts.tolerant);
7112
+ if (spliced) return this.wrapReuse(spliced, newInput);
6829
7113
  }
6830
7114
  }
6831
7115
  return reparse();
6832
7116
  }
6833
7117
  };
7118
+ function triviaOf(entry, opts) {
7119
+ if (opts.trivia) return opts.trivia;
7120
+ if (entry === void 0 || typeof entry === "function") return void 0;
7121
+ const meta = entry._meta;
7122
+ return meta ? meta.grammarTrivia : void 0;
7123
+ }
7124
+ function unconsumedAfter(end, input, trivia2) {
7125
+ let pos = end;
7126
+ if (trivia2 && pos < input.length) {
7127
+ const t = trivia2.parse(input, pos, { trackLines: false });
7128
+ if (t.ok && t.span.end > pos) pos = t.span.end;
7129
+ }
7130
+ return pos < input.length ? pos : null;
7131
+ }
7132
+ function collectEmbeddedErrors(node2, base, out) {
7133
+ const c = node2;
7134
+ if (!c || typeof c !== "object" || !c.span) return out;
7135
+ const start = base + c.span.start;
7136
+ if (c._tag === "parseError") {
7137
+ out.push({ _tag: "parseError", span: { start, end: base + c.span.end }, expected: c.expected ?? [] });
7138
+ return out;
7139
+ }
7140
+ if (Array.isArray(c.children)) for (const k of c.children) collectEmbeddedErrors(k, start, out);
7141
+ return out;
7142
+ }
6834
7143
  function parseDoc(registry, rootRule, input, opts = {}) {
6835
7144
  const entry = registry[rootRule];
6836
7145
  if (!entry) throw new Error(`No rule '${rootRule}' in registry`);
6837
- const ctx = { trackLines: false, state: opts.state, build: opts.build };
7146
+ const ctx = mkCtx(opts.state, opts.build, !!opts.tolerant);
6838
7147
  const r = asRuleFn(entry)(input, 0, ctx);
6839
7148
  if (r.ok) {
6840
- return new ParseDocImpl(registry, rootRule, opts, { abs: r.value }, [], input);
7149
+ const errors = ctx._errors ? [...ctx._errors] : [];
7150
+ const unconsumedFrom = unconsumedAfter(r.span.end, input, triviaOf(entry, opts));
7151
+ return new ParseDocImpl(registry, rootRule, opts, { abs: r.value }, errors, unconsumedFrom, input);
6841
7152
  }
6842
- return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [{ ok: false, expected: r.expected, span: r.span }], input);
7153
+ const fail = { _tag: "parseError", span: r.span, expected: r.expected };
7154
+ return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
6843
7155
  }
6844
7156
 
6845
7157
  // src/functional/run.ts
@@ -6860,7 +7172,8 @@ function run(entry, input, options = {}) {
6860
7172
  build: options.build,
6861
7173
  state: options.state,
6862
7174
  ...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...grammarTrivia._meta.triviaKindLabels ? { triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {} } : {},
6863
- ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {}
7175
+ ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
7176
+ ...options.tolerant ? { _tolerant: true, _rec: REC } : {}
6864
7177
  };
6865
7178
  const r = invoke(entry, input, 0, ctx);
6866
7179
  let unconsumedFrom = null;
@@ -7246,7 +7559,6 @@ export {
7246
7559
  literal,
7247
7560
  makeWord,
7248
7561
  many,
7249
- manyRecover,
7250
7562
  noTrivia,
7251
7563
  node,
7252
7564
  not,
@@ -7256,7 +7568,6 @@ export {
7256
7568
  parse,
7257
7569
  parseDoc,
7258
7570
  parser,
7259
- recover,
7260
7571
  ref,
7261
7572
  regex,
7262
7573
  relativize,
@@ -7265,7 +7576,6 @@ export {
7265
7576
  run,
7266
7577
  scanTo,
7267
7578
  sepBy,
7268
- sepByRecover,
7269
7579
  sequence,
7270
7580
  shiftAbsolute,
7271
7581
  skip,