parseman 0.24.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/combinators/completions.d.ts +14 -4
  3. package/dist/combinators/completions.d.ts.map +1 -1
  4. package/dist/combinators/expect.d.ts +2 -0
  5. package/dist/combinators/expect.d.ts.map +1 -1
  6. package/dist/combinators/grammar.d.ts +5 -5
  7. package/dist/combinators/not.d.ts.map +1 -1
  8. package/dist/combinators/parser.d.ts +3 -2
  9. package/dist/combinators/parser.d.ts.map +1 -1
  10. package/dist/combinators/repeat.d.ts.map +1 -1
  11. package/dist/combinators/sequence.d.ts.map +1 -1
  12. package/dist/combinators/trivia-skip.d.ts +1 -0
  13. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  14. package/dist/compiler/codegen.d.ts +9 -4
  15. package/dist/compiler/codegen.d.ts.map +1 -1
  16. package/dist/compiler/scannable-run.d.ts +2 -2
  17. package/dist/compiler/scannable-run.d.ts.map +1 -1
  18. package/dist/cst/capture-buffer.d.ts +1 -0
  19. package/dist/cst/capture-buffer.d.ts.map +1 -1
  20. package/dist/cst/relative-spans.d.ts +32 -0
  21. package/dist/cst/relative-spans.d.ts.map +1 -1
  22. package/dist/cst/types.d.ts +7 -7
  23. package/dist/cst/types.d.ts.map +1 -1
  24. package/dist/functional/doc.d.ts +89 -4
  25. package/dist/functional/doc.d.ts.map +1 -1
  26. package/dist/functional/run.d.ts +12 -1
  27. package/dist/functional/run.d.ts.map +1 -1
  28. package/dist/index.cjs +807 -278
  29. package/dist/index.cjs.map +4 -4
  30. package/dist/index.d.ts +2 -3
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +807 -278
  33. package/dist/index.js.map +4 -4
  34. package/dist/language-service/index.cjs +776 -0
  35. package/dist/language-service/index.cjs.map +7 -0
  36. package/dist/language-service/index.d.ts +94 -0
  37. package/dist/language-service/index.d.ts.map +1 -0
  38. package/dist/language-service/index.js +753 -0
  39. package/dist/language-service/index.js.map +7 -0
  40. package/dist/plugin/evaluator.d.ts.map +1 -1
  41. package/dist/plugin/index.cjs +568 -129
  42. package/dist/plugin/index.cjs.map +4 -4
  43. package/dist/plugin/index.d.ts +11 -1
  44. package/dist/plugin/index.d.ts.map +1 -1
  45. package/dist/plugin/index.js +568 -129
  46. package/dist/plugin/index.js.map +4 -4
  47. package/dist/recovery/scan.d.ts +54 -0
  48. package/dist/recovery/scan.d.ts.map +1 -0
  49. package/dist/spec/index.cjs +67 -6
  50. package/dist/spec/index.cjs.map +3 -3
  51. package/dist/spec/index.d.ts +10 -2
  52. package/dist/spec/index.d.ts.map +1 -1
  53. package/dist/spec/index.js +67 -6
  54. package/dist/spec/index.js.map +3 -3
  55. package/dist/spec/model.d.ts.map +1 -1
  56. package/dist/spec/railroad.d.ts +12 -0
  57. package/dist/spec/railroad.d.ts.map +1 -1
  58. package/dist/types.d.ts +60 -6
  59. package/dist/types.d.ts.map +1 -1
  60. package/package.json +8 -2
  61. package/dist/combinators/recover-list.d.ts +0 -41
  62. package/dist/combinators/recover-list.d.ts.map +0 -1
  63. package/dist/combinators/recover.d.ts +0 -20
  64. 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,34 +6654,162 @@ 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;
