parseman 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/analysis/duplication.d.ts.map +1 -1
  2. package/dist/analysis/gating.d.ts.map +1 -1
  3. package/dist/combinators/attempt.d.ts.map +1 -1
  4. package/dist/combinators/choice.d.ts.map +1 -1
  5. package/dist/combinators/dispatch.d.ts +20 -0
  6. package/dist/combinators/dispatch.d.ts.map +1 -0
  7. package/dist/combinators/expect.d.ts.map +1 -1
  8. package/dist/combinators/first-set.d.ts.map +1 -1
  9. package/dist/combinators/repeat.d.ts.map +1 -1
  10. package/dist/compiler/codegen.d.ts.map +1 -1
  11. package/dist/compiler/fields.d.ts.map +1 -1
  12. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  13. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  14. package/dist/compiler/value-usage.d.ts.map +1 -1
  15. package/dist/coverage.d.ts.map +1 -1
  16. package/dist/index.cjs +383 -31
  17. package/dist/index.cjs.map +4 -4
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +383 -31
  21. package/dist/index.js.map +4 -4
  22. package/dist/language-service/index.cjs.map +2 -2
  23. package/dist/language-service/index.js.map +2 -2
  24. package/dist/plugin/evaluator.d.ts.map +1 -1
  25. package/dist/plugin/index.cjs +436 -31
  26. package/dist/plugin/index.cjs.map +4 -4
  27. package/dist/plugin/index.js +436 -31
  28. package/dist/plugin/index.js.map +4 -4
  29. package/dist/spec/index.cjs +107 -0
  30. package/dist/spec/index.cjs.map +4 -4
  31. package/dist/spec/index.js +107 -0
  32. package/dist/spec/index.js.map +4 -4
  33. package/dist/spec/model.d.ts.map +1 -1
  34. package/dist/types.d.ts +17 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -77,6 +77,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
77
77
  return d.parsers.every(me);
78
78
  case "choice":
79
79
  return d.parsers.some(me);
80
+ case "dispatch":
81
+ return me(d.selector);
80
82
  case "transform":
81
83
  case "label":
82
84
  case "trivia":
@@ -150,6 +152,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
150
152
  case "keywords":
151
153
  return p._meta.firstSet;
152
154
  // terminals: no refs, cached set is exact
155
+ case "dispatch":
156
+ return fs(d.selector);
153
157
  case "lazy":
