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
@@ -332,7 +332,13 @@ function saveCstMark(ctx) {
332
332
  raw: cstRawLen(ctx),
333
333
  tlog: cstTlLen(ctx),
334
334
  leaves: cstLeavesLen(ctx),
335
- fields: ctx._fields?.length ?? 0
335
+ fields: ctx._fields?.length ?? 0,
336
+ // Speculative rollback must truncate the flat recovery-error sink in lockstep
337
+ // with the CST leaves: a branch that recovered (pushing a ParseError to
338
+ // ctx._errors AND embedding it as a CST child via captureError) then failed
339
+ // must remove BOTH, or the flat error survives as a GHOST that a tree walk
340
+ // can't see (flat run().errors would disagree with the embedded set).
341
+ errors: ctx._errors?.length ?? 0
336
342
  };
337
343
  }
338
344
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -348,6 +354,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
348
354
  if (len === 0) b[keySingle] = void 0;
349
355
  }
350
356
  function rollbackCstCapture(ctx, mark) {
357
+ if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
351
358
  const b = ctx._cstBuf;
352
359
  if (b) {
353
360
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
@@ -905,12 +912,159 @@ function keywords(words, opts = {}) {
905
912
  };
906
913
  }
907
914
 
915
+ // src/recovery/scan.ts
916
+ function probeAt(sentinel, input, pos, ctx) {
917
+ const s = {
918
+ _probe: ctx._probe,
919
+ _errors: ctx._errors,
920
+ _cstLeaves: ctx._cstLeaves,
921
+ _cstChildren: ctx._cstChildren,
922
+ _cstRawChildren: ctx._cstRawChildren,
923
+ _cstTriviaLog: ctx._cstTriviaLog,
924
+ _triviaLog: ctx._triviaLog,
925
+ _fields: ctx._fields,
926
+ _cstBuf: ctx._cstBuf,
927
+ _tolerant: ctx._tolerant,
928
+ _sync: ctx._sync
929
+ };
930
+ ctx._probe = void 0;
931
+ ctx._errors = void 0;
932
+ ctx._cstLeaves = void 0;
933
+ ctx._cstChildren = void 0;
934
+ ctx._cstRawChildren = void 0;
935
+ ctx._cstTriviaLog = void 0;
936
+ ctx._triviaLog = void 0;
937
+ ctx._fields = void 0;
938
+ ctx._cstBuf = void 0;
939
+ ctx._tolerant = void 0;
940
+ ctx._sync = void 0;
941
+ try {
942
+ return sentinel.parse(input, pos, ctx).ok;
943
+ } finally {
944
+ ctx._probe = s._probe;
945
+ ctx._errors = s._errors;
946
+ ctx._cstLeaves = s._cstLeaves;
947
+ ctx._cstChildren = s._cstChildren;
948
+ ctx._cstRawChildren = s._cstRawChildren;
949
+ ctx._cstTriviaLog = s._cstTriviaLog;
950
+ ctx._triviaLog = s._triviaLog;
951
+ ctx._fields = s._fields;
952
+ ctx._cstBuf = s._cstBuf;
953
+ ctx._tolerant = s._tolerant;
954
+ ctx._sync = s._sync;
955
+ }
956
+ }
957
+ function orSentinel(a, b) {
958
+ if (b === void 0) return a;
959
+ return {
960
+ _tag: "orSentinel",
961
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
962
+ _def: { tag: "unknown" },
963
+ parse(input, pos, ctx) {
964
+ const ra = a.parse(input, pos, ctx);
965
+ if (ra.ok) return ra;
966
+ return b.parse(input, pos, ctx);
967
+ }
968
+ };
969
+ }
970
+ function firstSetSentinel(fs2) {
971
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
972
+ const ranges = fs2.ranges;
973
+ return {
974
+ _tag: "firstSetSentinel",
975
+ _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
976
+ _def: { tag: "unknown" },
977
+ parse(input, pos) {
978
+ const code = pos < input.length ? input.codePointAt(pos) : void 0;
979
+ if (code !== void 0) {
980
+ for (const r of ranges) {
981
+ if (code >= r.lo && code <= r.hi) return { ok: true, value: null, span: { start: pos, end: pos } };
982
+ }
983
+ }
984
+ return { ok: false, expected: [], span: { start: pos, end: pos } };
985
+ }
986
+ };
987
+ }
988
+ function recoverScan(input, from, ctx, sync, expected) {
989
+ let scanPos = from;
990
+ while (scanPos < input.length && !probeAt(sync, input, scanPos, ctx)) scanPos++;
991
+ const error = { _tag: "parseError", span: { start: from, end: scanPos }, expected };
992
+ ctx._errors?.push(error);
993
+ return { error, end: scanPos };
994
+ }
995
+ function matchesAt(sentinel, input, pos, ctx) {
996
+ return probeAt(sentinel, input, pos, ctx);
997
+ }
998
+ function captureError(ctx, error) {
999
+ if (cstCaptureActive(ctx)) pushCstChild(ctx, error, error);
1000
+ }
1001
+
1002
+ // src/combinators/expect.ts
1003
+ function deriveExpected(c) {
1004
+ const def = c._def;
1005
+ switch (def.tag) {
1006
+ case "literal":
1007
+ return [JSON.stringify(def.value)];
1008
+ case "regex":
1009
+ return [`/${def.source}/`];
1010
+ case "keywords":
1011
+ return def.words.map((w) => JSON.stringify(w));
1012
+ case "label":
1013
+ return [def.label];
1014
+ case "choice":
1015
+ return def.parsers.flatMap(deriveExpected);
1016
+ case "sequence":
1017
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1018
+ case "node":
1019
+ case "grammar":
1020
+ case "trivia":
1021
+ case "token":
1022
+ case "optional":
1023
+ case "many":
1024
+ case "oneOrMore":
1025
+ case "transform":
1026
+ case "not":
1027
+ return deriveExpected(def.parser);
1028
+ case "lazy": {
1029
+ const name = c._ruleName;
1030
+ try {
1031
+ return deriveExpected(def.thunk());
1032
+ } catch {
1033
+ return name ? [name] : [];
1034
+ }
1035
+ }
1036
+ default:
1037
+ return [];
1038
+ }
1039
+ }
1040
+ function expect(combinator, label2) {
1041
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1042
+ const meta = {
1043
+ firstSet: combinator._meta.firstSet,
1044
+ canMatchNewline: combinator._meta.canMatchNewline,
1045
+ isTrivia: false
1046
+ };
1047
+ return {
1048
+ _tag: "expect",
1049
+ _meta: meta,
1050
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1051
+ parse(input, pos, ctx) {
1052
+ const result = combinator.parse(input, pos, ctx);
1053
+ if (result.ok) return result;
1054
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1055
+ ctx._errors?.push(error);
1056
+ if (ctx._tolerant) captureError(ctx, error);
1057
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1058
+ }
1059
+ };
1060
+ }
1061
+
908
1062
  // src/combinators/choice.ts
909
1063
  function choice(...args) {
910
1064
  const parsers = args.map((a) => "gate" in a ? a.combinator : a);
911
1065
  const gates = args.map((a) => "gate" in a ? a.gate : null);
912
1066
  const hasGates = gates.some((g) => g !== null);
913
- const disjoint = !hasGates && areDisjoint(parsers.map((p) => p._meta.firstSet));
1067
+ const disjoint = areDisjoint(parsers.map((p) => p._meta.firstSet)) && parsers.every((p) => !matchesEmpty(p));
914
1068
  let combined = { kind: "empty" };
915
1069
  for (const p of parsers) combined = union(combined, p._meta.firstSet);
916
1070
  const meta = {
@@ -949,17 +1103,21 @@ function choice(...args) {
949
1103
  const expected = [];
950
1104
  if (disjoint && pos < input.length) {
951
1105
  const code = input.codePointAt(pos);
952
- let parser2 = code < 128 ? asciiDispatch?.[code] ?? null : null;
953
- if (!parser2) {
954
- for (const p of parsers) {
955
- if (inFirstSet(code, p._meta.firstSet)) {
956
- parser2 = p;
1106
+ let idx = code < 128 ? asciiDispatch[code] : -1;
1107
+ if (idx < 0) {
1108
+ for (let i = 0; i < parsers.length; i++) {
1109
+ if (inFirstSet(code, parsers[i]._meta.firstSet)) {
1110
+ idx = i;
957
1111
  break;
958
1112
  }
959
1113
  }
960
1114
  }
961
- if (parser2) {
962
- const result = parser2.parse(input, pos, ctx);
1115
+ if (idx >= 0) {
1116
+ const gate = gates[idx];
1117
+ if (gate && !gate(ctx.state)) {
1118
+ return { ok: false, expected: deriveExpected(parsers[idx]), span: { start: pos, end: pos } };
1119
+ }
1120
+ const result = parsers[idx].parse(input, pos, ctx);
963
1121
  if (result.ok) return result;
964
1122
  expected.push(...result.expected);
965
1123
  return { ok: false, expected, span: { start: pos, end: pos } };
@@ -1140,13 +1298,13 @@ function areDisjoint(sets) {
1140
1298
  return true;
1141
1299
  }
1142
1300
  function buildAsciiDispatch(parsers) {
1143
- const table = Array(128).fill(null);
1144
- for (const parser2 of parsers) {
1145
- const fs2 = parser2._meta.firstSet;
1301
+ const table = Array(128).fill(-1);
1302
+ for (let i = 0; i < parsers.length; i++) {
1303
+ const fs2 = parsers[i]._meta.firstSet;
1146
1304
  if (fs2.kind !== "ranges") continue;
1147
1305
  for (const { lo, hi } of fs2.ranges) {
1148
1306
  for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
1149
- table[code] = parser2;
1307
+ table[code] = i;
1150
1308
  }
1151
1309
  }
1152
1310
  }
@@ -1291,10 +1449,10 @@ function needsDeferredTriviaCommit(ctx) {
1291
1449
  }
1292
1450
  function saveTriviaMark(ctx) {
1293
1451
  const m = saveCstMark(ctx);
1294
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1452
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, errors: m.errors, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1295
1453
  }
1296
1454
  function rollbackTrivia(ctx, mark) {
1297
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1455
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1298
1456
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1299
1457
  }
1300
1458
  function scanWithLabels(input, cur, ctx) {
@@ -1538,11 +1696,59 @@ function sequence(...parsers) {
1538
1696
  isTrivia: false
1539
1697
  };
1540
1698
  const def = { tag: "sequence", parsers };
1699
+ let followSentinels;
1700
+ function parseTolerant(input, pos, ctx) {
1701
+ followSentinels ??= parsers.map((_, i) => {
1702
+ const fs2 = parsers.slice(i + 1).reduce(
1703
+ (acc, p) => union(acc, firstSetOf(p)),
1704
+ { kind: "empty" }
1705
+ );
1706
+ return firstSetSentinel(fs2);
1707
+ });
1708
+ const values = def.valueUnused ? void 0 : [];
1709
+ let cur = pos;
1710
+ const inheritedSync = ctx._sync;
1711
+ try {
1712
+ for (let i = 0; i < parsers.length; i++) {
1713
+ ctx._sync = followSentinels[i] ?? inheritedSync;
1714
+ if (ctx.trivia && i > 0) {
1715
+ const mark = saveTriviaMark(ctx);
1716
+ let scanEnd;
1717
+ if (needsDeferredTriviaCommit(ctx)) {
1718
+ const scan = scanTrivia(input, cur, ctx);
1719
+ scan.commit();
1720
+ scanEnd = scan.end;
1721
+ } else {
1722
+ scanEnd = advanceTrivia(input, cur, ctx);
1723
+ }
1724
+ const result2 = parsers[i].parse(input, scanEnd, ctx);
1725
+ if (!result2.ok) return result2;
1726
+ if (result2.span.end > scanEnd) cur = result2.span.end;
1727
+ else rollbackTrivia(ctx, mark);
1728
+ if (values !== void 0) values.push(result2.value);
1729
+ continue;
1730
+ }
1731
+ const result = parsers[i].parse(input, cur, ctx);
1732
+ if (!result.ok) return result;
1733
+ if (values !== void 0) values.push(result.value);
1734
+ cur = result.span.end;
1735
+ }
1736
+ } finally {
1737
+ ctx._sync = inheritedSync;
1738
+ }
1739
+ return {
1740
+ ok: true,
1741
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1742
+ value: values ?? void 0,
1743
+ span: { start: pos, end: cur }
1744
+ };
1745
+ }
1541
1746
  return {
1542
1747
  _tag: "sequence",
1543
1748
  _meta: meta,
1544
1749
  _def: def,
1545
1750
  parse(input, pos, ctx) {
1751
+ if (ctx._tolerant) return parseTolerant(input, pos, ctx);
1546
1752
  const values = def.valueUnused ? void 0 : [];
1547
1753
  let cur = pos;
1548
1754
  for (let i = 0; i < parsers.length; i++) {
@@ -1601,7 +1807,7 @@ function repItem(combinator, input, cur, ctx) {
1601
1807
  const result = combinator.parse(input, pos, ctx);
1602
1808
  if (!result.ok) {
1603
1809
  rollbackTrivia(ctx, mark);
1604
- return { fail: result };
1810
+ return { fail: result, failPos: pos };
1605
1811
  }
1606
1812
  if (result.span.end === pos) {
1607
1813
  rollbackTrivia(ctx, mark);
@@ -1616,6 +1822,7 @@ function many(combinator) {
1616
1822
  isTrivia: false
1617
1823
  };
1618
1824
  const def = { tag: "many", parser: combinator, min: 0 };
1825
+ let expected;
1619
1826
  return {
1620
1827
  _tag: "many",
1621
1828
  _meta: meta,
@@ -1625,7 +1832,18 @@ function many(combinator) {
1625
1832
  let cur = pos;
1626
1833
  while (cur < input.length) {
1627
1834
  const item = repItem(combinator, input, cur, ctx);
1628
- if (item === "stop" || "fail" in item) break;
1835
+ if (item === "stop") break;
1836
+ if ("fail" in item) {
1837
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1838
+ if (sync === void 0) break;
1839
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1840
+ expected ??= deriveExpected(combinator);
1841
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1842
+ if (values !== void 0) values.push(error);
1843
+ captureError(ctx, error);
1844
+ cur = end;
1845
+ continue;
1846
+ }
1629
1847
  if (values !== void 0) values.push(item.value);
1630
1848
  cur = item.end;
1631
1849
  }
@@ -1640,6 +1858,7 @@ function oneOrMore(combinator) {
1640
1858
  isTrivia: false
1641
1859
  };
1642
1860
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1861
+ let expected;
1643
1862
  return {
1644
1863
  _tag: "oneOrMore",
1645
1864
  _meta: meta,
@@ -1651,7 +1870,18 @@ function oneOrMore(combinator) {
1651
1870
  let cur = first.span.end;
1652
1871
  while (cur < input.length) {
1653
1872
  const item = repItem(combinator, input, cur, ctx);
1654
- if (item === "stop" || "fail" in item) break;
1873
+ if (item === "stop") break;
1874
+ if ("fail" in item) {
1875
+ const sync = ctx._tolerant ? ctx._sync : void 0;
1876
+ if (sync === void 0) break;
1877
+ if (matchesAt(sync, input, item.failPos, ctx)) break;
1878
+ expected ??= deriveExpected(combinator);
1879
+ const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1880
+ if (values !== void 0) values.push(error);
1881
+ captureError(ctx, error);
1882
+ cur = end;
1883
+ continue;
1884
+ }
1655
1885
  if (values !== void 0) values.push(item.value);
1656
1886
  cur = item.end;
1657
1887
  }
@@ -1697,15 +1927,29 @@ function sepBy(combinator, separator) {
1697
1927
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
1698
1928
  isTrivia: false
1699
1929
  };
1930
+ let expected;
1700
1931
  return {
1701
1932
  _tag: "sepBy",
1702
1933
  _meta: meta,
1703
1934
  _def: { tag: "sepBy", parser: combinator, separator },
1704
1935
  parse(input, pos, ctx) {
1705
1936
  const first = combinator.parse(input, pos, ctx);
1706
- if (!first.ok) return { ok: true, value: [], span: { start: pos, end: pos } };
1707
- const values = [first.value];
1708
- let cur = first.span.end;
1937
+ const values = [];
1938
+ let cur;
1939
+ if (first.ok) {
1940
+ values.push(first.value);
1941
+ cur = first.span.end;
1942
+ } else {
1943
+ const term = ctx._tolerant ? ctx._sync : void 0;
1944
+ if (term === void 0 || matchesAt(term, input, pos, ctx)) {
1945
+ return { ok: true, value: [], span: { start: pos, end: pos } };
1946
+ }
1947
+ expected ??= deriveExpected(combinator);
1948
+ const rec = recoverScan(input, pos, ctx, orSentinel(separator, term), expected);
1949
+ values.push(rec.error);
1950
+ captureError(ctx, rec.error);
1951
+ cur = rec.end;
1952
+ }
1709
1953
  while (cur < input.length) {
1710
1954
  const loopMark = saveTriviaMark(ctx);
1711
1955
  let sepPos = cur;
@@ -1735,6 +1979,15 @@ function sepBy(combinator, separator) {
1735
1979
  }
1736
1980
  const next = combinator.parse(input, nextPos, ctx);
1737
1981
  if (!next.ok) {
1982
+ const term = ctx._tolerant ? ctx._sync : void 0;
1983
+ if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
1984
+ expected ??= deriveExpected(combinator);
1985
+ const rec = recoverScan(input, nextPos, ctx, orSentinel(separator, term), expected);
1986
+ values.push(rec.error);
1987
+ captureError(ctx, rec.error);
1988
+ cur = rec.end;
1989
+ continue;
1990
+ }
1738
1991
  rollbackTrivia(ctx, loopMark);
1739
1992
  break;
1740
1993
  }
@@ -1871,7 +2124,7 @@ function rules(a, b) {
1871
2124
  cache[key] = parser2;
1872
2125
  }
1873
2126
  }
1874
- if (options?.trivia !== void 0) {
2127
+ if (options?.trivia != null) {
1875
2128
  for (const key of Object.keys(definitions)) {
1876
2129
  const rule = cache[key];
1877
2130
  if (rule && !rule._meta.isTrivia) rule._meta.grammarTrivia = options.trivia;
@@ -1901,7 +2154,9 @@ function not(combinator) {
1901
2154
  _meta: meta,
1902
2155
  _def: { tag: "not", parser: combinator },
1903
2156
  parse(input, pos, ctx) {
2157
+ const mark = saveCstMark(ctx);
1904
2158
  const result = combinator.parse(input, pos, ctx);
2159
+ rollbackCstCapture(ctx, mark);
1905
2160
  if (result.ok) {
1906
2161
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
1907
2162
  }
@@ -2015,7 +2270,7 @@ function unwrapChild(child) {
2015
2270
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
2016
2271
  }
2017
2272
  function isCstChild(value) {
2018
- return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
2273
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "parseError");
2019
2274
  }
2020
2275
  function missingInferredType() {
2021
2276
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -2268,65 +2523,6 @@ function token(root) {
2268
2523
  };
2269
2524
  }
2270
2525
 
2271
- // src/combinators/expect.ts
2272
- function deriveExpected(c) {
2273
- const def = c._def;
2274
- switch (def.tag) {
2275
- case "literal":
2276
- return [JSON.stringify(def.value)];
2277
- case "regex":
2278
- return [`/${def.source}/`];
2279
- case "keywords":
2280
- return def.words.map((w) => JSON.stringify(w));
2281
- case "label":
2282
- return [def.label];
2283
- case "choice":
2284
- return def.parsers.flatMap(deriveExpected);
2285
- case "sequence":
2286
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
2287
- case "node":
2288
- case "grammar":
2289
- case "trivia":
2290
- case "token":
2291
- case "optional":
2292
- case "many":
2293
- case "oneOrMore":
2294
- case "transform":
2295
- case "not":
2296
- return deriveExpected(def.parser);
2297
- case "lazy": {
2298
- const name = c._ruleName;
2299
- try {
2300
- return deriveExpected(def.thunk());
2301
- } catch {
2302
- return name ? [name] : [];
2303
- }
2304
- }
2305
- default:
2306
- return [];
2307
- }
2308
- }
2309
- function expect(combinator, label2) {
2310
- const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
2311
- const meta = {
2312
- firstSet: combinator._meta.firstSet,
2313
- canMatchNewline: combinator._meta.canMatchNewline,
2314
- isTrivia: false
2315
- };
2316
- return {
2317
- _tag: "expect",
2318
- _meta: meta,
2319
- _def: { tag: "expect", parser: combinator, label: label2, expected },
2320
- parse(input, pos, ctx) {
2321
- const result = combinator.parse(input, pos, ctx);
2322
- if (result.ok) return result;
2323
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
2324
- ctx._errors?.push(error);
2325
- return { ok: true, value: error, span: { start: pos, end: pos } };
2326
- }
2327
- };
2328
- }
2329
-
2330
2526
  // src/compiler/scannable-run.ts
2331
2527
  var BACKSLASH = 92;
2332
2528
  var META = new Set("()[]{}*+?|^$.".split(""));
@@ -2398,20 +2594,39 @@ function parseSeqParts(source) {
2398
2594
  lit = [];
2399
2595
  }
2400
2596
  };
2401
- const runFrom = (ranges, negated, q) => {
2597
+ const braceQuant = (at) => {
2598
+ const close = source.indexOf("}", at);
2599
+ if (close === -1) return null;
2600
+ const body = source.slice(at + 1, close);
2601
+ const m = /^(\d+)(,(\d*)?)?$/.exec(body);
2602
+ if (!m) return null;
2603
+ const min = Number(m[1]);
2604
+ const max = m[2] === void 0 ? min : m[3] ? Number(m[3]) : Infinity;
2605
+ if (max < 1 || min > max) return null;
2606
+ return { min, max, len: close - at + 1 };
2607
+ };
2608
+ const runFrom = (ranges, negated, at) => {
2609
+ const q = source[at];
2402
2610
  if (q === "+") {
2403
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
2611
+ parts.push({ part: "run", ranges, negated, min: 1, max: Infinity });
2404
2612
  return 1;
2405
2613
  }
2406
2614
  if (q === "*") {
2407
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
2615
+ parts.push({ part: "run", ranges, negated, min: 0, max: Infinity });
2408
2616
  return 1;
2409
2617
  }
2410
2618
  if (q === "?") {
2411
- parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
2619
+ parts.push({ part: "run", ranges, negated, min: 0, max: 1 });
2412
2620
  return 1;
2413
2621
  }
2414
- parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
2622
+ if (q === "{") {
2623
+ const b = braceQuant(at);
2624
+ if (b) {
2625
+ parts.push({ part: "run", ranges, negated, min: b.min, max: b.max });
2626
+ return b.len;
2627
+ }
2628
+ }
2629
+ parts.push({ part: "run", ranges, negated, min: 1, max: 1 });
2415
2630
  return 0;
2416
2631
  };
2417
2632
  let i = 0;
@@ -2436,7 +2651,7 @@ function parseSeqParts(source) {
2436
2651
  if (!ranges) return null;
2437
2652
  flush();
2438
2653
  i = k + 1;
2439
- i += runFrom(ranges, neg, source[i]);
2654
+ i += runFrom(ranges, neg, i);
2440
2655
  continue;
2441
2656
  }
2442
2657
  if (ch === "\\") {
@@ -2444,7 +2659,7 @@ function parseSeqParts(source) {
2444
2659
  if (e === "d" || e === "w" || e === "s") {
2445
2660
  flush();
2446
2661
  i += 2;
2447
- i += runFrom(shorthandRanges(e), false, source[i]);
2662
+ i += runFrom(shorthandRanges(e), false, i);
2448
2663
  continue;
2449
2664
  }
2450
2665
  const uni = readUnicodeEscape(source, i);
@@ -2533,7 +2748,7 @@ function seqIsUnambiguous(parts) {
2533
2748
  let hasMandatory = false;
2534
2749
  for (let k = 0; k < parts.length; k++) {
2535
2750
  const p = parts[k];
2536
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2751
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2537
2752
  if (mandatory) hasMandatory = true;
2538
2753
  if (p.part === "group") {
2539
2754
  const exposure = groupPartExposure(p);
@@ -2544,9 +2759,8 @@ function seqIsUnambiguous(parts) {
2544
2759
  if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
2545
2760
  }
2546
2761
  }
2547
- const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
2548
- const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
2549
- if (skippableSingle || isUnboundedRepeat) {
2762
+ const needsDisjoint = p.part === "lit" ? p.optional : p.part === "run" ? p.max > p.min : p.unbounded || p.min === 0;
2763
+ if (needsDisjoint) {
2550
2764
  const rest = seqFirstAccept(parts.slice(k + 1));
2551
2765
  if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
2552
2766
  }
@@ -2603,8 +2817,8 @@ function fixedClassSeq(shape) {
2603
2817
  if (p.part === "lit") {
2604
2818
  if (p.optional) return null;
2605
2819
  for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
2606
- } else if (p.part === "run" && !p.unbounded && p.min === 1) {
2607
- out.push({ ranges: p.ranges, negated: p.negated });
2820
+ } else if (p.part === "run" && p.max === p.min && Number.isFinite(p.max)) {
2821
+ for (let c = 0; c < p.max; c++) out.push({ ranges: p.ranges, negated: p.negated });
2608
2822
  } else {
2609
2823
  return null;
2610
2824
  }
@@ -2656,7 +2870,7 @@ function trailingBacktrackClass(shape) {
2656
2870
  const last = shape.parts[shape.parts.length - 1];
2657
2871
  if (last.part === "lit") return null;
2658
2872
  if (last.part === "run") {
2659
- if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
2873
+ if (last.max > last.min) return { ranges: last.ranges, negated: last.negated };
2660
2874
  return null;
2661
2875
  }
2662
2876
  return groupPartExposure(last);
@@ -2793,7 +3007,7 @@ function partFirstAccept(part) {
2793
3007
  function seqFirstAccept(parts) {
2794
3008
  const sets = [];
2795
3009
  for (const p of parts) {
2796
- const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
3010
+ const mandatory = p.part === "lit" ? !p.optional : p.min >= 1;
2797
3011
  sets.push(partFirstAccept(p));
2798
3012
  if (mandatory) break;
2799
3013
  }
@@ -3208,7 +3422,13 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3208
3422
  lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
3209
3423
  lines.push(`${ind2} ${endV2} += ${L}`);
3210
3424
  }
3211
- } else if (p.part === "run" && !p.unbounded) {
3425
+ } else if (p.part === "run" && p.max === Infinity) {
3426
+ const s = mint("_s");
3427
+ lines.push(`${ind2} const ${s} = ${endV2}`);
3428
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3429
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3430
+ else if (p.min > 1) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3431
+ } else if (p.part === "run" && p.max === 1) {
3212
3432
  if (p.min === 1) {
3213
3433
  lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
3214
3434
  lines.push(`${ind2} ${endV2}++`);
@@ -3218,8 +3438,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
3218
3438
  } else if (p.part === "run") {
3219
3439
  const s = mint("_s");
3220
3440
  lines.push(`${ind2} const ${s} = ${endV2}`);
3221
- lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3222
- if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
3441
+ lines.push(`${ind2} while (${endV2} - ${s} < ${p.max} && ${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
3442
+ if (p.min > 0) lines.push(`${ind2} if (${endV2} - ${s} < ${p.min}) break`);
3223
3443
  } else {
3224
3444
  const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
3225
3445
  if (!p.unbounded) {
@@ -3686,21 +3906,29 @@ function hoistExpected(ctx, constArrSource) {
3686
3906
  }
3687
3907
  return name;
3688
3908
  }
3909
+ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3910
+ if (!ctx.recovery) return "";
3911
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3912
+ const pb = v(ctx, "_pb");
3913
+ 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} } } } `;
3914
+ }
3689
3915
  function failBody(ctx, expected, posExpr) {
3916
+ const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
3690
3917
  if (ctx.failLabel) {
3691
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3692
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3918
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3919
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
3693
3920
  }
3694
- return failReturn(expected, posExpr);
3921
+ return probe + failReturn(expected, posExpr);
3695
3922
  }
3696
3923
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3924
+ const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
3697
3925
  if (ctx.failLabel) {
3698
- if (!ctx.recordFail) return `break ${ctx.failLabel}`;
3926
+ if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3699
3927
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3700
- return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3928
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
3701
3929
  }
3702
- if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3703
- return failReturnArr(expectedArr, posExpr);
3930
+ if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
3931
+ return probe + failReturnArr(expectedArr, posExpr);
3704
3932
  }
3705
3933
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
3706
3934
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4200,6 +4428,10 @@ function emitRegex(def, ctx, pos) {
4200
4428
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
4201
4429
  return { stmts, valueVar: vv, endVar };
4202
4430
  }
4431
+ function syncSentinelExpr(fs2) {
4432
+ if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
4433
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
4434
+ }
4203
4435
  function emitSeqValues(def, ctx, pos) {
4204
4436
  const startV = v(ctx, "_start");
4205
4437
  const curV = v(ctx, "_cur");
@@ -4208,7 +4440,17 @@ function emitSeqValues(def, ctx, pos) {
4208
4440
  `${ind(ctx)}let ${curV} = ${pos}`
4209
4441
  ];
4210
4442
  const valueVars = [];
4443
+ const syncInV = ctx.recovery ? v(ctx, "_syncIn") : "";
4444
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
4445
+ const publishSync = (i) => {
4446
+ if (!ctx.recovery) return "";
4447
+ const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
4448
+ const ref2 = syncSentinelExpr(fs2);
4449
+ return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
4450
+ };
4211
4451
  for (let i = 0; i < def.parsers.length; i++) {
4452
+ const syncPub = publishSync(i);
4453
+ if (syncPub) stmts.push(syncPub);
4212
4454
  if (i > 0 && ctx.activeTrivia) {
4213
4455
  if (ctx.capturing) {
4214
4456
  const capFn = ensureTriviaCaptureFn(ctx);
@@ -4233,7 +4475,17 @@ function emitSeqValues(def, ctx, pos) {
4233
4475
  continue;
4234
4476
  } else {
4235
4477
  const trivFn = ensureTriviaFn(ctx);
4236
- stmts.push(`${ind(ctx)}${curV} = ${trivFn}(input, ${curV}, _ctx)`);
4478
+ const scanEndV = v(ctx, "_sne");
4479
+ stmts.push(`${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx)`);
4480
+ const r2 = emit(def.parsers[i], ctx, scanEndV);
4481
+ stmts.push(...r2.stmts);
4482
+ const endAfterV = v(ctx, "_sea");
4483
+ stmts.push(
4484
+ `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
4485
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}`
4486
+ );
4487
+ valueVars.push(r2.valueVar);
4488
+ continue;
4237
4489
  }
4238
4490
  }
4239
4491
  const r = emit(def.parsers[i], ctx, curV);
@@ -4311,6 +4563,21 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
4311
4563
  `${ind(ctx)}${endV} = ${endVar}`
4312
4564
  ];
4313
4565
  }
4566
+ function emitDisjointArmGated(def, i, ctx, pos, valV, endV) {
4567
+ const p = def.parsers[i];
4568
+ const gate = def.gates[i];
4569
+ if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV);
4570
+ const gateIdx = ctx.mapFns.length;
4571
+ ctx.mapFns.push(gate);
4572
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4573
+ const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4574
+ const stmts = [`${ind(ctx)}if (${gateCond}) {`];
4575
+ ctx.indent++;
4576
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4577
+ ctx.indent--;
4578
+ stmts.push(`${ind(ctx)}} else { ${failArrBody(ctx, deriveExpectedArr([p]), pos)} }`);
4579
+ return stmts;
4580
+ }
4314
4581
  function emitChoice(def, ctx, pos) {
4315
4582
  const allExpected = deriveExpectedArr(def.parsers);
4316
4583
  if (def.disjoint) {
@@ -4326,12 +4593,11 @@ function emitChoice(def, ctx, pos) {
4326
4593
  stmts.push(`${ind(ctx)}switch (${codeV}) {`);
4327
4594
  ctx.indent++;
4328
4595
  for (let i = 0; i < def.parsers.length; i++) {
4329
- const p = def.parsers[i];
4330
4596
  for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
4331
4597
  stmts.push(`${ind(ctx)}{`);
4332
4598
  ctx.indent++;
4333
4599
  stmts.push(
4334
- ...emitDisjointArm(p, ctx, pos, valV, endV),
4600
+ ...emitDisjointArmGated(def, i, ctx, pos, valV, endV),
4335
4601
  `${ind(ctx)}break`
4336
4602
  );
4337
4603
  ctx.indent--;
@@ -4343,13 +4609,14 @@ function emitChoice(def, ctx, pos) {
4343
4609
  return { stmts, valueVar: valV, endVar: endV };
4344
4610
  }
4345
4611
  let first = true;
4346
- for (const p of def.parsers) {
4612
+ for (let i = 0; i < def.parsers.length; i++) {
4613
+ const p = def.parsers[i];
4347
4614
  const cond = firstSetCond(codeV, p._meta.firstSet);
4348
4615
  const kw = first ? "if" : "else if";
4349
4616
  first = false;
4350
4617
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
4351
4618
  ctx.indent++;
4352
- stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4619
+ stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV));
4353
4620
  ctx.indent--;
4354
4621
  stmts.push(`${ind(ctx)}}`);
4355
4622
  }
@@ -4463,7 +4730,7 @@ function emitFirstMatch(def, ctx, pos) {
4463
4730
  if (gate) {
4464
4731
  const gateIdx = ctx.mapFns.length;
4465
4732
  ctx.mapFns.push(gate);
4466
- ctx.mapFnSrcs.push(null);
4733
+ ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4467
4734
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4468
4735
  }
4469
4736
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -4568,6 +4835,8 @@ function emitMany(def, ctx, pos) {
4568
4835
  ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
4569
4836
  `${ind(ctx)}let ${curV} = ${pos}`
4570
4837
  ];
4838
+ const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
4839
+ if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
4571
4840
  if (def.min === 1) {
4572
4841
  const firstR = emit(def.parser, ctx, curV);
4573
4842
  stmts.push(...firstR.stmts);
@@ -4601,10 +4870,26 @@ function emitMany(def, ctx, pos) {
4601
4870
  }
4602
4871
  }
4603
4872
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
4604
- stmts.push(
4605
- ...iterStmts,
4606
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
4607
- );
4873
+ stmts.push(...iterStmts);
4874
+ if (ctx.recovery) {
4875
+ const rrV = v(ctx, "_rr");
4876
+ const exp = hoistExpected(ctx, deriveExpectedArr([def.parser]));
4877
+ stmts.push(
4878
+ `${ind(ctx)}if (!${iterOk}) {`,
4879
+ `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
4880
+ `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
4881
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
4882
+ `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
4883
+ `${ind(ctx)} ${curV} = ${rrV}.end`,
4884
+ `${ind(ctx)} continue`,
4885
+ `${ind(ctx)} }`,
4886
+ `${ind(ctx)} break`,
4887
+ `${ind(ctx)}}`,
4888
+ `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
4889
+ );
4890
+ } else {
4891
+ stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
4892
+ }
4608
4893
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
4609
4894
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
4610
4895
  ctx.indent--;
@@ -4626,20 +4911,56 @@ function emitOptional(def, ctx, pos) {
4626
4911
  function emitSepBy(_p, def, ctx, pos) {
4627
4912
  const arrV = v(ctx, "_arr");
4628
4913
  const curV = v(ctx, "_cur");
4914
+ const rec = !!ctx.recovery;
4915
+ const mySyncV = rec ? v(ctx, "_mysy") : "";
4916
+ const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
4917
+ const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
4918
+ const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
4629
4919
  const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
4630
4920
  const stmts = [
4631
4921
  `${ind(ctx)}const ${arrV} = []`,
4632
4922
  `${ind(ctx)}let ${curV} = ${pos}`,
4633
- ...firstStmts,
4634
- `${ind(ctx)}if (${firstOk}) {`
4923
+ ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
4924
+ ...firstStmts
4635
4925
  ];
4636
- ctx.indent++;
4637
- stmts.push(
4638
- `${ind(ctx)}${arrV}.push(${firstVal})`,
4639
- `${ind(ctx)}${curV} = ${firstEnd}`,
4640
- `${ind(ctx)}while (${curV} < input.length) {`
4641
- );
4642
- ctx.indent++;
4926
+ const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
4927
+ if (!rec) return [originalFail];
4928
+ const rr = v(ctx, "_rr");
4929
+ return [
4930
+ `${ind(ctx)}if (!${nextOkVar}) {`,
4931
+ `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
4932
+ `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
4933
+ `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
4934
+ `${ind(ctx)} }`,
4935
+ `${ind(ctx)} ${rb}break`,
4936
+ `${ind(ctx)}}`
4937
+ ];
4938
+ };
4939
+ if (rec) {
4940
+ const rr0 = v(ctx, "_rr0");
4941
+ const didV = v(ctx, "_did");
4942
+ stmts.push(
4943
+ `${ind(ctx)}let ${didV} = false`,
4944
+ `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
4945
+ `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
4946
+ `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
4947
+ `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
4948
+ `${ind(ctx)}}`,
4949
+ `${ind(ctx)}if (${didV}) {`
4950
+ );
4951
+ ctx.indent++;
4952
+ stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
4953
+ ctx.indent++;
4954
+ } else {
4955
+ stmts.push(`${ind(ctx)}if (${firstOk}) {`);
4956
+ ctx.indent++;
4957
+ stmts.push(
4958
+ `${ind(ctx)}${arrV}.push(${firstVal})`,
4959
+ `${ind(ctx)}${curV} = ${firstEnd}`,
4960
+ `${ind(ctx)}while (${curV} < input.length) {`
4961
+ );
4962
+ ctx.indent++;
4963
+ }
4643
4964
  let sepAtPos = curV;
4644
4965
  if (ctx.activeTrivia) {
4645
4966
  if (ctx.capturing) {
@@ -4665,8 +4986,7 @@ function emitSepBy(_p, def, ctx, pos) {
4665
4986
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4666
4987
  stmts.push(
4667
4988
  ...nextStmts,
4668
- // item failed → unwind the separator too, back to the end of the last item
4669
- `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`,
4989
+ ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
4670
4990
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4671
4991
  `${ind(ctx)}${curV} = ${nextEnd}`
4672
4992
  );
@@ -4682,7 +5002,7 @@ function emitSepBy(_p, def, ctx, pos) {
4682
5002
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
4683
5003
  stmts.push(
4684
5004
  ...nextStmts,
4685
- `${ind(ctx)}if (!${nextOk}) break`,
5005
+ ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
4686
5006
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4687
5007
  `${ind(ctx)}${curV} = ${nextEnd}`
4688
5008
  );
@@ -4702,7 +5022,7 @@ function emitSepBy(_p, def, ctx, pos) {
4702
5022
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
4703
5023
  stmts.push(
4704
5024
  ...nextStmts,
4705
- `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
5025
+ ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
4706
5026
  `${ind(ctx)}${arrV}.push(${nextVal})`,
4707
5027
  `${ind(ctx)}${curV} = ${nextEnd}`
4708
5028
  );
@@ -4959,7 +5279,7 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4959
5279
  stmts.push(
4960
5280
  `${i}const ${ndV} = ${finalExpr}`,
4961
5281
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4962
- `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'error')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
5282
+ `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'parseError')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
4963
5283
  );
4964
5284
  return { stmts, valueVar: ndV, endVar };
4965
5285
  }
@@ -5049,6 +5369,10 @@ function emitExpect(def, ctx, pos) {
5049
5369
  `${ind0}if (!${okVar}) {`,
5050
5370
  `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${pos} }, expected: ${JSON.stringify(def.expected)} }`,
5051
5371
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
5372
+ // Embed the missing-token error as a `parseError` CST child, mirroring the
5373
+ // interpreter (expect.ts) and the list-recovery emit sites — guarded on
5374
+ // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
5375
+ `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
5052
5376
  `${ind0} ${valVar} = ${errV}`,
5053
5377
  `${ind0} ${endVar} = ${pos}`,
5054
5378
  `${ind0} ${okVar} = true`,
@@ -5215,7 +5539,7 @@ function emitDispatch(p, ctx, pos) {
5215
5539
  case "guard": {
5216
5540
  const fnIdx = ctx.mapFns.length;
5217
5541
  ctx.mapFns.push(def.predicate);
5218
- ctx.mapFnSrcs.push(null);
5542
+ ctx.mapFnSrcs.push(def.predSrc ?? null);
5219
5543
  const vv = v(ctx);
5220
5544
  return {
5221
5545
  stmts: [
@@ -5230,7 +5554,7 @@ function emitDispatch(p, ctx, pos) {
5230
5554
  const evIdx = ctx.mapFns.length;
5231
5555
  const extra = def.extra;
5232
5556
  ctx.mapFns.push((() => extra));
5233
- ctx.mapFnSrcs.push(null);
5557
+ ctx.mapFnSrcs.push(def.extraSrc !== void 0 ? `() => (${def.extraSrc})` : null);
5234
5558
  const innerParser = def.parser;
5235
5559
  if (!ctx.namedParsers.has(innerParser)) {
5236
5560
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -5474,7 +5798,7 @@ function ruleDependencies(ruleMap) {
5474
5798
  }
5475
5799
  return deps;
5476
5800
  }
5477
- function compile(combinator, mapFnSources) {
5801
+ function compile(combinator, mapFnSources, opts) {
5478
5802
  markUnusedValues(combinator);
5479
5803
  const grammarTrivia = combinator._meta.grammarTrivia;
5480
5804
  const ctx = {
@@ -5495,6 +5819,7 @@ function compile(combinator, mapFnSources) {
5495
5819
  triviaFnNames: /* @__PURE__ */ new Map(),
5496
5820
  namedFnDecls: [],
5497
5821
  capturing: hasNodeDef(combinator),
5822
+ recovery: opts?.recovery ?? false,
5498
5823
  lazyUsage: analyzeLazyUsage(combinator),
5499
5824
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5500
5825
  };
@@ -5547,7 +5872,7 @@ function compile(combinator, mapFnSources) {
5547
5872
  parseWithContext(input, parseCtx, pos = 0) {
5548
5873
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, parseCtx);
5549
5874
  },
5550
- // Note: collects recover()/expect() errors via _errors. Unlike interpreter
5875
+ // Note: collects expect() errors via _errors. Unlike interpreter
5551
5876
  // parse({recover:true}) it does NOT populate furthestFail — the compiled path
5552
5877
  // inlines failures for throughput and deliberately skips _probe bookkeeping.
5553
5878
  // Callers wanting a furthest-position diagnostic detect unconsumed input
@@ -5593,6 +5918,7 @@ function compileRuleMap(ruleMap, opts) {
5593
5918
  triviaFnNames: /* @__PURE__ */ new Map(),
5594
5919
  namedFnDecls: [],
5595
5920
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
5921
+ recovery: opts?.recovery ?? false,
5596
5922
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5597
5923
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5598
5924
  };
@@ -5688,6 +6014,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
5688
6014
  triviaFnNames: /* @__PURE__ */ new Map(),
5689
6015
  namedFnDecls: [],
5690
6016
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
6017
+ recovery: opts?.recovery ?? false,
5691
6018
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
5692
6019
  ns,
5693
6020
  deferFirstSetRefs: true,
@@ -6335,6 +6662,42 @@ function materializePiece(p, trivia2) {
6335
6662
  return pieces;
6336
6663
  }
6337
6664
 
6665
+ // src/combinators/guard.ts
6666
+ function guard(predicate) {
6667
+ const meta = {
6668
+ firstSet: { kind: "any" },
6669
+ canMatchNewline: false,
6670
+ isTrivia: false
6671
+ };
6672
+ return {
6673
+ _tag: "guard",
6674
+ _meta: meta,
6675
+ _def: { tag: "guard", predicate },
6676
+ parse(_input, pos, ctx) {
6677
+ if (predicate(ctx.state))
6678
+ return { ok: true, value: null, span: { start: pos, end: pos } };
6679
+ return { ok: false, expected: ["guard"], span: { start: pos, end: pos } };
6680
+ }
6681
+ };
6682
+ }
6683
+
6684
+ // src/combinators/withCtx.ts
6685
+ function withCtx(extra, combinator) {
6686
+ const meta = {
6687
+ firstSet: combinator._meta.firstSet,
6688
+ canMatchNewline: combinator._meta.canMatchNewline,
6689
+ isTrivia: false
6690
+ };
6691
+ return {
6692
+ _tag: "withCtx",
6693
+ _meta: meta,
6694
+ _def: { tag: "withCtx", extra, parser: combinator },
6695
+ parse(input, pos, ctx) {
6696
+ return combinator.parse(input, pos, { ...ctx, state: extra });
6697
+ }
6698
+ };
6699
+ }
6700
+
6338
6701
  // src/cst/offset-model.ts
6339
6702
  var { isFinite } = Number;
6340
6703
 
@@ -6366,6 +6729,35 @@ var SUPPORTED = {
6366
6729
  function isScopeEntry(v2) {
6367
6730
  return !!v2 && typeof v2 === "object" && "combi" in v2 && "mfSrcs" in v2;
6368
6731
  }
6732
+ function propName(p) {
6733
+ if (p.computed) return null;
6734
+ const key = p.key;
6735
+ return key.type === "Identifier" ? key.name ?? null : key.type === "Literal" ? String(key.value) : null;
6736
+ }
6737
+ function isGatedArmExpr(e) {
6738
+ if (e.type !== "ObjectExpression") return false;
6739
+ let hasGate = false, hasCombinator = false;
6740
+ for (const prop of e.properties) {
6741
+ if (prop.type !== "Property") continue;
6742
+ const name = propName(prop);
6743
+ if (name === "gate") hasGate = true;
6744
+ else if (name === "combinator") hasCombinator = true;
6745
+ }
6746
+ return hasGate && hasCombinator;
6747
+ }
6748
+ function gatedArmParts(e) {
6749
+ let gate;
6750
+ let combinator;
6751
+ for (const prop of e.properties) {
6752
+ if (prop.type !== "Property") return null;
6753
+ const op = prop;
6754
+ const name = propName(op);
6755
+ if (name === "gate") gate = op.value;
6756
+ else if (name === "combinator") combinator = op.value;
6757
+ else return null;
6758
+ }
6759
+ return gate && combinator ? { gate, combinator } : null;
6760
+ }
6369
6761
  function isCombinator(v2) {
6370
6762
  return !!v2 && typeof v2 === "object" && "_def" in v2;
6371
6763
  }
@@ -6547,6 +6939,72 @@ function exprToCombi(node2, scope, code, mfs) {
6547
6939
  return null;
6548
6940
  }
6549
6941
  }
6942
+ if (callee.name === "guard") {
6943
+ if (code === void 0 || mfs === void 0) return null;
6944
+ const [predArg] = node2.arguments;
6945
+ if (!predArg || predArg.type === "SpreadElement") return null;
6946
+ const predSrc = code.slice(predArg.start, predArg.end);
6947
+ mfs.push(predSrc);
6948
+ try {
6949
+ const combi = guard(() => true);
6950
+ if (combi._def.tag !== "guard") return null;
6951
+ combi._def.predSrc = predSrc;
6952
+ return combi;
6953
+ } catch {
6954
+ return null;
6955
+ }
6956
+ }
6957
+ if (callee.name === "withCtx") {
6958
+ if (code === void 0 || mfs === void 0) return null;
6959
+ const [extraArg, innerArg] = node2.arguments;
6960
+ if (!extraArg || !innerArg || extraArg.type === "SpreadElement" || innerArg.type === "SpreadElement") return null;
6961
+ const extraSrc = code.slice(extraArg.start, extraArg.end);
6962
+ mfs.push(extraSrc);
6963
+ const inner = anyValue(innerArg, scope, code, mfs);
6964
+ if (!isCombinator(inner)) return null;
6965
+ try {
6966
+ const combi = withCtx({}, inner);
6967
+ if (combi._def.tag !== "withCtx") return null;
6968
+ combi._def.extraSrc = extraSrc;
6969
+ return combi;
6970
+ } catch {
6971
+ return null;
6972
+ }
6973
+ }
6974
+ if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
6975
+ if (code === void 0 || mfs === void 0) return null;
6976
+ const arms = [];
6977
+ const gateSrcs = [];
6978
+ for (const argNode of node2.arguments) {
6979
+ if (argNode.type === "SpreadElement") return null;
6980
+ if (isGatedArmExpr(argNode)) {
6981
+ const parts = gatedArmParts(argNode);
6982
+ if (!parts) return null;
6983
+ const gateSrc = code.slice(parts.gate.start, parts.gate.end);
6984
+ mfs.push(gateSrc);
6985
+ const combi = anyValue(parts.combinator, scope, code, mfs);
6986
+ if (!isCombinator(combi)) return null;
6987
+ arms.push({ gate: () => true, combinator: combi });
6988
+ gateSrcs.push(gateSrc);
6989
+ } else {
6990
+ const combi = anyValue(argNode, scope, code, mfs);
6991
+ if (!isCombinator(combi)) return null;
6992
+ arms.push(combi);
6993
+ gateSrcs.push(null);
6994
+ }
6995
+ }
6996
+ try {
6997
+ const combi = choice(...arms);
6998
+ if (combi._def.tag !== "choice") return null;
6999
+ combi._def.gateSrcs = gateSrcs;
7000
+ for (let i = 0; i < combi._def.gates.length; i++) {
7001
+ if (combi._def.gates[i] !== null !== (gateSrcs[i] !== null)) return null;
7002
+ }
7003
+ return combi;
7004
+ } catch {
7005
+ return null;
7006
+ }
7007
+ }
6550
7008
  const factory = SUPPORTED[callee.name];
6551
7009
  if (!factory) return null;
6552
7010
  const args = node2.arguments.map((arg) => {
@@ -6609,9 +7067,9 @@ function anyValue(node2, scope, code, mfs) {
6609
7067
  if (typeof key !== "string" && typeof key !== "number") return null;
6610
7068
  return obj[key] ?? null;
6611
7069
  }
6612
- const propName = mem.property.name;
6613
- if (!propName) return null;
6614
- return obj[propName] ?? null;
7070
+ const propName2 = mem.property.name;
7071
+ if (!propName2) return null;
7072
+ return obj[propName2] ?? null;
6615
7073
  }
6616
7074
  if (node2.type === "CallExpression") {
6617
7075
  const callee = node2.callee;
@@ -6856,7 +7314,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
6856
7314
  if (!code.includes("parseman")) return null;
6857
7315
  if (!code.includes("macro")) return null;
6858
7316
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
6859
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
7317
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
6860
7318
  if (result?.warnings.length) {
6861
7319
  for (const w of result.warnings) {
6862
7320
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -6960,7 +7418,7 @@ function extractImportBindings(body) {
6960
7418
  }
6961
7419
  return out;
6962
7420
  }
6963
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
7421
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
6964
7422
  let result;
6965
7423
  try {
6966
7424
  result = (0, import_oxc_parser.parseSync)(id, code);
@@ -7031,7 +7489,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7031
7489
  (p) => p.key?.name === "trivia"
7032
7490
  )?.value : void 0;
7033
7491
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
7034
- const compiled = compileRuleMap([...ruleMap], gTrivia ? { trivia: gTrivia } : void 0);
7492
+ const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
7035
7493
  if (!compiled) {
7036
7494
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7037
7495
  return null;
@@ -7129,7 +7587,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7129
7587
  const ns = nsFor(label2);
7130
7588
  const ir = serializeRuleMap(entries);
7131
7589
  if (ir) return { carried: [{ ns, ir }] };
7132
- const p = compileLinkable(entries, ns, composing ? { trivia: composing } : void 0);
7590
+ const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
7133
7591
  return p ? { carried: [p] } : null;
7134
7592
  };
7135
7593
  const argPieces = (arg, label2, composing) => {
@@ -7305,7 +7763,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7305
7763
  const refEntry = scope.get(varName);
7306
7764
  const refCombi = refEntry?.combi ?? null;
7307
7765
  if (refCombi) {
7308
- const compiled2 = compile(refCombi);
7766
+ const compiled2 = compile(refCombi, void 0, { recovery });
7309
7767
  if (compiled2.inlineExpression === null) {
7310
7768
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
7311
7769
  continue;
@@ -7322,7 +7780,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7322
7780
  let replacement = compiledRules.replacement;
7323
7781
  if (exportPrefix) {
7324
7782
  const ns = nsFor(varName);
7325
- const pieces = compileLinkable([...compiledRules.ruleMap], ns);
7783
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
7326
7784
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
7327
7785
  const ir = serializeRuleMap([...compiledRules.ruleMap]);
7328
7786
  replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
@@ -7358,7 +7816,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7358
7816
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
7359
7817
  continue;
7360
7818
  }
7361
- const compiled = compile(parser2);
7819
+ const compiled = compile(parser2, void 0, { recovery });
7362
7820
  if (compiled.inlineExpression === null) {
7363
7821
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
7364
7822
  continue;