6657
+ // src/cst/relative-spans.ts
6658
+ function relativize(node2, parentBase = 0) {
6659
+ return {
6660
+ start: node2.start - parentBase,
6661
+ end: node2.end - parentBase,
6662
+ children: node2.children.map((c) => relativize(c, node2.start))
6663
+ };
6664
+ }
6665
+ function absolutize(node2, parentBase = 0) {
6666
+ const start = parentBase + node2.start;
6667
+ return {
6668
+ start,
6669
+ end: parentBase + node2.end,
6670
+ children: node2.children.map((c) => absolutize(c, start))
6671
+ };
6672
+ }
6673
+ function absoluteSpanAt(root, path) {
6674
+ let base = 0;
6675
+ let node2 = root;
6676
+ for (const idx of path) {
6677
+ base += node2.start;
6678
+ const child = node2.children[idx];
6679
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6680
+ node2 = child;
6681
+ }
6682
+ return { start: base + node2.start, end: base + node2.end };
6683
+ }
6684
+ function relativizeCST(node2, parentBase = 0) {
6685
+ const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
6686
+ if (node2.children) {
6687
+ const base = node2.span.start;
6688
+ return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
6689
+ }
6690
+ return { ...node2, span };
6691
+ }
6692
+ function absolutizeCST(node2, parentBase = 0) {
6693
+ const start = parentBase + node2.span.start;
6694
+ const span = { start, end: parentBase + node2.span.end };
6695
+ if (node2.children) {
6696
+ return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
6697
+ }
6698
+ return { ...node2, span };
6699
+ }
6700
+ function absoluteSpanCST(root, path) {
6701
+ let base = 0;
6702
+ let node2 = root;
6703
+ for (const idx of path) {
6704
+ base += node2.span.start;
6705
+ const child = node2.children?.[idx];
6706
+ if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
6707
+ node2 = child;
6708
+ }
6709
+ return { start: base + node2.span.start, end: base + node2.span.end };
6387
6710
  }
6388
- function sepByRecover(item, separator, until) {
6389
- const sentinel = choice(separator, until);
6390
- return sepBy(tolerantElement(item, sentinel, until), separator);
6711
+ function shiftAbsolute(node2, at, delta) {
6712
+ const s = node2.start >= at ? node2.start + delta : node2.start;
6713
+ const e = node2.end >= at ? node2.end + delta : node2.end;
6714
+ return {
6715
+ start: s,
6716
+ end: e,
6717
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6718
+ };
6391
6719
  }
6392
- function manyRecover(item, until) {
6393
- return many(tolerantElement(item, until, until));
6720
+ function applyEdit(root, at, delta, stats) {
6721
+ return editNode(root, at, delta, 0, 0, stats);
6722
+ }
6723
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
6724
+ const absStart = parentBase + node2.start;
6725
+ const absEnd = parentBase + node2.end;
6726
+ if (absEnd < at) return node2;
6727
+ const startShifts = absStart >= at;
6728
+ if (startShifts && parentShift === delta) return node2;
6729
+ if (stats) stats.allocated++;
6730
+ const newAbsStart = startShifts ? absStart + delta : absStart;
6731
+ const newAbsEnd = absEnd + delta;
6732
+ const newParentBase = parentBase + parentShift;
6733
+ const newStart = newAbsStart - newParentBase;
6734
+ const newEnd = newAbsEnd - newParentBase;
6735
+ const ownShift = newAbsStart - absStart;
6736
+ let changed = false;
6737
+ const newChildren = [];
6738
+ for (const c of node2.children) {
6739
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
6740
+ if (nc !== c) changed = true;
6741
+ newChildren.push(nc);
6742
+ }
6743
+ return {
6744
+ start: newStart,
6745
+ end: newEnd,
6746
+ children: changed ? newChildren : node2.children
6747
+ };
6394
6748
  }
6395
6749
 
6396
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
+ }
6754
+ function asRuleFn(entry) {
6755
+ return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
6756
+ }
6757
+ function defOf(entry) {
6758
+ return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
6759
+ }
6760
+ function producesRepetition(def, depth = 0) {
6761
+ if (!def || depth > 24) return false;
6762
+ const d = def;
6763
+ switch (d.tag) {
6764
+ case "sepBy":
6765
+ case "many":
6766
+ case "oneOrMore":
6767
+ return true;
6768
+ // Rule entries and `ref`s wrap their body in a `lazy` thunk — resolve it. The
6769
+ // `depth` cap bounds any self-referential cycle (a repetition, if present, is
6770
+ // found shallowly before recursion goes deep).
6771
+ case "lazy": {
6772
+ const thunk = d.thunk;
6773
+ let inner;
6774
+ try {
6775
+ inner = typeof thunk === "function" ? thunk() : void 0;
6776
+ } catch {
6777
+ return false;
6778
+ }
6779
+ return producesRepetition(inner?._def, depth + 1);
6780
+ }
6781
+ // Transparent wrappers — look through to the inner parser.
6782
+ case "node":
6783
+ case "transform":
6784
+ case "token":
6785
+ case "label":
6786
+ case "field":
6787
+ case "expect":
6788
+ case "withCtx":
6789
+ case "grammar":
6790
+ case "optional":
6791
+ return producesRepetition(defOf(d.parser), depth + 1);
6792
+ case "skip":
6793
+ return producesRepetition(defOf(d.main), depth + 1);
6794
+ // A bracketed/anchored list is a sequence whose element run is a repetition.
6795
+ case "sequence":
6796
+ return d.parsers.some((p) => producesRepetition(defOf(p), depth + 1));
6797
+ default:
6798
+ return false;
6799
+ }
6800
+ }
6397
6801
  var REENTRY_MAX_SPAN_FRACTION = 0.5;
