parseman 0.25.0 → 0.26.1

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 (55) hide show
  1. package/dist/combinators/choice.d.ts.map +1 -1
  2. package/dist/combinators/completions.d.ts +14 -4
  3. package/dist/combinators/completions.d.ts.map +1 -1
  4. package/dist/combinators/expect.d.ts +2 -0
  5. package/dist/combinators/expect.d.ts.map +1 -1
  6. package/dist/combinators/grammar.d.ts +5 -5
  7. package/dist/combinators/not.d.ts.map +1 -1
  8. package/dist/combinators/parser.d.ts +3 -2
  9. package/dist/combinators/parser.d.ts.map +1 -1
  10. package/dist/combinators/repeat.d.ts.map +1 -1
  11. package/dist/combinators/sequence.d.ts.map +1 -1
  12. package/dist/combinators/trivia-skip.d.ts +1 -0
  13. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  14. package/dist/compiler/codegen.d.ts +9 -4
  15. package/dist/compiler/codegen.d.ts.map +1 -1
  16. package/dist/compiler/scannable-run.d.ts +2 -2
  17. package/dist/compiler/scannable-run.d.ts.map +1 -1
  18. package/dist/cst/capture-buffer.d.ts +1 -0
  19. package/dist/cst/capture-buffer.d.ts.map +1 -1
  20. package/dist/cst/types.d.ts +7 -7
  21. package/dist/cst/types.d.ts.map +1 -1
  22. package/dist/functional/doc.d.ts +37 -2
  23. package/dist/functional/doc.d.ts.map +1 -1
  24. package/dist/functional/run.d.ts +12 -1
  25. package/dist/functional/run.d.ts.map +1 -1
  26. package/dist/index.cjs +540 -211
  27. package/dist/index.cjs.map +4 -4
  28. package/dist/index.d.ts +1 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +540 -211
  31. package/dist/index.js.map +4 -4
  32. package/dist/language-service/index.cjs +776 -0
  33. package/dist/language-service/index.cjs.map +7 -0
  34. package/dist/language-service/index.d.ts +94 -0
  35. package/dist/language-service/index.d.ts.map +1 -0
  36. package/dist/language-service/index.js +753 -0
  37. package/dist/language-service/index.js.map +7 -0
  38. package/dist/plugin/evaluator.d.ts.map +1 -1
  39. package/dist/plugin/index.cjs +603 -145
  40. package/dist/plugin/index.cjs.map +4 -4
  41. package/dist/plugin/index.d.ts +11 -1
  42. package/dist/plugin/index.d.ts.map +1 -1
  43. package/dist/plugin/index.js +603 -145
  44. package/dist/plugin/index.js.map +4 -4
  45. package/dist/recovery/scan.d.ts +54 -0
  46. package/dist/recovery/scan.d.ts.map +1 -0
  47. package/dist/spec/index.cjs.map +1 -1
  48. package/dist/spec/index.js.map +1 -1
  49. package/dist/types.d.ts +60 -6
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +6 -1
  52. package/dist/combinators/recover-list.d.ts +0 -41
  53. package/dist/combinators/recover-list.d.ts.map +0 -1
  54. package/dist/combinators/recover.d.ts +0 -20
  55. 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);
@@ -870,12 +877,159 @@ function keywords(words, opts = {}) {
870
877
  };
871
878
  }
872
879
 
880
+ // src/recovery/scan.ts
881
+ function probeAt(sentinel, input, pos, ctx) {
882
+ const s = {
883
+ _probe: ctx._probe,
884
+ _errors: ctx._errors,
885
+ _cstLeaves: ctx._cstLeaves,
886
+ _cstChildren: ctx._cstChildren,
887
+ _cstRawChildren: ctx._cstRawChildren,
888
+ _cstTriviaLog: ctx._cstTriviaLog,
889
+ _triviaLog: ctx._triviaLog,
890
+ _fields: ctx._fields,
891
+ _cstBuf: ctx._cstBuf,
892
+ _tolerant: ctx._tolerant,
893
+ _sync: ctx._sync
894
+ };
895
+ ctx._probe = void 0;
896
+ ctx._errors = void 0;
897
+ ctx._cstLeaves = void 0;
898
+ ctx._cstChildren = void 0;
899
+ ctx._cstRawChildren = void 0;
900
+ ctx._cstTriviaLog = void 0;
901
+ ctx._triviaLog = void 0;
902
+ ctx._fields = void 0;
903
+ ctx._cstBuf = void 0;
904
+ ctx._tolerant = void 0;
905
+ ctx._sync = void 0;
906
+ try {
907
+ return sentinel.parse(input, pos, ctx).ok;
908
+ } finally {
909
+ ctx._probe = s._probe;
910
+ ctx._errors = s._errors;
911
+ ctx._cstLeaves = s._cstLeaves;
912
+ ctx._cstChildren = s._cstChildren;
913
+ ctx._cstRawChildren = s._cstRawChildren;
914
+ ctx._cstTriviaLog = s._cstTriviaLog;
915
+ ctx._triviaLog = s._triviaLog;
916
+ ctx._fields = s._fields;
917
+ ctx._cstBuf = s._cstBuf;
918
+ ctx._tolerant = s._tolerant;
919
+ ctx._sync = s._sync;
920
+ }
921
+ }
922
+ function orSentinel(a, b) {
923
+ if (b === void 0) return a;
924
+ return {
925
+ _tag: "orSentinel",
926
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
927
+ _def: { tag: "unknown" },
928
+ parse(input, pos, ctx) {
929
+ const ra = a.parse(input, pos, ctx);
930
+ if (ra.ok) return ra;
931
+ return b.parse(input, pos, ctx);
932
+ }
933
+ };
934
+ }
935
+ function firstSetSentinel(fs2) {
936
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
937
+ const ranges = fs2.ranges;
938
+ return {
939
+ _tag: "firstSetSentinel",
940
+ _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
941
+ _def: { tag: "unknown" },
942
+ parse(input, pos) {
943
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
944
+ if (code !== void 0) {
945
+ for (const r of ranges) {
946
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
947
+ }
948
+ }
949
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
950
+ }
951
+ };
952
+ }
953
+ function recoverScan(input, from, ctx, sync, expected) {
954
+ let scanPos = from;
955
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
956
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
957
+ ctx._errors?.push(error);
958
+ return { error, end: scanPos };
959
+ }
960
+ function matchesAt(sentinel, input, pos, ctx) {
961
+ return probeAt(sentinel, input, pos, ctx);
962
+ }
963
+ function captureError(ctx, error) {
964
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
965
+ }
966
+
967
+ // src/combinators/expect.ts
968
+ function deriveExpected(c) {
969
+ const def = c._def;
970
+ switch (def.tag) {
971
+ case "literal":
972
+ return [JSON.stringify(def.value)];
973
+ case "regex":
974
+ return [`/${def.source}/`];
975
+ case "keywords":
976
+ return def.words.map((w) => JSON.stringify(w));
977
+ case "label":
978
+ return [def.label];
979
+ case "choice":
980
+ return def.parsers.flatMap(deriveExpected);
981
+ case "sequence":
982
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
983
+ case "node":
984
+ case "grammar":
985
+ case "trivia":
986
+ case "token":
987
+ case "optional":
988
+ case "many":
989
+ case "oneOrMore":
990
+ case "transform":
991
+ case "not":
992
+ return deriveExpected(def.parser);
993
+ case "lazy": {
994
+ const name = c._ruleName;
995
+ try {
996
+ return deriveExpected(def.thunk());
997
+ } catch {
998
+ return name ? [name] : [];
999
+ }
1000
+ }
1001
+ default:
1002
+ return [];
1003
+ }
1004
+ }
1005
+ function expect(combinator, label2) {
1006
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1007
+ const meta = {
1008
+ firstSet: combinator._meta.firstSet,
1009
+ canMatchNewline: combinator._meta.canMatchNewline,
1010
+ isTrivia: false
1011
+ };
1012
+ return {
1013
+ _tag: "expect",
1014
+ _meta: meta,
1015
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1016
+ parse(input, pos, ctx) {
1017
+ const result = combinator.parse(input, pos, ctx);
1018
+ if (result.ok) return result;
1019
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1020
+ ctx._errors?.push(error);
1021
+ if (ctx._tolerant) captureError(ctx, error);
1022
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1023
+ }
1024
+ };
1025
+ }
1026
+
873
1027
  // src/combinators/choice.ts
