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
package/dist/index.js CHANGED
@@ -51,6 +51,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
51
51
  return d.value.length === 0;
52
52
  case "keywords":
53
53
  return false;
54
+ case "routed":
55
+ return false;
54
56
  case "regex":
55
57
  try {
56
58
  const m = new RegExp(d.source).exec("");
@@ -77,6 +79,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
77
79
  return d.parsers.every(me);
78
80
  case "choice":
79
81
  return d.parsers.some(me);
82
+ case "dispatch":
83
+ return me(d.selector);
80
84
  case "transform":
81
85
  case "label":
82
86
  case "trivia":
@@ -150,6 +154,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
150
154
  case "keywords":
151
155
  return p._meta.firstSet;
152
156
  // terminals: no refs, cached set is exact
157
+ case "dispatch":
158
+ return fs(d.selector);
153
159
  case "lazy":
154
160
  try {
155
161
  return fs(d.thunk());
@@ -1067,6 +1073,8 @@ function derive(c, seen) {
1067
1073
  return [def.label];
1068
1074
  case "choice":
1069
1075
  return def.parsers.flatMap(deriveExpected2);
1076
+ case "dispatch":
1077
+ return deriveExpected2(def.selector);
1070
1078
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1071
1079
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1072
1080
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1106,6 +1114,8 @@ function derive(c, seen) {
1106
1114
  case "not":
1107
1115
  case "peek":
1108
1116
  return deriveExpected2(def.parser);
1117
+ case "routed":
1118
+ return ["routed()"];
1109
1119
  case "lazy": {
1110
1120
  const name = c._ruleName;
1111
1121
  if (seen.has(c)) return name ? [name] : [];
@@ -1208,6 +1218,7 @@ function choice(...args) {
1208
1218
  const result = parsers[idx].parse(input, pos, ctx);
1209
1219
  if (result.ok) return result;
1210
1220
  expected.push(...result.expected);
1221
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1211
1222
  return { ok: false, expected, span: { start: pos, end: pos } };
1212
1223
  }
1213
1224
  return {
@@ -1248,6 +1259,7 @@ function choice(...args) {
1248
1259
  rollbackCstCapture(ctx, mark);
1249
1260
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1250
1261
  expected.push(...result.expected);
1262
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1251
1263
  continue;
1252
1264
  }
1253
1265
  const checks = autoNot[i];
@@ -1927,6 +1939,248 @@ function sequence(...parsers) {
1927
1939
  };
1928
1940
  }
1929
1941
 
1942
+ // src/combinators/dispatch.ts
1943
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
1944
+ if (seen.has(parser2)) return false;
1945
+ seen.add(parser2);
1946
+ const def = parser2._def;
1947
+ switch (def.tag) {
1948
+ case "routed":
1949
+ return true;
1950
+ case "sequence":
1951
+ case "choice":
1952
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
1953
+ case "dispatch":
1954
+ return false;
1955
+ case "sepBy":
1956
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
1957
+ case "skip":
1958
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
1959
+ case "grammar":
1960
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
1961
+ case "scanTo":
1962
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
1963
+ case "recover":
1964
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
1965
+ case "lazy":
1966
+ try {
1967
+ return parserUsesRouted(def.thunk(), seen);
1968
+ } catch {
1969
+ return false;
1970
+ }
1971
+ case "many":
1972
+ case "oneOrMore":
1973
+ case "optional":
1974
+ case "attempt":
1975
+ case "transform":
1976
+ case "trivia":
1977
+ case "token":
1978
+ case "leaf":
1979
+ case "label":
1980
+ case "field":
1981
+ case "not":
1982
+ case "peek":
1983
+ case "node":
1984
+ case "withCtx":
1985
+ case "expect":
1986
+ return parserUsesRouted(def.parser, seen);
1987
+ default:
1988
+ return false;
1989
+ }
1990
+ }
1991
+ function branchUsesRouted(branch) {
1992
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
1993
+ }
1994
+ function asciiFoldKey(key) {
1995
+ let out = "";
1996
+ for (let i = 0; i < key.length; i++) {
1997
+ const c = key.charCodeAt(i);
1998
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
1999
+ }
2000
+ return out;
2001
+ }
2002
+ function matchesDispatchMatcher(value, matcher) {
2003
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2004
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2005
+ switch (matcher.kind) {
2006
+ case "startsWith":
2007
+ return candidate.startsWith(expected);
2008
+ case "endsWith":
2009
+ return candidate.endsWith(expected);
2010
+ case "matches": {
2011
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2012
+ return new RegExp(matcher.value, flags).test(value);
2013
+ }
2014
+ }
2015
+ }
2016
+ function isStringMatcher(value) {
2017
+ if (typeof value !== "object" || value === null) return false;
2018
+ const kind = value.kind;
2019
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2020
+ }
2021
+ function fixedMatcher(kind, value) {
2022
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2023
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2024
+ return { kind, value };
2025
+ }
2026
+ function startsWith(prefix) {
2027
+ return fixedMatcher("startsWith", prefix);
2028
+ }
2029
+ function endsWith(suffix) {
2030
+ return fixedMatcher("endsWith", suffix);
2031
+ }
2032
+ function matches(pattern) {
2033
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2034
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2035
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2036
+ }
2037
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2038
+ }
2039
+ function when(key, parser2, opts = {}) {
2040
+ const usesRouted = parserUsesRouted(parser2);
2041
+ if (isStringMatcher(key)) {
2042
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2043
+ }
2044
+ const keys = Array.isArray(key) ? [...key] : [key];
2045
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2046
+ for (const item of keys) {
2047
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2048
+ }
2049
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2050
+ }
2051
+ function makeWhen(opts = {}) {
2052
+ return (key, parser2) => when(key, parser2, opts);
2053
+ }
2054
+ function otherwise(parser2) {
2055
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2056
+ }
2057
+ function routed() {
2058
+ return {
2059
+ _tag: "routed",
2060
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2061
+ _def: { tag: "routed" },
2062
+ parse(_input, pos, ctx) {
2063
+ const item = ctx._routed;
2064
+ if (item === void 0) {
2065
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2066
+ }
2067
+ if (pos !== item.span.start) {
2068
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2069
+ }
2070
+ if (cstCaptureActive(ctx)) {
2071
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2072
+ }
2073
+ return { ok: true, value: item.value, span: item.span };
2074
+ }
2075
+ };
2076
+ }
2077
+ function dispatch(selector, ...arms) {
2078
+ if (parserUsesRouted(selector)) {
2079
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2080
+ }
2081
+ let fallback;
2082
+ let fallbackUsesRouted = false;
2083
+ const matchers = [];
2084
+ const cases = [];
2085
+ const seen = [];
2086
+ for (let i = 0; i < arms.length; i++) {
2087
+ const arm = arms[i];
2088
+ if (arm.kind === "otherwise") {
2089
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2090
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2091
+ fallback = arm.parser;
2092
+ fallbackUsesRouted = arm.usesRouted === true;
2093
+ continue;
2094
+ }
2095
+ if (arm.kind === "whenMatcher") {
2096
+ const matcher = arm.matcher;
2097
+ matchers.push({
2098
+ kind: matcher.kind,
2099
+ value: matcher.value,
2100
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2101
+ parser: arm.parser,
2102
+ caseInsensitive: arm.caseInsensitive,
2103
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2104
+ });
2105
+ continue;
2106
+ }
2107
+ for (const key of arm.keys) {
2108
+ const folded = asciiFoldKey(key);
2109
+ for (const prior of seen) {
2110
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2111
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2112
+ }
2113
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2114
+ }
2115
+ cases.push({
2116
+ keys: arm.keys,
2117
+ parser: arm.parser,
2118
+ caseInsensitive: arm.caseInsensitive,
2119
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2120
+ });
2121
+ }
2122
+ const meta = {
2123
+ firstSet: selector._meta.firstSet,
2124
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2125
+ isTrivia: false
2126
+ };
2127
+ const byKey = /* @__PURE__ */ new Map();
2128
+ const byFoldedKey = /* @__PURE__ */ new Map();
2129
+ for (const entry of cases) {
2130
+ for (const key of entry.keys) {
2131
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2132
+ else byKey.set(key, entry);
2133
+ }
2134
+ }
2135
+ return {
2136
+ _tag: "dispatch",
2137
+ _meta: meta,
2138
+ _def: {
2139
+ tag: "dispatch",
2140
+ selector,
2141
+ cases,
2142
+ ...matchers.length === 0 ? {} : { matchers },
2143
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2144
+ },
2145
+ parse(input, pos, ctx) {
2146
+ if (parserUsesRouted(selector)) {
2147
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2148
+ }
2149
+ const selectorMark = saveTriviaMark(ctx);
2150
+ const selected = selector.parse(input, pos, ctx);
2151
+ if (!selected.ok) return selected;
2152
+ 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 });
2153
+ if (branch === void 0) {
2154
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2155
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2156
+ }
2157
+ const savedRouted = ctx._routed;
2158
+ const usesRouted = branchUsesRouted(branch);
2159
+ let mark = saveTriviaMark(ctx);
2160
+ if (usesRouted) {
2161
+ rollbackTrivia(ctx, selectorMark);
2162
+ mark = saveTriviaMark(ctx);
2163
+ ctx._routed = { value: selected.value, span: selected.span };
2164
+ }
2165
+ let result;
2166
+ try {
2167
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2168
+ } finally {
2169
+ if (usesRouted) ctx._routed = savedRouted;
2170
+ }
2171
+ if (!result.ok) {
2172
+ rollbackTrivia(ctx, mark);
2173
+ return { ...result, committed: true };
2174
+ }
2175
+ return {
2176
+ ok: true,
2177
+ value: [selected.value, result.value],
2178
+ span: { start: pos, end: result.span.end }
2179
+ };
2180
+ }
2181
+ };
2182
+ }
2183
+
1930
2184
  // src/combinators/attempt.ts
1931
2185
  function attempt(parser2) {
1932
2186
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -1949,6 +2203,7 @@ function attempt(parser2) {
1949
2203
  if (result.ok) return result;
1950
2204
  rollbackCstCapture(ctx, mark);
1951
2205
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2206
+ if (result.committed) return result;
1952
2207
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1953
2208
  }
1954
2209
  };
@@ -1978,6 +2233,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
1978
2233
  const result = combinator.parse(input, pos, ctx);
1979
2234
  if (!result.ok) {
1980
2235
  rollbackTrivia(ctx, mark);
2236
+ if (result.committed) return { fail: result, failPos: pos };
1981
2237
  return { fail: result, failPos: pos };
1982
2238
  }
1983
2239
  if (result.span.end === pos) {
@@ -2021,6 +2277,7 @@ function many(combinator, opts = {}) {
2021
2277
  const item = repItem(combinator, input, cur, ctx, guardable);
2022
2278
  if (item === "stop") break;
2023
2279
  if ("fail" in item) {
2280
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2024
2281
  const sync = ctx._tolerant ? ctx._sync : void 0;
2025
2282
  if (sync === void 0) break;
2026
2283
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2065,6 +2322,7 @@ function atLeast(combinator, min, max) {
2065
2322
  while (count < min) {
2066
2323
  const item = repItem(combinator, input, cur, ctx, guardable);
2067
2324
  if (item === "stop" || "fail" in item) {
2325
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2068
2326
  expected ??= deriveExpected(combinator);
2069
2327
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2070
2328
  }
@@ -2077,6 +2335,7 @@ function atLeast(combinator, min, max) {
2077
2335
  const item = repItem(combinator, input, cur, ctx, guardable);
2078
2336
  if (item === "stop") break;
2079
2337
  if ("fail" in item) {
2338
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2080
2339
  const sync = ctx._tolerant ? ctx._sync : void 0;
2081
2340
  if (sync === void 0) break;
2082
2341
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2115,6 +2374,7 @@ function optional(combinator) {
2115
2374
  const result = combinator.parse(input, pos, ctx);
2116
2375
  if (result.ok) return result;
2117
2376
  rollbackTrivia(ctx, mark);
2377
+ if (result.committed) return result;
2118
2378
  return { ok: true, value: null, span: { start: pos, end: pos } };
2119
2379
  }
2120
2380
  };
@@ -2154,6 +2414,7 @@ function sepBy(combinator, separator, opts = {}) {
2154
2414
  values.push(first.value);
2155
2415
  cur = first.span.end;
2156
2416
  } else {
2417
+ if (first.committed) return first;
2157
2418
  const term = ctx._tolerant ? ctx._sync : void 0;
2158
2419
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2159
2420
  if (min >= 1) return failAt2(pos);
@@ -2181,6 +2442,7 @@ function sepBy(combinator, separator, opts = {}) {
2181
2442
  const sep = separator.parse(input, sepPos, ctx);
2182
2443
  if (!sep.ok) {
2183
2444
  rollbackTrivia(ctx, loopMark);
2445
+ if (sep.committed) return sep;
2184
2446
  break;
2185
2447
  }
2186
2448
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2196,6 +2458,7 @@ function sepBy(combinator, separator, opts = {}) {
2196
2458
  }
2197
2459
  const next = combinator.parse(input, nextPos, ctx);
2198
2460
  if (!next.ok) {
2461
+ if (next.committed) return next;
2199
2462
  const term = ctx._tolerant ? ctx._sync : void 0;
2200
2463
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2201
2464
  expected ??= deriveExpected(combinator);
@@ -2288,6 +2551,12 @@ function markUnusedValues(root) {
2288
2551
  case "choice":
2289
2552
  for (const p of def.parsers) visit(p, consumed);
2290
2553
  return;
2554
+ case "dispatch":
2555
+ visit(def.selector, consumed);
2556
+ for (const entry of def.cases) visit(entry.parser, consumed);
2557
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2558
+ if (def.otherwise) visit(def.otherwise, consumed);
2559
+ return;
2291
2560
  case "node":
2292
2561
  visit(def.parser, false);
2293
2562
  return;
@@ -2563,6 +2832,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2563
2832
  case "sequence":
2564
2833
  case "choice":
2565
2834
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2835
+ case "dispatch":
2836
+ 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);
2566
2837
  case "sepBy":
2567
2838
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2568
2839
  case "skip":
@@ -2630,6 +2901,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2630
2901
  return parserHasTriviaSite(d.parser, seen);
2631
2902
  case "choice":
2632
2903
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2904
+ case "dispatch":
2905
+ 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);
2633
2906
  case "grammar":
2634
2907
  return parserHasTriviaSite(d.parser, seen);
2635
2908
  case "lazy": {
@@ -2655,6 +2928,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2655
2928
  case "sequence":
2656
2929
  case "choice":
2657
2930
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2931
+ case "dispatch":
2932
+ 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);
2658
2933
  case "sepBy":
2659
2934
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2660
2935
  case "skip":
@@ -3056,7 +3331,7 @@ function leaf(root, fn) {
3056
3331
  }
3057
3332
 
3058
3333
  // src/version.ts
3059
- var PARSEMAN_VERSION = "0.38.0";
3334
+ var PARSEMAN_VERSION = "0.39.1";
3060
3335
 
3061
3336
  // src/cst/host-mode.ts
3062
3337
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3212,6 +3487,8 @@ function classifyBroadArm(arm, resolve) {
3212
3487
  }
3213
3488
  return null;
3214
3489
  }
3490
+ case "dispatch":
3491
+ return walk2(d.selector);
3215
3492
  default:
3216
3493
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3217
3494
  }
@@ -3311,6 +3588,12 @@ function analyzeGatingRules(ruleMap, opts) {
3311
3588
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3312
3589
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3313
3590
  if (rec[k]) kids.push(rec[k]);
3591
+ if (d.tag === "dispatch") {
3592
+ kids.push(d.selector);
3593
+ for (const c of d.cases) kids.push(c.parser);
3594
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3595
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3596
+ }
3314
3597
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3315
3598
  if (d.tag === "lazy") {
3316
3599
  try {
@@ -3417,6 +3700,12 @@ function childrenOf(d) {
3417
3700
  case "sequence":
3418
3701
  case "choice":
3419
3702
  return d.parsers;
3703
+ case "dispatch":
3704
+ return [
3705
+ d.selector,
3706
+ ...d.cases.map((c) => c.parser),
3707
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3708
+ ];
3420
3709
  case "skip":
3421
3710
  return [d.main, d.skipped];
3422
3711
  case "sepBy":
@@ -3473,6 +3762,8 @@ function payloadKey(p, d) {
3473
3762
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3474
3763
  case "sepBy":
3475
3764
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3765
+ case "dispatch":
3766
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3476
3767
  case "expect":
3477
3768
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3478
3769
  case "scanTo":
@@ -3544,6 +3835,11 @@ function render(p, depth = 3) {
3544
3835
  return `sequence(${kids.join(", ")})`;
3545
3836
  case "choice":
3546
3837
  return `choice(${kids.join(", ")})`;
3838
+ case "dispatch": {
3839
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3840
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3841
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3842
+ }
3547
3843
  case "node":
3548
3844
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3549
3845
  case "label":
@@ -3567,6 +3863,10 @@ function segment(d, index) {
3567
3863
  return `seq[${index}]`;
3568
3864
  case "choice":
3569
3865
  return `choice[${index}]`;
3866
+ case "dispatch":
3867
+ if (index === 0) return "dispatch.selector";
3868
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3869
+ return "dispatch.otherwise";
3570
3870
  case "node":
3571
3871
  return `node(${d.type ?? ""})`;
3572
3872
  case "label":
@@ -4815,11 +5115,25 @@ function escapeClassChar(ch2) {
4815
5115
  }
4816
5116
 
4817
5117
  // src/compiler/grammar-coverage-ids.ts
5118
+ function idPart(value) {
5119
+ return encodeURIComponent(value);
5120
+ }
5121
+ function dispatchArmIds(def, path) {
5122
+ const ids = [];
5123
+ for (const entry of def.cases) ids.push(`dispatch:${path}/when:${entry.keys.map(idPart).join("|")}`);
5124
+ for (const entry of def.matchers ?? []) {
5125
+ ids.push(`dispatch:${path}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5126
+ }
5127
+ if (def.otherwise) ids.push(`dispatch:${path}/otherwise`);
5128
+ return ids;
5129
+ }
4818
5130
  function children(def, winners) {
4819
5131
  switch (def.tag) {
4820
5132
  case "sequence":
4821
5133
  case "choice":
4822
5134
  return def.parsers;
5135
+ case "dispatch":
5136
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4823
5137
  case "many":
4824
5138
  case "oneOrMore":
4825
5139
  case "optional":
@@ -4862,6 +5176,7 @@ function children(def, winners) {
4862
5176
  function buildGrammarPlan(entry, winners) {
4863
5177
  const definitions = /* @__PURE__ */ new Map();
4864
5178
  const choices = /* @__PURE__ */ new WeakMap();
5179
+ const dispatches = /* @__PURE__ */ new WeakMap();
4865
5180
  const attempts = /* @__PURE__ */ new WeakMap();
4866
5181
  const labels = /* @__PURE__ */ new WeakMap();
4867
5182
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -4892,6 +5207,11 @@ function buildGrammarPlan(entry, winners) {
4892
5207
  choices.set(parser2, ids);
4893
5208
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
4894
5209
  }
5210
+ if (parser2._def.tag === "dispatch") {
5211
+ const ids = dispatchArmIds(parser2._def, path);
5212
+ dispatches.set(parser2, ids);
5213
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5214
+ }
4895
5215
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path}`);
4896
5216
  if (parser2._def.tag === "label") {
4897
5217
  const id = `label:${path}`;
@@ -4904,7 +5224,7 @@ function buildGrammarPlan(entry, winners) {
4904
5224
  for (const root of roots) {
4905
5225
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
4906
5226
  }
4907
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5227
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
4908
5228
  }
4909
5229
 
4910
5230
  // src/compiler/scannable-run.ts
@@ -6278,12 +6598,15 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6278
6598
  case "literal":
6279
6599
  case "regex":
6280
6600
  case "keywords":
6601
+ case "routed":
6281
6602
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6282
6603
  case "choice": {
6283
6604
  const alts = [];
6284
6605
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6285
6606
  return { alts };
6286
6607
  }
6608
+ case "dispatch":
6609
+ return rec(d.selector);
6287
6610
  case "sequence": {
6288
6611
  const chain = [];
6289
6612
  for (const term of d.parsers) {
@@ -6358,6 +6681,9 @@ function failReturn(expected, posExpr) {
6358
6681
  function failReturnArr(expectedArr, posExpr) {
6359
6682
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6360
6683
  }
6684
+ function committedReturnArr(expectedArr, posExpr) {
6685
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6686
+ }
6361
6687
  function hoistExpected(ctx, constArrSource) {
6362
6688
  let name = ctx.expectedMap.get(constArrSource);
6363
6689
  if (name === void 0) {
@@ -6394,15 +6720,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6394
6720
  return probe + trace + failReturnArr(expectedArr, posExpr);
6395
6721
  }
6396
6722
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6397
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6723
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6724
+ }
6725
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6726
+ if (ctx.failLabel) {
6727
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6728
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6729
+ }
6730
+ return committedReturnArr(expectedArr, posExpr);
6398
6731
  }
6399
6732
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6400
6733
  if (srcCtx !== "_ctx") {
6401
6734
  if (ctx.failLabel) {
6402
6735
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6403
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6736
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6404
6737
  }
6405
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6738
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6406
6739
  }
6407
6740
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6408
6741
  return resultFromRecorded();
@@ -6615,6 +6948,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6615
6948
  // nothing committed remains.
6616
6949
  case "choice":
6617
6950
  return false;
6951
+ case "dispatch":
6952
+ 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);
6618
6953
  case "attempt":
6619
6954
  return false;
6620
6955
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6659,6 +6994,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6659
6994
  return true;
6660
6995
  }
6661
6996
  }
6997
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
6998
+ if (seen.has(p)) return false;
6999
+ seen.add(p);
7000
+ const d = p._def;
7001
+ switch (d.tag) {
7002
+ case "dispatch":
7003
+ return true;
7004
+ case "choice":
7005
+ case "sequence":
7006
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
7007
+ case "many":
7008
+ case "oneOrMore":
7009
+ case "optional":
7010
+ case "attempt":
7011
+ case "transform":
7012
+ case "label":
7013
+ case "field":
7014
+ case "grammar":
7015
+ case "node":
7016
+ return mayCommitFailure(d.parser, seen);
7017
+ case "sepBy":
7018
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
7019
+ case "skip":
7020
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
7021
+ case "token":
7022
+ case "leaf":
7023
+ return mayCommitFailure(d.parser, seen);
7024
+ case "withCtx":
7025
+ return mayCommitFailure(d.parser, seen);
7026
+ case "scanTo":
7027
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
7028
+ case "lazy": {
7029
+ try {
7030
+ return mayCommitFailure(d.thunk(), seen);
7031
+ } catch {
7032
+ return true;
7033
+ }
7034
+ }
7035
+ case "unknown":
7036
+ return true;
7037
+ default:
7038
+ return false;
7039
+ }
7040
+ }
6662
7041
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6663
7042
  if (seen.has(p)) return false;
6664
7043
  seen.add(p);
@@ -6669,6 +7048,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6669
7048
  case "keywords":
6670
7049
  case "node":
6671
7050
  case "token":
7051
+ case "routed":
6672
7052
  case "leaf":
6673
7053
  return true;
6674
7054
  case "not":
@@ -6680,6 +7060,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6680
7060
  case "sequence":
6681
7061
  case "choice":
6682
7062
  return d.parsers.some((x) => capturesLeaf(x, seen));
7063
+ case "dispatch":
7064
+ 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);
6683
7065
  case "sepBy":
6684
7066
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6685
7067
  case "many":
@@ -6726,6 +7108,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6726
7108
  ctx.recordFail = savedRecord;
6727
7109
  const ind0 = ind(ctx);
6728
7110
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7111
+ const mayCommit = mayCommitFailure(inner);
6729
7112
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6730
7113
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6731
7114
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6739,6 +7122,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6739
7122
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6740
7123
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6741
7124
  ] : [],
7125
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6742
7126
  `${ind0}${lbl}: {`,
6743
7127
  ...r.stmts,
6744
7128
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6747,7 +7131,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6747
7131
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6748
7132
  ] : []
6749
7133
  ];
6750
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7134
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6751
7135
  }
6752
7136
  var CHARCODE_CHAIN_MAX = 16;
6753
7137
  function emitLit(def, ctx, pos) {
@@ -7124,6 +7508,186 @@ function emitChoice(parser2, def, ctx, pos) {
7124
7508
  }
7125
7509
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7126
7510
  }
7511
+ function emitTailFallible(parser2, ctx, pos) {
7512
+ const savedRecord = ctx.recordFail;
7513
+ ctx.recordFail = true;
7514
+ const out = emitFallible(parser2, ctx, pos);
7515
+ ctx.recordFail = savedRecord;
7516
+ return out;
7517
+ }
7518
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7519
+ if (seen.has(p)) return false;
7520
+ seen.add(p);
7521
+ const d = p._def;
7522
+ switch (d.tag) {
7523
+ case "expect":
7524
+ case "recover":
7525
+ return true;
7526
+ case "many":
7527
+ case "oneOrMore":
7528
+ case "sepBy":
7529
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7530
+ case "choice":
7531
+ case "sequence": {
7532
+ for (const item of d.parsers) {
7533
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7534
+ }
7535
+ return false;
7536
+ }
7537
+ case "dispatch": {
7538
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7539
+ for (const item of d.cases) {
7540
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7541
+ }
7542
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7543
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7544
+ }
7545
+ case "attempt":
7546
+ case "transform":
7547
+ case "label":
7548
+ case "field":
7549
+ case "grammar":
7550
+ case "node":
7551
+ case "token":
7552
+ case "leaf":
7553
+ case "withCtx":
7554
+ case "optional":
7555
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7556
+ case "skip":
7557
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7558
+ case "scanTo":
7559
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7560
+ for (const item of d.skip) {
7561
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7562
+ }
7563
+ return false;
7564
+ case "lazy": {
7565
+ try {
7566
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7567
+ } catch {
7568
+ return true;
7569
+ }
7570
+ }
7571
+ case "unknown":
7572
+ return true;
7573
+ default:
7574
+ return false;
7575
+ }
7576
+ }
7577
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7578
+ if (parserUsesRouted2(def.selector)) {
7579
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7580
+ }
7581
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7582
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7583
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7584
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7585
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7586
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7587
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7588
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7589
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7590
+ const selector = emit(def.selector, ctx, pos);
7591
+ const outV = v(ctx, "_dval");
7592
+ const outE = v(ctx, "_dend");
7593
+ const keyV = v(ctx, "_dkey");
7594
+ const stmts = [
7595
+ ...selectorLeafMark ? [
7596
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7597
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7598
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7599
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7600
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7601
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7602
+ ] : [],
7603
+ ...selector.stmts,
7604
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7605
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7606
+ ];
7607
+ let wroteBranch = false;
7608
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7609
+ const parser3 = entry.parser;
7610
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7611
+ stmts.push(head);
7612
+ ctx.indent++;
7613
+ const usesRouted = branchUsesRouted2(entry);
7614
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7615
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7616
+ const errMark = mayError ? v(ctx, "_derr") : null;
7617
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7618
+ if (routedV) {
7619
+ stmts.push(
7620
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7621
+ ...selectorLeafMark ? [
7622
+ `${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}`
7623
+ ] : []
7624
+ );
7625
+ }
7626
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7627
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7628
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7629
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7630
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7631
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7632
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7633
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7634
+ stmts.push(
7635
+ ...tail.stmts,
7636
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7637
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7638
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7639
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7640
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7641
+ );
7642
+ ctx.indent--;
7643
+ stmts.push(`${ind(ctx)}}`);
7644
+ };
7645
+ let coverageIndex = 0;
7646
+ for (const entry of def.cases) {
7647
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7648
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7649
+ wroteBranch = true;
7650
+ }
7651
+ const emitMatcherTails = () => {
7652
+ for (const matcher of def.matchers ?? []) {
7653
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7654
+ wroteBranch = true;
7655
+ }
7656
+ };
7657
+ if (def.otherwise) {
7658
+ emitMatcherTails();
7659
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7660
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7661
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7662
+ } else {
7663
+ emitMatcherTails();
7664
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7665
+ const fail = failArrBody(ctx, expected, selector.endVar);
7666
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7667
+ }
7668
+ return { stmts, valueVar: outV, endVar: outE };
7669
+ }
7670
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7671
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7672
+ const parts = [`${valueVar}.length === ${key.length}`];
7673
+ for (let i = 0; i < key.length; i++) {
7674
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7675
+ }
7676
+ return parts.join(" && ");
7677
+ }
7678
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7679
+ if (matcher.kind === "matches") {
7680
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7681
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7682
+ }
7683
+ const text = matcher.value;
7684
+ const parts = [`${valueVar}.length >= ${text.length}`];
7685
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7686
+ for (let i = 0; i < text.length; i++) {
7687
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7688
+ }
7689
+ return parts.join(" && ");
7690
+ }
7127
7691
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7128
7692
  const superParser = def.parsers[superIndex];
7129
7693
  const regexDef = getCoreRegexDef(superParser);
@@ -7245,12 +7809,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7245
7809
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7246
7810
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7247
7811
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7812
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7248
7813
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7249
7814
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7250
7815
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7251
7816
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7252
7817
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7253
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7818
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7819
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7820
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7821
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7254
7822
  const failSlot = atStart ? staticFx : "_ctx._fx";
7255
7823
  stmts.push(`${ind0}if (${skipCond}) {`);
7256
7824
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7265,7 +7833,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7265
7833
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7266
7834
  );
7267
7835
  }
7268
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7836
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7837
+ const arm = emitFallible(p, ctx, pos, atStart);
7838
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7269
7839
  stmts.push(...armStmts);
7270
7840
  if (autoNot && autoNot.length > 0) {
7271
7841
  const anCode = v(ctx, "_anc");
@@ -7289,10 +7859,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7289
7859
  stmts.push(`${ind(ctx)}}`);
7290
7860
  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} });`} }`);
