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
@@ -59,6 +59,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
59
59
  return d.value.length === 0;
60
60
  case "keywords":
61
61
  return false;
62
+ case "routed":
63
+ return false;
62
64
  case "regex":
63
65
  try {
64
66
  const m = new RegExp(d.source).exec("");
@@ -85,6 +87,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
85
87
  return d.parsers.every(me);
86
88
  case "choice":
87
89
  return d.parsers.some(me);
90
+ case "dispatch":
91
+ return me(d.selector);
88
92
  case "transform":
89
93
  case "label":
90
94
  case "trivia":
@@ -158,6 +162,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
158
162
  case "keywords":
159
163
  return p._meta.firstSet;
160
164
  // terminals: no refs, cached set is exact
165
+ case "dispatch":
166
+ return fs2(d.selector);
161
167
  case "lazy":
162
168
  try {
163
169
  return fs2(d.thunk());
@@ -1106,6 +1112,8 @@ function derive(c, seen) {
1106
1112
  return [def.label];
1107
1113
  case "choice":
1108
1114
  return def.parsers.flatMap(deriveExpected2);
1115
+ case "dispatch":
1116
+ return deriveExpected2(def.selector);
1109
1117
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1110
1118
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1111
1119
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1145,6 +1153,8 @@ function derive(c, seen) {
1145
1153
  case "not":
1146
1154
  case "peek":
1147
1155
  return deriveExpected2(def.parser);
1156
+ case "routed":
1157
+ return ["routed()"];
1148
1158
  case "lazy": {
1149
1159
  const name = c._ruleName;
1150
1160
  if (seen.has(c)) return name ? [name] : [];
@@ -1244,6 +1254,7 @@ function choice(...args) {
1244
1254
  const result = parsers[idx].parse(input, pos, ctx);
1245
1255
  if (result.ok) return result;
1246
1256
  expected.push(...result.expected);
1257
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1247
1258
  return { ok: false, expected, span: { start: pos, end: pos } };
1248
1259
  }
1249
1260
  return {
@@ -1284,6 +1295,7 @@ function choice(...args) {
1284
1295
  rollbackCstCapture(ctx, mark);
1285
1296
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1286
1297
  expected.push(...result.expected);
1298
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1287
1299
  continue;
1288
1300
  }
1289
1301
  const checks = autoNot[i];
@@ -1963,6 +1975,245 @@ function sequence(...parsers) {
1963
1975
  };
1964
1976
  }
1965
1977
 
1978
+ // src/combinators/dispatch.ts
1979
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
1980
+ if (seen.has(parser2)) return false;
1981
+ seen.add(parser2);
1982
+ const def = parser2._def;
1983
+ switch (def.tag) {
1984
+ case "routed":
1985
+ return true;
1986
+ case "sequence":
1987
+ case "choice":
1988
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
1989
+ case "dispatch":
1990
+ return false;
1991
+ case "sepBy":
1992
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
1993
+ case "skip":
1994
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
1995
+ case "grammar":
1996
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
1997
+ case "scanTo":
1998
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
1999
+ case "recover":
2000
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
2001
+ case "lazy":
2002
+ try {
2003
+ return parserUsesRouted(def.thunk(), seen);
2004
+ } catch {
2005
+ return false;
2006
+ }
2007
+ case "many":
2008
+ case "oneOrMore":
2009
+ case "optional":
2010
+ case "attempt":
2011
+ case "transform":
2012
+ case "trivia":
2013
+ case "token":
2014
+ case "leaf":
2015
+ case "label":
2016
+ case "field":
2017
+ case "not":
2018
+ case "peek":
2019
+ case "node":
2020
+ case "withCtx":
2021
+ case "expect":
2022
+ return parserUsesRouted(def.parser, seen);
2023
+ default:
2024
+ return false;
2025
+ }
2026
+ }
2027
+ function branchUsesRouted(branch) {
2028
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
2029
+ }
2030
+ function asciiFoldKey(key) {
2031
+ let out = "";
2032
+ for (let i = 0; i < key.length; i++) {
2033
+ const c = key.charCodeAt(i);
2034
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
2035
+ }
2036
+ return out;
2037
+ }
2038
+ function matchesDispatchMatcher(value, matcher) {
2039
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2040
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2041
+ switch (matcher.kind) {
2042
+ case "startsWith":
2043
+ return candidate.startsWith(expected);
2044
+ case "endsWith":
2045
+ return candidate.endsWith(expected);
2046
+ case "matches": {
2047
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2048
+ return new RegExp(matcher.value, flags).test(value);
2049
+ }
2050
+ }
2051
+ }
2052
+ function isStringMatcher(value) {
2053
+ if (typeof value !== "object" || value === null) return false;
2054
+ const kind = value.kind;
2055
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2056
+ }
2057
+ function fixedMatcher(kind, value) {
2058
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2059
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2060
+ return { kind, value };
2061
+ }
2062
+ function startsWith(prefix) {
2063
+ return fixedMatcher("startsWith", prefix);
2064
+ }
2065
+ function endsWith(suffix) {
2066
+ return fixedMatcher("endsWith", suffix);
2067
+ }
2068
+ function matches(pattern) {
2069
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2070
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2071
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2072
+ }
2073
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2074
+ }
2075
+ function when(key, parser2, opts = {}) {
2076
+ const usesRouted = parserUsesRouted(parser2);
2077
+ if (isStringMatcher(key)) {
2078
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2079
+ }
2080
+ const keys = Array.isArray(key) ? [...key] : [key];
2081
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2082
+ for (const item of keys) {
2083
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2084
+ }
2085
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2086
+ }
2087
+ function otherwise(parser2) {
2088
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2089
+ }
2090
+ function routed() {
2091
+ return {
2092
+ _tag: "routed",
2093
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2094
+ _def: { tag: "routed" },
2095
+ parse(_input, pos, ctx) {
2096
+ const item = ctx._routed;
2097
+ if (item === void 0) {
2098
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2099
+ }
2100
+ if (pos !== item.span.start) {
2101
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2102
+ }
2103
+ if (cstCaptureActive(ctx)) {
2104
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2105
+ }
2106
+ return { ok: true, value: item.value, span: item.span };
2107
+ }
2108
+ };
2109
+ }
2110
+ function dispatch(selector, ...arms) {
2111
+ if (parserUsesRouted(selector)) {
2112
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2113
+ }
2114
+ let fallback;
2115
+ let fallbackUsesRouted = false;
2116
+ const matchers = [];
2117
+ const cases = [];
2118
+ const seen = [];
2119
+ for (let i = 0; i < arms.length; i++) {
2120
+ const arm = arms[i];
2121
+ if (arm.kind === "otherwise") {
2122
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2123
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2124
+ fallback = arm.parser;
2125
+ fallbackUsesRouted = arm.usesRouted === true;
2126
+ continue;
2127
+ }
2128
+ if (arm.kind === "whenMatcher") {
2129
+ const matcher = arm.matcher;
2130
+ matchers.push({
2131
+ kind: matcher.kind,
2132
+ value: matcher.value,
2133
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2134
+ parser: arm.parser,
2135
+ caseInsensitive: arm.caseInsensitive,
2136
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2137
+ });
2138
+ continue;
2139
+ }
2140
+ for (const key of arm.keys) {
2141
+ const folded = asciiFoldKey(key);
2142
+ for (const prior of seen) {
2143
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2144
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2145
+ }
2146
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2147
+ }
2148
+ cases.push({
2149
+ keys: arm.keys,
2150
+ parser: arm.parser,
2151
+ caseInsensitive: arm.caseInsensitive,
2152
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2153
+ });
2154
+ }
2155
+ const meta = {
2156
+ firstSet: selector._meta.firstSet,
2157
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2158
+ isTrivia: false
2159
+ };
2160
+ const byKey = /* @__PURE__ */ new Map();
2161
+ const byFoldedKey = /* @__PURE__ */ new Map();
2162
+ for (const entry of cases) {
2163
+ for (const key of entry.keys) {
2164
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2165
+ else byKey.set(key, entry);
2166
+ }
2167
+ }
2168
+ return {
2169
+ _tag: "dispatch",
2170
+ _meta: meta,
2171
+ _def: {
2172
+ tag: "dispatch",
2173
+ selector,
2174
+ cases,
2175
+ ...matchers.length === 0 ? {} : { matchers },
2176
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2177
+ },
2178
+ parse(input, pos, ctx) {
2179
+ if (parserUsesRouted(selector)) {
2180
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2181
+ }
2182
+ const selectorMark = saveTriviaMark(ctx);
2183
+ const selected = selector.parse(input, pos, ctx);
2184
+ if (!selected.ok) return selected;
2185
+ 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 });
2186
+ if (branch === void 0) {
2187
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2188
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2189
+ }
2190
+ const savedRouted = ctx._routed;
2191
+ const usesRouted = branchUsesRouted(branch);
2192
+ let mark = saveTriviaMark(ctx);
2193
+ if (usesRouted) {
2194
+ rollbackTrivia(ctx, selectorMark);
2195
+ mark = saveTriviaMark(ctx);
2196
+ ctx._routed = { value: selected.value, span: selected.span };
2197
+ }
2198
+ let result;
2199
+ try {
2200
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2201
+ } finally {
2202
+ if (usesRouted) ctx._routed = savedRouted;
2203
+ }
2204
+ if (!result.ok) {
2205
+ rollbackTrivia(ctx, mark);
2206
+ return { ...result, committed: true };
2207
+ }
2208
+ return {
2209
+ ok: true,
2210
+ value: [selected.value, result.value],
2211
+ span: { start: pos, end: result.span.end }
2212
+ };
2213
+ }
2214
+ };
2215
+ }
2216
+
1966
2217
  // src/combinators/attempt.ts
1967
2218
  function attempt(parser2) {
1968
2219
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -1985,6 +2236,7 @@ function attempt(parser2) {
1985
2236
  if (result.ok) return result;
1986
2237
  rollbackCstCapture(ctx, mark);
1987
2238
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2239
+ if (result.committed) return result;
1988
2240
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1989
2241
  }
1990
2242
  };
@@ -2014,6 +2266,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2014
2266
  const result = combinator.parse(input, pos, ctx);
2015
2267
  if (!result.ok) {
2016
2268
  rollbackTrivia(ctx, mark);
2269
+ if (result.committed) return { fail: result, failPos: pos };
2017
2270
  return { fail: result, failPos: pos };
2018
2271
  }
2019
2272
  if (result.span.end === pos) {
@@ -2057,6 +2310,7 @@ function many(combinator, opts = {}) {
2057
2310
  const item = repItem(combinator, input, cur, ctx, guardable);
2058
2311
  if (item === "stop") break;
2059
2312
  if ("fail" in item) {
2313
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2060
2314
  const sync = ctx._tolerant ? ctx._sync : void 0;
2061
2315
  if (sync === void 0) break;
2062
2316
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2101,6 +2355,7 @@ function atLeast(combinator, min, max) {
2101
2355
  while (count < min) {
2102
2356
  const item = repItem(combinator, input, cur, ctx, guardable);
2103
2357
  if (item === "stop" || "fail" in item) {
2358
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2104
2359
  expected ??= deriveExpected(combinator);
2105
2360
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2106
2361
  }
@@ -2113,6 +2368,7 @@ function atLeast(combinator, min, max) {
2113
2368
  const item = repItem(combinator, input, cur, ctx, guardable);
2114
2369
  if (item === "stop") break;
2115
2370
  if ("fail" in item) {
2371
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2116
2372
  const sync = ctx._tolerant ? ctx._sync : void 0;
2117
2373
  if (sync === void 0) break;
2118
2374
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2151,6 +2407,7 @@ function optional(combinator) {
2151
2407
  const result = combinator.parse(input, pos, ctx);
2152
2408
  if (result.ok) return result;
2153
2409
  rollbackTrivia(ctx, mark);
2410
+ if (result.committed) return result;
2154
2411
  return { ok: true, value: null, span: { start: pos, end: pos } };
2155
2412
  }
2156
2413
  };
@@ -2190,6 +2447,7 @@ function sepBy(combinator, separator, opts = {}) {
2190
2447
  values.push(first.value);
2191
2448
  cur = first.span.end;
2192
2449
  } else {
2450
+ if (first.committed) return first;
2193
2451
  const term = ctx._tolerant ? ctx._sync : void 0;
2194
2452
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2195
2453
  if (min >= 1) return failAt2(pos);
@@ -2217,6 +2475,7 @@ function sepBy(combinator, separator, opts = {}) {
2217
2475
  const sep2 = separator.parse(input, sepPos, ctx);
2218
2476
  if (!sep2.ok) {
2219
2477
  rollbackTrivia(ctx, loopMark);
2478
+ if (sep2.committed) return sep2;
2220
2479
  break;
2221
2480
  }
2222
2481
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2232,6 +2491,7 @@ function sepBy(combinator, separator, opts = {}) {
2232
2491
  }
2233
2492
  const next = combinator.parse(input, nextPos, ctx);
2234
2493
  if (!next.ok) {
2494
+ if (next.committed) return next;
2235
2495
  const term = ctx._tolerant ? ctx._sync : void 0;
2236
2496
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2237
2497
  expected ??= deriveExpected(combinator);
@@ -2287,6 +2547,12 @@ function markUnusedValues(root) {
2287
2547
  case "choice":
2288
2548
  for (const p of def.parsers) visit(p, consumed);
2289
2549
  return;
2550
+ case "dispatch":
2551
+ visit(def.selector, consumed);
2552
+ for (const entry of def.cases) visit(entry.parser, consumed);
2553
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2554
+ if (def.otherwise) visit(def.otherwise, consumed);
2555
+ return;
2290
2556
  case "node":
2291
2557
  visit(def.parser, false);
2292
2558
  return;
@@ -2562,6 +2828,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2562
2828
  case "sequence":
2563
2829
  case "choice":
2564
2830
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2831
+ case "dispatch":
2832
+ 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);
2565
2833
  case "sepBy":
2566
2834
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2567
2835
  case "skip":
@@ -2629,6 +2897,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2629
2897
  return parserHasTriviaSite(d.parser, seen);
2630
2898
  case "choice":
2631
2899
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2900
+ case "dispatch":
2901
+ 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);
2632
2902
  case "grammar":
2633
2903
  return parserHasTriviaSite(d.parser, seen);
2634
2904
  case "lazy": {
@@ -2654,6 +2924,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2654
2924
  case "sequence":
2655
2925
  case "choice":
2656
2926
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2927
+ case "dispatch":
2928
+ 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);
2657
2929
  case "sepBy":
2658
2930
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2659
2931
  case "skip":
@@ -3028,7 +3300,7 @@ function leaf(root, fn) {
3028
3300
  }
3029
3301
 
3030
3302
  // src/version.ts
3031
- var PARSEMAN_VERSION = "0.38.0";
3303
+ var PARSEMAN_VERSION = "0.39.1";
3032
3304
 
3033
3305
  // src/cst/host-mode.ts
3034
3306
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3184,6 +3456,8 @@ function classifyBroadArm(arm, resolve2) {
3184
3456
  }
3185
3457
  return null;
3186
3458
  }
3459
+ case "dispatch":
3460
+ return walk2(d.selector);
3187
3461
  default:
3188
3462
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3189
3463
  }
@@ -3283,6 +3557,12 @@ function analyzeGatingRules(ruleMap, opts) {
3283
3557
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3284
3558
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3285
3559
  if (rec[k]) kids.push(rec[k]);
3560
+ if (d.tag === "dispatch") {
3561
+ kids.push(d.selector);
3562
+ for (const c of d.cases) kids.push(c.parser);
3563
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3564
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3565
+ }
3286
3566
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3287
3567
  if (d.tag === "lazy") {
3288
3568
  try {
@@ -3389,6 +3669,12 @@ function childrenOf(d) {
3389
3669
  case "sequence":
3390
3670
  case "choice":
3391
3671
  return d.parsers;
3672
+ case "dispatch":
3673
+ return [
3674
+ d.selector,
3675
+ ...d.cases.map((c) => c.parser),
3676
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3677
+ ];
3392
3678
  case "skip":
3393
3679
  return [d.main, d.skipped];
3394
3680
  case "sepBy":
@@ -3445,6 +3731,8 @@ function payloadKey(p, d) {
3445
3731
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3446
3732
  case "sepBy":
3447
3733
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3734
+ case "dispatch":
3735
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3448
3736
  case "expect":
3449
3737
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3450
3738
  case "scanTo":
@@ -3516,6 +3804,11 @@ function render(p, depth = 3) {
3516
3804
  return `sequence(${kids.join(", ")})`;
3517
3805
  case "choice":
3518
3806
  return `choice(${kids.join(", ")})`;
3807
+ case "dispatch": {
3808
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3809
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3810
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3811
+ }
3519
3812
  case "node":
3520
3813
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3521
3814
  case "label":
@@ -3539,6 +3832,10 @@ function segment(d, index) {
3539
3832
  return `seq[${index}]`;
3540
3833
  case "choice":
3541
3834
  return `choice[${index}]`;
3835
+ case "dispatch":
3836
+ if (index === 0) return "dispatch.selector";
3837
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3838
+ return "dispatch.otherwise";
3542
3839
  case "node":
3543
3840
  return `node(${d.type ?? ""})`;
3544
3841
  case "label":
@@ -4787,11 +5084,25 @@ function escapeClassChar(ch2) {
4787
5084
  }
4788
5085
 
4789
5086
  // src/compiler/grammar-coverage-ids.ts
5087
+ function idPart(value) {
5088
+ return encodeURIComponent(value);
5089
+ }
5090
+ function dispatchArmIds(def, path2) {
5091
+ const ids = [];
5092
+ for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5093
+ for (const entry of def.matchers ?? []) {
5094
+ ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5095
+ }
5096
+ if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5097
+ return ids;
5098
+ }
4790
5099
  function children(def, winners) {
4791
5100
  switch (def.tag) {
4792
5101
  case "sequence":
4793
5102
  case "choice":
4794
5103
  return def.parsers;
5104
+ case "dispatch":
5105
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4795
5106
  case "many":
4796
5107
  case "oneOrMore":
4797
5108
  case "optional":
@@ -4834,6 +5145,7 @@ function children(def, winners) {
4834
5145
  function buildGrammarPlan(entry, winners) {
4835
5146
  const definitions = /* @__PURE__ */ new Map();
4836
5147
  const choices = /* @__PURE__ */ new WeakMap();
5148
+ const dispatches = /* @__PURE__ */ new WeakMap();
4837
5149
  const attempts = /* @__PURE__ */ new WeakMap();
4838
5150
  const labels = /* @__PURE__ */ new WeakMap();
4839
5151
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -4864,6 +5176,11 @@ function buildGrammarPlan(entry, winners) {
4864
5176
  choices.set(parser2, ids);
4865
5177
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
4866
5178
  }
5179
+ if (parser2._def.tag === "dispatch") {
5180
+ const ids = dispatchArmIds(parser2._def, path2);
5181
+ dispatches.set(parser2, ids);
5182
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5183
+ }
4867
5184
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
4868
5185
  if (parser2._def.tag === "label") {
4869
5186
  const id = `label:${path2}`;
@@ -4876,7 +5193,7 @@ function buildGrammarPlan(entry, winners) {
4876
5193
  for (const root of roots) {
4877
5194
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
4878
5195
  }
4879
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5196
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
4880
5197
  }
4881
5198
 
4882
5199
  // src/compiler/scannable-run.ts
@@ -6250,12 +6567,15 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6250
6567
  case "literal":
6251
6568
  case "regex":
6252
6569
  case "keywords":
6570
+ case "routed":
6253
6571
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6254
6572
  case "choice": {
6255
6573
  const alts = [];
6256
6574
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6257
6575
  return { alts };
6258
6576
  }
6577
+ case "dispatch":
6578
+ return rec(d.selector);
6259
6579
  case "sequence": {
6260
6580
  const chain = [];
6261
6581
  for (const term of d.parsers) {
@@ -6338,6 +6658,9 @@ function failReturn(expected, posExpr) {
6338
6658
  function failReturnArr(expectedArr, posExpr) {
6339
6659
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6340
6660
  }
6661
+ function committedReturnArr(expectedArr, posExpr) {
6662
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6663
+ }
6341
6664
  function hoistExpected(ctx, constArrSource) {
6342
6665
  let name = ctx.expectedMap.get(constArrSource);
6343
6666
  if (name === void 0) {
@@ -6374,15 +6697,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6374
6697
  return probe + trace + failReturnArr(expectedArr, posExpr);
6375
6698
  }
6376
6699
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6377
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6700
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6701
+ }
6702
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6703
+ if (ctx.failLabel) {
6704
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6705
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6706
+ }
6707
+ return committedReturnArr(expectedArr, posExpr);
6378
6708
  }
6379
6709
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6380
6710
  if (srcCtx !== "_ctx") {
6381
6711
  if (ctx.failLabel) {
6382
6712
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6383
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6713
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6384
6714
  }
6385
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6715
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6386
6716
  }
6387
6717
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6388
6718
  return resultFromRecorded();
@@ -6595,6 +6925,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6595
6925
  // nothing committed remains.
6596
6926
  case "choice":
6597
6927
  return false;
6928
+ case "dispatch":
6929
+ 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);
6598
6930
  case "attempt":
6599
6931
  return false;
6600
6932
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6639,6 +6971,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6639
6971
  return true;
6640
6972
  }
6641
6973
  }
6974
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6975
+ if (seen.has(p)) return false;
6976
+ seen.add(p);
6977
+ const d = p._def;
6978
+ switch (d.tag) {
6979
+ case "dispatch":
6980
+ return true;
6981
+ case "choice":
6982
+ case "sequence":
6983
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
6984
+ case "many":
6985
+ case "oneOrMore":
6986
+ case "optional":
6987
+ case "attempt":
6988
+ case "transform":
6989
+ case "label":
6990
+ case "field":
6991
+ case "grammar":
6992
+ case "node":
6993
+ return mayCommitFailure(d.parser, seen);
6994
+ case "sepBy":
6995
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
6996
+ case "skip":
6997
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
6998
+ case "token":
6999
+ case "leaf":
7000
+ return mayCommitFailure(d.parser, seen);
7001
+ case "withCtx":
7002
+ return mayCommitFailure(d.parser, seen);
7003
+ case "scanTo":
7004
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
7005
+ case "lazy": {
7006
+ try {
7007
+ return mayCommitFailure(d.thunk(), seen);
7008
+ } catch {
7009
+ return true;
7010
+ }
7011
+ }
7012
+ case "unknown":
7013
+ return true;
7014
+ default:
7015
+ return false;
7016
+ }
7017
+ }
6642
7018
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6643
7019
  if (seen.has(p)) return false;
6644
7020
  seen.add(p);
@@ -6649,6 +7025,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6649
7025
  case "keywords":
6650
7026
  case "node":
6651
7027
  case "token":
7028
+ case "routed":
6652
7029
  case "leaf":
6653
7030
  return true;
6654
7031
  case "not":
@@ -6660,6 +7037,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6660
7037
  case "sequence":
6661
7038
  case "choice":
6662
7039
  return d.parsers.some((x) => capturesLeaf(x, seen));
7040
+ case "dispatch":
7041
+ 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);
6663
7042
  case "sepBy":
6664
7043
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6665
7044
  case "many":
@@ -6706,6 +7085,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6706
7085
  ctx.recordFail = savedRecord;
6707
7086
  const ind0 = ind(ctx);
6708
7087
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7088
+ const mayCommit = mayCommitFailure(inner);
6709
7089
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6710
7090
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6711
7091
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6719,6 +7099,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6719
7099
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6720
7100
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6721
7101
  ] : [],
7102
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6722
7103
  `${ind0}${lbl}: {`,
6723
7104
  ...r.stmts,
6724
7105
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6727,7 +7108,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6727
7108
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6728
7109
  ] : []
6729
7110
  ];
6730
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7111
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6731
7112
  }
6732
7113
  var CHARCODE_CHAIN_MAX = 16;
6733
7114
  function emitLit(def, ctx, pos) {
@@ -7104,6 +7485,186 @@ function emitChoice(parser2, def, ctx, pos) {
7104
7485
  }
7105
7486
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7106
7487
  }
7488
+ function emitTailFallible(parser2, ctx, pos) {
7489
+ const savedRecord = ctx.recordFail;
7490
+ ctx.recordFail = true;
7491
+ const out = emitFallible(parser2, ctx, pos);
7492
+ ctx.recordFail = savedRecord;
7493
+ return out;
7494
+ }
7495
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7496
+ if (seen.has(p)) return false;
7497
+ seen.add(p);
7498
+ const d = p._def;
7499
+ switch (d.tag) {
7500
+ case "expect":
7501
+ case "recover":
7502
+ return true;
7503
+ case "many":
7504
+ case "oneOrMore":
7505
+ case "sepBy":
7506
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7507
+ case "choice":
7508
+ case "sequence": {
7509
+ for (const item of d.parsers) {
7510
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7511
+ }
7512
+ return false;
7513
+ }
7514
+ case "dispatch": {
7515
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7516
+ for (const item of d.cases) {
7517
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7518
+ }
7519
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7520
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7521
+ }
7522
+ case "attempt":
7523
+ case "transform":
7524
+ case "label":
7525
+ case "field":
7526
+ case "grammar":
7527
+ case "node":
7528
+ case "token":
7529
+ case "leaf":
7530
+ case "withCtx":
7531
+ case "optional":
7532
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7533
+ case "skip":
7534
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7535
+ case "scanTo":
7536
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7537
+ for (const item of d.skip) {
7538
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7539
+ }
7540
+ return false;
7541
+ case "lazy": {
7542
+ try {
7543
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7544
+ } catch {
7545
+ return true;
7546
+ }
7547
+ }
7548
+ case "unknown":
7549
+ return true;
7550
+ default:
7551
+ return false;
7552
+ }
7553
+ }
7554
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7555
+ if (parserUsesRouted2(def.selector)) {
7556
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7557
+ }
7558
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7559
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7560
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7561
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7562
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7563
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7564
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7565
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7566
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7567
+ const selector = emit(def.selector, ctx, pos);
7568
+ const outV = v(ctx, "_dval");
7569
+ const outE = v(ctx, "_dend");
7570
+ const keyV = v(ctx, "_dkey");
7571
+ const stmts = [
7572
+ ...selectorLeafMark ? [
7573
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7574
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7575
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7576
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7577
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7578
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7579
+ ] : [],
7580
+ ...selector.stmts,
7581
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7582
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7583
+ ];
7584
+ let wroteBranch = false;
7585
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7586
+ const parser3 = entry.parser;
7587
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7588
+ stmts.push(head);
7589
+ ctx.indent++;
7590
+ const usesRouted = branchUsesRouted2(entry);
7591
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7592
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7593
+ const errMark = mayError ? v(ctx, "_derr") : null;
7594
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7595
+ if (routedV) {
7596
+ stmts.push(
7597
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7598
+ ...selectorLeafMark ? [
7599
+ `${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}`
7600
+ ] : []
7601
+ );
7602
+ }
7603
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7604
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7605
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7606
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7607
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7608
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7609
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7610
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7611
+ stmts.push(
7612
+ ...tail.stmts,
7613
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7614
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7615
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7616
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7617
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7618
+ );
7619
+ ctx.indent--;
7620
+ stmts.push(`${ind(ctx)}}`);
7621
+ };
7622
+ let coverageIndex = 0;
7623
+ for (const entry of def.cases) {
7624
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7625
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7626
+ wroteBranch = true;
7627
+ }
7628
+ const emitMatcherTails = () => {
7629
+ for (const matcher of def.matchers ?? []) {
7630
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7631
+ wroteBranch = true;
7632
+ }
7633
+ };
7634
+ if (def.otherwise) {
7635
+ emitMatcherTails();
7636
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7637
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7638
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7639
+ } else {
7640
+ emitMatcherTails();
7641
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7642
+ const fail = failArrBody(ctx, expected, selector.endVar);
7643
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7644
+ }
7645
+ return { stmts, valueVar: outV, endVar: outE };
7646
+ }
7647
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7648
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7649
+ const parts = [`${valueVar}.length === ${key.length}`];
7650
+ for (let i = 0; i < key.length; i++) {
7651
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7652
+ }
7653
+ return parts.join(" && ");
7654
+ }
7655
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7656
+ if (matcher.kind === "matches") {
7657
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7658
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7659
+ }
7660
+ const text = matcher.value;
7661
+ const parts = [`${valueVar}.length >= ${text.length}`];
7662
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7663
+ for (let i = 0; i < text.length; i++) {
7664
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7665
+ }
7666
+ return parts.join(" && ");
7667
+ }
7107
7668
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7108
7669
  const superParser = def.parsers[superIndex];
7109
7670
  const regexDef = getCoreRegexDef(superParser);
@@ -7225,12 +7786,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7225
7786
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7226
7787
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7227
7788
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7789
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7228
7790
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7229
7791
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7230
7792
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7231
7793
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7232
7794
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7233
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7795
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7796
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7797
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7798
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7234
7799
  const failSlot = atStart ? staticFx : "_ctx._fx";
7235
7800
  stmts.push(`${ind0}if (${skipCond}) {`);
7236
7801
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7245,7 +7810,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7245
7810
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7246
7811
  );
7247
7812
  }
7248
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7813
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7814
+ const arm = emitFallible(p, ctx, pos, atStart);
7815
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7249
7816
  stmts.push(...armStmts);
7250
7817
  if (autoNot && autoNot.length > 0) {
7251
7818
  const anCode = v(ctx, "_anc");
@@ -7269,10 +7836,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7269
7836
  stmts.push(`${ind(ctx)}}`);
7270
7837
  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} });`} }`);
7271
7838
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7839
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7272
7840
  } else {
7273
7841
  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} }) }`;
7274
7842
  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} })`} }`);
7275
7843
  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} })`} }`);
7844
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7276
7845
  }
7277
7846
  ctx.indent -= fsGuard ? 2 : 1;
7278
7847
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7429,7 +7998,8 @@ function emitMany(def, ctx, pos) {
7429
7998
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7430
7999
  );
7431
8000
  }
7432
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
8001
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
8002
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7433
8003
  stmts.push(...iterStmts);
7434
8004
  if (ctx.recovery) {
7435
8005
  const rrV = v(ctx, "_rr");
@@ -7448,7 +8018,8 @@ function emitMany(def, ctx, pos) {
7448
8018
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7449
8019
  );
7450
8020
  } else {
7451
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
8021
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
8022
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7452
8023
  }
7453
8024
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7454
8025
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7460,10 +8031,12 @@ function emitMany(def, ctx, pos) {
7460
8031
  function emitOptional(def, ctx, pos) {
7461
8032
  const valV = v(ctx, "_opt");
7462
8033
  const endV = v(ctx, "_opte");
7463
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
8034
+ const inner = emitFallible(def.parser, ctx, pos, true);
8035
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7464
8036
  const ind0 = ind(ctx);
7465
8037
  const stmts = [
7466
8038
  ...lblStmts,
8039
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7467
8040
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7468
8041
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7469
8042
  ];
@@ -7495,7 +8068,7 @@ function emitAttempt(p, def, ctx, pos) {
7495
8068
  ...preGuard,
7496
8069
  `${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`,
7497
8070
  ...inner.stmts,
7498
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
8071
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7499
8072
  ],
7500
8073
  valueVar: inner.valVar,
7501
8074
  endVar: inner.endVar
@@ -7509,18 +8082,21 @@ function emitSepBy(_p, def, ctx, pos) {
7509
8082
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7510
8083
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7511
8084
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7512
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
8085
+ const first = emitFallible(def.parser, ctx, pos, true);
8086
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7513
8087
  const stmts = [
7514
8088
  `${ind(ctx)}const ${arrV} = []`,
7515
8089
  `${ind(ctx)}let ${curV} = ${pos}`,
7516
8090
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7517
8091
  ...firstStmts
7518
8092
  ];
7519
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7520
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
8093
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
8094
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
8095
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7521
8096
  const rr = v(ctx, "_rr");
7522
8097
  return [
7523
8098
  `${ind(ctx)}if (!${nextOkVar}) {`,
8099
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7524
8100
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7525
8101
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7526
8102
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7549,6 +8125,7 @@ function emitSepBy(_p, def, ctx, pos) {
7549
8125
  stmts.push(
7550
8126
  `${ind(ctx)}let ${didV} = false`,
7551
8127
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8128
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7552
8129
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7553
8130
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7554
8131
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7559,6 +8136,7 @@ function emitSepBy(_p, def, ctx, pos) {
7559
8136
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7560
8137
  ctx.indent++;
7561
8138
  } else {
8139
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7562
8140
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7563
8141
  ctx.indent++;
7564
8142
  stmts.push(
@@ -7587,16 +8165,18 @@ function emitSepBy(_p, def, ctx, pos) {
7587
8165
  );
7588
8166
  sepAtPos = spV;
7589
8167
  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}; `;
7590
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7591
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
8168
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8169
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8170
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7592
8171
  const post = postSepMarks();
7593
8172
  stmts.push(...post.decl);
7594
8173
  const npV = v(ctx, "_np");
7595
8174
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7596
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8175
+ const next = emitFallible(def.parser, ctx, npV, true);
8176
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7597
8177
  stmts.push(
7598
8178
  ...nextStmts,
7599
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8179
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7600
8180
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7601
8181
  `${ind(ctx)}${curV} = ${nextEnd}`
7602
8182
  );
@@ -7605,14 +8185,16 @@ function emitSepBy(_p, def, ctx, pos) {
7605
8185
  const spV = v(ctx, "_sp");
7606
8186
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7607
8187
  sepAtPos = spV;
7608
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7609
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8188
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8189
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8190
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7610
8191
  const npV = v(ctx, "_np");
7611
8192
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7612
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8193
+ const next = emitFallible(def.parser, ctx, npV, true);
8194
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7613
8195
  stmts.push(
7614
8196
  ...nextStmts,
7615
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8197
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7616
8198
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7617
8199
  `${ind(ctx)}${curV} = ${nextEnd}`
7618
8200
  );
@@ -7626,15 +8208,17 @@ function emitSepBy(_p, def, ctx, pos) {
7626
8208
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7627
8209
  );
7628
8210
  }
7629
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7630
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8211
+ const sep2 = emitFallible(def.separator, ctx, sepAtPos, true);
8212
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep2;
8213
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep2.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7631
8214
  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}; ` : "";
7632
8215
  const post = postSepMarks();
7633
8216
  stmts.push(...post.decl);
7634
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
8217
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
8218
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7635
8219
  stmts.push(
7636
8220
  ...nextStmts,
7637
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8221
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7638
8222
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7639
8223
  `${ind(ctx)}${curV} = ${nextEnd}`
7640
8224
  );
@@ -7735,6 +8319,18 @@ function emitPeek(def, ctx, pos) {
7735
8319
  endVar: pos
7736
8320
  };
7737
8321
  }
8322
+ function emitRouted(ctx, pos) {
8323
+ const item = v(ctx, "_rt");
8324
+ return {
8325
+ stmts: [
8326
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8327
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8328
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8329
+ ],
8330
+ valueVar: `${item}.value`,
8331
+ endVar: `${item}.span.end`
8332
+ };
8333
+ }
7738
8334
  function escapeTokenLiteral(value) {
7739
8335
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
7740
8336
  }
@@ -8015,7 +8611,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8015
8611
  const rv = v(ctx, "_rt");
8016
8612
  const vv = v(ctx, "_rtv");
8017
8613
  const ev = v(ctx, "_rte");
8018
- 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 }`;
8614
+ 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 } : {}) }`;
8019
8615
  const stmts = [
8020
8616
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8021
8617
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8096,7 +8692,7 @@ function emitRecover(def, ctx, pos) {
8096
8692
  return { stmts, valueVar: valVar, endVar };
8097
8693
  }
8098
8694
  function emitExpect(def, ctx, pos) {
8099
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8695
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8100
8696
  const ind0 = ind(ctx);
8101
8697
  const errV = v(ctx, "_err");
8102
8698
  const stmts = [
@@ -8108,6 +8704,7 @@ function emitExpect(def, ctx, pos) {
8108
8704
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8109
8705
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8110
8706
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8707
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8111
8708
  `${ind0} ${valVar} = ${errV}`,
8112
8709
  `${ind0} ${endVar} = ${pos}`,
8113
8710
  `${ind0} ${okVar} = true`,
@@ -8118,7 +8715,7 @@ function emitExpect(def, ctx, pos) {
8118
8715
  function emit(p, ctx, pos) {
8119
8716
  const replay = ctx.replayPrefix?.get(p);
8120
8717
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8121
- const dispatch = (emissionPos = pos) => {
8718
+ const dispatch2 = (emissionPos = pos) => {
8122
8719
  const savedRule = ctx.activeCoverageRuleId;
8123
8720
  const rule = ctx.coverage?.plan.rules.get(p);
8124
8721
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8158,14 +8755,14 @@ function emit(p, ctx, pos) {
8158
8755
  ctx.indent = 1;
8159
8756
  ctx.failLabel = "_pfail";
8160
8757
  ctx.recordFail = true;
8161
- const r = dispatch("_pos");
8758
+ const r = dispatch2("_pos");
8162
8759
  ctx.indent = savedIndent;
8163
8760
  ctx.failLabel = savedFailLabel;
8164
8761
  ctx.recordFail = savedRecord;
8165
8762
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8166
8763
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8167
8764
  }
8168
- return instrument(dispatch());
8765
+ return instrument(dispatch2());
8169
8766
  }
8170
8767
  function emitDispatch(p, ctx, pos) {
8171
8768
  const bal = p._balancedAmbient;
@@ -8198,6 +8795,8 @@ function emitDispatch(p, ctx, pos) {
8198
8795
  return emitSeq(def, ctx, pos);
8199
8796
  case "choice":
8200
8797
  return emitChoice(p, def, ctx, pos);
8798
+ case "dispatch":
8799
+ return emitDispatchCombinator(p, def, ctx, pos);
8201
8800
  case "attempt":
8202
8801
  return emitAttempt(p, def, ctx, pos);
8203
8802
  case "many":
@@ -8322,6 +8921,8 @@ function emitDispatch(p, ctx, pos) {
8322
8921
  return emitNot(def, ctx, pos);
8323
8922
  case "peek":
8324
8923
  return emitPeek(def, ctx, pos);
8924
+ case "routed":
8925
+ return emitRouted(ctx, pos);
8325
8926
  case "node":
8326
8927
  return emitNode(def, ctx, pos);
8327
8928
  case "scanTo":
@@ -8418,6 +9019,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8418
9019
  case "sequence":
8419
9020
  case "choice":
8420
9021
  return d.parsers.some((x) => hasNodeDef(x, seen));
9022
+ case "dispatch":
9023
+ 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);
8421
9024
  case "sepBy":
8422
9025
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8423
9026
  case "scanTo":
@@ -8432,6 +9035,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8432
9035
  return false;
8433
9036
  }
8434
9037
  }
9038
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9039
+ if (seen.has(p)) return false;
9040
+ seen.add(p);
9041
+ const d = p._def;
9042
+ switch (d.tag) {
9043
+ case "routed":
9044
+ return true;
9045
+ case "lazy": {
9046
+ try {
9047
+ return parserUsesRouted2(d.thunk(), seen);
9048
+ } catch {
9049
+ return false;
9050
+ }
9051
+ }
9052
+ case "dispatch":
9053
+ return false;
9054
+ default:
9055
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9056
+ }
9057
+ }
8435
9058
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8436
9059
  if (seen.has(p)) return false;
8437
9060
  seen.add(p);
@@ -8451,6 +9074,8 @@ function childrenOf2(def) {
8451
9074
  case "sequence":
8452
9075
  case "choice":
8453
9076
  return def.parsers;
9077
+ case "dispatch":
9078
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8454
9079
  case "many":
8455
9080
  case "attempt":
8456
9081
  case "optional":
@@ -8487,6 +9112,7 @@ function childrenOf2(def) {
8487
9112
  case "regex":
8488
9113
  case "keywords":
8489
9114
  case "guard":
9115
+ case "routed":
8490
9116
  case "unknown":
8491
9117
  return [];
8492
9118
  }
@@ -8494,6 +9120,7 @@ function childrenOf2(def) {
8494
9120
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8495
9121
  "sequence",
8496
9122
  "choice",
9123
+ "dispatch",
8497
9124
  "many",
8498
9125
  "oneOrMore",
8499
9126
  "optional",
@@ -9318,6 +9945,8 @@ function childrenOf3(def) {
9318
9945
  case "sequence":
9319
9946
  case "choice":
9320
9947
  return def.parsers;
9948
+ case "dispatch":
9949
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9321
9950
  case "many":
9322
9951
  case "oneOrMore":
9323
9952
  case "optional":
@@ -9346,6 +9975,7 @@ function childrenOf3(def) {
9346
9975
  case "literal":
9347
9976
  case "regex":
9348
9977
  case "keywords":
9978
+ case "routed":
9349
9979
  case "guard":
9350
9980
  case "recover":
9351
9981
  case "unknown":
@@ -9370,6 +10000,16 @@ function repeatOpts(min, max, trailing) {
9370
10000
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9371
10001
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9372
10002
  }
10003
+ function matcherExpr(entry) {
10004
+ switch (entry.kind) {
10005
+ case "startsWith":
10006
+ return `startsWith(${JSON.stringify(entry.value)})`;
10007
+ case "endsWith":
10008
+ return `endsWith(${JSON.stringify(entry.value)})`;
10009
+ case "matches":
10010
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
10011
+ }
10012
+ }
9373
10013
  function serializeRuleMap(ruleMap, scanSkip) {
9374
10014
  try {
9375
10015
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9467,6 +10107,13 @@ function evalRuleMapIR(ir) {
9467
10107
  "keywords",
9468
10108
  "sequence",
9469
10109
  "choice",
10110
+ "dispatch",
10111
+ "when",
10112
+ "startsWith",
10113
+ "endsWith",
10114
+ "matches",
10115
+ "otherwise",
10116
+ "routed",
9470
10117
  "attempt",
9471
10118
  "many",
9472
10119
  "oneOrMore",
@@ -9500,6 +10147,13 @@ function evalRuleMapIR(ir) {
9500
10147
  keywords,
9501
10148
  sequence,
9502
10149
  choice,
10150
+ dispatch,
10151
+ when,
10152
+ startsWith,
10153
+ endsWith,
10154
+ matches,
10155
+ otherwise,
10156
+ routed,
9503
10157
  attempt,
9504
10158
  many,
9505
10159
  oneOrMore,
@@ -9685,6 +10339,18 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9685
10339
  });
9686
10340
  return `_gch([${items.join(", ")}])`;
9687
10341
  }
10342
+ case "dispatch": {
10343
+ const arms = def.cases.map(
10344
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10345
+ );
10346
+ if (def.matchers) {
10347
+ for (const entry of def.matchers) {
10348
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10349
+ }
10350
+ }
10351
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10352
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10353
+ }
9688
10354
  case "many":
9689
10355
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9690
10356
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -9701,6 +10367,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9701
10367
  return `not(${kid(def.parser)})`;
9702
10368
  case "peek":
9703
10369
  return `peek(${kid(def.parser)})`;
10370
+ case "routed":
10371
+ return "routed()";
9704
10372
  case "trivia":
9705
10373
  return `trivia(${kid(def.parser)})`;
9706
10374
  case "token":
@@ -10170,6 +10838,12 @@ function stripTsFromSource(node2, code) {
10170
10838
  function isWordFactory(v2) {
10171
10839
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10172
10840
  }
10841
+ function isWhenFactory(v2) {
10842
+ return !!v2 && typeof v2 === "object" && v2.tag === "whenFactory";
10843
+ }
10844
+ function isStaticValueEntry(v2) {
10845
+ return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10846
+ }
10173
10847
  function wordFactoryFromArgs(args, scope, code, mfs) {
10174
10848
  const [boundaryOrOptsArg, optsArg] = args;
10175
10849
  if (boundaryOrOptsArg?.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
@@ -10189,6 +10863,23 @@ function wordFactoryFromArgs(args, scope, code, mfs) {
10189
10863
  if (typeof caseInsensitive !== "boolean") return null;
10190
10864
  return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10191
10865
  }
10866
+ function dispatchWhenOptions(v2) {
10867
+ if (v2 === void 0) return {};
10868
+ if (typeof v2 !== "object" || v2 === null || Array.isArray(v2)) return null;
10869
+ for (const key of Object.keys(v2)) {
10870
+ if (key !== "caseInsensitive") return null;
10871
+ }
10872
+ const caseInsensitive = "caseInsensitive" in v2 ? v2.caseInsensitive : false;
10873
+ if (typeof caseInsensitive !== "boolean") return null;
10874
+ return { caseInsensitive };
10875
+ }
10876
+ function whenFactoryFromArgs(args, scope, code, mfs) {
10877
+ const [optsArg, extra] = args;
10878
+ if (optsArg?.type === "SpreadElement" || extra !== void 0) return null;
10879
+ const rawOpts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10880
+ const opts = dispatchWhenOptions(rawOpts);
10881
+ return opts === null ? null : { tag: "whenFactory", caseInsensitive: opts.caseInsensitive ?? false };
10882
+ }
10192
10883
  var SUPPORTED = {
10193
10884
  literal: (...a) => literal(a[0], a[1]),
10194
10885
  regex: (...a) => regex(a[0], a[1]),
@@ -10204,6 +10895,7 @@ var SUPPORTED = {
10204
10895
  field: (...a) => field(a[0], a[1]),
10205
10896
  noTrivia: (...a) => noTrivia(a[0]),
10206
10897
  token: (...a) => token(a[0]),
10898
+ routed: () => routed(),
10207
10899
  leaf: (...a) => leaf(a[0], a[1]),
10208
10900
  expect: (...a) => expect(a[0], a[1])
10209
10901
  };
@@ -10239,9 +10931,65 @@ function gatedArmParts(e) {
10239
10931
  }
10240
10932
  return gate2 && combinator ? { gate: gate2, combinator } : null;
10241
10933
  }
10934
+ function dispatchArmValue(node2, scope, code, mfs) {
10935
+ if (node2.type === "Identifier") {
10936
+ const value = anyValue(node2, scope, code, mfs);
10937
+ return isDispatchArm(value) ? value : null;
10938
+ }
10939
+ if (node2.type !== "CallExpression") return null;
10940
+ const callee = node2.callee;
10941
+ if (callee.type !== "Identifier") return null;
10942
+ const factory = scope.get(callee.name);
10943
+ if (isWhenFactory(factory)) {
10944
+ const [keyArg, parserArg, extraArg] = node2.arguments;
10945
+ if (!keyArg || !parserArg || extraArg !== void 0 || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10946
+ const key = anyValue(keyArg, scope, code, mfs);
10947
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10948
+ if (!isCombinator(parserValue)) return null;
10949
+ const opts = { caseInsensitive: factory.caseInsensitive };
10950
+ if (typeof key === "string") return when(key, parserValue, opts);
10951
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
10952
+ return null;
10953
+ }
10954
+ if (callee.name === "otherwise") {
10955
+ const [parserArg] = node2.arguments;
10956
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
10957
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10958
+ if (!isCombinator(parserValue)) return null;
10959
+ return otherwise(parserValue);
10960
+ }
10961
+ if (callee.name === "when") {
10962
+ const [keyArg, parserArg, optsArg] = node2.arguments;
10963
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10964
+ const key = anyValue(keyArg, scope, code, mfs);
10965
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10966
+ const opts = dispatchWhenOptions(optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs));
10967
+ if (opts === null) return null;
10968
+ if (!isCombinator(parserValue)) return null;
10969
+ if (typeof key === "string") return when(key, parserValue, opts);
10970
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
10971
+ if (isDispatchMatcher(key)) return when(key, parserValue, opts);
10972
+ }
10973
+ return null;
10974
+ }
10242
10975
  function isCombinator(v2) {
10243
10976
  return !!v2 && typeof v2 === "object" && "_def" in v2;
10244
10977
  }
10978
+ function isDispatchArm(v2) {
10979
+ if (!v2 || typeof v2 !== "object") return false;
10980
+ const rec = v2;
10981
+ if (rec.kind === "otherwise") return isCombinator(rec.parser);
10982
+ if (rec.kind === "whenMatcher") {
10983
+ return isDispatchMatcher(rec.matcher) && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
10984
+ }
10985
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
10986
+ }
10987
+ function isDispatchMatcher(v2) {
10988
+ if (!v2 || typeof v2 !== "object") return false;
10989
+ const rec = v2;
10990
+ if ((rec.kind === "startsWith" || rec.kind === "endsWith") && typeof rec.value === "string") return true;
10991
+ return rec.kind === "matches" && typeof rec.value === "string" && typeof rec.flags === "string";
10992
+ }
10245
10993
  function scopeGet(scope, name, mfs) {
10246
10994
  const entry = scope.get(name);
10247
10995
  if (!entry) return null;
@@ -10498,6 +11246,25 @@ function exprToCombi(node2, scope, code, mfs) {
10498
11246
  return null;
10499
11247
  }
10500
11248
  }
11249
+ if (callee.name === "dispatch") {
11250
+ const [selectorArg, ...armArgs] = node2.arguments;
11251
+ if (!selectorArg || selectorArg.type === "SpreadElement") return null;
11252
+ const selector = anyValue(selectorArg, scope, code, mfs);
11253
+ if (!isCombinator(selector)) return null;
11254
+ const arms = [];
11255
+ for (const arg of armArgs) {
11256
+ if (arg.type === "SpreadElement") return null;
11257
+ const arm = dispatchArmValue(arg, scope, code, mfs);
11258
+ if (!isDispatchArm(arm)) return null;
11259
+ arms.push(arm);
11260
+ }
11261
+ try {
11262
+ const unsafeDispatch = dispatch;
11263
+ return unsafeDispatch(selector, ...arms);
11264
+ } catch {
11265
+ return null;
11266
+ }
11267
+ }
10501
11268
  if (callee.name === "choice" && node2.arguments.some((a) => a.type !== "SpreadElement" && isGatedArmExpr(a))) {
10502
11269
  if (code === void 0 || mfs === void 0) return null;
10503
11270
  const arms = [];
@@ -10583,6 +11350,10 @@ function anyValue(node2, scope, code, mfs) {
10583
11350
  if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
10584
11351
  return entry.combi;
10585
11352
  }
11353
+ if (isStaticValueEntry(entry)) {
11354
+ if (mfs && entry.mfSrcs.length > 0) mfs.push(...entry.mfSrcs);
11355
+ return entry.value;
11356
+ }
10586
11357
  return entry;
10587
11358
  }
10588
11359
  if (node2.type === "MemberExpression") {
@@ -10603,6 +11374,27 @@ function anyValue(node2, scope, code, mfs) {
10603
11374
  if (callee.type === "Identifier" && callee.name === "makeWord") {
10604
11375
  return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
10605
11376
  }
11377
+ if (callee.type === "Identifier" && callee.name === "makeWhen") {
11378
+ return whenFactoryFromArgs(node2.arguments, scope, code, mfs);
11379
+ }
11380
+ if (callee.type === "Identifier" && (callee.name === "startsWith" || callee.name === "endsWith")) {
11381
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11382
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11383
+ if (typeof value !== "string") return null;
11384
+ return callee.name === "startsWith" ? startsWith(value) : endsWith(value);
11385
+ }
11386
+ if (callee.type === "Identifier" && callee.name === "matches") {
11387
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11388
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11389
+ if (!(value instanceof RegExp)) return null;
11390
+ return matches(value);
11391
+ }
11392
+ if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11393
+ return dispatchArmValue(node2, scope, code, mfs);
11394
+ }
11395
+ if (callee.type === "Identifier" && isWhenFactory(scope.get(callee.name))) {
11396
+ return dispatchArmValue(node2, scope, code, mfs);
11397
+ }
10606
11398
  return exprToCombi(node2, scope, code, mfs);
10607
11399
  }
10608
11400
  return null;
@@ -10613,6 +11405,12 @@ function evaluateWordFactory(node2, scope, code) {
10613
11405
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
10614
11406
  return wordFactoryFromArgs(node2.arguments, scope, code);
10615
11407
  }
11408
+ function evaluateWhenFactory(node2, scope, code) {
11409
+ if (node2.type !== "CallExpression") return null;
11410
+ const callee = node2.callee;
11411
+ if (callee.type !== "Identifier" || callee.name !== "makeWhen") return null;
11412
+ return whenFactoryFromArgs(node2.arguments, scope, code);
11413
+ }
10616
11414
  function evaluateExpr(node2, scope, code, mapFnSources) {
10617
11415
  return exprToCombi(node2, scope, code, mapFnSources);
10618
11416
  }
@@ -10661,6 +11459,7 @@ function evalBodyStatements(statements, scope, code) {
10661
11459
  if (val === null) return false;
10662
11460
  const thisDeclMfSrcs = bodyMfs.slice(before);
10663
11461
  if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
11462
+ else if (isDispatchArm(val)) scope.set(name, { value: val, mfSrcs: thisDeclMfSrcs });
10664
11463
  else scope.set(name, val);
10665
11464
  }
10666
11465
  }
@@ -11192,7 +11991,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11192
11991
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
11193
11992
  return [...ids].sort().map((id2) => ({
11194
11993
  id: id2,
11195
- kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
11994
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
11196
11995
  }));
11197
11996
  };
11198
11997
  const localComposedCarried = /* @__PURE__ */ new Map();
@@ -11638,6 +12437,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11638
12437
  if (wordFactory) {
11639
12438
  ;
11640
12439
  scope.set(varName, wordFactory);
12440
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12441
+ continue;
12442
+ }
12443
+ const whenFactory = evaluateWhenFactory(init, scope, code);
12444
+ if (whenFactory) {
12445
+ ;
12446
+ scope.set(varName, whenFactory);
12447
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
11641
12448
  continue;
11642
12449
  }
11643
12450
  const combiArray = evaluateCombinatorArray(init, scope, code);