parseman 0.37.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 (41) 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/keywords.d.ts +9 -8
  10. package/dist/combinators/keywords.d.ts.map +1 -1
  11. package/dist/combinators/parser.d.ts.map +1 -1
  12. package/dist/combinators/repeat.d.ts.map +1 -1
  13. package/dist/compiler/codegen.d.ts.map +1 -1
  14. package/dist/compiler/fields.d.ts.map +1 -1
  15. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  16. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  17. package/dist/compiler/value-usage.d.ts.map +1 -1
  18. package/dist/coverage.d.ts.map +1 -1
  19. package/dist/index.cjs +432 -53
  20. package/dist/index.cjs.map +4 -4
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +432 -53
  24. package/dist/index.js.map +4 -4
  25. package/dist/language-service/index.cjs.map +2 -2
  26. package/dist/language-service/index.js.map +2 -2
  27. package/dist/plugin/evaluator.d.ts +2 -1
  28. package/dist/plugin/evaluator.d.ts.map +1 -1
  29. package/dist/plugin/index.cjs +517 -67
  30. package/dist/plugin/index.cjs.map +4 -4
  31. package/dist/plugin/index.js +517 -67
  32. package/dist/plugin/index.js.map +4 -4
  33. package/dist/spec/index.cjs +133 -6
  34. package/dist/spec/index.cjs.map +4 -4
  35. package/dist/spec/index.js +133 -6
  36. package/dist/spec/index.js.map +4 -4
  37. package/dist/spec/model.d.ts.map +1 -1
  38. package/dist/types.d.ts +17 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. 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());
@@ -1036,8 +1043,10 @@ function word(str, boundaryOrOpts, opts) {
1036
1043
  const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
1037
1044
  return keywords([str], { ...rest, boundary });
1038
1045
  }