7291
7861
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7862
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7292
7863
  } else {
7293
7864
  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} }) }`;
7294
7865
  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} })`} }`);
7295
7866
  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} })`} }`);
7867
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7296
7868
  }
7297
7869
  ctx.indent -= fsGuard ? 2 : 1;
7298
7870
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7449,7 +8021,8 @@ function emitMany(def, ctx, pos) {
7449
8021
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7450
8022
  );
7451
8023
  }
7452
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
8024
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
8025
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7453
8026
  stmts.push(...iterStmts);
7454
8027
  if (ctx.recovery) {
7455
8028
  const rrV = v(ctx, "_rr");
@@ -7468,7 +8041,8 @@ function emitMany(def, ctx, pos) {
7468
8041
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7469
8042
  );
7470
8043
  } else {
7471
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
8044
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
8045
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7472
8046
  }
7473
8047
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7474
8048
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7480,10 +8054,12 @@ function emitMany(def, ctx, pos) {
7480
8054
  function emitOptional(def, ctx, pos) {
7481
8055
  const valV = v(ctx, "_opt");
7482
8056
  const endV = v(ctx, "_opte");
7483
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
8057
+ const inner = emitFallible(def.parser, ctx, pos, true);
8058
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7484
8059
  const ind0 = ind(ctx);
7485
8060
  const stmts = [
7486
8061
  ...lblStmts,
8062
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7487
8063
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7488
8064
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7489
8065
  ];
@@ -7515,7 +8091,7 @@ function emitAttempt(p, def, ctx, pos) {
7515
8091
  ...preGuard,
7516
8092
  `${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`,
7517
8093
  ...inner.stmts,
7518
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
8094
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7519
8095
  ],
7520
8096
  valueVar: inner.valVar,
7521
8097
  endVar: inner.endVar
@@ -7529,18 +8105,21 @@ function emitSepBy(_p, def, ctx, pos) {
7529
8105
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7530
8106
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7531
8107
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7532
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
8108
+ const first = emitFallible(def.parser, ctx, pos, true);
8109
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7533
8110
  const stmts = [
7534
8111
  `${ind(ctx)}const ${arrV} = []`,
7535
8112
  `${ind(ctx)}let ${curV} = ${pos}`,
7536
8113
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7537
8114
  ...firstStmts
7538
8115
  ];
7539
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7540
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
8116
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
8117
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
8118
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7541
8119
  const rr = v(ctx, "_rr");
7542
8120
  return [
7543
8121
  `${ind(ctx)}if (!${nextOkVar}) {`,
8122
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7544
8123
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7545
8124
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7546
8125
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7569,6 +8148,7 @@ function emitSepBy(_p, def, ctx, pos) {
7569
8148
  stmts.push(
7570
8149
  `${ind(ctx)}let ${didV} = false`,
7571
8150
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8151
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7572
8152
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7573
8153
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7574
8154
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7579,6 +8159,7 @@ function emitSepBy(_p, def, ctx, pos) {
7579
8159
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7580
8160
  ctx.indent++;
7581
8161
  } else {
8162
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7582
8163
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7583
8164
  ctx.indent++;
7584
8165
  stmts.push(
@@ -7607,16 +8188,18 @@ function emitSepBy(_p, def, ctx, pos) {
7607
8188
  );
7608
8189
  sepAtPos = spV;
7609
8190
  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}; `;
7610
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7611
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
8191
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8192
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8193
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7612
8194
  const post = postSepMarks();
7613
8195
  stmts.push(...post.decl);
7614
8196
  const npV = v(ctx, "_np");
7615
8197
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7616
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8198
+ const next = emitFallible(def.parser, ctx, npV, true);
8199
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7617
8200
  stmts.push(
7618
8201
  ...nextStmts,
7619
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8202
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7620
8203
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7621
8204
  `${ind(ctx)}${curV} = ${nextEnd}`
7622
8205
  );
@@ -7625,14 +8208,16 @@ function emitSepBy(_p, def, ctx, pos) {
7625
8208
  const spV = v(ctx, "_sp");
7626
8209
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7627
8210
  sepAtPos = spV;
7628
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7629
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8211
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8212
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8213
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7630
8214
  const npV = v(ctx, "_np");
7631
8215
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7632
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8216
+ const next = emitFallible(def.parser, ctx, npV, true);
8217
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7633
8218
  stmts.push(
7634
8219
  ...nextStmts,
7635
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8220
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7636
8221
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7637
8222
  `${ind(ctx)}${curV} = ${nextEnd}`
7638
8223
  );
@@ -7646,15 +8231,17 @@ function emitSepBy(_p, def, ctx, pos) {
7646
8231
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7647
8232
  );
7648
8233
  }
7649
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7650
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8234
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8235
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8236
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7651
8237
  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}; ` : "";
7652
8238
  const post = postSepMarks();
7653
8239
  stmts.push(...post.decl);
7654
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
8240
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
8241
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7655
8242
  stmts.push(
7656
8243
  ...nextStmts,
7657
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8244
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7658
8245
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7659
8246
  `${ind(ctx)}${curV} = ${nextEnd}`
7660
8247
  );
@@ -7755,6 +8342,18 @@ function emitPeek(def, ctx, pos) {
7755
8342
  endVar: pos
7756
8343
  };
7757
8344
  }
8345
+ function emitRouted(ctx, pos) {
8346
+ const item = v(ctx, "_rt");
8347
+ return {
8348
+ stmts: [
8349
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8350
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8351
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8352
+ ],
8353
+ valueVar: `${item}.value`,
8354
+ endVar: `${item}.span.end`
8355
+ };
8356
+ }
7758
8357
  function escapeTokenLiteral(value) {
7759
8358
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
7760
8359
  }
@@ -8035,7 +8634,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8035
8634
  const rv = v(ctx, "_rt");
8036
8635
  const vv = v(ctx, "_rtv");
8037
8636
  const ev = v(ctx, "_rte");
8038
- 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 }`;
8637
+ 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 } : {}) }`;
8039
8638
  const stmts = [
8040
8639
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8041
8640
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8116,7 +8715,7 @@ function emitRecover(def, ctx, pos) {
8116
8715
  return { stmts, valueVar: valVar, endVar };
8117
8716
  }
8118
8717
  function emitExpect(def, ctx, pos) {
8119
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8718
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8120
8719
  const ind0 = ind(ctx);
8121
8720
  const errV = v(ctx, "_err");
8122
8721
  const stmts = [
@@ -8128,6 +8727,7 @@ function emitExpect(def, ctx, pos) {
8128
8727
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8129
8728
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8130
8729
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8730
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8131
8731
  `${ind0} ${valVar} = ${errV}`,
8132
8732
  `${ind0} ${endVar} = ${pos}`,
8133
8733
  `${ind0} ${okVar} = true`,
@@ -8138,7 +8738,7 @@ function emitExpect(def, ctx, pos) {
8138
8738
  function emit(p, ctx, pos) {
8139
8739
  const replay = ctx.replayPrefix?.get(p);
8140
8740
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8141
- const dispatch = (emissionPos = pos) => {
8741
+ const dispatch2 = (emissionPos = pos) => {
8142
8742
  const savedRule = ctx.activeCoverageRuleId;
8143
8743
  const rule = ctx.coverage?.plan.rules.get(p);
8144
8744
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8178,14 +8778,14 @@ function emit(p, ctx, pos) {
8178
8778
  ctx.indent = 1;
8179
8779
  ctx.failLabel = "_pfail";
8180
8780
  ctx.recordFail = true;
8181
- const r = dispatch("_pos");
8781
+ const r = dispatch2("_pos");
8182
8782
  ctx.indent = savedIndent;
8183
8783
  ctx.failLabel = savedFailLabel;
8184
8784
  ctx.recordFail = savedRecord;
8185
8785
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8186
8786
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8187
8787
  }
8188
- return instrument(dispatch());
8788
+ return instrument(dispatch2());
8189
8789
  }
8190
8790
  function emitDispatch(p, ctx, pos) {
8191
8791
  const bal = p._balancedAmbient;
@@ -8218,6 +8818,8 @@ function emitDispatch(p, ctx, pos) {
8218
8818
  return emitSeq(def, ctx, pos);
8219
8819
  case "choice":
8220
8820
  return emitChoice(p, def, ctx, pos);
8821
+ case "dispatch":
8822
+ return emitDispatchCombinator(p, def, ctx, pos);
8221
8823
  case "attempt":
8222
8824
  return emitAttempt(p, def, ctx, pos);
8223
8825
  case "many":
@@ -8342,6 +8944,8 @@ function emitDispatch(p, ctx, pos) {
8342
8944
  return emitNot(def, ctx, pos);
8343
8945
  case "peek":
8344
8946
  return emitPeek(def, ctx, pos);
8947
+ case "routed":
8948
+ return emitRouted(ctx, pos);
8345
8949
  case "node":
8346
8950
  return emitNode(def, ctx, pos);
8347
8951
  case "scanTo":
@@ -8438,6 +9042,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8438
9042
  case "sequence":
8439
9043
  case "choice":
8440
9044
  return d.parsers.some((x) => hasNodeDef(x, seen));
9045
+ case "dispatch":
9046
+ 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);
8441
9047
  case "sepBy":
8442
9048
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8443
9049
  case "scanTo":
@@ -8452,6 +9058,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8452
9058
  return false;
8453
9059
  }
8454
9060
  }
9061
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9062
+ if (seen.has(p)) return false;
9063
+ seen.add(p);
9064
+ const d = p._def;
9065
+ switch (d.tag) {
9066
+ case "routed":
9067
+ return true;
9068
+ case "lazy": {
9069
+ try {
9070
+ return parserUsesRouted2(d.thunk(), seen);
9071
+ } catch {
9072
+ return false;
9073
+ }
9074
+ }
9075
+ case "dispatch":
9076
+ return false;
9077
+ default:
9078
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9079
+ }
9080
+ }
8455
9081
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8456
9082
  if (seen.has(p)) return false;
8457
9083
  seen.add(p);
@@ -8471,6 +9097,8 @@ function childrenOf2(def) {
8471
9097
  case "sequence":
8472
9098
  case "choice":
8473
9099
  return def.parsers;
9100
+ case "dispatch":
9101
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8474
9102
  case "many":
8475
9103
  case "attempt":
8476
9104
  case "optional":
@@ -8507,6 +9135,7 @@ function childrenOf2(def) {
8507
9135
  case "regex":
8508
9136
  case "keywords":
8509
9137
  case "guard":
9138
+ case "routed":
8510
9139
  case "unknown":
8511
9140
  return [];
8512
9141
  }
@@ -8514,6 +9143,7 @@ function childrenOf2(def) {
8514
9143
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8515
9144
  "sequence",
8516
9145
  "choice",
9146
+ "dispatch",
8517
9147
  "many",
8518
9148
  "oneOrMore",
8519
9149
  "optional",
@@ -9212,6 +9842,8 @@ function childrenOf3(def) {
9212
9842
  case "sequence":
9213
9843
  case "choice":
9214
9844
  return def.parsers;
9845
+ case "dispatch":
9846
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9215
9847
  case "many":
9216
9848
  case "oneOrMore":
9217
9849
  case "optional":
@@ -9240,6 +9872,7 @@ function childrenOf3(def) {
9240
9872
  case "literal":
9241
9873
  case "regex":
9242
9874
  case "keywords":
9875
+ case "routed":
9243
9876
  case "guard":
9244
9877
  case "recover":
9245
9878
  case "unknown":
@@ -9264,6 +9897,16 @@ function repeatOpts(min, max, trailing) {
9264
9897
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9265
9898
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9266
9899
  }
9900
+ function matcherExpr(entry) {
9901
+ switch (entry.kind) {
9902
+ case "startsWith":
9903
+ return `startsWith(${JSON.stringify(entry.value)})`;
9904
+ case "endsWith":
9905
+ return `endsWith(${JSON.stringify(entry.value)})`;
9906
+ case "matches":
9907
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
9908
+ }
9909
+ }
9267
9910
  function serializeRuleMap(ruleMap, scanSkip) {
9268
9911
  try {
9269
9912
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9361,6 +10004,13 @@ function evalRuleMapIR(ir) {
9361
10004
  "keywords",
9362
10005
  "sequence",
9363
10006
  "choice",
10007
+ "dispatch",
10008
+ "when",
10009
+ "startsWith",
10010
+ "endsWith",
10011
+ "matches",
10012
+ "otherwise",
10013
+ "routed",
9364
10014
  "attempt",
9365
10015
  "many",
9366
10016
  "oneOrMore",
@@ -9394,6 +10044,13 @@ function evalRuleMapIR(ir) {
9394
10044
  keywords,
9395
10045
  sequence,
9396
10046
  choice,
10047
+ dispatch,
10048
+ when,
10049
+ startsWith,
10050
+ endsWith,
10051
+ matches,
10052
+ otherwise,
10053
+ routed,
9397
10054
  attempt,
9398
10055
  many,
9399
10056
  oneOrMore,
@@ -9579,6 +10236,18 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9579
10236
  });
9580
10237
  return `_gch([${items.join(", ")}])`;
9581
10238
  }
10239
+ case "dispatch": {
10240
+ const arms = def.cases.map(
10241
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10242
+ );
10243
+ if (def.matchers) {
10244
+ for (const entry of def.matchers) {
10245
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10246
+ }
10247
+ }
10248
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10249
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10250
+ }
9582
10251
  case "many":
9583
10252
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9584
10253
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -9595,6 +10264,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9595
10264
  return `not(${kid(def.parser)})`;
9596
10265
  case "peek":
9597
10266
  return `peek(${kid(def.parser)})`;
10267
+ case "routed":
10268
+ return "routed()";
9598
10269
  case "trivia":
9599
10270
  return `trivia(${kid(def.parser)})`;
9600
10271
  case "token":
@@ -10616,10 +11287,20 @@ function run(entry, input, options = {}) {
10616
11287
  }
10617
11288
 
10618
11289
  // src/coverage.ts
11290
+ function dispatchMatcher(entry) {
11291
+ switch (entry.kind) {
11292
+ case "startsWith":
11293
+ return startsWith(entry.value);
11294
+ case "endsWith":
11295
+ return endsWith(entry.value);
11296
+ case "matches":
11297
+ return matches(new RegExp(entry.value, entry.flags));
11298
+ }
11299
+ }
10619
11300
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
10620
11301
  function compiledGrammarCoverageDefinitions(grammar) {
10621
11302
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
10622
- if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "label"))) {
11303
+ if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
10623
11304
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
10624
11305
  }
10625
11306
  return definitions;
@@ -10798,6 +11479,56 @@ function coverageEntry(entry, collector, maps, trace) {
10798
11479
  const unsafeSequence = sequence;
10799
11480
  return unsafeSequence(...def.parsers.map(build));
10800
11481
  }
11482
+ case "dispatch": {
11483
+ const ids = maps.dispatches.get(parser2) ?? [];
11484
+ let armIndex = 0;
11485
+ const dispatchStarts = [];
11486
+ const wrapArm = (arm, id) => {
11487
+ const child = build(arm);
11488
+ return {
11489
+ _tag: child._tag,
11490
+ _meta: child._meta,
11491
+ _def: child._def,
11492
+ parse(input, pos, ctx) {
11493
+ const dispatchStart = dispatchStarts[dispatchStarts.length - 1] ?? pos;
11494
+ if (id) trace?.write({ id, phase: "attempt", offset: dispatchStart });
11495
+ const result = child.parse(input, pos, ctx);
11496
+ if (result.ok) {
11497
+ if (id) {
11498
+ collector.hit(id);
11499
+ trace?.write({ id, phase: "selected", offset: dispatchStart, end: result.span.end });
11500
+ trace?.write({ id, phase: "success", offset: dispatchStart, end: result.span.end });
11501
+ }
11502
+ } else if (id) {
11503
+ trace?.write({ id, phase: "failure", offset: result.span.end });
11504
+ }
11505
+ return result;
11506
+ }
11507
+ };
11508
+ };
11509
+ const arms = def.cases.map(
11510
+ (entry2) => when(entry2.keys, wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0)
11511
+ );
11512
+ if (def.matchers) {
11513
+ for (const entry2 of def.matchers) {
11514
+ arms.push(when(dispatchMatcher(entry2), wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0));
11515
+ }
11516
+ }
11517
+ if (def.otherwise) arms.push(otherwise(wrapArm(def.otherwise, ids[armIndex++])));
11518
+ const unsafeDispatch = dispatch;
11519
+ const base2 = unsafeDispatch(build(def.selector), ...arms);
11520
+ return {
11521
+ ...base2,
11522
+ parse(input, pos, ctx) {
11523
+ dispatchStarts.push(pos);
11524
+ try {
11525
+ return base2.parse(input, pos, ctx);
11526
+ } finally {
11527
+ dispatchStarts.pop();
11528
+ }
11529
+ }
11530
+ };
11531
+ }
10801
11532
  case "many":
10802
11533
  return many(build(def.parser));
10803
11534
  case "oneOrMore":
@@ -11242,7 +11973,9 @@ export {
11242
11973
  createGrammarTraceSink,
11243
11974
  createVisitor,
11244
11975
  cstBuildHost,
11976
+ dispatch,
11245
11977
  duplicationFindingCount,
11978
+ endsWith,
11246
11979
  expect,
11247
11980
  extractCharClasses,
11248
11981
  field,
@@ -11265,8 +11998,10 @@ export {
11265
11998
  lineBreaksIn,
11266
11999
  lineStartWithin,
11267
12000
  literal,
12001
+ makeWhen,
11268
12002
  makeWord,
11269
12003
  many,
12004
+ matches,
11270
12005
  noTrivia,
11271
12006
  node,
11272
12007
  not,
@@ -11274,6 +12009,7 @@ export {
11274
12009
  oneOrMore,
11275
12010
  oneOrMoreSep,
11276
12011
  optional,
12012
+ otherwise,
11277
12013
  parse,
11278
12014
  parseDoc,
11279
12015
  parser,
@@ -11282,6 +12018,7 @@ export {
11282
12018
  regex,
11283
12019
  relativize,
11284
12020
  relativizeCST,
12021
+ routed,
11285
12022
  rules,
11286
12023
  run,
11287
12024
  runWithGrammarCoverage,
@@ -11291,12 +12028,14 @@ export {
11291
12028
  shiftAbsolute,
11292
12029
  siteToString,
11293
12030
  skip,
12031
+ startsWith,
11294
12032
  token,
11295
12033
  transform,
11296
12034
  trivia,
11297
12035
  triviaEntries,
11298
12036
  triviaKindMask,
11299
12037
  walk,
12038
+ when,
11300
12039
  withCtx,
11301
12040
  word
11302
12041
  };