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
@@ -120,6 +120,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
120
120
  return d.parsers.every(me);
121
121
  case "choice":
122
122
  return d.parsers.some(me);
123
+ case "dispatch":
124
+ return me(d.selector);
123
125
  case "transform":
124
126
  case "label":
125
127
  case "trivia":
@@ -193,6 +195,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
193
195
  case "keywords":
194
196
  return p._meta.firstSet;
195
197
  // terminals: no refs, cached set is exact
198
+ case "dispatch":
199
+ return fs2(d.selector);
196
200
  case "lazy":
197
201
  try {
198
202
  return fs2(d.thunk());
@@ -1141,6 +1145,8 @@ function derive(c, seen) {
1141
1145
  return [def.label];
1142
1146
  case "choice":
1143
1147
  return def.parsers.flatMap(deriveExpected2);
1148
+ case "dispatch":
1149
+ return deriveExpected2(def.selector);
1144
1150
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1145
1151
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1146
1152
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1279,6 +1285,7 @@ function choice(...args) {
1279
1285
  const result = parsers[idx].parse(input, pos, ctx);
1280
1286
  if (result.ok) return result;
1281
1287
  expected.push(...result.expected);
1288
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1282
1289
  return { ok: false, expected, span: { start: pos, end: pos } };
1283
1290
  }
1284
1291
  return {
@@ -1319,6 +1326,7 @@ function choice(...args) {
1319
1326
  rollbackCstCapture(ctx, mark);
1320
1327
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1321
1328
  expected.push(...result.expected);
1329
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1322
1330
  continue;
1323
1331
  }
1324
1332
  const checks = autoNot[i];
@@ -1998,6 +2006,75 @@ function sequence(...parsers) {
1998
2006
  };
1999
2007
  }
2000
2008
 
2009
+ // src/combinators/dispatch.ts
2010
+ function when(key, parser2) {
2011
+ const keys = Array.isArray(key) ? [...key] : [key];
2012
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2013
+ for (const item of keys) {
2014
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2015
+ }
2016
+ return { kind: "when", keys, parser: parser2 };
2017
+ }
2018
+ function otherwise(parser2) {
2019
+ return { kind: "otherwise", parser: parser2 };
2020
+ }
2021
+ function dispatch(selector, ...arms) {
2022
+ let fallback;
2023
+ const cases = [];
2024
+ const seen = /* @__PURE__ */ new Set();
2025
+ for (let i = 0; i < arms.length; i++) {
2026
+ const arm = arms[i];
2027
+ if (arm.kind === "otherwise") {
2028
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2029
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2030
+ fallback = arm.parser;
2031
+ continue;
2032
+ }
2033
+ for (const key of arm.keys) {
2034
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2035
+ seen.add(key);
2036
+ }
2037
+ cases.push({ keys: arm.keys, parser: arm.parser });
2038
+ }
2039
+ const meta = {
2040
+ firstSet: selector._meta.firstSet,
2041
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2042
+ isTrivia: false
2043
+ };
2044
+ const byKey = /* @__PURE__ */ new Map();
2045
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2046
+ return {
2047
+ _tag: "dispatch",
2048
+ _meta: meta,
2049
+ _def: {
2050
+ tag: "dispatch",
2051
+ selector,
2052
+ cases,
2053
+ ...fallback === void 0 ? {} : { otherwise: fallback }
2054
+ },
2055
+ parse(input, pos, ctx) {
2056
+ const selected = selector.parse(input, pos, ctx);
2057
+ if (!selected.ok) return selected;
2058
+ const tail = byKey.get(selected.value) ?? fallback;
2059
+ if (tail === void 0) {
2060
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2061
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2062
+ }
2063
+ const mark = saveTriviaMark(ctx);
2064
+ const result = tail.parse(input, selected.span.end, ctx);
2065
+ if (!result.ok) {
2066
+ rollbackTrivia(ctx, mark);
2067
+ return { ...result, committed: true };
2068
+ }
2069
+ return {
2070
+ ok: true,
2071
+ value: [selected.value, result.value],
2072
+ span: { start: pos, end: result.span.end }
2073
+ };
2074
+ }
2075
+ };
2076
+ }
2077
+
2001
2078
  // src/combinators/attempt.ts
2002
2079
  function attempt(parser2) {
2003
2080
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2020,6 +2097,7 @@ function attempt(parser2) {
2020
2097
  if (result.ok) return result;
2021
2098
  rollbackCstCapture(ctx, mark);
2022
2099
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2100
+ if (result.committed) return result;
2023
2101
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2024
2102
  }
2025
2103
  };
@@ -2049,6 +2127,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2049
2127
  const result = combinator.parse(input, pos, ctx);
2050
2128
  if (!result.ok) {
2051
2129
  rollbackTrivia(ctx, mark);
2130
+ if (result.committed) return { fail: result, failPos: pos };
2052
2131
  return { fail: result, failPos: pos };
2053
2132
  }
2054
2133
  if (result.span.end === pos) {
@@ -2092,6 +2171,7 @@ function many(combinator, opts = {}) {
2092
2171
  const item = repItem(combinator, input, cur, ctx, guardable);
2093
2172
  if (item === "stop") break;
2094
2173
  if ("fail" in item) {
2174
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2095
2175
  const sync = ctx._tolerant ? ctx._sync : void 0;
2096
2176
  if (sync === void 0) break;
2097
2177
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2136,6 +2216,7 @@ function atLeast(combinator, min, max) {
2136
2216
  while (count < min) {
2137
2217
  const item = repItem(combinator, input, cur, ctx, guardable);
2138
2218
  if (item === "stop" || "fail" in item) {
2219
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2139
2220
  expected ??= deriveExpected(combinator);
2140
2221
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2141
2222
  }
@@ -2148,6 +2229,7 @@ function atLeast(combinator, min, max) {
2148
2229
  const item = repItem(combinator, input, cur, ctx, guardable);
2149
2230
  if (item === "stop") break;
2150
2231
  if ("fail" in item) {
2232
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2151
2233
  const sync = ctx._tolerant ? ctx._sync : void 0;
2152
2234
  if (sync === void 0) break;
2153
2235
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2186,6 +2268,7 @@ function optional(combinator) {
2186
2268
  const result = combinator.parse(input, pos, ctx);
2187
2269
  if (result.ok) return result;
2188
2270
  rollbackTrivia(ctx, mark);
2271
+ if (result.committed) return result;
2189
2272
  return { ok: true, value: null, span: { start: pos, end: pos } };
2190
2273
  }
2191
2274
  };
@@ -2225,6 +2308,7 @@ function sepBy(combinator, separator, opts = {}) {
2225
2308
  values.push(first.value);
2226
2309
  cur = first.span.end;
2227
2310
  } else {
2311
+ if (first.committed) return first;
2228
2312
  const term = ctx._tolerant ? ctx._sync : void 0;
2229
2313
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2230
2314
  if (min >= 1) return failAt2(pos);
@@ -2252,6 +2336,7 @@ function sepBy(combinator, separator, opts = {}) {
2252
2336
  const sep2 = separator.parse(input, sepPos, ctx);
2253
2337
  if (!sep2.ok) {
2254
2338
  rollbackTrivia(ctx, loopMark);
2339
+ if (sep2.committed) return sep2;
2255
2340
  break;
2256
2341
  }
2257
2342
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2267,6 +2352,7 @@ function sepBy(combinator, separator, opts = {}) {
2267
2352
  }
2268
2353
  const next = combinator.parse(input, nextPos, ctx);
2269
2354
  if (!next.ok) {
2355
+ if (next.committed) return next;
2270
2356
  const term = ctx._tolerant ? ctx._sync : void 0;
2271
2357
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2272
2358
  expected ??= deriveExpected(combinator);
@@ -2322,6 +2408,11 @@ function markUnusedValues(root) {
2322
2408
  case "choice":
2323
2409
  for (const p of def.parsers) visit(p, consumed);
2324
2410
  return;
2411
+ case "dispatch":
2412
+ visit(def.selector, consumed);
2413
+ for (const entry of def.cases) visit(entry.parser, consumed);
2414
+ if (def.otherwise) visit(def.otherwise, consumed);
2415
+ return;
2325
2416
  case "node":
2326
2417
  visit(def.parser, false);
2327
2418
  return;
@@ -2597,6 +2688,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2597
2688
  case "sequence":
2598
2689
  case "choice":
2599
2690
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2691
+ case "dispatch":
2692
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2600
2693
  case "sepBy":
2601
2694
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2602
2695
  case "skip":
@@ -2664,6 +2757,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2664
2757
  return parserHasTriviaSite(d.parser, seen);
2665
2758
  case "choice":
2666
2759
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2760
+ case "dispatch":
2761
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2667
2762
  case "grammar":
2668
2763
  return parserHasTriviaSite(d.parser, seen);
2669
2764
  case "lazy": {
@@ -2689,6 +2784,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2689
2784
  case "sequence":
2690
2785
  case "choice":
2691
2786
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2787
+ case "dispatch":
2788
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2692
2789
  case "sepBy":
2693
2790
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2694
2791
  case "skip":
@@ -3063,7 +3160,7 @@ function leaf(root, fn) {
3063
3160
  }
3064
3161
 
3065
3162
  // src/version.ts
3066
- var PARSEMAN_VERSION = "0.38.0";
3163
+ var PARSEMAN_VERSION = "0.39.0";
3067
3164
 
3068
3165
  // src/cst/host-mode.ts
3069
3166
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3219,6 +3316,8 @@ function classifyBroadArm(arm, resolve2) {
3219
3316
  }
3220
3317
  return null;
3221
3318
  }
3319
+ case "dispatch":
3320
+ return walk2(d.selector);
3222
3321
  default:
3223
3322
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3224
3323
  }
@@ -3318,6 +3417,11 @@ function analyzeGatingRules(ruleMap, opts) {
3318
3417
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3319
3418
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3320
3419
  if (rec[k]) kids.push(rec[k]);
3420
+ if (d.tag === "dispatch") {
3421
+ kids.push(d.selector);
3422
+ for (const c of d.cases) kids.push(c.parser);
3423
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3424
+ }
3321
3425
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3322
3426
  if (d.tag === "lazy") {
3323
3427
  try {
@@ -3424,6 +3528,12 @@ function childrenOf(d) {
3424
3528
  case "sequence":
3425
3529
  case "choice":
3426
3530
  return d.parsers;
3531
+ case "dispatch":
3532
+ return [
3533
+ d.selector,
3534
+ ...d.cases.map((c) => c.parser),
3535
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3536
+ ];
3427
3537
  case "skip":
3428
3538
  return [d.main, d.skipped];
3429
3539
  case "sepBy":
@@ -3480,6 +3590,8 @@ function payloadKey(p, d) {
3480
3590
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3481
3591
  case "sepBy":
3482
3592
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3593
+ case "dispatch":
3594
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3483
3595
  case "expect":
3484
3596
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3485
3597
  case "scanTo":
@@ -3551,6 +3663,11 @@ function render(p, depth = 3) {
3551
3663
  return `sequence(${kids.join(", ")})`;
3552
3664
  case "choice":
3553
3665
  return `choice(${kids.join(", ")})`;
3666
+ case "dispatch": {
3667
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3668
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3669
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3670
+ }
3554
3671
  case "node":
3555
3672
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3556
3673
  case "label":
@@ -3574,6 +3691,10 @@ function segment(d, index) {
3574
3691
  return `seq[${index}]`;
3575
3692
  case "choice":
3576
3693
  return `choice[${index}]`;
3694
+ case "dispatch":
3695
+ if (index === 0) return "dispatch.selector";
3696
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3697
+ return "dispatch.otherwise";
3577
3698
  case "node":
3578
3699
  return `node(${d.type ?? ""})`;
3579
3700
  case "label":
@@ -4827,6 +4948,8 @@ function children(def, winners) {
4827
4948
  case "sequence":
4828
4949
  case "choice":
4829
4950
  return def.parsers;
4951
+ case "dispatch":
4952
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4830
4953
  case "many":
4831
4954
  case "oneOrMore":
4832
4955
  case "optional":
@@ -6291,6 +6414,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6291
6414
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6292
6415
  return { alts };
6293
6416
  }
6417
+ case "dispatch":
6418
+ return rec(d.selector);
6294
6419
  case "sequence": {
6295
6420
  const chain = [];
6296
6421
  for (const term of d.parsers) {
@@ -6373,6 +6498,9 @@ function failReturn(expected, posExpr) {
6373
6498
  function failReturnArr(expectedArr, posExpr) {
6374
6499
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6375
6500
  }
6501
+ function committedReturnArr(expectedArr, posExpr) {
6502
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6503
+ }
6376
6504
  function hoistExpected(ctx, constArrSource) {
6377
6505
  let name = ctx.expectedMap.get(constArrSource);
6378
6506
  if (name === void 0) {
@@ -6409,15 +6537,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6409
6537
  return probe + trace + failReturnArr(expectedArr, posExpr);
6410
6538
  }
6411
6539
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6412
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6540
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6541
+ }
6542
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6543
+ if (ctx.failLabel) {
6544
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6545
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6546
+ }
6547
+ return committedReturnArr(expectedArr, posExpr);
6413
6548
  }
6414
6549
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6415
6550
  if (srcCtx !== "_ctx") {
6416
6551
  if (ctx.failLabel) {
6417
6552
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6418
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6553
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6419
6554
  }
6420
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6555
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6421
6556
  }
6422
6557
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6423
6558
  return resultFromRecorded();
@@ -6630,6 +6765,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6630
6765
  // nothing committed remains.
6631
6766
  case "choice":
6632
6767
  return false;
6768
+ case "dispatch":
6769
+ 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);
6633
6770
  case "attempt":
6634
6771
  return false;
6635
6772
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6674,6 +6811,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6674
6811
  return true;
6675
6812
  }
6676
6813
  }
6814
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6815
+ if (seen.has(p)) return false;
6816
+ seen.add(p);
6817
+ const d = p._def;
6818
+ switch (d.tag) {
6819
+ case "dispatch":
6820
+ return true;
6821
+ case "choice":
6822
+ case "sequence":
6823
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6824
+ case "many":
6825
+ case "oneOrMore":
6826
+ case "optional":
6827
+ case "attempt":
6828
+ case "transform":
6829
+ case "label":
6830
+ case "field":
6831
+ case "grammar":
6832
+ case "node":
6833
+ return mayCommitFailure(d.parser, seen);
6834
+ case "sepBy":
6835
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6836
+ case "skip":
6837
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6838
+ case "token":
6839
+ case "leaf":
6840
+ return mayCommitFailure(d.parser, seen);
6841
+ case "withCtx":
6842
+ return mayCommitFailure(d.parser, seen);
6843
+ case "scanTo":
6844
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6845
+ case "lazy": {
6846
+ try {
6847
+ return mayCommitFailure(d.thunk(), seen);
6848
+ } catch {
6849
+ return true;
6850
+ }
6851
+ }
6852
+ case "unknown":
6853
+ return true;
6854
+ default:
6855
+ return false;
6856
+ }
6857
+ }
6677
6858
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6678
6859
  if (seen.has(p)) return false;
6679
6860
  seen.add(p);
@@ -6695,6 +6876,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6695
6876
  case "sequence":
6696
6877
  case "choice":
6697
6878
  return d.parsers.some((x) => capturesLeaf(x, seen));
6879
+ case "dispatch":
6880
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6698
6881
  case "sepBy":
6699
6882
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6700
6883
  case "many":
@@ -6741,6 +6924,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6741
6924
  ctx.recordFail = savedRecord;
6742
6925
  const ind0 = ind(ctx);
6743
6926
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
6927
+ const mayCommit = mayCommitFailure(inner);
6744
6928
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6745
6929
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6746
6930
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6754,6 +6938,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6754
6938
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6755
6939
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6756
6940
  ] : [],
6941
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6757
6942
  `${ind0}${lbl}: {`,
6758
6943
  ...r.stmts,
6759
6944
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6762,7 +6947,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6762
6947
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6763
6948
  ] : []
6764
6949
  ];
6765
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
6950
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6766
6951
  }
6767
6952
  var CHARCODE_CHAIN_MAX = 16;
6768
6953
  function emitLit(def, ctx, pos) {
@@ -7139,6 +7324,118 @@ function emitChoice(parser2, def, ctx, pos) {
7139
7324
  }
7140
7325
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7141
7326
  }
7327
+ function emitTailFallible(parser2, ctx, pos) {
7328
+ const savedRecord = ctx.recordFail;
7329
+ ctx.recordFail = true;
7330
+ const out = emitFallible(parser2, ctx, pos);
7331
+ ctx.recordFail = savedRecord;
7332
+ return out;
7333
+ }
7334
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7335
+ if (seen.has(p)) return false;
7336
+ seen.add(p);
7337
+ const d = p._def;
7338
+ switch (d.tag) {
7339
+ case "expect":
7340
+ case "recover":
7341
+ return true;
7342
+ case "many":
7343
+ case "oneOrMore":
7344
+ case "sepBy":
7345
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7346
+ case "choice":
7347
+ case "sequence": {
7348
+ for (const item of d.parsers) {
7349
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7350
+ }
7351
+ return false;
7352
+ }
7353
+ case "dispatch": {
7354
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7355
+ for (const item of d.cases) {
7356
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7357
+ }
7358
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7359
+ }
7360
+ case "attempt":
7361
+ case "transform":
7362
+ case "label":
7363
+ case "field":
7364
+ case "grammar":
7365
+ case "node":
7366
+ case "token":
7367
+ case "leaf":
7368
+ case "withCtx":
7369
+ case "optional":
7370
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7371
+ case "skip":
7372
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7373
+ case "scanTo":
7374
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7375
+ for (const item of d.skip) {
7376
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7377
+ }
7378
+ return false;
7379
+ case "lazy": {
7380
+ try {
7381
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7382
+ } catch {
7383
+ return true;
7384
+ }
7385
+ }
7386
+ case "unknown":
7387
+ return true;
7388
+ default:
7389
+ return false;
7390
+ }
7391
+ }
7392
+ function emitDispatchCombinator(def, ctx, pos) {
7393
+ const selector = emit(def.selector, ctx, pos);
7394
+ const outV = v(ctx, "_dval");
7395
+ const outE = v(ctx, "_dend");
7396
+ const keyV = v(ctx, "_dkey");
7397
+ const stmts = [
7398
+ ...selector.stmts,
7399
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7400
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7401
+ ];
7402
+ let wroteBranch = false;
7403
+ const emitSelectedTail = (parser2, keyword, condition) => {
7404
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7405
+ stmts.push(head);
7406
+ ctx.indent++;
7407
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7408
+ const errMark = mayError ? v(ctx, "_derr") : null;
7409
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7410
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7411
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7412
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7413
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7414
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7415
+ stmts.push(
7416
+ ...tail.stmts,
7417
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7418
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7419
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7420
+ );
7421
+ ctx.indent--;
7422
+ stmts.push(`${ind(ctx)}}`);
7423
+ };
7424
+ for (const entry of def.cases) {
7425
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7426
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7427
+ wroteBranch = true;
7428
+ }
7429
+ if (def.otherwise) {
7430
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7431
+ else emitSelectedTail(def.otherwise, "if", "true");
7432
+ } else {
7433
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7434
+ const fail = failArrBody(ctx, expected, selector.endVar);
7435
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7436
+ }
7437
+ return { stmts, valueVar: outV, endVar: outE };
7438
+ }
7142
7439
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7143
7440
  const superParser = def.parsers[superIndex];
7144
7441
  const regexDef = getCoreRegexDef(superParser);
@@ -7260,12 +7557,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7260
7557
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7261
7558
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7262
7559
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7560
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7263
7561
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7264
7562
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7265
7563
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7266
7564
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7267
7565
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7268
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7566
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7567
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7568
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7569
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7269
7570
  const failSlot = atStart ? staticFx : "_ctx._fx";
7270
7571
  stmts.push(`${ind0}if (${skipCond}) {`);
7271
7572
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7280,7 +7581,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7280
7581
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7281
7582
  );
7282
7583
  }
7283
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7584
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7585
+ const arm = emitFallible(p, ctx, pos, atStart);
7586
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7284
7587
  stmts.push(...armStmts);
7285
7588
  if (autoNot && autoNot.length > 0) {
7286
7589
  const anCode = v(ctx, "_anc");
@@ -7304,10 +7607,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7304
7607
  stmts.push(`${ind(ctx)}}`);
7305
7608
  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} });`} }`);
7306
7609
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7610
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7307
7611
  } else {
7308
7612
  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} }) }`;
7309
7613
  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} })`} }`);
7310
7614
  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} })`} }`);
7615
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7311
7616
  }
7312
7617
  ctx.indent -= fsGuard ? 2 : 1;
7313
7618
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7464,7 +7769,8 @@ function emitMany(def, ctx, pos) {
7464
7769
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7465
7770
  );
7466
7771
  }
7467
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7772
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7773
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7468
7774
  stmts.push(...iterStmts);
7469
7775
  if (ctx.recovery) {
7470
7776
  const rrV = v(ctx, "_rr");
@@ -7483,7 +7789,8 @@ function emitMany(def, ctx, pos) {
7483
7789
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7484
7790
  );
7485
7791
  } else {
7486
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7792
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7793
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7487
7794
  }
7488
7795
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7489
7796
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7495,10 +7802,12 @@ function emitMany(def, ctx, pos) {
7495
7802
  function emitOptional(def, ctx, pos) {
7496
7803
  const valV = v(ctx, "_opt");
7497
7804
  const endV = v(ctx, "_opte");
7498
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7805
+ const inner = emitFallible(def.parser, ctx, pos, true);
7806
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7499
7807
  const ind0 = ind(ctx);
7500
7808
  const stmts = [
7501
7809
  ...lblStmts,
7810
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7502
7811
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7503
7812
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7504
7813
  ];
@@ -7530,7 +7839,7 @@ function emitAttempt(p, def, ctx, pos) {
7530
7839
  ...preGuard,
7531
7840
  `${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`,
7532
7841
  ...inner.stmts,
7533
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7842
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7534
7843
  ],
7535
7844
  valueVar: inner.valVar,
7536
7845
  endVar: inner.endVar
@@ -7544,18 +7853,21 @@ function emitSepBy(_p, def, ctx, pos) {
7544
7853
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7545
7854
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7546
7855
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7547
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7856
+ const first = emitFallible(def.parser, ctx, pos, true);
7857
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7548
7858
  const stmts = [
7549
7859
  `${ind(ctx)}const ${arrV} = []`,
7550
7860
  `${ind(ctx)}let ${curV} = ${pos}`,
7551
7861
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7552
7862
  ...firstStmts
7553
7863
  ];
7554
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7555
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7864
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7865
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7866
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7556
7867
  const rr = v(ctx, "_rr");
7557
7868
  return [
7558
7869
  `${ind(ctx)}if (!${nextOkVar}) {`,
7870
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7559
7871
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7560
7872
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7561
7873
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7584,6 +7896,7 @@ function emitSepBy(_p, def, ctx, pos) {
7584
7896
  stmts.push(
7585
7897
  `${ind(ctx)}let ${didV} = false`,
7586
7898
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
7899
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7587
7900
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7588
7901
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7589
7902
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7594,6 +7907,7 @@ function emitSepBy(_p, def, ctx, pos) {
7594
7907
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7595
7908
  ctx.indent++;
7596
7909
  } else {
7910
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7597
7911
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7598
7912
  ctx.indent++;
7599
7913
  stmts.push(
@@ -7622,16 +7936,18 @@ function emitSepBy(_p, def, ctx, pos) {
7622
7936
  );
7623
7937
  sepAtPos = spV;
7624
7938
  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}; `;
7625
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7626
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
7939
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7940
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7941
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7627
7942
  const post = postSepMarks();
7628
7943
  stmts.push(...post.decl);
7629
7944
  const npV = v(ctx, "_np");
7630
7945
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7631
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7946
+ const next = emitFallible(def.parser, ctx, npV, true);
7947
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7632
7948
  stmts.push(
7633
7949
  ...nextStmts,
7634
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7950
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7635
7951
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7636
7952
  `${ind(ctx)}${curV} = ${nextEnd}`
7637
7953
  );
@@ -7640,14 +7956,16 @@ function emitSepBy(_p, def, ctx, pos) {
7640
7956
  const spV = v(ctx, "_sp");
7641
7957
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7642
7958
  sepAtPos = spV;
7643
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7644
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7959
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7960
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7961
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7645
7962
  const npV = v(ctx, "_np");
7646
7963
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7647
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7964
+ const next = emitFallible(def.parser, ctx, npV, true);
7965
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7648
7966
  stmts.push(
7649
7967
  ...nextStmts,
7650
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
7968
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7651
7969
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7652
7970
  `${ind(ctx)}${curV} = ${nextEnd}`
7653
7971
  );
@@ -7661,15 +7979,17 @@ function emitSepBy(_p, def, ctx, pos) {
7661
7979
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7662
7980
  );
7663
7981
  }
7664
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7665
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7982
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7983
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7984
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7666
7985
  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}; ` : "";
7667
7986
  const post = postSepMarks();
7668
7987
  stmts.push(...post.decl);
7669
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
7988
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
7989
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7670
7990
  stmts.push(
7671
7991
  ...nextStmts,
7672
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7992
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7673
7993
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7674
7994
  `${ind(ctx)}${curV} = ${nextEnd}`
7675
7995
  );
@@ -8050,7 +8370,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8050
8370
  const rv = v(ctx, "_rt");
8051
8371
  const vv = v(ctx, "_rtv");
8052
8372
  const ev = v(ctx, "_rte");
8053
- 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 }`;
8373
+ 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 } : {}) }`;
8054
8374
  const stmts = [
8055
8375
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8056
8376
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8131,7 +8451,7 @@ function emitRecover(def, ctx, pos) {
8131
8451
  return { stmts, valueVar: valVar, endVar };
8132
8452
  }
8133
8453
  function emitExpect(def, ctx, pos) {
8134
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8454
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8135
8455
  const ind0 = ind(ctx);
8136
8456
  const errV = v(ctx, "_err");
8137
8457
  const stmts = [
@@ -8143,6 +8463,7 @@ function emitExpect(def, ctx, pos) {
8143
8463
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8144
8464
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8145
8465
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8466
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8146
8467
  `${ind0} ${valVar} = ${errV}`,
8147
8468
  `${ind0} ${endVar} = ${pos}`,
8148
8469
  `${ind0} ${okVar} = true`,
@@ -8153,7 +8474,7 @@ function emitExpect(def, ctx, pos) {
8153
8474
  function emit(p, ctx, pos) {
8154
8475
  const replay = ctx.replayPrefix?.get(p);
8155
8476
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8156
- const dispatch = (emissionPos = pos) => {
8477
+ const dispatch2 = (emissionPos = pos) => {
8157
8478
  const savedRule = ctx.activeCoverageRuleId;
8158
8479
  const rule = ctx.coverage?.plan.rules.get(p);
8159
8480
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8193,14 +8514,14 @@ function emit(p, ctx, pos) {
8193
8514
  ctx.indent = 1;
8194
8515
  ctx.failLabel = "_pfail";
8195
8516
  ctx.recordFail = true;
8196
- const r = dispatch("_pos");
8517
+ const r = dispatch2("_pos");
8197
8518
  ctx.indent = savedIndent;
8198
8519
  ctx.failLabel = savedFailLabel;
8199
8520
  ctx.recordFail = savedRecord;
8200
8521
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8201
8522
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8202
8523
  }
8203
- return instrument(dispatch());
8524
+ return instrument(dispatch2());
8204
8525
  }
8205
8526
  function emitDispatch(p, ctx, pos) {
8206
8527
  const bal = p._balancedAmbient;
@@ -8233,6 +8554,8 @@ function emitDispatch(p, ctx, pos) {
8233
8554
  return emitSeq(def, ctx, pos);
8234
8555
  case "choice":
8235
8556
  return emitChoice(p, def, ctx, pos);
8557
+ case "dispatch":
8558
+ return emitDispatchCombinator(def, ctx, pos);
8236
8559
  case "attempt":
8237
8560
  return emitAttempt(p, def, ctx, pos);
8238
8561
  case "many":
@@ -8453,6 +8776,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8453
8776
  case "sequence":
8454
8777
  case "choice":
8455
8778
  return d.parsers.some((x) => hasNodeDef(x, seen));
8779
+ case "dispatch":
8780
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8456
8781
  case "sepBy":
8457
8782
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8458
8783
  case "scanTo":
@@ -8486,6 +8811,8 @@ function childrenOf2(def) {
8486
8811
  case "sequence":
8487
8812
  case "choice":
8488
8813
  return def.parsers;
8814
+ case "dispatch":
8815
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8489
8816
  case "many":
8490
8817
  case "attempt":
8491
8818
  case "optional":
@@ -8529,6 +8856,7 @@ function childrenOf2(def) {
8529
8856
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8530
8857
  "sequence",
8531
8858
  "choice",
8859
+ "dispatch",
8532
8860
  "many",
8533
8861
  "oneOrMore",
8534
8862
  "optional",
@@ -9353,6 +9681,8 @@ function childrenOf3(def) {
9353
9681
  case "sequence":
9354
9682
  case "choice":
9355
9683
  return def.parsers;
9684
+ case "dispatch":
9685
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9356
9686
  case "many":
9357
9687
  case "oneOrMore":
9358
9688
  case "optional":
@@ -9502,6 +9832,9 @@ function evalRuleMapIR(ir) {
9502
9832
  "keywords",
9503
9833
  "sequence",
9504
9834
  "choice",
9835
+ "dispatch",
9836
+ "when",
9837
+ "otherwise",
9505
9838
  "attempt",
9506
9839
  "many",
9507
9840
  "oneOrMore",
@@ -9535,6 +9868,9 @@ function evalRuleMapIR(ir) {
9535
9868
  keywords,
9536
9869
  sequence,
9537
9870
  choice,
9871
+ dispatch,
9872
+ when,
9873
+ otherwise,
9538
9874
  attempt,
9539
9875
  many,
9540
9876
  oneOrMore,
@@ -9720,6 +10056,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9720
10056
  });
9721
10057
  return `_gch([${items.join(", ")}])`;
9722
10058
  }
10059
+ case "dispatch": {
10060
+ const arms = def.cases.map(
10061
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10062
+ );
10063
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10064
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10065
+ }
9723
10066
  case "many":
9724
10067
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9725
10068
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10205,6 +10548,9 @@ function stripTsFromSource(node2, code) {
10205
10548
  function isWordFactory(v2) {
10206
10549
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10207
10550
  }
10551
+ function isStaticValueEntry(v2) {
10552
+ return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10553
+ }
10208
10554
  function wordFactoryFromArgs(args, scope, code, mfs) {
10209
10555
  const [boundaryOrOptsArg, optsArg] = args;
10210
10556
  if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
@@ -10274,9 +10620,41 @@ function gatedArmParts(e) {
10274
10620
  }
10275
10621
  return gate2 && combinator ? { gate: gate2, combinator } : null;
10276
10622
  }
10623
+ function dispatchArmValue(node2, scope, code, mfs) {
10624
+ if (node2.type === "Identifier") {
10625
+ const value = anyValue(node2, scope, code, mfs);
10626
+ return isDispatchArm(value) ? value : null;
10627
+ }
10628
+ if (node2.type !== "CallExpression") return null;
10629
+ const callee = node2.callee;
10630
+ if (callee.type !== "Identifier") return null;
10631
+ if (callee.name === "otherwise") {
10632
+ const [parserArg] = node2.arguments;
10633
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
10634
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10635
+ if (!isCombinator(parserValue)) return null;
10636
+ return otherwise(parserValue);
10637
+ }
10638
+ if (callee.name === "when") {
10639
+ const [keyArg, parserArg] = node2.arguments;
10640
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10641
+ const key = anyValue(keyArg, scope, code, mfs);
10642
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10643
+ if (!isCombinator(parserValue)) return null;
10644
+ if (typeof key === "string") return when(key, parserValue);
10645
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue);
10646
+ }
10647
+ return null;
10648
+ }
10277
10649
  function isCombinator(v2) {
10278
10650
  return !!v2 && typeof v2 === "object" && "_def" in v2;
10279
10651
  }
10652
+ function isDispatchArm(v2) {
10653
+ if (!v2 || typeof v2 !== "object") return false;
10654
+ const rec = v2;
10655
+ if (rec.kind === "otherwise") return isCombinator(rec.parser);
10656
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && isCombinator(rec.parser);
10657
+ }
10280
10658
  function scopeGet(scope, name, mfs) {
10281
10659
  const entry = scope.get(name);
10282
10660
  if (!entry) return null;
@@ -10533,6 +10911,25 @@ function exprToCombi(node2, scope, code, mfs) {
10533
10911
  return null;
10534
10912
  }
10535
10913
  }
10914
+ if (callee.name === "dispatch") {
10915
+ const [selectorArg, ...armArgs] = node2.arguments;
10916
+ if (!selectorArg || selectorArg.type === "SpreadElement") return null;
10917
+ const selector = anyValue(selectorArg, scope, code, mfs);
10918
+ if (!isCombinator(selector)) return null;
10919
+ const arms = [];
10920
+ for (const arg of armArgs) {
10921
+ if (arg.type === "SpreadElement") return null;
10922
+ const arm = dispatchArmValue(arg, scope, code, mfs);
10923
+ if (!isDispatchArm(arm)) return null;
10924
+ arms.push(arm);
10925
+ }
10926
+ try {
10927
+ const unsafeDispatch = dispatch;
10928
+ return unsafeDispatch(selector, ...arms);
10929
+ } catch {
10930
+ return null;
10931
+ }
10932
+ }
10536
10933
  if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
10537
10934
  if (code === void 0 || mfs === void 0) return null;
10538
10935
  const arms = [];
@@ -10618,6 +11015,10 @@ function anyValue(node2, scope, code, mfs) {
10618
11015
  if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10619
11016
  return entry.combi;
10620
11017
  }
11018
+ if (isStaticValueEntry(entry)) {
11019
+ if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
11020
+ return entry.value;
11021
+ }
10621
11022
  return entry;
10622
11023
  }
10623
11024
  if (node2.type === "MemberExpression") {
@@ -10638,6 +11039,9 @@ function anyValue(node2, scope, code, mfs) {
10638
11039
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10639
11040
  return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
10640
11041
  }
11042
+ if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11043
+ return dispatchArmValue(node2, scope, code, mfs);
11044
+ }
10641
11045
  return exprToCombi(node2, scope, code, mfs);
10642
11046
  }
10643
11047
  return null;
@@ -10696,6 +11100,7 @@ function evalBodyStatements(statements, scope, code) {
10696
11100
  if (val === null) return false;
10697
11101
  const thisDeclMfSrcs = bodyMfs.slice(before);
10698
11102
  if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
11103
+ else if (isDispatchArm(val)) scope.set(name, { value: val, mfSrcs: thisDeclMfSrcs });
10699
11104
  else scope.set(name, val);
10700
11105
  }
10701
11106
  }