parseman 0.38.0 → 0.39.1

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 (40) 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 +53 -0
  6. package/dist/combinators/dispatch.d.ts.map +1 -0
  7. package/dist/combinators/expect.d.ts.map +1 -1
  8. package/dist/combinators/first-set.d.ts.map +1 -1
  9. package/dist/combinators/repeat.d.ts.map +1 -1
  10. package/dist/compiler/codegen.d.ts.map +1 -1
  11. package/dist/compiler/fields.d.ts.map +1 -1
  12. package/dist/compiler/grammar-coverage-ids.d.ts +2 -1
  13. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  14. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  15. package/dist/compiler/value-usage.d.ts.map +1 -1
  16. package/dist/coverage.d.ts.map +1 -1
  17. package/dist/index.cjs +772 -33
  18. package/dist/index.cjs.map +4 -4
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +772 -33
  22. package/dist/index.js.map +4 -4
  23. package/dist/language-service/index.cjs.map +2 -2
  24. package/dist/language-service/index.js.map +2 -2
  25. package/dist/plugin/evaluator.d.ts +6 -0
  26. package/dist/plugin/evaluator.d.ts.map +1 -1
  27. package/dist/plugin/index.cjs +840 -33
  28. package/dist/plugin/index.cjs.map +4 -4
  29. package/dist/plugin/index.d.ts.map +1 -1
  30. package/dist/plugin/index.js +840 -33
  31. package/dist/plugin/index.js.map +4 -4
  32. package/dist/spec/index.cjs +294 -1
  33. package/dist/spec/index.cjs.map +4 -4
  34. package/dist/spec/index.js +294 -1
  35. package/dist/spec/index.js.map +4 -4
  36. package/dist/spec/model.d.ts.map +1 -1
  37. package/dist/types.d.ts +37 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/version.d.ts +1 -1
  40. package/package.json +1 -1
@@ -94,6 +94,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
94
94
  return d.value.length === 0;
95
95
  case "keywords":
96
96
  return false;
97
+ case "routed":
98
+ return false;
97
99
  case "regex":