1039
- function makeWord(boundary = "_0-9A-Za-z") {
1040
- return (str) => keywords([str], { boundary });
1046
+ function makeWord(boundaryOrOpts = "_0-9A-Za-z", opts) {
1047
+ const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
1048
+ const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
1049
+ return (str) => keywords([str], { ...rest, boundary });
1041
1050
  }
1042
1051
  function escapeRe(s) {
1043
1052
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1182,6 +1191,8 @@ function derive(c, seen) {
1182
1191
  return [def.label];
1183
1192
  case "choice":
1184
1193
  return def.parsers.flatMap(deriveExpected2);
1194
+ case "dispatch":
1195
+ return deriveExpected2(def.selector);
1185
1196
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1186
1197
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1187
1198
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1323,6 +1334,7 @@ function choice(...args) {
1323
1334
  const result = parsers[idx].parse(input, pos, ctx);
1324
1335
  if (result.ok) return result;
1325
1336
  expected.push(...result.expected);
1337
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1326
1338
  return { ok: false, expected, span: { start: pos, end: pos } };
1327
1339
  }
1328
1340
  return {
@@ -1363,6 +1375,7 @@ function choice(...args) {
1363
1375
  rollbackCstCapture(ctx, mark);
1364
1376
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1365
1377
  expected.push(...result.expected);
1378
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1366
1379
  continue;
1367
1380
  }
1368
1381
  const checks = autoNot[i];
@@ -2042,6 +2055,75 @@ function sequence(...parsers) {
2042
2055
  };
2043
2056
  }
2044
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
+
2045
2127
  // src/combinators/attempt.ts
2046
2128
  function attempt(parser2) {
2047
2129
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2064,6 +2146,7 @@ function attempt(parser2) {
2064
2146
  if (result.ok) return result;
2065
2147
  rollbackCstCapture(ctx, mark);
2066
2148
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2149
+ if (result.committed) return result;
2067
2150
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2068
2151
  }
2069
2152
  };
@@ -2093,6 +2176,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2093
2176
  const result = combinator.parse(input, pos, ctx);
2094
2177
  if (!result.ok) {
2095
2178
  rollbackTrivia(ctx, mark);
2179
+ if (result.committed) return { fail: result, failPos: pos };
2096
2180
  return { fail: result, failPos: pos };
2097
2181
  }
2098
2182
  if (result.span.end === pos) {
@@ -2136,6 +2220,7 @@ function many(combinator, opts = {}) {
2136
2220
  const item = repItem(combinator, input, cur, ctx, guardable);
2137
2221
  if (item === "stop") break;
2138
2222
  if ("fail" in item) {
2223
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2139
2224
  const sync = ctx._tolerant ? ctx._sync : void 0;
2140
2225
  if (sync === void 0) break;
2141
2226
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2180,6 +2265,7 @@ function atLeast(combinator, min, max) {
2180
2265
  while (count < min) {
2181
2266
  const item = repItem(combinator, input, cur, ctx, guardable);
2182
2267
  if (item === "stop" || "fail" in item) {
2268
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2183
2269
  expected ??= deriveExpected(combinator);
2184
2270
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2185
2271
  }
@@ -2192,6 +2278,7 @@ function atLeast(combinator, min, max) {
2192
2278
  const item = repItem(combinator, input, cur, ctx, guardable);
2193
2279
  if (item === "stop") break;
2194
2280
  if ("fail" in item) {
2281
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2195
2282
  const sync = ctx._tolerant ? ctx._sync : void 0;
2196
2283
  if (sync === void 0) break;
2197
2284
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2230,6 +2317,7 @@ function optional(combinator) {
2230
2317
  const result = combinator.parse(input, pos, ctx);
2231
2318
  if (result.ok) return result;
2232
2319
  rollbackTrivia(ctx, mark);
2320
+ if (result.committed) return result;
2233
2321
  return { ok: true, value: null, span: { start: pos, end: pos } };
2234
2322
  }
2235
2323
  };
@@ -2269,6 +2357,7 @@ function sepBy(combinator, separator, opts = {}) {
2269
2357
  values.push(first.value);
2270
2358
  cur = first.span.end;
2271
2359
  } else {
2360
+ if (first.committed) return first;
2272
2361
  const term = ctx._tolerant ? ctx._sync : void 0;
2273
2362
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2274
2363
  if (min >= 1) return failAt2(pos);
@@ -2296,6 +2385,7 @@ function sepBy(combinator, separator, opts = {}) {
2296
2385
  const sep = separator.parse(input, sepPos, ctx);
2297
2386
  if (!sep.ok) {
2298
2387
  rollbackTrivia(ctx, loopMark);
2388
+ if (sep.committed) return sep;
2299
2389
  break;
2300
2390
  }
2301
2391
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2311,6 +2401,7 @@ function sepBy(combinator, separator, opts = {}) {
2311
2401
  }
2312
2402
  const next = combinator.parse(input, nextPos, ctx);
2313
2403
  if (!next.ok) {
2404
+ if (next.committed) return next;
2314
2405
  const term = ctx._tolerant ? ctx._sync : void 0;
2315
2406
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2316
2407
  expected ??= deriveExpected(combinator);
@@ -2403,6 +2494,11 @@ function markUnusedValues(root) {
2403
2494
  case "choice":
2404
2495
  for (const p of def.parsers) visit(p, consumed);
2405
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;
2406
2502
  case "node":
2407
2503
  visit(def.parser, false);
2408
2504
  return;
@@ -2474,6 +2570,16 @@ function tagRule(r, key) {
2474
2570
  r._ruleName = key;
2475
2571
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
2476
2572
  }
2573
+ function isDefinableRef(v2) {
2574
+ return !!v2 && typeof v2 === "object" && "_def" in v2 && v2._def.tag === "lazy" && typeof v2.define === "function";
2575
+ }
2576
+ function ruleNameOf(r) {
2577
+ return r._ruleName;
2578
+ }
2579
+ function isNamedRuleRefForAnotherRule(r, key) {
2580
+ const name = r._def.tag === "lazy" ? ruleNameOf(r) : void 0;
2581
+ return name !== void 0 && name !== key;
2582
+ }
2477
2583
  function rules(a, b) {
2478
2584
  const factory = typeof a === "function" ? a : b;
2479
2585
  const options = typeof a === "function" ? b : a;
@@ -2494,11 +2600,21 @@ function rules(a, b) {
2494
2600
  for (const key of Object.keys(definitions)) {
2495
2601
  const placeholder = cache[key];
2496
2602
  const parser2 = definitions[key];
2497
- if (placeholder !== void 0 && typeof placeholder.define === "function") {
2498
- tagRule(parser2, key);
2603
+ if (placeholder === parser2) {
2604
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`);
2605
+ }
2606
+ if (isDefinableRef(placeholder)) {
2607
+ if (!isNamedRuleRefForAnotherRule(parser2, key)) tagRule(parser2, key);
2499
2608
  placeholder.define(parser2);
2500
2609
  placeholder._meta.firstSet = parser2._meta.firstSet;
2501
2610
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
2611
+ } else if (isNamedRuleRefForAnotherRule(parser2, key)) {
2612
+ const alias = ref();
2613
+ tagRule(alias, key);
2614
+ alias.define(parser2);
2615
+ alias._meta.firstSet = parser2._meta.firstSet;
2616
+ alias._meta.canMatchNewline = parser2._meta.canMatchNewline;
2617
+ cache[key] = alias;
2502
2618
  } else {
2503
2619
  tagRule(parser2, key);
2504
2620
  cache[key] = parser2;
@@ -2658,6 +2774,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2658
2774
  case "sequence":
2659
2775
  case "choice":
2660
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);
2661
2779
  case "sepBy":
2662
2780
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2663
2781
  case "skip":
@@ -2725,6 +2843,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2725
2843
  return parserHasTriviaSite(d.parser, seen);
2726
2844
  case "choice":
2727
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);
2728
2848
  case "grammar":
2729
2849
  return parserHasTriviaSite(d.parser, seen);
2730
2850
  case "lazy": {
@@ -2750,6 +2870,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2750
2870
  case "sequence":
2751
2871
  case "choice":
2752
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);
2753
2875
  case "sepBy":
2754
2876
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2755
2877
  case "skip":
@@ -3151,7 +3273,7 @@ function leaf(root, fn) {
3151
3273
  }
3152
3274
 
3153
3275
  // src/version.ts
3154
- var PARSEMAN_VERSION = "0.37.0";
3276
+ var PARSEMAN_VERSION = "0.39.0";
3155
3277
 
3156
3278
  // src/cst/host-mode.ts
3157
3279
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3205,7 +3327,7 @@ function firstSetToString(fs) {
3205
3327
  if (fs.kind === "empty") return "(empty)";
3206
3328
  return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3207
3329
  }
3208
- var ruleNameOf = (p) => p._ruleName;
3330
+ var ruleNameOf2 = (p) => p._ruleName;
3209
3331
  var SUGGESTIONS = {
3210
3332
  "broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
3211
3333
  "leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating \u2014 use peek(X), which is zero-width AND carries X first-set.",
@@ -3307,6 +3429,8 @@ function classifyBroadArm(arm, resolve) {
3307
3429
  }
3308
3430
  return null;
3309
3431
  }
3432
+ case "dispatch":
3433
+ return walk2(d.selector);
3310
3434
  default:
3311
3435
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3312
3436
  }
@@ -3363,7 +3487,7 @@ function detectAntiPatterns(rule, arms) {
3363
3487
  return out;
3364
3488
  }
3365
3489
  function analyzeGating(entry, opts) {
3366
- return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3490
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3367
3491
  }
3368
3492
  function analyzeGatingRules(ruleMap, opts) {
3369
3493
  const raw = [];
@@ -3396,7 +3520,7 @@ function analyzeGatingRules(ruleMap, opts) {
3396
3520
  return;
3397
3521
  }
3398
3522
  const d = def;
3399
- const rule = ruleNameOf(p) ?? enclosingRule;
3523
+ const rule = ruleNameOf2(p) ?? enclosingRule;
3400
3524
  if (d.tag === "choice") {
3401
3525
  raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3402
3526
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
@@ -3406,6 +3530,11 @@ function analyzeGatingRules(ruleMap, opts) {
3406
3530
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3407
3531
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3408
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
+ }
3409
3538
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3410
3539
  if (d.tag === "lazy") {
3411
3540
  try {
@@ -3415,7 +3544,7 @@ function analyzeGatingRules(ruleMap, opts) {
3415
3544
  }
3416
3545
  for (const k of kids) visit(k, rule);
3417
3546
  };
3418
- for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3547
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3419
3548
  const perRule = /* @__PURE__ */ new Map();
3420
3549
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3421
3550
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3512,6 +3641,12 @@ function childrenOf(d) {
3512
3641
  case "sequence":
3513
3642
  case "choice":
3514
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
+ ];
3515
3650
  case "skip":
3516
3651
  return [d.main, d.skipped];
3517
3652
  case "sepBy":
@@ -3539,7 +3674,7 @@ function childrenOf(d) {
3539
3674
  }
3540
3675
  }
3541
3676
  }
3542
- var ruleNameOf2 = (p) => p._ruleName;
3677
+ var ruleNameOf3 = (p) => p._ruleName;
3543
3678
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
3544
3679
  function payloadKey(p, d) {
3545
3680
  switch (d.tag) {
@@ -3550,7 +3685,7 @@ function payloadKey(p, d) {
3550
3685
  case "keywords":
3551
3686
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
3552
3687
  case "lazy": {
3553
- const n = ruleNameOf2(p);
3688
+ const n = ruleNameOf3(p);
3554
3689
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
3555
3690
  }
3556
3691
  case "label":
@@ -3568,6 +3703,8 @@ function payloadKey(p, d) {
3568
3703
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3569
3704
  case "sepBy":
3570
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}`;
3571
3708
  case "expect":
3572
3709
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3573
3710
  case "scanTo":
@@ -3617,7 +3754,7 @@ var Interner = class {
3617
3754
  var MAX_RENDER = 160;
3618
3755
  function render(p, depth = 3) {
3619
3756
  const d = p._def;
3620
- const name = ruleNameOf2(p);
3757
+ const name = ruleNameOf3(p);
3621
3758
  switch (d.tag) {
3622
3759
  case "literal":
3623
3760
  return `literal('${d.value}')`;
@@ -3639,6 +3776,11 @@ function render(p, depth = 3) {
3639
3776
  return `sequence(${kids.join(", ")})`;
3640
3777
  case "choice":
3641
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
+ }
3642
3784
  case "node":
3643
3785
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3644
3786
  case "label":
@@ -3662,6 +3804,10 @@ function segment(d, index) {
3662
3804
  return `seq[${index}]`;
3663
3805
  case "choice":
3664
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";
3665
3811
  case "node":
3666
3812
  return `node(${d.type ?? ""})`;
3667
3813
  case "label":
@@ -3796,7 +3942,7 @@ function keywordAlternationHazards(words, boundary) {
3796
3942
  return out;
3797
3943
  }
3798
3944
  function analyzeDuplication(entry, opts) {
3799
- return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3945
+ return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3800
3946
  }
3801
3947
  function assertAnalyzable(ruleMap) {
3802
3948
  for (const [name, rule] of ruleMap) {
@@ -3837,7 +3983,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
3837
3983
  const visit = (p, rule, path, enclosingNode, enclosingReducer) => {
3838
3984
  if (visited.has(p)) return;
3839
3985
  const d = p._def;
3840
- const own = ruleNameOf2(p);
3986
+ const own = ruleNameOf3(p);
3841
3987
  const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path };
3842
3988
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
3843
3989
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
@@ -3850,12 +3996,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
3850
3996
  if (d.tag === "lazy") {
3851
3997
  try {
3852
3998
  const target = d.thunk();
3853
- visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
3999
+ visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
3854
4000
  } catch {
3855
4001
  }
3856
4002
  }
3857
4003
  };
3858
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
4004
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
3859
4005
  const accept = new Set(opts?.accept ?? []);
3860
4006
  const used = /* @__PURE__ */ new Set();
3861
4007
  const keep = (f) => {
@@ -4915,6 +5061,8 @@ function children(def, winners) {
4915
5061
  case "sequence":
4916
5062
  case "choice":
4917
5063
  return def.parsers;
5064
+ case "dispatch":
5065
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4918
5066
  case "many":
4919
5067
  case "oneOrMore":
4920
5068
  case "optional":
@@ -6379,6 +6527,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6379
6527
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6380
6528
  return { alts };
6381
6529
  }
6530
+ case "dispatch":
6531
+ return rec(d.selector);
6382
6532
  case "sequence": {
6383
6533
  const chain = [];
6384
6534
  for (const term of d.parsers) {
@@ -6453,6 +6603,9 @@ function failReturn(expected, posExpr) {
6453
6603
  function failReturnArr(expectedArr, posExpr) {
6454
6604
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6455
6605
  }
6606
+ function committedReturnArr(expectedArr, posExpr) {
6607
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6608
+ }
6456
6609
  function hoistExpected(ctx, constArrSource) {
6457
6610
  let name = ctx.expectedMap.get(constArrSource);
6458
6611
  if (name === void 0) {
@@ -6489,15 +6642,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6489
6642
  return probe + trace + failReturnArr(expectedArr, posExpr);
6490
6643
  }
6491
6644
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6492
- 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);
6493
6653
  }
6494
6654
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6495
6655
  if (srcCtx !== "_ctx") {
6496
6656
  if (ctx.failLabel) {
6497
6657
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6498
- 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} }`;
6499
6659
  }
6500
- 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()} }`;
6501
6661
  }
6502
6662
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6503
6663
  return resultFromRecorded();
@@ -6710,6 +6870,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6710
6870
  // nothing committed remains.
6711
6871
  case "choice":
6712
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);
6713
6875
  case "attempt":
6714
6876
  return false;
6715
6877
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6754,6 +6916,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6754
6916
  return true;
6755
6917
  }
6756
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
+ }
6757
6963
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6758
6964
  if (seen.has(p)) return false;
6759
6965
  seen.add(p);
@@ -6775,6 +6981,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6775
6981
  case "sequence":
6776
6982
  case "choice":
6777
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);
6778
6986
  case "sepBy":
6779
6987
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6780
6988
  case "many":
@@ -6821,6 +7029,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6821
7029
  ctx.recordFail = savedRecord;
6822
7030
  const ind0 = ind(ctx);
6823
7031
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7032
+ const mayCommit = mayCommitFailure(inner);
6824
7033
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6825
7034
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6826
7035
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6834,6 +7043,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6834
7043
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6835
7044
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6836
7045
  ] : [],
7046
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6837
7047
  `${ind0}${lbl}: {`,
6838
7048
  ...r.stmts,
6839
7049
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6842,7 +7052,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6842
7052
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6843
7053
  ] : []
6844
7054
  ];
6845
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7055
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6846
7056
  }
6847
7057
  var CHARCODE_CHAIN_MAX = 16;
6848
7058
  function emitLit(def, ctx, pos) {
@@ -7219,6 +7429,118 @@ function emitChoice(parser2, def, ctx, pos) {
7219
7429
  }
7220
7430
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7221
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
+ }
7222
7544
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7223
7545
  const superParser = def.parsers[superIndex];
7224
7546
  const regexDef = getCoreRegexDef(superParser);
@@ -7340,12 +7662,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7340
7662
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7341
7663
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7342
7664
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7665
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7343
7666
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7344
7667
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7345
7668
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7346
7669
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7347
7670
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7348
- 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("; ");
7349
7675
  const failSlot = atStart ? staticFx : "_ctx._fx";
7350
7676
  stmts.push(`${ind0}if (${skipCond}) {`);
7351
7677
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7360,7 +7686,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7360
7686
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7361
7687
  );
7362
7688
  }
7363
- 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;
7364
7692
  stmts.push(...armStmts);
7365
7693
  if (autoNot && autoNot.length > 0) {
7366
7694
  const anCode = v(ctx, "_anc");
@@ -7384,10 +7712,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7384
7712
  stmts.push(`${ind(ctx)}}`);
7385
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} });`} }`);
7386
7714
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7715
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7387
7716
  } else {
7388
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} }) }`;
7389
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} })`} }`);
7390
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)}`);
7391
7721
  }
7392
7722
  ctx.indent -= fsGuard ? 2 : 1;
7393
7723
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7544,7 +7874,8 @@ function emitMany(def, ctx, pos) {
7544
7874
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7545
7875
  );
7546
7876
  }
7547
- 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;
7548
7879
  stmts.push(...iterStmts);
7549
7880
  if (ctx.recovery) {
7550
7881
  const rrV = v(ctx, "_rr");
@@ -7563,7 +7894,8 @@ function emitMany(def, ctx, pos) {
7563
7894
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7564
7895
  );
7565
7896
  } else {
7566
- 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 }`);
7567
7899
  }
7568
7900
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7569
7901
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7575,10 +7907,12 @@ function emitMany(def, ctx, pos) {
7575
7907
  function emitOptional(def, ctx, pos) {
7576
7908
  const valV = v(ctx, "_opt");
7577
7909
  const endV = v(ctx, "_opte");
7578
- 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;
7579
7912
  const ind0 = ind(ctx);
7580
7913
  const stmts = [
7581
7914
  ...lblStmts,
7915
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7582
7916
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7583
7917
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7584
7918
  ];
@@ -7610,7 +7944,7 @@ function emitAttempt(p, def, ctx, pos) {
7610
7944
  ...preGuard,
7611
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`,
7612
7946
  ...inner.stmts,
7613
- ...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)} }`)
7614
7948
  ],
7615
7949
  valueVar: inner.valVar,
7616
7950
  endVar: inner.endVar
@@ -7624,18 +7958,21 @@ function emitSepBy(_p, def, ctx, pos) {
7624
7958
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7625
7959
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7626
7960
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7627
- 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;
7628
7963
  const stmts = [
7629
7964
  `${ind(ctx)}const ${arrV} = []`,
7630
7965
  `${ind(ctx)}let ${curV} = ${pos}`,
7631
7966
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7632
7967
  ...firstStmts
7633
7968
  ];
7634
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7635
- 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} }`];
7636
7972
  const rr = v(ctx, "_rr");
