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.cjs CHANGED
@@ -55,6 +55,7 @@ __export(index_exports, {
55
55
  createGrammarTraceSink: () => createGrammarTraceSink,
56
56
  createVisitor: () => createVisitor,
57
57
  cstBuildHost: () => cstBuildHost,
58
+ dispatch: () => dispatch,
58
59
  duplicationFindingCount: () => duplicationFindingCount,
59
60
  expect: () => expect,
60
61
  extractCharClasses: () => extractCharClasses,
@@ -87,6 +88,7 @@ __export(index_exports, {
87
88
  oneOrMore: () => oneOrMore,
88
89
  oneOrMoreSep: () => oneOrMoreSep,
89
90
  optional: () => optional,
91
+ otherwise: () => otherwise,
90
92
  parse: () => parse,
91
93
  parseDoc: () => parseDoc,
92
94
  parser: () => parser,
@@ -110,6 +112,7 @@ __export(index_exports, {
110
112
  triviaEntries: () => triviaEntries,
111
113
  triviaKindMask: () => triviaKindMask,
112
114
  walk: () => walk,
115
+ when: () => when,
113
116
  withCtx: () => withCtx,
114
117
  word: () => word
115
118
  });
@@ -194,6 +197,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
194
197
  return d.parsers.every(me);
195
198
  case "choice":
196
199
  return d.parsers.some(me);
200
+ case "dispatch":
201
+ return me(d.selector);
197
202
  case "transform":
198
203
  case "label":
199
204
  case "trivia":
@@ -267,6 +272,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
267
272
  case "keywords":
268
273
  return p._meta.firstSet;
269
274
  // terminals: no refs, cached set is exact
275
+ case "dispatch":
276
+ return fs(d.selector);
270
277
  case "lazy":
271
278
  try {
272
279
  return fs(d.thunk());
@@ -1184,6 +1191,8 @@ function derive(c, seen) {
1184
1191
  return [def.label];
1185
1192
  case "choice":
1186
1193
  return def.parsers.flatMap(deriveExpected2);
1194
+ case "dispatch":
1195
+ return deriveExpected2(def.selector);
1187
1196
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1188
1197
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1189
1198
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1325,6 +1334,7 @@ function choice(...args) {
1325
1334
  const result = parsers[idx].parse(input, pos, ctx);
1326
1335
  if (result.ok) return result;
1327
1336
  expected.push(...result.expected);
1337
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1328
1338
  return { ok: false, expected, span: { start: pos, end: pos } };
1329
1339
  }
1330
1340
  return {
@@ -1365,6 +1375,7 @@ function choice(...args) {
1365
1375
  rollbackCstCapture(ctx, mark);
1366
1376
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1367
1377
  expected.push(...result.expected);
1378
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1368
1379
  continue;
1369
1380
  }
1370
1381
  const checks = autoNot[i];
@@ -2044,6 +2055,75 @@ function sequence(...parsers) {
2044
2055
  };
2045
2056
  }
2046
2057
 
2058
+ // src/combinators/dispatch.ts
2059
+ function when(key, parser2) {
2060
+ const keys = Array.isArray(key) ? [...key] : [key];
2061
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2062
+ for (const item of keys) {
2063
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2064
+ }
2065
+ return { kind: "when", keys, parser: parser2 };
2066
+ }
2067
+ function otherwise(parser2) {
2068
+ return { kind: "otherwise", parser: parser2 };
2069
+ }
2070
+ function dispatch(selector, ...arms) {
2071
+ let fallback;
2072
+ const cases = [];
2073
+ const seen = /* @__PURE__ */ new Set();
2074
+ for (let i = 0; i < arms.length; i++) {
2075
+ const arm = arms[i];
2076
+ if (arm.kind === "otherwise") {
2077
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2078
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2079
+ fallback = arm.parser;
2080
+ continue;
2081
+ }
2082
+ for (const key of arm.keys) {
2083
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2084
+ seen.add(key);
2085
+ }
2086
+ cases.push({ keys: arm.keys, parser: arm.parser });
2087
+ }
2088
+ const meta = {
2089
+ firstSet: selector._meta.firstSet,
2090
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2091
+ isTrivia: false
2092
+ };
2093
+ const byKey = /* @__PURE__ */ new Map();
2094
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2095
+ return {
2096
+ _tag: "dispatch",
2097
+ _meta: meta,
2098
+ _def: {
2099
+ tag: "dispatch",
2100
+ selector,
2101
+ cases,
2102
+ ...fallback === void 0 ? {} : { otherwise: fallback }
2103
+ },
2104
+ parse(input, pos, ctx) {
2105
+ const selected = selector.parse(input, pos, ctx);
2106
+ if (!selected.ok) return selected;
2107
+ const tail = byKey.get(selected.value) ?? fallback;
2108
+ if (tail === void 0) {
2109
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2110
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2111
+ }
2112
+ const mark = saveTriviaMark(ctx);
2113
+ const result = tail.parse(input, selected.span.end, ctx);
2114
+ if (!result.ok) {
2115
+ rollbackTrivia(ctx, mark);
2116
+ return { ...result, committed: true };
2117
+ }
2118
+ return {
2119
+ ok: true,
2120
+ value: [selected.value, result.value],
2121
+ span: { start: pos, end: result.span.end }
2122
+ };
2123
+ }
2124
+ };
2125
+ }
2126
+
2047
2127
  // src/combinators/attempt.ts
2048
2128
  function attempt(parser2) {
2049
2129
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2066,6 +2146,7 @@ function attempt(parser2) {
2066
2146
  if (result.ok) return result;
2067
2147
  rollbackCstCapture(ctx, mark);
2068
2148
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2149
+ if (result.committed) return result;
2069
2150
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2070
2151
  }
2071
2152
  };
@@ -2095,6 +2176,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2095
2176
  const result = combinator.parse(input, pos, ctx);
2096
2177
  if (!result.ok) {
2097
2178
  rollbackTrivia(ctx, mark);
2179
+ if (result.committed) return { fail: result, failPos: pos };
2098
2180
  return { fail: result, failPos: pos };
2099
2181
  }
2100
2182
  if (result.span.end === pos) {
@@ -2138,6 +2220,7 @@ function many(combinator, opts = {}) {
2138
2220
  const item = repItem(combinator, input, cur, ctx, guardable);
2139
2221
  if (item === "stop") break;
2140
2222
  if ("fail" in item) {
2223
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2141
2224
  const sync = ctx._tolerant ? ctx._sync : void 0;
2142
2225
  if (sync === void 0) break;
2143
2226
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2182,6 +2265,7 @@ function atLeast(combinator, min, max) {
2182
2265
  while (count < min) {
2183
2266
  const item = repItem(combinator, input, cur, ctx, guardable);
2184
2267
  if (item === "stop" || "fail" in item) {
2268
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2185
2269
  expected ??= deriveExpected(combinator);
2186
2270
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2187
2271
  }
@@ -2194,6 +2278,7 @@ function atLeast(combinator, min, max) {
2194
2278
  const item = repItem(combinator, input, cur, ctx, guardable);
2195
2279
  if (item === "stop") break;
2196
2280
  if ("fail" in item) {
2281
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2197
2282
  const sync = ctx._tolerant ? ctx._sync : void 0;
2198
2283
  if (sync === void 0) break;
2199
2284
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2232,6 +2317,7 @@ function optional(combinator) {
2232
2317
  const result = combinator.parse(input, pos, ctx);
2233
2318
  if (result.ok) return result;
2234
2319
  rollbackTrivia(ctx, mark);
2320
+ if (result.committed) return result;
2235
2321
  return { ok: true, value: null, span: { start: pos, end: pos } };
2236
2322
  }
2237
2323
  };
@@ -2271,6 +2357,7 @@ function sepBy(combinator, separator, opts = {}) {
2271
2357
  values.push(first.value);
2272
2358
  cur = first.span.end;
2273
2359
  } else {
2360
+ if (first.committed) return first;
2274
2361
  const term = ctx._tolerant ? ctx._sync : void 0;
2275
2362
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2276
2363
  if (min >= 1) return failAt2(pos);
@@ -2298,6 +2385,7 @@ function sepBy(combinator, separator, opts = {}) {
2298
2385
  const sep = separator.parse(input, sepPos, ctx);
2299
2386
  if (!sep.ok) {
2300
2387
  rollbackTrivia(ctx, loopMark);
2388
+ if (sep.committed) return sep;
2301
2389
  break;
2302
2390
  }
2303
2391
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2313,6 +2401,7 @@ function sepBy(combinator, separator, opts = {}) {
2313
2401
  }
2314
2402
  const next = combinator.parse(input, nextPos, ctx);
2315
2403
  if (!next.ok) {
2404
+ if (next.committed) return next;
2316
2405
  const term = ctx._tolerant ? ctx._sync : void 0;
2317
2406
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2318
2407
  expected ??= deriveExpected(combinator);
@@ -2405,6 +2494,11 @@ function markUnusedValues(root) {
2405
2494
  case "choice":
2406
2495
  for (const p of def.parsers) visit(p, consumed);
2407
2496
  return;
2497
+ case "dispatch":
2498
+ visit(def.selector, consumed);
2499
+ for (const entry of def.cases) visit(entry.parser, consumed);
2500
+ if (def.otherwise) visit(def.otherwise, consumed);
2501
+ return;
2408
2502
  case "node":
2409
2503
  visit(def.parser, false);
2410
2504
  return;
@@ -2680,6 +2774,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2680
2774
  case "sequence":
2681
2775
  case "choice":
2682
2776
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2777
+ case "dispatch":
2778
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2683
2779
  case "sepBy":
2684
2780
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2685
2781
  case "skip":
@@ -2747,6 +2843,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2747
2843
  return parserHasTriviaSite(d.parser, seen);
2748
2844
  case "choice":
2749
2845
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2846
+ case "dispatch":
2847
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2750
2848
  case "grammar":
2751
2849
  return parserHasTriviaSite(d.parser, seen);
2752
2850
  case "lazy": {
@@ -2772,6 +2870,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2772
2870
  case "sequence":
2773
2871
  case "choice":
2774
2872
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2873
+ case "dispatch":
2874
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2775
2875
  case "sepBy":
2776
2876
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2777
2877
  case "skip":
@@ -3173,7 +3273,7 @@ function leaf(root, fn) {
3173
3273
  }
3174
3274
 
3175
3275
  // src/version.ts
3176
- var PARSEMAN_VERSION = "0.38.0";
3276
+ var PARSEMAN_VERSION = "0.39.0";
3177
3277
 
3178
3278
  // src/cst/host-mode.ts
3179
3279
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3329,6 +3429,8 @@ function classifyBroadArm(arm, resolve) {
3329
3429
  }
3330
3430
  return null;
3331
3431
  }
3432
+ case "dispatch":
3433
+ return walk2(d.selector);
3332
3434
  default:
3333
3435
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3334
3436
  }
@@ -3428,6 +3530,11 @@ function analyzeGatingRules(ruleMap, opts) {
3428
3530
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3429
3531
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3430
3532
  if (rec[k]) kids.push(rec[k]);
3533
+ if (d.tag === "dispatch") {
3534
+ kids.push(d.selector);
3535
+ for (const c of d.cases) kids.push(c.parser);
3536
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3537
+ }
3431
3538
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3432
3539
  if (d.tag === "lazy") {
3433
3540
  try {
@@ -3534,6 +3641,12 @@ function childrenOf(d) {
3534
3641
  case "sequence":
3535
3642
  case "choice":
3536
3643
  return d.parsers;
3644
+ case "dispatch":
3645
+ return [
3646
+ d.selector,
3647
+ ...d.cases.map((c) => c.parser),
3648
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3649
+ ];
3537
3650
  case "skip":
3538
3651
  return [d.main, d.skipped];
3539
3652
  case "sepBy":
@@ -3590,6 +3703,8 @@ function payloadKey(p, d) {
3590
3703
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3591
3704
  case "sepBy":
3592
3705
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3706
+ case "dispatch":
3707
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3593
3708
  case "expect":
3594
3709
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3595
3710
  case "scanTo":
@@ -3661,6 +3776,11 @@ function render(p, depth = 3) {
3661
3776
  return `sequence(${kids.join(", ")})`;
3662
3777
  case "choice":
3663
3778
  return `choice(${kids.join(", ")})`;
3779
+ case "dispatch": {
3780
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3781
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3782
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3783
+ }
3664
3784
  case "node":
3665
3785
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3666
3786
  case "label":
@@ -3684,6 +3804,10 @@ function segment(d, index) {
3684
3804
  return `seq[${index}]`;
3685
3805
  case "choice":
3686
3806
  return `choice[${index}]`;
3807
+ case "dispatch":
3808
+ if (index === 0) return "dispatch.selector";
3809
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3810
+ return "dispatch.otherwise";
3687
3811
  case "node":
3688
3812
  return `node(${d.type ?? ""})`;
3689
3813
  case "label":
@@ -4937,6 +5061,8 @@ function children(def, winners) {
4937
5061
  case "sequence":
4938
5062
  case "choice":
4939
5063
  return def.parsers;
5064
+ case "dispatch":
5065
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4940
5066
  case "many":
4941
5067
  case "oneOrMore":
4942
5068
  case "optional":
@@ -6401,6 +6527,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6401
6527
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6402
6528
  return { alts };
6403
6529
  }
6530
+ case "dispatch":
6531
+ return rec(d.selector);
6404
6532
  case "sequence": {
6405
6533
  const chain = [];
6406
6534
  for (const term of d.parsers) {
@@ -6475,6 +6603,9 @@ function failReturn(expected, posExpr) {
6475
6603
  function failReturnArr(expectedArr, posExpr) {
6476
6604
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6477
6605
  }
6606
+ function committedReturnArr(expectedArr, posExpr) {
6607
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6608
+ }
6478
6609
  function hoistExpected(ctx, constArrSource) {
6479
6610
  let name = ctx.expectedMap.get(constArrSource);
6480
6611
  if (name === void 0) {
@@ -6511,15 +6642,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6511
6642
  return probe + trace + failReturnArr(expectedArr, posExpr);
6512
6643
  }
6513
6644
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6514
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6645
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6646
+ }
6647
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6648
+ if (ctx.failLabel) {
6649
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6650
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6651
+ }
6652
+ return committedReturnArr(expectedArr, posExpr);
6515
6653
  }
6516
6654
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6517
6655
  if (srcCtx !== "_ctx") {
6518
6656
  if (ctx.failLabel) {
6519
6657
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6520
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6658
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6521
6659
  }
6522
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6660
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6523
6661
  }
6524
6662
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6525
6663
  return resultFromRecorded();
@@ -6732,6 +6870,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6732
6870
  // nothing committed remains.
6733
6871
  case "choice":
6734
6872
  return false;
6873
+ case "dispatch":
6874
+ 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);
6735
6875
  case "attempt":
6736
6876
  return false;
6737
6877
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6776,6 +6916,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6776
6916
  return true;
6777
6917
  }
6778
6918
  }
6919
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6920
+ if (seen.has(p)) return false;
6921
+ seen.add(p);
6922
+ const d = p._def;
6923
+ switch (d.tag) {
6924
+ case "dispatch":
6925
+ return true;
6926
+ case "choice":
6927
+ case "sequence":
6928
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6929
+ case "many":
6930
+ case "oneOrMore":
6931
+ case "optional":
6932
+ case "attempt":
6933
+ case "transform":
6934
+ case "label":
6935
+ case "field":
6936
+ case "grammar":
6937
+ case "node":
6938
+ return mayCommitFailure(d.parser, seen);
6939
+ case "sepBy":
6940
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6941
+ case "skip":
6942
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6943
+ case "token":
6944
+ case "leaf":
6945
+ return mayCommitFailure(d.parser, seen);
6946
+ case "withCtx":
6947
+ return mayCommitFailure(d.parser, seen);
6948
+ case "scanTo":
6949
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6950
+ case "lazy": {
6951
+ try {
6952
+ return mayCommitFailure(d.thunk(), seen);
6953
+ } catch {
6954
+ return true;
6955
+ }
6956
+ }
6957
+ case "unknown":
6958
+ return true;
6959
+ default:
6960
+ return false;
6961
+ }
6962
+ }
6779
6963
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6780
6964
  if (seen.has(p)) return false;
6781
6965
  seen.add(p);
@@ -6797,6 +6981,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6797
6981
  case "sequence":
6798
6982
  case "choice":
6799
6983
  return d.parsers.some((x) => capturesLeaf(x, seen));
6984
+ case "dispatch":
6985
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6800
6986
  case "sepBy":
6801
6987
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6802
6988
  case "many":
@@ -6843,6 +7029,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6843
7029
  ctx.recordFail = savedRecord;
6844
7030
  const ind0 = ind(ctx);
6845
7031
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7032
+ const mayCommit = mayCommitFailure(inner);
6846
7033
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6847
7034
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6848
7035
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6856,6 +7043,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6856
7043
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6857
7044
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6858
7045
  ] : [],
7046
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6859
7047
  `${ind0}${lbl}: {`,
6860
7048
  ...r.stmts,
6861
7049
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6864,7 +7052,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6864
7052
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6865
7053
  ] : []
6866
7054
  ];
6867
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7055
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6868
7056
  }
6869
7057
  var CHARCODE_CHAIN_MAX = 16;
6870
7058
  function emitLit(def, ctx, pos) {
@@ -7241,6 +7429,118 @@ function emitChoice(parser2, def, ctx, pos) {
7241
7429
  }
7242
7430
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7243
7431
  }
7432
+ function emitTailFallible(parser2, ctx, pos) {
7433
+ const savedRecord = ctx.recordFail;
7434
+ ctx.recordFail = true;
7435
+ const out = emitFallible(parser2, ctx, pos);
7436
+ ctx.recordFail = savedRecord;
7437
+ return out;
7438
+ }
7439
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7440
+ if (seen.has(p)) return false;
7441
+ seen.add(p);
7442
+ const d = p._def;
7443
+ switch (d.tag) {
7444
+ case "expect":
7445
+ case "recover":
7446
+ return true;
7447
+ case "many":
7448
+ case "oneOrMore":
7449
+ case "sepBy":
7450
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7451
+ case "choice":
7452
+ case "sequence": {
7453
+ for (const item of d.parsers) {
7454
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7455
+ }
7456
+ return false;
7457
+ }
7458
+ case "dispatch": {
7459
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7460
+ for (const item of d.cases) {
7461
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7462
+ }
7463
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7464
+ }
7465
+ case "attempt":
7466
+ case "transform":
7467
+ case "label":
7468
+ case "field":
7469
+ case "grammar":
7470
+ case "node":
7471
+ case "token":
7472
+ case "leaf":
7473
+ case "withCtx":
7474
+ case "optional":
7475
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7476
+ case "skip":
7477
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7478
+ case "scanTo":
7479
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7480
+ for (const item of d.skip) {
7481
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7482
+ }
7483
+ return false;
7484
+ case "lazy": {
7485
+ try {
7486
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7487
+ } catch {
7488
+ return true;
7489
+ }
7490
+ }
7491
+ case "unknown":
7492
+ return true;
7493
+ default:
7494
+ return false;
7495
+ }
7496
+ }
7497
+ function emitDispatchCombinator(def, ctx, pos) {
7498
+ const selector = emit(def.selector, ctx, pos);
7499
+ const outV = v(ctx, "_dval");
7500
+ const outE = v(ctx, "_dend");
7501
+ const keyV = v(ctx, "_dkey");
7502
+ const stmts = [
7503
+ ...selector.stmts,
7504
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7505
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7506
+ ];
7507
+ let wroteBranch = false;
7508
+ const emitSelectedTail = (parser2, keyword, condition) => {
7509
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7510
+ stmts.push(head);
7511
+ ctx.indent++;
7512
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7513
+ const errMark = mayError ? v(ctx, "_derr") : null;
7514
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7515
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7516
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7517
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7518
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7519
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7520
+ stmts.push(
7521
+ ...tail.stmts,
7522
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7523
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7524
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7525
+ );
7526
+ ctx.indent--;
7527
+ stmts.push(`${ind(ctx)}}`);
7528
+ };
7529
+ for (const entry of def.cases) {
7530
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7531
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7532
+ wroteBranch = true;
7533
+ }
7534
+ if (def.otherwise) {
7535
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7536
+ else emitSelectedTail(def.otherwise, "if", "true");
7537
+ } else {
7538
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7539
+ const fail = failArrBody(ctx, expected, selector.endVar);
7540
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7541
+ }
7542
+ return { stmts, valueVar: outV, endVar: outE };
7543
+ }
7244
7544
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7245
7545
  const superParser = def.parsers[superIndex];
7246
7546
  const regexDef = getCoreRegexDef(superParser);
@@ -7362,12 +7662,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7362
7662
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7363
7663
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7364
7664
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7665
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7365
7666
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7366
7667
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7367
7668
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7368
7669
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7369
7670
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7370
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7671
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7672
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7673
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7674
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7371
7675
  const failSlot = atStart ? staticFx : "_ctx._fx";
7372
7676
  stmts.push(`${ind0}if (${skipCond}) {`);
7373
7677
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7382,7 +7686,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7382
7686
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7383
7687
  );
7384
7688
  }
7385
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7689
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7690
+ const arm = emitFallible(p, ctx, pos, atStart);
7691
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7386
7692
  stmts.push(...armStmts);
7387
7693
  if (autoNot && autoNot.length > 0) {
7388
7694
  const anCode = v(ctx, "_anc");
@@ -7406,10 +7712,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7406
7712
  stmts.push(`${ind(ctx)}}`);
7407
7713
  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} });`} }`);
7408
7714
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7715
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7409
7716
  } else {
7410
7717
  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} }) }`;
7411
7718
  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} })`} }`);
7412
7719
  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} })`} }`);
7720
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7413
7721
  }
7414
7722
  ctx.indent -= fsGuard ? 2 : 1;
7415
7723
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7566,7 +7874,8 @@ function emitMany(def, ctx, pos) {
7566
7874
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7567
7875
  );
7568
7876
  }
7569
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7877
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7878
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7570
7879
  stmts.push(...iterStmts);
7571
7880
  if (ctx.recovery) {
7572
7881
  const rrV = v(ctx, "_rr");
@@ -7585,7 +7894,8 @@ function emitMany(def, ctx, pos) {
7585
7894
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7586
7895
  );
7587
7896
  } else {
7588
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7897
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7898
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7589
7899
  }
7590
7900
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7591
7901
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7597,10 +7907,12 @@ function emitMany(def, ctx, pos) {
7597
7907
  function emitOptional(def, ctx, pos) {
7598
7908
  const valV = v(ctx, "_opt");
7599
7909
  const endV = v(ctx, "_opte");
7600
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7910
+ const inner = emitFallible(def.parser, ctx, pos, true);
7911
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7601
7912
  const ind0 = ind(ctx);
7602
7913
  const stmts = [
7603
7914
  ...lblStmts,
7915
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7604
7916
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7605
7917
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7606
7918
  ];
@@ -7632,7 +7944,7 @@ function emitAttempt(p, def, ctx, pos) {
7632
7944
  ...preGuard,
7633
7945
  `${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`,
7634
7946
  ...inner.stmts,
7635
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7947
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7636
7948
  ],
7637
7949
  valueVar: inner.valVar,
7638
7950
  endVar: inner.endVar
@@ -7646,18 +7958,21 @@ function emitSepBy(_p, def, ctx, pos) {
7646
7958
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7647
7959
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7648
7960
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7649
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7961
+ const first = emitFallible(def.parser, ctx, pos, true);
7962
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7650
7963
  const stmts = [
7651
7964
  `${ind(ctx)}const ${arrV} = []`,
7652
7965
  `${ind(ctx)}let ${curV} = ${pos}`,
7653
7966
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7654
7967
  ...firstStmts
7655
7968
  ];
7656
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7657
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7969
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7970
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7971
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7658
7972
  const rr = v(ctx, "_rr");
7659
7973
  return [
7660
7974
  `${ind(ctx)}if (!${nextOkVar}) {`,
7975
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7661
7976
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7662
7977
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7663
7978
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7686,6 +8001,7 @@ function emitSepBy(_p, def, ctx, pos) {
7686
8001
  stmts.push(
7687
8002
  `${ind(ctx)}let ${didV} = false`,
7688
8003
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8004
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7689
8005
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7690
8006
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7691
8007
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7696,6 +8012,7 @@ function emitSepBy(_p, def, ctx, pos) {
7696
8012
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7697
8013
  ctx.indent++;
7698
8014
  } else {
8015
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7699
8016
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7700
8017
  ctx.indent++;
7701
8018
  stmts.push(
@@ -7724,16 +8041,18 @@ function emitSepBy(_p, def, ctx, pos) {
7724
8041
  );
7725
8042
  sepAtPos = spV;
7726
8043
  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}; `;
7727
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7728
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
8044
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8045
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8046
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7729
8047
  const post = postSepMarks();
7730
8048
  stmts.push(...post.decl);
7731
8049
  const npV = v(ctx, "_np");
7732
8050
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7733
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8051
+ const next = emitFallible(def.parser, ctx, npV, true);
8052
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7734
8053
  stmts.push(
7735
8054
  ...nextStmts,
7736
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8055
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7737
8056
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7738
8057
  `${ind(ctx)}${curV} = ${nextEnd}`
7739
8058
  );
@@ -7742,14 +8061,16 @@ function emitSepBy(_p, def, ctx, pos) {
7742
8061
  const spV = v(ctx, "_sp");
7743
8062
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7744
8063
  sepAtPos = spV;
7745
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7746
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8064
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8065
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8066
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7747
8067
  const npV = v(ctx, "_np");
7748
8068
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7749
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8069
+ const next = emitFallible(def.parser, ctx, npV, true);
8070
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7750
8071
  stmts.push(
7751
8072
  ...nextStmts,
7752
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8073
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7753
8074
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7754
8075
  `${ind(ctx)}${curV} = ${nextEnd}`
7755
8076
  );
@@ -7763,15 +8084,17 @@ function emitSepBy(_p, def, ctx, pos) {
7763
8084
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7764
8085
  );
7765
8086
  }
7766
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7767
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8087
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8088
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8089
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7768
8090
  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}; ` : "";
7769
8091
  const post = postSepMarks();
7770
8092
  stmts.push(...post.decl);
7771
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
8093
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
8094
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7772
8095
  stmts.push(
7773
8096
  ...nextStmts,
7774
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8097
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7775
8098
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7776
8099
  `${ind(ctx)}${curV} = ${nextEnd}`
7777
8100
  );
@@ -8152,7 +8475,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8152
8475
  const rv = v(ctx, "_rt");
8153
8476
  const vv = v(ctx, "_rtv");
8154
8477
  const ev = v(ctx, "_rte");
8155
- 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 }`;
8478
+ 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 } : {}) }`;
8156
8479
  const stmts = [
8157
8480
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8158
8481
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8233,7 +8556,7 @@ function emitRecover(def, ctx, pos) {
8233
8556
  return { stmts, valueVar: valVar, endVar };
8234
8557
  }
8235
8558
  function emitExpect(def, ctx, pos) {
8236
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8559
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8237
8560
  const ind0 = ind(ctx);
8238
8561
  const errV = v(ctx, "_err");
8239
8562
  const stmts = [
@@ -8245,6 +8568,7 @@ function emitExpect(def, ctx, pos) {
8245
8568
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8246
8569
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8247
8570
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8571
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8248
8572
  `${ind0} ${valVar} = ${errV}`,
8249
8573
  `${ind0} ${endVar} = ${pos}`,
8250
8574
  `${ind0} ${okVar} = true`,
@@ -8255,7 +8579,7 @@ function emitExpect(def, ctx, pos) {
8255
8579
  function emit(p, ctx, pos) {
8256
8580
  const replay = ctx.replayPrefix?.get(p);
8257
8581
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8258
- const dispatch = (emissionPos = pos) => {
8582
+ const dispatch2 = (emissionPos = pos) => {
8259
8583
  const savedRule = ctx.activeCoverageRuleId;
8260
8584
  const rule = ctx.coverage?.plan.rules.get(p);
8261
8585
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8295,14 +8619,14 @@ function emit(p, ctx, pos) {
8295
8619
  ctx.indent = 1;
8296
8620
  ctx.failLabel = "_pfail";
8297
8621
  ctx.recordFail = true;
8298
- const r = dispatch("_pos");
8622
+ const r = dispatch2("_pos");
8299
8623
  ctx.indent = savedIndent;
8300
8624
  ctx.failLabel = savedFailLabel;
8301
8625
  ctx.recordFail = savedRecord;
8302
8626
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8303
8627
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8304
8628
  }
8305
- return instrument(dispatch());
8629
+ return instrument(dispatch2());
8306
8630
  }
8307
8631
  function emitDispatch(p, ctx, pos) {
8308
8632
  const bal = p._balancedAmbient;
@@ -8335,6 +8659,8 @@ function emitDispatch(p, ctx, pos) {
8335
8659
  return emitSeq(def, ctx, pos);
8336
8660
  case "choice":
8337
8661
  return emitChoice(p, def, ctx, pos);
8662
+ case "dispatch":
8663
+ return emitDispatchCombinator(def, ctx, pos);
8338
8664
  case "attempt":
8339
8665
  return emitAttempt(p, def, ctx, pos);
8340
8666
  case "many":
@@ -8555,6 +8881,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8555
8881
  case "sequence":
8556
8882
  case "choice":
8557
8883
  return d.parsers.some((x) => hasNodeDef(x, seen));
8884
+ case "dispatch":
8885
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8558
8886
  case "sepBy":
8559
8887
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8560
8888
  case "scanTo":
@@ -8588,6 +8916,8 @@ function childrenOf2(def) {
8588
8916
  case "sequence":
8589
8917
  case "choice":
8590
8918
  return def.parsers;
8919
+ case "dispatch":
8920
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8591
8921
  case "many":
8592
8922
  case "attempt":
8593
8923
  case "optional":
@@ -8631,6 +8961,7 @@ function childrenOf2(def) {
8631
8961
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8632
8962
  "sequence",
8633
8963
  "choice",
8964
+ "dispatch",
8634
8965
  "many",
8635
8966
  "oneOrMore",
8636
8967
  "optional",
@@ -9329,6 +9660,8 @@ function childrenOf3(def) {
9329
9660
  case "sequence":
9330
9661
  case "choice":
9331
9662
  return def.parsers;
9663
+ case "dispatch":
9664
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9332
9665
  case "many":
9333
9666
  case "oneOrMore":
9334
9667
  case "optional":
@@ -9478,6 +9811,9 @@ function evalRuleMapIR(ir) {
9478
9811
  "keywords",
9479
9812
  "sequence",
9480
9813
  "choice",
9814
+ "dispatch",
9815
+ "when",
9816
+ "otherwise",
9481
9817
  "attempt",
9482
9818
  "many",
9483
9819
  "oneOrMore",
@@ -9511,6 +9847,9 @@ function evalRuleMapIR(ir) {
9511
9847
  keywords,
9512
9848
  sequence,
9513
9849
  choice,
9850
+ dispatch,
9851
+ when,
9852
+ otherwise,
9514
9853
  attempt,
9515
9854
  many,
9516
9855
  oneOrMore,
@@ -9696,6 +10035,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9696
10035
  });
9697
10036
  return `_gch([${items.join(", ")}])`;
9698
10037
  }
10038
+ case "dispatch": {
10039
+ const arms = def.cases.map(
10040
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10041
+ );
10042
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10043
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10044
+ }
9699
10045
  case "many":
9700
10046
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9701
10047
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10915,6 +11261,12 @@ function coverageEntry(entry, collector, maps, trace) {
10915
11261
  const unsafeSequence = sequence;
10916
11262
  return unsafeSequence(...def.parsers.map(build));
10917
11263
  }
11264
+ case "dispatch": {
11265
+ const arms = def.cases.map((entry2) => when(entry2.keys, build(entry2.parser)));
11266
+ if (def.otherwise) arms.push(otherwise(build(def.otherwise)));
11267
+ const unsafeDispatch = dispatch;
11268
+ return unsafeDispatch(build(def.selector), ...arms);
11269
+ }
10918
11270
  case "many":
10919
11271
  return many(build(def.parser));
10920
11272
  case "oneOrMore":