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.cjs CHANGED
@@ -22,7 +22,9 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  OffsetIndex: () => OffsetIndex,
24
24
  absoluteSpanAt: () => absoluteSpanAt,
25
+ absoluteSpanCST: () => absoluteSpanCST,
25
26
  absolutize: () => absolutize,
27
+ absolutizeCST: () => absolutizeCST,
26
28
  annotateSpan: () => annotateSpan,
27
29
  applyEdit: () => applyEdit,
28
30
  balanced: () => balanced,
@@ -53,7 +55,6 @@ __export(index_exports, {
53
55
  literal: () => literal,
54
56
  makeWord: () => makeWord,
55
57
  many: () => many,
56
- manyRecover: () => manyRecover,
57
58
  noTrivia: () => noTrivia,
58
59
  node: () => node,
59
60
  not: () => not,
@@ -63,15 +64,14 @@ __export(index_exports, {
63
64
  parse: () => parse,
64
65
  parseDoc: () => parseDoc,
65
66
  parser: () => parser,
66
- recover: () => recover,
67
67
  ref: () => ref,
68
68
  regex: () => regex,
69
69
  relativize: () => relativize,
70
+ relativizeCST: () => relativizeCST,
70
71
  rules: () => rules,
71
72
  run: () => run,
72
73
  scanTo: () => scanTo,
73
74
  sepBy: () => sepBy,
74
- sepByRecover: () => sepByRecover,
75
75
  sequence: () => sequence,
76
76
  shiftAbsolute: () => shiftAbsolute,
77
77
  skip: () => skip,
@@ -341,7 +341,13 @@ function saveCstMark(ctx) {
341
341
  raw: cstRawLen(ctx),
342
342
  tlog: cstTlLen(ctx),
343
343
  leaves: cstLeavesLen(ctx),
344
- fields: ctx._fields?.length ?? 0
344
+ fields: ctx._fields?.length ?? 0,
345
+ // Speculative rollback must truncate the flat recovery-error sink in lockstep
346
+ // with the CST leaves: a branch that recovered (pushing a ParseError to
347
+ // ctx._errors AND embedding it as a CST child via captureError) then failed
348
+ // must remove BOTH, or the flat error survives as a GHOST that a tree walk
349
+ // can't see (flat run().errors would disagree with the embedded set).
350
+ errors: ctx._errors?.length ?? 0
345
351
  };
346
352
  }
347
353
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -357,6 +363,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
357
363
  if (len === 0) b[keySingle] = void 0;
358
364
  }
359
365
  function rollbackCstCapture(ctx, mark) {
366
+ if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
360
367
  const b = ctx._cstBuf;
361
368
  if (b) {
362
369
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
@@ -1303,10 +1310,10 @@ function needsDeferredTriviaCommit(ctx) {
1303
1310
  }
1304
1311
  function saveTriviaMark(ctx) {
1305
1312
  const m = saveCstMark(ctx);
1306
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1313
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1307
1314
  }
1308
1315
  function rollbackTrivia(ctx, mark) {
1309
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1316
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1310
1317
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1311
1318
  }
1312
1319
  function scanWithLabels(input, cur, ctx) {
@@ -1540,6 +1547,94 @@ function scanBlockComment(input, cur) {
1540
1547
  return close === -1 ? cur : close + 2;
1541
1548
  }
1542
1549
 
1550
+ // src/recovery/scan.ts
1551
+ function probeAt(sentinel, input, pos, ctx) {
1552
+ const s = {
1553
+ _probe: ctx._probe,
1554
+ _errors: ctx._errors,
1555
+ _cstLeaves: ctx._cstLeaves,
1556
+ _cstChildren: ctx._cstChildren,
1557
+ _cstRawChildren: ctx._cstRawChildren,
1558
+ _cstTriviaLog: ctx._cstTriviaLog,
1559
+ _triviaLog: ctx._triviaLog,
1560
+ _fields: ctx._fields,
1561
+ _cstBuf: ctx._cstBuf,
1562
+ _tolerant: ctx._tolerant,
1563
+ _sync: ctx._sync
1564
+ };
1565
+ ctx._probe = void 0;
1566
+ ctx._errors = void 0;
1567
+ ctx._cstLeaves = void 0;
1568
+ ctx._cstChildren = void 0;
1569
+ ctx._cstRawChildren = void 0;
1570
+ ctx._cstTriviaLog = void 0;
1571
+ ctx._triviaLog = void 0;
1572
+ ctx._fields = void 0;
1573
+ ctx._cstBuf = void 0;
1574
+ ctx._tolerant = void 0;
1575
+ ctx._sync = void 0;
1576
+ try {
1577
+ return sentinel.parse(input, pos, ctx).ok;
1578
+ } finally {
1579
+ ctx._probe = s._probe;
1580
+ ctx._errors = s._errors;
1581
+ ctx._cstLeaves = s._cstLeaves;
1582
+ ctx._cstChildren = s._cstChildren;
1583
+ ctx._cstRawChildren = s._cstRawChildren;
1584
+ ctx._cstTriviaLog = s._cstTriviaLog;
1585
+ ctx._triviaLog = s._triviaLog;
1586
+ ctx._fields = s._fields;
1587
+ ctx._cstBuf = s._cstBuf;
1588
+ ctx._tolerant = s._tolerant;
1589
+ ctx._sync = s._sync;
1590
+ }
1591
+ }
1592
+ function orSentinel(a, b) {
1593
+ if (b === void 0) return a;
1594
+ return {
1595
+ _tag: "orSentinel",
1596
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
1597
+ _def: { tag: "unknown" },
1598
+ parse(input, pos, ctx) {
1599
+ const ra = a.parse(input, pos, ctx);
1600
+ if (ra.ok) return ra;
1601
+ return b.parse(input, pos, ctx);
1602
+ }
1603
+ };
1604
+ }
1605
+ function firstSetSentinel(fs) {
1606
+ if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
1607
+ const ranges = fs.ranges;
1608
+ return {
1609
+ _tag: "firstSetSentinel",
1610
+ _meta: { firstSet: fs, canMatchNewline: false, isTrivia: false },
1611
+ _def: { tag: "unknown" },
1612
+ parse(input, pos) {
1613
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
1614
+ if (code !== void 0) {
1615
+ for (const r of ranges) {
1616
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
1617
+ }
1618
+ }
1619
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
1620
+ }
1621
+ };
1622
+ }
1623
+ function recoverScan(input, from, ctx, sync, expected) {
1624
+ let scanPos = from;
1625
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
1626
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
1627
+ ctx._errors?.push(error);
1628
+ return { error, end: scanPos };
1629
+ }
1630
+ function matchesAt(sentinel, input, pos, ctx) {
1631
+ return probeAt(sentinel, input, pos, ctx);
1632
+ }
1633
+ function captureError(ctx, error) {
1634
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
1635
+ }
1636
+ var REC = { scan: recoverScan, at: matchesAt, or: orSentinel, sentinel: firstSetSentinel, capture: captureError };
1637
+
1543
1638
  // src/combinators/sequence.ts
1544
1639
  function sequence(...parsers) {
1545
1640
  const meta = {
@@ -1550,11 +1645,59 @@ function sequence(...parsers) {
1550
1645
  isTrivia: false
1551
1646
  };
1552
1647
  const def = { tag: "sequence", parsers };
1648
+ let followSentinels;
1649
+ function parseTolerant(input, pos, ctx) {
1650
+ followSentinels ??= parsers.map((_, i) => {
1651
+ const fs = parsers.slice(i + 1).reduce(
1652
+ (acc, p) => union(acc, firstSetOf(p)),
1653
+ { kind: "empty" }
1654
+ );
1655
+ return firstSetSentinel(fs);
1656
+ });
1657
+ const values = def.valueUnused ? void 0 : [];
1658
+ let cur = pos;
1659
+ const inheritedSync = ctx._sync;
1660
+ try {
1661
+ for (let i = 0; i < parsers.length; i++) {
1662
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1663
+ if (ctx.trivia && i > 0) {
1664
+ const mark = saveTriviaMark(ctx);
1665
+ let scanEnd;
1666
+ if (needsDeferredTriviaCommit(ctx)) {
1667
+ const scan = scanTrivia(input, cur, ctx);
1668
+ scan.commit();
1669
+ scanEnd = scan.end;
1670
+ } else {
1671
+ scanEnd = advanceTrivia(input, cur, ctx);
1672
+ }
1673
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1674
+ if (!result2.ok) return result2;
1675
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1676
+ else rollbackTrivia(ctx, mark);
1677
+ if (values !== void 0) values.push(result2.value);
1678
+ continue;
1679
+ }
1680
+ const result = parsers[i].parse(input, cur, ctx);
1681
+ if (!result.ok) return result;
1682
+ if (values !== void 0) values.push(result.value);
1683
+ cur = result.span.end;
1684
+ }
1685
+ } finally {
1686
+ ctx._sync = inheritedSync;
1687
+ }
1688
+ return {
1689
+ ok: true,
1690
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1691
+ value: values ?? void 0,
1692
+ span: { start: pos, end: cur }
1693
+ };
1694
+ }
1553
1695
  return {
1554
1696
  _tag: "sequence",
1555
1697
  _meta: meta,
1556
1698
  _def: def,
1557
1699
  parse(input, pos, ctx) {
1700
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1558
1701
  const values = def.valueUnused ? void 0 : [];
1559
1702
  let cur = pos;
1560
1703
  for (let i = 0; i < parsers.length; i++) {
@@ -1593,6 +1736,69 @@ function sequence(...parsers) {
1593
1736
  };
1594
1737
  }
1595
1738
 
1739
+ // src/combinators/expect.ts
1740
+ function deriveExpected(c) {
1741
+ const def = c._def;
1742
+ switch (def.tag) {
1743
+ case "literal":
1744
+ return [JSON.stringify(def.value)];
1745
+ case "regex":
1746
+ return [`/${def.source}/`];
1747
+ case "keywords":
1748
+ return def.words.map((w) => JSON.stringify(w));
1749
+ case "label":
1750
+ return [def.label];
1751
+ case "choice":
1752
+ return def.parsers.flatMap(deriveExpected);
1753
+ case "sequence":
1754
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1755
+ case "node":
1756
+ case "grammar":
1757
+ case "trivia":
1758
+ case "token":
1759
+ case "optional":
1760
+ case "many":
1761
+ case "oneOrMore":
1762
+ case "transform":
1763
+ case "not":
1764
+ return deriveExpected(def.parser);
1765
+ case "lazy": {
1766
+ const name = c._ruleName;
1767
+ try {
1768
+ return deriveExpected(def.thunk());
1769
+ } catch {
1770
+ return name ? [name] : [];
1771
+ }
1772
+ }
1773
+ default:
1774
+ return [];
1775
+ }
1776
+ }
1777
+ function expect(combinator, label2) {
1778
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1779
+ const meta = {
1780
+ firstSet: combinator._meta.firstSet,
1781
+ canMatchNewline: combinator._meta.canMatchNewline,
1782
+ isTrivia: false
1783
+ };
1784
+ return {
1785
+ _tag: "expect",
1786
+ _meta: meta,
1787
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1788
+ parse(input, pos, ctx) {
1789
+ const result = combinator.parse(input, pos, ctx);
1790
+ if (result.ok) return result;
1791
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1792
+ ctx._errors?.push(error);
1793
+ if (ctx._tolerant) captureError(ctx, error);
1794
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1795
+ }
1796
+ };
1797
+ }
1798
+ function isParseError(value) {
1799
+ return typeof value === "object" && value !== null && value._tag === "parseError";
1800
+ }
1801
+
1596
1802
  // src/combinators/repeat.ts
1597
1803
  function repItem(combinator, input, cur, ctx) {
1598
1804
  const mark = saveTriviaMark(ctx);
@@ -1613,7 +1819,7 @@ function repItem(combinator, input, cur, ctx) {
1613
1819
  const result = combinator.parse(input, pos, ctx);
1614
1820
  if (!result.ok) {
1615
1821
  rollbackTrivia(ctx, mark);
1616
- return { fail: result };
1822
+ return { fail: result, failPos: pos };
1617
1823
  }
1618
1824
  if (result.span.end === pos) {
1619
1825
  rollbackTrivia(ctx, mark);
@@ -1628,6 +1834,7 @@ function many(combinator) {
1628
1834
  isTrivia: false
1629
1835
  };
1630
1836
  const def = { tag: "many", parser: combinator, min: 0 };
1837
+ let expected;
1631
1838
  return {
1632
1839
  _tag: "many",
1633
1840
  _meta: meta,
@@ -1637,7 +1844,18 @@ function many(combinator) {
1637
1844
  let cur = pos;
1638
1845
  while (cur < input.length) {
1639
1846
  const item = repItem(combinator, input, cur, ctx);
1640
- if (item === "stop" || "fail" in item) break;
1847
+ if (item === "stop") break;
1848
+ if ("fail" in item) {
1849
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1850
+ if (sync === void 0) break;
1851
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1852
+ expected ??= deriveExpected(combinator);
1853
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1854
+ if (values !== void 0) values.push(error);
1855
+ captureError(ctx, error);
1856
+ cur = end;
1857
+ continue;
1858
+ }
1641
1859
  if (values !== void 0) values.push(item.value);
1642
1860
  cur = item.end;
1643
1861
  }
@@ -1652,6 +1870,7 @@ function oneOrMore(combinator) {
1652
1870
  isTrivia: false
1653
1871
  };
1654
1872
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1873
+ let expected;
1655
1874
  return {
1656
1875
  _tag: "oneOrMore",
1657
1876
  _meta: meta,
@@ -1663,7 +1882,18 @@ function oneOrMore(combinator) {
1663
1882
  let cur = first.span.end;
1664
1883
  while (cur < input.length) {
1665
1884
  const item = repItem(combinator, input, cur, ctx);
1666
- if (item === "stop" || "fail" in item) break;
1885
+ if (item === "stop") break;
1886
+ if ("fail" in item) {
1887
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1888
+ if (sync === void 0) break;
1889
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1890
+ expected ??= deriveExpected(combinator);
1891
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1892
+ if (values !== void 0) values.push(error);
1893
+ captureError(ctx, error);
1894
+ cur = end;
1895
+ continue;
1896
+ }
1667
1897
  if (values !== void 0) values.push(item.value);
1668
1898
  cur = item.end;
1669
1899
  }
@@ -1709,15 +1939,29 @@ function sepBy(combinator, separator) {
1709
1939
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1710
1940
  isTrivia: false
1711
1941
  };
1942
+ let expected;
1712
1943
  return {
1713
1944
  _tag: "sepBy",
1714
1945
  _meta: meta,
1715
1946
  _def: { tag: "sepBy", parser: combinator, separator },
1716
1947
  parse(input, pos, ctx) {
1717
1948
  const first = combinator.parse(input, pos, ctx);
1718
- if (!first.ok) return { ok: true, value: [], span: { start: pos, end: pos } };
1719
- const values = [first.value];
1720
- let cur = first.span.end;
1949
+ const values = [];
1950
+ let cur;
1951
+ if (first.ok) {
1952
+ values.push(first.value);
1953
+ cur = first.span.end;
1954
+ } else {
1955
+ const term = ctx._tolerant ? ctx._sync : void 0;
1956
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1957
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1958
+ }
1959
+ expected ??= deriveExpected(combinator);
1960
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1961
+ values.push(rec.error);
1962
+ captureError(ctx, rec.error);
1963
+ cur = rec.end;
1964
+ }
1721
1965
  while (cur < input.length) {
1722
1966
  const loopMark = saveTriviaMark(ctx);
1723
1967
  let sepPos = cur;
@@ -1747,6 +1991,15 @@ function sepBy(combinator, separator) {
1747
1991
  }
1748
1992
  const next = combinator.parse(input, nextPos, ctx);
1749
1993
  if (!next.ok) {
1994
+ const term = ctx._tolerant ? ctx._sync : void 0;
1995
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1996
+ expected ??= deriveExpected(combinator);
1997
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1998
+ values.push(rec.error);
1999
+ captureError(ctx, rec.error);
2000
+ cur = rec.end;
2001
+ continue;
2002
+ }
1750
2003
  rollbackTrivia(ctx, loopMark);
1751
2004
  break;
1752
2005
  }
@@ -1920,7 +2173,7 @@ function rules(a, b) {
1920
2173
  cache[key] = parser2;
1921
2174
  }
1922
2175
  }
1923
- if (options?.trivia !== void 0) {
2176
+ if (options?.trivia != null) {
1924
2177
  for (const key of Object.keys(definitions)) {
1925
2178
  const rule = cache[key];
1926
2179
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1950,7 +2203,9 @@ function not(combinator) {
1950
2203
  _meta: meta,
1951
2204
  _def: { tag: "not", parser: combinator },
1952
2205
  parse(input, pos, ctx) {
2206
+ const mark = saveCstMark(ctx);
1953
2207
  const result = combinator.parse(input, pos, ctx);
2208
+ rollbackCstCapture(ctx, mark);
1954
2209
  if (result.ok) {
1955
2210
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1956
2211
  }
@@ -2064,7 +2319,7 @@ function unwrapChild(child) {
2064
2319
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
2065
2320
  }
2066
2321
  function isCstChild(value) {
2067
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2322
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
2068
2323
  }
2069
2324
  function missingInferredType() {
2070
2325
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2342,65 +2597,6 @@ function token(root) {
2342
2597
  };
2343
2598
  }
2344
2599
 
2345
- // src/combinators/expect.ts
2346
- function deriveExpected(c) {
2347
- const def = c._def;
2348
- switch (def.tag) {
2349
- case "literal":
2350
- return [JSON.stringify(def.value)];
2351
- case "regex":
2352
- return [`/${def.source}/`];
2353
- case "keywords":
2354
- return def.words.map((w) => JSON.stringify(w));
2355
- case "label":
2356
- return [def.label];
2357
- case "choice":
2358
- return def.parsers.flatMap(deriveExpected);
2359
- case "sequence":
2360
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
2361
- case "node":
2362
- case "grammar":
2363
- case "trivia":
2364
- case "token":
2365
- case "optional":
2366
- case "many":
2367
- case "oneOrMore":
2368
- case "transform":
2369
- case "not":
2370
- return deriveExpected(def.parser);
2371
- case "lazy": {
2372
- const name = c._ruleName;
2373
- try {
2374
- return deriveExpected(def.thunk());
2375
- } catch {
2376
- return name ? [name] : [];
2377
- }
2378
- }
2379
- default:
2380
- return [];
2381
- }
2382
- }
2383
- function expect(combinator, label2) {
2384
- const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
2385
- const meta = {
2386
- firstSet: combinator._meta.firstSet,
2387
- canMatchNewline: combinator._meta.canMatchNewline,
2388
- isTrivia: false
2389
- };
2390
- return {
2391
- _tag: "expect",
2392
- _meta: meta,
2393
- _def: { tag: "expect", parser: combinator, label: label2, expected },
2394
- parse(input, pos, ctx) {
2395
- const result = combinator.parse(input, pos, ctx);
2396
- if (result.ok) return result;
2397
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2398
- ctx._errors?.push(error);
2399
- return { ok: true, value: error, span: { start: pos, end: pos } };
2400
- }
2401
- };
2402
- }
2403
-
2404
2600
  // src/compiler/scannable-run.ts
2405
2601
  var BACKSLASH = 92;
2406
2602
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2472,20 +2668,39 @@ function parseSeqParts(source) {
2472
2668
  lit = [];
2473
2669
  }
2474
2670
  };
2475
- const runFrom = (ranges, negated, q) => {
2671
+ const braceQuant = (at) => {
2672
+ const close = source.indexOf("}", at);
2673
+ if (close === -1) return null;
2674
+ const body = source.slice(at + 1, close);
2675
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2676
+ if (!m) return null;
2677
+ const min = Number(m[1]);
2678
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2679
+ if (max < 1 || min > max) return null;
2680
+ return { min, max, len: close - at + 1 };
2681
+ };
2682
+ const runFrom = (ranges, negated, at) => {
2683
+ const q = source[at];
2476
2684
  if (q === "+") {
2477
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2685
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2478
2686
  return 1;
2479
2687
  }
2480
2688
  if (q === "*") {
2481
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2689
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2482
2690
  return 1;
2483
2691
  }
2484
2692
  if (q === "?") {
2485
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2693
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2486
2694
  return 1;
2487
2695
  }
2488
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2696
+ if (q === "{") {
2697
+ const b = braceQuant(at);
2698
+ if (b) {
2699
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2700
+ return b.len;
2701
+ }
2702
+ }
2703
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2489
2704
  return 0;
2490
2705
  };
2491
2706
  let i = 0;
@@ -2510,7 +2725,7 @@ function parseSeqParts(source) {
2510
2725
  if (!ranges) return null;
2511
2726
  flush();
2512
2727
  i = k + 1;
2513
- i += runFrom(ranges, neg, source[i]);
2728
+ i += runFrom(ranges, neg, i);
2514
2729
  continue;
2515
2730
  }
2516
2731
  if (ch === "\\") {
@@ -2518,7 +2733,7 @@ function parseSeqParts(source) {
2518
2733
  if (e === "d" || e === "w" || e === "s") {
2519
2734
  flush();
2520
2735
  i += 2;
2521
- i += runFrom(shorthandRanges(e), false, source[i]);
2736
+ i += runFrom(shorthandRanges(e), false, i);
2522
2737
  continue;
2523
2738
  }
2524
2739
  const uni = readUnicodeEscape(source, i);
@@ -2607,7 +2822,7 @@ function seqIsUnambiguous(parts) {
2607
2822
  let hasMandatory = false;
2608
2823
  for (let k = 0; k < parts.length; k++) {
2609
2824
  const p = parts[k];
2610
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2825
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2611
2826
  if (mandatory) hasMandatory = true;
2612
2827
  if (p.part === "group") {
2613
2828
  const exposure = groupPartExposure(p);
@@ -2618,9 +2833,8 @@ function seqIsUnambiguous(parts) {
2618
2833
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2619
2834
  }
2620
2835
  }
2621
- const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
2622
- const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
2623
- if (skippableSingle || isUnboundedRepeat) {
2836
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2837
+ if (needsDisjoint) {
2624
2838
  const rest = seqFirstAccept(parts.slice(k + 1));
2625
2839
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2626
2840
  }
@@ -2677,8 +2891,8 @@ function fixedClassSeq(shape) {
2677
2891
  if (p.part === "lit") {
2678
2892
  if (p.optional) return null;
2679
2893
  for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
2680
- } else if (p.part === "run" && !p.unbounded && p.min === 1) {
2681
- out.push({ ranges: p.ranges, negated: p.negated });
2894
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2895
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2682
2896
  } else {
2683
2897
  return null;
2684
2898
  }
@@ -2730,7 +2944,7 @@ function trailingBacktrackClass(shape) {
2730
2944
  const last = shape.parts[shape.parts.length - 1];
2731
2945
  if (last.part === "lit") return null;
2732
2946
  if (last.part === "run") {
2733
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2947
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2734
2948
  return null;
2735
2949
  }
2736
2950
  return groupPartExposure(last);
@@ -2867,7 +3081,7 @@ function partFirstAccept(part) {
2867
3081
  function seqFirstAccept(parts) {
2868
3082
  const sets = [];
2869
3083
  for (const p of parts) {
2870
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
3084
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2871
3085
  sets.push(partFirstAccept(p));
2872
3086
  if (mandatory) break;
2873
3087
  }
@@ -3282,7 +3496,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3282
3496
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3283
3497
  lines.push(`${ind2} ${endV2} += ${L}`);
3284
3498
  }
3285
- } else if (p.part === "run" && !p.unbounded) {
3499
+ } else if (p.part === "run" && p.max === Infinity) {
3500
+ const s = mint("_s");
3501
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3502
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3503
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3504
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3505
+ } else if (p.part === "run" && p.max === 1) {
3286
3506
  if (p.min === 1) {
3287
3507
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3288
3508
  lines.push(`${ind2} ${endV2}++`);
@@ -3292,8 +3512,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3292
3512
  } else if (p.part === "run") {
3293
3513
  const s = mint("_s");
3294
3514
  lines.push(`${ind2} const ${s} = ${endV2}`);
3295
- lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3296
- if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3515
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3516
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3297
3517
  } else {
3298
3518
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3299
3519
  if (!p.unbounded) {
@@ -3752,21 +3972,29 @@ function hoistExpected(ctx, constArrSource) {
3752
3972
  }
3753
3973
  return name;
3754
3974
  }
3975
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3976
+ if (!ctx.recovery) return "";
3977
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3978
+ const pb = v(ctx, "_pb");
3979
+ 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} } } } `;
3980
+ }
3755
3981
  function failBody(ctx, expected, posExpr) {
3982
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3756
3983
  if (ctx.failLabel) {
3757
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3758
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3984
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3985
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3759
3986
  }
3760
- return failReturn(expected, posExpr);
3987
+ return probe + failReturn(expected, posExpr);
3761
3988
  }
3762
3989
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3990
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3763
3991
  if (ctx.failLabel) {
3764
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3992
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3765
3993
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3766
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3994
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3767
3995
  }
3768
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3769
- return failReturnArr(expectedArr, posExpr);
3996
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3997
+ return probe + failReturnArr(expectedArr, posExpr);
3770
3998
  }
3771
3999
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3772
4000
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4266,6 +4494,10 @@ function emitRegex(def, ctx, pos) {
4266
4494
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4267
4495
  return { stmts, valueVar: vv, endVar };
4268
4496
  }
4497
+ function syncSentinelExpr(fs) {
4498
+ if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
4499
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs.ranges)} })`;
4500
+ }
4269
4501
  function emitSeqValues(def, ctx, pos) {
4270
4502
  const startV = v(ctx, "_start");
4271
4503
  const curV = v(ctx, "_cur");
@@ -4274,7 +4506,17 @@ function emitSeqValues(def, ctx, pos) {
4274
4506
  `${ind(ctx)}let ${curV} = ${pos}`
4275
4507
  ];
4276
4508
  const valueVars = [];
4509
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4510
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4511
+ const publishSync = (i) => {
4512
+ if (!ctx.recovery) return "";
4513
+ const fs = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4514
+ const ref2 = syncSentinelExpr(fs);
4515
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4516
+ };
4277
4517
  for (let i = 0; i < def.parsers.length; i++) {
4518
+ const syncPub = publishSync(i);
4519
+ if (syncPub) stmts.push(syncPub);
4278
4520
  if (i > 0 && ctx.activeTrivia) {
4279
4521
  if (ctx.capturing) {
4280
4522
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4299,7 +4541,17 @@ function emitSeqValues(def, ctx, pos) {
4299
4541
  continue;
4300
4542
  } else {
4301
4543
  const trivFn = ensureTriviaFn(ctx);
4302
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4544
+ const scanEndV = v(ctx, "_sne");
4545
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4546
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4547
+ stmts.push(...r2.stmts);
4548
+ const endAfterV = v(ctx, "_sea");
4549
+ stmts.push(
4550
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4551
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4552
+ );
4553
+ valueVars.push(r2.valueVar);
4554
+ continue;
4303
4555
  }
4304
4556
  }
4305
4557
  const r = emit(def.parsers[i], ctx, curV);
@@ -4529,7 +4781,7 @@ function emitFirstMatch(def, ctx, pos) {
4529
4781
  if (gate) {
4530
4782
  const gateIdx = ctx.mapFns.length;
4531
4783
  ctx.mapFns.push(gate);
4532
- ctx.mapFnSrcs.push(null);
4784
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4533
4785
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4534
4786
  }
4535
4787
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4634,6 +4886,8 @@ function emitMany(def, ctx, pos) {
4634
4886
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4635
4887
  `${ind(ctx)}let ${curV} = ${pos}`
4636
4888
  ];
4889
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4890
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4637
4891
  if (def.min === 1) {
4638
4892
  const firstR = emit(def.parser, ctx, curV);
4639
4893
  stmts.push(...firstR.stmts);
@@ -4667,10 +4921,26 @@ function emitMany(def, ctx, pos) {
4667
4921
  }
4668
4922
  }
4669
4923
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
4670
- stmts.push(
4671
- ...iterStmts,
4672
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
4673
- );
4924
+ stmts.push(...iterStmts);
4925
+ if (ctx.recovery) {
4926
+ const rrV = v(ctx, "_rr");
4927
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4928
+ stmts.push(
4929
+ `${ind(ctx)}if (!${iterOk}) {`,
4930
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4931
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4932
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4933
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4934
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4935
+ `${ind(ctx)} continue`,
4936
+ `${ind(ctx)} }`,
4937
+ `${ind(ctx)} break`,
4938
+ `${ind(ctx)}}`,
4939
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4940
+ );
4941
+ } else {
4942
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4943
+ }
4674
4944
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4675
4945
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4676
4946
  ctx.indent--;
@@ -4692,20 +4962,56 @@ function emitOptional(def, ctx, pos) {
4692
4962
  function emitSepBy(_p, def, ctx, pos) {
4693
4963
  const arrV = v(ctx, "_arr");
4694
4964
  const curV = v(ctx, "_cur");
4965
+ const rec = !!ctx.recovery;
4966
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4967
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4968
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4969
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4695
4970
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4696
4971
  const stmts = [
4697
4972
  `${ind(ctx)}const ${arrV} = []`,
4698
4973
  `${ind(ctx)}let ${curV} = ${pos}`,
4699
- ...firstStmts,
4700
- `${ind(ctx)}if (${firstOk}) {`
4974
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4975
+ ...firstStmts
4701
4976
  ];
4702
- ctx.indent++;
4703
- stmts.push(
4704
- `${ind(ctx)}${arrV}.push(${firstVal})`,
4705
- `${ind(ctx)}${curV} = ${firstEnd}`,
4706
- `${ind(ctx)}while (${curV} < input.length) {`
4707
- );
4708
- ctx.indent++;
4977
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4978
+ if (!rec) return [originalFail];
4979
+ const rr = v(ctx, "_rr");
4980
+ return [
4981
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4982
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4983
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4984
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4985
+ `${ind(ctx)} }`,
4986
+ `${ind(ctx)} ${rb}break`,
4987
+ `${ind(ctx)}}`
4988
+ ];
4989
+ };
4990
+ if (rec) {
4991
+ const rr0 = v(ctx, "_rr0");
4992
+ const didV = v(ctx, "_did");
4993
+ stmts.push(
4994
+ `${ind(ctx)}let ${didV} = false`,
4995
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4996
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4997
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4998
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4999
+ `${ind(ctx)}}`,
5000
+ `${ind(ctx)}if (${didV}) {`
5001
+ );
5002
+ ctx.indent++;
5003
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
5004
+ ctx.indent++;
5005
+ } else {
5006
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
5007
+ ctx.indent++;
5008
+ stmts.push(
5009
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
5010
+ `${ind(ctx)}${curV} = ${firstEnd}`,
5011
+ `${ind(ctx)}while (${curV} < input.length) {`
5012
+ );
5013
+ ctx.indent++;
5014
+ }
4709
5015
  let sepAtPos = curV;
4710
5016
  if (ctx.activeTrivia) {
4711
5017
  if (ctx.capturing) {
@@ -4731,8 +5037,7 @@ function emitSepBy(_p, def, ctx, pos) {
4731
5037
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4732
5038
  stmts.push(
4733
5039
  ...nextStmts,
4734
- // item failed → unwind the separator too, back to the end of the last item
4735
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
5040
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4736
5041
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4737
5042
  `${ind(ctx)}${curV} = ${nextEnd}`
4738
5043
  );
@@ -4748,7 +5053,7 @@ function emitSepBy(_p, def, ctx, pos) {
4748
5053
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4749
5054
  stmts.push(
4750
5055
  ...nextStmts,
4751
- `${ind(ctx)}if (!${nextOk}) break`,
5056
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4752
5057
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4753
5058
  `${ind(ctx)}${curV} = ${nextEnd}`
4754
5059
  );
@@ -4768,7 +5073,7 @@ function emitSepBy(_p, def, ctx, pos) {
4768
5073
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4769
5074
  stmts.push(
4770
5075
  ...nextStmts,
4771
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
5076
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4772
5077
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4773
5078
  `${ind(ctx)}${curV} = ${nextEnd}`
4774
5079
  );
@@ -5025,7 +5330,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
5025
5330
  stmts.push(
5026
5331
  `${i}const ${ndV} = ${finalExpr}`,
5027
5332
  `${i}if (${sc}) ${sc}.push(${ndV})`,
5028
- `${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} } })`
5333
+ `${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} } })`
5029
5334
  );
5030
5335
  return { stmts, valueVar: ndV, endVar };
5031
5336
  }
@@ -5115,6 +5420,10 @@ function emitExpect(def, ctx, pos) {
5115
5420
  `${ind0}if (!${okVar}) {`,
5116
5421
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5117
5422
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5423
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5424
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5425
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5426
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5118
5427
  `${ind0} ${valVar} = ${errV}`,
5119
5428
  `${ind0} ${endVar} = ${pos}`,
5120
5429
  `${ind0} ${okVar} = true`,
@@ -5281,7 +5590,7 @@ function emitDispatch(p, ctx, pos) {
5281
5590
  case "guard": {
5282
5591
  const fnIdx = ctx.mapFns.length;
5283
5592
  ctx.mapFns.push(def.predicate);
5284
- ctx.mapFnSrcs.push(null);
5593
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5285
5594
  const vv = v(ctx);
5286
5595
  return {
5287
5596
  stmts: [
@@ -5296,7 +5605,7 @@ function emitDispatch(p, ctx, pos) {
5296
5605
  const evIdx = ctx.mapFns.length;
5297
5606
  const extra = def.extra;
5298
5607
  ctx.mapFns.push((() => extra));
5299
- ctx.mapFnSrcs.push(null);
5608
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5300
5609
  const innerParser = def.parser;
5301
5610
  if (!ctx.namedParsers.has(innerParser)) {
5302
5611
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5540,7 +5849,7 @@ function ruleDependencies(ruleMap) {
5540
5849
  }
5541
5850
  return deps;
5542
5851
  }
5543
- function compile(combinator, mapFnSources) {
5852
+ function compile(combinator, mapFnSources, opts) {
5544
5853
  markUnusedValues(combinator);
5545
5854
  const grammarTrivia = combinator._meta.grammarTrivia;
5546
5855
  const ctx = {
@@ -5561,6 +5870,7 @@ function compile(combinator, mapFnSources) {
5561
5870
  triviaFnNames: /* @__PURE__ */ new Map(),
5562
5871
  namedFnDecls: [],
5563
5872
  capturing: hasNodeDef(combinator),
5873
+ recovery: opts?.recovery ?? false,
5564
5874
  lazyUsage: analyzeLazyUsage(combinator),
5565
5875
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5566
5876
  };
@@ -5613,7 +5923,7 @@ function compile(combinator, mapFnSources) {
5613
5923
  parseWithContext(input, parseCtx, pos = 0) {
5614
5924
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5615
5925
  },
5616
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5926
+ // Note: collects expect() errors via _errors. Unlike interpreter
5617
5927
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5618
5928
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5619
5929
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5670,6 +5980,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5670
5980
  triviaFnNames: /* @__PURE__ */ new Map(),
5671
5981
  namedFnDecls: [],
5672
5982
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5983
+ recovery: opts?.recovery ?? false,
5673
5984
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5674
5985
  ns,
5675
5986
  deferFirstSetRefs: true,
@@ -6415,46 +6726,13 @@ function withCtx(extra, combinator) {
6415
6726
  };
6416
6727
  }
6417
6728
 
6418
- // src/combinators/recover.ts
6419
- function recover(combinator, sentinel) {
6420
- const meta = {
6421
- firstSet: { kind: "any" },
6422
- canMatchNewline: true,
6423
- isTrivia: false
6424
- };
6425
- const expected = deriveExpected(combinator);
6426
- return {
6427
- _tag: "recover",
6428
- _meta: meta,
6429
- _def: { tag: "recover", parser: combinator, sentinel },
6430
- parse(input, pos, ctx) {
6431
- const result = combinator.parse(input, pos, ctx);
6432
- if (result.ok) return result;
6433
- let scanPos = pos;
6434
- while (scanPos < input.length) {
6435
- if (sentinel.parse(input, scanPos, ctx).ok) break;
6436
- scanPos++;
6437
- }
6438
- const error = {
6439
- _tag: "parseError",
6440
- span: { start: pos, end: scanPos },
6441
- expected
6442
- };
6443
- ctx._errors?.push(error);
6444
- return { ok: true, value: error, span: { start: pos, end: scanPos } };
6445
- }
6446
- };
6447
- }
6448
- function isParseError(value) {
6449
- return typeof value === "object" && value !== null && value._tag === "parseError";
6450
- }
6451
-
6452
6729
  // src/combinators/completions.ts
6453
- function completionsAt(combinator, input, offset) {
6730
+ function completionsAt(target, input, offset, options = {}) {
6454
6731
  const probe = { offset, best: null };
6455
- const ctx = { trackLines: false, _probe: probe };
6456
- const result = combinator.parse(input.slice(0, offset), 0, ctx);
6457
- if (result.ok) return [];
6732
+ const ctx = { trackLines: false, _probe: probe, ...options.tolerant ? { _tolerant: true, _rec: REC } : {} };
6733
+ const sliced = input.slice(0, offset);
6734
+ const result = "parseWithContext" in target ? target.parseWithContext(sliced, ctx, 0) : target.parse(sliced, 0, ctx);
6735
+ if (result.ok) return options.tolerant ? probe.best?.expected ?? [] : [];
6458
6736
  const best = deeperFail(probe.best, result);
6459
6737
  return best?.expected ?? [];
6460
6738
  }
@@ -6464,34 +6742,162 @@ function deeperFail(a, b) {
6464
6742
  return a.span.start >= b.span.start ? a : b;
6465
6743
  }
6466
6744
 
6467
- // src/combinators/recover-list.ts
6468
- function tolerantElement(item, sentinel, until) {
6469
- const el = transform(
6470
- sequence(not(until), recover(item, sentinel)),
6471
- ([, value]) => value
6472
- );
6473
- if (el._def.tag === "transform") el._def.fnSrc = "([, value]) => value";
6474
- return el;
6745
+ // src/cst/relative-spans.ts
6746
+ function relativize(node2, parentBase = 0) {
6747
+ return {
6748
+ start: node2.start - parentBase,
6749
+ end: node2.end - parentBase,
6750
+ children: node2.children.map((c) => relativize(c, node2.start))
6751
+ };
6752
+ }
6753
+ function absolutize(node2, parentBase = 0) {
6754
+ const start = parentBase + node2.start;
6755
+ return {
6756
+ start,
6757
+ end: parentBase + node2.end,
6758
+ children: node2.children.map((c) => absolutize(c, start))
6759
+ };
6760
+ }
6761
+ function absoluteSpanAt(root, path) {
6762
+ let base = 0;
6763
+ let node2 = root;
6764
+ for (const idx of path) {
6765
+ base += node2.start;
6766
+ const child = node2.children[idx];
6767
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6768
+ node2 = child;
6769
+ }
6770
+ return { start: base + node2.start, end: base + node2.end };
6771
+ }
6772
+ function relativizeCST(node2, parentBase = 0) {
6773
+ const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
6774
+ if (node2.children) {
6775
+ const base = node2.span.start;
6776
+ return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
6777
+ }
6778
+ return { ...node2, span };
6779
+ }
6780
+ function absolutizeCST(node2, parentBase = 0) {
6781
+ const start = parentBase + node2.span.start;
6782
+ const span = { start, end: parentBase + node2.span.end };
6783
+ if (node2.children) {
6784
+ return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
6785
+ }
6786
+ return { ...node2, span };
6787
+ }
6788
+ function absoluteSpanCST(root, path) {
6789
+ let base = 0;
6790
+ let node2 = root;
6791
+ for (const idx of path) {
6792
+ base += node2.span.start;
6793
+ const child = node2.children?.[idx];
6794
+ if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
6795
+ node2 = child;
6796
+ }
6797
+ return { start: base + node2.span.start, end: base + node2.span.end };
6475
6798
  }
6476
- function sepByRecover(item, separator, until) {
6477
- const sentinel = choice(separator, until);
6478
- return sepBy(tolerantElement(item, sentinel, until), separator);
6799
+ function shiftAbsolute(node2, at, delta) {
6800
+ const s = node2.start >= at ? node2.start + delta : node2.start;
6801
+ const e = node2.end >= at ? node2.end + delta : node2.end;
6802
+ return {
6803
+ start: s,
6804
+ end: e,
6805
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6806
+ };
6479
6807
  }
6480
- function manyRecover(item, until) {
6481
- return many(tolerantElement(item, until, until));
6808
+ function applyEdit(root, at, delta, stats) {
6809
+ return editNode(root, at, delta, 0, 0, stats);
6810
+ }
6811
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
6812
+ const absStart = parentBase + node2.start;
6813
+ const absEnd = parentBase + node2.end;
6814
+ if (absEnd < at) return node2;
6815
+ const startShifts = absStart >= at;
6816
+ if (startShifts && parentShift === delta) return node2;
6817
+ if (stats) stats.allocated++;
6818
+ const newAbsStart = startShifts ? absStart + delta : absStart;
6819
+ const newAbsEnd = absEnd + delta;
6820
+ const newParentBase = parentBase + parentShift;
6821
+ const newStart = newAbsStart - newParentBase;
6822
+ const newEnd = newAbsEnd - newParentBase;
6823
+ const ownShift = newAbsStart - absStart;
6824
+ let changed = false;
6825
+ const newChildren = [];
6826
+ for (const c of node2.children) {
6827
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
6828
+ if (nc !== c) changed = true;
6829
+ newChildren.push(nc);
6830
+ }
6831
+ return {
6832
+ start: newStart,
6833
+ end: newEnd,
6834
+ children: changed ? newChildren : node2.children
6835
+ };
6482
6836
  }
6483
6837
 
6484
6838
  // src/functional/doc.ts
6839
+ function mkCtx(state, build, tolerant) {
6840
+ return tolerant ? { trackLines: false, state, build, _tolerant: true, _rec: REC, _errors: [] } : { trackLines: false, state, build };
6841
+ }
6842
+ function asRuleFn(entry) {
6843
+ return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
6844
+ }
6845
+ function defOf(entry) {
6846
+ return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
6847
+ }
6848
+ function producesRepetition(def, depth = 0) {
6849
+ if (!def || depth > 24) return false;
6850
+ const d = def;
6851
+ switch (d.tag) {
6852
+ case "sepBy":
6853
+ case "many":
6854
+ case "oneOrMore":
6855
+ return true;
6856
+ // Rule entries and `ref`s wrap their body in a `lazy` thunk — resolve it. The
6857
+ // `depth` cap bounds any self-referential cycle (a repetition, if present, is
6858
+ // found shallowly before recursion goes deep).
6859
+ case "lazy": {
6860
+ const thunk = d.thunk;
6861
+ let inner;
6862
+ try {
6863
+ inner = typeof thunk === "function" ? thunk() : void 0;
6864
+ } catch {
6865
+ return false;
6866
+ }
6867
+ return producesRepetition(inner?._def, depth + 1);
6868
+ }
6869
+ // Transparent wrappers — look through to the inner parser.
6870
+ case "node":
6871
+ case "transform":
6872
+ case "token":
6873
+ case "label":
6874
+ case "field":
6875
+ case "expect":
6876
+ case "withCtx":
6877
+ case "grammar":
6878
+ case "optional":
6879
+ return producesRepetition(defOf(d.parser), depth + 1);
6880
+ case "skip":
6881
+ return producesRepetition(defOf(d.main), depth + 1);
6882
+ // A bracketed/anchored list is a sequence whose element run is a repetition.
6883
+ case "sequence":
6884
+ return d.parsers.some((p) => producesRepetition(defOf(p), depth + 1));
6885
+ default:
6886
+ return false;
6887
+ }
6888
+ }
6485
6889
  var REENTRY_MAX_SPAN_FRACTION = 0.5;
6486
6890
  function isNode(x) {
6487
6891
  return typeof x === "object" && x !== null && x._tag === "node";
6488
6892
  }
6489
- function findContaining(node2, pos, path = []) {
6893
+ function findContaining(node2, nodeAbsStart, pos, path = []) {
6490
6894
  for (let i = 0; i < node2.children.length; i++) {
6491
6895
  const child = node2.children[i];
6492
6896
  if (!isNode(child)) continue;
6493
- if (child.span.start <= pos && pos < child.span.end) {
6494
- const inner = findContaining(child, pos, [...path, i]);
6897
+ const childAbsStart = nodeAbsStart + child.span.start;
6898
+ const childAbsEnd = nodeAbsStart + child.span.end;
6899
+ if (childAbsStart <= pos && pos < childAbsEnd) {
6900
+ const inner = findContaining(child, childAbsStart, pos, [...path, i]);
6495
6901
  return inner ?? { node: child, path: [...path, i] };
6496
6902
  }
6497
6903
  }
@@ -6518,24 +6924,20 @@ function replaceAtPath(rebuild, root, path, newNode) {
6518
6924
  function defaultRebuild(node2, children) {
6519
6925
  return { ...node2, children };
6520
6926
  }
6521
- function shiftSpans(child, delta) {
6927
+ function shiftRelStart(child, delta) {
6522
6928
  const c = child;
6523
- const span = { start: c.span.start + delta, end: c.span.end + delta };
6524
- if (Array.isArray(c.children)) {
6525
- return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
6526
- }
6527
- return { ...c, span };
6929
+ return { ...c, span: { start: c.span.start + delta, end: c.span.end + delta } };
6528
6930
  }
6529
- function graftAndShift(root, path, newNode, delta) {
6931
+ function graftRelative(root, path, newNode, delta) {
6530
6932
  if (path.length === 0) return newNode;
6531
6933
  const [idx, ...rest] = path;
6532
6934
  const oldChildren = root.children;
6533
6935
  const newChildren = oldChildren.map((child, i) => {
6534
6936
  if (i < idx) return child;
6535
6937
  if (i === idx) {
6536
- return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
6938
+ return rest.length === 0 ? newNode : graftRelative(child, rest, newNode, delta);
6537
6939
  }
6538
- return shiftSpans(child, delta);
6940
+ return shiftRelStart(child, delta);
6539
6941
  });
6540
6942
  return {
6541
6943
  ...root,
@@ -6543,6 +6945,96 @@ function graftAndShift(root, path, newNode, delta) {
6543
6945
  children: newChildren
6544
6946
  };
6545
6947
  }
6948
+ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, tolerant) {
6949
+ const ctx = mkCtx(null, build, tolerant);
6950
+ const out = [];
6951
+ let pos = reStart;
6952
+ let guard2 = 0;
6953
+ while (pos < reEnd) {
6954
+ if (++guard2 > reEnd - reStart + 2) return null;
6955
+ if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
6956
+ if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
6957
+ out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
6958
+ pos += separator.length;
6959
+ continue;
6960
+ }
6961
+ let r;
6962
+ try {
6963
+ r = ruleFn(input, pos, ctx);
6964
+ } catch {
6965
+ return null;
6966
+ }
6967
+ if (!r.ok || r.span.end <= pos || r.span.end > reEnd) return null;
6968
+ out.push(relativizeCST(r.value, cStart));
6969
+ pos = r.span.end;
6970
+ }
6971
+ return pos === reEnd ? out : null;
6972
+ }
6973
+ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build, tolerant) {
6974
+ const kids = C.children;
6975
+ if (kids.length === 0) return null;
6976
+ let h = 0;
6977
+ while (h < kids.length && cStart + kids[h].span.end <= from) h++;
6978
+ while (h > 0 && kids[h - 1]._tag === "node") h--;
6979
+ let t = kids.length;
6980
+ while (t > h && cStart + kids[t - 1].span.start >= to) t--;
6981
+ while (t < kids.length && kids[t]._tag !== "node") t++;
6982
+ if (t >= kids.length) return null;
6983
+ for (let i = t; i < kids.length; i++) {
6984
+ if (kids[i]._tag === "node" && kids[i].state != null) return null;
6985
+ }
6986
+ let elemType;
6987
+ let elemCount = 0;
6988
+ for (const k of kids) {
6989
+ if (k._tag !== "node") continue;
6990
+ elemCount++;
6991
+ if (elemType === void 0) elemType = k.type;
6992
+ else if (k.type !== elemType) return null;
6993
+ }
6994
+ if (elemType === void 0 || elemCount < 2) return null;
6995
+ const ruleFn = registry[elemType];
6996
+ if (!ruleFn) return null;
6997
+ let separator = null;
6998
+ for (let i = 1; i < kids.length - 1; i++) {
6999
+ if (kids[i - 1]._tag === "node" && kids[i + 1]._tag === "node") {
7000
+ if (kids[i]._tag !== "leaf") return null;
7001
+ const sep = kids[i].value ?? null;
7002
+ if (separator === null) separator = sep;
7003
+ else if (sep !== separator) return null;
7004
+ }
7005
+ }
7006
+ if (separator === null) return null;
7007
+ const first = kids[0];
7008
+ const last = kids[kids.length - 1];
7009
+ if (first._tag === "leaf" && first.value !== separator && from < cStart + first.span.end) return null;
7010
+ if (last._tag === "leaf" && last.value !== separator && to > cStart + last.span.start) return null;
7011
+ const reStart = h > 0 ? cStart + kids[h - 1].span.end : cStart;
7012
+ const reEnd = cStart + kids[t].span.start + delta;
7013
+ if (reEnd < reStart) return null;
7014
+ const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
7015
+ if (!middle) return null;
7016
+ if (middle.length > 0) {
7017
+ const last2 = middle[middle.length - 1];
7018
+ if (!(last2._tag === "leaf" && last2.value === separator)) return null;
7019
+ }
7020
+ if (reEnd < newInput.length) {
7021
+ for (const sentinel of [" ", "\uFFFF"]) {
7022
+ if (newInput[reEnd] === sentinel) continue;
7023
+ const probed = newInput.slice(0, reEnd) + sentinel.repeat(newInput.length - reEnd);
7024
+ const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build, tolerant);
7025
+ if (!probe || probe.length !== middle.length) return null;
7026
+ for (let i = 0; i < middle.length; i++) if (!structurallyEqual(probe[i], middle[i])) return null;
7027
+ }
7028
+ }
7029
+ const head = kids.slice(0, h);
7030
+ const tail = kids.slice(t).map((k) => shiftRelStart(k, delta));
7031
+ const newC = {
7032
+ ...C,
7033
+ span: { start: C.span.start, end: C.span.end + delta },
7034
+ children: [...head, ...middle, ...tail]
7035
+ };
7036
+ return graftRelative(root, path, newC, delta);
7037
+ }
6546
7038
  function structurallyEqual(a, b) {
6547
7039
  if (a === b) return true;
6548
7040
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
@@ -6557,6 +7049,11 @@ function structurallyEqual(a, b) {
6557
7049
  if (at === "leaf" || at === "trivia") {
6558
7050
  return a.value === b.value;
6559
7051
  }
7052
+ if (at === "parseError") {
7053
+ const ae = a.expected ?? [];
7054
+ const be = b.expected ?? [];
7055
+ return ae.length === be.length && ae.every((x, i) => x === be[i]);
7056
+ }
6560
7057
  if (a.type !== b.type) return false;
6561
7058
  const ac = a.children;
6562
7059
  const bc = b.children;
@@ -6566,13 +7063,13 @@ function structurallyEqual(a, b) {
6566
7063
  }
6567
7064
  return true;
6568
7065
  }
6569
- function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
7066
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced, tolerant) {
6570
7067
  if (!produced.ok) return false;
6571
7068
  if (boundary >= newInput.length) return true;
6572
7069
  for (const sentinel of [" ", "\uFFFF"]) {
6573
7070
  if (newInput[boundary] === sentinel) continue;
6574
7071
  const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
6575
- const ctx = { trackLines: false, state, build };
7072
+ const ctx = mkCtx(state, build, tolerant);
6576
7073
  let r;
6577
7074
  try {
6578
7075
  r = ruleFn(probed, start, ctx);
@@ -6586,27 +7083,83 @@ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produce
6586
7083
  }
6587
7084
  var ParseDocImpl = class _ParseDocImpl {
6588
7085
  _registry;
7086
+ /** Registry entries normalized to callable parse functions (memoized once). */
7087
+ _fns;
7088
+ /** Rule names the grammar proves are genuine repetitions — the ONLY splice-safe types. */
7089
+ _reps;
6589
7090
  _rootRule;
6590
7091
  _opts;
6591
- tree;
7092
+ /**
7093
+ * The tree, held in whichever coordinate system it arrived in. A fresh parse
7094
+ * arrives ABSOLUTE (`_abs` set); an incremental graft arrives PARENT-RELATIVE
7095
+ * (`_rel` set). The public `tree` is always the RELATIVE form (so untouched
7096
+ * subtrees stay shared by identity across edits and reads are O(1)); it's
7097
+ * materialized from `_abs` once, on demand, and memoized. `undefined` = not yet
7098
+ * computed; `null` = failed parse. A fresh parse that's never edited nor
7099
+ * tree-read pays no conversion — important for the full-reparse fallback path.
7100
+ */
7101
+ _abs;
7102
+ _rel;
6592
7103
  errors;
7104
+ unconsumedFrom;
6593
7105
  input;
6594
- constructor(registry, rootRule, opts, tree, errors, input) {
7106
+ constructor(registry, rootRule, opts, trees, errors, unconsumedFrom, input) {
6595
7107
  this._registry = registry;
7108
+ this._fns = {};
7109
+ this._reps = /* @__PURE__ */ new Set();
7110
+ for (const [name, entry] of Object.entries(registry)) {
7111
+ this._fns[name] = asRuleFn(entry);
7112
+ if (producesRepetition(defOf(entry))) this._reps.add(name);
7113
+ }
6596
7114
  this._rootRule = rootRule;
6597
7115
  this._opts = opts;
6598
- this.tree = tree;
7116
+ this._abs = trees.abs;
7117
+ this._rel = trees.rel;
6599
7118
  this.errors = errors;
7119
+ this.unconsumedFrom = unconsumedFrom;
6600
7120
  this.input = input;
6601
7121
  }
7122
+ /**
7123
+ * The parse tree with PARENT-RELATIVE spans — a node's `span` is relative to
7124
+ * its parent's start (root base 0). This is the shareable representation:
7125
+ * untouched subtrees keep the same identity across `.edit()`s, and reading the
7126
+ * tree after a length-changing edit is O(1) (no offset rewrite). For absolute
7127
+ * positions use the O(depth) cursor `spanAt(path)`, or `absolutizeCST(tree)` to
7128
+ * materialize the whole absolute tree. (A fresh non-incremental `node().parse()`
7129
+ * result is unchanged — still absolute.)
7130
+ */
7131
+ get tree() {
7132
+ if (this._rel === void 0) {
7133
+ this._rel = this._abs ? relativizeCST(this._abs, 0) : null;
7134
+ }
7135
+ return this._rel;
7136
+ }
7137
+ spanAt(path) {
7138
+ const rel = this.tree;
7139
+ if (!rel) throw new Error("spanAt on a failed parse (tree is null)");
7140
+ return absoluteSpanCST(rel, path);
7141
+ }
7142
+ /**
7143
+ * Wrap a reused (grafted/spliced) RELATIVE tree, recomputing `errors` and
7144
+ * `unconsumedFrom` from it so both stay consistent with the tree across the
7145
+ * edit (the flat errors are recovered errors embedded in the reused subtrees;
7146
+ * unconsumedFrom re-derives trailing junk over the new input).
7147
+ */
7148
+ wrapReuse(newTree, newInput) {
7149
+ const trivia2 = triviaOf(this._registry[this._rootRule], this._opts);
7150
+ const end = newTree.span.end;
7151
+ const errors = collectEmbeddedErrors(newTree, 0, []);
7152
+ const unconsumedFrom = unconsumedAfter(end, newInput, trivia2);
7153
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree }, errors, unconsumedFrom, newInput);
7154
+ }
6602
7155
  edit(from, to, replacement) {
6603
7156
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
6604
7157
  const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
6605
- if (!this.tree) return reparse();
7158
+ const root = this.tree;
7159
+ if (!root) return reparse();
6606
7160
  const delta = replacement.length - (to - from);
6607
- const found = findContaining(this.tree, from);
6608
- if (!found) return reparse();
6609
- const ancestors = ancestorsAt(this.tree, found.path);
7161
+ const found = findContaining(root, 0, from) ?? { node: root, path: [] };
7162
+ const ancestors = ancestorsAt(root, found.path);
6610
7163
  const candidates = [found];
6611
7164
  const pathCopy = [...found.path];
6612
7165
  for (let i = ancestors.length - 2; i >= 0; i--) {
@@ -6615,37 +7168,78 @@ var ParseDocImpl = class _ParseDocImpl {
6615
7168
  }
6616
7169
  const rebuild = this._opts.rebuild ?? defaultRebuild;
6617
7170
  for (const { node: node2, path } of candidates) {
6618
- if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6619
- const ruleFn = this._registry[node2.type];
7171
+ const { start: absStart, end: absEnd } = absoluteSpanCST(root, path);
7172
+ if (absEnd - absStart > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
7173
+ const ruleFn = this._fns[node2.type];
6620
7174
  if (!ruleFn) continue;
6621
- if (!(node2.span.start <= from && to <= node2.span.end)) continue;
6622
- const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
6623
- const r = ruleFn(newInput, node2.span.start, ctx);
7175
+ if (!(absStart <= from && to <= absEnd)) continue;
7176
+ const ctx = mkCtx(node2.state, this._opts.build, !!this._opts.tolerant);
7177
+ const r = ruleFn(newInput, absStart, ctx);
6624
7178
  if (!r.ok) continue;
6625
- if (r.span.end !== node2.span.end + delta) continue;
6626
- if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
7179
+ if (r.span.end !== absEnd + delta) continue;
7180
+ if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r, !!this._opts.tolerant)) {
6627
7181
  continue;
6628
7182
  }
7183
+ const parentBase = absStart - node2.span.start;
7184
+ const newRel = relativizeCST(r.value, parentBase);
6629
7185
  if (delta === 0) {
6630
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
6631
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
7186
+ const newTree2 = replaceAtPath(rebuild, root, path, newRel);
7187
+ return this.wrapReuse(newTree2, newInput);
6632
7188
  }
6633
7189
  if (this._opts.rebuild) return reparse();
6634
- const newTree = graftAndShift(this.tree, path, r.value, delta);
6635
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
7190
+ const newTree = graftRelative(root, path, newRel, delta);
7191
+ return this.wrapReuse(newTree, newInput);
7192
+ }
7193
+ if (this._opts.structuralReuse && delta !== 0 && !this._opts.rebuild) {
7194
+ const spliceCandidates = [...candidates, { node: root, path: [] }];
7195
+ for (const { node: node2, path } of spliceCandidates) {
7196
+ if (!this._reps.has(node2.type)) continue;
7197
+ const { start: cStart, end: cEnd } = absoluteSpanCST(root, path);
7198
+ if (!(cStart <= from && to <= cEnd)) continue;
7199
+ const spliced = tryListSplice(root, path, node2, cStart, newInput, from, to, delta, this._fns, this._opts.build, !!this._opts.tolerant);
7200
+ if (spliced) return this.wrapReuse(spliced, newInput);
7201
+ }
6636
7202
  }
6637
7203
  return reparse();
6638
7204
  }
6639
7205
  };
7206
+ function triviaOf(entry, opts) {
7207
+ if (opts.trivia) return opts.trivia;
7208
+ if (entry === void 0 || typeof entry === "function") return void 0;
7209
+ const meta = entry._meta;
7210
+ return meta ? meta.grammarTrivia : void 0;
7211
+ }
7212
+ function unconsumedAfter(end, input, trivia2) {
7213
+ let pos = end;
7214
+ if (trivia2 && pos < input.length) {
7215
+ const t = trivia2.parse(input, pos, { trackLines: false });
7216
+ if (t.ok && t.span.end > pos) pos = t.span.end;
7217
+ }
7218
+ return pos < input.length ? pos : null;
7219
+ }
7220
+ function collectEmbeddedErrors(node2, base, out) {
7221
+ const c = node2;
7222
+ if (!c || typeof c !== "object" || !c.span) return out;
7223
+ const start = base + c.span.start;
7224
+ if (c._tag === "parseError") {
7225
+ out.push({ _tag: "parseError", span: { start, end: base + c.span.end }, expected: c.expected ?? [] });
7226
+ return out;
7227
+ }
7228
+ if (Array.isArray(c.children)) for (const k of c.children) collectEmbeddedErrors(k, start, out);
7229
+ return out;
7230
+ }
6640
7231
  function parseDoc(registry, rootRule, input, opts = {}) {
6641
- const ruleFn = registry[rootRule];
6642
- if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
6643
- const ctx = { trackLines: false, state: opts.state, build: opts.build };
6644
- const r = ruleFn(input, 0, ctx);
7232
+ const entry = registry[rootRule];
7233
+ if (!entry) throw new Error(`No rule '${rootRule}' in registry`);
7234
+ const ctx = mkCtx(opts.state, opts.build, !!opts.tolerant);
7235
+ const r = asRuleFn(entry)(input, 0, ctx);
6645
7236
  if (r.ok) {
6646
- return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
7237
+ const errors = ctx._errors ? [...ctx._errors] : [];
7238
+ const unconsumedFrom = unconsumedAfter(r.span.end, input, triviaOf(entry, opts));
7239
+ return new ParseDocImpl(registry, rootRule, opts, { abs: r.value }, errors, unconsumedFrom, input);
6647
7240
  }
6648
- return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
7241
+ const fail = { _tag: "parseError", span: r.span, expected: r.expected };
7242
+ return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [fail], null, input);
6649
7243
  }
6650
7244
 
6651
7245
  // src/functional/run.ts
@@ -6666,7 +7260,8 @@ function run(entry, input, options = {}) {
6666
7260
  build: options.build,
6667
7261
  state: options.state,
6668
7262
  ...grammarTrivia !== void 0 ? { trivia: grammarTrivia, ...grammarTrivia._meta.triviaKindLabels ? { triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {} } : {},
6669
- ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {}
7263
+ ...options.triviaCaptureMask !== void 0 ? { _triviaCaptureMask: options.triviaCaptureMask } : {},
7264
+ ...options.tolerant ? { _tolerant: true, _rec: REC } : {}
6670
7265
  };
6671
7266
  const r = invoke(entry, input, 0, ctx);
6672
7267
  let unconsumedFrom = null;
@@ -7014,72 +7609,6 @@ function buildOffsetIndex(root, input = "") {
7014
7609
  return new OffsetIndex(collectLeafSlots(root), 0, input.length);
7015
7610
  }
7016
7611
 
7017
- // src/cst/relative-spans.ts
7018
- function relativize(node2, parentBase = 0) {
7019
- return {
7020
- start: node2.start - parentBase,
7021
- end: node2.end - parentBase,
7022
- children: node2.children.map((c) => relativize(c, node2.start))
7023
- };
7024
- }
7025
- function absolutize(node2, parentBase = 0) {
7026
- const start = parentBase + node2.start;
7027
- return {
7028
- start,
7029
- end: parentBase + node2.end,
7030
- children: node2.children.map((c) => absolutize(c, start))
7031
- };
7032
- }
7033
- function absoluteSpanAt(root, path) {
7034
- let base = 0;
7035
- let node2 = root;
7036
- for (const idx of path) {
7037
- base += node2.start;
7038
- const child = node2.children[idx];
7039
- if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
7040
- node2 = child;
7041
- }
7042
- return { start: base + node2.start, end: base + node2.end };
7043
- }
7044
- function shiftAbsolute(node2, at, delta) {
7045
- const s = node2.start >= at ? node2.start + delta : node2.start;
7046
- const e = node2.end >= at ? node2.end + delta : node2.end;
7047
- return {
7048
- start: s,
7049
- end: e,
7050
- children: node2.children.map((c) => shiftAbsolute(c, at, delta))
7051
- };
7052
- }
7053
- function applyEdit(root, at, delta, stats) {
7054
- return editNode(root, at, delta, 0, 0, stats);
7055
- }
7056
- function editNode(node2, at, delta, parentBase, parentShift, stats) {
7057
- const absStart = parentBase + node2.start;
7058
- const absEnd = parentBase + node2.end;
7059
- if (absEnd < at) return node2;
7060
- const startShifts = absStart >= at;
7061
- if (startShifts && parentShift === delta) return node2;
7062
- if (stats) stats.allocated++;
7063
- const newAbsStart = startShifts ? absStart + delta : absStart;
7064
- const newAbsEnd = absEnd + delta;
7065
- const newParentBase = parentBase + parentShift;
7066
- const newStart = newAbsStart - newParentBase;
7067
- const newEnd = newAbsEnd - newParentBase;
7068
- const ownShift = newAbsStart - absStart;
7069
- let changed = false;
7070
- const newChildren = [];
7071
- for (const c of node2.children) {
7072
- const nc = editNode(c, at, delta, absStart, ownShift, stats);
7073
- if (nc !== c) changed = true;
7074
- newChildren.push(nc);
7075
- }
7076
- return {
7077
- start: newStart,
7078
- end: newEnd,
7079
- children: changed ? newChildren : node2.children
7080
- };
7081
- }
7082
-
7083
7612
  // src/index.ts
7084
7613
  registerRegexAnalyzer(firstSetFromRegex);
7085
7614
  //# sourceMappingURL=index.cjs.map