98
100
  try {
99
101
  const m = new RegExp(d.source).exec("");
@@ -120,6 +122,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
120
122
  return d.parsers.every(me);
121
123
  case "choice":
122
124
  return d.parsers.some(me);
125
+ case "dispatch":
126
+ return me(d.selector);
123
127
  case "transform":
124
128
  case "label":
125
129
  case "trivia":
@@ -193,6 +197,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
193
197
  case "keywords":
194
198
  return p._meta.firstSet;
195
199
  // terminals: no refs, cached set is exact
200
+ case "dispatch":
201
+ return fs2(d.selector);
196
202
  case "lazy":
197
203
  try {
198
204
  return fs2(d.thunk());
@@ -1141,6 +1147,8 @@ function derive(c, seen) {
1141
1147
  return [def.label];
1142
1148
  case "choice":
1143
1149
  return def.parsers.flatMap(deriveExpected2);
1150
+ case "dispatch":
1151
+ return deriveExpected2(def.selector);
1144
1152
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1145
1153
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1146
1154
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1180,6 +1188,8 @@ function derive(c, seen) {
1180
1188
  case "not":
1181
1189
  case "peek":
1182
1190
  return deriveExpected2(def.parser);
1191
+ case "routed":
1192
+ return ["routed()"];
1183
1193
  case "lazy": {
1184
1194
  const name = c._ruleName;
1185
1195
  if (seen.has(c)) return name ? [name] : [];
@@ -1279,6 +1289,7 @@ function choice(...args) {
1279
1289
  const result = parsers[idx].parse(input, pos, ctx);
1280
1290
  if (result.ok) return result;
1281
1291
  expected.push(...result.expected);
1292
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1282
1293
  return { ok: false, expected, span: { start: pos, end: pos } };
1283
1294
  }
1284
1295
  return {
@@ -1319,6 +1330,7 @@ function choice(...args) {
1319
1330
  rollbackCstCapture(ctx, mark);
1320
1331
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1321
1332
  expected.push(...result.expected);
1333
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1322
1334
  continue;
1323
1335
  }
1324
1336
  const checks = autoNot[i];
@@ -1998,6 +2010,245 @@ function sequence(...parsers) {
1998
2010
  };
1999
2011
  }
2000
2012
 
2013
+ // src/combinators/dispatch.ts
2014
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
2015
+ if (seen.has(parser2)) return false;
2016
+ seen.add(parser2);
2017
+ const def = parser2._def;
2018
+ switch (def.tag) {
2019
+ case "routed":
2020
+ return true;
2021
+ case "sequence":
2022
+ case "choice":
2023
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
2024
+ case "dispatch":
2025
+ return false;
2026
+ case "sepBy":
2027
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
2028
+ case "skip":
2029
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
2030
+ case "grammar":
2031
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
2032
+ case "scanTo":
2033
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
2034
+ case "recover":
2035
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
2036
+ case "lazy":
2037
+ try {
2038
+ return parserUsesRouted(def.thunk(), seen);
2039
+ } catch {
2040
+ return false;
2041
+ }
2042
+ case "many":
2043
+ case "oneOrMore":
2044
+ case "optional":
2045
+ case "attempt":
2046
+ case "transform":
2047
+ case "trivia":
2048
+ case "token":
2049
+ case "leaf":
2050
+ case "label":
2051
+ case "field":
2052
+ case "not":
2053
+ case "peek":
2054
+ case "node":
2055
+ case "withCtx":
2056
+ case "expect":
2057
+ return parserUsesRouted(def.parser, seen);
2058
+ default:
2059
+ return false;
2060
+ }
2061
+ }
2062
+ function branchUsesRouted(branch) {
2063
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
2064
+ }
2065
+ function asciiFoldKey(key) {
2066
+ let out = "";
2067
+ for (let i = 0; i < key.length; i++) {
2068
+ const c = key.charCodeAt(i);
2069
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
2070
+ }
2071
+ return out;
2072
+ }
2073
+ function matchesDispatchMatcher(value, matcher) {
2074
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2075
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2076
+ switch (matcher.kind) {
2077
+ case "startsWith":
2078
+ return candidate.startsWith(expected);
2079
+ case "endsWith":
2080
+ return candidate.endsWith(expected);
2081
+ case "matches": {
2082
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2083
+ return new RegExp(matcher.value, flags).test(value);
2084
+ }
2085
+ }
2086
+ }
2087
+ function isStringMatcher(value) {
2088
+ if (typeof value !== "object" || value === null) return false;
2089
+ const kind = value.kind;
2090
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2091
+ }
2092
+ function fixedMatcher(kind, value) {
2093
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2094
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2095
+ return { kind, value };
2096
+ }
2097
+ function startsWith(prefix) {
2098
+ return fixedMatcher("startsWith", prefix);
2099
+ }
2100
+ function endsWith(suffix) {
2101
+ return fixedMatcher("endsWith", suffix);
2102
+ }
2103
+ function matches(pattern) {
2104
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2105
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2106
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2107
+ }
2108
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2109
+ }
2110
+ function when(key, parser2, opts = {}) {
2111
+ const usesRouted = parserUsesRouted(parser2);
2112
+ if (isStringMatcher(key)) {
2113
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2114
+ }
2115
+ const keys = Array.isArray(key) ? [...key] : [key];
2116
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2117
+ for (const item of keys) {
2118
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2119
+ }
2120
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2121
+ }
2122
+ function otherwise(parser2) {
2123
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2124
+ }
2125
+ function routed() {
2126
+ return {
2127
+ _tag: "routed",
2128
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2129
+ _def: { tag: "routed" },
2130
+ parse(_input, pos, ctx) {
2131
+ const item = ctx._routed;
2132
+ if (item === void 0) {
2133
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2134
+ }
2135
+ if (pos !== item.span.start) {
2136
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2137
+ }
2138
+ if (cstCaptureActive(ctx)) {
2139
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2140
+ }
2141
+ return { ok: true, value: item.value, span: item.span };
2142
+ }
2143
+ };
2144
+ }
2145
+ function dispatch(selector, ...arms) {
2146
+ if (parserUsesRouted(selector)) {
2147
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2148
+ }
2149
+ let fallback;
2150
+ let fallbackUsesRouted = false;
2151
+ const matchers = [];
2152
+ const cases = [];
2153
+ const seen = [];
2154
+ for (let i = 0; i < arms.length; i++) {
2155
+ const arm = arms[i];
2156
+ if (arm.kind === "otherwise") {
2157
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2158
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2159
+ fallback = arm.parser;
2160
+ fallbackUsesRouted = arm.usesRouted === true;
2161
+ continue;
2162
+ }
2163
+ if (arm.kind === "whenMatcher") {
2164
+ const matcher = arm.matcher;
2165
+ matchers.push({
2166
+ kind: matcher.kind,
2167
+ value: matcher.value,
2168
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2169
+ parser: arm.parser,
2170
+ caseInsensitive: arm.caseInsensitive,
2171
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2172
+ });
2173
+ continue;
2174
+ }
2175
+ for (const key of arm.keys) {
2176
+ const folded = asciiFoldKey(key);
2177
+ for (const prior of seen) {
2178
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2179
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2180
+ }
2181
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2182
+ }
2183
+ cases.push({
2184
+ keys: arm.keys,
2185
+ parser: arm.parser,
2186
+ caseInsensitive: arm.caseInsensitive,
2187
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2188
+ });
2189
+ }
2190
+ const meta = {
2191
+ firstSet: selector._meta.firstSet,
2192
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2193
+ isTrivia: false
2194
+ };
2195
+ const byKey = /* @__PURE__ */ new Map();
2196
+ const byFoldedKey = /* @__PURE__ */ new Map();
2197
+ for (const entry of cases) {
2198
+ for (const key of entry.keys) {
2199
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2200
+ else byKey.set(key, entry);
2201
+ }
2202
+ }
2203
+ return {
2204
+ _tag: "dispatch",
2205
+ _meta: meta,
2206
+ _def: {
2207
+ tag: "dispatch",
2208
+ selector,
2209
+ cases,
2210
+ ...matchers.length === 0 ? {} : { matchers },
2211
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2212
+ },
2213
+ parse(input, pos, ctx) {
2214
+ if (parserUsesRouted(selector)) {
2215
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2216
+ }
2217
+ const selectorMark = saveTriviaMark(ctx);
2218
+ const selected = selector.parse(input, pos, ctx);
2219
+ if (!selected.ok) return selected;
2220
+ const branch = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher)) ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
2221
+ if (branch === void 0) {
2222
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2223
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2224
+ }
2225
+ const savedRouted = ctx._routed;
2226
+ const usesRouted = branchUsesRouted(branch);
2227
+ let mark = saveTriviaMark(ctx);
2228
+ if (usesRouted) {
2229
+ rollbackTrivia(ctx, selectorMark);
2230
+ mark = saveTriviaMark(ctx);
2231
+ ctx._routed = { value: selected.value, span: selected.span };
2232
+ }
2233
+ let result;
2234
+ try {
2235
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2236
+ } finally {
2237
+ if (usesRouted) ctx._routed = savedRouted;
2238
+ }
2239
+ if (!result.ok) {
2240
+ rollbackTrivia(ctx, mark);
2241
+ return { ...result, committed: true };
2242
+ }
2243
+ return {
2244
+ ok: true,
2245
+ value: [selected.value, result.value],
2246
+ span: { start: pos, end: result.span.end }
2247
+ };
2248
+ }
2249
+ };
2250
+ }
2251
+
2001
2252
  // src/combinators/attempt.ts
2002
2253
  function attempt(parser2) {
2003
2254
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2020,6 +2271,7 @@ function attempt(parser2) {
2020
2271
  if (result.ok) return result;
2021
2272
  rollbackCstCapture(ctx, mark);
2022
2273
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2274
+ if (result.committed) return result;
2023
2275
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2024
2276
  }
2025
2277
  };
@@ -2049,6 +2301,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2049
2301
  const result = combinator.parse(input, pos, ctx);
2050
2302
  if (!result.ok) {
2051
2303
  rollbackTrivia(ctx, mark);
2304
+ if (result.committed) return { fail: result, failPos: pos };
2052
2305
  return { fail: result, failPos: pos };
2053
2306
  }
2054
2307
  if (result.span.end === pos) {
@@ -2092,6 +2345,7 @@ function many(combinator, opts = {}) {
2092
2345
  const item = repItem(combinator, input, cur, ctx, guardable);
2093
2346
  if (item === "stop") break;
2094
2347
  if ("fail" in item) {
2348
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2095
2349
  const sync = ctx._tolerant ? ctx._sync : void 0;
2096
2350
  if (sync === void 0) break;
2097
2351
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2136,6 +2390,7 @@ function atLeast(combinator, min, max) {
2136
2390
  while (count < min) {
2137
2391
  const item = repItem(combinator, input, cur, ctx, guardable);
2138
2392
  if (item === "stop" || "fail" in item) {
2393
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2139
2394
  expected ??= deriveExpected(combinator);
2140
2395
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2141
2396
  }
@@ -2148,6 +2403,7 @@ function atLeast(combinator, min, max) {
2148
2403
  const item = repItem(combinator, input, cur, ctx, guardable);
2149
2404
  if (item === "stop") break;
2150
2405
  if ("fail" in item) {
2406
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2151
2407
  const sync = ctx._tolerant ? ctx._sync : void 0;
2152
2408
  if (sync === void 0) break;
2153
2409
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2186,6 +2442,7 @@ function optional(combinator) {
2186
2442
  const result = combinator.parse(input, pos, ctx);
2187
2443
  if (result.ok) return result;
2188
2444
  rollbackTrivia(ctx, mark);
2445
+ if (result.committed) return result;
2189
2446
  return { ok: true, value: null, span: { start: pos, end: pos } };
2190
2447
  }
2191
2448
  };
@@ -2225,6 +2482,7 @@ function sepBy(combinator, separator, opts = {}) {
2225
2482
  values.push(first.value);
2226
2483
  cur = first.span.end;
2227
2484
  } else {
2485
+ if (first.committed) return first;
2228
2486
  const term = ctx._tolerant ? ctx._sync : void 0;
2229
2487
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2230
2488
  if (min >= 1) return failAt2(pos);
@@ -2252,6 +2510,7 @@ function sepBy(combinator, separator, opts = {}) {
2252
2510
  const sep2 = separator.parse(input, sepPos, ctx);
2253
2511
  if (!sep2.ok) {
2254
2512
  rollbackTrivia(ctx, loopMark);
2513
+ if (sep2.committed) return sep2;
2255
2514
  break;
2256
2515
  }
2257
2516
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2267,6 +2526,7 @@ function sepBy(combinator, separator, opts = {}) {
2267
2526
  }
2268
2527
  const next = combinator.parse(input, nextPos, ctx);
2269
2528
  if (!next.ok) {
2529
+ if (next.committed) return next;
2270
2530
  const term = ctx._tolerant ? ctx._sync : void 0;
2271
2531
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2272
2532
  expected ??= deriveExpected(combinator);
@@ -2322,6 +2582,12 @@ function markUnusedValues(root) {
2322
2582
  case "choice":
2323
2583
  for (const p of def.parsers) visit(p, consumed);
2324
2584
  return;
2585
+ case "dispatch":
2586
+ visit(def.selector, consumed);
2587
+ for (const entry of def.cases) visit(entry.parser, consumed);
2588
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2589
+ if (def.otherwise) visit(def.otherwise, consumed);
2590
+ return;
2325
2591
  case "node":
2326
2592
  visit(def.parser, false);
2327
2593
  return;
@@ -2597,6 +2863,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2597
2863
  case "sequence":
2598
2864
  case "choice":
2599
2865
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2866
+ case "dispatch":
2867
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasOwnFields(entry.parser, seen)) : false) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2600
2868
  case "sepBy":
2601
2869
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2602
2870
  case "skip":
@@ -2664,6 +2932,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2664
2932
  return parserHasTriviaSite(d.parser, seen);
2665
2933
  case "choice":
2666
2934
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2935
+ case "dispatch":
2936
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasTriviaSite(entry.parser, seen)) : false) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2667
2937
  case "grammar":
2668
2938
  return parserHasTriviaSite(d.parser, seen);
2669
2939
  case "lazy": {
@@ -2689,6 +2959,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2689
2959
  case "sequence":
2690
2960
  case "choice":
2691
2961
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2962
+ case "dispatch":
2963
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserEnablesTriviaCapture(entry.parser, seen)) : false) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2692
2964
  case "sepBy":
2693
2965
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2694
2966
  case "skip":
@@ -3063,7 +3335,7 @@ function leaf(root, fn) {
3063
3335
  }
3064
3336
 
3065
3337
  // src/version.ts
3066
- var PARSEMAN_VERSION = "0.38.0";
3338
+ var PARSEMAN_VERSION = "0.39.1";
3067
3339
 
3068
3340
  // src/cst/host-mode.ts
3069
3341
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3219,6 +3491,8 @@ function classifyBroadArm(arm, resolve2) {
3219
3491
  }
3220
3492
  return null;
3221
3493
  }
3494
+ case "dispatch":
3495
+ return walk2(d.selector);
3222
3496
  default:
3223
3497
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3224
3498
  }
@@ -3318,6 +3592,12 @@ function analyzeGatingRules(ruleMap, opts) {
3318
3592
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3319
3593
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3320
3594
  if (rec[k]) kids.push(rec[k]);
3595
+ if (d.tag === "dispatch") {
3596
+ kids.push(d.selector);
3597
+ for (const c of d.cases) kids.push(c.parser);
3598
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3599
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3600
+ }
3321
3601
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3322
3602
  if (d.tag === "lazy") {
3323
3603
  try {
@@ -3424,6 +3704,12 @@ function childrenOf(d) {
3424
3704
  case "sequence":
3425
3705
  case "choice":
3426
3706
  return d.parsers;
3707
+ case "dispatch":
3708
+ return [
3709
+ d.selector,
3710
+ ...d.cases.map((c) => c.parser),
3711
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3712
+ ];
3427
3713
  case "skip":
3428
3714
  return [d.main, d.skipped];
3429
3715
  case "sepBy":
@@ -3480,6 +3766,8 @@ function payloadKey(p, d) {
3480
3766
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3481
3767
  case "sepBy":
3482
3768
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3769
+ case "dispatch":
3770
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3483
3771
  case "expect":
3484
3772
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3485
3773
  case "scanTo":
@@ -3551,6 +3839,11 @@ function render(p, depth = 3) {
3551
3839
  return `sequence(${kids.join(", ")})`;
3552
3840
  case "choice":
3553
3841
  return `choice(${kids.join(", ")})`;
3842
+ case "dispatch": {
3843
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3844
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3845
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3846
+ }
3554
3847
  case "node":
3555
3848
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3556
3849
  case "label":
@@ -3574,6 +3867,10 @@ function segment(d, index) {
3574
3867
  return `seq[${index}]`;
3575
3868
  case "choice":
3576
3869
  return `choice[${index}]`;
3870
+ case "dispatch":
3871
+ if (index === 0) return "dispatch.selector";
3872
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3873
+ return "dispatch.otherwise";
3577
3874
  case "node":
3578
3875
  return `node(${d.type ?? ""})`;
3579
3876
  case "label":
@@ -4822,11 +5119,25 @@ function escapeClassChar(ch2) {
4822
5119
  }
4823
5120
 
4824
5121
  // src/compiler/grammar-coverage-ids.ts
5122
+ function idPart(value) {
5123
+ return encodeURIComponent(value);
5124
+ }
5125
+ function dispatchArmIds(def, path2) {
5126
+ const ids = [];
5127
+ for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5128
+ for (const entry of def.matchers ?? []) {
5129
+ ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5130
+ }
5131
+ if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5132
+ return ids;
5133
+ }
4825
5134
  function children(def, winners) {
4826
5135
  switch (def.tag) {
4827
5136
  case "sequence":
4828
5137
  case "choice":
4829
5138
  return def.parsers;
5139
+ case "dispatch":
5140
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4830
5141
  case "many":
4831
5142
  case "oneOrMore":
4832
5143
  case "optional":
@@ -4869,6 +5180,7 @@ function children(def, winners) {
4869
5180
  function buildGrammarPlan(entry, winners) {
4870
5181
  const definitions = /* @__PURE__ */ new Map();
4871
5182
  const choices = /* @__PURE__ */ new WeakMap();
5183
+ const dispatches = /* @__PURE__ */ new WeakMap();
4872
5184
  const attempts = /* @__PURE__ */ new WeakMap();
4873
5185
  const labels = /* @__PURE__ */ new WeakMap();
4874
5186
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -4899,6 +5211,11 @@ function buildGrammarPlan(entry, winners) {
4899
5211
  choices.set(parser2, ids);
4900
5212
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
4901
5213
  }
5214
+ if (parser2._def.tag === "dispatch") {
5215
+ const ids = dispatchArmIds(parser2._def, path2);
5216
+ dispatches.set(parser2, ids);
5217
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5218
+ }
4902
5219
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
4903
5220
  if (parser2._def.tag === "label") {
4904
5221
  const id = `label:${path2}`;
@@ -4911,7 +5228,7 @@ function buildGrammarPlan(entry, winners) {
4911
5228
  for (const root of roots) {
4912
5229
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
4913
5230
  }
4914
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5231
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
4915
5232
  }
4916
5233
 
4917
5234
  // src/compiler/scannable-run.ts
@@ -6285,12 +6602,15 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6285
6602
  case "literal":
6286
6603
  case "regex":
6287
6604
  case "keywords":
6605
+ case "routed":
6288
6606
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6289
6607
  case "choice": {
6290
6608
  const alts = [];
6291
6609
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6292
6610
  return { alts };
6293
6611
  }
6612
+ case "dispatch":
6613
+ return rec(d.selector);
6294
6614
  case "sequence": {
6295
6615
  const chain = [];
6296
6616
  for (const term of d.parsers) {
@@ -6373,6 +6693,9 @@ function failReturn(expected, posExpr) {
6373
6693
  function failReturnArr(expectedArr, posExpr) {
6374
6694
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6375
6695
  }
6696
+ function committedReturnArr(expectedArr, posExpr) {
6697
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6698
+ }
6376
6699
  function hoistExpected(ctx, constArrSource) {
6377
6700
  let name = ctx.expectedMap.get(constArrSource);
6378
6701
  if (name === void 0) {
@@ -6409,15 +6732,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6409
6732
  return probe + trace + failReturnArr(expectedArr, posExpr);
6410
6733
  }
6411
6734
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6412
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6735
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6736
+ }
6737
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6738
+ if (ctx.failLabel) {
6739
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6740
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6741
+ }
6742
+ return committedReturnArr(expectedArr, posExpr);
6413
6743
  }
6414
6744
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6415
6745
  if (srcCtx !== "_ctx") {
6416
6746
  if (ctx.failLabel) {
6417
6747
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6418
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6748
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6419
6749
  }
6420
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6750
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6421
6751
  }
6422
6752
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6423
6753
  return resultFromRecorded();
@@ -6630,6 +6960,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6630
6960
  // nothing committed remains.
6631
6961
  case "choice":
6632
6962
  return false;
6963
+ case "dispatch":
6964
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6633
6965
  case "attempt":
6634
6966
  return false;
6635
6967
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6674,6 +7006,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6674
7006
  return true;
6675
7007
  }
6676
7008
  }
7009
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
7010
+ if (seen.has(p)) return false;
7011
+ seen.add(p);
7012
+ const d = p._def;
7013
+ switch (d.tag) {
7014
+ case "dispatch":
7015
+ return true;
7016
+ case "choice":
7017
+ case "sequence":
7018
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
7019
+ case "many":
7020
+ case "oneOrMore":
7021
+ case "optional":
7022
+ case "attempt":
7023
+ case "transform":
7024
+ case "label":
7025
+ case "field":
7026
+ case "grammar":
7027
+ case "node":
7028
+ return mayCommitFailure(d.parser, seen);
7029
+ case "sepBy":
7030
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
7031
+ case "skip":
7032
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
7033
+ case "token":
7034
+ case "leaf":
7035
+ return mayCommitFailure(d.parser, seen);
7036
+ case "withCtx":
7037
+ return mayCommitFailure(d.parser, seen);
7038
+ case "scanTo":
7039
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
7040
+ case "lazy": {
7041
+ try {
7042
+ return mayCommitFailure(d.thunk(), seen);
7043
+ } catch {
7044
+ return true;
7045
+ }
7046
+ }
7047
+ case "unknown":
7048
+ return true;
7049
+ default:
7050
+ return false;
7051
+ }
7052
+ }
6677
7053
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6678
7054
  if (seen.has(p)) return false;
6679
7055
  seen.add(p);
@@ -6684,6 +7060,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6684
7060
  case "keywords":
6685
7061
  case "node":
6686
7062
  case "token":
7063
+ case "routed":
6687
7064
  case "leaf":
6688
7065
  return true;
6689
7066
  case "not":
@@ -6695,6 +7072,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6695
7072
  case "sequence":
6696
7073
  case "choice":
6697
7074
  return d.parsers.some((x) => capturesLeaf(x, seen));
7075
+ case "dispatch":
7076
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => capturesLeaf(entry.parser, seen)) : false) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6698
7077
  case "sepBy":
6699
7078
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6700
7079
  case "many":
@@ -6741,6 +7120,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6741
7120
  ctx.recordFail = savedRecord;
6742
7121
  const ind0 = ind(ctx);
6743
7122
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7123
+ const mayCommit = mayCommitFailure(inner);
6744
7124
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6745
7125
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6746
7126
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6754,6 +7134,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6754
7134
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6755
7135
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6756
7136
  ] : [],
7137
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6757
7138
  `${ind0}${lbl}: {`,
6758
7139
  ...r.stmts,
6759
7140
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6762,7 +7143,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6762
7143
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6763
7144
  ] : []
6764
7145
  ];
6765
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7146
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6766
7147
  }
6767
7148
  var CHARCODE_CHAIN_MAX = 16;
6768
7149
  function emitLit(def, ctx, pos) {
@@ -7139,6 +7520,186 @@ function emitChoice(parser2, def, ctx, pos) {
7139
7520
  }
7140
7521
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7141
7522
  }
7523
+ function emitTailFallible(parser2, ctx, pos) {
7524
+ const savedRecord = ctx.recordFail;
7525
+ ctx.recordFail = true;
7526
+ const out = emitFallible(parser2, ctx, pos);
7527
+ ctx.recordFail = savedRecord;
7528
+ return out;
7529
+ }
7530
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7531
+ if (seen.has(p)) return false;
7532
+ seen.add(p);
7533
+ const d = p._def;
7534
+ switch (d.tag) {
7535
+ case "expect":
7536
+ case "recover":
7537
+ return true;
7538
+ case "many":
7539
+ case "oneOrMore":
7540
+ case "sepBy":
7541
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7542
+ case "choice":
7543
+ case "sequence": {
7544
+ for (const item of d.parsers) {
7545
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7546
+ }
7547
+ return false;
7548
+ }
7549
+ case "dispatch": {
7550
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7551
+ for (const item of d.cases) {
7552
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7553
+ }
7554
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7555
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7556
+ }
7557
+ case "attempt":
7558
+ case "transform":
7559
+ case "label":
7560
+ case "field":
7561
+ case "grammar":
7562
+ case "node":
7563
+ case "token":
7564
+ case "leaf":
7565
+ case "withCtx":
7566
+ case "optional":
7567
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7568
+ case "skip":
7569
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7570
+ case "scanTo":
7571
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7572
+ for (const item of d.skip) {
7573
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7574
+ }
7575
+ return false;
7576
+ case "lazy": {
7577
+ try {
7578
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7579
+ } catch {
7580
+ return true;
7581
+ }
7582
+ }
7583
+ case "unknown":
7584
+ return true;
7585
+ default:
7586
+ return false;
7587
+ }
7588
+ }
7589
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7590
+ if (parserUsesRouted2(def.selector)) {
7591
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7592
+ }
7593
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7594
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7595
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7596
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7597
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7598
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7599
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7600
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7601
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7602
+ const selector = emit(def.selector, ctx, pos);
7603
+ const outV = v(ctx, "_dval");
7604
+ const outE = v(ctx, "_dend");
7605
+ const keyV = v(ctx, "_dkey");
7606
+ const stmts = [
7607
+ ...selectorLeafMark ? [
7608
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7609
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7610
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7611
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7612
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7613
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7614
+ ] : [],
7615
+ ...selector.stmts,
7616
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7617
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7618
+ ];
7619
+ let wroteBranch = false;
7620
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7621
+ const parser3 = entry.parser;
7622
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7623
+ stmts.push(head);
7624
+ ctx.indent++;
7625
+ const usesRouted = branchUsesRouted2(entry);
7626
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7627
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7628
+ const errMark = mayError ? v(ctx, "_derr") : null;
7629
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7630
+ if (routedV) {
7631
+ stmts.push(
7632
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7633
+ ...selectorLeafMark ? [
7634
+ `${ind(ctx)}if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${selectorLeafMark}) _ctx._cstLeaves.length = ${selectorLeafMark}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${selectorRawMark}) _ctx._cstRawChildren.length = ${selectorRawMark}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${selectorTriviaMark}) _ctx._cstTriviaLog.length = ${selectorTriviaMark}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${selectorLogMark}) _ctx._triviaLog.length = ${selectorLogMark}; if (_ctx._fields && _ctx._fields.length !== ${selectorFieldMark}) _ctx._fields.length = ${selectorFieldMark}; if (_ctx._errors && _ctx._errors.length !== ${selectorErrorMark}) _ctx._errors.length = ${selectorErrorMark}`
7635
+ ] : []
7636
+ );
7637
+ }
7638
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7639
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7640
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7641
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7642
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7643
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7644
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7645
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7646
+ stmts.push(
7647
+ ...tail.stmts,
7648
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7649
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7650
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7651
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7652
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7653
+ );
7654
+ ctx.indent--;
7655
+ stmts.push(`${ind(ctx)}}`);
7656
+ };
7657
+ let coverageIndex = 0;
7658
+ for (const entry of def.cases) {
7659
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7660
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7661
+ wroteBranch = true;
7662
+ }
7663
+ const emitMatcherTails = () => {
7664
+ for (const matcher of def.matchers ?? []) {
7665
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7666
+ wroteBranch = true;
7667
+ }
7668
+ };
7669
+ if (def.otherwise) {
7670
+ emitMatcherTails();
7671
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7672
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7673
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7674
+ } else {
7675
+ emitMatcherTails();
7676
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7677
+ const fail = failArrBody(ctx, expected, selector.endVar);
7678
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7679
+ }
7680
+ return { stmts, valueVar: outV, endVar: outE };
7681
+ }
7682
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7683
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7684
+ const parts = [`${valueVar}.length === ${key.length}`];
7685
+ for (let i = 0; i < key.length; i++) {
7686
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7687
+ }
7688
+ return parts.join(" && ");
7689
+ }
7690
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7691
+ if (matcher.kind === "matches") {
7692
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7693
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7694
+ }
7695
+ const text = matcher.value;
7696
+ const parts = [`${valueVar}.length >= ${text.length}`];
7697
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7698
+ for (let i = 0; i < text.length; i++) {
7699
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7700
+ }
7701
+ return parts.join(" && ");
7702
+ }
7142
7703
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7143
7704
  const superParser = def.parsers[superIndex];
7144
7705
  const regexDef = getCoreRegexDef(superParser);
@@ -7260,12 +7821,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7260
7821
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7261
7822
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7262
7823
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7824
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7263
7825
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7264
7826
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7265
7827
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7266
7828
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7267
7829
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7268
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7830
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7831
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7832
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7833
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7269
7834
  const failSlot = atStart ? staticFx : "_ctx._fx";
7270
7835
  stmts.push(`${ind0}if (${skipCond}) {`);
7271
7836
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7280,7 +7845,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7280
7845
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7281
7846
  );
7282
7847
  }
7283
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7848
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7849
+ const arm = emitFallible(p, ctx, pos, atStart);
7850
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7284
7851
  stmts.push(...armStmts);
7285
7852
  if (autoNot && autoNot.length > 0) {
7286
7853
  const anCode = v(ctx, "_anc");
@@ -7304,10 +7871,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7304
7871
  stmts.push(`${ind(ctx)}}`);
7305
7872
  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} });`} }`);
