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
@@ -120,6 +120,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
120
120
  return d.parsers.every(me);
121
121
  case "choice":
122
122
  return d.parsers.some(me);
123
+ case "dispatch":
124
+ return me(d.selector);
123
125
  case "transform":
124
126
  case "label":
125
127
  case "trivia":
@@ -193,6 +195,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
193
195
  case "keywords":
194
196
  return p._meta.firstSet;
195
197
  // terminals: no refs, cached set is exact
198
+ case "dispatch":
199
+ return fs2(d.selector);
196
200
  case "lazy":
197
201
  try {
198
202
  return fs2(d.thunk());
@@ -1141,6 +1145,8 @@ function derive(c, seen) {
1141
1145
  return [def.label];
1142
1146
  case "choice":
1143
1147
  return def.parsers.flatMap(deriveExpected2);
1148
+ case "dispatch":
1149
+ return deriveExpected2(def.selector);
1144
1150
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1145
1151
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1146
1152
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1279,6 +1285,7 @@ function choice(...args) {
1279
1285
  const result = parsers[idx].parse(input, pos, ctx);
1280
1286
  if (result.ok) return result;
1281
1287
  expected.push(...result.expected);
1288
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1282
1289
  return { ok: false, expected, span: { start: pos, end: pos } };
1283
1290
  }
1284
1291
  return {
@@ -1319,6 +1326,7 @@ function choice(...args) {
1319
1326
  rollbackCstCapture(ctx, mark);
1320
1327
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1321
1328
  expected.push(...result.expected);
1329
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1322
1330
  continue;
1323
1331
  }
1324
1332
  const checks = autoNot[i];
@@ -1998,6 +2006,75 @@ function sequence(...parsers) {
1998
2006
  };
1999
2007
  }
2000
2008
 
2009
+ // src/combinators/dispatch.ts
2010
+ function when(key, parser2) {
2011
+ const keys = Array.isArray(key) ? [...key] : [key];
2012
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2013
+ for (const item of keys) {
2014
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2015
+ }
2016
+ return { kind: "when", keys, parser: parser2 };
2017
+ }
2018
+ function otherwise(parser2) {
2019
+ return { kind: "otherwise", parser: parser2 };
2020
+ }
2021
+ function dispatch(selector, ...arms) {
2022
+ let fallback;
2023
+ const cases = [];
2024
+ const seen = /* @__PURE__ */ new Set();
2025
+ for (let i = 0; i < arms.length; i++) {
2026
+ const arm = arms[i];
2027
+ if (arm.kind === "otherwise") {
2028
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2029
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2030
+ fallback = arm.parser;
2031
+ continue;
2032
+ }
2033
+ for (const key of arm.keys) {
2034
+ if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2035
+ seen.add(key);
2036
+ }
2037
+ cases.push({ keys: arm.keys, parser: arm.parser });
2038
+ }
2039
+ const meta = {
2040
+ firstSet: selector._meta.firstSet,
2041
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2042
+ isTrivia: false
2043
+ };
2044
+ const byKey = /* @__PURE__ */ new Map();
2045
+ for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2046
+ return {
2047
+ _tag: "dispatch",
2048
+ _meta: meta,
2049
+ _def: {
2050
+ tag: "dispatch",
2051
+ selector,
2052
+ cases,
2053
+ ...fallback === void 0 ? {} : { otherwise: fallback }
2054
+ },
2055
+ parse(input, pos, ctx) {
2056
+ const selected = selector.parse(input, pos, ctx);
2057
+ if (!selected.ok) return selected;
2058
+ const tail = byKey.get(selected.value) ?? fallback;
2059
+ if (tail === void 0) {
2060
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2061
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2062
+ }
2063
+ const mark = saveTriviaMark(ctx);
2064
+ const result = tail.parse(input, selected.span.end, ctx);
2065
+ if (!result.ok) {
2066
+ rollbackTrivia(ctx, mark);
2067
+ return { ...result, committed: true };
2068
+ }
2069
+ return {
2070
+ ok: true,
2071
+ value: [selected.value, result.value],
2072
+ span: { start: pos, end: result.span.end }
2073
+ };
2074
+ }
2075
+ };
2076
+ }
2077
+
2001
2078
  // src/combinators/attempt.ts
2002
2079
  function attempt(parser2) {
2003
2080
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2020,6 +2097,7 @@ function attempt(parser2) {
2020
2097
  if (result.ok) return result;
2021
2098
  rollbackCstCapture(ctx, mark);
2022
2099
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2100
+ if (result.committed) return result;
2023
2101
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2024
2102
  }
2025
2103
  };
@@ -2049,6 +2127,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2049
2127
  const result = combinator.parse(input, pos, ctx);
2050
2128
  if (!result.ok) {
2051
2129
  rollbackTrivia(ctx, mark);
2130
+ if (result.committed) return { fail: result, failPos: pos };
2052
2131
  return { fail: result, failPos: pos };
2053
2132
  }
2054
2133
  if (result.span.end === pos) {
@@ -2092,6 +2171,7 @@ function many(combinator, opts = {}) {
2092
2171
  const item = repItem(combinator, input, cur, ctx, guardable);
2093
2172
  if (item === "stop") break;
2094
2173
  if ("fail" in item) {
2174
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2095
2175
  const sync = ctx._tolerant ? ctx._sync : void 0;
2096
2176
  if (sync === void 0) break;
2097
2177
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2136,6 +2216,7 @@ function atLeast(combinator, min, max) {
2136
2216
  while (count < min) {
2137
2217
  const item = repItem(combinator, input, cur, ctx, guardable);
2138
2218
  if (item === "stop" || "fail" in item) {
2219
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2139
2220
  expected ??= deriveExpected(combinator);
2140
2221
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2141
2222
  }
@@ -2148,6 +2229,7 @@ function atLeast(combinator, min, max) {
2148
2229
  const item = repItem(combinator, input, cur, ctx, guardable);
2149
2230
  if (item === "stop") break;
2150
2231
  if ("fail" in item) {
2232
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2151
2233
  const sync = ctx._tolerant ? ctx._sync : void 0;
2152
2234
  if (sync === void 0) break;
2153
2235
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2186,6 +2268,7 @@ function optional(combinator) {
2186
2268
  const result = combinator.parse(input, pos, ctx);
2187
2269
  if (result.ok) return result;
2188
2270
  rollbackTrivia(ctx, mark);
2271
+ if (result.committed) return result;
2189
2272
  return { ok: true, value: null, span: { start: pos, end: pos } };
2190
2273
  }
2191
2274
  };
@@ -2225,6 +2308,7 @@ function sepBy(combinator, separator, opts = {}) {
2225
2308
  values.push(first.value);
2226
2309
  cur = first.span.end;
2227
2310
  } else {
2311
+ if (first.committed) return first;
2228
2312
  const term = ctx._tolerant ? ctx._sync : void 0;
2229
2313
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2230
2314
  if (min >= 1) return failAt2(pos);
@@ -2252,6 +2336,7 @@ function sepBy(combinator, separator, opts = {}) {
2252
2336
  const sep2 = separator.parse(input, sepPos, ctx);
2253
2337
  if (!sep2.ok) {
2254
2338
  rollbackTrivia(ctx, loopMark);
2339
+ if (sep2.committed) return sep2;
2255
2340
  break;
2256
2341
  }
2257
2342
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2267,6 +2352,7 @@ function sepBy(combinator, separator, opts = {}) {
2267
2352
  }
2268
2353
  const next = combinator.parse(input, nextPos, ctx);
2269
2354
  if (!next.ok) {
2355
+ if (next.committed) return next;
2270
2356
  const term = ctx._tolerant ? ctx._sync : void 0;
2271
2357
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2272
2358
  expected ??= deriveExpected(combinator);
@@ -2322,6 +2408,11 @@ function markUnusedValues(root) {
2322
2408
  case "choice":
2323
2409
  for (const p of def.parsers) visit(p, consumed);
2324
2410
  return;
2411
+ case "dispatch":
2412
+ visit(def.selector, consumed);
2413
+ for (const entry of def.cases) visit(entry.parser, consumed);
2414
+ if (def.otherwise) visit(def.otherwise, consumed);
2415
+ return;
2325
2416
  case "node":
2326
2417
  visit(def.parser, false);
2327
2418
  return;
@@ -2393,6 +2484,16 @@ function tagRule(r, key) {
2393
2484
  r._ruleName = key;
2394
2485
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
2395
2486
  }
2487
+ function isDefinableRef(v2) {
2488
+ return !!v2 && typeof v2 === "object" && "_def" in v2 && v2._def.tag === "lazy" && typeof v2.define === "function";
2489
+ }
2490
+ function ruleNameOf(r) {
2491
+ return r._ruleName;
2492
+ }
2493
+ function isNamedRuleRefForAnotherRule(r, key) {
2494
+ const name = r._def.tag === "lazy" ? ruleNameOf(r) : void 0;
2495
+ return name !== void 0 && name !== key;
2496
+ }
2396
2497
  function rules(a, b) {
2397
2498
  const factory = typeof a === "function" ? a : b;
2398
2499
  const options = typeof a === "function" ? b : a;
@@ -2413,11 +2514,21 @@ function rules(a, b) {
2413
2514
  for (const key of Object.keys(definitions)) {
2414
2515
  const placeholder = cache[key];
2415
2516
  const parser2 = definitions[key];
2416
- if (placeholder !== void 0 && typeof placeholder.define === "function") {
2417
- tagRule(parser2, key);
2517
+ if (placeholder === parser2) {
2518
+ throw new Error(`rules(): rule "${key}" cannot be a direct alias to itself`);
2519
+ }
2520
+ if (isDefinableRef(placeholder)) {
2521
+ if (!isNamedRuleRefForAnotherRule(parser2, key)) tagRule(parser2, key);
2418
2522
  placeholder.define(parser2);
2419
2523
  placeholder._meta.firstSet = parser2._meta.firstSet;
2420
2524
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
2525
+ } else if (isNamedRuleRefForAnotherRule(parser2, key)) {
2526
+ const alias = ref();
2527
+ tagRule(alias, key);
2528
+ alias.define(parser2);
2529
+ alias._meta.firstSet = parser2._meta.firstSet;
2530
+ alias._meta.canMatchNewline = parser2._meta.canMatchNewline;
2531
+ cache[key] = alias;
2421
2532
  } else {
2422
2533
  tagRule(parser2, key);
2423
2534
  cache[key] = parser2;
@@ -2577,6 +2688,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2577
2688
  case "sequence":
2578
2689
  case "choice":
2579
2690
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2691
+ case "dispatch":
2692
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2580
2693
  case "sepBy":
2581
2694
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2582
2695
  case "skip":
@@ -2644,6 +2757,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2644
2757
  return parserHasTriviaSite(d.parser, seen);
2645
2758
  case "choice":
2646
2759
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2760
+ case "dispatch":
2761
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2647
2762
  case "grammar":
2648
2763
  return parserHasTriviaSite(d.parser, seen);
2649
2764
  case "lazy": {
@@ -2669,6 +2784,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2669
2784
  case "sequence":
2670
2785
  case "choice":
2671
2786
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2787
+ case "dispatch":
2788
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2672
2789
  case "sepBy":
2673
2790
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2674
2791
  case "skip":
@@ -3043,7 +3160,7 @@ function leaf(root, fn) {
3043
3160
  }
3044
3161
 
3045
3162
  // src/version.ts
3046
- var PARSEMAN_VERSION = "0.37.0";
3163
+ var PARSEMAN_VERSION = "0.39.0";
3047
3164
 
3048
3165
  // src/cst/host-mode.ts
3049
3166
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3097,7 +3214,7 @@ function firstSetToString(fs2) {
3097
3214
  if (fs2.kind === "empty") return "(empty)";
3098
3215
  return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3099
3216
  }
3100
- var ruleNameOf = (p) => p._ruleName;
3217
+ var ruleNameOf2 = (p) => p._ruleName;
3101
3218
  var SUGGESTIONS = {
3102
3219
  "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.",
3103
3220
  "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.",
@@ -3199,6 +3316,8 @@ function classifyBroadArm(arm, resolve2) {
3199
3316
  }
3200
3317
  return null;
3201
3318
  }
3319
+ case "dispatch":
3320
+ return walk2(d.selector);
3202
3321
  default:
3203
3322
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3204
3323
  }
@@ -3255,7 +3374,7 @@ function detectAntiPatterns(rule, arms) {
3255
3374
  return out;
3256
3375
  }
3257
3376
  function analyzeGating(entry, opts) {
3258
- return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3377
+ return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3259
3378
  }
3260
3379
  function analyzeGatingRules(ruleMap, opts) {
3261
3380
  const raw = [];
@@ -3288,7 +3407,7 @@ function analyzeGatingRules(ruleMap, opts) {
3288
3407
  return;
3289
3408
  }
3290
3409
  const d = def;
3291
- const rule = ruleNameOf(p) ?? enclosingRule;
3410
+ const rule = ruleNameOf2(p) ?? enclosingRule;
3292
3411
  if (d.tag === "choice") {
3293
3412
  raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3294
3413
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
@@ -3298,6 +3417,11 @@ function analyzeGatingRules(ruleMap, opts) {
3298
3417
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3299
3418
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3300
3419
  if (rec[k]) kids.push(rec[k]);
3420
+ if (d.tag === "dispatch") {
3421
+ kids.push(d.selector);
3422
+ for (const c of d.cases) kids.push(c.parser);
3423
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3424
+ }
3301
3425
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3302
3426
  if (d.tag === "lazy") {
3303
3427
  try {
@@ -3307,7 +3431,7 @@ function analyzeGatingRules(ruleMap, opts) {
3307
3431
  }
3308
3432
  for (const k of kids) visit(k, rule);
3309
3433
  };
3310
- for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3434
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3311
3435
  const perRule = /* @__PURE__ */ new Map();
3312
3436
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3313
3437
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3404,6 +3528,12 @@ function childrenOf(d) {
3404
3528
  case "sequence":
3405
3529
  case "choice":
3406
3530
  return d.parsers;
3531
+ case "dispatch":
3532
+ return [
3533
+ d.selector,
3534
+ ...d.cases.map((c) => c.parser),
3535
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3536
+ ];
3407
3537
  case "skip":
3408
3538
  return [d.main, d.skipped];
3409
3539
  case "sepBy":
@@ -3431,7 +3561,7 @@ function childrenOf(d) {
3431
3561
  }
3432
3562
  }
3433
3563
  }
3434
- var ruleNameOf2 = (p) => p._ruleName;
3564
+ var ruleNameOf3 = (p) => p._ruleName;
3435
3565
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
3436
3566
  function payloadKey(p, d) {
3437
3567
  switch (d.tag) {
@@ -3442,7 +3572,7 @@ function payloadKey(p, d) {
3442
3572
  case "keywords":
3443
3573
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
3444
3574
  case "lazy": {
3445
- const n = ruleNameOf2(p);
3575
+ const n = ruleNameOf3(p);
3446
3576
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
3447
3577
  }
3448
3578
  case "label":
@@ -3460,6 +3590,8 @@ function payloadKey(p, d) {
3460
3590
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3461
3591
  case "sepBy":
3462
3592
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3593
+ case "dispatch":
3594
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3463
3595
  case "expect":
3464
3596
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3465
3597
  case "scanTo":
@@ -3509,7 +3641,7 @@ var Interner = class {
3509
3641
  var MAX_RENDER = 160;
3510
3642
  function render(p, depth = 3) {
3511
3643
  const d = p._def;
3512
- const name = ruleNameOf2(p);
3644
+ const name = ruleNameOf3(p);
3513
3645
  switch (d.tag) {
3514
3646
  case "literal":
3515
3647
  return `literal('${d.value}')`;
@@ -3531,6 +3663,11 @@ function render(p, depth = 3) {
3531
3663
  return `sequence(${kids.join(", ")})`;
3532
3664
  case "choice":
3533
3665
  return `choice(${kids.join(", ")})`;
3666
+ case "dispatch": {
3667
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3668
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3669
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3670
+ }
3534
3671
  case "node":
3535
3672
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3536
3673
  case "label":
@@ -3554,6 +3691,10 @@ function segment(d, index) {
3554
3691
  return `seq[${index}]`;
3555
3692
  case "choice":
3556
3693
  return `choice[${index}]`;
3694
+ case "dispatch":
3695
+ if (index === 0) return "dispatch.selector";
3696
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3697
+ return "dispatch.otherwise";
3557
3698
  case "node":
3558
3699
  return `node(${d.type ?? ""})`;
3559
3700
  case "label":
@@ -3688,7 +3829,7 @@ function keywordAlternationHazards(words, boundary) {
3688
3829
  return out;
3689
3830
  }
3690
3831
  function analyzeDuplication(entry, opts) {
3691
- return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3832
+ return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3692
3833
  }
3693
3834
  function assertAnalyzable(ruleMap) {
3694
3835
  for (const [name, rule] of ruleMap) {
@@ -3729,7 +3870,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
3729
3870
  const visit = (p, rule, path2, enclosingNode, enclosingReducer) => {
3730
3871
  if (visited.has(p)) return;
3731
3872
  const d = p._def;
3732
- const own = ruleNameOf2(p);
3873
+ const own = ruleNameOf3(p);
3733
3874
  const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path2 };
3734
3875
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
3735
3876
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
@@ -3742,12 +3883,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
3742
3883
  if (d.tag === "lazy") {
3743
3884
  try {
3744
3885
  const target = d.thunk();
3745
- visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
3886
+ visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
3746
3887
  } catch {
3747
3888
  }
3748
3889
  }
3749
3890
  };
3750
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
3891
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
3751
3892
  const accept = new Set(opts?.accept ?? []);
3752
3893
  const used = /* @__PURE__ */ new Set();
3753
3894
  const keep = (f) => {
@@ -4807,6 +4948,8 @@ function children(def, winners) {
4807
4948
  case "sequence":
4808
4949
  case "choice":
4809
4950
  return def.parsers;
4951
+ case "dispatch":
4952
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
4810
4953
  case "many":
4811
4954
  case "oneOrMore":
4812
4955
  case "optional":
@@ -6271,6 +6414,8 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6271
6414
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6272
6415
  return { alts };
6273
6416
  }
6417
+ case "dispatch":
6418
+ return rec(d.selector);
6274
6419
  case "sequence": {
6275
6420
  const chain = [];
6276
6421
  for (const term of d.parsers) {
@@ -6353,6 +6498,9 @@ function failReturn(expected, posExpr) {
6353
6498
  function failReturnArr(expectedArr, posExpr) {
6354
6499
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6355
6500
  }
6501
+ function committedReturnArr(expectedArr, posExpr) {
6502
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6503
+ }
6356
6504
  function hoistExpected(ctx, constArrSource) {
6357
6505
  let name = ctx.expectedMap.get(constArrSource);
6358
6506
  if (name === void 0) {
@@ -6389,15 +6537,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6389
6537
  return probe + trace + failReturnArr(expectedArr, posExpr);
6390
6538
  }
6391
6539
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6392
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6540
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6541
+ }
6542
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6543
+ if (ctx.failLabel) {
6544
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6545
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6546
+ }
6547
+ return committedReturnArr(expectedArr, posExpr);
6393
6548
  }
6394
6549
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6395
6550
  if (srcCtx !== "_ctx") {
6396
6551
  if (ctx.failLabel) {
6397
6552
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6398
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6553
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6399
6554
  }
6400
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6555
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6401
6556
  }
6402
6557
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6403
6558
  return resultFromRecorded();
@@ -6610,6 +6765,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6610
6765
  // nothing committed remains.
6611
6766
  case "choice":
6612
6767
  return false;
6768
+ case "dispatch":
6769
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6613
6770
  case "attempt":
6614
6771
  return false;
6615
6772
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6654,6 +6811,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6654
6811
  return true;
6655
6812
  }
6656
6813
  }
6814
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6815
+ if (seen.has(p)) return false;
6816
+ seen.add(p);
6817
+ const d = p._def;
6818
+ switch (d.tag) {
6819
+ case "dispatch":
6820
+ return true;
6821
+ case "choice":
6822
+ case "sequence":
6823
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6824
+ case "many":
6825
+ case "oneOrMore":
6826
+ case "optional":
6827
+ case "attempt":
6828
+ case "transform":
6829
+ case "label":
6830
+ case "field":
6831
+ case "grammar":
6832
+ case "node":
6833
+ return mayCommitFailure(d.parser, seen);
6834
+ case "sepBy":
6835
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6836
+ case "skip":
6837
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6838
+ case "token":
6839
+ case "leaf":
6840
+ return mayCommitFailure(d.parser, seen);
6841
+ case "withCtx":
6842
+ return mayCommitFailure(d.parser, seen);
6843
+ case "scanTo":
6844
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
6845
+ case "lazy": {
6846
+ try {
6847
+ return mayCommitFailure(d.thunk(), seen);
6848
+ } catch {
6849
+ return true;
6850
+ }
6851
+ }
6852
+ case "unknown":
6853
+ return true;
6854
+ default:
6855
+ return false;
6856
+ }
6857
+ }
6657
6858
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6658
6859
  if (seen.has(p)) return false;
6659
6860
  seen.add(p);
@@ -6675,6 +6876,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6675
6876
  case "sequence":
6676
6877
  case "choice":
6677
6878
  return d.parsers.some((x) => capturesLeaf(x, seen));
6879
+ case "dispatch":
6880
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6678
6881
  case "sepBy":
6679
6882
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6680
6883
  case "many":
@@ -6721,6 +6924,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6721
6924
  ctx.recordFail = savedRecord;
6722
6925
  const ind0 = ind(ctx);
6723
6926
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
6927
+ const mayCommit = mayCommitFailure(inner);
6724
6928
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6725
6929
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6726
6930
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6734,6 +6938,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6734
6938
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6735
6939
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6736
6940
  ] : [],
6941
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6737
6942
  `${ind0}${lbl}: {`,
6738
6943
  ...r.stmts,
6739
6944
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6742,7 +6947,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6742
6947
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6743
6948
  ] : []
6744
6949
  ];
6745
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
6950
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6746
6951
  }
6747
6952
  var CHARCODE_CHAIN_MAX = 16;
6748
6953
  function emitLit(def, ctx, pos) {
@@ -7119,6 +7324,118 @@ function emitChoice(parser2, def, ctx, pos) {
7119
7324
  }
7120
7325
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7121
7326
  }
7327
+ function emitTailFallible(parser2, ctx, pos) {
7328
+ const savedRecord = ctx.recordFail;
7329
+ ctx.recordFail = true;
7330
+ const out = emitFallible(parser2, ctx, pos);
7331
+ ctx.recordFail = savedRecord;
7332
+ return out;
7333
+ }
7334
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7335
+ if (seen.has(p)) return false;
7336
+ seen.add(p);
7337
+ const d = p._def;
7338
+ switch (d.tag) {
7339
+ case "expect":
7340
+ case "recover":
7341
+ return true;
7342
+ case "many":
7343
+ case "oneOrMore":
7344
+ case "sepBy":
7345
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7346
+ case "choice":
7347
+ case "sequence": {
7348
+ for (const item of d.parsers) {
7349
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7350
+ }
7351
+ return false;
7352
+ }
7353
+ case "dispatch": {
7354
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7355
+ for (const item of d.cases) {
7356
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7357
+ }
7358
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7359
+ }
7360
+ case "attempt":
7361
+ case "transform":
7362
+ case "label":
7363
+ case "field":
7364
+ case "grammar":
7365
+ case "node":
7366
+ case "token":
7367
+ case "leaf":
7368
+ case "withCtx":
7369
+ case "optional":
7370
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7371
+ case "skip":
7372
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7373
+ case "scanTo":
7374
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7375
+ for (const item of d.skip) {
7376
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7377
+ }
7378
+ return false;
7379
+ case "lazy": {
7380
+ try {
7381
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7382
+ } catch {
7383
+ return true;
7384
+ }
7385
+ }
7386
+ case "unknown":
7387
+ return true;
7388
+ default:
7389
+ return false;
7390
+ }
7391
+ }
7392
+ function emitDispatchCombinator(def, ctx, pos) {
7393
+ const selector = emit(def.selector, ctx, pos);
7394
+ const outV = v(ctx, "_dval");
7395
+ const outE = v(ctx, "_dend");
7396
+ const keyV = v(ctx, "_dkey");
7397
+ const stmts = [
7398
+ ...selector.stmts,
7399
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7400
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7401
+ ];
7402
+ let wroteBranch = false;
7403
+ const emitSelectedTail = (parser2, keyword, condition) => {
7404
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7405
+ stmts.push(head);
7406
+ ctx.indent++;
7407
+ const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7408
+ const errMark = mayError ? v(ctx, "_derr") : null;
7409
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7410
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7411
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7412
+ const tail = emitTailFallible(parser2, ctx, selector.endVar);
7413
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7414
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7415
+ stmts.push(
7416
+ ...tail.stmts,
7417
+ `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7418
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7419
+ `${ind(ctx)}${outE} = ${tail.endVar}`
7420
+ );
7421
+ ctx.indent--;
7422
+ stmts.push(`${ind(ctx)}}`);
7423
+ };
7424
+ for (const entry of def.cases) {
7425
+ const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7426
+ emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7427
+ wroteBranch = true;
7428
+ }
7429
+ if (def.otherwise) {
7430
+ if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7431
+ else emitSelectedTail(def.otherwise, "if", "true");
7432
+ } else {
7433
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7434
+ const fail = failArrBody(ctx, expected, selector.endVar);
7435
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7436
+ }
7437
+ return { stmts, valueVar: outV, endVar: outE };
7438
+ }
7122
7439
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7123
7440
  const superParser = def.parsers[superIndex];
7124
7441
  const regexDef = getCoreRegexDef(superParser);
@@ -7240,12 +7557,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7240
7557
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7241
7558
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7242
7559
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7560
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7243
7561
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7244
7562
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7245
7563
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7246
7564
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7247
7565
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7248
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7566
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7567
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7568
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7569
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7249
7570
  const failSlot = atStart ? staticFx : "_ctx._fx";
7250
7571
  stmts.push(`${ind0}if (${skipCond}) {`);
7251
7572
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7260,7 +7581,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7260
7581
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7261
7582
  );
7262
7583
  }
7263
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7584
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7585
+ const arm = emitFallible(p, ctx, pos, atStart);
7586
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7264
7587
  stmts.push(...armStmts);
7265
7588
  if (autoNot && autoNot.length > 0) {
7266
7589
  const anCode = v(ctx, "_anc");
@@ -7284,10 +7607,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7284
7607
  stmts.push(`${ind(ctx)}}`);
7285
7608
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}; ${coverageBase === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} });`} }`);
7286
7609
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7610
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7287
7611
  } else {
7288
7612
  const rejectedFlush = autoRejectedV === void 0 ? "" : `; for (const _rejectedId of ${autoRejectedV}) { _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'failure', offset: ${pos} }); _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'backtrack', offset: ${pos} }) }`;
7289
7613
  stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true${coverageBase === void 0 ? "" : `${rejectedFlush}; _ctx._grammarCoverage?.(${JSON.stringify(`${coverageBase}${i}`)}); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'selected', offset: ${pos}, end: ${endVar} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'success', offset: ${pos}, end: ${endVar} })`} }`);
7290
7614
  stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""}${coverageBase === void 0 ? "" : `; _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} })`} }`);
7615
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7291
7616
  }
7292
7617
  ctx.indent -= fsGuard ? 2 : 1;
7293
7618
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7444,7 +7769,8 @@ function emitMany(def, ctx, pos) {
7444
7769
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7445
7770
  );
7446
7771
  }
7447
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
7772
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
7773
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7448
7774
  stmts.push(...iterStmts);
7449
7775
  if (ctx.recovery) {
7450
7776
  const rrV = v(ctx, "_rr");
@@ -7463,7 +7789,8 @@ function emitMany(def, ctx, pos) {
7463
7789
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7464
7790
  );
7465
7791
  } else {
7466
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7792
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7793
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7467
7794
  }
7468
7795
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7469
7796
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7475,10 +7802,12 @@ function emitMany(def, ctx, pos) {
7475
7802
  function emitOptional(def, ctx, pos) {
7476
7803
  const valV = v(ctx, "_opt");
7477
7804
  const endV = v(ctx, "_opte");
7478
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
7805
+ const inner = emitFallible(def.parser, ctx, pos, true);
7806
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7479
7807
  const ind0 = ind(ctx);
7480
7808
  const stmts = [
7481
7809
  ...lblStmts,
7810
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7482
7811
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7483
7812
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7484
7813
  ];
@@ -7510,7 +7839,7 @@ function emitAttempt(p, def, ctx, pos) {
7510
7839
  ...preGuard,
7511
7840
  `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${trivia2} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`,
7512
7841
  ...inner.stmts,
7513
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7842
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7514
7843
  ],
7515
7844
  valueVar: inner.valVar,
7516
7845
  endVar: inner.endVar
@@ -7524,18 +7853,21 @@ function emitSepBy(_p, def, ctx, pos) {
7524
7853
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7525
7854
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7526
7855
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7527
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
7856
+ const first = emitFallible(def.parser, ctx, pos, true);
7857
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7528
7858
  const stmts = [
7529
7859
  `${ind(ctx)}const ${arrV} = []`,
7530
7860
  `${ind(ctx)}let ${curV} = ${pos}`,
7531
7861
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7532
7862
  ...firstStmts
7533
7863
  ];
7534
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7535
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
7864
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
7865
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
7866
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7536
7867
  const rr = v(ctx, "_rr");
7537
7868
  return [
7538
7869
  `${ind(ctx)}if (!${nextOkVar}) {`,
7870
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7539
7871
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7540
7872
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7541
7873
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7564,6 +7896,7 @@ function emitSepBy(_p, def, ctx, pos) {
7564
7896
  stmts.push(
7565
7897
  `${ind(ctx)}let ${didV} = false`,
7566
7898
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
7899
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7567
7900
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7568
7901
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7569
7902
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7574,6 +7907,7 @@ function emitSepBy(_p, def, ctx, pos) {
7574
7907
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7575
7908
  ctx.indent++;
7576
7909
  } else {
7910
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7577
7911
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7578
7912
  ctx.indent++;
7579
7913
  stmts.push(
@@ -7602,16 +7936,18 @@ function emitSepBy(_p, def, ctx, pos) {
7602
7936
  );
7603
7937
  sepAtPos = spV;
7604
7938
  const rollbackToSep = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
7605
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7606
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
7939
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7940
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7941
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7607
7942
  const post = postSepMarks();
7608
7943
  stmts.push(...post.decl);
7609
7944
  const npV = v(ctx, "_np");
7610
7945
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7611
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7946
+ const next = emitFallible(def.parser, ctx, npV, true);
7947
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7612
7948
  stmts.push(
7613
7949
  ...nextStmts,
7614
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7950
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7615
7951
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7616
7952
  `${ind(ctx)}${curV} = ${nextEnd}`
7617
7953
  );
@@ -7620,14 +7956,16 @@ function emitSepBy(_p, def, ctx, pos) {
7620
7956
  const spV = v(ctx, "_sp");
7621
7957
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7622
7958
  sepAtPos = spV;
7623
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7624
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7959
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7960
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7961
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7625
7962
  const npV = v(ctx, "_np");
7626
7963
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7627
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
7964
+ const next = emitFallible(def.parser, ctx, npV, true);
7965
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7628
7966
  stmts.push(
7629
7967
  ...nextStmts,
7630
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
7968
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7631
7969
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7632
7970
  `${ind(ctx)}${curV} = ${nextEnd}`
7633
7971
  );
@@ -7641,15 +7979,17 @@ function emitSepBy(_p, def, ctx, pos) {
7641
7979
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7642
7980
  );
7643
7981
  }
7644
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7645
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
7982
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
7983
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
7984
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7646
7985
  const nextRb = markLv ? `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markRw}) _ctx._cstRawChildren.length = ${markRw}; ` : "";
7647
7986
  const post = postSepMarks();
7648
7987
  stmts.push(...post.decl);
7649
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
7988
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
7989
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7650
7990
  stmts.push(
7651
7991
  ...nextStmts,
7652
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7992
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7653
7993
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7654
7994
  `${ind(ctx)}${curV} = ${nextEnd}`
7655
7995
  );
@@ -8030,7 +8370,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8030
8370
  const rv = v(ctx, "_rt");
8031
8371
  const vv = v(ctx, "_rtv");
8032
8372
  const ev = v(ctx, "_rte");
8033
- const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span }`;
8373
+ const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `{ _ctx._fc = ${rv}.committed === true; break ${ctx.failLabel} }` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span, ...(${rv}.committed ? { committed: true } : {}) }`;
8034
8374
  const stmts = [
8035
8375
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8036
8376
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8111,7 +8451,7 @@ function emitRecover(def, ctx, pos) {
8111
8451
  return { stmts, valueVar: valVar, endVar };
8112
8452
  }
8113
8453
  function emitExpect(def, ctx, pos) {
8114
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8454
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8115
8455
  const ind0 = ind(ctx);
8116
8456
  const errV = v(ctx, "_err");
8117
8457
  const stmts = [
@@ -8123,6 +8463,7 @@ function emitExpect(def, ctx, pos) {
8123
8463
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8124
8464
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8125
8465
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8466
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8126
8467
  `${ind0} ${valVar} = ${errV}`,
8127
8468
  `${ind0} ${endVar} = ${pos}`,
8128
8469
  `${ind0} ${okVar} = true`,
@@ -8133,7 +8474,7 @@ function emitExpect(def, ctx, pos) {
8133
8474
  function emit(p, ctx, pos) {
8134
8475
  const replay = ctx.replayPrefix?.get(p);
8135
8476
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8136
- const dispatch = (emissionPos = pos) => {
8477
+ const dispatch2 = (emissionPos = pos) => {
8137
8478
  const savedRule = ctx.activeCoverageRuleId;
8138
8479
  const rule = ctx.coverage?.plan.rules.get(p);
8139
8480
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8173,14 +8514,14 @@ function emit(p, ctx, pos) {
8173
8514
  ctx.indent = 1;
8174
8515
  ctx.failLabel = "_pfail";
8175
8516
  ctx.recordFail = true;
8176
- const r = dispatch("_pos");
8517
+ const r = dispatch2("_pos");
8177
8518
  ctx.indent = savedIndent;
8178
8519
  ctx.failLabel = savedFailLabel;
8179
8520
  ctx.recordFail = savedRecord;
8180
8521
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8181
8522
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8182
8523
  }
8183
- return instrument(dispatch());
8524
+ return instrument(dispatch2());
8184
8525
  }
8185
8526
  function emitDispatch(p, ctx, pos) {
8186
8527
  const bal = p._balancedAmbient;
@@ -8213,6 +8554,8 @@ function emitDispatch(p, ctx, pos) {
8213
8554
  return emitSeq(def, ctx, pos);
8214
8555
  case "choice":
8215
8556
  return emitChoice(p, def, ctx, pos);
8557
+ case "dispatch":
8558
+ return emitDispatchCombinator(def, ctx, pos);
8216
8559
  case "attempt":
8217
8560
  return emitAttempt(p, def, ctx, pos);
8218
8561
  case "many":
@@ -8433,6 +8776,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8433
8776
  case "sequence":
8434
8777
  case "choice":
8435
8778
  return d.parsers.some((x) => hasNodeDef(x, seen));
8779
+ case "dispatch":
8780
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8436
8781
  case "sepBy":
8437
8782
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8438
8783
  case "scanTo":
@@ -8466,6 +8811,8 @@ function childrenOf2(def) {
8466
8811
  case "sequence":
8467
8812
  case "choice":
8468
8813
  return def.parsers;
8814
+ case "dispatch":
8815
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
8469
8816
  case "many":
8470
8817
  case "attempt":
8471
8818
  case "optional":
@@ -8509,6 +8856,7 @@ function childrenOf2(def) {
8509
8856
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8510
8857
  "sequence",
8511
8858
  "choice",
8859
+ "dispatch",
8512
8860
  "many",
8513
8861
  "oneOrMore",
8514
8862
  "optional",
@@ -8602,7 +8950,7 @@ function ruleDependencies(ruleMap) {
8602
8950
  const seen = /* @__PURE__ */ new Set();
8603
8951
  const walk2 = (p, isRoot) => {
8604
8952
  const def = p._def;
8605
- const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
8953
+ const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
8606
8954
  if (!isRoot && boundary !== void 0) {
8607
8955
  found.add(boundary);
8608
8956
  return;
@@ -9333,6 +9681,8 @@ function childrenOf3(def) {
9333
9681
  case "sequence":
9334
9682
  case "choice":
9335
9683
  return def.parsers;
9684
+ case "dispatch":
9685
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9336
9686
  case "many":
9337
9687
  case "oneOrMore":
9338
9688
  case "optional":
@@ -9367,7 +9717,7 @@ function childrenOf3(def) {
9367
9717
  return [];
9368
9718
  }
9369
9719
  }
9370
- var ruleNameOf3 = (c) => c._ruleName;
9720
+ var ruleNameOf4 = (c) => c._ruleName;
9371
9721
  function lazyTarget(c) {
9372
9722
  if (c._def.tag !== "lazy") return null;
9373
9723
  try {
@@ -9482,6 +9832,9 @@ function evalRuleMapIR(ir) {
9482
9832
  "keywords",
9483
9833
  "sequence",
9484
9834
  "choice",
9835
+ "dispatch",
9836
+ "when",
9837
+ "otherwise",
9485
9838
  "attempt",
9486
9839
  "many",
9487
9840
  "oneOrMore",
@@ -9515,6 +9868,9 @@ function evalRuleMapIR(ir) {
9515
9868
  keywords,
9516
9869
  sequence,
9517
9870
  choice,
9871
+ dispatch,
9872
+ when,
9873
+ otherwise,
9518
9874
  attempt,
9519
9875
  many,
9520
9876
  oneOrMore,
@@ -9549,12 +9905,16 @@ var Serializer = class {
9549
9905
  emitted = /* @__PURE__ */ new Set();
9550
9906
  decls = [];
9551
9907
  ruleValues = /* @__PURE__ */ new Set();
9908
+ localRuleNames = /* @__PURE__ */ new Map();
9552
9909
  ruleMap;
9553
9910
  scanSkip;
9554
9911
  constructor(ruleMap, scanSkip = []) {
9555
9912
  this.ruleMap = ruleMap;
9556
9913
  this.scanSkip = scanSkip;
9557
- for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
9914
+ for (const [name, c] of ruleMap) {
9915
+ this.localRuleNames.set(c, name);
9916
+ this.ruleValues.add(this.body(c));
9917
+ }
9558
9918
  }
9559
9919
  /** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
9560
9920
  * bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
@@ -9565,7 +9925,7 @@ var Serializer = class {
9565
9925
  if (seen.has(c)) return;
9566
9926
  seen.add(c);
9567
9927
  if (c._def.tag === "lazy") {
9568
- if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9928
+ if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
9569
9929
  const target = lazyTarget(c);
9570
9930
  if (target) this.assertNoRuleRef(target, seen);
9571
9931
  return;
@@ -9576,7 +9936,7 @@ var Serializer = class {
9576
9936
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
9577
9937
  * `g[name]` ref — only the top-level entry is unwrapped. */
9578
9938
  body(c) {
9579
- return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
9939
+ return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
9580
9940
  }
9581
9941
  run() {
9582
9942
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -9618,7 +9978,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9618
9978
  analyze(c, active) {
9619
9979
  const def = c._def;
9620
9980
  if (def.tag === "lazy") {
9621
- if (ruleNameOf3(c) !== void 0) return;
9981
+ if (ruleNameOf4(c) !== void 0) return;
9622
9982
  const target = lazyTarget(c);
9623
9983
  if (!target) throw new Unserializable("unresolved unnamed ref");
9624
9984
  if (active.has(target)) {
@@ -9670,7 +10030,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9670
10030
  const kid = (x) => this.ref(x, selfOf);
9671
10031
  switch (def.tag) {
9672
10032
  case "lazy": {
9673
- if (ruleNameOf3(c) !== void 0) return `g[${JSON.stringify(ruleNameOf3(c))}]`;
10033
+ const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
10034
+ if (name !== void 0) return `g[${JSON.stringify(name)}]`;
9674
10035
  const target = lazyTarget(c);
9675
10036
  if (selfOf && target === selfOf.comb) return selfOf.var;
9676
10037
  return this.ref(target, selfOf);
@@ -9695,6 +10056,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9695
10056
  });
9696
10057
  return `_gch([${items.join(", ")}])`;
9697
10058
  }
10059
+ case "dispatch": {
10060
+ const arms = def.cases.map(
10061
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10062
+ );
10063
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10064
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10065
+ }
9698
10066
  case "many":
9699
10067
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9700
10068
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -10180,6 +10548,28 @@ function stripTsFromSource(node2, code) {
10180
10548
  function isWordFactory(v2) {
10181
10549
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10182
10550
  }
10551
+ function isStaticValueEntry(v2) {
10552
+ return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10553
+ }
10554
+ function wordFactoryFromArgs(args, scope, code, mfs) {
10555
+ const [boundaryOrOptsArg, optsArg] = args;
10556
+ if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10557
+ const rawBoundaryOrOpts = boundaryOrOptsArg === void 0 ? void 0 : anyValue(boundaryOrOptsArg, scope, code, mfs);
10558
+ const boundaryOrOpts = rawBoundaryOrOpts === void 0 ? "_0-9A-Za-z" : rawBoundaryOrOpts;
10559
+ const opts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10560
+ if (typeof boundaryOrOpts === "string") {
10561
+ if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) return null;
10562
+ const caseInsensitive2 = typeof opts === "object" && opts !== null && "caseInsensitive" in opts ? opts.caseInsensitive : false;
10563
+ if (typeof caseInsensitive2 !== "boolean") return null;
10564
+ return { tag: "wordFactory", boundary: boundaryOrOpts, caseInsensitive: caseInsensitive2 };
10565
+ }
10566
+ if (typeof boundaryOrOpts !== "object" || boundaryOrOpts === null || Array.isArray(boundaryOrOpts) || opts !== void 0) {
10567
+ return null;
10568
+ }
10569
+ const caseInsensitive = "caseInsensitive" in boundaryOrOpts ? boundaryOrOpts.caseInsensitive : false;
10570
+ if (typeof caseInsensitive !== "boolean") return null;
10571
+ return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10572
+ }
10183
10573
  var SUPPORTED = {
10184
10574
  literal: (...a) => literal(a[0], a[1]),
10185
10575
  regex: (...a) => regex(a[0], a[1]),
@@ -10230,9 +10620,41 @@ function gatedArmParts(e) {
10230
10620
  }
10231
10621
  return gate2 && combinator ? { gate: gate2, combinator } : null;
10232
10622
  }
10623
+ function dispatchArmValue(node2, scope, code, mfs) {
10624
+ if (node2.type === "Identifier") {
10625
+ const value = anyValue(node2, scope, code, mfs);
10626
+ return isDispatchArm(value) ? value : null;
10627
+ }
10628
+ if (node2.type !== "CallExpression") return null;
10629
+ const callee = node2.callee;
10630
+ if (callee.type !== "Identifier") return null;
10631
+ if (callee.name === "otherwise") {
10632
+ const [parserArg] = node2.arguments;
10633
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
10634
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10635
+ if (!isCombinator(parserValue)) return null;
10636
+ return otherwise(parserValue);
10637
+ }
10638
+ if (callee.name === "when") {
10639
+ const [keyArg, parserArg] = node2.arguments;
10640
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10641
+ const key = anyValue(keyArg, scope, code, mfs);
10642
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10643
+ if (!isCombinator(parserValue)) return null;
10644
+ if (typeof key === "string") return when(key, parserValue);
10645
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue);
10646
+ }
10647
+ return null;
10648
+ }
10233
10649
  function isCombinator(v2) {
10234
10650
  return !!v2 && typeof v2 === "object" && "_def" in v2;
10235
10651
  }
10652
+ function isDispatchArm(v2) {
10653
+ if (!v2 || typeof v2 !== "object") return false;
10654
+ const rec = v2;
10655
+ if (rec.kind === "otherwise") return isCombinator(rec.parser);
10656
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && isCombinator(rec.parser);
10657
+ }
10236
10658
  function scopeGet(scope, name, mfs) {
10237
10659
  const entry = scope.get(name);
10238
10660
  if (!entry) return null;
@@ -10263,14 +10685,13 @@ function exprToCombi(node2, scope, code, mfs) {
10263
10685
  if (node2.type !== "CallExpression") return null;
10264
10686
  const callee = node2.callee;
10265
10687
  if (callee.type === "CallExpression" && callee.callee.type === "Identifier" && callee.callee.name === "makeWord") {
10266
- const boundaryArg = callee.arguments[0];
10267
10688
  const strArg = node2.arguments[0];
10268
10689
  if (!strArg || strArg.type === "SpreadElement") return null;
10269
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10690
+ const factory2 = wordFactoryFromArgs(callee.arguments, scope, code, mfs);
10270
10691
  const str = anyValue(strArg, scope, code, mfs);
10271
- if (typeof boundary !== "string" || typeof str !== "string") return null;
10692
+ if (factory2 === null || typeof str !== "string") return null;
10272
10693
  try {
10273
- return word(str, boundary);
10694
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10274
10695
  } catch {
10275
10696
  return null;
10276
10697
  }
@@ -10283,7 +10704,7 @@ function exprToCombi(node2, scope, code, mfs) {
10283
10704
  const str = anyValue(strArg, scope, code, mfs);
10284
10705
  if (typeof str !== "string") return null;
10285
10706
  try {
10286
- return word(str, factory2.boundary);
10707
+ return word(str, factory2.boundary, { caseInsensitive: factory2.caseInsensitive });
10287
10708
  } catch {
10288
10709
  return null;
10289
10710
  }
@@ -10490,6 +10911,25 @@ function exprToCombi(node2, scope, code, mfs) {
10490
10911
  return null;
10491
10912
  }
10492
10913
  }
10914
+ if (callee.name === "dispatch") {
10915
+ const [selectorArg, ...armArgs] = node2.arguments;
10916
+ if (!selectorArg || selectorArg.type === "SpreadElement") return null;
10917
+ const selector = anyValue(selectorArg, scope, code, mfs);
10918
+ if (!isCombinator(selector)) return null;
10919
+ const arms = [];
10920
+ for (const arg of armArgs) {
10921
+ if (arg.type === "SpreadElement") return null;
10922
+ const arm = dispatchArmValue(arg, scope, code, mfs);
10923
+ if (!isDispatchArm(arm)) return null;
10924
+ arms.push(arm);
10925
+ }
10926
+ try {
10927
+ const unsafeDispatch = dispatch;
10928
+ return unsafeDispatch(selector, ...arms);
10929
+ } catch {
10930
+ return null;
10931
+ }
10932
+ }
10493
10933
  if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
10494
10934
  if (code === void 0 || mfs === void 0) return null;
10495
10935
  const arms = [];
@@ -10575,6 +11015,10 @@ function anyValue(node2, scope, code, mfs) {
10575
11015
  if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10576
11016
  return entry.combi;
10577
11017
  }
11018
+ if (isStaticValueEntry(entry)) {
11019
+ if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
11020
+ return entry.value;
11021
+ }
10578
11022
  return entry;
10579
11023
  }
10580
11024
  if (node2.type === "MemberExpression") {
@@ -10593,10 +11037,10 @@ function anyValue(node2, scope, code, mfs) {
10593
11037
  if (node2.type === "CallExpression") {
10594
11038
  const callee = node2.callee;
10595
11039
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10596
- const boundaryArg = node2.arguments[0];
10597
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code, mfs) : "_0-9A-Za-z";
10598
- if (typeof boundary !== "string") return null;
10599
- return { tag: "wordFactory", boundary };
11040
+ return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
11041
+ }
11042
+ if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11043
+ return dispatchArmValue(node2, scope, code, mfs);
10600
11044
  }
10601
11045
  return exprToCombi(node2, scope, code, mfs);
10602
11046
  }
@@ -10606,10 +11050,7 @@ function evaluateWordFactory(node2, scope, code) {
10606
11050
  if (node2.type !== "CallExpression") return null;
10607
11051
  const callee = node2.callee;
10608
11052
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
10609
- const boundaryArg = node2.arguments[0];
10610
- const boundary = boundaryArg && boundaryArg.type !== "SpreadElement" ? anyValue(boundaryArg, scope, code) : "_0-9A-Za-z";
10611
- if (typeof boundary !== "string") return null;
10612
- return { tag: "wordFactory", boundary };
11053
+ return wordFactoryFromArgs(node2.arguments, scope, code);
10613
11054
  }
10614
11055
  function evaluateExpr(node2, scope, code, mapFnSources) {
10615
11056
  return exprToCombi(node2, scope, code, mapFnSources);
@@ -10659,6 +11100,7 @@ function evalBodyStatements(statements, scope, code) {
10659
11100
  if (val === null) return false;
10660
11101
  const thisDeclMfSrcs = bodyMfs.slice(before);
10661
11102
  if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
11103
+ else if (isDispatchArm(val)) scope.set(name, { value: val, mfSrcs: thisDeclMfSrcs });
10662
11104
  else scope.set(name, val);
10663
11105
  }
10664
11106
  }
@@ -10704,6 +11146,11 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10704
11146
  r._ruleName = name;
10705
11147
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
10706
11148
  };
11149
+ const ruleNameOf5 = (r) => r._ruleName;
11150
+ const isNamedRuleRefForAnotherRule2 = (r, key) => {
11151
+ const name = r._def.tag === "lazy" ? ruleNameOf5(r) : void 0;
11152
+ return name !== void 0 && name !== key;
11153
+ };
10707
11154
  for (const key of keys) {
10708
11155
  if (!ruleRefs.has(key)) {
10709
11156
  const r = ref();
@@ -10734,12 +11181,15 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
10734
11181
  for (const [key, e] of finalByKey) {
10735
11182
  const val = anyValue(e.value, e.scope, e.code, mapFnSources);
10736
11183
  if (!isCombinator(val)) return null;
10737
- tagRef(val, key);
11184
+ if (val === ruleRefs.get(key)) return null;
11185
+ if (!isNamedRuleRefForAnotherRule2(val, key)) {
11186
+ tagRef(val, key);
11187
+ }
10738
11188
  ruleRefs.get(key).define(val);
10739
11189
  }
10740
11190
  return ruleRefs;
10741
11191
  }
10742
- function isDefinableRef(v2) {
11192
+ function isDefinableRef2(v2) {
10743
11193
  return isCombinator(v2) && typeof v2.define === "function" && v2._def.tag === "lazy";
10744
11194
  }
10745
11195
  function evaluateRefDeclaration(init, name, scope) {
@@ -10761,7 +11211,7 @@ function applyDefineStatement(callExpr, scope, code) {
10761
11211
  if (mem.object.type !== "Identifier" || !mem.object.name) return false;
10762
11212
  const target = scope.get(mem.object.name);
10763
11213
  const refCombi = isScopeEntry(target) ? target.combi : isCombinator(target) ? target : null;
10764
- if (!refCombi || !isDefinableRef(refCombi)) return false;
11214
+ if (!refCombi || !isDefinableRef2(refCombi)) return false;
10765
11215
  const args = callExpr.arguments;
10766
11216
  if (args.length !== 1 || args[0].type === "SpreadElement") return false;
10767
11217
  const inner = anyValue(args[0], scope, code, []);