7637
7973
  return [
7638
7974
  `${ind(ctx)}if (!${nextOkVar}) {`,
7975
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7639
7976
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7640
7977
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7641
7978
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7664,6 +8001,7 @@ function emitSepBy(_p, def, ctx, pos) {
7664
8001
  stmts.push(
7665
8002
  `${ind(ctx)}let ${didV} = false`,
7666
8003
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8004
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7667
8005
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7668
8006
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7669
8007
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7674,6 +8012,7 @@ function emitSepBy(_p, def, ctx, pos) {
7674
8012
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7675
8013
  ctx.indent++;
7676
8014
  } else {
8015
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7677
8016
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7678
8017
  ctx.indent++;
7679
8018
  stmts.push(
@@ -7702,16 +8041,18 @@ function emitSepBy(_p, def, ctx, pos) {
7702
8041
  );
7703
8042
  sepAtPos = spV;
7704
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}; `;
7705
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7706
- 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 }`);
7707
8047
  const post = postSepMarks();
7708
8048
  stmts.push(...post.decl);
7709
8049
  const npV = v(ctx, "_np");
7710
8050
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7711
- 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;
7712
8053
  stmts.push(
7713
8054
  ...nextStmts,
7714
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8055
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7715
8056
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7716
8057
  `${ind(ctx)}${curV} = ${nextEnd}`
7717
8058
  );
@@ -7720,14 +8061,16 @@ function emitSepBy(_p, def, ctx, pos) {
7720
8061
  const spV = v(ctx, "_sp");
7721
8062
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7722
8063
  sepAtPos = spV;
7723
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7724
- 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 }`);
7725
8067
  const npV = v(ctx, "_np");
7726
8068
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7727
- 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;
7728
8071
  stmts.push(
7729
8072
  ...nextStmts,
7730
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8073
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7731
8074
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7732
8075
  `${ind(ctx)}${curV} = ${nextEnd}`
7733
8076
  );
@@ -7741,15 +8084,17 @@ function emitSepBy(_p, def, ctx, pos) {
7741
8084
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7742
8085
  );
7743
8086
  }
7744
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7745
- 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 }`);
7746
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}; ` : "";
7747
8091
  const post = postSepMarks();
7748
8092
  stmts.push(...post.decl);
7749
- 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;
7750
8095
  stmts.push(
7751
8096
  ...nextStmts,
7752
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8097
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7753
8098
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7754
8099
  `${ind(ctx)}${curV} = ${nextEnd}`
7755
8100
  );
@@ -8130,7 +8475,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8130
8475
  const rv = v(ctx, "_rt");
8131
8476
  const vv = v(ctx, "_rtv");
8132
8477
  const ev = v(ctx, "_rte");
8133
- 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 } : {}) }`;
8134
8479
  const stmts = [
8135
8480
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8136
8481
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8211,7 +8556,7 @@ function emitRecover(def, ctx, pos) {
8211
8556
  return { stmts, valueVar: valVar, endVar };
8212
8557
  }
8213
8558
  function emitExpect(def, ctx, pos) {
8214
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8559
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8215
8560
  const ind0 = ind(ctx);
8216
8561
  const errV = v(ctx, "_err");
8217
8562
  const stmts = [
@@ -8223,6 +8568,7 @@ function emitExpect(def, ctx, pos) {
8223
8568
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8224
8569
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8225
8570
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8571
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8226
8572
  `${ind0} ${valVar} = ${errV}`,
8227
8573
  `${ind0} ${endVar} = ${pos}`,
8228
8574
  `${ind0} ${okVar} = true`,
@@ -8233,7 +8579,7 @@ function emitExpect(def, ctx, pos) {
8233
8579
  function emit(p, ctx, pos) {
8234
8580
  const replay = ctx.replayPrefix?.get(p);
8235
8581
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8236
- const dispatch = (emissionPos = pos) => {
8582
+ const dispatch2 = (emissionPos = pos) => {
8237
8583
  const savedRule = ctx.activeCoverageRuleId;
8238
8584
  const rule = ctx.coverage?.plan.rules.get(p);
8239
8585
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8273,14 +8619,14 @@ function emit(p, ctx, pos) {
8273
8619
  ctx.indent = 1;
8274
8620
  ctx.failLabel = "_pfail";
8275
8621
  ctx.recordFail = true;
8276
- const r = dispatch("_pos");
8622
+ const r = dispatch2("_pos");
8277
8623
  ctx.indent = savedIndent;
8278
8624
  ctx.failLabel = savedFailLabel;
8279
8625
  ctx.recordFail = savedRecord;
8280
8626
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8281
8627
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8282
8628
  }
8283
- return instrument(dispatch());
8629
+ return instrument(dispatch2());
8284
8630
  }
8285
8631
  function emitDispatch(p, ctx, pos) {
8286
8632
  const bal = p._balancedAmbient;
@@ -8313,6 +8659,8 @@ function emitDispatch(p, ctx, pos) {
8313
8659
  return emitSeq(def, ctx, pos);
8314
8660
  case "choice":
8315
8661
  return emitChoice(p, def, ctx, pos);
8662
+ case "dispatch":
8663
+ return emitDispatchCombinator(def, ctx, pos);
8316
8664
  case "attempt":
8317
8665
  return emitAttempt(p, def, ctx, pos);
8318
8666
  case "many":
@@ -8533,6 +8881,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8533
8881
  case "sequence":
8534
8882
  case "choice":
8535
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);
8536
8886
  case "sepBy":
8537
8887
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8538
8888
  case "scanTo":
@@ -8566,6 +8916,8 @@ function childrenOf2(def) {
8566
8916
  case "sequence":
8567
8917
  case "choice":
8568
8918
  return def.parsers;
8919
+ case "dispatch":
8920
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8569
8921
  case "many":
8570
8922
  case "attempt":
8571
8923
  case "optional":
@@ -8609,6 +8961,7 @@ function childrenOf2(def) {
8609
8961
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8610
8962
  "sequence",
8611
8963
  "choice",
8964
+ "dispatch",
8612
8965
  "many",
8613
8966
  "oneOrMore",
8614
8967
  "optional",
@@ -8702,7 +9055,7 @@ function ruleDependencies(ruleMap) {
8702
9055
  const seen = /* @__PURE__ */ new Set();
8703
9056
  const walk2 = (p, isRoot) => {
8704
9057
  const def = p._def;
8705
- const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
9058
+ const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
8706
9059
  if (!isRoot && boundary !== void 0) {
8707
9060
  found.add(boundary);
8708
9061
  return;
@@ -9307,6 +9660,8 @@ function childrenOf3(def) {
9307
9660
  case "sequence":
9308
9661
  case "choice":
9309
9662
  return def.parsers;
9663
+ case "dispatch":
9664
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9310
9665
  case "many":
9311
9666
  case "oneOrMore":
9312
9667
  case "optional":
@@ -9341,7 +9696,7 @@ function childrenOf3(def) {
9341
9696
  return [];
9342
9697
  }
9343
9698
  }
9344
- var ruleNameOf3 = (c) => c._ruleName;
9699
+ var ruleNameOf4 = (c) => c._ruleName;
9345
9700
  function lazyTarget(c) {
9346
9701
  if (c._def.tag !== "lazy") return null;
9347
9702
  try {
@@ -9456,6 +9811,9 @@ function evalRuleMapIR(ir) {
9456
9811
  "keywords",
9457
9812
  "sequence",
9458
9813
  "choice",
9814
+ "dispatch",
9815
+ "when",
9816
+ "otherwise",
9459
9817
  "attempt",
9460
9818
  "many",
9461
9819
  "oneOrMore",
@@ -9489,6 +9847,9 @@ function evalRuleMapIR(ir) {
9489
9847
  keywords,
9490
9848
  sequence,
9491
9849
  choice,
9850
+ dispatch,
9851
+ when,
9852
+ otherwise,
9492
9853
  attempt,
9493
9854
  many,
9494
9855
  oneOrMore,
@@ -9523,12 +9884,16 @@ var Serializer = class {
9523
9884
  emitted = /* @__PURE__ */ new Set();
9524
9885
  decls = [];
9525
9886
  ruleValues = /* @__PURE__ */ new Set();
9887
+ localRuleNames = /* @__PURE__ */ new Map();
9526
9888
  ruleMap;
9527
9889
  scanSkip;
9528
9890
  constructor(ruleMap, scanSkip = []) {
9529
9891
  this.ruleMap = ruleMap;
9530
9892
  this.scanSkip = scanSkip;
9531
- for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
9893
+ for (const [name, c] of ruleMap) {
9894
+ this.localRuleNames.set(c, name);
9895
+ this.ruleValues.add(this.body(c));
9896
+ }
9532
9897
  }
9533
9898
  /** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
9534
9899
  * bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
@@ -9539,7 +9904,7 @@ var Serializer = class {
9539
9904
  if (seen.has(c)) return;
9540
9905
  seen.add(c);
9541
9906
  if (c._def.tag === "lazy") {
9542
- if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9907
+ if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9543
9908
  const target = lazyTarget(c);
9544
9909
  if (target) this.assertNoRuleRef(target, seen);
9545
9910
  return;
@@ -9550,7 +9915,7 @@ var Serializer = class {
9550
9915
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
9551
9916
  * `g[name]` ref — only the top-level entry is unwrapped. */
9552
9917
  body(c) {
9553
- return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
9918
+ return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
9554
9919
  }
9555
9920
  run() {
9556
9921
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -9592,7 +9957,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9592
9957
  analyze(c, active) {
9593
9958
  const def = c._def;
9594
9959
  if (def.tag === "lazy") {
9595
- if (ruleNameOf3(c) !== void 0) return;
9960
+ if (ruleNameOf4(c) !== void 0) return;
9596
9961
  const target = lazyTarget(c);
9597
9962
  if (!target) throw new Unserializable("unresolved unnamed ref");
9598
9963
  if (active.has(target)) {
@@ -9644,7 +10009,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9644
10009
  const kid = (x) => this.ref(x, selfOf);
9645
10010
  switch (def.tag) {
9646
10011
  case "lazy": {
9647
- if (ruleNameOf3(c) !== void 0) return `g[${JSON.stringify(ruleNameOf3(c))}]`;
10012
+ const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
10013
+ if (name !== void 0) return `g[${JSON.stringify(name)}]`;
9648
10014
  const target = lazyTarget(c);
9649
10015
  if (selfOf && target === selfOf.comb) return selfOf.var;
9650
10016
  return this.ref(target, selfOf);
@@ -9669,6 +10035,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9669
10035
  });
9670
10036
  return `_gch([${items.join(", ")}])`;
9671
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
+ }
9672
10045
  case "many":
9673
10046
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9674
10047
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10888,6 +11261,12 @@ function coverageEntry(entry, collector, maps, trace) {
10888
11261
  const unsafeSequence = sequence;
10889
11262
  return unsafeSequence(...def.parsers.map(build));
10890
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
+ }
10891
11270
  case "many":
10892
11271
  return many(build(def.parser));
10893
11272
  case "oneOrMore":