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
@@ -297,7 +297,13 @@ function saveCstMark(ctx) {
297
297
  raw: cstRawLen(ctx),
298
298
  tlog: cstTlLen(ctx),
299
299
  leaves: cstLeavesLen(ctx),
300
- fields: ctx._fields?.length ?? 0
300
+ fields: ctx._fields?.length ?? 0,
301
+ // Speculative rollback must truncate the flat recovery-error sink in lockstep
302
+ // with the CST leaves: a branch that recovered (pushing a ParseError to
303
+ // ctx._errors AND embedding it as a CST child via captureError) then failed
304
+ // must remove BOTH, or the flat error survives as a GHOST that a tree walk
305
+ // can't see (flat run().errors would disagree with the embedded set).
306
+ errors: ctx._errors?.length ?? 0
301
307
  };
302
308
  }
303
309
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -313,6 +319,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
313
319
  if (len === 0) b[keySingle] = void 0;
314
320
  }
315
321
  function rollbackCstCapture(ctx, mark) {
322
+ if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
316
323
  const b = ctx._cstBuf;
317
324
  if (b) {
318
325
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
@@ -1256,10 +1263,10 @@ function needsDeferredTriviaCommit(ctx) {
1256
1263
  }
1257
1264
  function saveTriviaMark(ctx) {
1258
1265
  const m = saveCstMark(ctx);
1259
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1266
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1260
1267
  }
1261
1268
  function rollbackTrivia(ctx, mark) {
1262
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1269
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1263
1270
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1264
1271
  }
1265
1272
  function scanWithLabels(input, cur, ctx) {
@@ -1493,6 +1500,93 @@ function scanBlockComment(input, cur) {
1493
1500
  return close === -1 ? cur : close + 2;
1494
1501
  }
1495
1502
 
1503
+ // src/recovery/scan.ts
1504
+ function probeAt(sentinel, input, pos, ctx) {
1505
+ const s = {
1506
+ _probe: ctx._probe,
1507
+ _errors: ctx._errors,
1508
+ _cstLeaves: ctx._cstLeaves,
1509
+ _cstChildren: ctx._cstChildren,
1510
+ _cstRawChildren: ctx._cstRawChildren,
1511
+ _cstTriviaLog: ctx._cstTriviaLog,
1512
+ _triviaLog: ctx._triviaLog,
1513
+ _fields: ctx._fields,
1514
+ _cstBuf: ctx._cstBuf,
1515
+ _tolerant: ctx._tolerant,
1516
+ _sync: ctx._sync
1517
+ };
1518
+ ctx._probe = void 0;
1519
+ ctx._errors = void 0;
1520
+ ctx._cstLeaves = void 0;
1521
+ ctx._cstChildren = void 0;
1522
+ ctx._cstRawChildren = void 0;
1523
+ ctx._cstTriviaLog = void 0;
1524
+ ctx._triviaLog = void 0;
1525
+ ctx._fields = void 0;
1526
+ ctx._cstBuf = void 0;
1527
+ ctx._tolerant = void 0;
1528
+ ctx._sync = void 0;
1529
+ try {
1530
+ return sentinel.parse(input, pos, ctx).ok;
1531
+ } finally {
1532
+ ctx._probe = s._probe;
1533
+ ctx._errors = s._errors;
1534
+ ctx._cstLeaves = s._cstLeaves;
1535
+ ctx._cstChildren = s._cstChildren;
1536
+ ctx._cstRawChildren = s._cstRawChildren;
1537
+ ctx._cstTriviaLog = s._cstTriviaLog;
1538
+ ctx._triviaLog = s._triviaLog;
1539
+ ctx._fields = s._fields;
1540
+ ctx._cstBuf = s._cstBuf;
1541
+ ctx._tolerant = s._tolerant;
1542
+ ctx._sync = s._sync;
1543
+ }
1544
+ }
1545
+ function orSentinel(a, b) {
1546
+ if (b === void 0) return a;
1547
+ return {
1548
+ _tag: "orSentinel",
1549
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
1550
+ _def: { tag: "unknown" },
1551
+ parse(input, pos, ctx) {
1552
+ const ra = a.parse(input, pos, ctx);
1553
+ if (ra.ok) return ra;
1554
+ return b.parse(input, pos, ctx);
1555
+ }
1556
+ };
1557
+ }
1558
+ function firstSetSentinel(fs2) {
1559
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
1560
+ const ranges = fs2.ranges;
1561
+ return {
1562
+ _tag: "firstSetSentinel",
1563
+ _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
1564
+ _def: { tag: "unknown" },
1565
+ parse(input, pos) {
1566
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
1567
+ if (code !== void 0) {
1568
+ for (const r of ranges) {
1569
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
1570
+ }
1571
+ }
1572
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
1573
+ }
1574
+ };
1575
+ }
1576
+ function recoverScan(input, from, ctx, sync, expected) {
1577
+ let scanPos = from;
1578
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
1579
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
1580
+ ctx._errors?.push(error);
1581
+ return { error, end: scanPos };
1582
+ }
1583
+ function matchesAt(sentinel, input, pos, ctx) {
1584
+ return probeAt(sentinel, input, pos, ctx);
1585
+ }
1586
+ function captureError(ctx, error) {
1587
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
1588
+ }
1589
+
1496
1590
  // src/combinators/sequence.ts
1497
1591
  function sequence(...parsers) {
1498
1592
  const meta = {
@@ -1503,11 +1597,59 @@ function sequence(...parsers) {
1503
1597
  isTrivia: false
1504
1598
  };
1505
1599
  const def = { tag: "sequence", parsers };
1600
+ let followSentinels;
1601
+ function parseTolerant(input, pos, ctx) {
1602
+ followSentinels ??= parsers.map((_, i) => {
1603
+ const fs2 = parsers.slice(i + 1).reduce(
1604
+ (acc, p) => union(acc, firstSetOf(p)),
1605
+ { kind: "empty" }
1606
+ );
1607
+ return firstSetSentinel(fs2);
1608
+ });
1609
+ const values = def.valueUnused ? void 0 : [];
1610
+ let cur = pos;
1611
+ const inheritedSync = ctx._sync;
1612
+ try {
1613
+ for (let i = 0; i < parsers.length; i++) {
1614
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1615
+ if (ctx.trivia && i > 0) {
1616
+ const mark = saveTriviaMark(ctx);
1617
+ let scanEnd;
1618
+ if (needsDeferredTriviaCommit(ctx)) {
1619
+ const scan = scanTrivia(input, cur, ctx);
1620
+ scan.commit();
1621
+ scanEnd = scan.end;
1622
+ } else {
1623
+ scanEnd = advanceTrivia(input, cur, ctx);
1624
+ }
1625
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1626
+ if (!result2.ok) return result2;
1627
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1628
+ else rollbackTrivia(ctx, mark);
1629
+ if (values !== void 0) values.push(result2.value);
1630
+ continue;
1631
+ }
1632
+ const result = parsers[i].parse(input, cur, ctx);
1633
+ if (!result.ok) return result;
1634
+ if (values !== void 0) values.push(result.value);
1635
+ cur = result.span.end;
1636
+ }
1637
+ } finally {
1638
+ ctx._sync = inheritedSync;
1639
+ }
1640
+ return {
1641
+ ok: true,
1642
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1643
+ value: values ?? void 0,
1644
+ span: { start: pos, end: cur }
1645
+ };
1646
+ }
1506
1647
  return {
1507
1648
  _tag: "sequence",
1508
1649
  _meta: meta,
1509
1650
  _def: def,
1510
1651
  parse(input, pos, ctx) {
1652
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1511
1653
  const values = def.valueUnused ? void 0 : [];
1512
1654
  let cur = pos;
1513
1655
  for (let i = 0; i < parsers.length; i++) {
@@ -1546,6 +1688,66 @@ function sequence(...parsers) {
1546
1688
  };
1547
1689
  }
1548
1690
 
1691
+ // src/combinators/expect.ts
1692
+ function deriveExpected(c) {
1693
+ const def = c._def;
1694
+ switch (def.tag) {
1695
+ case "literal":
1696
+ return [JSON.stringify(def.value)];
1697
+ case "regex":
1698
+ return [`/${def.source}/`];
1699
+ case "keywords":
1700
+ return def.words.map((w) => JSON.stringify(w));
1701
+ case "label":
1702
+ return [def.label];
1703
+ case "choice":
1704
+ return def.parsers.flatMap(deriveExpected);
1705
+ case "sequence":
1706
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1707
+ case "node":
1708
+ case "grammar":
1709
+ case "trivia":
1710
+ case "token":
1711
+ case "optional":
1712
+ case "many":
1713
+ case "oneOrMore":
1714
+ case "transform":
1715
+ case "not":
1716
+ return deriveExpected(def.parser);
1717
+ case "lazy": {
1718
+ const name = c._ruleName;
1719
+ try {
1720
+ return deriveExpected(def.thunk());
1721
+ } catch {
1722
+ return name ? [name] : [];
1723
+ }
1724
+ }
1725
+ default:
1726
+ return [];
1727
+ }
1728
+ }
1729
+ function expect(combinator, label2) {
1730
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1731
+ const meta = {
1732
+ firstSet: combinator._meta.firstSet,
1733
+ canMatchNewline: combinator._meta.canMatchNewline,
1734
+ isTrivia: false
1735
+ };
1736
+ return {
1737
+ _tag: "expect",
1738
+ _meta: meta,
1739
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1740
+ parse(input, pos, ctx) {
1741
+ const result = combinator.parse(input, pos, ctx);
1742
+ if (result.ok) return result;
1743
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1744
+ ctx._errors?.push(error);
1745
+ if (ctx._tolerant) captureError(ctx, error);
1746
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1747
+ }
1748
+ };
1749
+ }
1750
+
1549
1751
  // src/combinators/repeat.ts
1550
1752
  function repItem(combinator, input, cur, ctx) {
1551
1753
  const mark = saveTriviaMark(ctx);
@@ -1566,7 +1768,7 @@ function repItem(combinator, input, cur, ctx) {
1566
1768
  const result = combinator.parse(input, pos, ctx);
1567
1769
  if (!result.ok) {
1568
1770
  rollbackTrivia(ctx, mark);
1569
- return { fail: result };
1771
+ return { fail: result, failPos: pos };
1570
1772
  }
1571
1773
  if (result.span.end === pos) {
1572
1774
  rollbackTrivia(ctx, mark);
@@ -1581,6 +1783,7 @@ function many(combinator) {
1581
1783
  isTrivia: false
1582
1784
  };
1583
1785
  const def = { tag: "many", parser: combinator, min: 0 };
1786
+ let expected;
1584
1787
  return {
1585
1788
  _tag: "many",
1586
1789
  _meta: meta,
@@ -1590,7 +1793,18 @@ function many(combinator) {
1590
1793
  let cur = pos;
1591
1794
  while (cur < input.length) {
1592
1795
  const item = repItem(combinator, input, cur, ctx);
1593
- if (item === "stop" || "fail" in item) break;
1796
+ if (item === "stop") break;
1797
+ if ("fail" in item) {
1798
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1799
+ if (sync === void 0) break;
1800
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1801
+ expected ??= deriveExpected(combinator);
1802
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1803
+ if (values !== void 0) values.push(error);
1804
+ captureError(ctx, error);
1805
+ cur = end;
1806
+ continue;
1807
+ }
1594
1808
  if (values !== void 0) values.push(item.value);
1595
1809
  cur = item.end;
1596
1810
  }
@@ -1605,6 +1819,7 @@ function oneOrMore(combinator) {
1605
1819
  isTrivia: false
1606
1820
  };
1607
1821
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1822
+ let expected;
1608
1823
  return {
1609
1824
  _tag: "oneOrMore",
1610
1825
  _meta: meta,
@@ -1616,7 +1831,18 @@ function oneOrMore(combinator) {
1616
1831
  let cur = first.span.end;
1617
1832
  while (cur < input.length) {
1618
1833
  const item = repItem(combinator, input, cur, ctx);
1619
- if (item === "stop" || "fail" in item) break;
1834
+ if (item === "stop") break;
1835
+ if ("fail" in item) {
1836
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1837
+ if (sync === void 0) break;
1838
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1839
+ expected ??= deriveExpected(combinator);
1840
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1841
+ if (values !== void 0) values.push(error);
1842
+ captureError(ctx, error);
1843
+ cur = end;
1844
+ continue;
1845
+ }
1620
1846
  if (values !== void 0) values.push(item.value);
1621
1847
  cur = item.end;
1622
1848
  }
@@ -1662,15 +1888,29 @@ function sepBy(combinator, separator) {
1662
1888
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1663
1889
  isTrivia: false
1664
1890
  };
1891
+ let expected;
1665
1892
  return {
1666
1893
  _tag: "sepBy",
1667
1894
  _meta: meta,
1668
1895
  _def: { tag: "sepBy", parser: combinator, separator },
1669
1896
  parse(input, pos, ctx) {
1670
1897
  const first = combinator.parse(input, pos, ctx);
1671
- if (!first.ok) return { ok: true, value: [], span: { start: pos, end: pos } };
1672
- const values = [first.value];
1673
- let cur = first.span.end;
1898
+ const values = [];
1899
+ let cur;
1900
+ if (first.ok) {
1901
+ values.push(first.value);
1902
+ cur = first.span.end;
1903
+ } else {
1904
+ const term = ctx._tolerant ? ctx._sync : void 0;
1905
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1906
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1907
+ }
1908
+ expected ??= deriveExpected(combinator);
1909
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1910
+ values.push(rec.error);
1911
+ captureError(ctx, rec.error);
1912
+ cur = rec.end;
1913
+ }
1674
1914
  while (cur < input.length) {
1675
1915
  const loopMark = saveTriviaMark(ctx);
1676
1916
  let sepPos = cur;
@@ -1700,6 +1940,15 @@ function sepBy(combinator, separator) {
1700
1940
  }
1701
1941
  const next = combinator.parse(input, nextPos, ctx);
1702
1942
  if (!next.ok) {
1943
+ const term = ctx._tolerant ? ctx._sync : void 0;
1944
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1945
+ expected ??= deriveExpected(combinator);
1946
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1947
+ values.push(rec.error);
1948
+ captureError(ctx, rec.error);
1949
+ cur = rec.end;
1950
+ continue;
1951
+ }
1703
1952
  rollbackTrivia(ctx, loopMark);
1704
1953
  break;
1705
1954
  }
@@ -1836,7 +2085,7 @@ function rules(a, b) {
1836
2085
  cache[key] = parser2;
1837
2086
  }
1838
2087
  }
1839
- if (options?.trivia !== void 0) {
2088
+ if (options?.trivia != null) {
1840
2089
  for (const key of Object.keys(definitions)) {
1841
2090
  const rule = cache[key];
1842
2091
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1866,7 +2115,9 @@ function not(combinator) {
1866
2115
  _meta: meta,
1867
2116
  _def: { tag: "not", parser: combinator },
1868
2117
  parse(input, pos, ctx) {
2118
+ const mark = saveCstMark(ctx);
1869
2119
  const result = combinator.parse(input, pos, ctx);
2120
+ rollbackCstCapture(ctx, mark);
1870
2121
  if (result.ok) {
1871
2122
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1872
2123
  }
@@ -1980,7 +2231,7 @@ function unwrapChild(child) {
1980
2231
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1981
2232
  }
1982
2233
  function isCstChild(value) {
1983
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2234
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
1984
2235
  }
1985
2236
  function missingInferredType() {
1986
2237
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2233,65 +2484,6 @@ function token(root) {
2233
2484
  };
2234
2485
  }
2235
2486
 
2236
- // src/combinators/expect.ts
2237
- function deriveExpected(c) {
2238
- const def = c._def;
2239
- switch (def.tag) {
2240
- case "literal":
2241
- return [JSON.stringify(def.value)];
2242
- case "regex":
2243
- return [`/${def.source}/`];
2244
- case "keywords":
2245
- return def.words.map((w) => JSON.stringify(w));
2246
- case "label":
2247
- return [def.label];
2248
- case "choice":
2249
- return def.parsers.flatMap(deriveExpected);
2250
- case "sequence":
2251
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
2252
- case "node":
2253
- case "grammar":
2254
- case "trivia":
2255
- case "token":
2256
- case "optional":
2257
- case "many":
2258
- case "oneOrMore":
2259
- case "transform":
2260
- case "not":
2261
- return deriveExpected(def.parser);
2262
- case "lazy": {
2263
- const name = c._ruleName;
2264
- try {
2265
- return deriveExpected(def.thunk());
2266
- } catch {
2267
- return name ? [name] : [];
2268
- }
2269
- }
2270
- default:
2271
- return [];
2272
- }
2273
- }
2274
- function expect(combinator, label2) {
2275
- const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
2276
- const meta = {
2277
- firstSet: combinator._meta.firstSet,
2278
- canMatchNewline: combinator._meta.canMatchNewline,
2279
- isTrivia: false
2280
- };
2281
- return {
2282
- _tag: "expect",
2283
- _meta: meta,
2284
- _def: { tag: "expect", parser: combinator, label: label2, expected },
2285
- parse(input, pos, ctx) {
2286
- const result = combinator.parse(input, pos, ctx);
2287
- if (result.ok) return result;
2288
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2289
- ctx._errors?.push(error);
2290
- return { ok: true, value: error, span: { start: pos, end: pos } };
2291
- }
2292
- };
2293
- }
2294
-
2295
2487
  // src/compiler/scannable-run.ts
2296
2488
  var BACKSLASH = 92;
2297
2489
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2363,20 +2555,39 @@ function parseSeqParts(source) {
2363
2555
  lit = [];
2364
2556
  }
2365
2557
  };
2366
- const runFrom = (ranges, negated, q) => {
2558
+ const braceQuant = (at) => {
2559
+ const close = source.indexOf("}", at);
2560
+ if (close === -1) return null;
2561
+ const body = source.slice(at + 1, close);
2562
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2563
+ if (!m) return null;
2564
+ const min = Number(m[1]);
2565
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2566
+ if (max < 1 || min > max) return null;
2567
+ return { min, max, len: close - at + 1 };
2568
+ };
2569
+ const runFrom = (ranges, negated, at) => {
2570
+ const q = source[at];
2367
2571
  if (q === "+") {
2368
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2572
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2369
2573
  return 1;
2370
2574
  }
2371
2575
  if (q === "*") {
2372
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2576
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2373
2577
  return 1;
2374
2578
  }
2375
2579
  if (q === "?") {
2376
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2580
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2377
2581
  return 1;
2378
2582
  }
2379
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2583
+ if (q === "{") {
2584
+ const b = braceQuant(at);
2585
+ if (b) {
2586
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2587
+ return b.len;
2588
+ }
2589
+ }
2590
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2380
2591
  return 0;
2381
2592
  };
2382
2593
  let i = 0;
@@ -2401,7 +2612,7 @@ function parseSeqParts(source) {
2401
2612
  if (!ranges) return null;
2402
2613
  flush();
2403
2614
  i = k + 1;
2404
- i += runFrom(ranges, neg, source[i]);
2615
+ i += runFrom(ranges, neg, i);
2405
2616
  continue;
2406
2617
  }
2407
2618
  if (ch === "\\") {
@@ -2409,7 +2620,7 @@ function parseSeqParts(source) {
2409
2620
  if (e === "d" || e === "w" || e === "s") {
2410
2621
  flush();
2411
2622
  i += 2;
2412
- i += runFrom(shorthandRanges(e), false, source[i]);
2623
+ i += runFrom(shorthandRanges(e), false, i);
2413
2624
  continue;
2414
2625
  }
2415
2626
  const uni = readUnicodeEscape(source, i);
@@ -2498,7 +2709,7 @@ function seqIsUnambiguous(parts) {
2498
2709
  let hasMandatory = false;
2499
2710
  for (let k = 0; k < parts.length; k++) {
2500
2711
  const p = parts[k];
2501
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2712
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2502
2713
  if (mandatory) hasMandatory = true;
2503
2714
  if (p.part === "group") {
2504
2715
  const exposure = groupPartExposure(p);
@@ -2509,9 +2720,8 @@ function seqIsUnambiguous(parts) {
2509
2720
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2510
2721
  }
2511
2722
  }
2512
- const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
2513
- const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
2514
- if (skippableSingle || isUnboundedRepeat) {
2723
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2724
+ if (needsDisjoint) {
2515
2725
  const rest = seqFirstAccept(parts.slice(k + 1));
2516
2726
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2517
2727
  }
@@ -2568,8 +2778,8 @@ function fixedClassSeq(shape) {
2568
2778
  if (p.part === "lit") {
2569
2779
  if (p.optional) return null;
2570
2780
  for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
2571
- } else if (p.part === "run" && !p.unbounded && p.min === 1) {
2572
- out.push({ ranges: p.ranges, negated: p.negated });
2781
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2782
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2573
2783
  } else {
2574
2784
  return null;
2575
2785
  }
@@ -2621,7 +2831,7 @@ function trailingBacktrackClass(shape) {
2621
2831
  const last = shape.parts[shape.parts.length - 1];
2622
2832
  if (last.part === "lit") return null;
2623
2833
  if (last.part === "run") {
2624
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2834
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2625
2835
  return null;
2626
2836
  }
2627
2837
  return groupPartExposure(last);
@@ -2758,7 +2968,7 @@ function partFirstAccept(part) {
2758
2968
  function seqFirstAccept(parts) {
2759
2969
  const sets = [];
2760
2970
  for (const p of parts) {
2761
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2971
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2762
2972
  sets.push(partFirstAccept(p));
2763
2973
  if (mandatory) break;
2764
2974
  }
@@ -3173,7 +3383,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3173
3383
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3174
3384
  lines.push(`${ind2} ${endV2} += ${L}`);
3175
3385
  }
3176
- } else if (p.part === "run" && !p.unbounded) {
3386
+ } else if (p.part === "run" && p.max === Infinity) {
3387
+ const s = mint("_s");
3388
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3389
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3390
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3391
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3392
+ } else if (p.part === "run" && p.max === 1) {
3177
3393
  if (p.min === 1) {
3178
3394
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3179
3395
  lines.push(`${ind2} ${endV2}++`);
@@ -3183,8 +3399,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3183
3399
  } else if (p.part === "run") {
3184
3400
  const s = mint("_s");
3185
3401
  lines.push(`${ind2} const ${s} = ${endV2}`);
3186
- lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3187
- if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3402
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3403
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3188
3404
  } else {
3189
3405
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3190
3406
  if (!p.unbounded) {
@@ -3651,21 +3867,29 @@ function hoistExpected(ctx, constArrSource) {
3651
3867
  }
3652
3868
  return name;
3653
3869
  }
3870
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3871
+ if (!ctx.recovery) return "";
3872
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3873
+ const pb = v(ctx, "_pb");
3874
+ 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} } } } `;
3875
+ }
3654
3876
  function failBody(ctx, expected, posExpr) {
3877
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3655
3878
  if (ctx.failLabel) {
3656
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3657
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3879
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3880
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3658
3881
  }
3659
- return failReturn(expected, posExpr);
3882
+ return probe + failReturn(expected, posExpr);
3660
3883
  }
3661
3884
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3885
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3662
3886
  if (ctx.failLabel) {
3663
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3887
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3664
3888
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3665
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3889
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3666
3890
  }
3667
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3668
- return failReturnArr(expectedArr, posExpr);
3891
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3892
+ return probe + failReturnArr(expectedArr, posExpr);
3669
3893
  }
3670
3894
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3671
3895
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4165,6 +4389,10 @@ function emitRegex(def, ctx, pos) {
4165
4389
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4166
4390
  return { stmts, valueVar: vv, endVar };
4167
4391
  }
4392
+ function syncSentinelExpr(fs2) {
4393
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
4394
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
4395
+ }
4168
4396
  function emitSeqValues(def, ctx, pos) {
4169
4397
  const startV = v(ctx, "_start");
4170
4398
  const curV = v(ctx, "_cur");
@@ -4173,7 +4401,17 @@ function emitSeqValues(def, ctx, pos) {
4173
4401
  `${ind(ctx)}let ${curV} = ${pos}`
4174
4402
  ];
4175
4403
  const valueVars = [];
4404
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4405
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4406
+ const publishSync = (i) => {
4407
+ if (!ctx.recovery) return "";
4408
+ const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4409
+ const ref2 = syncSentinelExpr(fs2);
4410
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4411
+ };
4176
4412
  for (let i = 0; i < def.parsers.length; i++) {
4413
+ const syncPub = publishSync(i);
4414
+ if (syncPub) stmts.push(syncPub);
4177
4415
  if (i > 0 && ctx.activeTrivia) {
4178
4416
  if (ctx.capturing) {
4179
4417
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4198,7 +4436,17 @@ function emitSeqValues(def, ctx, pos) {
4198
4436
  continue;
4199
4437
  } else {
4200
4438
  const trivFn = ensureTriviaFn(ctx);
4201
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4439
+ const scanEndV = v(ctx, "_sne");
4440
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4441
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4442
+ stmts.push(...r2.stmts);
4443
+ const endAfterV = v(ctx, "_sea");
4444
+ stmts.push(
4445
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4446
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4447
+ );
4448
+ valueVars.push(r2.valueVar);
4449
+ continue;
4202
4450
  }
4203
4451
  }
4204
4452
  const r = emit(def.parsers[i], ctx, curV);
@@ -4428,7 +4676,7 @@ function emitFirstMatch(def, ctx, pos) {
4428
4676
  if (gate) {
4429
4677
  const gateIdx = ctx.mapFns.length;
4430
4678
  ctx.mapFns.push(gate);
4431
- ctx.mapFnSrcs.push(null);
4679
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4432
4680
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4433
4681
  }
4434
4682
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4533,6 +4781,8 @@ function emitMany(def, ctx, pos) {
4533
4781
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4534
4782
  `${ind(ctx)}let ${curV} = ${pos}`
4535
4783
  ];
4784
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4785
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4536
4786
  if (def.min === 1) {
4537
4787
  const firstR = emit(def.parser, ctx, curV);
4538
4788
  stmts.push(...firstR.stmts);
@@ -4566,10 +4816,26 @@ function emitMany(def, ctx, pos) {
4566
4816
  }
4567
4817
  }
4568
4818
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
4569
- stmts.push(
4570
- ...iterStmts,
4571
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
4572
- );
4819
+ stmts.push(...iterStmts);
4820
+ if (ctx.recovery) {
4821
+ const rrV = v(ctx, "_rr");
4822
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4823
+ stmts.push(
4824
+ `${ind(ctx)}if (!${iterOk}) {`,
4825
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4826
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4827
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4828
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4829
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4830
+ `${ind(ctx)} continue`,
4831
+ `${ind(ctx)} }`,
4832
+ `${ind(ctx)} break`,
4833
+ `${ind(ctx)}}`,
4834
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4835
+ );
4836
+ } else {
4837
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4838
+ }
4573
4839
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4574
4840
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4575
4841
  ctx.indent--;
@@ -4591,20 +4857,56 @@ function emitOptional(def, ctx, pos) {
4591
4857
  function emitSepBy(_p, def, ctx, pos) {
4592
4858
  const arrV = v(ctx, "_arr");
4593
4859
  const curV = v(ctx, "_cur");
4860
+ const rec = !!ctx.recovery;
4861
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4862
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4863
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4864
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4594
4865
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4595
4866
  const stmts = [
4596
4867
  `${ind(ctx)}const ${arrV} = []`,
4597
4868
  `${ind(ctx)}let ${curV} = ${pos}`,
4598
- ...firstStmts,
4599
- `${ind(ctx)}if (${firstOk}) {`
4869
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4870
+ ...firstStmts
4600
4871
  ];
4601
- ctx.indent++;
4602
- stmts.push(
4603
- `${ind(ctx)}${arrV}.push(${firstVal})`,
4604
- `${ind(ctx)}${curV} = ${firstEnd}`,
4605
- `${ind(ctx)}while (${curV} < input.length) {`
4606
- );
4607
- ctx.indent++;
4872
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4873
+ if (!rec) return [originalFail];
4874
+ const rr = v(ctx, "_rr");
4875
+ return [
4876
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4877
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4878
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4879
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4880
+ `${ind(ctx)} }`,
4881
+ `${ind(ctx)} ${rb}break`,
4882
+ `${ind(ctx)}}`
4883
+ ];
4884
+ };
4885
+ if (rec) {
4886
+ const rr0 = v(ctx, "_rr0");
4887
+ const didV = v(ctx, "_did");
4888
+ stmts.push(
4889
+ `${ind(ctx)}let ${didV} = false`,
4890
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4891
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4892
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4893
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4894
+ `${ind(ctx)}}`,
4895
+ `${ind(ctx)}if (${didV}) {`
4896
+ );
4897
+ ctx.indent++;
4898
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
4899
+ ctx.indent++;
4900
+ } else {
4901
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
4902
+ ctx.indent++;
4903
+ stmts.push(
4904
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
4905
+ `${ind(ctx)}${curV} = ${firstEnd}`,
4906
+ `${ind(ctx)}while (${curV} < input.length) {`
4907
+ );
4908
+ ctx.indent++;
4909
+ }
4608
4910
  let sepAtPos = curV;
4609
4911
  if (ctx.activeTrivia) {
4610
4912
  if (ctx.capturing) {
@@ -4630,8 +4932,7 @@ function emitSepBy(_p, def, ctx, pos) {
4630
4932
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4631
4933
  stmts.push(
4632
4934
  ...nextStmts,
4633
- // item failed → unwind the separator too, back to the end of the last item
4634
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
4935
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4635
4936
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4636
4937
  `${ind(ctx)}${curV} = ${nextEnd}`
4637
4938
  );
@@ -4647,7 +4948,7 @@ function emitSepBy(_p, def, ctx, pos) {
4647
4948
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4648
4949
  stmts.push(
4649
4950
  ...nextStmts,
4650
- `${ind(ctx)}if (!${nextOk}) break`,
4951
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4651
4952
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4652
4953
  `${ind(ctx)}${curV} = ${nextEnd}`
4653
4954
  );
@@ -4667,7 +4968,7 @@ function emitSepBy(_p, def, ctx, pos) {
4667
4968
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4668
4969
  stmts.push(
4669
4970
  ...nextStmts,
4670
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
4971
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4671
4972
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4672
4973
  `${ind(ctx)}${curV} = ${nextEnd}`
4673
4974
  );
@@ -4924,7 +5225,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4924
5225
  stmts.push(
4925
5226
  `${i}const ${ndV} = ${finalExpr}`,
4926
5227
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4927
- `${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} } })`
5228
+ `${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} } })`
4928
5229
  );
4929
5230
  return { stmts, valueVar: ndV, endVar };
4930
5231
  }
@@ -5014,6 +5315,10 @@ function emitExpect(def, ctx, pos) {
5014
5315
  `${ind0}if (!${okVar}) {`,
5015
5316
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5016
5317
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5318
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5319
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5320
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5321
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5017
5322
  `${ind0} ${valVar} = ${errV}`,
5018
5323
  `${ind0} ${endVar} = ${pos}`,
5019
5324
  `${ind0} ${okVar} = true`,
@@ -5180,7 +5485,7 @@ function emitDispatch(p, ctx, pos) {
5180
5485
  case "guard": {
5181
5486
  const fnIdx = ctx.mapFns.length;
5182
5487
  ctx.mapFns.push(def.predicate);
5183
- ctx.mapFnSrcs.push(null);
5488
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5184
5489
  const vv = v(ctx);
5185
5490
  return {
5186
5491
  stmts: [
@@ -5195,7 +5500,7 @@ function emitDispatch(p, ctx, pos) {
5195
5500
  const evIdx = ctx.mapFns.length;
5196
5501
  const extra = def.extra;
5197
5502
  ctx.mapFns.push((() => extra));
5198
- ctx.mapFnSrcs.push(null);
5503
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5199
5504
  const innerParser = def.parser;
5200
5505
  if (!ctx.namedParsers.has(innerParser)) {
5201
5506
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5439,7 +5744,7 @@ function ruleDependencies(ruleMap) {
5439
5744
  }
5440
5745
  return deps;
5441
5746
  }
5442
- function compile(combinator, mapFnSources) {
5747
+ function compile(combinator, mapFnSources, opts) {
5443
5748
  markUnusedValues(combinator);
5444
5749
  const grammarTrivia = combinator._meta.grammarTrivia;
5445
5750
  const ctx = {
@@ -5460,6 +5765,7 @@ function compile(combinator, mapFnSources) {
5460
5765
  triviaFnNames: /* @__PURE__ */ new Map(),
5461
5766
  namedFnDecls: [],
5462
5767
  capturing: hasNodeDef(combinator),
5768
+ recovery: opts?.recovery ?? false,
5463
5769
  lazyUsage: analyzeLazyUsage(combinator),
5464
5770
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5465
5771
  };
@@ -5512,7 +5818,7 @@ function compile(combinator, mapFnSources) {
5512
5818
  parseWithContext(input, parseCtx, pos = 0) {
5513
5819
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5514
5820
  },
5515
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5821
+ // Note: collects expect() errors via _errors. Unlike interpreter
5516
5822
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5517
5823
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5518
5824
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5558,6 +5864,7 @@ function compileRuleMap(ruleMap, opts) {
5558
5864
  triviaFnNames: /* @__PURE__ */ new Map(),
5559
5865
  namedFnDecls: [],
5560
5866
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5867
+ recovery: opts?.recovery ?? false,
5561
5868
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5562
5869
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5563
5870
  };
@@ -5653,6 +5960,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5653
5960
  triviaFnNames: /* @__PURE__ */ new Map(),
5654
5961
  namedFnDecls: [],
5655
5962
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5963
+ recovery: opts?.recovery ?? false,
5656
5964
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5657
5965
  ns,
5658
5966
  deferFirstSetRefs: true,
@@ -6300,6 +6608,42 @@ function materializePiece(p, trivia2) {
6300
6608
  return pieces;
6301
6609
  }
6302
6610
 
6611
+ // src/combinators/guard.ts
6612
+ function guard(predicate) {
6613
+ const meta = {
6614
+ firstSet: { kind: "any" },
6615
+ canMatchNewline: false,
6616
+ isTrivia: false
6617
+ };
6618
+ return {
6619
+ _tag: "guard",
6620
+ _meta: meta,
6621
+ _def: { tag: "guard", predicate },
6622
+ parse(_input, pos, ctx) {
6623
+ if (predicate(ctx.state))
6624
+ return { ok: true, value: null, span: { start: pos, end: pos } };
6625
+ return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
6626
+ }
6627
+ };
6628
+ }
6629
+
6630
+ // src/combinators/withCtx.ts
6631
+ function withCtx(extra, combinator) {
6632
+ const meta = {
6633
+ firstSet: combinator._meta.firstSet,
6634
+ canMatchNewline: combinator._meta.canMatchNewline,
6635
+ isTrivia: false
6636
+ };
6637
+ return {
6638
+ _tag: "withCtx",
6639
+ _meta: meta,
6640
+ _def: { tag: "withCtx", extra, parser: combinator },
6641
+ parse(input, pos, ctx) {
6642
+ return combinator.parse(input, pos, { ...ctx, state: extra });
6643
+ }
6644
+ };
6645
+ }
6646
+
6303
6647
  // src/cst/offset-model.ts
6304
6648
  var { isFinite } = Number;
6305
6649
 
@@ -6331,6 +6675,35 @@ var SUPPORTED = {
6331
6675
  function isScopeEntry(v2) {
6332
6676
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
6333
6677
  }
6678
+ function propName(p) {
6679
+ if (p.computed) return null;
6680
+ const key = p.key;
6681
+ return key.type === "Identifier" ? key.name ?? null : key.type === "Literal" ? String(key.value) : null;
6682
+ }
6683
+ function isGatedArmExpr(e) {
6684
+ if (e.type !== "ObjectExpression") return false;
6685
+ let hasGate = false, hasCombinator = false;
6686
+ for (const prop of e.properties) {
6687
+ if (prop.type !== "Property") continue;
6688
+ const name = propName(prop);
6689
+ if (name === "gate") hasGate = true;
6690
+ else if (name === "combinator") hasCombinator = true;
6691
+ }
6692
+ return hasGate && hasCombinator;
6693
+ }
6694
+ function gatedArmParts(e) {
6695
+ let gate;
6696
+ let combinator;
6697
+ for (const prop of e.properties) {
6698
+ if (prop.type !== "Property") return null;
6699
+ const op = prop;
6700
+ const name = propName(op);
6701
+ if (name === "gate") gate = op.value;
6702
+ else if (name === "combinator") combinator = op.value;
6703
+ else return null;
6704
+ }
6705
+ return gate && combinator ? { gate, combinator } : null;
6706
+ }
6334
6707
  function isCombinator(v2) {
6335
6708
  return !!v2 && typeof v2 === "object" && "_def" in v2;
6336
6709
  }
@@ -6512,6 +6885,72 @@ function exprToCombi(node2, scope, code, mfs) {
6512
6885
  return null;
6513
6886
  }
6514
6887
  }
6888
+ if (callee.name === "guard") {
6889
+ if (code === void 0 || mfs === void 0) return null;
6890
+ const [predArg] = node2.arguments;
6891
+ if (!predArg || predArg.type === "SpreadElement") return null;
6892
+ const predSrc = code.slice(predArg.start, predArg.end);
6893
+ mfs.push(predSrc);
6894
+ try {
6895
+ const combi = guard(() => true);
6896
+ if (combi._def.tag !== "guard") return null;
6897
+ combi._def.predSrc = predSrc;
6898
+ return combi;
6899
+ } catch {
6900
+ return null;
6901
+ }
6902
+ }
6903
+ if (callee.name === "withCtx") {
6904
+ if (code === void 0 || mfs === void 0) return null;
6905
+ const [extraArg, innerArg] = node2.arguments;
6906
+ if (!extraArg || !innerArg || extraArg.type === "SpreadElement" || innerArg.type === "SpreadElement") return null;
6907
+ const extraSrc = code.slice(extraArg.start, extraArg.end);
6908
+ mfs.push(extraSrc);
6909
+ const inner = anyValue(innerArg, scope, code, mfs);
6910
+ if (!isCombinator(inner)) return null;
6911
+ try {
6912
+ const combi = withCtx({}, inner);
6913
+ if (combi._def.tag !== "withCtx") return null;
6914
+ combi._def.extraSrc = extraSrc;
6915
+ return combi;
6916
+ } catch {
6917
+ return null;
6918
+ }
6919
+ }
6920
+ if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
6921
+ if (code === void 0 || mfs === void 0) return null;
6922
+ const arms = [];
6923
+ const gateSrcs = [];
6924
+ for (const argNode of node2.arguments) {
6925
+ if (argNode.type === "SpreadElement") return null;
6926
+ if (isGatedArmExpr(argNode)) {
6927
+ const parts = gatedArmParts(argNode);
6928
+ if (!parts) return null;
6929
+ const gateSrc = code.slice(parts.gate.start, parts.gate.end);
6930
+ mfs.push(gateSrc);
6931
+ const combi = anyValue(parts.combinator, scope, code, mfs);
6932
+ if (!isCombinator(combi)) return null;
6933
+ arms.push({ gate: () => true, combinator: combi });
6934
+ gateSrcs.push(gateSrc);
6935
+ } else {
6936
+ const combi = anyValue(argNode, scope, code, mfs);
6937
+ if (!isCombinator(combi)) return null;
6938
+ arms.push(combi);
6939
+ gateSrcs.push(null);
6940
+ }
6941
+ }
6942
+ try {
6943
+ const combi = choice(...arms);
6944
+ if (combi._def.tag !== "choice") return null;
6945
+ combi._def.gateSrcs = gateSrcs;
6946
+ for (let i = 0; i < combi._def.gates.length; i++) {
6947
+ if (combi._def.gates[i] !== null !== (gateSrcs[i] !== null)) return null;
6948
+ }
6949
+ return combi;
6950
+ } catch {
6951
+ return null;
6952
+ }
6953
+ }
6515
6954
  const factory = SUPPORTED[callee.name];
6516
6955
  if (!factory) return null;
6517
6956
  const args = node2.arguments.map((arg) => {
@@ -6574,9 +7013,9 @@ function anyValue(node2, scope, code, mfs) {
6574
7013
  if (typeof key !== "string" && typeof key !== "number") return null;
6575
7014
  return obj[key] ?? null;
6576
7015
  }
6577
- const propName = mem.property.name;
6578
- if (!propName) return null;
6579
- return obj[propName] ?? null;
7016
+ const propName2 = mem.property.name;
7017
+ if (!propName2) return null;
7018
+ return obj[propName2] ?? null;
6580
7019
  }
6581
7020
  if (node2.type === "CallExpression") {
6582
7021
  const callee = node2.callee;
@@ -6821,7 +7260,7 @@ var plugin_default = createUnplugin((opts = {}) => ({
6821
7260
  if (!code.includes("parseman")) return null;
6822
7261
  if (!code.includes("macro")) return null;
6823
7262
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
6824
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
7263
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
6825
7264
  if (result?.warnings.length) {
6826
7265
  for (const w of result.warnings) {
6827
7266
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -6925,7 +7364,7 @@ function extractImportBindings(body) {
6925
7364
  }
6926
7365
  return out;
6927
7366
  }
6928
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
7367
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
6929
7368
  let result;
6930
7369
  try {
6931
7370
  result = parseSync(id, code);
@@ -6996,7 +7435,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
6996
7435
  (p) => p.key?.name === "trivia"
6997
7436
  )?.value : void 0;
6998
7437
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
6999
- const compiled = compileRuleMap([...ruleMap], gTrivia ? { trivia: gTrivia } : void 0);
7438
+ const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
7000
7439
  if (!compiled) {
7001
7440
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7002
7441
  return null;
@@ -7094,7 +7533,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7094
7533
  const ns = nsFor(label2);
7095
7534
  const ir = serializeRuleMap(entries);
7096
7535
  if (ir) return { carried: [{ ns, ir }] };
7097
- const p = compileLinkable(entries, ns, composing ? { trivia: composing } : void 0);
7536
+ const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
7098
7537
  return p ? { carried: [p] } : null;
7099
7538
  };
7100
7539
  const argPieces = (arg, label2, composing) => {
@@ -7270,7 +7709,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7270
7709
  const refEntry = scope.get(varName);
7271
7710
  const refCombi = refEntry?.combi ?? null;
7272
7711
  if (refCombi) {
7273
- const compiled2 = compile(refCombi);
7712
+ const compiled2 = compile(refCombi, void 0, { recovery });
7274
7713
  if (compiled2.inlineExpression === null) {
7275
7714
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
7276
7715
  continue;
@@ -7287,7 +7726,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7287
7726
  let replacement = compiledRules.replacement;
7288
7727
  if (exportPrefix) {
7289
7728
  const ns = nsFor(varName);
7290
- const pieces = compileLinkable([...compiledRules.ruleMap], ns);
7729
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
7291
7730
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
7292
7731
  const ir = serializeRuleMap([...compiledRules.ruleMap]);
7293
7732
  replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
@@ -7323,7 +7762,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7323
7762
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
7324
7763
  continue;
7325
7764
  }
7326
- const compiled = compile(parser2);
7765
+ const compiled = compile(parser2, void 0, { recovery });
7327
7766
  if (compiled.inlineExpression === null) {
7328
7767
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
7329
7768
  continue;