parseman 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/combinators/completions.d.ts +14 -4
  2. package/dist/combinators/completions.d.ts.map +1 -1
  3. package/dist/combinators/expect.d.ts +2 -0
  4. package/dist/combinators/expect.d.ts.map +1 -1
  5. package/dist/combinators/grammar.d.ts +5 -5
  6. package/dist/combinators/not.d.ts.map +1 -1
  7. package/dist/combinators/parser.d.ts +3 -2
  8. package/dist/combinators/parser.d.ts.map +1 -1
  9. package/dist/combinators/repeat.d.ts.map +1 -1
  10. package/dist/combinators/sequence.d.ts.map +1 -1
  11. package/dist/combinators/trivia-skip.d.ts +1 -0
  12. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  13. package/dist/compiler/codegen.d.ts +9 -4
  14. package/dist/compiler/codegen.d.ts.map +1 -1
  15. package/dist/compiler/scannable-run.d.ts +2 -2
  16. package/dist/compiler/scannable-run.d.ts.map +1 -1
  17. package/dist/cst/capture-buffer.d.ts +1 -0
  18. package/dist/cst/capture-buffer.d.ts.map +1 -1
  19. package/dist/cst/types.d.ts +7 -7
  20. package/dist/cst/types.d.ts.map +1 -1
  21. package/dist/functional/doc.d.ts +37 -2
  22. package/dist/functional/doc.d.ts.map +1 -1
  23. package/dist/functional/run.d.ts +12 -1
  24. package/dist/functional/run.d.ts.map +1 -1
  25. package/dist/index.cjs +505 -195
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +1 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +505 -195
  30. package/dist/index.js.map +4 -4
  31. package/dist/language-service/index.cjs +776 -0
  32. package/dist/language-service/index.cjs.map +7 -0
  33. package/dist/language-service/index.d.ts +94 -0
  34. package/dist/language-service/index.d.ts.map +1 -0
  35. package/dist/language-service/index.js +753 -0
  36. package/dist/language-service/index.js.map +7 -0
  37. package/dist/plugin/evaluator.d.ts.map +1 -1
  38. package/dist/plugin/index.cjs +568 -129
  39. package/dist/plugin/index.cjs.map +4 -4
  40. package/dist/plugin/index.d.ts +11 -1
  41. package/dist/plugin/index.d.ts.map +1 -1
  42. package/dist/plugin/index.js +568 -129
  43. package/dist/plugin/index.js.map +4 -4
  44. package/dist/recovery/scan.d.ts +54 -0
  45. package/dist/recovery/scan.d.ts.map +1 -0
  46. package/dist/spec/index.cjs.map +1 -1
  47. package/dist/spec/index.js.map +1 -1
  48. package/dist/types.d.ts +60 -6
  49. package/dist/types.d.ts.map +1 -1
  50. package/package.json +6 -1
  51. package/dist/combinators/recover-list.d.ts +0 -41
  52. package/dist/combinators/recover-list.d.ts.map +0 -1
  53. package/dist/combinators/recover.d.ts +0 -20
  54. package/dist/combinators/recover.d.ts.map +0 -1
@@ -332,7 +332,13 @@ function saveCstMark(ctx) {
332
332
  raw: cstRawLen(ctx),
333
333
  tlog: cstTlLen(ctx),
334
334
  leaves: cstLeavesLen(ctx),
335
- fields: ctx._fields?.length ?? 0
335
+ fields: ctx._fields?.length ?? 0,
336
+ // Speculative rollback must truncate the flat recovery-error sink in lockstep
337
+ // with the CST leaves: a branch that recovered (pushing a ParseError to
338
+ // ctx._errors AND embedding it as a CST child via captureError) then failed
339
+ // must remove BOTH, or the flat error survives as a GHOST that a tree walk
340
+ // can't see (flat run().errors would disagree with the embedded set).
341
+ errors: ctx._errors?.length ?? 0
336
342
  };
337
343
  }
338
344
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -348,6 +354,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
348
354
  if (len === 0) b[keySingle] = void 0;
349
355
  }