154
158
  try {
155
159
  return fs(d.thunk());
@@ -1067,6 +1071,8 @@ function derive(c, seen) {
1067
1071
  return [def.label];
1068
1072
  case "choice":
1069
1073
  return def.parsers.flatMap(deriveExpected2);
1074
+ case "dispatch":
1075
+ return deriveExpected2(def.selector);
1070
1076
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1071
1077
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1072
1078
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1208,6 +1214,7 @@ function choice(...args) {
1208
1214
  const result = parsers[idx].parse(input, pos, ctx);
1209
1215
  if (result.ok) return result;
1210
1216
  expected.push(...result.expected);
1217
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1211
1218
  return { ok: false, expected, span: { start: pos, end: pos } };
1212
1219
  }
1213
1220
  return {
@@ -1248,6 +1255,7 @@ function choice(...args) {
1248
1255
  rollbackCstCapture(ctx, mark);
1249
1256
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1250
1257
  expected.push(...result.expected);
1258
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1251
1259
  continue;
1252
1260
  }
1253
1261
  const checks = autoNot[i];
@@ -1927,6 +1935,75 @@ function sequence(...parsers) {
1927
1935
  };
1928
1936
  }
1929
1937
 
1938
+ // src/combinators/dispatch.ts
1939
+ function when(key, parser2) {
1940
+ const keys = Array.isArray(key) ? [...key] : [key];
1941
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
1942
+ for (const item of keys) {
1943
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
1944
+ }
1945
+ return { kind: "when", keys, parser: parser2 };
1946
+ }
1947
+ function otherwise(parser2) {
1948
+ return { kind: "otherwise", parser: parser2 };
1949
+ }
1950
+ function dispatch(selector, ...arms) {
1951
+ let fallback;
1952
+ const cases = [];
1953
+ const seen = /* @__PURE__ */ new Set();
1954
+ for (let i = 0; i < arms.length; i++) {
1955
+ const arm = arms[i];
1956
+ if (arm.kind === "otherwise") {
1957
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
1958
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
1959
+ fallback = arm.parser;
1960
+ continue;
1961
+ }
1962
+ for (const key of arm.keys) {
1963
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
1964
+ seen.add(key);
1965
+ }
1966
+ cases.push({ keys: arm.keys, parser: arm.parser });
1967
+ }
1968
+ const meta = {
1969
+ firstSet: selector._meta.firstSet,
1970
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
1971
+ isTrivia: false
1972
+ };
1973
+ const byKey = /* @__PURE__ */ new Map();
1974
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
1975
+ return {
1976
+ _tag: "dispatch",
1977
+ _meta: meta,
1978
+ _def: {
1979
+ tag: "dispatch",
1980
+ selector,
1981
+ cases,
1982
+ ...fallback === void 0 ? {} : { otherwise: fallback }
1983
+ },
1984
+ parse(input, pos, ctx) {
1985
+ const selected = selector.parse(input, pos, ctx);
1986
+ if (!selected.ok) return selected;
1987
+ const tail = byKey.get(selected.value) ?? fallback;
1988
+ if (tail === void 0) {
1989
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
1990
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
1991
+ }
1992
+ const mark = saveTriviaMark(ctx);
1993
+ const result = tail.parse(input, selected.span.end, ctx);
1994
+ if (!result.ok) {
1995
+ rollbackTrivia(ctx, mark);
1996
+ return { ...result, committed: true };
1997
+ }
1998
+ return {
1999
+ ok: true,
2000
+ value: [selected.value, result.value],
2001
+ span: { start: pos, end: result.span.end }
2002
+ };
2003
+ }
2004
+ };
2005
+ }
2006
+
1930
2007
  // src/combinators/attempt.ts
1931
2008
  function attempt(parser2) {
1932
2009
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -1949,6 +2026,7 @@ function attempt(parser2) {
1949
2026
  if (result.ok) return result;
1950
2027
  rollbackCstCapture(ctx, mark);
1951
2028
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2029
+ if (result.committed) return result;
1952
2030
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1953
2031
  }
1954
2032
  };
@@ -1978,6 +2056,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
1978
2056
  const result = combinator.parse(input, pos, ctx);
1979
2057
  if (!result.ok) {
1980
2058
  rollbackTrivia(ctx, mark);
2059
+ if (result.committed) return { fail: result, failPos: pos };
1981
2060
  return { fail: result, failPos: pos };
1982
2061
  }
1983
2062
  if (result.span.end === pos) {
@@ -2021,6 +2100,7 @@ function many(combinator, opts = {}) {
2021
2100
  const item = repItem(combinator, input, cur, ctx, guardable);
2022
2101
  if (item === "stop") break;
2023
2102
  if ("fail" in item) {
2103
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2024
2104
  const sync = ctx._tolerant ? ctx._sync : void 0;
2025
2105
  if (sync === void 0) break;
2026
2106
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2065,6 +2145,7 @@ function atLeast(combinator, min, max) {
2065
2145
  while (count < min) {
2066
2146
  const item = repItem(combinator, input, cur, ctx, guardable);
2067
2147
  if (item === "stop" || "fail" in item) {
2148
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2068
2149
  expected ??= deriveExpected(combinator);
2069
2150
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2070
2151
  }
@@ -2077,6 +2158,7 @@ function atLeast(combinator, min, max) {
2077
2158
  const item = repItem(combinator, input, cur, ctx, guardable);
2078
2159
  if (item === "stop") break;
2079
2160
  if ("fail" in item) {
2161
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2080
2162
  const sync = ctx._tolerant ? ctx._sync : void 0;
2081
2163
  if (sync === void 0) break;
2082
2164
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2115,6 +2197,7 @@ function optional(combinator) {
2115
2197
  const result = combinator.parse(input, pos, ctx);
2116
2198
  if (result.ok) return result;
2117
2199
  rollbackTrivia(ctx, mark);
2200
+ if (result.committed) return result;
2118
2201
  return { ok: true, value: null, span: { start: pos, end: pos } };
2119
2202
  }
2120
2203
  };
@@ -2154,6 +2237,7 @@ function sepBy(combinator, separator, opts = {}) {
2154
2237
  values.push(first.value);
2155
2238
  cur = first.span.end;
2156
2239
  } else {
2240
+ if (first.committed) return first;
2157
2241
  const term = ctx._tolerant ? ctx._sync : void 0;
2158
2242
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2159
2243
  if (min >= 1) return failAt2(pos);
@@ -2181,6 +2265,7 @@ function sepBy(combinator, separator, opts = {}) {
2181
2265
  const sep = separator.parse(input, sepPos, ctx);
2182
2266
  if (!sep.ok) {
2183
2267
  rollbackTrivia(ctx, loopMark);
2268
+ if (sep.committed) return sep;
2184
2269
  break;
2185
2270
  }
2186
2271
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2196,6 +2281,7 @@ function sepBy(combinator, separator, opts = {}) {
2196
2281
  }
2197
2282
  const next = combinator.parse(input, nextPos, ctx);
2198
2283
  if (!next.ok) {
2284
+ if (next.committed) return next;
2199
2285
  const term = ctx._tolerant ? ctx._sync : void 0;
2200
2286
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2201
2287
  expected ??= deriveExpected(combinator);
@@ -2288,6 +2374,11 @@ function markUnusedValues(root) {
2288
2374
  case "choice":
2289
2375
  for (const p of def.parsers) visit(p, consumed);
2290
2376
  return;
2377
+ case "dispatch":
2378
+ visit(def.selector, consumed);
2379
+ for (const entry of def.cases) visit(entry.parser, consumed);
2380
+ if (def.otherwise) visit(def.otherwise, consumed);
2381
+ return;
2291
2382
  case "node":
2292
2383
  visit(def.parser, false);
2293
2384
  return;
@@ -2563,6 +2654,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2563
2654
  case "sequence":
2564
2655
  case "choice":
2565
2656
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2657
+ case "dispatch":
2658
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2566
2659
  case "sepBy":
2567
2660
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2568
2661
  case "skip":
@@ -2630,6 +2723,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2630
2723
  return parserHasTriviaSite(d.parser, seen);
2631
2724
  case "choice":
2632
2725
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2726
+ case "dispatch":
2727
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2633
2728
  case "grammar":
2634
2729
  return parserHasTriviaSite(d.parser, seen);
2635
2730
  case "lazy": {
@@ -2655,6 +2750,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2655
2750
  case "sequence":
2656
2751
  case "choice":
2657
2752
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2753
+ case "dispatch":
2754
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2658
2755
  case "sepBy":
2659
2756
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2660
2757
  case "skip":
@@ -3056,7 +3153,7 @@ function leaf(root, fn) {
3056
3153
  }
3057
3154
 
3058
3155
  // src/version.ts
3059
- var PARSEMAN_VERSION = "0.38.0";
3156
+ var PARSEMAN_VERSION = "0.39.0";
3060
3157
 
3061
3158
  // src/cst/host-mode.ts
3062
3159
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3212,6 +3309,8 @@ function classifyBroadArm(arm, resolve) {
3212
3309
  }
3213
3310
  return null;
3214
3311
  }
3312
+ case "dispatch":
3313
+ return walk2(d.selector);
3215
3314
  default:
3216
3315
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3217
3316
  }
@@ -3311,6 +3410,11 @@ function analyzeGatingRules(ruleMap, opts) {
3311
3410
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3312
3411
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3313
3412
  if (rec[k]) kids.push(rec[k]);
3413
+ if (d.tag === "dispatch") {
3414
+ kids.push(d.selector);
3415
+ for (const c of d.cases) kids.push(c.parser);
3416
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3417
+ }
3314
3418
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3315
3419
  if (d.tag === "lazy") {
3316
3420
  try {
@@ -3417,6 +3521,12 @@ function childrenOf(d) {
3417
3521
  case "sequence":
3418
3522
  case "choice":
3419
3523
  return d.parsers;
3524
+ case "dispatch":
3525
+ return [
3526
+ d.selector,
3527
+ ...d.cases.map((c) => c.parser),
3528
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3529
+ ];
3420
3530
  case "skip":
3421
3531
  return [d.main, d.skipped];
3422
3532
  case "sepBy":
@@ -3473,6 +3583,8 @@ function payloadKey(p, d) {
3473
3583
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3474
3584
  case "sepBy":
3475
3585
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3586
+ case "dispatch":
3587
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3476
3588
  case "expect":
3477
3589
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3478
3590
  case "scanTo":
@@ -3544,6 +3656,11 @@ function render(p, depth = 3) {
3544
3656
  return `sequence(${kids.join(", ")})`;
3545
3657
  case "choice":
3546
3658
  return `choice(${kids.join(", ")})`;
3659
+ case "dispatch": {
3660
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3661
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3662
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3663
+ }
3547
3664
  case "node":
3548
3665
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3549
3666
  case "label":
@@ -3567,6 +3684,10 @@ function segment(d, index) {
3567
3684
  return `seq[${index}]`;
3568
3685
  case "choice":
3569
3686
  return `choice[${index}]`;
3687
+ case "dispatch":
3688
+ if (index === 0) return "dispatch.selector";
3689
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3690
+ return "dispatch.otherwise";
3570
3691
  case "node":
3571
3692
  return `node(${d.type ?? ""})`;
3572
3693
  case "label":
@@ -4820,6 +4941,8 @@ function children(def, winners) {
4820
4941
  case "sequence":
4821
4942
  case "choice":
4822
4943
  return def.parsers;
4944
+ case "dispatch":
4945
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4823
4946
  case "many":
4824
4947
  case "oneOrMore":
4825
4948
  case "optional":
@@ -6284,6 +6407,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6284
6407
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6285
6408
  return { alts };
6286
6409
  }
6410
+ case "dispatch":
6411
+ return rec(d.selector);
6287
6412
  case "sequence": {
6288
6413
  const chain = [];
6289
6414
  for (const term of d.parsers) {
@@ -6358,6 +6483,9 @@ function failReturn(expected, posExpr) {
6358
6483
  function failReturnArr(expectedArr, posExpr) {
6359
6484
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6360
6485
  }
6486
+ function committedReturnArr(expectedArr, posExpr) {
6487
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6488
+ }
6361
6489
  function hoistExpected(ctx, constArrSource) {
6362
6490
  let name = ctx.expectedMap.get(constArrSource);
6363
6491
  if (name === void 0) {
@@ -6394,15 +6522,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6394
6522
  return probe + trace + failReturnArr(expectedArr, posExpr);
6395
6523
  }
6396
6524
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6397
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6525
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6526
+ }
6527
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6528
+ if (ctx.failLabel) {
6529
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6530
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6531
+ }
6532
+ return committedReturnArr(expectedArr, posExpr);
6398
6533
  }
6399
6534
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6400
6535
  if (srcCtx !== "_ctx") {
6401
6536
  if (ctx.failLabel) {
6402
6537
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6403
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6538
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6404
6539
  }
6405
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6540
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6406
6541
  }
6407
6542
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6408
6543
  return resultFromRecorded();
@@ -6615,6 +6750,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6615
6750
  // nothing committed remains.
6616
6751
  case "choice":
6617
6752
  return false;
6753
+ case "dispatch":
6754
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6618
6755
  case "attempt":
6619
6756
  return false;
6620
6757
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6659,6 +6796,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6659
6796
  return true;
6660
6797
  }
6661
6798
  }
6799
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6800
+ if (seen.has(p)) return false;
6801
+ seen.add(p);
6802
+ const d = p._def;
6803
+ switch (d.tag) {
6804
+ case "dispatch":
6805
+ return true;
6806
+ case "choice":
6807
+ case "sequence":
6808
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6809
+ case "many":
6810
+ case "oneOrMore":
6811
+ case "optional":
6812
+ case "attempt":
6813
+ case "transform":
6814
+ case "label":
6815
+ case "field":
6816
+ case "grammar":
6817
+ case "node":
6818
+ return mayCommitFailure(d.parser, seen);
6819
+ case "sepBy":
6820
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6821
+ case "skip":
6822
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6823
+ case "token":
6824
+ case "leaf":
6825
+ return mayCommitFailure(d.parser, seen);
6826
+ case "withCtx":
6827
+ return mayCommitFailure(d.parser, seen);
6828
+ case "scanTo":
6829
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6830
+ case "lazy": {
6831
+ try {
6832
+ return mayCommitFailure(d.thunk(), seen);
6833
+ } catch {
6834
+ return true;
6835
+ }
6836
+ }
6837
+ case "unknown":
6838
+ return true;
6839
+ default:
6840
+ return false;
6841
+ }
6842
+ }
6662
6843
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6663
6844
  if (seen.has(p)) return false;
6664
6845
  seen.add(p);
@@ -6680,6 +6861,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6680
6861
  case "sequence":
6681
6862
  case "choice":
6682
6863
  return d.parsers.some((x) => capturesLeaf(x, seen));
6864
+ case "dispatch":
6865
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6683
6866
  case "sepBy":
6684
6867
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6685
6868
  case "many":
@@ -6726,6 +6909,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6726
6909
  ctx.recordFail = savedRecord;
6727
6910
  const ind0 = ind(ctx);
6728
6911
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
6912
+ const mayCommit = mayCommitFailure(inner);
6729
6913
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6730
6914
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6731
6915
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6739,6 +6923,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6739
6923
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6740
6924
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6741
6925
  ] : [],
6926
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6742
6927
  `${ind0}${lbl}: {`,
6743
6928
  ...r.stmts,
6744
6929
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6747,7 +6932,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6747
6932
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6748
6933
  ] : []
6749
6934
  ];
6750
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
6935
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6751
6936
  }
6752
6937
  var CHARCODE_CHAIN_MAX = 16;
6753
6938
  function emitLit(def, ctx, pos) {
@@ -7124,6 +7309,118 @@ function emitChoice(parser2, def, ctx, pos) {
7124
7309
  }
7125
7310
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7126
7311
  }
7312
+ function emitTailFallible(parser2, ctx, pos) {
7313
+ const savedRecord = ctx.recordFail;
7314
+ ctx.recordFail = true;
7315
+ const out = emitFallible(parser2, ctx, pos);
7316
+ ctx.recordFail = savedRecord;
7317
+ return out;
7318
+ }
7319
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7320
+ if (seen.has(p)) return false;
7321
+ seen.add(p);
7322
+ const d = p._def;
7323
+ switch (d.tag) {
7324
+ case "expect":
7325
+ case "recover":
7326
+ return true;
7327
+ case "many":
7328
+ case "oneOrMore":
7329
+ case "sepBy":
7330
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7331
+ case "choice":
7332
+ case "sequence": {
7333
+ for (const item of d.parsers) {
7334
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7335
+ }
7336
+ return false;
7337
+ }
7338
+ case "dispatch": {
7339
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7340
+ for (const item of d.cases) {
7341
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7342
+ }
7343
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7344
+ }
7345
+ case "attempt":
7346
+ case "transform":
7347
+ case "label":
7348
+ case "field":
7349
+ case "grammar":
7350
+ case "node":
7351
+ case "token":
7352
+ case "leaf":
7353
+ case "withCtx":
7354
+ case "optional":
7355
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7356
+ case "skip":
7357
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7358
+ case "scanTo":
7359
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7360
+ for (const item of d.skip) {
7361
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7362
+ }
7363
+ return false;
7364
+ case "lazy": {
7365
+ try {
7366
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7367
+ } catch {
7368
+ return true;
7369
+ }
7370
+ }
7371
+ case "unknown":
7372
+ return true;
7373
+ default:
7374
+ return false;
7375
+ }
7376
+ }
7377
+ function emitDispatchCombinator(def, ctx, pos) {
7378
+ const selector = emit(def.selector, ctx, pos);
7379
+ const outV = v(ctx, "_dval");
7380
+ const outE = v(ctx, "_dend");
7381
+ const keyV = v(ctx, "_dkey");
7382
+ const stmts = [
7383
+ ...selector.stmts,
7384
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7385
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7386
+ ];
7387
+ let wroteBranch = false;
7388
+ const emitSelectedTail = (parser2, keyword, condition) => {
7389
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7390
+ stmts.push(head);
7391
+ ctx.indent++;
7392
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7393
+ const errMark = mayError ? v(ctx, "_derr") : null;
7394
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7395
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7396
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7397
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7398
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7399
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7400
+ stmts.push(
7401
+ ...tail.stmts,
7402
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7403
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7404
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7405
+ );
7406
+ ctx.indent--;
7407
+ stmts.push(`${ind(ctx)}}`);
7408
+ };
7409
+ for (const entry of def.cases) {
7410
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7411
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7412
+ wroteBranch = true;
7413
+ }
7414
+ if (def.otherwise) {
7415
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7416
+ else emitSelectedTail(def.otherwise, "if", "true");
7417
+ } else {
7418
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7419
+ const fail = failArrBody(ctx, expected, selector.endVar);
7420
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7421
+ }
7422
+ return { stmts, valueVar: outV, endVar: outE };
7423
+ }
7127
7424
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7128
7425
  const superParser = def.parsers[superIndex];
7129
7426
  const regexDef = getCoreRegexDef(superParser);
@@ -7245,12 +7542,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7245
7542
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7246
7543
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7247
7544
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7545
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7248
7546
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7249
7547
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7250
7548
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7251
7549
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7252
7550
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7253
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7551
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7552
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7553
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7554
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7254
7555
  const failSlot = atStart ? staticFx : "_ctx._fx";
7255
7556
  stmts.push(`${ind0}if (${skipCond}) {`);
7256
7557
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7265,7 +7566,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7265
7566
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7266
7567
  );
7267
7568
  }
7268
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7569
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7570
+ const arm = emitFallible(p, ctx, pos, atStart);
7571
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7269
7572
  stmts.push(...armStmts);
7270
7573
  if (autoNot && autoNot.length > 0) {
7271
7574
  const anCode = v(ctx, "_anc");
@@ -7289,10 +7592,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7289
7592
  stmts.push(`${ind(ctx)}}`);
7290
7593
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}; ${coverageBase === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} });`} }`);
7291
7594
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7595
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7292
7596
  } else {
7293
7597
  const rejectedFlush = autoRejectedV === void 0 ? "" : `; for (const _rejectedId of ${autoRejectedV}) { _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'failure', offset: ${pos} }); _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'backtrack', offset: ${pos} }) }`;
7294
7598
  stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true${coverageBase === void 0 ? "" : `${rejectedFlush}; _ctx._grammarCoverage?.(${JSON.stringify(`${coverageBase}${i}`)}); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'selected', offset: ${pos}, end: ${endVar} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'success', offset: ${pos}, end: ${endVar} })`} }`);
7295
7599
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""}${coverageBase === void 0 ? "" : `; _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} })`} }`);
7600
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7296
7601
  }
7297
7602
  ctx.indent -= fsGuard ? 2 : 1;
7298
7603
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7449,7 +7754,8 @@ function emitMany(def, ctx, pos) {
7449
7754
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7450
7755
  );
7451
7756
  }