874
1028
  function choice(...args) {
875
1029
  const parsers = args.map((a) => "gate" in a ? a.combinator : a);
876
1030
  const gates = args.map((a) => "gate" in a ? a.gate : null);
877
1031
  const hasGates = gates.some((g) => g !== null);
878
- const disjoint = !hasGates && areDisjoint(parsers.map((p) => p._meta.firstSet));
1032
+ const disjoint = areDisjoint(parsers.map((p) => p._meta.firstSet)) && parsers.every((p) => !matchesEmpty(p));
879
1033
  let combined = { kind: "empty" };
880
1034
  for (const p of parsers) combined = union(combined, p._meta.firstSet);
881
1035
  const meta = {
@@ -914,17 +1068,21 @@ function choice(...args) {
914
1068
  const expected = [];
915
1069
  if (disjoint && pos < input.length) {
916
1070
  const code = input.codePointAt(pos);
917
- let parser2 = code < 128 ? asciiDispatch?.[code] ?? null : null;
918
- if (!parser2) {
919
- for (const p of parsers) {
920
- if (inFirstSet(code, p._meta.firstSet)) {
921
- parser2 = p;
1071
+ let idx = code < 128 ? asciiDispatch[code] : -1;
1072
+ if (idx < 0) {
1073
+ for (let i = 0; i < parsers.length; i++) {
1074
+ if (inFirstSet(code, parsers[i]._meta.firstSet)) {
1075
+ idx = i;
922
1076
  break;
923
1077
  }
924
1078
  }
925
1079
  }
926
- if (parser2) {
927
- const result = parser2.parse(input, pos, ctx);
1080
+ if (idx >= 0) {
1081
+ const gate = gates[idx];
1082
+ if (gate && !gate(ctx.state)) {
1083
+ return { ok: false, expected: deriveExpected(parsers[idx]), span: { start: pos, end: pos } };
1084
+ }
1085
+ const result = parsers[idx].parse(input, pos, ctx);
928
1086
  if (result.ok) return result;
929
1087
  expected.push(...result.expected);
930
1088
  return { ok: false, expected, span: { start: pos, end: pos } };
@@ -1105,13 +1263,13 @@ function areDisjoint(sets) {
1105
1263
  return true;
1106
1264
  }
1107
1265
  function buildAsciiDispatch(parsers) {
1108
- const table = Array(128).fill(null);
1109
- for (const parser2 of parsers) {
1110
- const fs2 = parser2._meta.firstSet;
1266
+ const table = Array(128).fill(-1);
1267
+ for (let i = 0; i < parsers.length; i++) {
1268
+ const fs2 = parsers[i]._meta.firstSet;
1111
1269
  if (fs2.kind !== "ranges") continue;
1112
1270
  for (const { lo, hi } of fs2.ranges) {
1113
1271
  for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
1114
- table[code] = parser2;
1272
+ table[code] = i;
1115
1273
  }
1116
1274
  }
1117
1275
  }
@@ -1256,10 +1414,10 @@ function needsDeferredTriviaCommit(ctx) {
1256
1414
  }
1257
1415
  function saveTriviaMark(ctx) {
1258
1416
  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 };
1417
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1260
1418
  }
1261
1419
  function rollbackTrivia(ctx, mark) {
1262
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1420
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1263
1421
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1264
1422
  }
1265
1423
  function scanWithLabels(input, cur, ctx) {
@@ -1503,11 +1661,59 @@ function sequence(...parsers) {
1503
1661
  isTrivia: false
1504
1662
  };
1505
1663
  const def = { tag: "sequence", parsers };
1664
+ let followSentinels;
1665
+ function parseTolerant(input, pos, ctx) {
1666
+ followSentinels ??= parsers.map((_, i) => {
1667
+ const fs2 = parsers.slice(i + 1).reduce(
1668
+ (acc, p) => union(acc, firstSetOf(p)),
1669
+ { kind: "empty" }
1670
+ );
1671
+ return firstSetSentinel(fs2);
1672
+ });
1673
+ const values = def.valueUnused ? void 0 : [];
1674
+ let cur = pos;
1675
+ const inheritedSync = ctx._sync;
1676
+ try {
1677
+ for (let i = 0; i < parsers.length; i++) {
1678
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1679
+ if (ctx.trivia && i > 0) {
1680
+ const mark = saveTriviaMark(ctx);
1681
+ let scanEnd;
1682
+ if (needsDeferredTriviaCommit(ctx)) {
1683
+ const scan = scanTrivia(input, cur, ctx);
1684
+ scan.commit();
1685
+ scanEnd = scan.end;
1686
+ } else {
1687
+ scanEnd = advanceTrivia(input, cur, ctx);
1688
+ }
1689
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1690
+ if (!result2.ok) return result2;
1691
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1692
+ else rollbackTrivia(ctx, mark);
1693
+ if (values !== void 0) values.push(result2.value);
1694
+ continue;
1695
+ }
1696
+ const result = parsers[i].parse(input, cur, ctx);
1697
+ if (!result.ok) return result;
1698
+ if (values !== void 0) values.push(result.value);
1699
+ cur = result.span.end;
1700
+ }
1701
+ } finally {
1702
+ ctx._sync = inheritedSync;
1703
+ }
1704
+ return {
1705
+ ok: true,
1706
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1707
+ value: values ?? void 0,
1708
+ span: { start: pos, end: cur }
1709
+ };
1710
+ }
1506
1711
  return {
1507
1712
  _tag: "sequence",
1508
1713
  _meta: meta,
1509
1714
  _def: def,
1510
1715
  parse(input, pos, ctx) {
1716
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1511
1717
  const values = def.valueUnused ? void 0 : [];
1512
1718
  let cur = pos;
1513
1719
  for (let i = 0; i < parsers.length; i++) {
@@ -1566,7 +1772,7 @@ function repItem(combinator, input, cur, ctx) {
1566
1772
  const result = combinator.parse(input, pos, ctx);
1567
1773
  if (!result.ok) {
1568
1774
  rollbackTrivia(ctx, mark);
1569
- return { fail: result };
1775
+ return { fail: result, failPos: pos };
1570
1776
  }
1571
1777
  if (result.span.end === pos) {
1572
1778
  rollbackTrivia(ctx, mark);
@@ -1581,6 +1787,7 @@ function many(combinator) {
1581
1787
  isTrivia: false
1582
1788
  };
1583
1789
  const def = { tag: "many", parser: combinator, min: 0 };
1790
+ let expected;
1584
1791
  return {
1585
1792
  _tag: "many",
1586
1793
  _meta: meta,
@@ -1590,7 +1797,18 @@ function many(combinator) {
1590
1797
  let cur = pos;
1591
1798
  while (cur < input.length) {
1592
1799
  const item = repItem(combinator, input, cur, ctx);
1593
- if (item === "stop" || "fail" in item) break;
1800
+ if (item === "stop") break;
1801
+ if ("fail" in item) {
1802
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1803
+ if (sync === void 0) break;
1804
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1805
+ expected ??= deriveExpected(combinator);
1806
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1807
+ if (values !== void 0) values.push(error);
1808
+ captureError(ctx, error);
1809
+ cur = end;
1810
+ continue;
1811
+ }
1594
1812
  if (values !== void 0) values.push(item.value);
1595
1813
  cur = item.end;
1596
1814
  }
@@ -1605,6 +1823,7 @@ function oneOrMore(combinator) {
1605
1823
  isTrivia: false
1606
1824
  };
1607
1825
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1826
+ let expected;
1608
1827
  return {
1609
1828
  _tag: "oneOrMore",
1610
1829
  _meta: meta,
@@ -1616,7 +1835,18 @@ function oneOrMore(combinator) {
1616
1835
  let cur = first.span.end;
1617
1836
  while (cur < input.length) {
1618
1837
  const item = repItem(combinator, input, cur, ctx);
1619
- if (item === "stop" || "fail" in item) break;
1838
+ if (item === "stop") break;
1839
+ if ("fail" in item) {
1840
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1841
+ if (sync === void 0) break;
1842
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1843
+ expected ??= deriveExpected(combinator);
1844
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1845
+ if (values !== void 0) values.push(error);
1846
+ captureError(ctx, error);
1847
+ cur = end;
1848
+ continue;
1849
+ }
1620
1850
  if (values !== void 0) values.push(item.value);
1621
1851
  cur = item.end;
1622
1852
  }
@@ -1662,15 +1892,29 @@ function sepBy(combinator, separator) {
1662
1892
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1663
1893
  isTrivia: false
1664
1894
  };
1895
+ let expected;
1665
1896
  return {
1666
1897
  _tag: "sepBy",
1667
1898
  _meta: meta,
1668
1899
  _def: { tag: "sepBy", parser: combinator, separator },
1669
1900
  parse(input, pos, ctx) {
1670
1901
  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;
1902
+ const values = [];
1903
+ let cur;
1904
+ if (first.ok) {
1905
+ values.push(first.value);
1906
+ cur = first.span.end;
1907
+ } else {
1908
+ const term = ctx._tolerant ? ctx._sync : void 0;
1909
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1910
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1911
+ }
1912
+ expected ??= deriveExpected(combinator);
1913
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1914
+ values.push(rec.error);
1915
+ captureError(ctx, rec.error);
1916
+ cur = rec.end;
1917
+ }
1674
1918
  while (cur < input.length) {
1675
1919
  const loopMark = saveTriviaMark(ctx);
1676
1920
  let sepPos = cur;
@@ -1700,6 +1944,15 @@ function sepBy(combinator, separator) {
1700
1944
  }
1701
1945
  const next = combinator.parse(input, nextPos, ctx);
1702
1946
  if (!next.ok) {
1947
+ const term = ctx._tolerant ? ctx._sync : void 0;
1948
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1949
+ expected ??= deriveExpected(combinator);
1950
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1951
+ values.push(rec.error);
1952
+ captureError(ctx, rec.error);
1953
+ cur = rec.end;
1954
+ continue;
1955
+ }
1703
1956
  rollbackTrivia(ctx, loopMark);
1704
1957
  break;
1705
1958
  }
@@ -1836,7 +2089,7 @@ function rules(a, b) {
1836
2089
  cache[key] = parser2;
1837
2090
  }
1838
2091
  }
1839
- if (options?.trivia !== void 0) {
2092
+ if (options?.trivia != null) {
1840
2093
  for (const key of Object.keys(definitions)) {
1841
2094
  const rule = cache[key];
1842
2095
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1866,7 +2119,9 @@ function not(combinator) {
1866
2119
  _meta: meta,
1867
2120
  _def: { tag: "not", parser: combinator },
1868
2121
  parse(input, pos, ctx) {
2122
+ const mark = saveCstMark(ctx);
1869
2123
  const result = combinator.parse(input, pos, ctx);
2124
+ rollbackCstCapture(ctx, mark);
1870
2125
  if (result.ok) {
1871
2126
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1872
2127
  }
@@ -1980,7 +2235,7 @@ function unwrapChild(child) {
1980
2235
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1981
2236
  }
1982
2237
  function isCstChild(value) {
1983
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2238
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
1984
2239
  }
1985
2240
  function missingInferredType() {
1986
2241
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2233,65 +2488,6 @@ function token(root) {
2233
2488
  };
2234
2489
  }
2235
2490
 
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
2491
  // src/compiler/scannable-run.ts
2296
2492
  var BACKSLASH = 92;
2297
2493
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2363,20 +2559,39 @@ function parseSeqParts(source) {
2363
2559
  lit = [];
2364
2560
  }
2365
2561
  };
2366
- const runFrom = (ranges, negated, q) => {
2562
+ const braceQuant = (at) => {
2563
+ const close = source.indexOf("}", at);
2564
+ if (close === -1) return null;
2565
+ const body = source.slice(at + 1, close);
2566
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2567
+ if (!m) return null;
2568
+ const min = Number(m[1]);
2569
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2570
+ if (max < 1 || min > max) return null;
2571
+ return { min, max, len: close - at + 1 };
2572
+ };
2573
+ const runFrom = (ranges, negated, at) => {
2574
+ const q = source[at];
2367
2575
  if (q === "+") {
2368
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2576
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2369
2577
  return 1;
2370
2578
  }
2371
2579
  if (q === "*") {
2372
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2580
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2373
2581
  return 1;
2374
2582
  }
2375
2583
  if (q === "?") {
2376
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2584
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2377
2585
  return 1;
2378
2586
  }
2379
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2587
+ if (q === "{") {
2588
+ const b = braceQuant(at);
2589
+ if (b) {
2590
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2591
+ return b.len;
2592
+ }
2593
+ }
2594
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2380
2595
  return 0;
2381
2596
  };
2382
2597
  let i = 0;
@@ -2401,7 +2616,7 @@ function parseSeqParts(source) {
2401
2616
  if (!ranges) return null;
2402
2617
  flush();
2403
2618
  i = k + 1;
2404
- i += runFrom(ranges, neg, source[i]);
2619
+ i += runFrom(ranges, neg, i);
2405
2620
  continue;
2406
2621
  }
2407
2622
  if (ch === "\\") {
@@ -2409,7 +2624,7 @@ function parseSeqParts(source) {
2409
2624
  if (e === "d" || e === "w" || e === "s") {
2410
2625
  flush();
2411
2626
  i += 2;
2412
- i += runFrom(shorthandRanges(e), false, source[i]);
2627
+ i += runFrom(shorthandRanges(e), false, i);
2413
2628
  continue;
2414
2629
  }
2415
2630
  const uni = readUnicodeEscape(source, i);
@@ -2498,7 +2713,7 @@ function seqIsUnambiguous(parts) {
2498
2713
  let hasMandatory = false;
2499
2714
  for (let k = 0; k < parts.length; k++) {
2500
2715
  const p = parts[k];
2501
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2716
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2502
2717
  if (mandatory) hasMandatory = true;
2503
2718
  if (p.part === "group") {
2504
2719
  const exposure = groupPartExposure(p);
@@ -2509,9 +2724,8 @@ function seqIsUnambiguous(parts) {
2509
2724
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2510
2725
  }
2511
2726
  }
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) {
2727
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2728
+ if (needsDisjoint) {
2515
2729
  const rest = seqFirstAccept(parts.slice(k + 1));
2516
2730
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2517
2731
  }
@@ -2568,8 +2782,8 @@ function fixedClassSeq(shape) {
2568
2782
  if (p.part === "lit") {
2569
2783
  if (p.optional) return null;
2570
2784
  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 });
2785
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2786
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2573
2787
  } else {
2574
2788
  return null;
2575
2789
  }
@@ -2621,7 +2835,7 @@ function trailingBacktrackClass(shape) {
2621
2835
  const last = shape.parts[shape.parts.length - 1];
2622
2836
  if (last.part === "lit") return null;
2623
2837
  if (last.part === "run") {
2624
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2838
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2625
2839
  return null;
2626
2840
  }
2627
2841
  return groupPartExposure(last);
@@ -2758,7 +2972,7 @@ function partFirstAccept(part) {
2758
2972
  function seqFirstAccept(parts) {
2759
2973
  const sets = [];
2760
2974
  for (const p of parts) {
2761
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2975
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2762
2976
  sets.push(partFirstAccept(p));
2763
2977
  if (mandatory) break;
2764
2978
  }
@@ -3173,7 +3387,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3173
3387
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3174
3388
  lines.push(`${ind2} ${endV2} += ${L}`);
3175
3389
  }
3176
- } else if (p.part === "run" && !p.unbounded) {
3390
+ } else if (p.part === "run" && p.max === Infinity) {
3391
+ const s = mint("_s");
3392
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3393
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3394
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3395
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3396
+ } else if (p.part === "run" && p.max === 1) {
3177
3397
  if (p.min === 1) {
3178
3398
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3179
3399
  lines.push(`${ind2} ${endV2}++`);
@@ -3183,8 +3403,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3183
3403
  } else if (p.part === "run") {
3184
3404
  const s = mint("_s");
3185
3405
  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`);
3406
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3407
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3188
3408
  } else {
3189
3409
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3190
3410
  if (!p.unbounded) {
@@ -3651,21 +3871,29 @@ function hoistExpected(ctx, constArrSource) {
3651
3871
  }
3652
3872
  return name;
3653
3873
  }
3874
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3875
+ if (!ctx.recovery) return "";
3876
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3877
+ const pb = v(ctx, "_pb");
3878
+ 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} } } } `;
3879
+ }
3654
3880
  function failBody(ctx, expected, posExpr) {
3881
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3655
3882
  if (ctx.failLabel) {
3656
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3657
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3883
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3884
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3658
3885
  }
3659
- return failReturn(expected, posExpr);
3886
+ return probe + failReturn(expected, posExpr);
3660
3887
  }
3661
3888
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3889
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3662
3890
  if (ctx.failLabel) {
3663
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3891
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3664
3892
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3665
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3893
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3666
3894
  }
3667
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3668
- return failReturnArr(expectedArr, posExpr);
3895
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3896
+ return probe + failReturnArr(expectedArr, posExpr);
3669
3897
  }
3670
3898
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3671
3899
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4165,6 +4393,10 @@ function emitRegex(def, ctx, pos) {
4165
4393
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4166
4394
  return { stmts, valueVar: vv, endVar };
4167
4395
  }
4396
+ function syncSentinelExpr(fs2) {
4397
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
4398
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
4399
+ }
4168
4400
  function emitSeqValues(def, ctx, pos) {
4169
4401
  const startV = v(ctx, "_start");
4170
4402
  const curV = v(ctx, "_cur");
@@ -4173,7 +4405,17 @@ function emitSeqValues(def, ctx, pos) {
4173
4405
  `${ind(ctx)}let ${curV} = ${pos}`
4174
4406
  ];
4175
4407
  const valueVars = [];
4408
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4409
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4410
+ const publishSync = (i) => {
4411
+ if (!ctx.recovery) return "";
4412
+ const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4413
+ const ref2 = syncSentinelExpr(fs2);
4414
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4415
+ };
4176
4416
  for (let i = 0; i < def.parsers.length; i++) {
4417
+ const syncPub = publishSync(i);
4418
+ if (syncPub) stmts.push(syncPub);
4177
4419
  if (i > 0 && ctx.activeTrivia) {
4178
4420
  if (ctx.capturing) {
4179
4421
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4198,7 +4440,17 @@ function emitSeqValues(def, ctx, pos) {
4198
4440
  continue;
4199
4441
  } else {
4200
4442
  const trivFn = ensureTriviaFn(ctx);
4201
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4443
+ const scanEndV = v(ctx, "_sne");
4444
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4445
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4446
+ stmts.push(...r2.stmts);
4447
+ const endAfterV = v(ctx, "_sea");
4448
+ stmts.push(
4449
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4450
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4451
+ );
4452
+ valueVars.push(r2.valueVar);
4453
+ continue;
4202
4454
  }
4203
4455
  }
4204
4456
  const r = emit(def.parsers[i], ctx, curV);
@@ -4276,6 +4528,21 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
4276
4528
  `${ind(ctx)}${endV} = ${endVar}`
4277
4529
  ];
4278
4530
  }
4531
+ function emitDisjointArmGated(def, i, ctx, pos, valV, endV) {
4532
+ const p = def.parsers[i];
4533
+ const gate = def.gates[i];
4534
+ if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV);
4535
+ const gateIdx = ctx.mapFns.length;
4536
+ ctx.mapFns.push(gate);
4537
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4538
+ const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4539
+ const stmts = [`${ind(ctx)}if (${gateCond}) {`];
4540
+ ctx.indent++;
4541
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4542
+ ctx.indent--;
4543
+ stmts.push(`${ind(ctx)}} else { ${failArrBody(ctx, deriveExpectedArr([p]), pos)} }`);
4544
+ return stmts;
4545
+ }
4279
4546
  function emitChoice(def, ctx, pos) {
4280
4547
  const allExpected = deriveExpectedArr(def.parsers);
4281
4548
  if (def.disjoint) {
@@ -4291,12 +4558,11 @@ function emitChoice(def, ctx, pos) {
4291
4558
  stmts.push(`${ind(ctx)}switch (${codeV}) {`);
4292
4559
  ctx.indent++;
4293
4560
  for (let i = 0; i < def.parsers.length; i++) {
4294
- const p = def.parsers[i];
4295
4561
  for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
4296
4562
  stmts.push(`${ind(ctx)}{`);
4297
4563
  ctx.indent++;
4298
4564
  stmts.push(
4299
- ...emitDisjointArm(p, ctx, pos, valV, endV),
4565
+ ...emitDisjointArmGated(def, i, ctx, pos, valV, endV),
4300
4566
  `${ind(ctx)}break`
4301
4567
  );
4302
4568
  ctx.indent--;
@@ -4308,13 +4574,14 @@ function emitChoice(def, ctx, pos) {
4308
4574
  return { stmts, valueVar: valV, endVar: endV };
4309
4575
  }
4310
4576
  let first = true;
4311
- for (const p of def.parsers) {
4577
+ for (let i = 0; i < def.parsers.length; i++) {
4578
+ const p = def.parsers[i];
4312
4579
  const cond = firstSetCond(codeV, p._meta.firstSet);
4313
4580
  const kw = first ? "if" : "else if";
4314
4581
  first = false;
4315
4582
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
4316
4583
  ctx.indent++;
4317
- stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4584
+ stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV));
4318
4585
  ctx.indent--;
4319
4586
  stmts.push(`${ind(ctx)}}`);
4320
4587
  }
@@ -4428,7 +4695,7 @@ function emitFirstMatch(def, ctx, pos) {
4428
4695
  if (gate) {
4429
4696
  const gateIdx = ctx.mapFns.length;
4430
4697
  ctx.mapFns.push(gate);
4431
- ctx.mapFnSrcs.push(null);
4698
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4432
4699
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4433
4700
  }
4434
4701
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4533,6 +4800,8 @@ function emitMany(def, ctx, pos) {
4533
4800
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4534
4801
  `${ind(ctx)}let ${curV} = ${pos}`
4535
4802
  ];
4803
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4804
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4536
4805
  if (def.min === 1) {
4537
4806
  const firstR = emit(def.parser, ctx, curV);
4538
4807
  stmts.push(...firstR.stmts);
@@ -4566,10 +4835,26 @@ function emitMany(def, ctx, pos) {
4566
4835
  }
4567
4836
  }
4568
4837
  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
- );
4838
+ stmts.push(...iterStmts);
4839
+ if (ctx.recovery) {
4840
+ const rrV = v(ctx, "_rr");
4841
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4842
+ stmts.push(
4843
+ `${ind(ctx)}if (!${iterOk}) {`,
4844
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4845
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4846
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4847
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4848
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4849
+ `${ind(ctx)} continue`,
4850
+ `${ind(ctx)} }`,
4851
+ `${ind(ctx)} break`,
4852
+ `${ind(ctx)}}`,
4853
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4854
+ );
4855
+ } else {
4856
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4857
+ }
4573
4858
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4574
4859
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4575
4860
  ctx.indent--;
@@ -4591,20 +4876,56 @@ function emitOptional(def, ctx, pos) {
4591
4876
  function emitSepBy(_p, def, ctx, pos) {
4592
4877
  const arrV = v(ctx, "_arr");
4593
4878
  const curV = v(ctx, "_cur");
4879
+ const rec = !!ctx.recovery;
4880
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4881
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4882
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4883
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4594
4884
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4595
4885
  const stmts = [
4596
4886
  `${ind(ctx)}const ${arrV} = []`,
4597
4887
  `${ind(ctx)}let ${curV} = ${pos}`,
4598
- ...firstStmts,
4599
- `${ind(ctx)}if (${firstOk}) {`
4888
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4889
+ ...firstStmts
4600
4890
  ];
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++;
4891
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4892
+ if (!rec) return [originalFail];
4893
+ const rr = v(ctx, "_rr");
4894
+ return [
4895
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4896
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4897
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4898
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4899
+ `${ind(ctx)} }`,
4900
+ `${ind(ctx)} ${rb}break`,
4901
+ `${ind(ctx)}}`
4902
+ ];
4903
+ };
4904
+ if (rec) {
4905
+ const rr0 = v(ctx, "_rr0");
4906
+ const didV = v(ctx, "_did");
4907
+ stmts.push(
4908
+ `${ind(ctx)}let ${didV} = false`,
4909
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4910
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4911
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4912
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4913
+ `${ind(ctx)}}`,
4914
+ `${ind(ctx)}if (${didV}) {`
4915
+ );
4916
+ ctx.indent++;
4917
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
4918
+ ctx.indent++;
4919
+ } else {
4920
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
4921
+ ctx.indent++;
4922
+ stmts.push(
4923
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
4924
+ `${ind(ctx)}${curV} = ${firstEnd}`,
4925
+ `${ind(ctx)}while (${curV} < input.length) {`
4926
+ );
4927
+ ctx.indent++;
4928
+ }
4608
4929
  let sepAtPos = curV;
4609
4930
  if (ctx.activeTrivia) {
4610
4931
  if (ctx.capturing) {
@@ -4630,8 +4951,7 @@ function emitSepBy(_p, def, ctx, pos) {
4630
4951
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4631
4952
  stmts.push(
4632
4953
  ...nextStmts,
4633
- // item failed → unwind the separator too, back to the end of the last item
4634
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
4954
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4635
4955
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4636
4956
  `${ind(ctx)}${curV} = ${nextEnd}`
4637
4957
  );
@@ -4647,7 +4967,7 @@ function emitSepBy(_p, def, ctx, pos) {
4647
4967
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4648
4968
  stmts.push(
4649
4969
  ...nextStmts,
4650
- `${ind(ctx)}if (!${nextOk}) break`,
4970
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4651
4971
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4652
4972
  `${ind(ctx)}${curV} = ${nextEnd}`
4653
4973
  );
@@ -4667,7 +4987,7 @@ function emitSepBy(_p, def, ctx, pos) {
4667
4987
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4668
4988
  stmts.push(
4669
4989
  ...nextStmts,
4670
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
4990
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4671
4991
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4672
4992
  `${ind(ctx)}${curV} = ${nextEnd}`
4673
4993
  );
@@ -4924,7 +5244,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4924
5244
  stmts.push(
4925
5245
  `${i}const ${ndV} = ${finalExpr}`,
4926
5246
  `${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} } })`
5247
+ `${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
5248
  );
4929
5249
  return { stmts, valueVar: ndV, endVar };
4930
5250
  }
@@ -5014,6 +5334,10 @@ function emitExpect(def, ctx, pos) {
5014
5334
  `${ind0}if (!${okVar}) {`,
5015
5335
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5016
5336
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5337
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5338
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5339
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5340
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5017
5341
  `${ind0} ${valVar} = ${errV}`,
5018
5342
  `${ind0} ${endVar} = ${pos}`,
5019
5343
  `${ind0} ${okVar} = true`,
@@ -5180,7 +5504,7 @@ function emitDispatch(p, ctx, pos) {
5180
5504
  case "guard": {
5181
5505
  const fnIdx = ctx.mapFns.length;
5182
5506
  ctx.mapFns.push(def.predicate);
5183
- ctx.mapFnSrcs.push(null);
5507
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5184
5508
  const vv = v(ctx);
5185
5509
  return {
5186
5510
  stmts: [
@@ -5195,7 +5519,7 @@ function emitDispatch(p, ctx, pos) {
5195
5519
  const evIdx = ctx.mapFns.length;
5196
5520
  const extra = def.extra;
5197
5521
  ctx.mapFns.push((() => extra));
5198
- ctx.mapFnSrcs.push(null);
5522
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5199
5523
  const innerParser = def.parser;
5200
5524
  if (!ctx.namedParsers.has(innerParser)) {
5201
5525
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5439,7 +5763,7 @@ function ruleDependencies(ruleMap) {
5439
5763
  }
5440
5764
  return deps;
5441
5765
  }
5442
- function compile(combinator, mapFnSources) {
5766
+ function compile(combinator, mapFnSources, opts) {
5443
5767
  markUnusedValues(combinator);
5444
5768
  const grammarTrivia = combinator._meta.grammarTrivia;
5445
5769
  const ctx = {
@@ -5460,6 +5784,7 @@ function compile(combinator, mapFnSources) {
5460
5784
  triviaFnNames: /* @__PURE__ */ new Map(),
5461
5785
  namedFnDecls: [],
5462
5786
  capturing: hasNodeDef(combinator),
5787
+ recovery: opts?.recovery ?? false,
5463
5788
  lazyUsage: analyzeLazyUsage(combinator),
5464
5789
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5465
5790
  };
@@ -5512,7 +5837,7 @@ function compile(combinator, mapFnSources) {
5512
5837
  parseWithContext(input, parseCtx, pos = 0) {
5513
5838
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5514
5839
  },
5515
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5840
+ // Note: collects expect() errors via _errors. Unlike interpreter
5516
5841
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5517
5842
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5518
5843
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5558,6 +5883,7 @@ function compileRuleMap(ruleMap, opts) {
5558
5883
  triviaFnNames: /* @__PURE__ */ new Map(),
5559
5884
  namedFnDecls: [],
5560
5885
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5886
+ recovery: opts?.recovery ?? false,
5561
5887
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5562
5888
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5563
5889
  };
@@ -5653,6 +5979,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5653
5979
  triviaFnNames: /* @__PURE__ */ new Map(),
5654
5980
  namedFnDecls: [],
5655
5981
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5982
+ recovery: opts?.recovery ?? false,
5656
5983
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5657
5984
  ns,
5658
5985
  deferFirstSetRefs: true,
@@ -6300,6 +6627,42 @@ function materializePiece(p, trivia2) {
6300
6627
  return pieces;
6301
6628
  }
6302
6629
 
6630
+ // src/combinators/guard.ts
6631
+ function guard(predicate) {
6632
+ const meta = {
6633
+ firstSet: { kind: "any" },
6634
+ canMatchNewline: false,
6635
+ isTrivia: false
6636
+ };
6637
+ return {
6638
+ _tag: "guard",
6639
+ _meta: meta,
6640
+ _def: { tag: "guard", predicate },
6641
+ parse(_input, pos, ctx) {
6642
+ if (predicate(ctx.state))
6643
+ return { ok: true, value: null, span: { start: pos, end: pos } };
6644
+ return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
6645
+ }
6646
+ };
6647
+ }
6648
+
6649
+ // src/combinators/withCtx.ts
6650
+ function withCtx(extra, combinator) {
6651
+ const meta = {
6652
+ firstSet: combinator._meta.firstSet,
6653
+ canMatchNewline: combinator._meta.canMatchNewline,
6654
+ isTrivia: false
6655
+ };
6656
+ return {
6657
+ _tag: "withCtx",
6658
+ _meta: meta,
6659
+ _def: { tag: "withCtx", extra, parser: combinator },
6660
+ parse(input, pos, ctx) {
6661
+ return combinator.parse(input, pos, { ...ctx, state: extra });
6662
+ }
6663
+ };
6664
+ }
6665
+
6303
6666
  // src/cst/offset-model.ts
6304
6667
  var { isFinite } = Number;
6305
6668
 
@@ -6331,6 +6694,35 @@ var SUPPORTED = {
6331
6694
  function isScopeEntry(v2) {
6332
6695
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
6333
6696
  }
6697
+ function propName(p) {
6698
+ if (p.computed) return null;
6699
+ const key = p.key;
6700
+ return key.type === "Identifier" ? key.name ?? null : key.type === "Literal" ? String(key.value) : null;
6701
+ }
6702
+ function isGatedArmExpr(e) {
6703
+ if (e.type !== "ObjectExpression") return false;
6704
+ let hasGate = false, hasCombinator = false;
6705
+ for (const prop of e.properties) {
6706
+ if (prop.type !== "Property") continue;
6707
+ const name = propName(prop);
6708
+ if (name === "gate") hasGate = true;
6709
+ else if (name === "combinator") hasCombinator = true;
6710
+ }
6711
+ return hasGate && hasCombinator;
6712
+ }
6713
+ function gatedArmParts(e) {
6714
+ let gate;
6715
+ let combinator;
6716
+ for (const prop of e.properties) {
6717
+ if (prop.type !== "Property") return null;
6718
+ const op = prop;
6719
+ const name = propName(op);
6720
+ if (name === "gate") gate = op.value;
6721
+ else if (name === "combinator") combinator = op.value;
6722
+ else return null;
6723
+ }
6724
+ return gate && combinator ? { gate, combinator } : null;
6725
+ }
6334
6726
  function isCombinator(v2) {
6335
6727
  return !!v2 && typeof v2 === "object" && "_def" in v2;
6336
6728
  }
@@ -6512,6 +6904,72 @@ function exprToCombi(node2, scope, code, mfs) {
6512
6904
  return null;
6513
6905
  }
6514
6906
  }
6907
+ if (callee.name === "guard") {
6908
+ if (code === void 0 || mfs === void 0) return null;
6909
+ const [predArg] = node2.arguments;
6910
+ if (!predArg || predArg.type === "SpreadElement") return null;
6911
+ const predSrc = code.slice(predArg.start, predArg.end);
6912
+ mfs.push(predSrc);
6913
+ try {
6914
+ const combi = guard(() => true);
6915
+ if (combi._def.tag !== "guard") return null;
6916
+ combi._def.predSrc = predSrc;
6917
+ return combi;
6918
+ } catch {
6919
+ return null;
6920
+ }
6921
+ }
6922
+ if (callee.name === "withCtx") {
6923
+ if (code === void 0 || mfs === void 0) return null;
6924
+ const [extraArg, innerArg] = node2.arguments;
6925
+ if (!extraArg || !innerArg || extraArg.type === "SpreadElement" || innerArg.type === "SpreadElement") return null;
6926
+ const extraSrc = code.slice(extraArg.start, extraArg.end);
6927
+ mfs.push(extraSrc);
6928
+ const inner = anyValue(innerArg, scope, code, mfs);
6929
+ if (!isCombinator(inner)) return null;
6930
+ try {
6931
+ const combi = withCtx({}, inner);
6932
+ if (combi._def.tag !== "withCtx") return null;
6933
+ combi._def.extraSrc = extraSrc;
6934
+ return combi;
6935
+ } catch {
6936
+ return null;
6937
+ }
6938
+ }
6939
+ if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
6940
+ if (code === void 0 || mfs === void 0) return null;
6941
+ const arms = [];
6942
+ const gateSrcs = [];
6943
+ for (const argNode of node2.arguments) {
6944
+ if (argNode.type === "SpreadElement") return null;
6945
+ if (isGatedArmExpr(argNode)) {
6946
+ const parts = gatedArmParts(argNode);
6947
+ if (!parts) return null;
6948
+ const gateSrc = code.slice(parts.gate.start, parts.gate.end);
6949
+ mfs.push(gateSrc);
6950
+ const combi = anyValue(parts.combinator, scope, code, mfs);
6951
+ if (!isCombinator(combi)) return null;
6952
+ arms.push({ gate: () => true, combinator: combi });
6953
+ gateSrcs.push(gateSrc);
6954
+ } else {
6955
+ const combi = anyValue(argNode, scope, code, mfs);
6956
+ if (!isCombinator(combi)) return null;
6957
+ arms.push(combi);
6958
+ gateSrcs.push(null);
6959
+ }
6960
+ }
6961
+ try {
6962
+ const combi = choice(...arms);
6963
+ if (combi._def.tag !== "choice") return null;
6964
+ combi._def.gateSrcs = gateSrcs;
6965
+ for (let i = 0; i < combi._def.gates.length; i++) {
6966
+ if (combi._def.gates[i] !== null !== (gateSrcs[i] !== null)) return null;
6967
+ }
6968
+ return combi;
6969
+ } catch {
6970
+ return null;
6971
+ }
6972
+ }
6515
6973
  const factory = SUPPORTED[callee.name];
6516
6974
  if (!factory) return null;
6517
6975
  const args = node2.arguments.map((arg) => {
@@ -6574,9 +7032,9 @@ function anyValue(node2, scope, code, mfs) {
6574
7032
  if (typeof key !== "string" && typeof key !== "number") return null;
6575
7033
  return obj[key] ?? null;
6576
7034
  }
6577
- const propName = mem.property.name;
6578
- if (!propName) return null;
6579
- return obj[propName] ?? null;
7035
+ const propName2 = mem.property.name;
7036
+ if (!propName2) return null;
7037
+ return obj[propName2] ?? null;
6580
7038
  }
6581
7039
  if (node2.type === "CallExpression") {
6582
7040
  const callee = node2.callee;
@@ -6821,7 +7279,7 @@ var plugin_default = createUnplugin((opts = {}) => ({
6821
7279
  if (!code.includes("parseman")) return null;
6822
7280
  if (!code.includes("macro")) return null;
6823
7281
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
6824
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
7282
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
6825
7283
  if (result?.warnings.length) {
6826
7284
  for (const w of result.warnings) {
6827
7285
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -6925,7 +7383,7 @@ function extractImportBindings(body) {
6925
7383
  }
6926
7384
  return out;
6927
7385
  }
6928
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
7386
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
6929
7387
  let result;
6930
7388
  try {
6931
7389
  result = parseSync(id, code);
@@ -6996,7 +7454,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
6996
7454
  (p) => p.key?.name === "trivia"
6997
7455
  )?.value : void 0;
6998
7456
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
6999
- const compiled = compileRuleMap([...ruleMap], gTrivia ? { trivia: gTrivia } : void 0);
7457
+ const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
7000
7458
  if (!compiled) {
7001
7459
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7002
7460
  return null;
@@ -7094,7 +7552,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7094
7552
  const ns = nsFor(label2);
7095
7553
  const ir = serializeRuleMap(entries);
7096
7554
  if (ir) return { carried: [{ ns, ir }] };
7097
- const p = compileLinkable(entries, ns, composing ? { trivia: composing } : void 0);
7555
+ const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
7098
7556
  return p ? { carried: [p] } : null;
7099
7557
  };
7100
7558
  const argPieces = (arg, label2, composing) => {
@@ -7270,7 +7728,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7270
7728
  const refEntry = scope.get(varName);
7271
7729
  const refCombi = refEntry?.combi ?? null;
7272
7730
  if (refCombi) {
7273
- const compiled2 = compile(refCombi);
7731
+ const compiled2 = compile(refCombi, void 0, { recovery });
7274
7732
  if (compiled2.inlineExpression === null) {
7275
7733
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
7276
7734
  continue;
@@ -7287,7 +7745,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7287
7745
  let replacement = compiledRules.replacement;
7288
7746
  if (exportPrefix) {
7289
7747
  const ns = nsFor(varName);
7290
- const pieces = compileLinkable([...compiledRules.ruleMap], ns);
7748
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
7291
7749
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
7292
7750
  const ir = serializeRuleMap([...compiledRules.ruleMap]);
7293
7751
  replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
@@ -7323,7 +7781,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7323
7781
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
7324
7782
  continue;
7325
7783
  }
7326
- const compiled = compile(parser2);
7784
+ const compiled = compile(parser2, void 0, { recovery });
7327
7785
  if (compiled.inlineExpression === null) {
7328
7786
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
7329
7787
  continue;