6398
6802
  function isNode(x) {
6399
6803
  return typeof x === "object" && x !== null && x._tag === "node";
6400
6804
  }
6401
- function findContaining(node2, pos, path = []) {
6805
+ function findContaining(node2, nodeAbsStart, pos, path = []) {
6402
6806
  for (let i = 0; i < node2.children.length; i++) {
6403
6807
  const child = node2.children[i];
6404
6808
  if (!isNode(child)) continue;
6405
- if (child.span.start <= pos && pos < child.span.end) {
6406
- const inner = findContaining(child, pos, [...path, i]);
6809
+ const childAbsStart = nodeAbsStart + child.span.start;
6810
+ const childAbsEnd = nodeAbsStart + child.span.end;
6811
+ if (childAbsStart <= pos && pos < childAbsEnd) {
6812
+ const inner = findContaining(child, childAbsStart, pos, [...path, i]);
6407
6813
  return inner ?? { node: child, path: [...path, i] };
6408
6814
  }
6409
6815
  }
@@ -6430,24 +6836,20 @@ function replaceAtPath(rebuild, root, path, newNode) {
6430
6836
  function defaultRebuild(node2, children) {
6431
6837
  return { ...node2, children };
6432
6838
  }
6433
- function shiftSpans(child, delta) {
6839
+ function shiftRelStart(child, delta) {
6434
6840
  const c = child;
6435
- const span = { start: c.span.start + delta, end: c.span.end + delta };
6436
- if (Array.isArray(c.children)) {
6437
- return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
6438
- }
6439
- return { ...c, span };
6841
+ return { ...c, span: { start: c.span.start + delta, end: c.span.end + delta } };
6440
6842
  }
6441
- function graftAndShift(root, path, newNode, delta) {
6843
+ function graftRelative(root, path, newNode, delta) {
6442
6844
  if (path.length === 0) return newNode;
6443
6845
  const [idx, ...rest] = path;
6444
6846
  const oldChildren = root.children;
6445
6847
  const newChildren = oldChildren.map((child, i) => {
6446
6848
  if (i < idx) return child;
6447
6849
  if (i === idx) {
6448
- return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
6850
+ return rest.length === 0 ? newNode : graftRelative(child, rest, newNode, delta);
6449
6851
  }
6450
- return shiftSpans(child, delta);
6852
+ return shiftRelStart(child, delta);
6451
6853
  });
6452
6854
  return {
6453
6855
  ...root,
@@ -6455,6 +6857,96 @@ function graftAndShift(root, path, newNode, delta) {
6455
6857
  children: newChildren
6456
6858
  };
6457
6859
  }
6860
+ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, tolerant) {
6861
+ const ctx = mkCtx(null, build, tolerant);
6862
+ const out = [];
6863
+ let pos = reStart;
6864
+ let guard2 = 0;
6865
+ while (pos < reEnd) {
6866
+ if (++guard2 > reEnd - reStart + 2) return null;
6867
+ if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
6868
+ if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
6869
+ out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
6870
+ pos += separator.length;
6871
+ continue;
6872
+ }
6873
+ let r;
6874
+ try {
6875
+ r = ruleFn(input, pos, ctx);
6876
+ } catch {
6877
+ return null;
6878
+ }
6879
+ if (!r.ok || r.span.end <= pos || r.span.end > reEnd) return null;
6880
+ out.push(relativizeCST(r.value, cStart));
6881
+ pos = r.span.end;
6882
+ }
6883
+ return pos === reEnd ? out : null;
6884
+ }
6885
+ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build, tolerant) {
6886
+ const kids = C.children;
6887
+ if (kids.length === 0) return null;
6888
+ let h = 0;
6889
+ while (h < kids.length && cStart + kids[h].span.end <= from) h++;
6890
+ while (h > 0 && kids[h - 1]._tag === "node") h--;
6891
+ let t = kids.length;
6892
+ while (t > h && cStart + kids[t - 1].span.start >= to) t--;
6893
+ while (t < kids.length && kids[t]._tag !== "node") t++;
6894
+ if (t >= kids.length) return null;
6895
+ for (let i = t; i < kids.length; i++) {
6896
+ if (kids[i]._tag === "node" && kids[i].state != null) return null;
6897
+ }
6898
+ let elemType;
6899
+ let elemCount = 0;
6900
+ for (const k of kids) {
6901
+ if (k._tag !== "node") continue;
6902
+ elemCount++;
6903
+ if (elemType === void 0) elemType = k.type;
6904
+ else if (k.type !== elemType) return null;
6905
+ }
6906
+ if (elemType === void 0 || elemCount < 2) return null;
6907
+ const ruleFn = registry[elemType];
6908
+ if (!ruleFn) return null;
6909
+ let separator = null;
6910
+ for (let i = 1; i < kids.length - 1; i++) {
6911
+ if (kids[i - 1]._tag === "node" && kids[i + 1]._tag === "node") {
6912
+ if (kids[i]._tag !== "leaf") return null;
6913
+ const sep = kids[i].value ?? null;
6914
+ if (separator === null) separator = sep;
6915
+ else if (sep !== separator) return null;
6916
+ }
6917
+ }
6918
+ if (separator === null) return null;
6919
+ const first = kids[0];
6920
+ const last = kids[kids.length - 1];
6921
+ if (first._tag === "leaf" && first.value !== separator && from < cStart + first.span.end) return null;
6922
+ if (last._tag === "leaf" && last.value !== separator && to > cStart + last.span.start) return null;
6923
+ const reStart = h > 0 ? cStart + kids[h - 1].span.end : cStart;
6924
+ const reEnd = cStart + kids[t].span.start + delta;
6925
+ if (reEnd < reStart) return null;
6926
+ const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
6927
+ if (!middle) return null;
6928
+ if (middle.length > 0) {
6929
+ const last2 = middle[middle.length - 1];
6930
+ if (!(last2._tag === "leaf" && last2.value === separator)) return null;
6931
+ }
6932
+ if (reEnd < newInput.length) {
6933
+ for (const sentinel of [" ", "\uFFFF"]) {
6934
+ if (newInput[reEnd] === sentinel) continue;
6935
+ const probed = newInput.slice(0, reEnd) + sentinel.repeat(newInput.length - reEnd);
6936
+ const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
6937
+ if (!probe || probe.length !== middle.length) return null;
6938
+ for (let i = 0; i < middle.length; i++) if (!structurallyEqual(probe[i], middle[i])) return null;
6939
+ }
6940
+ }
6941
+ const head = kids.slice(0, h);
6942
+ const tail = kids.slice(t).map((k) => shiftRelStart(k, delta));
6943
+ const newC = {
6944
+ ...C,
6945
+ span: { start: C.span.start, end: C.span.end + delta },
6946
+ children: [...head, ...middle, ...tail]
6947
+ };
6948
+ return graftRelative(root, path, newC, delta);
6949
+ }
6458
6950
  function structurallyEqual(a, b) {
6459
6951
  if (a === b) return true;
6460
6952
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
@@ -6469,6 +6961,11 @@ function structurallyEqual(a, b) {
6469
6961
  if (at === "leaf" || at === "trivia") {
6470
6962
  return a.value === b.value;
6471
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
+ }
6472
6969
  if (a.type !== b.type) return false;
6473
6970
  const ac = a.children;
6474
6971
  const bc = b.children;
@@ -6478,13 +6975,13 @@ function structurallyEqual(a, b) {
6478
6975
  }
6479
6976
  return true;
6480
6977
  }
6481
- function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
6978
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced, tolerant) {
6482
6979
  if (!produced.ok) return false;
6483
6980
  if (boundary >= newInput.length) return true;
6484
6981
  for (const sentinel of [" ", "\uFFFF"]) {
6485
6982
  if (newInput[boundary] === sentinel) continue;
6486
6983
  const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
6487
- const ctx = { trackLines: false, state, build };
6984
+ const ctx = mkCtx(state, build, tolerant);
6488
6985
  let r;
6489
6986
  try {
6490
6987
  r = ruleFn(probed, start, ctx);
@@ -6498,27 +6995,83 @@ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produce
6498
6995
  }
6499
6996
  var ParseDocImpl = class _ParseDocImpl {
6500
6997
  _registry;
6998
+ /** Registry entries normalized to callable parse functions (memoized once). */
6999
+ _fns;
7000
+ /** Rule names the grammar proves are genuine repetitions — the ONLY splice-safe types. */
7001
+ _reps;
6501
7002
  _rootRule;
6502
7003
  _opts;
6503
- tree;
7004
+ /**
7005
+ * The tree, held in whichever coordinate system it arrived in. A fresh parse
7006
+ * arrives ABSOLUTE (`_abs` set); an incremental graft arrives PARENT-RELATIVE
7007
+ * (`_rel` set). The public `tree` is always the RELATIVE form (so untouched
7008
+ * subtrees stay shared by identity across edits and reads are O(1)); it's
7009
+ * materialized from `_abs` once, on demand, and memoized. `undefined` = not yet
7010
+ * computed; `null` = failed parse. A fresh parse that's never edited nor
7011
+ * tree-read pays no conversion — important for the full-reparse fallback path.
7012
+ */
7013
+ _abs;
7014
+ _rel;
6504
7015
  errors;
7016
+ unconsumedFrom;
6505
7017
  input;
6506
- constructor(registry, rootRule, opts, tree, errors, input) {
7018
+ constructor(registry, rootRule, opts, trees, errors, unconsumedFrom, input) {
6507
7019
  this._registry = registry;
7020
+ this._fns = {};
7021
+ this._reps = /* @__PURE__ */ new Set();
7022
+ for (const [name, entry] of Object.entries(registry)) {
7023
+ this._fns[name] = asRuleFn(entry);
7024
+ if (producesRepetition(defOf(entry))) this._reps.add(name);
7025
+ }
6508
7026
  this._rootRule = rootRule;
6509
7027
  this._opts = opts;
6510
- this.tree = tree;
7028
+ this._abs = trees.abs;
7029
+ this._rel = trees.rel;
6511
7030
  this.errors = errors;
7031
+ this.unconsumedFrom = unconsumedFrom;
6512
7032
  this.input = input;
6513
7033
  }
7034
+ /**
7035
+ * The parse tree with PARENT-RELATIVE spans — a node's `span` is relative to
7036
+ * its parent's start (root base 0). This is the shareable representation:
7037
+ * untouched subtrees keep the same identity across `.edit()`s, and reading the
7038
+ * tree after a length-changing edit is O(1) (no offset rewrite). For absolute
7039
+ * positions use the O(depth) cursor `spanAt(path)`, or `absolutizeCST(tree)` to
7040
+ * materialize the whole absolute tree. (A fresh non-incremental `node().parse()`
7041
+ * result is unchanged — still absolute.)
7042
+ */
7043
+ get tree() {
7044
+ if (this._rel === void 0) {
7045
+ this._rel = this._abs ? relativizeCST(this._abs, 0) : null;
7046
+ }
7047
+ return this._rel;
7048
+ }
7049
+ spanAt(path) {
7050
+ const rel = this.tree;
7051
+ if (!rel) throw new Error("spanAt on a failed parse (tree is null)");
7052
+ return absoluteSpanCST(rel, path);
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
+ }
6514
7067
  edit(from, to, replacement) {
6515
7068
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
6516
7069
  const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
6517
- if (!this.tree) return reparse();
7070
+ const root = this.tree;
7071
+ if (!root) return reparse();
6518
7072
  const delta = replacement.length - (to - from);
6519
- const found = findContaining(this.tree, from);
6520
- if (!found) return reparse();
6521
- const ancestors = ancestorsAt(this.tree, found.path);
7073
+ const found = findContaining(root, 0, from) ?? { node: root, path: [] };
7074
+ const ancestors = ancestorsAt(root, found.path);
6522
7075
  const candidates = [found];
6523
7076
  const pathCopy = [...found.path];
6524
7077
  for (let i = ancestors.length - 2; i >= 0; i--) {
@@ -6527,37 +7080,78 @@ var ParseDocImpl = class _ParseDocImpl {
6527
7080
  }
6528
7081
  const rebuild = this._opts.rebuild ?? defaultRebuild;
6529
7082
  for (const { node: node2, path } of candidates) {
6530
- if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6531
- const ruleFn = this._registry[node2.type];
7083
+ const { start: absStart, end: absEnd } = absoluteSpanCST(root, path);
7084
+ if (absEnd - absStart > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
7085
+ const ruleFn = this._fns[node2.type];
6532
7086
  if (!ruleFn) continue;
6533
- if (!(node2.span.start <= from && to <= node2.span.end)) continue;
6534
- const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
6535
- const r = ruleFn(newInput, node2.span.start, ctx);
7087
+ if (!(absStart <= from && to <= absEnd)) continue;
7088
+ const ctx = mkCtx(node2.state, this._opts.build, !!this._opts.tolerant);
7089
+ const r = ruleFn(newInput, absStart, ctx);
6536
7090
  if (!r.ok) continue;
6537
- if (r.span.end !== node2.span.end + delta) continue;
6538
- if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
7091
+ if (r.span.end !== absEnd + delta) continue;
7092
+ if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r, !!this._opts.tolerant)) {
6539
7093
  continue;
6540
7094
  }
7095
+ const parentBase = absStart - node2.span.start;
7096
+ const newRel = relativizeCST(r.value, parentBase);
6541
7097
  if (delta === 0) {
6542
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
6543
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
7098
+ const newTree2 = replaceAtPath(rebuild, root, path, newRel);
7099
+ return this.wrapReuse(newTree2, newInput);
6544
7100
  }
6545
7101
  if (this._opts.rebuild) return reparse();
6546
- const newTree = graftAndShift(this.tree, path, r.value, delta);
6547
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
7102
+ const newTree = graftRelative(root, path, newRel, delta);
7103
+ return this.wrapReuse(newTree, newInput);
7104
+ }
7105
+ if (this._opts.structuralReuse && delta !== 0 && !this._opts.rebuild) {
7106
+ const spliceCandidates = [...candidates, { node: root, path: [] }];
7107
+ for (const { node: node2, path } of spliceCandidates) {
7108
+ if (!this._reps.has(node2.type)) continue;
7109
+ const { start: cStart, end: cEnd } = absoluteSpanCST(root, path);
7110
+ if (!(cStart <= from && to <= cEnd)) continue;
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);
7113
+ }
6548
7114
  }
6549
7115
  return reparse();
6550
7116
  }
6551
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
+ }
6552
7143
  function parseDoc(registry, rootRule, input, opts = {}) {
6553
- const ruleFn = registry[rootRule];
6554
- if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
6555
- const ctx = { trackLines: false, state: opts.state, build: opts.build };
6556
- const r = ruleFn(input, 0, ctx);
7144
+ const entry = registry[rootRule];
7145
+ if (!entry) throw new Error(`No rule '${rootRule}' in registry`);
7146
+ const ctx = mkCtx(opts.state, opts.build, !!opts.tolerant);
7147
+ const r = asRuleFn(entry)(input, 0, ctx);
6557
7148
  if (r.ok) {
6558
- return new ParseDocImpl(registry, rootRule, opts, 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);
6559
7152
  }
6560
- return new ParseDocImpl(registry, rootRule, opts, 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);
6561
7155
  }
6562
7156
 
6563
7157
  // src/functional/run.ts
@@ -6578,7 +7172,8 @@ function run(entry, input, options = {}) {
6578
7172
  build: options.build,
6579
7173
  state: options.state,
6580
7174
  ...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...grammarTrivia._meta.triviaKindLabels ? { triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {} } : {},
6581
- ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {}
7175
+ ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
7176
+ ...options.tolerant ? { _tolerant: true, _rec: REC } : {}
6582
7177
  };
6583
7178
  const r = invoke(entry, input, 0, ctx);
6584
7179
  let unconsumedFrom = null;
@@ -6926,78 +7521,14 @@ function buildOffsetIndex(root, input = "") {
6926
7521
  return new OffsetIndex(collectLeafSlots(root), 0, input.length);
6927
7522
  }
6928
7523
 
6929
- // src/cst/relative-spans.ts
6930
- function relativize(node2, parentBase = 0) {
6931
- return {
6932
- start: node2.start - parentBase,
6933
- end: node2.end - parentBase,
6934
- children: node2.children.map((c) => relativize(c, node2.start))
6935
- };
6936
- }
6937
- function absolutize(node2, parentBase = 0) {
6938
- const start = parentBase + node2.start;
6939
- return {
6940
- start,
6941
- end: parentBase + node2.end,
6942
- children: node2.children.map((c) => absolutize(c, start))
6943
- };
6944
- }
6945
- function absoluteSpanAt(root, path) {
6946
- let base = 0;
6947
- let node2 = root;
6948
- for (const idx of path) {
6949
- base += node2.start;
6950
- const child = node2.children[idx];
6951
- if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6952
- node2 = child;
6953
- }
6954
- return { start: base + node2.start, end: base + node2.end };
6955
- }
6956
- function shiftAbsolute(node2, at, delta) {
6957
- const s = node2.start >= at ? node2.start + delta : node2.start;
6958
- const e = node2.end >= at ? node2.end + delta : node2.end;
6959
- return {
6960
- start: s,
6961
- end: e,
6962
- children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6963
- };
6964
- }
6965
- function applyEdit(root, at, delta, stats) {
6966
- return editNode(root, at, delta, 0, 0, stats);
6967
- }
6968
- function editNode(node2, at, delta, parentBase, parentShift, stats) {
6969
- const absStart = parentBase + node2.start;
6970
- const absEnd = parentBase + node2.end;
6971
- if (absEnd < at) return node2;
6972
- const startShifts = absStart >= at;
6973
- if (startShifts && parentShift === delta) return node2;
6974
- if (stats) stats.allocated++;
6975
- const newAbsStart = startShifts ? absStart + delta : absStart;
6976
- const newAbsEnd = absEnd + delta;
6977
- const newParentBase = parentBase + parentShift;
6978
- const newStart = newAbsStart - newParentBase;
6979
- const newEnd = newAbsEnd - newParentBase;
6980
- const ownShift = newAbsStart - absStart;
6981
- let changed = false;
6982
- const newChildren = [];
6983
- for (const c of node2.children) {
6984
- const nc = editNode(c, at, delta, absStart, ownShift, stats);
6985
- if (nc !== c) changed = true;
6986
- newChildren.push(nc);
6987
- }
6988
- return {
6989
- start: newStart,
6990
- end: newEnd,
6991
- children: changed ? newChildren : node2.children
6992
- };
6993
- }
6994
-
6995
7524
  // src/index.ts
6996
7525
  registerRegexAnalyzer(firstSetFromRegex);
6997
7526
  export {
6998
7527
  OffsetIndex,
6999
7528
  absoluteSpanAt,
7529
+ absoluteSpanCST,
7000
7530
  absolutize,
7531
+ absolutizeCST,
7001
7532
  annotateSpan,
7002
7533
  applyEdit,
7003
7534
  balanced,
@@ -7028,7 +7559,6 @@ export {
7028
7559
  literal,
7029
7560
  makeWord,
7030
7561
  many,
7031
- manyRecover,
7032
7562
  noTrivia,
7033
7563
  node,
7034
7564
  not,
@@ -7038,15 +7568,14 @@ export {
7038
7568
  parse,
7039
7569
  parseDoc,
7040
7570
  parser,
7041
- recover,
7042
7571
  ref,
7043
7572
  regex,
7044
7573
  relativize,
7574
+ relativizeCST,
7045
7575
  rules,
7046
7576
  run,
7047
7577
  scanTo,
7048
7578
  sepBy,
7049
- sepByRecover,
7050
7579
  sequence,
7051
7580
  shiftAbsolute,
7052
7581
  skip,