7306
7873
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7874
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7307
7875
  } else {
7308
7876
  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} }) }`;
7309
7877
  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} })`} }`);
7310
7878
  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} })`} }`);
7879
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7311
7880
  }
7312
7881
  ctx.indent -= fsGuard ? 2 : 1;
7313
7882
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7464,7 +8033,8 @@ function emitMany(def, ctx, pos) {
7464
8033
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7465
8034
  );
7466
8035
  }
7467
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
8036
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
8037
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7468
8038
  stmts.push(...iterStmts);
7469
8039
  if (ctx.recovery) {
7470
8040
  const rrV = v(ctx, "_rr");
@@ -7483,7 +8053,8 @@ function emitMany(def, ctx, pos) {
7483
8053
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7484
8054
  );
7485
8055
  } else {
7486
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
8056
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
8057
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7487
8058
  }
7488
8059
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7489
8060
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7495,10 +8066,12 @@ function emitMany(def, ctx, pos) {
7495
8066
  function emitOptional(def, ctx, pos) {
7496
8067
  const valV = v(ctx, "_opt");
7497
8068
  const endV = v(ctx, "_opte");
7498
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
8069
+ const inner = emitFallible(def.parser, ctx, pos, true);
8070
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7499
8071
  const ind0 = ind(ctx);
7500
8072
  const stmts = [
7501
8073
  ...lblStmts,
8074
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7502
8075
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7503
8076
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7504
8077
  ];
@@ -7530,7 +8103,7 @@ function emitAttempt(p, def, ctx, pos) {
7530
8103
  ...preGuard,
7531
8104
  `${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`,
7532
8105
  ...inner.stmts,
7533
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
8106
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7534
8107
  ],
7535
8108
  valueVar: inner.valVar,
7536
8109
  endVar: inner.endVar
@@ -7544,18 +8117,21 @@ function emitSepBy(_p, def, ctx, pos) {
7544
8117
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7545
8118
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7546
8119
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7547
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
8120
+ const first = emitFallible(def.parser, ctx, pos, true);
8121
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7548
8122
  const stmts = [
7549
8123
  `${ind(ctx)}const ${arrV} = []`,
7550
8124
  `${ind(ctx)}let ${curV} = ${pos}`,
7551
8125
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7552
8126
  ...firstStmts
7553
8127
  ];
7554
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7555
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
8128
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
8129
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
8130
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7556
8131
  const rr = v(ctx, "_rr");
7557
8132
  return [
7558
8133
  `${ind(ctx)}if (!${nextOkVar}) {`,
8134
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7559
8135
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7560
8136
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7561
8137
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7584,6 +8160,7 @@ function emitSepBy(_p, def, ctx, pos) {
7584
8160
  stmts.push(
7585
8161
  `${ind(ctx)}let ${didV} = false`,
7586
8162
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8163
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7587
8164
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7588
8165
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7589
8166
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7594,6 +8171,7 @@ function emitSepBy(_p, def, ctx, pos) {
7594
8171
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7595
8172
  ctx.indent++;
7596
8173
  } else {
8174
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7597
8175
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7598
8176
  ctx.indent++;
7599
8177
  stmts.push(
@@ -7622,16 +8200,18 @@ function emitSepBy(_p, def, ctx, pos) {
7622
8200
  );
7623
8201
  sepAtPos = spV;
7624
8202
  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}; `;
7625
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7626
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
8203
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8204
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8205
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7627
8206
  const post = postSepMarks();
7628
8207
  stmts.push(...post.decl);
7629
8208
  const npV = v(ctx, "_np");
7630
8209
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7631
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8210
+ const next = emitFallible(def.parser, ctx, npV, true);
8211
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7632
8212
  stmts.push(
7633
8213
  ...nextStmts,
7634
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8214
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7635
8215
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7636
8216
  `${ind(ctx)}${curV} = ${nextEnd}`
7637
8217
  );
@@ -7640,14 +8220,16 @@ function emitSepBy(_p, def, ctx, pos) {
7640
8220
  const spV = v(ctx, "_sp");
7641
8221
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7642
8222
  sepAtPos = spV;
7643
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7644
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8223
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8224
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8225
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7645
8226
  const npV = v(ctx, "_np");
7646
8227
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7647
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8228
+ const next = emitFallible(def.parser, ctx, npV, true);
8229
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7648
8230
  stmts.push(
7649
8231
  ...nextStmts,
7650
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8232
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7651
8233
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7652
8234
  `${ind(ctx)}${curV} = ${nextEnd}`
7653
8235
  );
@@ -7661,15 +8243,17 @@ function emitSepBy(_p, def, ctx, pos) {
7661
8243
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7662
8244
  );
7663
8245
  }
7664
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7665
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8246
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8247
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8248
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7666
8249
  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}; ` : "";
7667
8250
  const post = postSepMarks();
7668
8251
  stmts.push(...post.decl);
7669
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
8252
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
8253
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7670
8254
  stmts.push(
7671
8255
  ...nextStmts,
7672
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8256
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7673
8257
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7674
8258
  `${ind(ctx)}${curV} = ${nextEnd}`
7675
8259
  );
@@ -7770,6 +8354,18 @@ function emitPeek(def, ctx, pos) {
7770
8354
  endVar: pos
7771
8355
  };
7772
8356
  }
8357
+ function emitRouted(ctx, pos) {
8358
+ const item = v(ctx, "_rt");
8359
+ return {
8360
+ stmts: [
8361
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8362
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8363
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8364
+ ],
8365
+ valueVar: `${item}.value`,
8366
+ endVar: `${item}.span.end`
8367
+ };
8368
+ }
7773
8369
  function escapeTokenLiteral(value) {
7774
8370
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
7775
8371
  }
@@ -8050,7 +8646,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8050
8646
  const rv = v(ctx, "_rt");
8051
8647
  const vv = v(ctx, "_rtv");
8052
8648
  const ev = v(ctx, "_rte");
8053
- 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 }`;
8649
+ 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 } : {}) }`;
8054
8650
  const stmts = [
8055
8651
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8056
8652
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8131,7 +8727,7 @@ function emitRecover(def, ctx, pos) {
8131
8727
  return { stmts, valueVar: valVar, endVar };
8132
8728
  }
8133
8729
  function emitExpect(def, ctx, pos) {
8134
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8730
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8135
8731
  const ind0 = ind(ctx);
8136
8732
  const errV = v(ctx, "_err");
8137
8733
  const stmts = [
@@ -8143,6 +8739,7 @@ function emitExpect(def, ctx, pos) {
8143
8739
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8144
8740
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8145
8741
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8742
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8146
8743
  `${ind0} ${valVar} = ${errV}`,
8147
8744
  `${ind0} ${endVar} = ${pos}`,
8148
8745
  `${ind0} ${okVar} = true`,
@@ -8153,7 +8750,7 @@ function emitExpect(def, ctx, pos) {
8153
8750
  function emit(p, ctx, pos) {
8154
8751
  const replay = ctx.replayPrefix?.get(p);
8155
8752
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8156
- const dispatch = (emissionPos = pos) => {
8753
+ const dispatch2 = (emissionPos = pos) => {
8157
8754
  const savedRule = ctx.activeCoverageRuleId;
8158
8755
  const rule = ctx.coverage?.plan.rules.get(p);
8159
8756
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8193,14 +8790,14 @@ function emit(p, ctx, pos) {
8193
8790
  ctx.indent = 1;
8194
8791
  ctx.failLabel = "_pfail";
8195
8792
  ctx.recordFail = true;
8196
- const r = dispatch("_pos");
8793
+ const r = dispatch2("_pos");
8197
8794
  ctx.indent = savedIndent;
8198
8795
  ctx.failLabel = savedFailLabel;
8199
8796
  ctx.recordFail = savedRecord;
8200
8797
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8201
8798
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8202
8799
  }
8203
- return instrument(dispatch());
8800
+ return instrument(dispatch2());
8204
8801
  }
8205
8802
  function emitDispatch(p, ctx, pos) {
8206
8803
  const bal = p._balancedAmbient;
@@ -8233,6 +8830,8 @@ function emitDispatch(p, ctx, pos) {
8233
8830
  return emitSeq(def, ctx, pos);
8234
8831
  case "choice":
8235
8832
  return emitChoice(p, def, ctx, pos);
8833
+ case "dispatch":
8834
+ return emitDispatchCombinator(p, def, ctx, pos);
8236
8835
  case "attempt":
8237
8836
  return emitAttempt(p, def, ctx, pos);
8238
8837
  case "many":
@@ -8357,6 +8956,8 @@ function emitDispatch(p, ctx, pos) {
8357
8956
  return emitNot(def, ctx, pos);
8358
8957
  case "peek":
8359
8958
  return emitPeek(def, ctx, pos);
8959
+ case "routed":
8960
+ return emitRouted(ctx, pos);
8360
8961
  case "node":
8361
8962
  return emitNode(def, ctx, pos);
8362
8963
  case "scanTo":
@@ -8453,6 +9054,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8453
9054
  case "sequence":
8454
9055
  case "choice":
8455
9056
  return d.parsers.some((x) => hasNodeDef(x, seen));
9057
+ case "dispatch":
9058
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => hasNodeDef(entry.parser, seen)) : false) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8456
9059
  case "sepBy":
8457
9060
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8458
9061
  case "scanTo":
@@ -8467,6 +9070,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8467
9070
  return false;
8468
9071
  }
8469
9072
  }
9073
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9074
+ if (seen.has(p)) return false;
9075
+ seen.add(p);
9076
+ const d = p._def;
9077
+ switch (d.tag) {
9078
+ case "routed":
9079
+ return true;
9080
+ case "lazy": {
9081
+ try {
9082
+ return parserUsesRouted2(d.thunk(), seen);
9083
+ } catch {
9084
+ return false;
9085
+ }
9086
+ }
9087
+ case "dispatch":
9088
+ return false;
9089
+ default:
9090
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9091
+ }
9092
+ }
8470
9093
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8471
9094
  if (seen.has(p)) return false;
8472
9095
  seen.add(p);
@@ -8486,6 +9109,8 @@ function childrenOf2(def) {
8486
9109
  case "sequence":
8487
9110
  case "choice":
8488
9111
  return def.parsers;
9112
+ case "dispatch":
9113
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8489
9114
  case "many":
8490
9115
  case "attempt":
8491
9116
  case "optional":
@@ -8522,6 +9147,7 @@ function childrenOf2(def) {
8522
9147
  case "regex":
8523
9148
  case "keywords":
8524
9149
  case "guard":
9150
+ case "routed":
8525
9151
  case "unknown":
8526
9152
  return [];
8527
9153
  }
@@ -8529,6 +9155,7 @@ function childrenOf2(def) {
8529
9155
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8530
9156
  "sequence",
8531
9157
  "choice",
9158
+ "dispatch",
8532
9159
  "many",
8533
9160
  "oneOrMore",
8534
9161
  "optional",
@@ -9353,6 +9980,8 @@ function childrenOf3(def) {
9353
9980
  case "sequence":
9354
9981
  case "choice":
9355
9982
  return def.parsers;
9983
+ case "dispatch":
9984
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9356
9985
  case "many":
9357
9986
  case "oneOrMore":
9358
9987
  case "optional":
@@ -9381,6 +10010,7 @@ function childrenOf3(def) {
9381
10010
  case "literal":
9382
10011
  case "regex":
9383
10012
  case "keywords":
10013
+ case "routed":
9384
10014
  case "guard":
9385
10015
  case "recover":
9386
10016
  case "unknown":
@@ -9405,6 +10035,16 @@ function repeatOpts(min, max, trailing) {
9405
10035
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9406
10036
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9407
10037
  }
10038
+ function matcherExpr(entry) {
10039
+ switch (entry.kind) {
10040
+ case "startsWith":
10041
+ return `startsWith(${JSON.stringify(entry.value)})`;
10042
+ case "endsWith":
10043
+ return `endsWith(${JSON.stringify(entry.value)})`;
10044
+ case "matches":
10045
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
10046
+ }
10047
+ }
9408
10048
  function serializeRuleMap(ruleMap, scanSkip) {
9409
10049
  try {
9410
10050
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9502,6 +10142,13 @@ function evalRuleMapIR(ir) {
9502
10142
  "keywords",
9503
10143
  "sequence",
9504
10144
  "choice",
10145
+ "dispatch",
10146
+ "when",
10147
+ "startsWith",
10148
+ "endsWith",
10149
+ "matches",
10150
+ "otherwise",
10151
+ "routed",
9505
10152
  "attempt",
9506
10153
  "many",
9507
10154
  "oneOrMore",
@@ -9535,6 +10182,13 @@ function evalRuleMapIR(ir) {
9535
10182
  keywords,
9536
10183
  sequence,
9537
10184
  choice,
10185
+ dispatch,
10186
+ when,
10187
+ startsWith,
10188
+ endsWith,
10189
+ matches,
10190
+ otherwise,
10191
+ routed,
9538
10192
  attempt,
9539
10193
  many,
9540
10194
  oneOrMore,
@@ -9720,6 +10374,18 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9720
10374
  });
9721
10375
  return `_gch([${items.join(", ")}])`;
9722
10376
  }
10377
+ case "dispatch": {
10378
+ const arms = def.cases.map(
10379
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10380
+ );
10381
+ if (def.matchers) {
10382
+ for (const entry of def.matchers) {
10383
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10384
+ }
10385
+ }
10386
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10387
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10388
+ }
9723
10389
  case "many":
9724
10390
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9725
10391
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -9736,6 +10402,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9736
10402
  return `not(${kid(def.parser)})`;
9737
10403
  case "peek":
9738
10404
  return `peek(${kid(def.parser)})`;
10405
+ case "routed":
10406
+ return "routed()";
9739
10407
  case "trivia":
9740
10408
  return `trivia(${kid(def.parser)})`;
9741
10409
  case "token":
@@ -10205,6 +10873,12 @@ function stripTsFromSource(node2, code) {
10205
10873
  function isWordFactory(v2) {
10206
10874
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10207
10875
  }
10876
+ function isWhenFactory(v2) {
10877
+ return !!v2 && typeof v2 === "object" && v2.tag === "whenFactory";
10878
+ }
10879
+ function isStaticValueEntry(v2) {
10880
+ return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10881
+ }
10208
10882
  function wordFactoryFromArgs(args, scope, code, mfs) {
10209
10883
  const [boundaryOrOptsArg, optsArg] = args;
10210
10884
  if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
@@ -10224,6 +10898,23 @@ function wordFactoryFromArgs(args, scope, code, mfs) {
10224
10898
  if (typeof caseInsensitive !== "boolean") return null;
10225
10899
  return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10226
10900
  }
10901
+ function dispatchWhenOptions(v2) {
10902
+ if (v2 === void 0) return {};
10903
+ if (typeof v2 !== "object" || v2 === null || Array.isArray(v2)) return null;
10904
+ for (const key of Object.keys(v2)) {
10905
+ if (key !== "caseInsensitive") return null;
10906
+ }
10907
+ const caseInsensitive = "caseInsensitive" in v2 ? v2.caseInsensitive : false;
10908
+ if (typeof caseInsensitive !== "boolean") return null;
10909
+ return { caseInsensitive };
10910
+ }
10911
+ function whenFactoryFromArgs(args, scope, code, mfs) {
10912
+ const [optsArg, extra] = args;
10913
+ if (optsArg?.type === "SpreadElement" || extra !== void 0) return null;
10914
+ const rawOpts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10915
+ const opts = dispatchWhenOptions(rawOpts);
10916
+ return opts === null ? null : { tag: "whenFactory", caseInsensitive: opts.caseInsensitive ?? false };
10917
+ }
10227
10918
  var SUPPORTED = {
10228
10919
  literal: (...a) => literal(a[0], a[1]),
10229
10920
  regex: (...a) => regex(a[0], a[1]),
@@ -10239,6 +10930,7 @@ var SUPPORTED = {
10239
10930
  field: (...a) => field(a[0], a[1]),
10240
10931
  noTrivia: (...a) => noTrivia(a[0]),
10241
10932
  token: (...a) => token(a[0]),
10933
+ routed: () => routed(),
10242
10934
  leaf: (...a) => leaf(a[0], a[1]),
10243
10935
  expect: (...a) => expect(a[0], a[1])
10244
10936
  };
@@ -10274,9 +10966,65 @@ function gatedArmParts(e) {
10274
10966
  }
10275
10967
  return gate2 && combinator ? { gate: gate2, combinator } : null;
10276
10968
  }
10969
+ function dispatchArmValue(node2, scope, code, mfs) {
10970
+ if (node2.type === "Identifier") {
10971
+ const value = anyValue(node2, scope, code, mfs);
10972
+ return isDispatchArm(value) ? value : null;
10973
+ }
10974
+ if (node2.type !== "CallExpression") return null;
10975
+ const callee = node2.callee;
10976
+ if (callee.type !== "Identifier") return null;
10977
+ const factory = scope.get(callee.name);
10978
+ if (isWhenFactory(factory)) {
10979
+ const [keyArg, parserArg, extraArg] = node2.arguments;
10980
+ if (!keyArg || !parserArg || extraArg !== void 0 || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10981
+ const key = anyValue(keyArg, scope, code, mfs);
10982
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10983
+ if (!isCombinator(parserValue)) return null;
10984
+ const opts = { caseInsensitive: factory.caseInsensitive };
10985
+ if (typeof key === "string") return when(key, parserValue, opts);
10986
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
10987
+ return null;
10988
+ }
10989
+ if (callee.name === "otherwise") {
10990
+ const [parserArg] = node2.arguments;
10991
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
10992
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10993
+ if (!isCombinator(parserValue)) return null;
10994
+ return otherwise(parserValue);
10995
+ }
10996
+ if (callee.name === "when") {
10997
+ const [keyArg, parserArg, optsArg] = node2.arguments;
10998
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10999
+ const key = anyValue(keyArg, scope, code, mfs);
11000
+ const parserValue = anyValue(parserArg, scope, code, mfs);
11001
+ const opts = dispatchWhenOptions(optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs));
11002
+ if (opts === null) return null;
11003
+ if (!isCombinator(parserValue)) return null;
11004
+ if (typeof key === "string") return when(key, parserValue, opts);
11005
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
11006
+ if (isDispatchMatcher(key)) return when(key, parserValue, opts);
11007
+ }
11008
+ return null;
11009
+ }
10277
11010
  function isCombinator(v2) {
10278
11011
  return !!v2 && typeof v2 === "object" && "_def" in v2;
10279
11012
  }
11013
+ function isDispatchArm(v2) {
11014
+ if (!v2 || typeof v2 !== "object") return false;
11015
+ const rec = v2;
11016
+ if (rec.kind === "otherwise") return isCombinator(rec.parser);
11017
+ if (rec.kind === "whenMatcher") {
11018
+ return isDispatchMatcher(rec.matcher) && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11019
+ }
11020
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11021
+ }
11022
+ function isDispatchMatcher(v2) {
11023
+ if (!v2 || typeof v2 !== "object") return false;
11024
+ const rec = v2;
11025
+ if ((rec.kind === "startsWith" || rec.kind === "endsWith") && typeof rec.value === "string") return true;
11026
+ return rec.kind === "matches" && typeof rec.value === "string" && typeof rec.flags === "string";
11027
+ }
10280
11028
  function scopeGet(scope, name, mfs) {
10281
11029
  const entry = scope.get(name);
10282
11030
  if (!entry) return null;
@@ -10533,6 +11281,25 @@ function exprToCombi(node2, scope, code, mfs) {
10533
11281
  return null;
10534
11282
  }
10535
11283
  }
11284
+ if (callee.name === "dispatch") {
11285
+ const [selectorArg, ...armArgs] = node2.arguments;
11286
+ if (!selectorArg || selectorArg.type === "SpreadElement") return null;
11287
+ const selector = anyValue(selectorArg, scope, code, mfs);
11288
+ if (!isCombinator(selector)) return null;
11289
+ const arms = [];
11290
+ for (const arg of armArgs) {
11291
+ if (arg.type === "SpreadElement") return null;
11292
+ const arm = dispatchArmValue(arg, scope, code, mfs);
11293
+ if (!isDispatchArm(arm)) return null;
11294
+ arms.push(arm);
11295
+ }
11296
+ try {
11297
+ const unsafeDispatch = dispatch;
11298
+ return unsafeDispatch(selector, ...arms);
11299
+ } catch {
11300
+ return null;
11301
+ }
11302
+ }
10536
11303
  if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
10537
11304
  if (code === void 0 || mfs === void 0) return null;
10538
11305
  const arms = [];
@@ -10618,6 +11385,10 @@ function anyValue(node2, scope, code, mfs) {
10618
11385
  if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10619
11386
  return entry.combi;
10620
11387
  }
11388
+ if (isStaticValueEntry(entry)) {
11389
+ if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
11390
+ return entry.value;
11391
+ }
10621
11392
  return entry;
10622
11393
  }
10623
11394
  if (node2.type === "MemberExpression") {
@@ -10638,6 +11409,27 @@ function anyValue(node2, scope, code, mfs) {
10638
11409
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10639
11410
  return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
10640
11411
  }
11412
+ if (callee.type === "Identifier" && callee.name === "makeWhen") {
11413
+ return whenFactoryFromArgs(node2.arguments, scope, code, mfs);
11414
+ }
11415
+ if (callee.type === "Identifier" && (callee.name === "startsWith" || callee.name === "endsWith")) {
11416
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11417
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11418
+ if (typeof value !== "string") return null;
11419
+ return callee.name === "startsWith" ? startsWith(value) : endsWith(value);
11420
+ }
11421
+ if (callee.type === "Identifier" && callee.name === "matches") {
11422
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11423
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11424
+ if (!(value instanceof RegExp)) return null;
11425
+ return matches(value);
11426
+ }
11427
+ if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11428
+ return dispatchArmValue(node2, scope, code, mfs);
11429
+ }
11430
+ if (callee.type === "Identifier" && isWhenFactory(scope.get(callee.name))) {
11431
+ return dispatchArmValue(node2, scope, code, mfs);
11432
+ }
10641
11433
  return exprToCombi(node2, scope, code, mfs);
10642
11434
  }
10643
11435
  return null;
@@ -10648,6 +11440,12 @@ function evaluateWordFactory(node2, scope, code) {
10648
11440
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
10649
11441
  return wordFactoryFromArgs(node2.arguments, scope, code);
10650
11442
  }
11443
+ function evaluateWhenFactory(node2, scope, code) {
11444
+ if (node2.type !== "CallExpression") return null;
11445
+ const callee = node2.callee;
11446
+ if (callee.type !== "Identifier" || callee.name !== "makeWhen") return null;
11447
+ return whenFactoryFromArgs(node2.arguments, scope, code);
11448
+ }
10651
11449
  function evaluateExpr(node2, scope, code, mapFnSources) {
10652
11450
  return exprToCombi(node2, scope, code, mapFnSources);
10653
11451
  }
@@ -10696,6 +11494,7 @@ function evalBodyStatements(statements, scope, code) {
10696
11494
  if (val === null) return false;
10697
11495
  const thisDeclMfSrcs = bodyMfs.slice(before);
10698
11496
  if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
11497
+ else if (isDispatchArm(val)) scope.set(name, { value: val, mfSrcs: thisDeclMfSrcs });
10699
11498
  else scope.set(name, val);
10700
11499
  }
10701
11500
  }
@@ -11227,7 +12026,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11227
12026
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
11228
12027
  return [...ids].sort().map((id2) => ({
11229
12028
  id: id2,
11230
- kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
12029
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
11231
12030
  }));
11232
12031
  };
11233
12032
  const localComposedCarried = /* @__PURE__ */ new Map();
@@ -11673,6 +12472,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11673
12472
  if (wordFactory) {
11674
12473
  ;
11675
12474
  scope.set(varName, wordFactory);
12475
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12476
+ continue;
12477
+ }
12478
+ const whenFactory = evaluateWhenFactory(init, scope, code);
12479
+ if (whenFactory) {
12480
+ ;
12481
+ scope.set(varName, whenFactory);
12482
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
11676
12483
  continue;
11677
12484
  }
11678
12485
  const combiArray = evaluateCombinatorArray(init, scope, code);