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.js CHANGED
@@ -77,6 +77,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
77
77
  return d.parsers.every(me);
78
78
  case "choice":
79
79
  return d.parsers.some(me);
80
+ case "dispatch":
81
+ return me(d.selector);
80
82
  case "transform":
81
83
  case "label":
82
84
  case "trivia":
@@ -150,6 +152,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
150
152
  case "keywords":
151
153
  return p._meta.firstSet;
152
154
  // terminals: no refs, cached set is exact
155
+ case "dispatch":
156
+ return fs(d.selector);
153
157
  case "lazy":
154
158
  try {
155
159
  return fs(d.thunk());
@@ -919,8 +923,10 @@ function word(str, boundaryOrOpts, opts) {
919
923
  const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
920
924
  return keywords([str], { ...rest, boundary });
921
925
  }
922
- function makeWord(boundary = "_0-9A-Za-z") {
923
- return (str) => keywords([str], { boundary });
926
+ function makeWord(boundaryOrOpts = "_0-9A-Za-z", opts) {
927
+ const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
928
+ const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
929
+ return (str) => keywords([str], { ...rest, boundary });
924
930
  }
925
931
  function escapeRe(s) {
926
932
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1065,6 +1071,8 @@ function derive(c, seen) {
1065
1071
  return [def.label];
1066
1072
  case "choice":
1067
1073
  return def.parsers.flatMap(deriveExpected2);
1074
+ case "dispatch":
1075
+ return deriveExpected2(def.selector);
1068
1076
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1069
1077
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1070
1078
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1206,6 +1214,7 @@ function choice(...args) {
1206
1214
  const result = parsers[idx].parse(input, pos, ctx);
1207
1215
  if (result.ok) return result;
1208
1216
  expected.push(...result.expected);
1217
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1209
1218
  return { ok: false, expected, span: { start: pos, end: pos } };
1210
1219
  }
1211
1220
  return {
@@ -1246,6 +1255,7 @@ function choice(...args) {
1246
1255
  rollbackCstCapture(ctx, mark);
1247
1256
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1248
1257
  expected.push(...result.expected);
1258
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1249
1259
  continue;
1250
1260
  }
1251
1261
  const checks = autoNot[i];
@@ -1925,6 +1935,75 @@ function sequence(...parsers) {
1925
1935
  };
1926
1936
  }
1927
1937
 
1938
+ // src/combinators/dispatch.ts
1939
+ function when(key, parser2) {
1940
+ const keys = Array.isArray(key) ? [...key] : [key];
1941
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
1942
+ for (const item of keys) {
1943
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
1944
+ }
1945
+ return { kind: "when", keys, parser: parser2 };
1946
+ }
1947
+ function otherwise(parser2) {
1948
+ return { kind: "otherwise", parser: parser2 };
1949
+ }
1950
+ function dispatch(selector, ...arms) {
1951
+ let fallback;
1952
+ const cases = [];
1953
+ const seen = /* @__PURE__ */ new Set();
1954
+ for (let i = 0; i < arms.length; i++) {
1955
+ const arm = arms[i];
1956
+ if (arm.kind === "otherwise") {
1957
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
1958
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
1959
+ fallback = arm.parser;
1960
+ continue;
1961
+ }
1962
+ for (const key of arm.keys) {
1963
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
1964
+ seen.add(key);
1965
+ }
1966
+ cases.push({ keys: arm.keys, parser: arm.parser });
1967
+ }
1968
+ const meta = {
1969
+ firstSet: selector._meta.firstSet,
1970
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
1971
+ isTrivia: false
1972
+ };
1973
+ const byKey = /* @__PURE__ */ new Map();
1974
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
1975
+ return {
1976
+ _tag: "dispatch",
1977
+ _meta: meta,
1978
+ _def: {
1979
+ tag: "dispatch",
1980
+ selector,
1981
+ cases,
1982
+ ...fallback === void 0 ? {} : { otherwise: fallback }
1983
+ },
1984
+ parse(input, pos, ctx) {
1985
+ const selected = selector.parse(input, pos, ctx);
1986
+ if (!selected.ok) return selected;
1987
+ const tail = byKey.get(selected.value) ?? fallback;
1988
+ if (tail === void 0) {
1989
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
1990
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
1991
+ }
1992
+ const mark = saveTriviaMark(ctx);
1993
+ const result = tail.parse(input, selected.span.end, ctx);
1994
+ if (!result.ok) {
1995
+ rollbackTrivia(ctx, mark);
1996
+ return { ...result, committed: true };
1997
+ }
1998
+ return {
1999
+ ok: true,
2000
+ value: [selected.value, result.value],
2001
+ span: { start: pos, end: result.span.end }
2002
+ };
2003
+ }
2004
+ };
2005
+ }
2006
+
1928
2007
  // src/combinators/attempt.ts
1929
2008
  function attempt(parser2) {
1930
2009
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -1947,6 +2026,7 @@ function attempt(parser2) {
1947
2026
  if (result.ok) return result;
1948
2027
  rollbackCstCapture(ctx, mark);
1949
2028
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2029
+ if (result.committed) return result;
1950
2030
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1951
2031
  }
1952
2032
  };
@@ -1976,6 +2056,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
1976
2056
  const result = combinator.parse(input, pos, ctx);
1977
2057
  if (!result.ok) {
1978
2058
  rollbackTrivia(ctx, mark);
2059
+ if (result.committed) return { fail: result, failPos: pos };
1979
2060
  return { fail: result, failPos: pos };
1980
2061
  }
1981
2062
  if (result.span.end === pos) {
@@ -2019,6 +2100,7 @@ function many(combinator, opts = {}) {
2019
2100
  const item = repItem(combinator, input, cur, ctx, guardable);
2020
2101
  if (item === "stop") break;
2021
2102
  if ("fail" in item) {
2103
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2022
2104
  const sync = ctx._tolerant ? ctx._sync : void 0;
2023
2105
  if (sync === void 0) break;
2024
2106
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2063,6 +2145,7 @@ function atLeast(combinator, min, max) {
2063
2145
  while (count < min) {
2064
2146
  const item = repItem(combinator, input, cur, ctx, guardable);
2065
2147
  if (item === "stop" || "fail" in item) {
2148
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2066
2149
  expected ??= deriveExpected(combinator);
2067
2150
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2068
2151
  }
@@ -2075,6 +2158,7 @@ function atLeast(combinator, min, max) {
2075
2158
  const item = repItem(combinator, input, cur, ctx, guardable);
2076
2159
  if (item === "stop") break;
2077
2160
  if ("fail" in item) {
2161
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2078
2162
  const sync = ctx._tolerant ? ctx._sync : void 0;
2079
2163
  if (sync === void 0) break;
2080
2164
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2113,6 +2197,7 @@ function optional(combinator) {
2113
2197
  const result = combinator.parse(input, pos, ctx);
2114
2198
  if (result.ok) return result;
2115
2199
  rollbackTrivia(ctx, mark);
2200
+ if (result.committed) return result;
2116
2201
  return { ok: true, value: null, span: { start: pos, end: pos } };
2117
2202
  }
2118
2203
  };
@@ -2152,6 +2237,7 @@ function sepBy(combinator, separator, opts = {}) {
2152
2237
  values.push(first.value);
2153
2238
  cur = first.span.end;
2154
2239
  } else {
2240
+ if (first.committed) return first;
2155
2241
  const term = ctx._tolerant ? ctx._sync : void 0;
2156
2242
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2157
2243
  if (min >= 1) return failAt2(pos);
@@ -2179,6 +2265,7 @@ function sepBy(combinator, separator, opts = {}) {
2179
2265
  const sep = separator.parse(input, sepPos, ctx);
2180
2266
  if (!sep.ok) {
2181
2267
  rollbackTrivia(ctx, loopMark);
2268
+ if (sep.committed) return sep;
2182
2269
  break;
2183
2270
  }
2184
2271
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2194,6 +2281,7 @@ function sepBy(combinator, separator, opts = {}) {
2194
2281
  }
2195
2282
  const next = combinator.parse(input, nextPos, ctx);
2196
2283
  if (!next.ok) {
2284
+ if (next.committed) return next;
2197
2285
  const term = ctx._tolerant ? ctx._sync : void 0;
2198
2286
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2199
2287
  expected ??= deriveExpected(combinator);
@@ -2286,6 +2374,11 @@ function markUnusedValues(root) {
2286
2374
  case "choice":
2287
2375
  for (const p of def.parsers) visit(p, consumed);
2288
2376
  return;
2377
+ case "dispatch":
2378
+ visit(def.selector, consumed);
2379
+ for (const entry of def.cases) visit(entry.parser, consumed);
2380
+ if (def.otherwise) visit(def.otherwise, consumed);
2381
+ return;
2289
2382
  case "node":
2290
2383
  visit(def.parser, false);
2291
2384
  return;
@@ -2357,6 +2450,16 @@ function tagRule(r, key) {
2357
2450
  r._ruleName = key;
2358
2451
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
2359
2452
  }
2453
+ function isDefinableRef(v2) {
2454
+ return !!v2 && typeof v2 === "object" && "_def" in v2 && v2._def.tag === "lazy" && typeof v2.define === "function";
2455
+ }
2456
+ function ruleNameOf(r) {
2457
+ return r._ruleName;
2458
+ }
2459
+ function isNamedRuleRefForAnotherRule(r, key) {
2460
+ const name = r._def.tag === "lazy" ? ruleNameOf(r) : void 0;
2461
+ return name !== void 0 && name !== key;
2462
+ }
2360
2463
  function rules(a, b) {
2361
2464
  const factory = typeof a === "function" ? a : b;
2362
2465
  const options = typeof a === "function" ? b : a;
@@ -2377,11 +2480,21 @@ function rules(a, b) {
2377
2480
  for (const key of Object.keys(definitions)) {
2378
2481
  const placeholder = cache[key];
2379
2482
  const parser2 = definitions[key];
2380
- if (placeholder !== void 0 && typeof placeholder.define === "function") {
2381
- tagRule(parser2, key);
2483
+ if (placeholder === parser2) {
2484
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`);
2485
+ }
2486
+ if (isDefinableRef(placeholder)) {
2487
+ if (!isNamedRuleRefForAnotherRule(parser2, key)) tagRule(parser2, key);
2382
2488
  placeholder.define(parser2);
2383
2489
  placeholder._meta.firstSet = parser2._meta.firstSet;
2384
2490
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
2491
+ } else if (isNamedRuleRefForAnotherRule(parser2, key)) {
2492
+ const alias = ref();
2493
+ tagRule(alias, key);
2494
+ alias.define(parser2);
2495
+ alias._meta.firstSet = parser2._meta.firstSet;
2496
+ alias._meta.canMatchNewline = parser2._meta.canMatchNewline;
2497
+ cache[key] = alias;
2385
2498
  } else {
2386
2499
  tagRule(parser2, key);
2387
2500
  cache[key] = parser2;
@@ -2541,6 +2654,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2541
2654
  case "sequence":
2542
2655
  case "choice":
2543
2656
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2657
+ case "dispatch":
2658
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2544
2659
  case "sepBy":
2545
2660
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2546
2661
  case "skip":
@@ -2608,6 +2723,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2608
2723
  return parserHasTriviaSite(d.parser, seen);
2609
2724
  case "choice":
2610
2725
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2726
+ case "dispatch":
2727
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2611
2728
  case "grammar":
2612
2729
  return parserHasTriviaSite(d.parser, seen);
2613
2730
  case "lazy": {
@@ -2633,6 +2750,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2633
2750
  case "sequence":
2634
2751
  case "choice":
2635
2752
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2753
+ case "dispatch":
2754
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2636
2755
  case "sepBy":
2637
2756
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2638
2757
  case "skip":
@@ -3034,7 +3153,7 @@ function leaf(root, fn) {
3034
3153
  }
3035
3154
 
3036
3155
  // src/version.ts
3037
- var PARSEMAN_VERSION = "0.37.0";
3156
+ var PARSEMAN_VERSION = "0.39.0";
3038
3157
 
3039
3158
  // src/cst/host-mode.ts
3040
3159
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3088,7 +3207,7 @@ function firstSetToString(fs) {
3088
3207
  if (fs.kind === "empty") return "(empty)";
3089
3208
  return fs.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3090
3209
  }
3091
- var ruleNameOf = (p) => p._ruleName;
3210
+ var ruleNameOf2 = (p) => p._ruleName;
3092
3211
  var SUGGESTIONS = {
3093
3212
  "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.",
3094
3213
  "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.",
@@ -3190,6 +3309,8 @@ function classifyBroadArm(arm, resolve) {
3190
3309
  }
3191
3310
  return null;
3192
3311
  }
3312
+ case "dispatch":
3313
+ return walk2(d.selector);
3193
3314
  default:
3194
3315
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3195
3316
  }
@@ -3246,7 +3367,7 @@ function detectAntiPatterns(rule, arms) {
3246
3367
  return out;
3247
3368
  }
3248
3369
  function analyzeGating(entry, opts) {
3249
- return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3370
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3250
3371
  }
3251
3372
  function analyzeGatingRules(ruleMap, opts) {
3252
3373
  const raw = [];
@@ -3279,7 +3400,7 @@ function analyzeGatingRules(ruleMap, opts) {
3279
3400
  return;
3280
3401
  }
3281
3402
  const d = def;
3282
- const rule = ruleNameOf(p) ?? enclosingRule;
3403
+ const rule = ruleNameOf2(p) ?? enclosingRule;
3283
3404
  if (d.tag === "choice") {
3284
3405
  raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3285
3406
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
@@ -3289,6 +3410,11 @@ function analyzeGatingRules(ruleMap, opts) {
3289
3410
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3290
3411
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3291
3412
  if (rec[k]) kids.push(rec[k]);
3413
+ if (d.tag === "dispatch") {
3414
+ kids.push(d.selector);
3415
+ for (const c of d.cases) kids.push(c.parser);
3416
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3417
+ }
3292
3418
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3293
3419
  if (d.tag === "lazy") {
3294
3420
  try {
@@ -3298,7 +3424,7 @@ function analyzeGatingRules(ruleMap, opts) {
3298
3424
  }
3299
3425
  for (const k of kids) visit(k, rule);
3300
3426
  };
3301
- for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3427
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3302
3428
  const perRule = /* @__PURE__ */ new Map();
3303
3429
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3304
3430
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3395,6 +3521,12 @@ function childrenOf(d) {
3395
3521
  case "sequence":
3396
3522
  case "choice":
3397
3523
  return d.parsers;
3524
+ case "dispatch":
3525
+ return [
3526
+ d.selector,
3527
+ ...d.cases.map((c) => c.parser),
3528
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3529
+ ];
3398
3530
  case "skip":
3399
3531
  return [d.main, d.skipped];
3400
3532
  case "sepBy":
@@ -3422,7 +3554,7 @@ function childrenOf(d) {
3422
3554
  }
3423
3555
  }
3424
3556
  }
3425
- var ruleNameOf2 = (p) => p._ruleName;
3557
+ var ruleNameOf3 = (p) => p._ruleName;
3426
3558
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
3427
3559
  function payloadKey(p, d) {
3428
3560
  switch (d.tag) {
@@ -3433,7 +3565,7 @@ function payloadKey(p, d) {
3433
3565
  case "keywords":
3434
3566
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
3435
3567
  case "lazy": {
3436
- const n = ruleNameOf2(p);
3568
+ const n = ruleNameOf3(p);
3437
3569
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
3438
3570
  }
3439
3571
  case "label":
@@ -3451,6 +3583,8 @@ function payloadKey(p, d) {
3451
3583
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3452
3584
  case "sepBy":
3453
3585
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3586
+ case "dispatch":
3587
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3454
3588
  case "expect":
3455
3589
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3456
3590
  case "scanTo":
@@ -3500,7 +3634,7 @@ var Interner = class {
3500
3634
  var MAX_RENDER = 160;
3501
3635
  function render(p, depth = 3) {
3502
3636
  const d = p._def;
3503
- const name = ruleNameOf2(p);
3637
+ const name = ruleNameOf3(p);
3504
3638
  switch (d.tag) {
3505
3639
  case "literal":
3506
3640
  return `literal('${d.value}')`;
@@ -3522,6 +3656,11 @@ function render(p, depth = 3) {
3522
3656
  return `sequence(${kids.join(", ")})`;
3523
3657
  case "choice":
3524
3658
  return `choice(${kids.join(", ")})`;
3659
+ case "dispatch": {
3660
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3661
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3662
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3663
+ }
3525
3664
  case "node":
3526
3665
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3527
3666
  case "label":
@@ -3545,6 +3684,10 @@ function segment(d, index) {
3545
3684
  return `seq[${index}]`;
3546
3685
  case "choice":
3547
3686
  return `choice[${index}]`;
3687
+ case "dispatch":
3688
+ if (index === 0) return "dispatch.selector";
3689
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3690
+ return "dispatch.otherwise";
3548
3691
  case "node":
3549
3692
  return `node(${d.type ?? ""})`;
3550
3693
  case "label":
@@ -3679,7 +3822,7 @@ function keywordAlternationHazards(words, boundary) {
3679
3822
  return out;
3680
3823
  }
3681
3824
  function analyzeDuplication(entry, opts) {
3682
- return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3825
+ return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3683
3826
  }
3684
3827
  function assertAnalyzable(ruleMap) {
3685
3828
  for (const [name, rule] of ruleMap) {
@@ -3720,7 +3863,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
3720
3863
  const visit = (p, rule, path, enclosingNode, enclosingReducer) => {
3721
3864
  if (visited.has(p)) return;
3722
3865
  const d = p._def;
3723
- const own = ruleNameOf2(p);
3866
+ const own = ruleNameOf3(p);
3724
3867
  const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path };
3725
3868
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
3726
3869
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
@@ -3733,12 +3876,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
3733
3876
  if (d.tag === "lazy") {
3734
3877
  try {
3735
3878
  const target = d.thunk();
3736
- visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
3879
+ visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
3737
3880
  } catch {
3738
3881
  }
3739
3882
  }
3740
3883
  };
3741
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
3884
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
3742
3885
  const accept = new Set(opts?.accept ?? []);
3743
3886
  const used = /* @__PURE__ */ new Set();
3744
3887
  const keep = (f) => {
@@ -4798,6 +4941,8 @@ function children(def, winners) {
4798
4941
  case "sequence":
4799
4942
  case "choice":
4800
4943
  return def.parsers;
4944
+ case "dispatch":
4945
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4801
4946
  case "many":
4802
4947
  case "oneOrMore":
4803
4948
  case "optional":
@@ -6262,6 +6407,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6262
6407
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6263
6408
  return { alts };
6264
6409
  }
6410
+ case "dispatch":
6411
+ return rec(d.selector);
6265
6412
  case "sequence": {
6266
6413
  const chain = [];
6267
6414
  for (const term of d.parsers) {
@@ -6336,6 +6483,9 @@ function failReturn(expected, posExpr) {
6336
6483
  function failReturnArr(expectedArr, posExpr) {
6337
6484
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6338
6485
  }
6486
+ function committedReturnArr(expectedArr, posExpr) {
6487
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6488
+ }
6339
6489
  function hoistExpected(ctx, constArrSource) {
6340
6490
  let name = ctx.expectedMap.get(constArrSource);
6341
6491
  if (name === void 0) {
@@ -6372,15 +6522,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6372
6522
  return probe + trace + failReturnArr(expectedArr, posExpr);
6373
6523
  }
6374
6524
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6375
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6525
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6526
+ }
6527
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6528
+ if (ctx.failLabel) {
6529
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6530
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6531
+ }
6532
+ return committedReturnArr(expectedArr, posExpr);
6376
6533
  }
6377
6534
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6378
6535
  if (srcCtx !== "_ctx") {
6379
6536
  if (ctx.failLabel) {
6380
6537
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6381
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6538
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6382
6539
  }
6383
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6540
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6384
6541
  }
6385
6542
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6386
6543
  return resultFromRecorded();
@@ -6593,6 +6750,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6593
6750
  // nothing committed remains.
6594
6751
  case "choice":
6595
6752
  return false;
6753
+ case "dispatch":
6754
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6596
6755
  case "attempt":
6597
6756
  return false;
6598
6757
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6637,6 +6796,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6637
6796
  return true;
6638
6797
  }
6639
6798
  }
6799
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6800
+ if (seen.has(p)) return false;
6801
+ seen.add(p);
6802
+ const d = p._def;
6803
+ switch (d.tag) {
6804
+ case "dispatch":
6805
+ return true;
6806
+ case "choice":
6807
+ case "sequence":
6808
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6809
+ case "many":
6810
+ case "oneOrMore":
6811
+ case "optional":
6812
+ case "attempt":
6813
+ case "transform":
6814
+ case "label":
6815
+ case "field":
6816
+ case "grammar":
6817
+ case "node":
6818
+ return mayCommitFailure(d.parser, seen);
6819
+ case "sepBy":
6820
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6821
+ case "skip":
6822
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6823
+ case "token":
6824
+ case "leaf":
6825
+ return mayCommitFailure(d.parser, seen);
6826
+ case "withCtx":
6827
+ return mayCommitFailure(d.parser, seen);
6828
+ case "scanTo":
6829
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6830
+ case "lazy": {
6831
+ try {
6832
+ return mayCommitFailure(d.thunk(), seen);
6833
+ } catch {
6834
+ return true;
6835
+ }
6836
+ }
6837
+ case "unknown":
6838
+ return true;
6839
+ default:
6840
+ return false;
6841
+ }
6842
+ }
6640
6843
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6641
6844
  if (seen.has(p)) return false;
6642
6845
  seen.add(p);
@@ -6658,6 +6861,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6658
6861
  case "sequence":
6659
6862
  case "choice":
6660
6863
  return d.parsers.some((x) => capturesLeaf(x, seen));
6864
+ case "dispatch":
6865
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6661
6866
  case "sepBy":
6662
6867
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6663
6868
  case "many":
@@ -6704,6 +6909,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6704
6909
  ctx.recordFail = savedRecord;
6705
6910
  const ind0 = ind(ctx);
6706
6911
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
6912
+ const mayCommit = mayCommitFailure(inner);
6707
6913
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6708
6914
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6709
6915
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6717,6 +6923,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6717
6923
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6718
6924
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6719
6925
  ] : [],
6926
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6720
6927
  `${ind0}${lbl}: {`,
6721
6928
  ...r.stmts,
6722
6929
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6725,7 +6932,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6725
6932
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6726
6933
  ] : []
6727
6934
  ];
6728
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
6935
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6729
6936
  }
6730
6937
  var CHARCODE_CHAIN_MAX = 16;
6731
6938
  function emitLit(def, ctx, pos) {
@@ -7102,6 +7309,118 @@ function emitChoice(parser2, def, ctx, pos) {
7102
7309
  }
7103
7310
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7104
7311
  }
7312
+ function emitTailFallible(parser2, ctx, pos) {
7313
+ const savedRecord = ctx.recordFail;
7314
+ ctx.recordFail = true;
7315
+ const out = emitFallible(parser2, ctx, pos);
7316
+ ctx.recordFail = savedRecord;
7317
+ return out;
7318
+ }
7319
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7320
+ if (seen.has(p)) return false;
7321
+ seen.add(p);
7322
+ const d = p._def;
7323
+ switch (d.tag) {
7324
+ case "expect":
7325
+ case "recover":
7326
+ return true;
7327
+ case "many":
7328
+ case "oneOrMore":
7329
+ case "sepBy":
7330
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7331
+ case "choice":
7332
+ case "sequence": {
7333
+ for (const item of d.parsers) {
7334
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7335
+ }
7336
+ return false;
7337
+ }
7338
+ case "dispatch": {
7339
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7340
+ for (const item of d.cases) {
7341
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7342
+ }
7343
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7344
+ }
7345
+ case "attempt":
7346
+ case "transform":
7347
+ case "label":
7348
+ case "field":
7349
+ case "grammar":
7350
+ case "node":
7351
+ case "token":
7352
+ case "leaf":
7353
+ case "withCtx":
7354
+ case "optional":
7355
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7356
+ case "skip":
7357
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7358
+ case "scanTo":
7359
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7360
+ for (const item of d.skip) {
7361
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7362
+ }
7363
+ return false;
7364
+ case "lazy": {
7365
+ try {
7366
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7367
+ } catch {
7368
+ return true;
7369
+ }
7370
+ }
7371
+ case "unknown":
7372
+ return true;
7373
+ default:
7374
+ return false;
7375
+ }
7376
+ }
7377
+ function emitDispatchCombinator(def, ctx, pos) {
7378
+ const selector = emit(def.selector, ctx, pos);
7379
+ const outV = v(ctx, "_dval");
7380
+ const outE = v(ctx, "_dend");
7381
+ const keyV = v(ctx, "_dkey");
7382
+ const stmts = [
7383
+ ...selector.stmts,
7384
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7385
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7386
+ ];
7387
+ let wroteBranch = false;
7388
+ const emitSelectedTail = (parser2, keyword, condition) => {
7389
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7390
+ stmts.push(head);
7391
+ ctx.indent++;
7392
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7393
+ const errMark = mayError ? v(ctx, "_derr") : null;
7394
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7395
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7396
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7397
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7398
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7399
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7400
+ stmts.push(
7401
+ ...tail.stmts,
7402
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7403
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7404
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7405
+ );
7406
+ ctx.indent--;
7407
+ stmts.push(`${ind(ctx)}}`);
7408
+ };
7409
+ for (const entry of def.cases) {
7410
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7411
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7412
+ wroteBranch = true;
7413
+ }
7414
+ if (def.otherwise) {
7415
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7416
+ else emitSelectedTail(def.otherwise, "if", "true");
7417
+ } else {
7418
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7419
+ const fail = failArrBody(ctx, expected, selector.endVar);
7420
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7421
+ }
7422
+ return { stmts, valueVar: outV, endVar: outE };
7423
+ }
7105
7424
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7106
7425
  const superParser = def.parsers[superIndex];
7107
7426
  const regexDef = getCoreRegexDef(superParser);
@@ -7223,12 +7542,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7223
7542
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7224
7543
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7225
7544
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7545
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7226
7546
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7227
7547
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7228
7548
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7229
7549
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7230
7550
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7231
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7551
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7552
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7553
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7554
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7232
7555
  const failSlot = atStart ? staticFx : "_ctx._fx";
7233
7556
  stmts.push(`${ind0}if (${skipCond}) {`);
7234
7557
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7243,7 +7566,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7243
7566
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7244
7567
  );
7245
7568
  }
7246
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7569
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7570
+ const arm = emitFallible(p, ctx, pos, atStart);
7571
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7247
7572
  stmts.push(...armStmts);
7248
7573
  if (autoNot && autoNot.length > 0) {
7249
7574
  const anCode = v(ctx, "_anc");
@@ -7267,10 +7592,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7267
7592
  stmts.push(`${ind(ctx)}}`);
7268
7593
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}; ${coverageBase === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} });`} }`);
7269
7594
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7595
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7270
7596
  } else {
7271
7597
  const rejectedFlush = autoRejectedV === void 0 ? "" : `; for (const _rejectedId of ${autoRejectedV}) { _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'failure', offset: ${pos} }); _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'backtrack', offset: ${pos} }) }`;
7272
7598
  stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true${coverageBase === void 0 ? "" : `${rejectedFlush}; _ctx._grammarCoverage?.(${JSON.stringify(`${coverageBase}${i}`)}); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'selected', offset: ${pos}, end: ${endVar} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'success', offset: ${pos}, end: ${endVar} })`} }`);
7273
7599
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""}${coverageBase === void 0 ? "" : `; _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} })`} }`);
7600
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7274
7601
  }
7275
7602
  ctx.indent -= fsGuard ? 2 : 1;
7276
7603
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7427,7 +7754,8 @@ function emitMany(def, ctx, pos) {
7427
7754
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7428
7755
  );
7429
7756
  }
7430
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7757
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7758
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7431
7759
  stmts.push(...iterStmts);
7432
7760
  if (ctx.recovery) {
7433
7761
  const rrV = v(ctx, "_rr");
@@ -7446,7 +7774,8 @@ function emitMany(def, ctx, pos) {
7446
7774
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7447
7775
  );
7448
7776
  } else {
7449
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7777
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7778
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7450
7779
  }
7451
7780
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7452
7781
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7458,10 +7787,12 @@ function emitMany(def, ctx, pos) {
7458
7787
  function emitOptional(def, ctx, pos) {
7459
7788
  const valV = v(ctx, "_opt");
7460
7789
  const endV = v(ctx, "_opte");
7461
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7790
+ const inner = emitFallible(def.parser, ctx, pos, true);
7791
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7462
7792
  const ind0 = ind(ctx);
7463
7793
  const stmts = [
7464
7794
  ...lblStmts,
7795
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7465
7796
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7466
7797
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7467
7798
  ];
@@ -7493,7 +7824,7 @@ function emitAttempt(p, def, ctx, pos) {
7493
7824
  ...preGuard,
7494
7825
  `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${trivia2} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`,
7495
7826
  ...inner.stmts,
7496
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7827
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7497
7828
  ],
7498
7829
  valueVar: inner.valVar,
7499
7830
  endVar: inner.endVar
@@ -7507,18 +7838,21 @@ function emitSepBy(_p, def, ctx, pos) {
7507
7838
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7508
7839
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7509
7840
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7510
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7841
+ const first = emitFallible(def.parser, ctx, pos, true);
7842
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7511
7843
  const stmts = [
7512
7844
  `${ind(ctx)}const ${arrV} = []`,
7513
7845
  `${ind(ctx)}let ${curV} = ${pos}`,
7514
7846
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7515
7847
  ...firstStmts
7516
7848
  ];
7517
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7518
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7849
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7850
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7851
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7519
7852
  const rr = v(ctx, "_rr");
7520
7853
  return [
7521
7854
  `${ind(ctx)}if (!${nextOkVar}) {`,
7855
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7522
7856
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7523
7857
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7524
7858
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7547,6 +7881,7 @@ function emitSepBy(_p, def, ctx, pos) {
7547
7881
  stmts.push(
7548
7882
  `${ind(ctx)}let ${didV} = false`,
7549
7883
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
7884
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7550
7885
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7551
7886
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7552
7887
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7557,6 +7892,7 @@ function emitSepBy(_p, def, ctx, pos) {
7557
7892
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7558
7893
  ctx.indent++;
7559
7894
  } else {
7895
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7560
7896
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7561
7897
  ctx.indent++;
7562
7898
  stmts.push(
@@ -7585,16 +7921,18 @@ function emitSepBy(_p, def, ctx, pos) {
7585
7921
  );
7586
7922
  sepAtPos = spV;
7587
7923
  const rollbackToSep = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
7588
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7589
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
7924
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7925
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7926
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7590
7927
  const post = postSepMarks();
7591
7928
  stmts.push(...post.decl);
7592
7929
  const npV = v(ctx, "_np");
7593
7930
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7594
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7931
+ const next = emitFallible(def.parser, ctx, npV, true);
7932
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7595
7933
  stmts.push(
7596
7934
  ...nextStmts,
7597
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7935
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7598
7936
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7599
7937
  `${ind(ctx)}${curV} = ${nextEnd}`
7600
7938
  );
@@ -7603,14 +7941,16 @@ function emitSepBy(_p, def, ctx, pos) {
7603
7941
  const spV = v(ctx, "_sp");
7604
7942
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7605
7943
  sepAtPos = spV;
7606
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7607
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7944
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7945
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7946
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7608
7947
  const npV = v(ctx, "_np");
7609
7948
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7610
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7949
+ const next = emitFallible(def.parser, ctx, npV, true);
7950
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7611
7951
  stmts.push(
7612
7952
  ...nextStmts,
7613
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
7953
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7614
7954
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7615
7955
  `${ind(ctx)}${curV} = ${nextEnd}`
7616
7956
  );
@@ -7624,15 +7964,17 @@ function emitSepBy(_p, def, ctx, pos) {
7624
7964
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7625
7965
  );
7626
7966
  }
7627
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7628
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7967
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
7968
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
7969
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7629
7970
  const nextRb = markLv ? `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markRw}) _ctx._cstRawChildren.length = ${markRw}; ` : "";
7630
7971
  const post = postSepMarks();
7631
7972
  stmts.push(...post.decl);
7632
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
7973
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
7974
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7633
7975
  stmts.push(
7634
7976
  ...nextStmts,
7635
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7977
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7636
7978
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7637
7979
  `${ind(ctx)}${curV} = ${nextEnd}`
7638
7980
  );
@@ -8013,7 +8355,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8013
8355
  const rv = v(ctx, "_rt");
8014
8356
  const vv = v(ctx, "_rtv");
8015
8357
  const ev = v(ctx, "_rte");
8016
- const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span }`;
8358
+ const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `{ _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span, ...(${rv}.committed ? { committed: true } : {}) }`;
8017
8359
  const stmts = [
8018
8360
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8019
8361
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8094,7 +8436,7 @@ function emitRecover(def, ctx, pos) {
8094
8436
  return { stmts, valueVar: valVar, endVar };
8095
8437
  }
8096
8438
  function emitExpect(def, ctx, pos) {
8097
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8439
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8098
8440
  const ind0 = ind(ctx);
8099
8441
  const errV = v(ctx, "_err");
8100
8442
  const stmts = [
@@ -8106,6 +8448,7 @@ function emitExpect(def, ctx, pos) {
8106
8448
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8107
8449
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8108
8450
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8451
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8109
8452
  `${ind0} ${valVar} = ${errV}`,
8110
8453
  `${ind0} ${endVar} = ${pos}`,
8111
8454
  `${ind0} ${okVar} = true`,
@@ -8116,7 +8459,7 @@ function emitExpect(def, ctx, pos) {
8116
8459
  function emit(p, ctx, pos) {
8117
8460
  const replay = ctx.replayPrefix?.get(p);
8118
8461
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8119
- const dispatch = (emissionPos = pos) => {
8462
+ const dispatch2 = (emissionPos = pos) => {
8120
8463
  const savedRule = ctx.activeCoverageRuleId;
8121
8464
  const rule = ctx.coverage?.plan.rules.get(p);
8122
8465
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8156,14 +8499,14 @@ function emit(p, ctx, pos) {
8156
8499
  ctx.indent = 1;
8157
8500
  ctx.failLabel = "_pfail";
8158
8501
  ctx.recordFail = true;
8159
- const r = dispatch("_pos");
8502
+ const r = dispatch2("_pos");
8160
8503
  ctx.indent = savedIndent;
8161
8504
  ctx.failLabel = savedFailLabel;
8162
8505
  ctx.recordFail = savedRecord;
8163
8506
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8164
8507
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8165
8508
  }
8166
- return instrument(dispatch());
8509
+ return instrument(dispatch2());
8167
8510
  }
8168
8511
  function emitDispatch(p, ctx, pos) {
8169
8512
  const bal = p._balancedAmbient;
@@ -8196,6 +8539,8 @@ function emitDispatch(p, ctx, pos) {
8196
8539
  return emitSeq(def, ctx, pos);
8197
8540
  case "choice":
8198
8541
  return emitChoice(p, def, ctx, pos);
8542
+ case "dispatch":
8543
+ return emitDispatchCombinator(def, ctx, pos);
8199
8544
  case "attempt":
8200
8545
  return emitAttempt(p, def, ctx, pos);
8201
8546
  case "many":
@@ -8416,6 +8761,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8416
8761
  case "sequence":
8417
8762
  case "choice":
8418
8763
  return d.parsers.some((x) => hasNodeDef(x, seen));
8764
+ case "dispatch":
8765
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8419
8766
  case "sepBy":
8420
8767
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8421
8768
  case "scanTo":
@@ -8449,6 +8796,8 @@ function childrenOf2(def) {
8449
8796
  case "sequence":
8450
8797
  case "choice":
8451
8798
  return def.parsers;
8799
+ case "dispatch":
8800
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8452
8801
  case "many":
8453
8802
  case "attempt":
8454
8803
  case "optional":
@@ -8492,6 +8841,7 @@ function childrenOf2(def) {
8492
8841
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8493
8842
  "sequence",
8494
8843
  "choice",
8844
+ "dispatch",
8495
8845
  "many",
8496
8846
  "oneOrMore",
8497
8847
  "optional",
@@ -8585,7 +8935,7 @@ function ruleDependencies(ruleMap) {
8585
8935
  const seen = /* @__PURE__ */ new Set();
8586
8936
  const walk2 = (p, isRoot) => {
8587
8937
  const def = p._def;
8588
- const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
8938
+ const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
8589
8939
  if (!isRoot && boundary !== void 0) {
8590
8940
  found.add(boundary);
8591
8941
  return;
@@ -9190,6 +9540,8 @@ function childrenOf3(def) {
9190
9540
  case "sequence":
9191
9541
  case "choice":
9192
9542
  return def.parsers;
9543
+ case "dispatch":
9544
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9193
9545
  case "many":
9194
9546
  case "oneOrMore":
9195
9547
  case "optional":
@@ -9224,7 +9576,7 @@ function childrenOf3(def) {
9224
9576
  return [];
9225
9577
  }
9226
9578
  }
9227
- var ruleNameOf3 = (c) => c._ruleName;
9579
+ var ruleNameOf4 = (c) => c._ruleName;
9228
9580
  function lazyTarget(c) {
9229
9581
  if (c._def.tag !== "lazy") return null;
9230
9582
  try {
@@ -9339,6 +9691,9 @@ function evalRuleMapIR(ir) {
9339
9691
  "keywords",
9340
9692
  "sequence",
9341
9693
  "choice",
9694
+ "dispatch",
9695
+ "when",
9696
+ "otherwise",
9342
9697
  "attempt",
9343
9698
  "many",
9344
9699
  "oneOrMore",
@@ -9372,6 +9727,9 @@ function evalRuleMapIR(ir) {
9372
9727
  keywords,
9373
9728
  sequence,
9374
9729
  choice,
9730
+ dispatch,
9731
+ when,
9732
+ otherwise,
9375
9733
  attempt,
9376
9734
  many,
9377
9735
  oneOrMore,
@@ -9406,12 +9764,16 @@ var Serializer = class {
9406
9764
  emitted = /* @__PURE__ */ new Set();
9407
9765
  decls = [];
9408
9766
  ruleValues = /* @__PURE__ */ new Set();
9767
+ localRuleNames = /* @__PURE__ */ new Map();
9409
9768
  ruleMap;
9410
9769
  scanSkip;
9411
9770
  constructor(ruleMap, scanSkip = []) {
9412
9771
  this.ruleMap = ruleMap;
9413
9772
  this.scanSkip = scanSkip;
9414
- for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
9773
+ for (const [name, c] of ruleMap) {
9774
+ this.localRuleNames.set(c, name);
9775
+ this.ruleValues.add(this.body(c));
9776
+ }
9415
9777
  }
9416
9778
  /** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
9417
9779
  * bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
@@ -9422,7 +9784,7 @@ var Serializer = class {
9422
9784
  if (seen.has(c)) return;
9423
9785
  seen.add(c);
9424
9786
  if (c._def.tag === "lazy") {
9425
- if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9787
+ if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9426
9788
  const target = lazyTarget(c);
9427
9789
  if (target) this.assertNoRuleRef(target, seen);
9428
9790
  return;
@@ -9433,7 +9795,7 @@ var Serializer = class {
9433
9795
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
9434
9796
  * `g[name]` ref — only the top-level entry is unwrapped. */
9435
9797
  body(c) {
9436
- return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
9798
+ return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
9437
9799
  }
9438
9800
  run() {
9439
9801
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -9475,7 +9837,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9475
9837
  analyze(c, active) {
9476
9838
  const def = c._def;
9477
9839
  if (def.tag === "lazy") {
9478
- if (ruleNameOf3(c) !== void 0) return;
9840
+ if (ruleNameOf4(c) !== void 0) return;
9479
9841
  const target = lazyTarget(c);
9480
9842
  if (!target) throw new Unserializable("unresolved unnamed ref");
9481
9843
  if (active.has(target)) {
@@ -9527,7 +9889,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9527
9889
  const kid = (x) => this.ref(x, selfOf);
9528
9890
  switch (def.tag) {
9529
9891
  case "lazy": {
9530
- if (ruleNameOf3(c) !== void 0) return `g[${JSON.stringify(ruleNameOf3(c))}]`;
9892
+ const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
9893
+ if (name !== void 0) return `g[${JSON.stringify(name)}]`;
9531
9894
  const target = lazyTarget(c);
9532
9895
  if (selfOf && target === selfOf.comb) return selfOf.var;
9533
9896
  return this.ref(target, selfOf);
@@ -9552,6 +9915,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9552
9915
  });
9553
9916
  return `_gch([${items.join(", ")}])`;
9554
9917
  }
9918
+ case "dispatch": {
9919
+ const arms = def.cases.map(
9920
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
9921
+ );
9922
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
9923
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
9924
+ }
9555
9925
  case "many":
9556
9926
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9557
9927
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10771,6 +11141,12 @@ function coverageEntry(entry, collector, maps, trace) {
10771
11141
  const unsafeSequence = sequence;
10772
11142
  return unsafeSequence(...def.parsers.map(build));
10773
11143
  }
11144
+ case "dispatch": {
11145
+ const arms = def.cases.map((entry2) => when(entry2.keys, build(entry2.parser)));
11146
+ if (def.otherwise) arms.push(otherwise(build(def.otherwise)));
11147
+ const unsafeDispatch = dispatch;
11148
+ return unsafeDispatch(build(def.selector), ...arms);
11149
+ }
10774
11150
  case "many":
10775
11151
  return many(build(def.parser));
10776
11152
  case "oneOrMore":
@@ -11215,6 +11591,7 @@ export {
11215
11591
  createGrammarTraceSink,
11216
11592
  createVisitor,
11217
11593
  cstBuildHost,
11594
+ dispatch,
11218
11595
  duplicationFindingCount,
11219
11596
  expect,
11220
11597
  extractCharClasses,
@@ -11247,6 +11624,7 @@ export {
11247
11624
  oneOrMore,
11248
11625
  oneOrMoreSep,
11249
11626
  optional,
11627
+ otherwise,
11250
11628
  parse,
11251
11629
  parseDoc,
11252
11630
  parser,
@@ -11270,6 +11648,7 @@ export {
11270
11648
  triviaEntries,
11271
11649
  triviaKindMask,
11272
11650
  walk,
11651
+ when,
11273
11652
  withCtx,
11274
11653
  word
11275
11654
  };