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
@@ -85,6 +85,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
85
85
  return d.parsers.every(me);
86
86
  case "choice":
87
87
  return d.parsers.some(me);
88
+ case "dispatch":
89
+ return me(d.selector);
88
90
  case "transform":
89
91
  case "label":
90
92
  case "trivia":
@@ -158,6 +160,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
158
160
  case "keywords":
159
161
  return p._meta.firstSet;
160
162
  // terminals: no refs, cached set is exact
163
+ case "dispatch":
164
+ return fs2(d.selector);
161
165
  case "lazy":
162
166
  try {
163
167
  return fs2(d.thunk());
@@ -1106,6 +1110,8 @@ function derive(c, seen) {
1106
1110
  return [def.label];
1107
1111
  case "choice":
1108
1112
  return def.parsers.flatMap(deriveExpected2);
1113
+ case "dispatch":
1114
+ return deriveExpected2(def.selector);
1109
1115
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1110
1116
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1111
1117
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1244,6 +1250,7 @@ function choice(...args) {
1244
1250
  const result = parsers[idx].parse(input, pos, ctx);
1245
1251
  if (result.ok) return result;
1246
1252
  expected.push(...result.expected);
1253
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1247
1254
  return { ok: false, expected, span: { start: pos, end: pos } };
1248
1255
  }
1249
1256
  return {
@@ -1284,6 +1291,7 @@ function choice(...args) {
1284
1291
  rollbackCstCapture(ctx, mark);
1285
1292
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1286
1293
  expected.push(...result.expected);
1294
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1287
1295
  continue;
1288
1296
  }
1289
1297
  const checks = autoNot[i];
@@ -1963,6 +1971,75 @@ function sequence(...parsers) {
1963
1971
  };
1964
1972
  }
1965
1973
 
1974
+ // src/combinators/dispatch.ts
1975
+ function when(key, parser2) {
1976
+ const keys = Array.isArray(key) ? [...key] : [key];
1977
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
1978
+ for (const item of keys) {
1979
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
1980
+ }
1981
+ return { kind: "when", keys, parser: parser2 };
1982
+ }
1983
+ function otherwise(parser2) {
1984
+ return { kind: "otherwise", parser: parser2 };
1985
+ }
1986
+ function dispatch(selector, ...arms) {
1987
+ let fallback;
1988
+ const cases = [];
1989
+ const seen = /* @__PURE__ */ new Set();
1990
+ for (let i = 0; i < arms.length; i++) {
1991
+ const arm = arms[i];
1992
+ if (arm.kind === "otherwise") {
1993
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
1994
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
1995
+ fallback = arm.parser;
1996
+ continue;
1997
+ }
1998
+ for (const key of arm.keys) {
1999
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2000
+ seen.add(key);
2001
+ }
2002
+ cases.push({ keys: arm.keys, parser: arm.parser });
2003
+ }
2004
+ const meta = {
2005
+ firstSet: selector._meta.firstSet,
2006
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2007
+ isTrivia: false
2008
+ };
2009
+ const byKey = /* @__PURE__ */ new Map();
2010
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2011
+ return {
2012
+ _tag: "dispatch",
2013
+ _meta: meta,
2014
+ _def: {
2015
+ tag: "dispatch",
2016
+ selector,
2017
+ cases,
2018
+ ...fallback === void 0 ? {} : { otherwise: fallback }
2019
+ },
2020
+ parse(input, pos, ctx) {
2021
+ const selected = selector.parse(input, pos, ctx);
2022
+ if (!selected.ok) return selected;
2023
+ const tail = byKey.get(selected.value) ?? fallback;
2024
+ if (tail === void 0) {
2025
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2026
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2027
+ }
2028
+ const mark = saveTriviaMark(ctx);
2029
+ const result = tail.parse(input, selected.span.end, ctx);
2030
+ if (!result.ok) {
2031
+ rollbackTrivia(ctx, mark);
2032
+ return { ...result, committed: true };
2033
+ }
2034
+ return {
2035
+ ok: true,
2036
+ value: [selected.value, result.value],
2037
+ span: { start: pos, end: result.span.end }
2038
+ };
2039
+ }
2040
+ };
2041
+ }
2042
+
1966
2043
  // src/combinators/attempt.ts
1967
2044
  function attempt(parser2) {
1968
2045
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -1985,6 +2062,7 @@ function attempt(parser2) {
1985
2062
  if (result.ok) return result;
1986
2063
  rollbackCstCapture(ctx, mark);
1987
2064
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2065
+ if (result.committed) return result;
1988
2066
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1989
2067
  }
1990
2068
  };
@@ -2014,6 +2092,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2014
2092
  const result = combinator.parse(input, pos, ctx);
2015
2093
  if (!result.ok) {
2016
2094
  rollbackTrivia(ctx, mark);
2095
+ if (result.committed) return { fail: result, failPos: pos };
2017
2096
  return { fail: result, failPos: pos };
2018
2097
  }
2019
2098
  if (result.span.end === pos) {
@@ -2057,6 +2136,7 @@ function many(combinator, opts = {}) {
2057
2136
  const item = repItem(combinator, input, cur, ctx, guardable);
2058
2137
  if (item === "stop") break;
2059
2138
  if ("fail" in item) {
2139
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2060
2140
  const sync = ctx._tolerant ? ctx._sync : void 0;
2061
2141
  if (sync === void 0) break;
2062
2142
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2101,6 +2181,7 @@ function atLeast(combinator, min, max) {
2101
2181
  while (count < min) {
2102
2182
  const item = repItem(combinator, input, cur, ctx, guardable);
2103
2183
  if (item === "stop" || "fail" in item) {
2184
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2104
2185
  expected ??= deriveExpected(combinator);
2105
2186
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2106
2187
  }
@@ -2113,6 +2194,7 @@ function atLeast(combinator, min, max) {
2113
2194
  const item = repItem(combinator, input, cur, ctx, guardable);
2114
2195
  if (item === "stop") break;
2115
2196
  if ("fail" in item) {
2197
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2116
2198
  const sync = ctx._tolerant ? ctx._sync : void 0;
2117
2199
  if (sync === void 0) break;
2118
2200
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2151,6 +2233,7 @@ function optional(combinator) {
2151
2233
  const result = combinator.parse(input, pos, ctx);
2152
2234
  if (result.ok) return result;
2153
2235
  rollbackTrivia(ctx, mark);
2236
+ if (result.committed) return result;
2154
2237
  return { ok: true, value: null, span: { start: pos, end: pos } };
2155
2238
  }
2156
2239
  };
@@ -2190,6 +2273,7 @@ function sepBy(combinator, separator, opts = {}) {
2190
2273
  values.push(first.value);
2191
2274
  cur = first.span.end;
2192
2275
  } else {
2276
+ if (first.committed) return first;
2193
2277
  const term = ctx._tolerant ? ctx._sync : void 0;
2194
2278
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2195
2279
  if (min >= 1) return failAt2(pos);
@@ -2217,6 +2301,7 @@ function sepBy(combinator, separator, opts = {}) {
2217
2301
  const sep2 = separator.parse(input, sepPos, ctx);
2218
2302
  if (!sep2.ok) {
2219
2303
  rollbackTrivia(ctx, loopMark);
2304
+ if (sep2.committed) return sep2;
2220
2305
  break;
2221
2306
  }
2222
2307
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2232,6 +2317,7 @@ function sepBy(combinator, separator, opts = {}) {
2232
2317
  }
2233
2318
  const next = combinator.parse(input, nextPos, ctx);
2234
2319
  if (!next.ok) {
2320
+ if (next.committed) return next;
2235
2321
  const term = ctx._tolerant ? ctx._sync : void 0;
2236
2322
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2237
2323
  expected ??= deriveExpected(combinator);
@@ -2287,6 +2373,11 @@ function markUnusedValues(root) {
2287
2373
  case "choice":
2288
2374
  for (const p of def.parsers) visit(p, consumed);
2289
2375
  return;
2376
+ case "dispatch":
2377
+ visit(def.selector, consumed);
2378
+ for (const entry of def.cases) visit(entry.parser, consumed);
2379
+ if (def.otherwise) visit(def.otherwise, consumed);
2380
+ return;
2290
2381
  case "node":
2291
2382
  visit(def.parser, false);
2292
2383
  return;
@@ -2358,6 +2449,16 @@ function tagRule(r, key) {
2358
2449
  r._ruleName = key;
2359
2450
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
2360
2451
  }
2452
+ function isDefinableRef(v2) {
2453
+ return !!v2 && typeof v2 === "object" && "_def" in v2 && v2._def.tag === "lazy" && typeof v2.define === "function";
2454
+ }
2455
+ function ruleNameOf(r) {
2456
+ return r._ruleName;
2457
+ }
2458
+ function isNamedRuleRefForAnotherRule(r, key) {
2459
+ const name = r._def.tag === "lazy" ? ruleNameOf(r) : void 0;
2460
+ return name !== void 0 && name !== key;
2461
+ }
2361
2462
  function rules(a, b) {
2362
2463
  const factory = typeof a === "function" ? a : b;
2363
2464
  const options = typeof a === "function" ? b : a;
@@ -2378,11 +2479,21 @@ function rules(a, b) {
2378
2479
  for (const key of Object.keys(definitions)) {
2379
2480
  const placeholder = cache[key];
2380
2481
  const parser2 = definitions[key];
2381
- if (placeholder !== void 0 && typeof placeholder.define === "function") {
2382
- tagRule(parser2, key);
2482
+ if (placeholder === parser2) {
2483
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`);
2484
+ }
2485
+ if (isDefinableRef(placeholder)) {
2486
+ if (!isNamedRuleRefForAnotherRule(parser2, key)) tagRule(parser2, key);
2383
2487
  placeholder.define(parser2);
2384
2488
  placeholder._meta.firstSet = parser2._meta.firstSet;
2385
2489
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
2490
+ } else if (isNamedRuleRefForAnotherRule(parser2, key)) {
2491
+ const alias = ref();
2492
+ tagRule(alias, key);
2493
+ alias.define(parser2);
2494
+ alias._meta.firstSet = parser2._meta.firstSet;
2495
+ alias._meta.canMatchNewline = parser2._meta.canMatchNewline;
2496
+ cache[key] = alias;
2386
2497
  } else {
2387
2498
  tagRule(parser2, key);
2388
2499
  cache[key] = parser2;
@@ -2542,6 +2653,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2542
2653
  case "sequence":
2543
2654
  case "choice":
2544
2655
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2656
+ case "dispatch":
2657
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2545
2658
  case "sepBy":
2546
2659
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2547
2660
  case "skip":
@@ -2609,6 +2722,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2609
2722
  return parserHasTriviaSite(d.parser, seen);
2610
2723
  case "choice":
2611
2724
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2725
+ case "dispatch":
2726
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2612
2727
  case "grammar":
2613
2728
  return parserHasTriviaSite(d.parser, seen);
2614
2729
  case "lazy": {
@@ -2634,6 +2749,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2634
2749
  case "sequence":
2635
2750
  case "choice":
2636
2751
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2752
+ case "dispatch":
2753
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2637
2754
  case "sepBy":
2638
2755
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2639
2756
  case "skip":
@@ -3008,7 +3125,7 @@ function leaf(root, fn) {
3008
3125
  }
3009
3126
 
3010
3127
  // src/version.ts
3011
- var PARSEMAN_VERSION = "0.37.0";
3128
+ var PARSEMAN_VERSION = "0.39.0";
3012
3129
 
3013
3130
  // src/cst/host-mode.ts
3014
3131
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3062,7 +3179,7 @@ function firstSetToString(fs2) {
3062
3179
  if (fs2.kind === "empty") return "(empty)";
3063
3180
  return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3064
3181
  }
3065
- var ruleNameOf = (p) => p._ruleName;
3182
+ var ruleNameOf2 = (p) => p._ruleName;
3066
3183
  var SUGGESTIONS = {
3067
3184
  "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.",
3068
3185
  "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.",
@@ -3164,6 +3281,8 @@ function classifyBroadArm(arm, resolve2) {
3164
3281
  }
3165
3282
  return null;
3166
3283
  }
3284
+ case "dispatch":
3285
+ return walk2(d.selector);
3167
3286
  default:
3168
3287
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3169
3288
  }
@@ -3220,7 +3339,7 @@ function detectAntiPatterns(rule, arms) {
3220
3339
  return out;
3221
3340
  }
3222
3341
  function analyzeGating(entry, opts) {
3223
- return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3342
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3224
3343
  }
3225
3344
  function analyzeGatingRules(ruleMap, opts) {
3226
3345
  const raw = [];
@@ -3253,7 +3372,7 @@ function analyzeGatingRules(ruleMap, opts) {
3253
3372
  return;
3254
3373
  }
3255
3374
  const d = def;
3256
- const rule = ruleNameOf(p) ?? enclosingRule;
3375
+ const rule = ruleNameOf2(p) ?? enclosingRule;
3257
3376
  if (d.tag === "choice") {
3258
3377
  raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3259
3378
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
@@ -3263,6 +3382,11 @@ function analyzeGatingRules(ruleMap, opts) {
3263
3382
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3264
3383
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3265
3384
  if (rec[k]) kids.push(rec[k]);
3385
+ if (d.tag === "dispatch") {
3386
+ kids.push(d.selector);
3387
+ for (const c of d.cases) kids.push(c.parser);
3388
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3389
+ }
3266
3390
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3267
3391
  if (d.tag === "lazy") {
3268
3392
  try {
@@ -3272,7 +3396,7 @@ function analyzeGatingRules(ruleMap, opts) {
3272
3396
  }
3273
3397
  for (const k of kids) visit(k, rule);
3274
3398
  };
3275
- for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3399
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3276
3400
  const perRule = /* @__PURE__ */ new Map();
3277
3401
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3278
3402
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3369,6 +3493,12 @@ function childrenOf(d) {
3369
3493
  case "sequence":
3370
3494
  case "choice":
3371
3495
  return d.parsers;
3496
+ case "dispatch":
3497
+ return [
3498
+ d.selector,
3499
+ ...d.cases.map((c) => c.parser),
3500
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3501
+ ];
3372
3502
  case "skip":
3373
3503
  return [d.main, d.skipped];
3374
3504
  case "sepBy":
@@ -3396,7 +3526,7 @@ function childrenOf(d) {
3396
3526
  }
3397
3527
  }
3398
3528
  }
3399
- var ruleNameOf2 = (p) => p._ruleName;
3529
+ var ruleNameOf3 = (p) => p._ruleName;
3400
3530
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
3401
3531
  function payloadKey(p, d) {
3402
3532
  switch (d.tag) {
@@ -3407,7 +3537,7 @@ function payloadKey(p, d) {
3407
3537
  case "keywords":
3408
3538
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
3409
3539
  case "lazy": {
3410
- const n = ruleNameOf2(p);
3540
+ const n = ruleNameOf3(p);
3411
3541
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
3412
3542
  }
3413
3543
  case "label":
@@ -3425,6 +3555,8 @@ function payloadKey(p, d) {
3425
3555
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3426
3556
  case "sepBy":
3427
3557
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3558
+ case "dispatch":
3559
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3428
3560
  case "expect":
3429
3561
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3430
3562
  case "scanTo":
@@ -3474,7 +3606,7 @@ var Interner = class {
3474
3606
  var MAX_RENDER = 160;
3475
3607
  function render(p, depth = 3) {
3476
3608
  const d = p._def;
3477
- const name = ruleNameOf2(p);
3609
+ const name = ruleNameOf3(p);
3478
3610
  switch (d.tag) {
3479
3611
  case "literal":
3480
3612
  return `literal('${d.value}')`;
@@ -3496,6 +3628,11 @@ function render(p, depth = 3) {
3496
3628
  return `sequence(${kids.join(", ")})`;
3497
3629
  case "choice":
3498
3630
  return `choice(${kids.join(", ")})`;
3631
+ case "dispatch": {
3632
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3633
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3634
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3635
+ }
3499
3636
  case "node":
3500
3637
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3501
3638
  case "label":
@@ -3519,6 +3656,10 @@ function segment(d, index) {
3519
3656
  return `seq[${index}]`;
3520
3657
  case "choice":
3521
3658
  return `choice[${index}]`;
3659
+ case "dispatch":
3660
+ if (index === 0) return "dispatch.selector";
3661
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3662
+ return "dispatch.otherwise";
3522
3663
  case "node":
3523
3664
  return `node(${d.type ?? ""})`;
3524
3665
  case "label":
@@ -3653,7 +3794,7 @@ function keywordAlternationHazards(words, boundary) {
3653
3794
  return out;
3654
3795
  }
3655
3796
  function analyzeDuplication(entry, opts) {
3656
- return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3797
+ return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3657
3798
  }
3658
3799
  function assertAnalyzable(ruleMap) {
3659
3800
  for (const [name, rule] of ruleMap) {
@@ -3694,7 +3835,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
3694
3835
  const visit = (p, rule, path2, enclosingNode, enclosingReducer) => {
3695
3836
  if (visited.has(p)) return;
3696
3837
  const d = p._def;
3697
- const own = ruleNameOf2(p);
3838
+ const own = ruleNameOf3(p);
3698
3839
  const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path2 };
3699
3840
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
3700
3841
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
@@ -3707,12 +3848,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
3707
3848
  if (d.tag === "lazy") {
3708
3849
  try {
3709
3850
  const target = d.thunk();
3710
- visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
3851
+ visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
3711
3852
  } catch {
3712
3853
  }
3713
3854
  }
3714
3855
  };
3715
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
3856
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
3716
3857
  const accept = new Set(opts?.accept ?? []);
3717
3858
  const used = /* @__PURE__ */ new Set();
3718
3859
  const keep = (f) => {
@@ -4772,6 +4913,8 @@ function children(def, winners) {
4772
4913
  case "sequence":
4773
4914
  case "choice":
4774
4915
  return def.parsers;
4916
+ case "dispatch":
4917
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4775
4918
  case "many":
4776
4919
  case "oneOrMore":
4777
4920
  case "optional":
@@ -6236,6 +6379,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6236
6379
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6237
6380
  return { alts };
6238
6381
  }
6382
+ case "dispatch":
6383
+ return rec(d.selector);
6239
6384
  case "sequence": {
6240
6385
  const chain = [];
6241
6386
  for (const term of d.parsers) {
@@ -6318,6 +6463,9 @@ function failReturn(expected, posExpr) {
6318
6463
  function failReturnArr(expectedArr, posExpr) {
6319
6464
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6320
6465
  }
6466
+ function committedReturnArr(expectedArr, posExpr) {
6467
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6468
+ }
6321
6469
  function hoistExpected(ctx, constArrSource) {
6322
6470
  let name = ctx.expectedMap.get(constArrSource);
6323
6471
  if (name === void 0) {
@@ -6354,15 +6502,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6354
6502
  return probe + trace + failReturnArr(expectedArr, posExpr);
6355
6503
  }
6356
6504
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6357
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6505
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6506
+ }
6507
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6508
+ if (ctx.failLabel) {
6509
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6510
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6511
+ }
6512
+ return committedReturnArr(expectedArr, posExpr);
6358
6513
  }
6359
6514
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6360
6515
  if (srcCtx !== "_ctx") {
6361
6516
  if (ctx.failLabel) {
6362
6517
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6363
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6518
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6364
6519
  }
6365
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6520
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6366
6521
  }
6367
6522
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6368
6523
  return resultFromRecorded();
@@ -6575,6 +6730,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6575
6730
  // nothing committed remains.
6576
6731
  case "choice":
6577
6732
  return false;
6733
+ case "dispatch":
6734
+ 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);
6578
6735
  case "attempt":
6579
6736
  return false;
6580
6737
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6619,6 +6776,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6619
6776
  return true;
6620
6777
  }
6621
6778
  }
6779
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6780
+ if (seen.has(p)) return false;
6781
+ seen.add(p);
6782
+ const d = p._def;
6783
+ switch (d.tag) {
6784
+ case "dispatch":
6785
+ return true;
6786
+ case "choice":
6787
+ case "sequence":
6788
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6789
+ case "many":
6790
+ case "oneOrMore":
6791
+ case "optional":
6792
+ case "attempt":
6793
+ case "transform":
6794
+ case "label":
6795
+ case "field":
6796
+ case "grammar":
6797
+ case "node":
6798
+ return mayCommitFailure(d.parser, seen);
6799
+ case "sepBy":
6800
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6801
+ case "skip":
6802
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6803
+ case "token":
6804
+ case "leaf":
6805
+ return mayCommitFailure(d.parser, seen);
6806
+ case "withCtx":
6807
+ return mayCommitFailure(d.parser, seen);
6808
+ case "scanTo":
6809
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6810
+ case "lazy": {
6811
+ try {
6812
+ return mayCommitFailure(d.thunk(), seen);
6813
+ } catch {
6814
+ return true;
6815
+ }
6816
+ }
6817
+ case "unknown":
6818
+ return true;
6819
+ default:
6820
+ return false;
6821
+ }
6822
+ }
6622
6823
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6623
6824
  if (seen.has(p)) return false;
6624
6825
  seen.add(p);
@@ -6640,6 +6841,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6640
6841
  case "sequence":
6641
6842
  case "choice":
6642
6843
  return d.parsers.some((x) => capturesLeaf(x, seen));
6844
+ case "dispatch":
6845
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6643
6846
  case "sepBy":
6644
6847
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6645
6848
  case "many":
@@ -6686,6 +6889,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6686
6889
  ctx.recordFail = savedRecord;
6687
6890
  const ind0 = ind(ctx);
6688
6891
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
6892
+ const mayCommit = mayCommitFailure(inner);
6689
6893
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6690
6894
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6691
6895
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6699,6 +6903,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6699
6903
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6700
6904
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6701
6905
  ] : [],
6906
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6702
6907
  `${ind0}${lbl}: {`,
6703
6908
  ...r.stmts,
6704
6909
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6707,7 +6912,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6707
6912
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6708
6913
  ] : []
6709
6914
  ];
6710
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
6915
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6711
6916
  }
6712
6917
  var CHARCODE_CHAIN_MAX = 16;
6713
6918
  function emitLit(def, ctx, pos) {
@@ -7084,6 +7289,118 @@ function emitChoice(parser2, def, ctx, pos) {
7084
7289
  }
7085
7290
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7086
7291
  }
7292
+ function emitTailFallible(parser2, ctx, pos) {
7293
+ const savedRecord = ctx.recordFail;
7294
+ ctx.recordFail = true;
7295
+ const out = emitFallible(parser2, ctx, pos);
7296
+ ctx.recordFail = savedRecord;
7297
+ return out;
7298
+ }
7299
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7300
+ if (seen.has(p)) return false;
7301
+ seen.add(p);
7302
+ const d = p._def;
7303
+ switch (d.tag) {
7304
+ case "expect":
7305
+ case "recover":
7306
+ return true;
7307
+ case "many":
7308
+ case "oneOrMore":
7309
+ case "sepBy":
7310
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7311
+ case "choice":
7312
+ case "sequence": {
7313
+ for (const item of d.parsers) {
7314
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7315
+ }
7316
+ return false;
7317
+ }
7318
+ case "dispatch": {
7319
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7320
+ for (const item of d.cases) {
7321
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7322
+ }
7323
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7324
+ }
7325
+ case "attempt":
7326
+ case "transform":
7327
+ case "label":
7328
+ case "field":
7329
+ case "grammar":
7330
+ case "node":
7331
+ case "token":
7332
+ case "leaf":
7333
+ case "withCtx":
7334
+ case "optional":
7335
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7336
+ case "skip":
7337
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7338
+ case "scanTo":
7339
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7340
+ for (const item of d.skip) {
7341
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7342
+ }
7343
+ return false;
7344
+ case "lazy": {
7345
+ try {
7346
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7347
+ } catch {
7348
+ return true;
7349
+ }
7350
+ }
7351
+ case "unknown":
7352
+ return true;
7353
+ default:
7354
+ return false;
7355
+ }
7356
+ }
7357
+ function emitDispatchCombinator(def, ctx, pos) {
7358
+ const selector = emit(def.selector, ctx, pos);
7359
+ const outV = v(ctx, "_dval");
7360
+ const outE = v(ctx, "_dend");
7361
+ const keyV = v(ctx, "_dkey");
7362
+ const stmts = [
7363
+ ...selector.stmts,
7364
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7365
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7366
+ ];
7367
+ let wroteBranch = false;
7368
+ const emitSelectedTail = (parser2, keyword, condition) => {
7369
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7370
+ stmts.push(head);
7371
+ ctx.indent++;
7372
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7373
+ const errMark = mayError ? v(ctx, "_derr") : null;
7374
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7375
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7376
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7377
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7378
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7379
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7380
+ stmts.push(
7381
+ ...tail.stmts,
7382
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7383
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7384
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7385
+ );
7386
+ ctx.indent--;
7387
+ stmts.push(`${ind(ctx)}}`);
7388
+ };
7389
+ for (const entry of def.cases) {
7390
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7391
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7392
+ wroteBranch = true;
7393
+ }
7394
+ if (def.otherwise) {
7395
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7396
+ else emitSelectedTail(def.otherwise, "if", "true");
7397
+ } else {
7398
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7399
+ const fail = failArrBody(ctx, expected, selector.endVar);
7400
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7401
+ }
7402
+ return { stmts, valueVar: outV, endVar: outE };
7403
+ }
7087
7404
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7088
7405
  const superParser = def.parsers[superIndex];
7089
7406
  const regexDef = getCoreRegexDef(superParser);
@@ -7205,12 +7522,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7205
7522
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7206
7523
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7207
7524
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7525
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7208
7526
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7209
7527
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7210
7528
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7211
7529
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7212
7530
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7213
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7531
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7532
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7533
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7534
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7214
7535
  const failSlot = atStart ? staticFx : "_ctx._fx";
7215
7536
  stmts.push(`${ind0}if (${skipCond}) {`);
7216
7537
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7225,7 +7546,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7225
7546
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7226
7547
  );
7227
7548
  }
7228
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7549
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7550
+ const arm = emitFallible(p, ctx, pos, atStart);
7551
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7229
7552
  stmts.push(...armStmts);
7230
7553
  if (autoNot && autoNot.length > 0) {
7231
7554
  const anCode = v(ctx, "_anc");
@@ -7249,10 +7572,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7249
7572
  stmts.push(`${ind(ctx)}}`);
7250
7573
  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} });`} }`);
7251
7574
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7575
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7252
7576
  } else {
7253
7577
  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} }) }`;
7254
7578
  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} })`} }`);
7255
7579
  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} })`} }`);
7580
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7256
7581
  }
7257
7582
  ctx.indent -= fsGuard ? 2 : 1;
7258
7583
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7409,7 +7734,8 @@ function emitMany(def, ctx, pos) {
7409
7734
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7410
7735
  );
7411
7736
  }
7412
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7737
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7738
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7413
7739
  stmts.push(...iterStmts);
7414
7740
  if (ctx.recovery) {
7415
7741
  const rrV = v(ctx, "_rr");
@@ -7428,7 +7754,8 @@ function emitMany(def, ctx, pos) {
7428
7754
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7429
7755
  );
7430
7756
  } else {
7431
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7757
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7758
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7432
7759
  }
7433
7760
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7434
7761
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7440,10 +7767,12 @@ function emitMany(def, ctx, pos) {
7440
7767
  function emitOptional(def, ctx, pos) {
7441
7768
  const valV = v(ctx, "_opt");
7442
7769
  const endV = v(ctx, "_opte");
7443
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7770
+ const inner = emitFallible(def.parser, ctx, pos, true);
7771
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7444
7772
  const ind0 = ind(ctx);
7445
7773
  const stmts = [
7446
7774
  ...lblStmts,
7775
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7447
7776
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7448
7777
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7449
7778
  ];
@@ -7475,7 +7804,7 @@ function emitAttempt(p, def, ctx, pos) {
7475
7804
  ...preGuard,
7476
7805
  `${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`,
7477
7806
  ...inner.stmts,
7478
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7807
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7479
7808
  ],
7480
7809
  valueVar: inner.valVar,
7481
7810
  endVar: inner.endVar
@@ -7489,18 +7818,21 @@ function emitSepBy(_p, def, ctx, pos) {
7489
7818
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7490
7819
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7491
7820
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7492
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7821
+ const first = emitFallible(def.parser, ctx, pos, true);
7822
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7493
7823
  const stmts = [
7494
7824
  `${ind(ctx)}const ${arrV} = []`,
7495
7825
  `${ind(ctx)}let ${curV} = ${pos}`,
7496
7826
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7497
7827
  ...firstStmts
7498
7828
  ];
7499
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7500
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7829
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7830
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7831
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7501
7832
  const rr = v(ctx, "_rr");
7502
7833
  return [
7503
7834
  `${ind(ctx)}if (!${nextOkVar}) {`,
7835
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7504
7836
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7505
7837
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7506
7838
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7529,6 +7861,7 @@ function emitSepBy(_p, def, ctx, pos) {
7529
7861
  stmts.push(
7530
7862
  `${ind(ctx)}let ${didV} = false`,
7531
7863
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
7864
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7532
7865
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7533
7866
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7534
7867
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7539,6 +7872,7 @@ function emitSepBy(_p, def, ctx, pos) {
7539
7872
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7540
7873
  ctx.indent++;
7541
7874
  } else {
7875
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7542
7876
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7543
7877
  ctx.indent++;
7544
7878
  stmts.push(
@@ -7567,16 +7901,18 @@ function emitSepBy(_p, def, ctx, pos) {
7567
7901
  );
7568
7902
  sepAtPos = spV;
7569
7903
  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}; `;
7570
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7571
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
7904
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7905
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7906
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7572
7907
  const post = postSepMarks();
7573
7908
  stmts.push(...post.decl);
7574
7909
  const npV = v(ctx, "_np");
7575
7910
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7576
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7911
+ const next = emitFallible(def.parser, ctx, npV, true);
7912
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7577
7913
  stmts.push(
7578
7914
  ...nextStmts,
7579
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7915
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7580
7916
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7581
7917
  `${ind(ctx)}${curV} = ${nextEnd}`
7582
7918
  );
@@ -7585,14 +7921,16 @@ function emitSepBy(_p, def, ctx, pos) {
7585
7921
  const spV = v(ctx, "_sp");
7586
7922
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7587
7923
  sepAtPos = spV;
7588
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7589
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7924
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7925
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7926
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7590
7927
  const npV = v(ctx, "_np");
7591
7928
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7592
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7929
+ const next = emitFallible(def.parser, ctx, npV, true);
7930
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7593
7931
  stmts.push(
7594
7932
  ...nextStmts,
7595
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
7933
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7596
7934
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7597
7935
  `${ind(ctx)}${curV} = ${nextEnd}`
7598
7936
  );
@@ -7606,15 +7944,17 @@ function emitSepBy(_p, def, ctx, pos) {
7606
7944
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7607
7945
  );
7608
7946
  }
7609
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7610
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7947
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7948
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7949
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7611
7950
  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}; ` : "";
7612
7951
  const post = postSepMarks();
7613
7952
  stmts.push(...post.decl);
7614
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
7953
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
7954
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7615
7955
  stmts.push(
7616
7956
  ...nextStmts,
7617
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7957
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7618
7958
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7619
7959
  `${ind(ctx)}${curV} = ${nextEnd}`
7620
7960
  );
@@ -7995,7 +8335,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
7995
8335
  const rv = v(ctx, "_rt");
7996
8336
  const vv = v(ctx, "_rtv");
7997
8337
  const ev = v(ctx, "_rte");
7998
- 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 }`;
8338
+ 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 } : {}) }`;
7999
8339
  const stmts = [
8000
8340
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8001
8341
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8076,7 +8416,7 @@ function emitRecover(def, ctx, pos) {
8076
8416
  return { stmts, valueVar: valVar, endVar };
8077
8417
  }
8078
8418
  function emitExpect(def, ctx, pos) {
8079
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8419
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8080
8420
  const ind0 = ind(ctx);
8081
8421
  const errV = v(ctx, "_err");
8082
8422
  const stmts = [
@@ -8088,6 +8428,7 @@ function emitExpect(def, ctx, pos) {
8088
8428
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8089
8429
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8090
8430
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8431
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8091
8432
  `${ind0} ${valVar} = ${errV}`,
8092
8433
  `${ind0} ${endVar} = ${pos}`,
8093
8434
  `${ind0} ${okVar} = true`,
@@ -8098,7 +8439,7 @@ function emitExpect(def, ctx, pos) {
8098
8439
  function emit(p, ctx, pos) {
8099
8440
  const replay = ctx.replayPrefix?.get(p);
8100
8441
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8101
- const dispatch = (emissionPos = pos) => {
8442
+ const dispatch2 = (emissionPos = pos) => {
8102
8443
  const savedRule = ctx.activeCoverageRuleId;
8103
8444
  const rule = ctx.coverage?.plan.rules.get(p);
8104
8445
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8138,14 +8479,14 @@ function emit(p, ctx, pos) {
8138
8479
  ctx.indent = 1;
8139
8480
  ctx.failLabel = "_pfail";
8140
8481
  ctx.recordFail = true;
8141
- const r = dispatch("_pos");
8482
+ const r = dispatch2("_pos");
8142
8483
  ctx.indent = savedIndent;
8143
8484
  ctx.failLabel = savedFailLabel;
8144
8485
  ctx.recordFail = savedRecord;
8145
8486
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8146
8487
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8147
8488
  }
8148
- return instrument(dispatch());
8489
+ return instrument(dispatch2());
8149
8490
  }
8150
8491
  function emitDispatch(p, ctx, pos) {
8151
8492
  const bal = p._balancedAmbient;
@@ -8178,6 +8519,8 @@ function emitDispatch(p, ctx, pos) {
8178
8519
  return emitSeq(def, ctx, pos);
8179
8520
  case "choice":
8180
8521
  return emitChoice(p, def, ctx, pos);
8522
+ case "dispatch":
8523
+ return emitDispatchCombinator(def, ctx, pos);
8181
8524
  case "attempt":
8182
8525
  return emitAttempt(p, def, ctx, pos);
8183
8526
  case "many":
@@ -8398,6 +8741,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8398
8741
  case "sequence":
8399
8742
  case "choice":
8400
8743
  return d.parsers.some((x) => hasNodeDef(x, seen));
8744
+ case "dispatch":
8745
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8401
8746
  case "sepBy":
8402
8747
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8403
8748
  case "scanTo":
@@ -8431,6 +8776,8 @@ function childrenOf2(def) {
8431
8776
  case "sequence":
8432
8777
  case "choice":
8433
8778
  return def.parsers;
8779
+ case "dispatch":
8780
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8434
8781
  case "many":
8435
8782
  case "attempt":
8436
8783
  case "optional":
@@ -8474,6 +8821,7 @@ function childrenOf2(def) {
8474
8821
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8475
8822
  "sequence",
8476
8823
  "choice",
8824
+ "dispatch",
8477
8825
  "many",
8478
8826
  "oneOrMore",
8479
8827
  "optional",
@@ -8567,7 +8915,7 @@ function ruleDependencies(ruleMap) {
8567
8915
  const seen = /* @__PURE__ */ new Set();
8568
8916
  const walk2 = (p, isRoot) => {
8569
8917
  const def = p._def;
8570
- const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
8918
+ const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
8571
8919
  if (!isRoot && boundary !== void 0) {
8572
8920
  found.add(boundary);
8573
8921
  return;
@@ -9298,6 +9646,8 @@ function childrenOf3(def) {
9298
9646
  case "sequence":
9299
9647
  case "choice":
9300
9648
  return def.parsers;
9649
+ case "dispatch":
9650
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9301
9651
  case "many":
9302
9652
  case "oneOrMore":
9303
9653
  case "optional":
@@ -9332,7 +9682,7 @@ function childrenOf3(def) {
9332
9682
  return [];
9333
9683
  }
9334
9684
  }
9335
- var ruleNameOf3 = (c) => c._ruleName;
9685
+ var ruleNameOf4 = (c) => c._ruleName;
9336
9686
  function lazyTarget(c) {
9337
9687
  if (c._def.tag !== "lazy") return null;
9338
9688
  try {
@@ -9447,6 +9797,9 @@ function evalRuleMapIR(ir) {
9447
9797
  "keywords",
9448
9798
  "sequence",
9449
9799
  "choice",
9800
+ "dispatch",
9801
+ "when",
9802
+ "otherwise",
9450
9803
  "attempt",
9451
9804
  "many",
9452
9805
  "oneOrMore",
@@ -9480,6 +9833,9 @@ function evalRuleMapIR(ir) {
9480
9833
  keywords,
9481
9834
  sequence,
9482
9835
  choice,
9836
+ dispatch,
9837
+ when,
9838
+ otherwise,
9483
9839
  attempt,
9484
9840
  many,
9485
9841
  oneOrMore,
@@ -9514,12 +9870,16 @@ var Serializer = class {
9514
9870
  emitted = /* @__PURE__ */ new Set();
9515
9871
  decls = [];
9516
9872
  ruleValues = /* @__PURE__ */ new Set();
9873
+ localRuleNames = /* @__PURE__ */ new Map();
9517
9874
  ruleMap;
9518
9875
  scanSkip;
9519
9876
  constructor(ruleMap, scanSkip = []) {
9520
9877
  this.ruleMap = ruleMap;
9521
9878
  this.scanSkip = scanSkip;
9522
- for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
9879
+ for (const [name, c] of ruleMap) {
9880
+ this.localRuleNames.set(c, name);
9881
+ this.ruleValues.add(this.body(c));
9882
+ }
9523
9883
  }
9524
9884
  /** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
9525
9885
  * bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
@@ -9530,7 +9890,7 @@ var Serializer = class {
9530
9890
  if (seen.has(c)) return;
9531
9891
  seen.add(c);
9532
9892
  if (c._def.tag === "lazy") {
9533
- if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9893
+ if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9534
9894
  const target = lazyTarget(c);
9535
9895
  if (target) this.assertNoRuleRef(target, seen);
9536
9896
  return;
@@ -9541,7 +9901,7 @@ var Serializer = class {
9541
9901
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
9542
9902
  * `g[name]` ref — only the top-level entry is unwrapped. */
9543
9903
  body(c) {
9544
- return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
9904
+ return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
9545
9905
  }
9546
9906
  run() {
9547
9907
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -9583,7 +9943,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9583
9943
  analyze(c, active) {
9584
9944
  const def = c._def;
9585
9945
  if (def.tag === "lazy") {
9586
- if (ruleNameOf3(c) !== void 0) return;
9946
+ if (ruleNameOf4(c) !== void 0) return;
9587
9947
  const target = lazyTarget(c);
9588
9948
  if (!target) throw new Unserializable("unresolved unnamed ref");
9589
9949
  if (active.has(target)) {
@@ -9635,7 +9995,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9635
9995
  const kid = (x) => this.ref(x, selfOf);
9636
9996
  switch (def.tag) {
9637
9997
  case "lazy": {
9638
- if (ruleNameOf3(c) !== void 0) return `g[${JSON.stringify(ruleNameOf3(c))}]`;
9998
+ const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
9999
+ if (name !== void 0) return `g[${JSON.stringify(name)}]`;
9639
10000
  const target = lazyTarget(c);
9640
10001
  if (selfOf && target === selfOf.comb) return selfOf.var;
9641
10002
  return this.ref(target, selfOf);
@@ -9660,6 +10021,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9660
10021
  });
9661
10022
  return `_gch([${items.join(", ")}])`;
9662
10023
  }
10024
+ case "dispatch": {
10025
+ const arms = def.cases.map(
10026
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10027
+ );
10028
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10029
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10030
+ }
9663
10031
  case "many":
9664
10032
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9665
10033
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10145,6 +10513,28 @@ function stripTsFromSource(node2, code) {
10145
10513
  function isWordFactory(v2) {
10146
10514
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10147
10515
  }
10516
+ function isStaticValueEntry(v2) {
10517
+ return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10518
+ }
10519
+ function wordFactoryFromArgs(args, scope, code, mfs) {
10520
+ const [boundaryOrOptsArg, optsArg] = args;
10521
+ if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10522
+ const rawBoundaryOrOpts = boundaryOrOptsArg === void 0 ? void 0 : anyValue(boundaryOrOptsArg, scope, code, mfs);
10523
+ const boundaryOrOpts = rawBoundaryOrOpts === void 0 ? "_0-9A-Za-z" : rawBoundaryOrOpts;
10524
+ const opts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10525
+ if (typeof boundaryOrOpts === "string") {
10526
+ if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) return null;
10527
+ const caseInsensitive2 = typeof opts === "object" && opts !== null && "caseInsensitive" in opts ? opts.caseInsensitive : false;
10528
+ if (typeof caseInsensitive2 !== "boolean") return null;
10529
+ return { tag: "wordFactory", boundary: boundaryOrOpts, caseInsensitive: caseInsensitive2 };
10530
+ }
10531
+ if (typeof boundaryOrOpts !== "object" || boundaryOrOpts === null || Array.isArray(boundaryOrOpts) || opts !== void 0) {
10532
+ return null;
10533
+ }
10534
+ const caseInsensitive = "caseInsensitive" in boundaryOrOpts ? boundaryOrOpts.caseInsensitive : false;
10535
+ if (typeof caseInsensitive !== "boolean") return null;
10536
+ return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10537
+ }
10148
10538
  var SUPPORTED = {
10149
10539
  literal: (...a) => literal(a[0], a[1]),
10150
10540
  regex: (...a) => regex(a[0], a[1]),
@@ -10195,9 +10585,41 @@ function gatedArmParts(e) {
10195
10585
  }
10196
10586
  return gate2 && combinator ? { gate: gate2, combinator } : null;
10197
10587
  }
10588
+ function dispatchArmValue(node2, scope, code, mfs) {
10589
+ if (node2.type === "Identifier") {
10590
+ const value = anyValue(node2, scope, code, mfs);
10591
+ return isDispatchArm(value) ? value : null;
10592
+ }
10593
+ if (node2.type !== "CallExpression") return null;
10594
+ const callee = node2.callee;
10595
+ if (callee.type !== "Identifier") return null;
10596
+ if (callee.name === "otherwise") {
10597
+ const [parserArg] = node2.arguments;
10598
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
10599
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10600
+ if (!isCombinator(parserValue)) return null;
10601
+ return otherwise(parserValue);
10602
+ }
10603
+ if (callee.name === "when") {
10604
+ const [keyArg, parserArg] = node2.arguments;
10605
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10606
+ const key = anyValue(keyArg, scope, code, mfs);
10607
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10608
+ if (!isCombinator(parserValue)) return null;
10609
+ if (typeof key === "string") return when(key, parserValue);
10610
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue);
10611
+ }
10612
+ return null;
10613
+ }
10198
10614
  function isCombinator(v2) {
10199
10615
  return !!v2 && typeof v2 === "object" && "_def" in v2;
10200
10616
  }
10617
+ function isDispatchArm(v2) {
10618
+ if (!v2 || typeof v2 !== "object") return false;
10619
+ const rec = v2;
10620
+ if (rec.kind === "otherwise") return isCombinator(rec.parser);
10621
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && isCombinator(rec.parser);
10622
+ }
10201
10623
  function scopeGet(scope, name, mfs) {
10202
10624
  const entry = scope.get(name);
10203
10625
  if (!entry) return null;
@@ -10228,14 +10650,13 @@ function exprToCombi(node2, scope, code, mfs) {
10228
10650
  if (node2.type !== "CallExpression") return null;
10229
10651
  const callee = node2.callee;
10230
10652
  if (callee.type === "CallExpression" && callee.callee.type === "Identifier" && callee.callee.name === "makeWord") {
10231
- const boundaryArg = callee.arguments[0];
10232
10653
  const strArg = node2.arguments[0];
10233
10654
  if (!strArg || strArg.type === "SpreadElement") return null;
10234
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10655
+ const factory2 = wordFactoryFromArgs(callee.arguments, scope, code, mfs);
10235
10656
  const str = anyValue(strArg, scope, code, mfs);
10236
- if (typeof boundary !== "string" || typeof str !== "string") return null;
10657
+ if (factory2 === null || typeof str !== "string") return null;
10237
10658
  try {
10238
- return word(str, boundary);
10659
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10239
10660
  } catch {
10240
10661
  return null;
10241
10662
  }
@@ -10248,7 +10669,7 @@ function exprToCombi(node2, scope, code, mfs) {
10248
10669
  const str = anyValue(strArg, scope, code, mfs);
10249
10670
  if (typeof str !== "string") return null;
10250
10671
  try {
10251
- return word(str, factory2.boundary);
10672
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10252
10673
  } catch {
10253
10674
  return null;
10254
10675
  }
@@ -10455,6 +10876,25 @@ function exprToCombi(node2, scope, code, mfs) {
10455
10876
  return null;
10456
10877
  }
10457
10878
  }
10879
+ if (callee.name === "dispatch") {
10880
+ const [selectorArg, ...armArgs] = node2.arguments;
10881
+ if (!selectorArg || selectorArg.type === "SpreadElement") return null;
10882
+ const selector = anyValue(selectorArg, scope, code, mfs);
10883
+ if (!isCombinator(selector)) return null;
10884
+ const arms = [];
10885
+ for (const arg of armArgs) {
10886
+ if (arg.type === "SpreadElement") return null;
10887
+ const arm = dispatchArmValue(arg, scope, code, mfs);
10888
+ if (!isDispatchArm(arm)) return null;
10889
+ arms.push(arm);
10890
+ }
10891
+ try {
10892
+ const unsafeDispatch = dispatch;
10893
+ return unsafeDispatch(selector, ...arms);
10894
+ } catch {
10895
+ return null;
10896
+ }
10897
+ }
10458
10898
  if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
10459
10899
  if (code === void 0 || mfs === void 0) return null;
10460
10900
  const arms = [];
@@ -10540,6 +10980,10 @@ function anyValue(node2, scope, code, mfs) {
10540
10980
  if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10541
10981
  return entry.combi;
10542
10982
  }
10983
+ if (isStaticValueEntry(entry)) {
10984
+ if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10985
+ return entry.value;
10986
+ }
10543
10987
  return entry;
10544
10988
  }
10545
10989
  if (node2.type === "MemberExpression") {
@@ -10558,10 +11002,10 @@ function anyValue(node2, scope, code, mfs) {
10558
11002
  if (node2.type === "CallExpression") {
10559
11003
  const callee = node2.callee;
10560
11004
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10561
- const boundaryArg = node2.arguments[0];
10562
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10563
- if (typeof boundary !== "string") return null;
10564
- return { tag: "wordFactory", boundary };
11005
+ return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
11006
+ }
11007
+ if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11008
+ return dispatchArmValue(node2, scope, code, mfs);
10565
11009
  }
10566
11010
  return exprToCombi(node2, scope, code, mfs);
10567
11011
  }
@@ -10571,10 +11015,7 @@ function evaluateWordFactory(node2, scope, code) {
10571
11015
  if (node2.type !== "CallExpression") return null;
10572
11016
  const callee = node2.callee;
10573
11017
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
10574
- const boundaryArg = node2.arguments[0];
10575
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code) : "_0-9A-Za-z";
10576
- if (typeof boundary !== "string") return null;
10577
- return { tag: "wordFactory", boundary };
11018
+ return wordFactoryFromArgs(node2.arguments, scope, code);
10578
11019
  }
10579
11020
  function evaluateExpr(node2, scope, code, mapFnSources) {
10580
11021
  return exprToCombi(node2, scope, code, mapFnSources);
@@ -10624,6 +11065,7 @@ function evalBodyStatements(statements, scope, code) {
10624
11065
  if (val === null) return false;
10625
11066
  const thisDeclMfSrcs = bodyMfs.slice(before);
10626
11067
  if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
11068
+ else if (isDispatchArm(val)) scope.set(name, { value: val, mfSrcs: thisDeclMfSrcs });
10627
11069
  else scope.set(name, val);
10628
11070
  }
10629
11071
  }
@@ -10669,6 +11111,11 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10669
11111
  r._ruleName = name;
10670
11112
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
10671
11113
  };
11114
+ const ruleNameOf5 = (r) => r._ruleName;
11115
+ const isNamedRuleRefForAnotherRule2 = (r, key) => {
11116
+ const name = r._def.tag === "lazy" ? ruleNameOf5(r) : void 0;
11117
+ return name !== void 0 && name !== key;
11118
+ };
10672
11119
  for (const key of keys) {
10673
11120
  if (!ruleRefs.has(key)) {
10674
11121
  const r = ref();
@@ -10699,12 +11146,15 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10699
11146
  for (const [key, e] of finalByKey) {
10700
11147
  const val = anyValue(e.value, e.scope, e.code, mapFnSources);
10701
11148
  if (!isCombinator(val)) return null;
10702
- tagRef(val, key);
11149
+ if (val === ruleRefs.get(key)) return null;
11150
+ if (!isNamedRuleRefForAnotherRule2(val, key)) {
11151
+ tagRef(val, key);
11152
+ }
10703
11153
  ruleRefs.get(key).define(val);
10704
11154
  }
10705
11155
  return ruleRefs;
10706
11156
  }
10707
- function isDefinableRef(v2) {
11157
+ function isDefinableRef2(v2) {
10708
11158
  return isCombinator(v2) && typeof v2.define === "function" && v2._def.tag === "lazy";
10709
11159
  }
10710
11160
  function evaluateRefDeclaration(init, name, scope) {
@@ -10726,7 +11176,7 @@ function applyDefineStatement(callExpr, scope, code) {
10726
11176
  if (mem.object.type !== "Identifier" || !mem.object.name) return false;
10727
11177
  const target = scope.get(mem.object.name);
10728
11178
  const refCombi = isScopeEntry(target) ? target.combi : isCombinator(target) ? target : null;
10729
- if (!refCombi || !isDefinableRef(refCombi)) return false;
11179
+ if (!refCombi || !isDefinableRef2(refCombi)) return false;
10730
11180
  const args = callExpr.arguments;
10731
11181
  if (args.length !== 1 || args[0].type === "SpreadElement") return false;
10732
11182
  const inner = anyValue(args[0], scope, code, []);