350
356
  function rollbackCstCapture(ctx, mark) {
357
+ if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
351
358
  const b = ctx._cstBuf;
352
359
  if (b) {
353
360
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
@@ -1291,10 +1298,10 @@ function needsDeferredTriviaCommit(ctx) {
1291
1298
  }
1292
1299
  function saveTriviaMark(ctx) {
1293
1300
  const m = saveCstMark(ctx);
1294
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1301
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1295
1302
  }
1296
1303
  function rollbackTrivia(ctx, mark) {
1297
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1304
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1298
1305
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1299
1306
  }
1300
1307
  function scanWithLabels(input, cur, ctx) {
@@ -1528,6 +1535,93 @@ function scanBlockComment(input, cur) {
1528
1535
  return close === -1 ? cur : close + 2;
1529
1536
  }
1530
1537
 
1538
+ // src/recovery/scan.ts
1539
+ function probeAt(sentinel, input, pos, ctx) {
1540
+ const s = {
1541
+ _probe: ctx._probe,
1542
+ _errors: ctx._errors,
1543
+ _cstLeaves: ctx._cstLeaves,
1544
+ _cstChildren: ctx._cstChildren,
1545
+ _cstRawChildren: ctx._cstRawChildren,
1546
+ _cstTriviaLog: ctx._cstTriviaLog,
1547
+ _triviaLog: ctx._triviaLog,
1548
+ _fields: ctx._fields,
1549
+ _cstBuf: ctx._cstBuf,
1550
+ _tolerant: ctx._tolerant,
1551
+ _sync: ctx._sync
1552
+ };
1553
+ ctx._probe = void 0;
1554
+ ctx._errors = void 0;
1555
+ ctx._cstLeaves = void 0;
1556
+ ctx._cstChildren = void 0;
1557
+ ctx._cstRawChildren = void 0;
1558
+ ctx._cstTriviaLog = void 0;
1559
+ ctx._triviaLog = void 0;
1560
+ ctx._fields = void 0;
1561
+ ctx._cstBuf = void 0;
1562
+ ctx._tolerant = void 0;
1563
+ ctx._sync = void 0;
1564
+ try {
1565
+ return sentinel.parse(input, pos, ctx).ok;
1566
+ } finally {
1567
+ ctx._probe = s._probe;
1568
+ ctx._errors = s._errors;
1569
+ ctx._cstLeaves = s._cstLeaves;
1570
+ ctx._cstChildren = s._cstChildren;
1571
+ ctx._cstRawChildren = s._cstRawChildren;
1572
+ ctx._cstTriviaLog = s._cstTriviaLog;
1573
+ ctx._triviaLog = s._triviaLog;
1574
+ ctx._fields = s._fields;
1575
+ ctx._cstBuf = s._cstBuf;
1576
+ ctx._tolerant = s._tolerant;
1577
+ ctx._sync = s._sync;
1578
+ }
1579
+ }
1580
+ function orSentinel(a, b) {
1581
+ if (b === void 0) return a;
1582
+ return {
1583
+ _tag: "orSentinel",
1584
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
1585
+ _def: { tag: "unknown" },
1586
+ parse(input, pos, ctx) {
1587
+ const ra = a.parse(input, pos, ctx);
1588
+ if (ra.ok) return ra;
1589
+ return b.parse(input, pos, ctx);
1590
+ }
1591
+ };
1592
+ }
1593
+ function firstSetSentinel(fs2) {
1594
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
1595
+ const ranges = fs2.ranges;
1596
+ return {
1597
+ _tag: "firstSetSentinel",
1598
+ _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
1599
+ _def: { tag: "unknown" },
1600
+ parse(input, pos) {
1601
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
1602
+ if (code !== void 0) {
1603
+ for (const r of ranges) {
1604
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
1605
+ }
1606
+ }
1607
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
1608
+ }
1609
+ };
1610
+ }
1611
+ function recoverScan(input, from, ctx, sync, expected) {
1612
+ let scanPos = from;
1613
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
1614
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
1615
+ ctx._errors?.push(error);
1616
+ return { error, end: scanPos };
1617
+ }
1618
+ function matchesAt(sentinel, input, pos, ctx) {
1619
+ return probeAt(sentinel, input, pos, ctx);
1620
+ }
1621
+ function captureError(ctx, error) {
1622
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
1623
+ }
1624
+
1531
1625
  // src/combinators/sequence.ts
1532
1626
  function sequence(...parsers) {
1533
1627
  const meta = {
@@ -1538,11 +1632,59 @@ function sequence(...parsers) {
1538
1632
  isTrivia: false
1539
1633
  };
1540
1634
  const def = { tag: "sequence", parsers };
1635
+ let followSentinels;
1636
+ function parseTolerant(input, pos, ctx) {
1637
+ followSentinels ??= parsers.map((_, i) => {
1638
+ const fs2 = parsers.slice(i + 1).reduce(
1639
+ (acc, p) => union(acc, firstSetOf(p)),
1640
+ { kind: "empty" }
1641
+ );
1642
+ return firstSetSentinel(fs2);
1643
+ });
1644
+ const values = def.valueUnused ? void 0 : [];
1645
+ let cur = pos;
1646
+ const inheritedSync = ctx._sync;
1647
+ try {
1648
+ for (let i = 0; i < parsers.length; i++) {
1649
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1650
+ if (ctx.trivia && i > 0) {
1651
+ const mark = saveTriviaMark(ctx);
1652
+ let scanEnd;
1653
+ if (needsDeferredTriviaCommit(ctx)) {
1654
+ const scan = scanTrivia(input, cur, ctx);
1655
+ scan.commit();
1656
+ scanEnd = scan.end;
1657
+ } else {
1658
+ scanEnd = advanceTrivia(input, cur, ctx);
1659
+ }
1660
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1661
+ if (!result2.ok) return result2;
1662
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1663
+ else rollbackTrivia(ctx, mark);
1664
+ if (values !== void 0) values.push(result2.value);
1665
+ continue;
1666
+ }
1667
+ const result = parsers[i].parse(input, cur, ctx);
1668
+ if (!result.ok) return result;
1669
+ if (values !== void 0) values.push(result.value);
1670
+ cur = result.span.end;
1671
+ }
1672
+ } finally {
1673
+ ctx._sync = inheritedSync;
1674
+ }
1675
+ return {
1676
+ ok: true,
1677
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1678
+ value: values ?? void 0,
1679
+ span: { start: pos, end: cur }
1680
+ };
1681
+ }
1541
1682
  return {
1542
1683
  _tag: "sequence",
1543
1684
  _meta: meta,
1544
1685
  _def: def,
1545
1686
  parse(input, pos, ctx) {
1687
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1546
1688
  const values = def.valueUnused ? void 0 : [];
1547
1689
  let cur = pos;
1548
1690
  for (let i = 0; i < parsers.length; i++) {
@@ -1581,6 +1723,66 @@ function sequence(...parsers) {
1581
1723
  };
1582
1724
  }
1583
1725
 
1726
+ // src/combinators/expect.ts
1727
+ function deriveExpected(c) {
1728
+ const def = c._def;
1729
+ switch (def.tag) {
1730
+ case "literal":
1731
+ return [JSON.stringify(def.value)];
1732
+ case "regex":
1733
+ return [`/${def.source}/`];
1734
+ case "keywords":
1735
+ return def.words.map((w) => JSON.stringify(w));
1736
+ case "label":
1737
+ return [def.label];
1738
+ case "choice":
1739
+ return def.parsers.flatMap(deriveExpected);
1740
+ case "sequence":
1741
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1742
+ case "node":
1743
+ case "grammar":
1744
+ case "trivia":
1745
+ case "token":
1746
+ case "optional":
1747
+ case "many":
1748
+ case "oneOrMore":
1749
+ case "transform":
1750
+ case "not":
1751
+ return deriveExpected(def.parser);
1752
+ case "lazy": {
1753
+ const name = c._ruleName;
1754
+ try {
1755
+ return deriveExpected(def.thunk());
1756
+ } catch {
1757
+ return name ? [name] : [];
1758
+ }
1759
+ }
1760
+ default:
1761
+ return [];
1762
+ }
1763
+ }
1764
+ function expect(combinator, label2) {
1765
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1766
+ const meta = {
1767
+ firstSet: combinator._meta.firstSet,
1768
+ canMatchNewline: combinator._meta.canMatchNewline,
1769
+ isTrivia: false
1770
+ };
1771
+ return {
1772
+ _tag: "expect",
1773
+ _meta: meta,
1774
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1775
+ parse(input, pos, ctx) {
1776
+ const result = combinator.parse(input, pos, ctx);
1777
+ if (result.ok) return result;
1778
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1779
+ ctx._errors?.push(error);
1780
+ if (ctx._tolerant) captureError(ctx, error);
1781
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1782
+ }
1783
+ };
1784
+ }
1785
+
1584
1786
  // src/combinators/repeat.ts
1585
1787
  function repItem(combinator, input, cur, ctx) {
1586
1788
  const mark = saveTriviaMark(ctx);
@@ -1601,7 +1803,7 @@ function repItem(combinator, input, cur, ctx) {
1601
1803
  const result = combinator.parse(input, pos, ctx);
1602
1804
  if (!result.ok) {
1603
1805
  rollbackTrivia(ctx, mark);
1604
- return { fail: result };
1806
+ return { fail: result, failPos: pos };
1605
1807
  }
1606
1808
  if (result.span.end === pos) {
1607
1809
  rollbackTrivia(ctx, mark);
@@ -1616,6 +1818,7 @@ function many(combinator) {
1616
1818
  isTrivia: false
1617
1819
  };
1618
1820
  const def = { tag: "many", parser: combinator, min: 0 };
1821
+ let expected;
1619
1822
  return {
1620
1823
  _tag: "many",
1621
1824
  _meta: meta,
@@ -1625,7 +1828,18 @@ function many(combinator) {
1625
1828
  let cur = pos;
1626
1829
  while (cur < input.length) {
1627
1830
  const item = repItem(combinator, input, cur, ctx);
1628
- if (item === "stop" || "fail" in item) break;
1831
+ if (item === "stop") break;
1832
+ if ("fail" in item) {
1833
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1834
+ if (sync === void 0) break;
1835
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1836
+ expected ??= deriveExpected(combinator);
1837
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1838
+ if (values !== void 0) values.push(error);
1839
+ captureError(ctx, error);
1840
+ cur = end;
1841
+ continue;
1842
+ }
1629
1843
  if (values !== void 0) values.push(item.value);
1630
1844
  cur = item.end;
1631
1845
  }
@@ -1640,6 +1854,7 @@ function oneOrMore(combinator) {
1640
1854
  isTrivia: false
1641
1855
  };
1642
1856
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1857
+ let expected;
1643
1858
  return {
1644
1859
  _tag: "oneOrMore",
1645
1860
  _meta: meta,
@@ -1651,7 +1866,18 @@ function oneOrMore(combinator) {
1651
1866
  let cur = first.span.end;
1652
1867
  while (cur < input.length) {
1653
1868
  const item = repItem(combinator, input, cur, ctx);
1654
- if (item === "stop" || "fail" in item) break;
1869
+ if (item === "stop") break;
1870
+ if ("fail" in item) {
1871
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1872
+ if (sync === void 0) break;
1873
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1874
+ expected ??= deriveExpected(combinator);
1875
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1876
+ if (values !== void 0) values.push(error);
1877
+ captureError(ctx, error);
1878
+ cur = end;
1879
+ continue;
1880
+ }
1655
1881
  if (values !== void 0) values.push(item.value);
1656
1882
  cur = item.end;
1657
1883
  }
@@ -1697,15 +1923,29 @@ function sepBy(combinator, separator) {
1697
1923
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1698
1924
  isTrivia: false
1699
1925
  };
1926
+ let expected;
1700
1927
  return {
1701
1928
  _tag: "sepBy",
1702
1929
  _meta: meta,
1703
1930
  _def: { tag: "sepBy", parser: combinator, separator },
1704
1931
  parse(input, pos, ctx) {
1705
1932
  const first = combinator.parse(input, pos, ctx);
1706
- if (!first.ok) return { ok: true, value: [], span: { start: pos, end: pos } };
1707
- const values = [first.value];
1708
- let cur = first.span.end;
1933
+ const values = [];
1934
+ let cur;
1935
+ if (first.ok) {
1936
+ values.push(first.value);
1937
+ cur = first.span.end;
1938
+ } else {
1939
+ const term = ctx._tolerant ? ctx._sync : void 0;
1940
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1941
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1942
+ }
1943
+ expected ??= deriveExpected(combinator);
1944
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1945
+ values.push(rec.error);
1946
+ captureError(ctx, rec.error);
1947
+ cur = rec.end;
1948
+ }
1709
1949
  while (cur < input.length) {
1710
1950
  const loopMark = saveTriviaMark(ctx);
1711
1951
  let sepPos = cur;
@@ -1735,6 +1975,15 @@ function sepBy(combinator, separator) {
1735
1975
  }
1736
1976
  const next = combinator.parse(input, nextPos, ctx);
1737
1977
  if (!next.ok) {
1978
+ const term = ctx._tolerant ? ctx._sync : void 0;
1979
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1980
+ expected ??= deriveExpected(combinator);
1981
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1982
+ values.push(rec.error);
1983
+ captureError(ctx, rec.error);
1984
+ cur = rec.end;
1985
+ continue;
1986
+ }
1738
1987
  rollbackTrivia(ctx, loopMark);
1739
1988
  break;
1740
1989
  }
@@ -1871,7 +2120,7 @@ function rules(a, b) {
1871
2120
  cache[key] = parser2;
1872
2121
  }
1873
2122
  }
1874
- if (options?.trivia !== void 0) {
2123
+ if (options?.trivia != null) {
1875
2124
  for (const key of Object.keys(definitions)) {
1876
2125
  const rule = cache[key];
1877
2126
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1901,7 +2150,9 @@ function not(combinator) {
1901
2150
  _meta: meta,
1902
2151
  _def: { tag: "not", parser: combinator },
1903
2152
  parse(input, pos, ctx) {
2153
+ const mark = saveCstMark(ctx);
1904
2154
  const result = combinator.parse(input, pos, ctx);
2155
+ rollbackCstCapture(ctx, mark);
1905
2156
  if (result.ok) {
1906
2157
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1907
2158
  }
@@ -2015,7 +2266,7 @@ function unwrapChild(child) {
2015
2266
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
2016
2267
  }
2017
2268
  function isCstChild(value) {
2018
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2269
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
2019
2270
  }
2020
2271
  function missingInferredType() {
2021
2272
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2268,65 +2519,6 @@ function token(root) {
2268
2519
  };
2269
2520
  }
2270
2521
 
2271
- // src/combinators/expect.ts
2272
- function deriveExpected(c) {
2273
- const def = c._def;
2274
- switch (def.tag) {
2275
- case "literal":
2276
- return [JSON.stringify(def.value)];
2277
- case "regex":
2278
- return [`/${def.source}/`];
2279
- case "keywords":
2280
- return def.words.map((w) => JSON.stringify(w));
2281
- case "label":
2282
- return [def.label];
2283
- case "choice":
2284
- return def.parsers.flatMap(deriveExpected);
2285
- case "sequence":
2286
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
2287
- case "node":
2288
- case "grammar":
2289
- case "trivia":
2290
- case "token":
2291
- case "optional":
2292
- case "many":
2293
- case "oneOrMore":
2294
- case "transform":
2295
- case "not":
2296
- return deriveExpected(def.parser);
2297
- case "lazy": {
2298
- const name = c._ruleName;
2299
- try {
2300
- return deriveExpected(def.thunk());
2301
- } catch {
2302
- return name ? [name] : [];
2303
- }
2304
- }
2305
- default:
2306
- return [];
2307
- }
2308
- }
2309
- function expect(combinator, label2) {
2310
- const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
2311
- const meta = {
2312
- firstSet: combinator._meta.firstSet,
2313
- canMatchNewline: combinator._meta.canMatchNewline,
2314
- isTrivia: false
2315
- };
2316
- return {
2317
- _tag: "expect",
2318
- _meta: meta,
2319
- _def: { tag: "expect", parser: combinator, label: label2, expected },
2320
- parse(input, pos, ctx) {
2321
- const result = combinator.parse(input, pos, ctx);
2322
- if (result.ok) return result;
2323
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2324
- ctx._errors?.push(error);
2325
- return { ok: true, value: error, span: { start: pos, end: pos } };
2326
- }
2327
- };
2328
- }
2329
-
2330
2522
  // src/compiler/scannable-run.ts
2331
2523
  var BACKSLASH = 92;
2332
2524
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2398,20 +2590,39 @@ function parseSeqParts(source) {
2398
2590
  lit = [];
2399
2591
  }
2400
2592
  };
2401
- const runFrom = (ranges, negated, q) => {
2593
+ const braceQuant = (at) => {
2594
+ const close = source.indexOf("}", at);
2595
+ if (close === -1) return null;
2596
+ const body = source.slice(at + 1, close);
2597
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2598
+ if (!m) return null;
2599
+ const min = Number(m[1]);
2600
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2601
+ if (max < 1 || min > max) return null;
2602
+ return { min, max, len: close - at + 1 };
2603
+ };
2604
+ const runFrom = (ranges, negated, at) => {
2605
+ const q = source[at];
2402
2606
  if (q === "+") {
2403
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2607
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2404
2608
  return 1;
2405
2609
  }
2406
2610
  if (q === "*") {
2407
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2611
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2408
2612
  return 1;
2409
2613
  }
2410
2614
  if (q === "?") {
2411
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2615
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2412
2616
  return 1;
2413
2617
  }
2414
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2618
+ if (q === "{") {
2619
+ const b = braceQuant(at);
2620
+ if (b) {
2621
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2622
+ return b.len;
2623
+ }
2624
+ }
2625
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2415
2626
  return 0;
2416
2627
  };
2417
2628
  let i = 0;
@@ -2436,7 +2647,7 @@ function parseSeqParts(source) {
2436
2647
  if (!ranges) return null;
2437
2648
  flush();
2438
2649
  i = k + 1;
2439
- i += runFrom(ranges, neg, source[i]);
2650
+ i += runFrom(ranges, neg, i);
2440
2651
  continue;
2441
2652
  }
2442
2653
  if (ch === "\\") {
@@ -2444,7 +2655,7 @@ function parseSeqParts(source) {
2444
2655
  if (e === "d" || e === "w" || e === "s") {
2445
2656
  flush();
2446
2657
  i += 2;
2447
- i += runFrom(shorthandRanges(e), false, source[i]);
2658
+ i += runFrom(shorthandRanges(e), false, i);
2448
2659
  continue;
2449
2660
  }
2450
2661
  const uni = readUnicodeEscape(source, i);
@@ -2533,7 +2744,7 @@ function seqIsUnambiguous(parts) {
2533
2744
  let hasMandatory = false;
2534
2745
  for (let k = 0; k < parts.length; k++) {
2535
2746
  const p = parts[k];
2536
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2747
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2537
2748
  if (mandatory) hasMandatory = true;
2538
2749
  if (p.part === "group") {
2539
2750
  const exposure = groupPartExposure(p);
@@ -2544,9 +2755,8 @@ function seqIsUnambiguous(parts) {
2544
2755
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2545
2756
  }
2546
2757
  }
2547
- const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
2548
- const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
2549
- if (skippableSingle || isUnboundedRepeat) {
2758
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2759
+ if (needsDisjoint) {
2550
2760
  const rest = seqFirstAccept(parts.slice(k + 1));
2551
2761
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2552
2762
  }
@@ -2603,8 +2813,8 @@ function fixedClassSeq(shape) {
2603
2813
  if (p.part === "lit") {
2604
2814
  if (p.optional) return null;
2605
2815
  for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
2606
- } else if (p.part === "run" && !p.unbounded && p.min === 1) {
2607
- out.push({ ranges: p.ranges, negated: p.negated });
2816
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2817
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2608
2818
  } else {
2609
2819
  return null;
2610
2820
  }
@@ -2656,7 +2866,7 @@ function trailingBacktrackClass(shape) {
2656
2866
  const last = shape.parts[shape.parts.length - 1];
2657
2867
  if (last.part === "lit") return null;
2658
2868
  if (last.part === "run") {
2659
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2869
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2660
2870
  return null;
2661
2871
  }
2662
2872
  return groupPartExposure(last);
@@ -2793,7 +3003,7 @@ function partFirstAccept(part) {
2793
3003
  function seqFirstAccept(parts) {
2794
3004
  const sets = [];
2795
3005
  for (const p of parts) {
2796
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
3006
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2797
3007
  sets.push(partFirstAccept(p));
2798
3008
  if (mandatory) break;
2799
3009
  }
@@ -3208,7 +3418,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3208
3418
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3209
3419
  lines.push(`${ind2} ${endV2} += ${L}`);
3210
3420
  }
3211
- } else if (p.part === "run" && !p.unbounded) {
3421
+ } else if (p.part === "run" && p.max === Infinity) {
3422
+ const s = mint("_s");
3423
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3424
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3425
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3426
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3427
+ } else if (p.part === "run" && p.max === 1) {
3212
3428
  if (p.min === 1) {
3213
3429
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3214
3430
  lines.push(`${ind2} ${endV2}++`);
@@ -3218,8 +3434,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3218
3434
  } else if (p.part === "run") {
3219
3435
  const s = mint("_s");
3220
3436
  lines.push(`${ind2} const ${s} = ${endV2}`);
3221
- lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3222
- if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3437
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3438
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3223
3439
  } else {
3224
3440
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3225
3441
  if (!p.unbounded) {
@@ -3686,21 +3902,29 @@ function hoistExpected(ctx, constArrSource) {
3686
3902
  }
3687
3903
  return name;
3688
3904
  }
3905
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3906
+ if (!ctx.recovery) return "";
3907
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3908
+ const pb = v(ctx, "_pb");
3909
+ 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} } } } `;
3910
+ }
3689
3911
  function failBody(ctx, expected, posExpr) {
3912
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3690
3913
  if (ctx.failLabel) {
3691
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3692
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3914
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3915
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3693
3916
  }
3694
- return failReturn(expected, posExpr);
3917
+ return probe + failReturn(expected, posExpr);
3695
3918
  }
3696
3919
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3920
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3697
3921
  if (ctx.failLabel) {
3698
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3922
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3699
3923
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3700
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3924
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3701
3925
  }
3702
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3703
- return failReturnArr(expectedArr, posExpr);
3926
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3927
+ return probe + failReturnArr(expectedArr, posExpr);
3704
3928
  }
3705
3929
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3706
3930
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4200,6 +4424,10 @@ function emitRegex(def, ctx, pos) {
4200
4424
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4201
4425
  return { stmts, valueVar: vv, endVar };
4202
4426
  }
4427
+ function syncSentinelExpr(fs2) {
4428
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
4429
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
4430
+ }
4203
4431
  function emitSeqValues(def, ctx, pos) {
4204
4432
  const startV = v(ctx, "_start");
4205
4433
  const curV = v(ctx, "_cur");
@@ -4208,7 +4436,17 @@ function emitSeqValues(def, ctx, pos) {
4208
4436
  `${ind(ctx)}let ${curV} = ${pos}`
4209
4437
  ];
4210
4438
  const valueVars = [];
4439
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4440
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4441
+ const publishSync = (i) => {
4442
+ if (!ctx.recovery) return "";
4443
+ const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4444
+ const ref2 = syncSentinelExpr(fs2);
4445
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4446
+ };
4211
4447
  for (let i = 0; i < def.parsers.length; i++) {
4448
+ const syncPub = publishSync(i);
4449
+ if (syncPub) stmts.push(syncPub);
4212
4450
  if (i > 0 && ctx.activeTrivia) {
4213
4451
  if (ctx.capturing) {
4214
4452
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4233,7 +4471,17 @@ function emitSeqValues(def, ctx, pos) {
4233
4471
  continue;
4234
4472
  } else {
4235
4473
  const trivFn = ensureTriviaFn(ctx);
4236
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4474
+ const scanEndV = v(ctx, "_sne");
4475
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4476
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4477
+ stmts.push(...r2.stmts);
4478
+ const endAfterV = v(ctx, "_sea");
4479
+ stmts.push(
4480
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4481
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4482
+ );
4483
+ valueVars.push(r2.valueVar);
4484
+ continue;
4237
4485
  }
4238
4486
  }
4239
4487
  const r = emit(def.parsers[i], ctx, curV);
@@ -4463,7 +4711,7 @@ function emitFirstMatch(def, ctx, pos) {
4463
4711
  if (gate) {
4464
4712
  const gateIdx = ctx.mapFns.length;
4465
4713
  ctx.mapFns.push(gate);
4466
- ctx.mapFnSrcs.push(null);
4714
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4467
4715
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4468
4716
  }
4469
4717
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4568,6 +4816,8 @@ function emitMany(def, ctx, pos) {
4568
4816
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4569
4817
  `${ind(ctx)}let ${curV} = ${pos}`
4570
4818
  ];
4819
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4820
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4571
4821
  if (def.min === 1) {
4572
4822
  const firstR = emit(def.parser, ctx, curV);
4573
4823
  stmts.push(...firstR.stmts);
@@ -4601,10 +4851,26 @@ function emitMany(def, ctx, pos) {
4601
4851
  }
4602
4852
  }
4603
4853
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
4604
- stmts.push(
4605
- ...iterStmts,
4606
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
4607
- );
4854
+ stmts.push(...iterStmts);
4855
+ if (ctx.recovery) {
4856
+ const rrV = v(ctx, "_rr");
4857
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4858
+ stmts.push(
4859
+ `${ind(ctx)}if (!${iterOk}) {`,
4860
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4861
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4862
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4863
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4864
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4865
+ `${ind(ctx)} continue`,
4866
+ `${ind(ctx)} }`,
4867
+ `${ind(ctx)} break`,
4868
+ `${ind(ctx)}}`,
4869
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4870
+ );
4871
+ } else {
4872
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4873
+ }
4608
4874
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4609
4875
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4610
4876
  ctx.indent--;
@@ -4626,20 +4892,56 @@ function emitOptional(def, ctx, pos) {
4626
4892
  function emitSepBy(_p, def, ctx, pos) {
4627
4893
  const arrV = v(ctx, "_arr");
4628
4894
  const curV = v(ctx, "_cur");
4895
+ const rec = !!ctx.recovery;
4896
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4897
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4898
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4899
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4629
4900
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4630
4901
  const stmts = [
4631
4902
  `${ind(ctx)}const ${arrV} = []`,
4632
4903
  `${ind(ctx)}let ${curV} = ${pos}`,
4633
- ...firstStmts,
4634
- `${ind(ctx)}if (${firstOk}) {`
4904
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4905
+ ...firstStmts
4635
4906
  ];
4636
- ctx.indent++;
4637
- stmts.push(
4638
- `${ind(ctx)}${arrV}.push(${firstVal})`,
4639
- `${ind(ctx)}${curV} = ${firstEnd}`,
4640
- `${ind(ctx)}while (${curV} < input.length) {`
4641
- );
4642
- ctx.indent++;
4907
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4908
+ if (!rec) return [originalFail];
4909
+ const rr = v(ctx, "_rr");
4910
+ return [
4911
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4912
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4913
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4914
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4915
+ `${ind(ctx)} }`,
4916
+ `${ind(ctx)} ${rb}break`,
4917
+ `${ind(ctx)}}`
4918
+ ];
4919
+ };
4920
+ if (rec) {
4921
+ const rr0 = v(ctx, "_rr0");
4922
+ const didV = v(ctx, "_did");
4923
+ stmts.push(
4924
+ `${ind(ctx)}let ${didV} = false`,
4925
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4926
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4927
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4928
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4929
+ `${ind(ctx)}}`,
4930
+ `${ind(ctx)}if (${didV}) {`
4931
+ );
4932
+ ctx.indent++;
4933
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
4934
+ ctx.indent++;
4935
+ } else {
4936
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
4937
+ ctx.indent++;
4938
+ stmts.push(
4939
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
4940
+ `${ind(ctx)}${curV} = ${firstEnd}`,
4941
+ `${ind(ctx)}while (${curV} < input.length) {`
4942
+ );
4943
+ ctx.indent++;
4944
+ }
4643
4945
  let sepAtPos = curV;
4644
4946
  if (ctx.activeTrivia) {
4645
4947
  if (ctx.capturing) {
@@ -4665,8 +4967,7 @@ function emitSepBy(_p, def, ctx, pos) {
4665
4967
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4666
4968
  stmts.push(
4667
4969
  ...nextStmts,
4668
- // item failed → unwind the separator too, back to the end of the last item
4669
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
4970
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4670
4971
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4671
4972
  `${ind(ctx)}${curV} = ${nextEnd}`
4672
4973
  );
@@ -4682,7 +4983,7 @@ function emitSepBy(_p, def, ctx, pos) {
4682
4983
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4683
4984
  stmts.push(
4684
4985
  ...nextStmts,
4685
- `${ind(ctx)}if (!${nextOk}) break`,
4986
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4686
4987
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4687
4988
  `${ind(ctx)}${curV} = ${nextEnd}`
4688
4989
  );
@@ -4702,7 +5003,7 @@ function emitSepBy(_p, def, ctx, pos) {
4702
5003
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4703
5004
  stmts.push(
4704
5005
  ...nextStmts,
4705
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
5006
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4706
5007
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4707
5008
  `${ind(ctx)}${curV} = ${nextEnd}`
4708
5009
  );
@@ -4959,7 +5260,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4959
5260
  stmts.push(
4960
5261
  `${i}const ${ndV} = ${finalExpr}`,
4961
5262
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4962
- `${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} } })`
5263
+ `${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} } })`
4963
5264
  );
4964
5265
  return { stmts, valueVar: ndV, endVar };
4965
5266
  }
@@ -5049,6 +5350,10 @@ function emitExpect(def, ctx, pos) {
5049
5350
  `${ind0}if (!${okVar}) {`,
5050
5351
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5051
5352
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5353
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5354
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5355
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5356
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5052
5357
  `${ind0} ${valVar} = ${errV}`,
5053
5358
  `${ind0} ${endVar} = ${pos}`,
5054
5359
  `${ind0} ${okVar} = true`,
@@ -5215,7 +5520,7 @@ function emitDispatch(p, ctx, pos) {
5215
5520
  case "guard": {
5216
5521
  const fnIdx = ctx.mapFns.length;
5217
5522
  ctx.mapFns.push(def.predicate);
5218
- ctx.mapFnSrcs.push(null);
5523
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5219
5524
  const vv = v(ctx);
5220
5525
  return {
5221
5526
  stmts: [
@@ -5230,7 +5535,7 @@ function emitDispatch(p, ctx, pos) {
5230
5535
  const evIdx = ctx.mapFns.length;
5231
5536
  const extra = def.extra;
5232
5537
  ctx.mapFns.push((() => extra));
5233
- ctx.mapFnSrcs.push(null);
5538
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5234
5539
  const innerParser = def.parser;
5235
5540
  if (!ctx.namedParsers.has(innerParser)) {
5236
5541
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5474,7 +5779,7 @@ function ruleDependencies(ruleMap) {
5474
5779
  }
5475
5780
  return deps;
5476
5781
  }
5477
- function compile(combinator, mapFnSources) {
5782
+ function compile(combinator, mapFnSources, opts) {
5478
5783
  markUnusedValues(combinator);
5479
5784
  const grammarTrivia = combinator._meta.grammarTrivia;
5480
5785
  const ctx = {
@@ -5495,6 +5800,7 @@ function compile(combinator, mapFnSources) {
5495
5800
  triviaFnNames: /* @__PURE__ */ new Map(),
5496
5801
  namedFnDecls: [],
5497
5802
  capturing: hasNodeDef(combinator),
5803
+ recovery: opts?.recovery ?? false,
5498
5804
  lazyUsage: analyzeLazyUsage(combinator),
5499
5805
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5500
5806
  };
@@ -5547,7 +5853,7 @@ function compile(combinator, mapFnSources) {
5547
5853
  parseWithContext(input, parseCtx, pos = 0) {
5548
5854
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5549
5855
  },
5550
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5856
+ // Note: collects expect() errors via _errors. Unlike interpreter
5551
5857
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5552
5858
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5553
5859
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5593,6 +5899,7 @@ function compileRuleMap(ruleMap, opts) {
5593
5899
  triviaFnNames: /* @__PURE__ */ new Map(),
5594
5900
  namedFnDecls: [],
5595
5901
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5902
+ recovery: opts?.recovery ?? false,
5596
5903
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5597
5904
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5598
5905
  };
@@ -5688,6 +5995,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5688
5995
  triviaFnNames: /* @__PURE__ */ new Map(),
5689
5996
  namedFnDecls: [],
5690
5997
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5998
+ recovery: opts?.recovery ?? false,
5691
5999
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5692
6000
  ns,
5693
6001
  deferFirstSetRefs: true,
@@ -6335,6 +6643,42 @@ function materializePiece(p, trivia2) {
6335
6643
  return pieces;
6336
6644
  }
6337
6645
 
6646
+ // src/combinators/guard.ts
6647
+ function guard(predicate) {
6648
+ const meta = {
6649
+ firstSet: { kind: "any" },
6650
+ canMatchNewline: false,
6651
+ isTrivia: false
6652
+ };
6653
+ return {
6654
+ _tag: "guard",
6655
+ _meta: meta,
6656
+ _def: { tag: "guard", predicate },
6657
+ parse(_input, pos, ctx) {
6658
+ if (predicate(ctx.state))
6659
+ return { ok: true, value: null, span: { start: pos, end: pos } };
6660
+ return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
6661
+ }
6662
+ };
6663
+ }
6664
+
6665
+ // src/combinators/withCtx.ts
6666
+ function withCtx(extra, combinator) {
6667
+ const meta = {
6668
+ firstSet: combinator._meta.firstSet,
6669
+ canMatchNewline: combinator._meta.canMatchNewline,
6670
+ isTrivia: false
6671
+ };
6672
+ return {
6673
+ _tag: "withCtx",
6674
+ _meta: meta,
6675
+ _def: { tag: "withCtx", extra, parser: combinator },
6676
+ parse(input, pos, ctx) {
6677
+ return combinator.parse(input, pos, { ...ctx, state: extra });
6678
+ }
6679
+ };
6680
+ }
6681
+
6338
6682
  // src/cst/offset-model.ts
6339
6683
  var { isFinite } = Number;
6340
6684
 
@@ -6366,6 +6710,35 @@ var SUPPORTED = {
6366
6710
  function isScopeEntry(v2) {
6367
6711
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
6368
6712
  }
6713
+ function propName(p) {
6714
+ if (p.computed) return null;
6715
+ const key = p.key;
6716
+ return key.type === "Identifier" ? key.name ?? null : key.type === "Literal" ? String(key.value) : null;
6717
+ }
6718
+ function isGatedArmExpr(e) {
6719
+ if (e.type !== "ObjectExpression") return false;
6720
+ let hasGate = false, hasCombinator = false;
6721
+ for (const prop of e.properties) {
6722
+ if (prop.type !== "Property") continue;
6723
+ const name = propName(prop);
6724
+ if (name === "gate") hasGate = true;
6725
+ else if (name === "combinator") hasCombinator = true;
6726
+ }
6727
+ return hasGate && hasCombinator;
6728
+ }
6729
+ function gatedArmParts(e) {
6730
+ let gate;
6731
+ let combinator;
6732
+ for (const prop of e.properties) {
6733
+ if (prop.type !== "Property") return null;
6734
+ const op = prop;
6735
+ const name = propName(op);
6736
+ if (name === "gate") gate = op.value;
6737
+ else if (name === "combinator") combinator = op.value;
6738
+ else return null;
6739
+ }
6740
+ return gate && combinator ? { gate, combinator } : null;
6741
+ }
6369
6742
  function isCombinator(v2) {
6370
6743
  return !!v2 && typeof v2 === "object" && "_def" in v2;
6371
6744
  }
@@ -6547,6 +6920,72 @@ function exprToCombi(node2, scope, code, mfs) {
6547
6920
  return null;
6548
6921
  }
6549
6922
  }
6923
+ if (callee.name === "guard") {
6924
+ if (code === void 0 || mfs === void 0) return null;
6925
+ const [predArg] = node2.arguments;
6926
+ if (!predArg || predArg.type === "SpreadElement") return null;
6927
+ const predSrc = code.slice(predArg.start, predArg.end);
6928
+ mfs.push(predSrc);
6929
+ try {
6930
+ const combi = guard(() => true);
6931
+ if (combi._def.tag !== "guard") return null;
6932
+ combi._def.predSrc = predSrc;
6933
+ return combi;
6934
+ } catch {
6935
+ return null;
6936
+ }
6937
+ }
6938
+ if (callee.name === "withCtx") {
6939
+ if (code === void 0 || mfs === void 0) return null;
6940
+ const [extraArg, innerArg] = node2.arguments;
6941
+ if (!extraArg || !innerArg || extraArg.type === "SpreadElement" || innerArg.type === "SpreadElement") return null;
6942
+ const extraSrc = code.slice(extraArg.start, extraArg.end);
6943
+ mfs.push(extraSrc);
6944
+ const inner = anyValue(innerArg, scope, code, mfs);
6945
+ if (!isCombinator(inner)) return null;
6946
+ try {
6947
+ const combi = withCtx({}, inner);
6948
+ if (combi._def.tag !== "withCtx") return null;
6949
+ combi._def.extraSrc = extraSrc;
6950
+ return combi;
6951
+ } catch {
6952
+ return null;
6953
+ }
6954
+ }
6955
+ if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
6956
+ if (code === void 0 || mfs === void 0) return null;
6957
+ const arms = [];
6958
+ const gateSrcs = [];
6959
+ for (const argNode of node2.arguments) {
6960
+ if (argNode.type === "SpreadElement") return null;
6961
+ if (isGatedArmExpr(argNode)) {
6962
+ const parts = gatedArmParts(argNode);
6963
+ if (!parts) return null;
6964
+ const gateSrc = code.slice(parts.gate.start, parts.gate.end);
6965
+ mfs.push(gateSrc);
6966
+ const combi = anyValue(parts.combinator, scope, code, mfs);
6967
+ if (!isCombinator(combi)) return null;
6968
+ arms.push({ gate: () => true, combinator: combi });
6969
+ gateSrcs.push(gateSrc);
6970
+ } else {
6971
+ const combi = anyValue(argNode, scope, code, mfs);
6972
+ if (!isCombinator(combi)) return null;
6973
+ arms.push(combi);
6974
+ gateSrcs.push(null);
6975
+ }
6976
+ }
6977
+ try {
6978
+ const combi = choice(...arms);
6979
+ if (combi._def.tag !== "choice") return null;
6980
+ combi._def.gateSrcs = gateSrcs;
6981
+ for (let i = 0; i < combi._def.gates.length; i++) {
6982
+ if (combi._def.gates[i] !== null !== (gateSrcs[i] !== null)) return null;
6983
+ }
6984
+ return combi;
6985
+ } catch {
6986
+ return null;
6987
+ }
6988
+ }
6550
6989
  const factory = SUPPORTED[callee.name];
6551
6990
  if (!factory) return null;
6552
6991
  const args = node2.arguments.map((arg) => {
@@ -6609,9 +7048,9 @@ function anyValue(node2, scope, code, mfs) {
6609
7048
  if (typeof key !== "string" && typeof key !== "number") return null;
6610
7049
  return obj[key] ?? null;
6611
7050
  }
6612
- const propName = mem.property.name;
6613
- if (!propName) return null;
6614
- return obj[propName] ?? null;
7051
+ const propName2 = mem.property.name;
7052
+ if (!propName2) return null;
7053
+ return obj[propName2] ?? null;
6615
7054
  }
6616
7055
  if (node2.type === "CallExpression") {
6617
7056
  const callee = node2.callee;
@@ -6856,7 +7295,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
6856
7295
  if (!code.includes("parseman")) return null;
6857
7296
  if (!code.includes("macro")) return null;
6858
7297
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
6859
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
7298
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
6860
7299
  if (result?.warnings.length) {
6861
7300
  for (const w of result.warnings) {
6862
7301
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -6960,7 +7399,7 @@ function extractImportBindings(body) {
6960
7399
  }
6961
7400
  return out;
6962
7401
  }
6963
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
7402
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
6964
7403
  let result;
6965
7404
  try {
6966
7405
  result = (0, import_oxc_parser.parseSync)(id, code);
@@ -7031,7 +7470,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7031
7470
  (p) => p.key?.name === "trivia"
7032
7471
  )?.value : void 0;
7033
7472
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
7034
- const compiled = compileRuleMap([...ruleMap], gTrivia ? { trivia: gTrivia } : void 0);
7473
+ const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
7035
7474
  if (!compiled) {
7036
7475
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7037
7476
  return null;
@@ -7129,7 +7568,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7129
7568
  const ns = nsFor(label2);
7130
7569
  const ir = serializeRuleMap(entries);
7131
7570
  if (ir) return { carried: [{ ns, ir }] };
7132
- const p = compileLinkable(entries, ns, composing ? { trivia: composing } : void 0);
7571
+ const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
7133
7572
  return p ? { carried: [p] } : null;
7134
7573
  };
7135
7574
  const argPieces = (arg, label2, composing) => {
@@ -7305,7 +7744,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7305
7744
  const refEntry = scope.get(varName);
7306
7745
  const refCombi = refEntry?.combi ?? null;
7307
7746
  if (refCombi) {
7308
- const compiled2 = compile(refCombi);
7747
+ const compiled2 = compile(refCombi, void 0, { recovery });
7309
7748
  if (compiled2.inlineExpression === null) {
7310
7749
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
7311
7750
  continue;
@@ -7322,7 +7761,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7322
7761
  let replacement = compiledRules.replacement;
7323
7762
  if (exportPrefix) {
7324
7763
  const ns = nsFor(varName);
7325
- const pieces = compileLinkable([...compiledRules.ruleMap], ns);
7764
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
7326
7765
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
7327
7766
  const ir = serializeRuleMap([...compiledRules.ruleMap]);
7328
7767
  replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
@@ -7358,7 +7797,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7358
7797
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
7359
7798
  continue;
7360
7799
  }
7361
- const compiled = compile(parser2);
7800
+ const compiled = compile(parser2, void 0, { recovery });
7362
7801
  if (compiled.inlineExpression === null) {
7363
7802
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
7364
7803
  continue;