7452
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7757
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7758
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7453
7759
  stmts.push(...iterStmts);
7454
7760
  if (ctx.recovery) {
7455
7761
  const rrV = v(ctx, "_rr");
@@ -7468,7 +7774,8 @@ function emitMany(def, ctx, pos) {
7468
7774
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7469
7775
  );
7470
7776
  } else {
7471
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7777
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7778
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7472
7779
  }
7473
7780
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7474
7781
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7480,10 +7787,12 @@ function emitMany(def, ctx, pos) {
7480
7787
  function emitOptional(def, ctx, pos) {
7481
7788
  const valV = v(ctx, "_opt");
7482
7789
  const endV = v(ctx, "_opte");
7483
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7790
+ const inner = emitFallible(def.parser, ctx, pos, true);
7791
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7484
7792
  const ind0 = ind(ctx);
7485
7793
  const stmts = [
7486
7794
  ...lblStmts,
7795
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7487
7796
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7488
7797
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7489
7798
  ];
@@ -7515,7 +7824,7 @@ function emitAttempt(p, def, ctx, pos) {
7515
7824
  ...preGuard,
7516
7825
  `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${trivia2} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`,
7517
7826
  ...inner.stmts,
7518
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7827
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7519
7828
  ],
7520
7829
  valueVar: inner.valVar,
7521
7830
  endVar: inner.endVar
@@ -7529,18 +7838,21 @@ function emitSepBy(_p, def, ctx, pos) {
7529
7838
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7530
7839
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7531
7840
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7532
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7841
+ const first = emitFallible(def.parser, ctx, pos, true);
7842
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7533
7843
  const stmts = [
7534
7844
  `${ind(ctx)}const ${arrV} = []`,
7535
7845
  `${ind(ctx)}let ${curV} = ${pos}`,
7536
7846
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7537
7847
  ...firstStmts
7538
7848
  ];
7539
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7540
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7849
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7850
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7851
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7541
7852
  const rr = v(ctx, "_rr");
7542
7853
  return [
7543
7854
  `${ind(ctx)}if (!${nextOkVar}) {`,
7855
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7544
7856
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7545
7857
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7546
7858
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7569,6 +7881,7 @@ function emitSepBy(_p, def, ctx, pos) {
7569
7881
  stmts.push(
7570
7882
  `${ind(ctx)}let ${didV} = false`,
7571
7883
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
7884
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7572
7885
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7573
7886
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7574
7887
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7579,6 +7892,7 @@ function emitSepBy(_p, def, ctx, pos) {
7579
7892
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7580
7893
  ctx.indent++;
7581
7894
  } else {
7895
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7582
7896
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7583
7897
  ctx.indent++;
7584
7898
  stmts.push(
@@ -7607,16 +7921,18 @@ function emitSepBy(_p, def, ctx, pos) {
7607
7921
  );
7608
7922
  sepAtPos = spV;
7609
7923
  const rollbackToSep = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
7610
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7611
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
7924
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7925
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7926
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7612
7927
  const post = postSepMarks();
7613
7928
  stmts.push(...post.decl);
7614
7929
  const npV = v(ctx, "_np");
7615
7930
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7616
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7931
+ const next = emitFallible(def.parser, ctx, npV, true);
7932
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7617
7933
  stmts.push(
7618
7934
  ...nextStmts,
7619
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7935
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7620
7936
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7621
7937
  `${ind(ctx)}${curV} = ${nextEnd}`
7622
7938
  );
@@ -7625,14 +7941,16 @@ function emitSepBy(_p, def, ctx, pos) {
7625
7941
  const spV = v(ctx, "_sp");
7626
7942
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7627
7943
  sepAtPos = spV;
7628
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7629
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7944
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7945
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7946
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7630
7947
  const npV = v(ctx, "_np");
7631
7948
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7632
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7949
+ const next = emitFallible(def.parser, ctx, npV, true);
7950
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7633
7951
  stmts.push(
7634
7952
  ...nextStmts,
7635
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
7953
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7636
7954
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7637
7955
  `${ind(ctx)}${curV} = ${nextEnd}`
7638
7956
  );
@@ -7646,15 +7964,17 @@ function emitSepBy(_p, def, ctx, pos) {
7646
7964
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7647
7965
  );
7648
7966
  }
7649
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7650
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7967
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7968
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7969
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7651
7970
  const nextRb = markLv ? `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markRw}) _ctx._cstRawChildren.length = ${markRw}; ` : "";
7652
7971
  const post = postSepMarks();
7653
7972
  stmts.push(...post.decl);
7654
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
7973
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
7974
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7655
7975
  stmts.push(
7656
7976
  ...nextStmts,
7657
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7977
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7658
7978
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7659
7979
  `${ind(ctx)}${curV} = ${nextEnd}`
7660
7980
  );
@@ -8035,7 +8355,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8035
8355
  const rv = v(ctx, "_rt");
8036
8356
  const vv = v(ctx, "_rtv");
8037
8357
  const ev = v(ctx, "_rte");
8038
- const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span }`;
8358
+ const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `{ _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span, ...(${rv}.committed ? { committed: true } : {}) }`;
8039
8359
  const stmts = [
8040
8360
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8041
8361
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8116,7 +8436,7 @@ function emitRecover(def, ctx, pos) {
8116
8436
  return { stmts, valueVar: valVar, endVar };
8117
8437
  }
8118
8438
  function emitExpect(def, ctx, pos) {
8119
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8439
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8120
8440
  const ind0 = ind(ctx);
8121
8441
  const errV = v(ctx, "_err");
8122
8442
  const stmts = [
@@ -8128,6 +8448,7 @@ function emitExpect(def, ctx, pos) {
8128
8448
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8129
8449
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8130
8450
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8451
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8131
8452
  `${ind0} ${valVar} = ${errV}`,
8132
8453
  `${ind0} ${endVar} = ${pos}`,
8133
8454
  `${ind0} ${okVar} = true`,
@@ -8138,7 +8459,7 @@ function emitExpect(def, ctx, pos) {
8138
8459
  function emit(p, ctx, pos) {
8139
8460
  const replay = ctx.replayPrefix?.get(p);
8140
8461
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8141
- const dispatch = (emissionPos = pos) => {
8462
+ const dispatch2 = (emissionPos = pos) => {
8142
8463
  const savedRule = ctx.activeCoverageRuleId;
8143
8464
  const rule = ctx.coverage?.plan.rules.get(p);
8144
8465
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8178,14 +8499,14 @@ function emit(p, ctx, pos) {
8178
8499
  ctx.indent = 1;
8179
8500
  ctx.failLabel = "_pfail";
8180
8501
  ctx.recordFail = true;
8181
- const r = dispatch("_pos");
8502
+ const r = dispatch2("_pos");
8182
8503
  ctx.indent = savedIndent;
8183
8504
  ctx.failLabel = savedFailLabel;
8184
8505
  ctx.recordFail = savedRecord;
8185
8506
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8186
8507
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8187
8508
  }
8188
- return instrument(dispatch());
8509
+ return instrument(dispatch2());
8189
8510
  }
8190
8511
  function emitDispatch(p, ctx, pos) {
8191
8512
  const bal = p._balancedAmbient;
@@ -8218,6 +8539,8 @@ function emitDispatch(p, ctx, pos) {
8218
8539
  return emitSeq(def, ctx, pos);
8219
8540
  case "choice":
8220
8541
  return emitChoice(p, def, ctx, pos);
8542
+ case "dispatch":
8543
+ return emitDispatchCombinator(def, ctx, pos);
8221
8544
  case "attempt":
8222
8545
  return emitAttempt(p, def, ctx, pos);
8223
8546
  case "many":
@@ -8438,6 +8761,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8438
8761
  case "sequence":
8439
8762
  case "choice":
8440
8763
  return d.parsers.some((x) => hasNodeDef(x, seen));
8764
+ case "dispatch":
8765
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8441
8766
  case "sepBy":
8442
8767
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8443
8768
  case "scanTo":
@@ -8471,6 +8796,8 @@ function childrenOf2(def) {
8471
8796
  case "sequence":
8472
8797
  case "choice":
8473
8798
  return def.parsers;
8799
+ case "dispatch":
8800
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8474
8801
  case "many":
8475
8802
  case "attempt":
8476
8803
  case "optional":
@@ -8514,6 +8841,7 @@ function childrenOf2(def) {
8514
8841
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8515
8842
  "sequence",
8516
8843
  "choice",
8844
+ "dispatch",
8517
8845
  "many",
8518
8846
  "oneOrMore",
8519
8847
  "optional",
@@ -9212,6 +9540,8 @@ function childrenOf3(def) {
9212
9540
  case "sequence":
9213
9541
  case "choice":
9214
9542
  return def.parsers;
9543
+ case "dispatch":
9544
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9215
9545
  case "many":
9216
9546
  case "oneOrMore":
9217
9547
  case "optional":
@@ -9361,6 +9691,9 @@ function evalRuleMapIR(ir) {
9361
9691
  "keywords",
9362
9692
  "sequence",
9363
9693
  "choice",
9694
+ "dispatch",
9695
+ "when",
9696
+ "otherwise",
9364
9697
  "attempt",
9365
9698
  "many",
9366
9699
  "oneOrMore",
@@ -9394,6 +9727,9 @@ function evalRuleMapIR(ir) {
9394
9727
  keywords,
9395
9728
  sequence,
9396
9729
  choice,
9730
+ dispatch,
9731
+ when,
9732
+ otherwise,
9397
9733
  attempt,
9398
9734
  many,
9399
9735
  oneOrMore,
@@ -9579,6 +9915,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9579
9915
  });
9580
9916
  return `_gch([${items.join(", ")}])`;
9581
9917
  }
9918
+ case "dispatch": {
9919
+ const arms = def.cases.map(
9920
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
9921
+ );
9922
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
9923
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
9924
+ }
9582
9925
  case "many":
9583
9926
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9584
9927
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10798,6 +11141,12 @@ function coverageEntry(entry, collector, maps, trace) {
10798
11141
  const unsafeSequence = sequence;
10799
11142
  return unsafeSequence(...def.parsers.map(build));
10800
11143
  }
11144
+ case "dispatch": {
11145
+ const arms = def.cases.map((entry2) => when(entry2.keys, build(entry2.parser)));
11146
+ if (def.otherwise) arms.push(otherwise(build(def.otherwise)));
11147
+ const unsafeDispatch = dispatch;
11148
+ return unsafeDispatch(build(def.selector), ...arms);
11149
+ }
10801
11150
  case "many":
10802
11151
  return many(build(def.parser));
10803
11152
  case "oneOrMore":
@@ -11242,6 +11591,7 @@ export {
11242
11591
  createGrammarTraceSink,
11243
11592
  createVisitor,
11244
11593
  cstBuildHost,
11594
+ dispatch,
11245
11595
  duplicationFindingCount,
11246
11596
  expect,
11247
11597
  extractCharClasses,
@@ -11274,6 +11624,7 @@ export {
11274
11624
  oneOrMore,
11275
11625
  oneOrMoreSep,
11276
11626
  optional,
11627
+ otherwise,
11277
11628
  parse,
11278
11629
  parseDoc,
11279
11630
  parser,
@@ -11297,6 +11648,7 @@ export {
11297
11648
  triviaEntries,
11298
11649
  triviaKindMask,
11299
11650
  walk,
11651
+ when,
11300
11652
  withCtx,
11301
11653
  word
11302
11654
  };