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.cjs CHANGED
@@ -55,7 +55,9 @@ __export(index_exports, {
55
55
  createGrammarTraceSink: () => createGrammarTraceSink,
56
56
  createVisitor: () => createVisitor,
57
57
  cstBuildHost: () => cstBuildHost,
58
+ dispatch: () => dispatch,
58
59
  duplicationFindingCount: () => duplicationFindingCount,
60
+ endsWith: () => endsWith,
59
61
  expect: () => expect,
60
62
  extractCharClasses: () => extractCharClasses,
61
63
  field: () => field,
@@ -78,8 +80,10 @@ __export(index_exports, {
78
80
  lineBreaksIn: () => lineBreaksIn,
79
81
  lineStartWithin: () => lineStartWithin,
80
82
  literal: () => literal,
83
+ makeWhen: () => makeWhen,
81
84
  makeWord: () => makeWord,
82
85
  many: () => many,
86
+ matches: () => matches,
83
87
  noTrivia: () => noTrivia,
84
88
  node: () => node,
85
89
  not: () => not,
@@ -87,6 +91,7 @@ __export(index_exports, {
87
91
  oneOrMore: () => oneOrMore,
88
92
  oneOrMoreSep: () => oneOrMoreSep,
89
93
  optional: () => optional,
94
+ otherwise: () => otherwise,
90
95
  parse: () => parse,
91
96
  parseDoc: () => parseDoc,
92
97
  parser: () => parser,
@@ -95,6 +100,7 @@ __export(index_exports, {
95
100
  regex: () => regex,
96
101
  relativize: () => relativize,
97
102
  relativizeCST: () => relativizeCST,
103
+ routed: () => routed,
98
104
  rules: () => rules,
99
105
  run: () => run,
100
106
  runWithGrammarCoverage: () => runWithGrammarCoverage,
@@ -104,12 +110,14 @@ __export(index_exports, {
104
110
  shiftAbsolute: () => shiftAbsolute,
105
111
  siteToString: () => siteToString,
106
112
  skip: () => skip,
113
+ startsWith: () => startsWith,
107
114
  token: () => token,
108
115
  transform: () => transform,
109
116
  trivia: () => trivia,
110
117
  triviaEntries: () => triviaEntries,
111
118
  triviaKindMask: () => triviaKindMask,
112
119
  walk: () => walk,
120
+ when: () => when,
113
121
  withCtx: () => withCtx,
114
122
  word: () => word
115
123
  });
@@ -168,6 +176,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
168
176
  return d.value.length === 0;
169
177
  case "keywords":
170
178
  return false;
179
+ case "routed":
180
+ return false;
171
181
  case "regex":
172
182
  try {
173
183
  const m = new RegExp(d.source).exec("");
@@ -194,6 +204,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
194
204
  return d.parsers.every(me);
195
205
  case "choice":
196
206
  return d.parsers.some(me);
207
+ case "dispatch":
208
+ return me(d.selector);
197
209
  case "transform":
198
210
  case "label":
199
211
  case "trivia":
@@ -267,6 +279,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
267
279
  case "keywords":
268
280
  return p._meta.firstSet;
269
281
  // terminals: no refs, cached set is exact
282
+ case "dispatch":
283
+ return fs(d.selector);
270
284
  case "lazy":
271
285
  try {
272
286
  return fs(d.thunk());
@@ -1184,6 +1198,8 @@ function derive(c, seen) {
1184
1198
  return [def.label];
1185
1199
  case "choice":
1186
1200
  return def.parsers.flatMap(deriveExpected2);
1201
+ case "dispatch":
1202
+ return deriveExpected2(def.selector);
1187
1203
  // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1188
1204
  // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1189
1205
  // able to fail first — deriving from term 0 alone named a token the parse
@@ -1223,6 +1239,8 @@ function derive(c, seen) {
1223
1239
  case "not":
1224
1240
  case "peek":
1225
1241
  return deriveExpected2(def.parser);
1242
+ case "routed":
1243
+ return ["routed()"];
1226
1244
  case "lazy": {
1227
1245
  const name = c._ruleName;
1228
1246
  if (seen.has(c)) return name ? [name] : [];
@@ -1325,6 +1343,7 @@ function choice(...args) {
1325
1343
  const result = parsers[idx].parse(input, pos, ctx);
1326
1344
  if (result.ok) return result;
1327
1345
  expected.push(...result.expected);
1346
+ if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
1328
1347
  return { ok: false, expected, span: { start: pos, end: pos } };
1329
1348
  }
1330
1349
  return {
@@ -1365,6 +1384,7 @@ function choice(...args) {
1365
1384
  rollbackCstCapture(ctx, mark);
1366
1385
  if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1367
1386
  expected.push(...result.expected);
1387
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
1368
1388
  continue;
1369
1389
  }
1370
1390
  const checks = autoNot[i];
@@ -2044,6 +2064,248 @@ function sequence(...parsers) {
2044
2064
  };
2045
2065
  }
2046
2066
 
2067
+ // src/combinators/dispatch.ts
2068
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
2069
+ if (seen.has(parser2)) return false;
2070
+ seen.add(parser2);
2071
+ const def = parser2._def;
2072
+ switch (def.tag) {
2073
+ case "routed":
2074
+ return true;
2075
+ case "sequence":
2076
+ case "choice":
2077
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
2078
+ case "dispatch":
2079
+ return false;
2080
+ case "sepBy":
2081
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
2082
+ case "skip":
2083
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
2084
+ case "grammar":
2085
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
2086
+ case "scanTo":
2087
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
2088
+ case "recover":
2089
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
2090
+ case "lazy":
2091
+ try {
2092
+ return parserUsesRouted(def.thunk(), seen);
2093
+ } catch {
2094
+ return false;
2095
+ }
2096
+ case "many":
2097
+ case "oneOrMore":
2098
+ case "optional":
2099
+ case "attempt":
2100
+ case "transform":
2101
+ case "trivia":
2102
+ case "token":
2103
+ case "leaf":
2104
+ case "label":
2105
+ case "field":
2106
+ case "not":
2107
+ case "peek":
2108
+ case "node":
2109
+ case "withCtx":
2110
+ case "expect":
2111
+ return parserUsesRouted(def.parser, seen);
2112
+ default:
2113
+ return false;
2114
+ }
2115
+ }
2116
+ function branchUsesRouted(branch) {
2117
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
2118
+ }
2119
+ function asciiFoldKey(key) {
2120
+ let out = "";
2121
+ for (let i = 0; i < key.length; i++) {
2122
+ const c = key.charCodeAt(i);
2123
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
2124
+ }
2125
+ return out;
2126
+ }
2127
+ function matchesDispatchMatcher(value, matcher) {
2128
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2129
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2130
+ switch (matcher.kind) {
2131
+ case "startsWith":
2132
+ return candidate.startsWith(expected);
2133
+ case "endsWith":
2134
+ return candidate.endsWith(expected);
2135
+ case "matches": {
2136
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2137
+ return new RegExp(matcher.value, flags).test(value);
2138
+ }
2139
+ }
2140
+ }
2141
+ function isStringMatcher(value) {
2142
+ if (typeof value !== "object" || value === null) return false;
2143
+ const kind = value.kind;
2144
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2145
+ }
2146
+ function fixedMatcher(kind, value) {
2147
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2148
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2149
+ return { kind, value };
2150
+ }
2151
+ function startsWith(prefix) {
2152
+ return fixedMatcher("startsWith", prefix);
2153
+ }
2154
+ function endsWith(suffix) {
2155
+ return fixedMatcher("endsWith", suffix);
2156
+ }
2157
+ function matches(pattern) {
2158
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2159
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2160
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2161
+ }
2162
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2163
+ }
2164
+ function when(key, parser2, opts = {}) {
2165
+ const usesRouted = parserUsesRouted(parser2);
2166
+ if (isStringMatcher(key)) {
2167
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2168
+ }
2169
+ const keys = Array.isArray(key) ? [...key] : [key];
2170
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2171
+ for (const item of keys) {
2172
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2173
+ }
2174
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2175
+ }
2176
+ function makeWhen(opts = {}) {
2177
+ return (key, parser2) => when(key, parser2, opts);
2178
+ }
2179
+ function otherwise(parser2) {
2180
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2181
+ }
2182
+ function routed() {
2183
+ return {
2184
+ _tag: "routed",
2185
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2186
+ _def: { tag: "routed" },
2187
+ parse(_input, pos, ctx) {
2188
+ const item = ctx._routed;
2189
+ if (item === void 0) {
2190
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2191
+ }
2192
+ if (pos !== item.span.start) {
2193
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2194
+ }
2195
+ if (cstCaptureActive(ctx)) {
2196
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2197
+ }
2198
+ return { ok: true, value: item.value, span: item.span };
2199
+ }
2200
+ };
2201
+ }
2202
+ function dispatch(selector, ...arms) {
2203
+ if (parserUsesRouted(selector)) {
2204
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2205
+ }
2206
+ let fallback;
2207
+ let fallbackUsesRouted = false;
2208
+ const matchers = [];
2209
+ const cases = [];
2210
+ const seen = [];
2211
+ for (let i = 0; i < arms.length; i++) {
2212
+ const arm = arms[i];
2213
+ if (arm.kind === "otherwise") {
2214
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2215
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2216
+ fallback = arm.parser;
2217
+ fallbackUsesRouted = arm.usesRouted === true;
2218
+ continue;
2219
+ }
2220
+ if (arm.kind === "whenMatcher") {
2221
+ const matcher = arm.matcher;
2222
+ matchers.push({
2223
+ kind: matcher.kind,
2224
+ value: matcher.value,
2225
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2226
+ parser: arm.parser,
2227
+ caseInsensitive: arm.caseInsensitive,
2228
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2229
+ });
2230
+ continue;
2231
+ }
2232
+ for (const key of arm.keys) {
2233
+ const folded = asciiFoldKey(key);
2234
+ for (const prior of seen) {
2235
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2236
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2237
+ }
2238
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2239
+ }
2240
+ cases.push({
2241
+ keys: arm.keys,
2242
+ parser: arm.parser,
2243
+ caseInsensitive: arm.caseInsensitive,
2244
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2245
+ });
2246
+ }
2247
+ const meta = {
2248
+ firstSet: selector._meta.firstSet,
2249
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2250
+ isTrivia: false
2251
+ };
2252
+ const byKey = /* @__PURE__ */ new Map();
2253
+ const byFoldedKey = /* @__PURE__ */ new Map();
2254
+ for (const entry of cases) {
2255
+ for (const key of entry.keys) {
2256
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2257
+ else byKey.set(key, entry);
2258
+ }
2259
+ }
2260
+ return {
2261
+ _tag: "dispatch",
2262
+ _meta: meta,
2263
+ _def: {
2264
+ tag: "dispatch",
2265
+ selector,
2266
+ cases,
2267
+ ...matchers.length === 0 ? {} : { matchers },
2268
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2269
+ },
2270
+ parse(input, pos, ctx) {
2271
+ if (parserUsesRouted(selector)) {
2272
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2273
+ }
2274
+ const selectorMark = saveTriviaMark(ctx);
2275
+ const selected = selector.parse(input, pos, ctx);
2276
+ if (!selected.ok) return selected;
2277
+ 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 });
2278
+ if (branch === void 0) {
2279
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2280
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2281
+ }
2282
+ const savedRouted = ctx._routed;
2283
+ const usesRouted = branchUsesRouted(branch);
2284
+ let mark = saveTriviaMark(ctx);
2285
+ if (usesRouted) {
2286
+ rollbackTrivia(ctx, selectorMark);
2287
+ mark = saveTriviaMark(ctx);
2288
+ ctx._routed = { value: selected.value, span: selected.span };
2289
+ }
2290
+ let result;
2291
+ try {
2292
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2293
+ } finally {
2294
+ if (usesRouted) ctx._routed = savedRouted;
2295
+ }
2296
+ if (!result.ok) {
2297
+ rollbackTrivia(ctx, mark);
2298
+ return { ...result, committed: true };
2299
+ }
2300
+ return {
2301
+ ok: true,
2302
+ value: [selected.value, result.value],
2303
+ span: { start: pos, end: result.span.end }
2304
+ };
2305
+ }
2306
+ };
2307
+ }
2308
+
2047
2309
  // src/combinators/attempt.ts
2048
2310
  function attempt(parser2) {
2049
2311
  const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
@@ -2066,6 +2328,7 @@ function attempt(parser2) {
2066
2328
  if (result.ok) return result;
2067
2329
  rollbackCstCapture(ctx, mark);
2068
2330
  if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
2331
+ if (result.committed) return result;
2069
2332
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
2070
2333
  }
2071
2334
  };
@@ -2095,6 +2358,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
2095
2358
  const result = combinator.parse(input, pos, ctx);
2096
2359
  if (!result.ok) {
2097
2360
  rollbackTrivia(ctx, mark);
2361
+ if (result.committed) return { fail: result, failPos: pos };
2098
2362
  return { fail: result, failPos: pos };
2099
2363
  }
2100
2364
  if (result.span.end === pos) {
@@ -2138,6 +2402,7 @@ function many(combinator, opts = {}) {
2138
2402
  const item = repItem(combinator, input, cur, ctx, guardable);
2139
2403
  if (item === "stop") break;
2140
2404
  if ("fail" in item) {
2405
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2141
2406
  const sync = ctx._tolerant ? ctx._sync : void 0;
2142
2407
  if (sync === void 0) break;
2143
2408
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2182,6 +2447,7 @@ function atLeast(combinator, min, max) {
2182
2447
  while (count < min) {
2183
2448
  const item = repItem(combinator, input, cur, ctx, guardable);
2184
2449
  if (item === "stop" || "fail" in item) {
2450
+ if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
2185
2451
  expected ??= deriveExpected(combinator);
2186
2452
  return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2187
2453
  }
@@ -2194,6 +2460,7 @@ function atLeast(combinator, min, max) {
2194
2460
  const item = repItem(combinator, input, cur, ctx, guardable);
2195
2461
  if (item === "stop") break;
2196
2462
  if ("fail" in item) {
2463
+ if (!item.fail.ok && item.fail.committed) return item.fail;
2197
2464
  const sync = ctx._tolerant ? ctx._sync : void 0;
2198
2465
  if (sync === void 0) break;
2199
2466
  if (matchesAt(sync, input, item.failPos, ctx)) break;
@@ -2232,6 +2499,7 @@ function optional(combinator) {
2232
2499
  const result = combinator.parse(input, pos, ctx);
2233
2500
  if (result.ok) return result;
2234
2501
  rollbackTrivia(ctx, mark);
2502
+ if (result.committed) return result;
2235
2503
  return { ok: true, value: null, span: { start: pos, end: pos } };
2236
2504
  }
2237
2505
  };
@@ -2271,6 +2539,7 @@ function sepBy(combinator, separator, opts = {}) {
2271
2539
  values.push(first.value);
2272
2540
  cur = first.span.end;
2273
2541
  } else {
2542
+ if (first.committed) return first;
2274
2543
  const term = ctx._tolerant ? ctx._sync : void 0;
2275
2544
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2276
2545
  if (min >= 1) return failAt2(pos);
@@ -2298,6 +2567,7 @@ function sepBy(combinator, separator, opts = {}) {
2298
2567
  const sep = separator.parse(input, sepPos, ctx);
2299
2568
  if (!sep.ok) {
2300
2569
  rollbackTrivia(ctx, loopMark);
2570
+ if (sep.committed) return sep;
2301
2571
  break;
2302
2572
  }
2303
2573
  const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
@@ -2313,6 +2583,7 @@ function sepBy(combinator, separator, opts = {}) {
2313
2583
  }
2314
2584
  const next = combinator.parse(input, nextPos, ctx);
2315
2585
  if (!next.ok) {
2586
+ if (next.committed) return next;
2316
2587
  const term = ctx._tolerant ? ctx._sync : void 0;
2317
2588
  if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
2318
2589
  expected ??= deriveExpected(combinator);
@@ -2405,6 +2676,12 @@ function markUnusedValues(root) {
2405
2676
  case "choice":
2406
2677
  for (const p of def.parsers) visit(p, consumed);
2407
2678
  return;
2679
+ case "dispatch":
2680
+ visit(def.selector, consumed);
2681
+ for (const entry of def.cases) visit(entry.parser, consumed);
2682
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2683
+ if (def.otherwise) visit(def.otherwise, consumed);
2684
+ return;
2408
2685
  case "node":
2409
2686
  visit(def.parser, false);
2410
2687
  return;
@@ -2680,6 +2957,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2680
2957
  case "sequence":
2681
2958
  case "choice":
2682
2959
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2960
+ case "dispatch":
2961
+ 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);
2683
2962
  case "sepBy":
2684
2963
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2685
2964
  case "skip":
@@ -2747,6 +3026,8 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2747
3026
  return parserHasTriviaSite(d.parser, seen);
2748
3027
  case "choice":
2749
3028
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
3029
+ case "dispatch":
3030
+ 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);
2750
3031
  case "grammar":
2751
3032
  return parserHasTriviaSite(d.parser, seen);
2752
3033
  case "lazy": {
@@ -2772,6 +3053,8 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2772
3053
  case "sequence":
2773
3054
  case "choice":
2774
3055
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
3056
+ case "dispatch":
3057
+ 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);
2775
3058
  case "sepBy":
2776
3059
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2777
3060
  case "skip":
@@ -3173,7 +3456,7 @@ function leaf(root, fn) {
3173
3456
  }
3174
3457
 
3175
3458
  // src/version.ts
3176
- var PARSEMAN_VERSION = "0.38.0";
3459
+ var PARSEMAN_VERSION = "0.39.1";
3177
3460
 
3178
3461
  // src/cst/host-mode.ts
3179
3462
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3329,6 +3612,8 @@ function classifyBroadArm(arm, resolve) {
3329
3612
  }
3330
3613
  return null;
3331
3614
  }
3615
+ case "dispatch":
3616
+ return walk2(d.selector);
3332
3617
  default:
3333
3618
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3334
3619
  }
@@ -3428,6 +3713,12 @@ function analyzeGatingRules(ruleMap, opts) {
3428
3713
  if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3429
3714
  for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3430
3715
  if (rec[k]) kids.push(rec[k]);
3716
+ if (d.tag === "dispatch") {
3717
+ kids.push(d.selector);
3718
+ for (const c of d.cases) kids.push(c.parser);
3719
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3720
+ if (d.otherwise !== void 0) kids.push(d.otherwise);
3721
+ }
3431
3722
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3432
3723
  if (d.tag === "lazy") {
3433
3724
  try {
@@ -3534,6 +3825,12 @@ function childrenOf(d) {
3534
3825
  case "sequence":
3535
3826
  case "choice":
3536
3827
  return d.parsers;
3828
+ case "dispatch":
3829
+ return [
3830
+ d.selector,
3831
+ ...d.cases.map((c) => c.parser),
3832
+ ...d.otherwise === void 0 ? [] : [d.otherwise]
3833
+ ];
3537
3834
  case "skip":
3538
3835
  return [d.main, d.skipped];
3539
3836
  case "sepBy":
@@ -3590,6 +3887,8 @@ function payloadKey(p, d) {
3590
3887
  return `${d.tag}\0${d.min}\0${d.max ?? ""}`;
3591
3888
  case "sepBy":
3592
3889
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
3890
+ case "dispatch":
3891
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
3593
3892
  case "expect":
3594
3893
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
3595
3894
  case "scanTo":
@@ -3661,6 +3960,11 @@ function render(p, depth = 3) {
3661
3960
  return `sequence(${kids.join(", ")})`;
3662
3961
  case "choice":
3663
3962
  return `choice(${kids.join(", ")})`;
3963
+ case "dispatch": {
3964
+ const arms = d.cases.map((c, i) => `when([${c.keys.map((k) => JSON.stringify(k)).join(", ")}], ${kids[i + 1] ?? ""})`);
3965
+ if (d.otherwise !== void 0) arms.push(`otherwise(${kids[kids.length - 1] ?? ""})`);
3966
+ return `dispatch(${kids[0] ?? ""}, ${arms.join(", ")})`;
3967
+ }
3664
3968
  case "node":
3665
3969
  return `node(${d.type ? `'${d.type}', ` : ""}${kids[0] ?? ""})`;
3666
3970
  case "label":
@@ -3684,6 +3988,10 @@ function segment(d, index) {
3684
3988
  return `seq[${index}]`;
3685
3989
  case "choice":
3686
3990
  return `choice[${index}]`;
3991
+ case "dispatch":
3992
+ if (index === 0) return "dispatch.selector";
3993
+ if (index <= d.cases.length) return `dispatch.when[${index - 1}]`;
3994
+ return "dispatch.otherwise";
3687
3995
  case "node":
3688
3996
  return `node(${d.type ?? ""})`;
3689
3997
  case "label":
@@ -4932,11 +5240,25 @@ function escapeClassChar(ch2) {
4932
5240
  }
4933
5241
 
4934
5242
  // src/compiler/grammar-coverage-ids.ts
5243
+ function idPart(value) {
5244
+ return encodeURIComponent(value);
5245
+ }
5246
+ function dispatchArmIds(def, path) {
5247
+ const ids = [];
5248
+ for (const entry of def.cases) ids.push(`dispatch:${path}/when:${entry.keys.map(idPart).join("|")}`);
5249
+ for (const entry of def.matchers ?? []) {
5250
+ ids.push(`dispatch:${path}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5251
+ }
5252
+ if (def.otherwise) ids.push(`dispatch:${path}/otherwise`);
5253
+ return ids;
5254
+ }
4935
5255
  function children(def, winners) {
4936
5256
  switch (def.tag) {
4937
5257
  case "sequence":
4938
5258
  case "choice":
4939
5259
  return def.parsers;
5260
+ case "dispatch":
5261
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4940
5262
  case "many":
4941
5263
  case "oneOrMore":
4942
5264
  case "optional":
@@ -4979,6 +5301,7 @@ function children(def, winners) {
4979
5301
  function buildGrammarPlan(entry, winners) {
4980
5302
  const definitions = /* @__PURE__ */ new Map();
4981
5303
  const choices = /* @__PURE__ */ new WeakMap();
5304
+ const dispatches = /* @__PURE__ */ new WeakMap();
4982
5305
  const attempts = /* @__PURE__ */ new WeakMap();
4983
5306
  const labels = /* @__PURE__ */ new WeakMap();
4984
5307
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -5009,6 +5332,11 @@ function buildGrammarPlan(entry, winners) {
5009
5332
  choices.set(parser2, ids);
5010
5333
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5011
5334
  }
5335
+ if (parser2._def.tag === "dispatch") {
5336
+ const ids = dispatchArmIds(parser2._def, path);
5337
+ dispatches.set(parser2, ids);
5338
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5339
+ }
5012
5340
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path}`);
5013
5341
  if (parser2._def.tag === "label") {
5014
5342
  const id = `label:${path}`;
@@ -5021,7 +5349,7 @@ function buildGrammarPlan(entry, winners) {
5021
5349
  for (const root of roots) {
5022
5350
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
5023
5351
  }
5024
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5352
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
5025
5353
  }
5026
5354
 
5027
5355
  // src/compiler/scannable-run.ts
@@ -6395,12 +6723,15 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6395
6723
  case "literal":
6396
6724
  case "regex":
6397
6725
  case "keywords":
6726
+ case "routed":
6398
6727
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6399
6728
  case "choice": {
6400
6729
  const alts = [];
6401
6730
  for (const arm of d.parsers) alts.push(...rec(arm).alts);
6402
6731
  return { alts };
6403
6732
  }
6733
+ case "dispatch":
6734
+ return rec(d.selector);
6404
6735
  case "sequence": {
6405
6736
  const chain = [];
6406
6737
  for (const term of d.parsers) {
@@ -6475,6 +6806,9 @@ function failReturn(expected, posExpr) {
6475
6806
  function failReturnArr(expectedArr, posExpr) {
6476
6807
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
6477
6808
  }
6809
+ function committedReturnArr(expectedArr, posExpr) {
6810
+ return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} }, committed: true }`;
6811
+ }
6478
6812
  function hoistExpected(ctx, constArrSource) {
6479
6813
  let name = ctx.expectedMap.get(constArrSource);
6480
6814
  if (name === void 0) {
@@ -6511,15 +6845,22 @@ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
6511
6845
  return probe + trace + failReturnArr(expectedArr, posExpr);
6512
6846
  }
6513
6847
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
6514
- return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
6848
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} }, ...(_ctx._fc ? { committed: true } : {}) }`;
6849
+ }
6850
+ function committedFailBody(ctx, expectedArr = "_ctx._fx", posExpr = "_ctx._fe") {
6851
+ if (ctx.failLabel) {
6852
+ if (!ctx.recordFail) return `{ _ctx._fc = true; break ${ctx.failLabel} }`;
6853
+ return `{ _ctx._fc = true; _ctx._fe = ${posExpr}; _ctx._fx = ${expectedArr}; break ${ctx.failLabel} }`;
6854
+ }
6855
+ return committedReturnArr(expectedArr, posExpr);
6515
6856
  }
6516
6857
  function propagateFailBody(ctx, srcCtx = "_ctx") {
6517
6858
  if (srcCtx !== "_ctx") {
6518
6859
  if (ctx.failLabel) {
6519
6860
  if (!ctx.recordFail) return `break ${ctx.failLabel}`;
6520
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
6861
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; break ${ctx.failLabel} }`;
6521
6862
  }
6522
- return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
6863
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; _ctx._fc = ${srcCtx}._fc; ${resultFromRecorded()} }`;
6523
6864
  }
6524
6865
  if (ctx.failLabel) return `break ${ctx.failLabel}`;
6525
6866
  return resultFromRecorded();
@@ -6732,6 +7073,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6732
7073
  // nothing committed remains.
6733
7074
  case "choice":
6734
7075
  return false;
7076
+ case "dispatch":
7077
+ 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);
6735
7078
  case "attempt":
6736
7079
  return false;
6737
7080
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6776,6 +7119,50 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6776
7119
  return true;
6777
7120
  }
6778
7121
  }
7122
+ function mayCommitFailure(p, seen = /* @__PURE__ */ new Set()) {
7123
+ if (seen.has(p)) return false;
7124
+ seen.add(p);
7125
+ const d = p._def;
7126
+ switch (d.tag) {
7127
+ case "dispatch":
7128
+ return true;
7129
+ case "choice":
7130
+ case "sequence":
7131
+ return d.parsers.some((x) => mayCommitFailure(x, seen));
7132
+ case "many":
7133
+ case "oneOrMore":
7134
+ case "optional":
7135
+ case "attempt":
7136
+ case "transform":
7137
+ case "label":
7138
+ case "field":
7139
+ case "grammar":
7140
+ case "node":
7141
+ return mayCommitFailure(d.parser, seen);
7142
+ case "sepBy":
7143
+ return mayCommitFailure(d.parser, seen) || mayCommitFailure(d.separator, seen);
7144
+ case "skip":
7145
+ return mayCommitFailure(d.main, seen) || mayCommitFailure(d.skipped, seen);
7146
+ case "token":
7147
+ case "leaf":
7148
+ return mayCommitFailure(d.parser, seen);
7149
+ case "withCtx":
7150
+ return mayCommitFailure(d.parser, seen);
7151
+ case "scanTo":
7152
+ return mayCommitFailure(d.sentinel, seen) || d.skip.some((x) => mayCommitFailure(x, seen));
7153
+ case "lazy": {
7154
+ try {
7155
+ return mayCommitFailure(d.thunk(), seen);
7156
+ } catch {
7157
+ return true;
7158
+ }
7159
+ }
7160
+ case "unknown":
7161
+ return true;
7162
+ default:
7163
+ return false;
7164
+ }
7165
+ }
6779
7166
  function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6780
7167
  if (seen.has(p)) return false;
6781
7168
  seen.add(p);
@@ -6786,6 +7173,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6786
7173
  case "keywords":
6787
7174
  case "node":
6788
7175
  case "token":
7176
+ case "routed":
6789
7177
  case "leaf":
6790
7178
  return true;
6791
7179
  case "not":
@@ -6797,6 +7185,8 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6797
7185
  case "sequence":
6798
7186
  case "choice":
6799
7187
  return d.parsers.some((x) => capturesLeaf(x, seen));
7188
+ case "dispatch":
7189
+ 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);
6800
7190
  case "sepBy":
6801
7191
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6802
7192
  case "many":
@@ -6843,6 +7233,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6843
7233
  ctx.recordFail = savedRecord;
6844
7234
  const ind0 = ind(ctx);
6845
7235
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
7236
+ const mayCommit = mayCommitFailure(inner);
6846
7237
  const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
6847
7238
  const mL = needsRollback ? v(ctx, "_fcl") : null;
6848
7239
  const mR = needsRollback ? v(ctx, "_fcr") : null;
@@ -6856,6 +7247,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6856
7247
  `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
6857
7248
  ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
6858
7249
  ] : [],
7250
+ ...mayCommit ? [`${ind0}_ctx._fc = false`] : [],
6859
7251
  `${ind0}${lbl}: {`,
6860
7252
  ...r.stmts,
6861
7253
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
@@ -6864,7 +7256,7 @@ function emitFallible(inner, ctx, pos, swallow = false) {
6864
7256
  `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
6865
7257
  ] : []
6866
7258
  ];
6867
- return { stmts, okVar: okV, valVar: valV, endVar: endV };
7259
+ return { stmts, okVar: okV, valVar: valV, endVar: endV, mayCommit };
6868
7260
  }
6869
7261
  var CHARCODE_CHAIN_MAX = 16;
6870
7262
  function emitLit(def, ctx, pos) {
@@ -7241,6 +7633,186 @@ function emitChoice(parser2, def, ctx, pos) {
7241
7633
  }
7242
7634
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
7243
7635
  }
7636
+ function emitTailFallible(parser2, ctx, pos) {
7637
+ const savedRecord = ctx.recordFail;
7638
+ ctx.recordFail = true;
7639
+ const out = emitFallible(parser2, ctx, pos);
7640
+ ctx.recordFail = savedRecord;
7641
+ return out;
7642
+ }
7643
+ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7644
+ if (seen.has(p)) return false;
7645
+ seen.add(p);
7646
+ const d = p._def;
7647
+ switch (d.tag) {
7648
+ case "expect":
7649
+ case "recover":
7650
+ return true;
7651
+ case "many":
7652
+ case "oneOrMore":
7653
+ case "sepBy":
7654
+ return recovery || mayRecordRecoveryError(d.parser, recovery, seen);
7655
+ case "choice":
7656
+ case "sequence": {
7657
+ for (const item of d.parsers) {
7658
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7659
+ }
7660
+ return false;
7661
+ }
7662
+ case "dispatch": {
7663
+ if (mayRecordRecoveryError(d.selector, recovery, seen)) return true;
7664
+ for (const item of d.cases) {
7665
+ if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7666
+ }
7667
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7668
+ return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7669
+ }
7670
+ case "attempt":
7671
+ case "transform":
7672
+ case "label":
7673
+ case "field":
7674
+ case "grammar":
7675
+ case "node":
7676
+ case "token":
7677
+ case "leaf":
7678
+ case "withCtx":
7679
+ case "optional":
7680
+ return mayRecordRecoveryError(d.parser, recovery, seen);
7681
+ case "skip":
7682
+ return mayRecordRecoveryError(d.main, recovery, seen) || mayRecordRecoveryError(d.skipped, recovery, seen);
7683
+ case "scanTo":
7684
+ if (mayRecordRecoveryError(d.sentinel, recovery, seen)) return true;
7685
+ for (const item of d.skip) {
7686
+ if (mayRecordRecoveryError(item, recovery, seen)) return true;
7687
+ }
7688
+ return false;
7689
+ case "lazy": {
7690
+ try {
7691
+ return mayRecordRecoveryError(d.thunk(), recovery, seen);
7692
+ } catch {
7693
+ return true;
7694
+ }
7695
+ }
7696
+ case "unknown":
7697
+ return true;
7698
+ default:
7699
+ return false;
7700
+ }
7701
+ }
7702
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7703
+ if (parserUsesRouted2(def.selector)) {
7704
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7705
+ }
7706
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7707
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7708
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7709
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7710
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7711
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7712
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7713
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7714
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7715
+ const selector = emit(def.selector, ctx, pos);
7716
+ const outV = v(ctx, "_dval");
7717
+ const outE = v(ctx, "_dend");
7718
+ const keyV = v(ctx, "_dkey");
7719
+ const stmts = [
7720
+ ...selectorLeafMark ? [
7721
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7722
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7723
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7724
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7725
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7726
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7727
+ ] : [],
7728
+ ...selector.stmts,
7729
+ `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7730
+ `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7731
+ ];
7732
+ let wroteBranch = false;
7733
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7734
+ const parser3 = entry.parser;
7735
+ const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7736
+ stmts.push(head);
7737
+ ctx.indent++;
7738
+ const usesRouted = branchUsesRouted2(entry);
7739
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7740
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7741
+ const errMark = mayError ? v(ctx, "_derr") : null;
7742
+ const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7743
+ if (routedV) {
7744
+ stmts.push(
7745
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7746
+ ...selectorLeafMark ? [
7747
+ `${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}`
7748
+ ] : []
7749
+ );
7750
+ }
7751
+ if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7752
+ if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7753
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7754
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7755
+ const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7756
+ const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7757
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7758
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7759
+ stmts.push(
7760
+ ...tail.stmts,
7761
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7762
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7763
+ `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7764
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7765
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7766
+ );
7767
+ ctx.indent--;
7768
+ stmts.push(`${ind(ctx)}}`);
7769
+ };
7770
+ let coverageIndex = 0;
7771
+ for (const entry of def.cases) {
7772
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7773
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7774
+ wroteBranch = true;
7775
+ }
7776
+ const emitMatcherTails = () => {
7777
+ for (const matcher of def.matchers ?? []) {
7778
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7779
+ wroteBranch = true;
7780
+ }
7781
+ };
7782
+ if (def.otherwise) {
7783
+ emitMatcherTails();
7784
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7785
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7786
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7787
+ } else {
7788
+ emitMatcherTails();
7789
+ const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7790
+ const fail = failArrBody(ctx, expected, selector.endVar);
7791
+ stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7792
+ }
7793
+ return { stmts, valueVar: outV, endVar: outE };
7794
+ }
7795
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7796
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7797
+ const parts = [`${valueVar}.length === ${key.length}`];
7798
+ for (let i = 0; i < key.length; i++) {
7799
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7800
+ }
7801
+ return parts.join(" && ");
7802
+ }
7803
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7804
+ if (matcher.kind === "matches") {
7805
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7806
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7807
+ }
7808
+ const text = matcher.value;
7809
+ const parts = [`${valueVar}.length >= ${text.length}`];
7810
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7811
+ for (let i = 0; i < text.length; i++) {
7812
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7813
+ }
7814
+ return parts.join(" && ");
7815
+ }
7244
7816
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7245
7817
  const superParser = def.parsers[superIndex];
7246
7818
  const regexDef = getCoreRegexDef(superParser);
@@ -7362,12 +7934,16 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7362
7934
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
7363
7935
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
7364
7936
  const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
7937
+ const armMayRecordError = mayRecordRecoveryError(p, !!ctx.recovery);
7365
7938
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
7366
7939
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
7367
7940
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
7368
7941
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
7369
7942
  const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
7370
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7943
+ const markErrors = armMayRecordError ? v(ctx, "_cme") : null;
7944
+ const captureRollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
7945
+ const errorRollback = markErrors ? `if (_ctx._errors && _ctx._errors.length !== ${markErrors}) _ctx._errors.length = ${markErrors}` : "";
7946
+ const rollback = [captureRollback, errorRollback].filter(Boolean).join("; ");
7371
7947
  const failSlot = atStart ? staticFx : "_ctx._fx";
7372
7948
  stmts.push(`${ind0}if (${skipCond}) {`);
7373
7949
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -7382,7 +7958,9 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7382
7958
  ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
7383
7959
  );
7384
7960
  }
7385
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
7961
+ if (markErrors) stmts.push(`${ind(ctx)}const ${markErrors} = _ctx._errors?.length ?? 0`);
7962
+ const arm = emitFallible(p, ctx, pos, atStart);
7963
+ const { stmts: armStmts, okVar, valVar, endVar } = arm;
7386
7964
  stmts.push(...armStmts);
7387
7965
  if (autoNot && autoNot.length > 0) {
7388
7966
  const anCode = v(ctx, "_anc");
@@ -7406,10 +7984,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
7406
7984
  stmts.push(`${ind(ctx)}}`);
7407
7985
  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} });`} }`);
7408
7986
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
7987
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7409
7988
  } else {
7410
7989
  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} }) }`;
7411
7990
  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} })`} }`);
7412
7991
  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} })`} }`);
7992
+ if (arm.mayCommit) stmts.push(`${ind(ctx)}if (!${resOkV} && _ctx._fc) ${committedFailBody(ctx)}`);
7413
7993
  }
7414
7994
  ctx.indent -= fsGuard ? 2 : 1;
7415
7995
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -7566,7 +8146,8 @@ function emitMany(def, ctx, pos) {
7566
8146
  `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
7567
8147
  );
7568
8148
  }
7569
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
8149
+ const iter = emitFallible(def.parser, ctx, itemPos, true);
8150
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = iter;
7570
8151
  stmts.push(...iterStmts);
7571
8152
  if (ctx.recovery) {
7572
8153
  const rrV = v(ctx, "_rr");
@@ -7585,7 +8166,8 @@ function emitMany(def, ctx, pos) {
7585
8166
  `${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`
7586
8167
  );
7587
8168
  } else {
7588
- stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
8169
+ stmts.push(`${ind(ctx)}if (!${iterOk}) { ${rollback}${iter.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
8170
+ stmts.push(`${ind(ctx)}if (${iterEnd} <= ${itemPos}) { ${rollback}break }`);
7589
8171
  }
7590
8172
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
7591
8173
  else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
@@ -7597,10 +8179,12 @@ function emitMany(def, ctx, pos) {
7597
8179
  function emitOptional(def, ctx, pos) {
7598
8180
  const valV = v(ctx, "_opt");
7599
8181
  const endV = v(ctx, "_opte");
7600
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
8182
+ const inner = emitFallible(def.parser, ctx, pos, true);
8183
+ const { stmts: lblStmts, okVar, valVar, endVar } = inner;
7601
8184
  const ind0 = ind(ctx);
7602
8185
  const stmts = [
7603
8186
  ...lblStmts,
8187
+ ...inner.mayCommit ? [`${ind0}if (!${okVar} && _ctx._fc) ${committedFailBody(ctx)}`] : [],
7604
8188
  `${ind0}const ${valV} = ${okVar} ? ${valVar} : null`,
7605
8189
  `${ind0}const ${endV} = ${okVar} ? ${endVar} : ${pos}`
7606
8190
  ];
@@ -7632,7 +8216,7 @@ function emitAttempt(p, def, ctx, pos) {
7632
8216
  ...preGuard,
7633
8217
  `${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`,
7634
8218
  ...inner.stmts,
7635
- ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
8219
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback}${inner.mayCommit ? ` if (_ctx._fc) ${committedFailBody(ctx)};` : ""} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
7636
8220
  ],
7637
8221
  valueVar: inner.valVar,
7638
8222
  endVar: inner.endVar
@@ -7646,18 +8230,21 @@ function emitSepBy(_p, def, ctx, pos) {
7646
8230
  const exp = rec ? hoistExpected(ctx, deriveExpectedArr([def.parser])) : "";
7647
8231
  const sepSent = rec ? syncSentinelExpr(firstSetOf(def.separator)) : null;
7648
8232
  const scanSync = sepSent ? `_ctx._rec.or(${sepSent}, ${mySyncV})` : mySyncV;
7649
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
8233
+ const first = emitFallible(def.parser, ctx, pos, true);
8234
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = first;
7650
8235
  const stmts = [
7651
8236
  `${ind(ctx)}const ${arrV} = []`,
7652
8237
  `${ind(ctx)}let ${curV} = ${pos}`,
7653
8238
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
7654
8239
  ...firstStmts
7655
8240
  ];
7656
- const failItem = (nextOkVar, itemPosVar, breakStmt) => {
7657
- if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
8241
+ const failItem = (nextOkVar, nextMayCommit, itemPosVar, breakStmt) => {
8242
+ const commit = nextMayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : "";
8243
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) { ${commit}${breakStmt} }`];
7658
8244
  const rr = v(ctx, "_rr");
7659
8245
  return [
7660
8246
  `${ind(ctx)}if (!${nextOkVar}) {`,
8247
+ ...nextMayCommit ? [`${ind(ctx)} if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7661
8248
  `${ind(ctx)} if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPosVar}, _ctx)) {`,
7662
8249
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
7663
8250
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
@@ -7686,6 +8273,7 @@ function emitSepBy(_p, def, ctx, pos) {
7686
8273
  stmts.push(
7687
8274
  `${ind(ctx)}let ${didV} = false`,
7688
8275
  `${ind(ctx)}if (${firstOk}) { ${arrV}.push(${firstVal}); ${curV} = ${firstEnd}; ${didV} = true }`,
8276
+ ...first.mayCommit ? [`${ind(ctx)}else if (_ctx._fc) ${committedFailBody(ctx)}`] : [],
7689
8277
  `${ind(ctx)}else if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${pos}, _ctx)) {`,
7690
8278
  `${ind(ctx)} const ${rr0} = _ctx._rec.scan(input, ${pos}, _ctx, ${scanSync}, ${exp})`,
7691
8279
  `${ind(ctx)} ${arrV}.push(${rr0}.error); _ctx._rec.capture(_ctx, ${rr0}.error); ${curV} = ${rr0}.end; ${didV} = true`,
@@ -7696,6 +8284,7 @@ function emitSepBy(_p, def, ctx, pos) {
7696
8284
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
7697
8285
  ctx.indent++;
7698
8286
  } else {
8287
+ if (first.mayCommit) stmts.push(`${ind(ctx)}if (!${firstOk} && _ctx._fc) ${committedFailBody(ctx)}`);
7699
8288
  stmts.push(`${ind(ctx)}if (${firstOk}) {`);
7700
8289
  ctx.indent++;
7701
8290
  stmts.push(
@@ -7724,16 +8313,18 @@ function emitSepBy(_p, def, ctx, pos) {
7724
8313
  );
7725
8314
  sepAtPos = spV;
7726
8315
  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}; `;
7727
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7728
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
8316
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8317
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8318
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7729
8319
  const post = postSepMarks();
7730
8320
  stmts.push(...post.decl);
7731
8321
  const npV = v(ctx, "_np");
7732
8322
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
7733
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8323
+ const next = emitFallible(def.parser, ctx, npV, true);
8324
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7734
8325
  stmts.push(
7735
8326
  ...nextStmts,
7736
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
8327
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
7737
8328
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7738
8329
  `${ind(ctx)}${curV} = ${nextEnd}`
7739
8330
  );
@@ -7742,14 +8333,16 @@ function emitSepBy(_p, def, ctx, pos) {
7742
8333
  const spV = v(ctx, "_sp");
7743
8334
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
7744
8335
  sepAtPos = spV;
7745
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7746
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8336
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8337
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8338
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7747
8339
  const npV = v(ctx, "_np");
7748
8340
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
7749
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
8341
+ const next = emitFallible(def.parser, ctx, npV, true);
8342
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7750
8343
  stmts.push(
7751
8344
  ...nextStmts,
7752
- ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
8345
+ ...failItem(nextOk, next.mayCommit, npV, itemFailBreak(sepEnd, "", "")),
7753
8346
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7754
8347
  `${ind(ctx)}${curV} = ${nextEnd}`
7755
8348
  );
@@ -7763,15 +8356,17 @@ function emitSepBy(_p, def, ctx, pos) {
7763
8356
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
7764
8357
  );
7765
8358
  }
7766
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
7767
- stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
8359
+ const sep = emitFallible(def.separator, ctx, sepAtPos, true);
8360
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = sep;
8361
+ stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${sep.mayCommit ? `if (_ctx._fc) ${committedFailBody(ctx)}; ` : ""}break }`);
7768
8362
  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}; ` : "";
7769
8363
  const post = postSepMarks();
7770
8364
  stmts.push(...post.decl);
7771
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
8365
+ const next = emitFallible(def.parser, ctx, sepEnd, true);
8366
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = next;
7772
8367
  stmts.push(
7773
8368
  ...nextStmts,
7774
- ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
8369
+ ...failItem(nextOk, next.mayCommit, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
7775
8370
  `${ind(ctx)}${arrV}.push(${nextVal})`,
7776
8371
  `${ind(ctx)}${curV} = ${nextEnd}`
7777
8372
  );
@@ -7872,6 +8467,18 @@ function emitPeek(def, ctx, pos) {
7872
8467
  endVar: pos
7873
8468
  };
7874
8469
  }
8470
+ function emitRouted(ctx, pos) {
8471
+ const item = v(ctx, "_rt");
8472
+ return {
8473
+ stmts: [
8474
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8475
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8476
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8477
+ ],
8478
+ valueVar: `${item}.value`,
8479
+ endVar: `${item}.span.end`
8480
+ };
8481
+ }
7875
8482
  function escapeTokenLiteral(value) {
7876
8483
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
7877
8484
  }
@@ -8152,7 +8759,7 @@ function emitRuntimeFallback(parser2, ctx, pos) {
8152
8759
  const rv = v(ctx, "_rt");
8153
8760
  const vv = v(ctx, "_rtv");
8154
8761
  const ev = v(ctx, "_rte");
8155
- 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 }`;
8762
+ 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 } : {}) }`;
8156
8763
  const stmts = [
8157
8764
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
8158
8765
  ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
@@ -8233,7 +8840,7 @@ function emitRecover(def, ctx, pos) {
8233
8840
  return { stmts, valueVar: valVar, endVar };
8234
8841
  }
8235
8842
  function emitExpect(def, ctx, pos) {
8236
- const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
8843
+ const { stmts: innerStmts, okVar, valVar, endVar, mayCommit } = emitFallible(def.parser, ctx, pos);
8237
8844
  const ind0 = ind(ctx);
8238
8845
  const errV = v(ctx, "_err");
8239
8846
  const stmts = [
@@ -8245,6 +8852,7 @@ function emitExpect(def, ctx, pos) {
8245
8852
  // interpreter (expect.ts) and the list-recovery emit sites — guarded on
8246
8853
  // `_ctx._tolerant`, under which the driver always installs `_ctx._rec`.
8247
8854
  `${ind0} if (_ctx._tolerant) _ctx._rec.capture(_ctx, ${errV})`,
8855
+ ...mayCommit ? [`${ind0} _ctx._fc = false`] : [],
8248
8856
  `${ind0} ${valVar} = ${errV}`,
8249
8857
  `${ind0} ${endVar} = ${pos}`,
8250
8858
  `${ind0} ${okVar} = true`,
@@ -8255,7 +8863,7 @@ function emitExpect(def, ctx, pos) {
8255
8863
  function emit(p, ctx, pos) {
8256
8864
  const replay = ctx.replayPrefix?.get(p);
8257
8865
  if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
8258
- const dispatch = (emissionPos = pos) => {
8866
+ const dispatch2 = (emissionPos = pos) => {
8259
8867
  const savedRule = ctx.activeCoverageRuleId;
8260
8868
  const rule = ctx.coverage?.plan.rules.get(p);
8261
8869
  if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
@@ -8295,14 +8903,14 @@ function emit(p, ctx, pos) {
8295
8903
  ctx.indent = 1;
8296
8904
  ctx.failLabel = "_pfail";
8297
8905
  ctx.recordFail = true;
8298
- const r = dispatch("_pos");
8906
+ const r = dispatch2("_pos");
8299
8907
  ctx.indent = savedIndent;
8300
8908
  ctx.failLabel = savedFailLabel;
8301
8909
  ctx.recordFail = savedRecord;
8302
8910
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
8303
8911
  return instrument(emitNamedFnCall(ctx, fnName, pos));
8304
8912
  }
8305
- return instrument(dispatch());
8913
+ return instrument(dispatch2());
8306
8914
  }
8307
8915
  function emitDispatch(p, ctx, pos) {
8308
8916
  const bal = p._balancedAmbient;
@@ -8335,6 +8943,8 @@ function emitDispatch(p, ctx, pos) {
8335
8943
  return emitSeq(def, ctx, pos);
8336
8944
  case "choice":
8337
8945
  return emitChoice(p, def, ctx, pos);
8946
+ case "dispatch":
8947
+ return emitDispatchCombinator(p, def, ctx, pos);
8338
8948
  case "attempt":
8339
8949
  return emitAttempt(p, def, ctx, pos);
8340
8950
  case "many":
@@ -8459,6 +9069,8 @@ function emitDispatch(p, ctx, pos) {
8459
9069
  return emitNot(def, ctx, pos);
8460
9070
  case "peek":
8461
9071
  return emitPeek(def, ctx, pos);
9072
+ case "routed":
9073
+ return emitRouted(ctx, pos);
8462
9074
  case "node":
8463
9075
  return emitNode(def, ctx, pos);
8464
9076
  case "scanTo":
@@ -8555,6 +9167,8 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8555
9167
  case "sequence":
8556
9168
  case "choice":
8557
9169
  return d.parsers.some((x) => hasNodeDef(x, seen));
9170
+ case "dispatch":
9171
+ 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);
8558
9172
  case "sepBy":
8559
9173
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8560
9174
  case "scanTo":
@@ -8569,6 +9183,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8569
9183
  return false;
8570
9184
  }
8571
9185
  }
9186
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9187
+ if (seen.has(p)) return false;
9188
+ seen.add(p);
9189
+ const d = p._def;
9190
+ switch (d.tag) {
9191
+ case "routed":
9192
+ return true;
9193
+ case "lazy": {
9194
+ try {
9195
+ return parserUsesRouted2(d.thunk(), seen);
9196
+ } catch {
9197
+ return false;
9198
+ }
9199
+ }
9200
+ case "dispatch":
9201
+ return false;
9202
+ default:
9203
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9204
+ }
9205
+ }
8572
9206
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8573
9207
  if (seen.has(p)) return false;
8574
9208
  seen.add(p);
@@ -8588,6 +9222,8 @@ function childrenOf2(def) {
8588
9222
  case "sequence":
8589
9223
  case "choice":
8590
9224
  return def.parsers;
9225
+ case "dispatch":
9226
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8591
9227
  case "many":
8592
9228
  case "attempt":
8593
9229
  case "optional":
@@ -8624,6 +9260,7 @@ function childrenOf2(def) {
8624
9260
  case "regex":
8625
9261
  case "keywords":
8626
9262
  case "guard":
9263
+ case "routed":
8627
9264
  case "unknown":
8628
9265
  return [];
8629
9266
  }
@@ -8631,6 +9268,7 @@ function childrenOf2(def) {
8631
9268
  var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
8632
9269
  "sequence",
8633
9270
  "choice",
9271
+ "dispatch",
8634
9272
  "many",
8635
9273
  "oneOrMore",
8636
9274
  "optional",
@@ -9329,6 +9967,8 @@ function childrenOf3(def) {
9329
9967
  case "sequence":
9330
9968
  case "choice":
9331
9969
  return def.parsers;
9970
+ case "dispatch":
9971
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9332
9972
  case "many":
9333
9973
  case "oneOrMore":
9334
9974
  case "optional":
@@ -9357,6 +9997,7 @@ function childrenOf3(def) {
9357
9997
  case "literal":
9358
9998
  case "regex":
9359
9999
  case "keywords":
10000
+ case "routed":
9360
10001
  case "guard":
9361
10002
  case "recover":
9362
10003
  case "unknown":
@@ -9381,6 +10022,16 @@ function repeatOpts(min, max, trailing) {
9381
10022
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9382
10023
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9383
10024
  }
10025
+ function matcherExpr(entry) {
10026
+ switch (entry.kind) {
10027
+ case "startsWith":
10028
+ return `startsWith(${JSON.stringify(entry.value)})`;
10029
+ case "endsWith":
10030
+ return `endsWith(${JSON.stringify(entry.value)})`;
10031
+ case "matches":
10032
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
10033
+ }
10034
+ }
9384
10035
  function serializeRuleMap(ruleMap, scanSkip) {
9385
10036
  try {
9386
10037
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9478,6 +10129,13 @@ function evalRuleMapIR(ir) {
9478
10129
  "keywords",
9479
10130
  "sequence",
9480
10131
  "choice",
10132
+ "dispatch",
10133
+ "when",
10134
+ "startsWith",
10135
+ "endsWith",
10136
+ "matches",
10137
+ "otherwise",
10138
+ "routed",
9481
10139
  "attempt",
9482
10140
  "many",
9483
10141
  "oneOrMore",
@@ -9511,6 +10169,13 @@ function evalRuleMapIR(ir) {
9511
10169
  keywords,
9512
10170
  sequence,
9513
10171
  choice,
10172
+ dispatch,
10173
+ when,
10174
+ startsWith,
10175
+ endsWith,
10176
+ matches,
10177
+ otherwise,
10178
+ routed,
9514
10179
  attempt,
9515
10180
  many,
9516
10181
  oneOrMore,
@@ -9696,6 +10361,18 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9696
10361
  });
9697
10362
  return `_gch([${items.join(", ")}])`;
9698
10363
  }
10364
+ case "dispatch": {
10365
+ const arms = def.cases.map(
10366
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10367
+ );
10368
+ if (def.matchers) {
10369
+ for (const entry of def.matchers) {
10370
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10371
+ }
10372
+ }
10373
+ if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10374
+ return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10375
+ }
9699
10376
  case "many":
9700
10377
  return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
9701
10378
  // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
@@ -9712,6 +10389,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9712
10389
  return `not(${kid(def.parser)})`;
9713
10390
  case "peek":
9714
10391
  return `peek(${kid(def.parser)})`;
10392
+ case "routed":
10393
+ return "routed()";
9715
10394
  case "trivia":
9716
10395
  return `trivia(${kid(def.parser)})`;
9717
10396
  case "token":
@@ -10733,10 +11412,20 @@ function run(entry, input, options = {}) {
10733
11412
  }
10734
11413
 
10735
11414
  // src/coverage.ts
11415
+ function dispatchMatcher(entry) {
11416
+ switch (entry.kind) {
11417
+ case "startsWith":
11418
+ return startsWith(entry.value);
11419
+ case "endsWith":
11420
+ return endsWith(entry.value);
11421
+ case "matches":
11422
+ return matches(new RegExp(entry.value, entry.flags));
11423
+ }
11424
+ }
10736
11425
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
10737
11426
  function compiledGrammarCoverageDefinitions(grammar) {
10738
11427
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
10739
- 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"))) {
11428
+ 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"))) {
10740
11429
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
10741
11430
  }
10742
11431
  return definitions;
@@ -10915,6 +11604,56 @@ function coverageEntry(entry, collector, maps, trace) {
10915
11604
  const unsafeSequence = sequence;
10916
11605
  return unsafeSequence(...def.parsers.map(build));
10917
11606
  }
11607
+ case "dispatch": {
11608
+ const ids = maps.dispatches.get(parser2) ?? [];
11609
+ let armIndex = 0;
11610
+ const dispatchStarts = [];
11611
+ const wrapArm = (arm, id) => {
11612
+ const child = build(arm);
11613
+ return {
11614
+ _tag: child._tag,
11615
+ _meta: child._meta,
11616
+ _def: child._def,
11617
+ parse(input, pos, ctx) {
11618
+ const dispatchStart = dispatchStarts[dispatchStarts.length - 1] ?? pos;
11619
+ if (id) trace?.write({ id, phase: "attempt", offset: dispatchStart });
11620
+ const result = child.parse(input, pos, ctx);
11621
+ if (result.ok) {
11622
+ if (id) {
11623
+ collector.hit(id);
11624
+ trace?.write({ id, phase: "selected", offset: dispatchStart, end: result.span.end });
11625
+ trace?.write({ id, phase: "success", offset: dispatchStart, end: result.span.end });
11626
+ }
11627
+ } else if (id) {
11628
+ trace?.write({ id, phase: "failure", offset: result.span.end });
11629
+ }
11630
+ return result;
11631
+ }
11632
+ };
11633
+ };
11634
+ const arms = def.cases.map(
11635
+ (entry2) => when(entry2.keys, wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0)
11636
+ );
11637
+ if (def.matchers) {
11638
+ for (const entry2 of def.matchers) {
11639
+ arms.push(when(dispatchMatcher(entry2), wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0));
11640
+ }
11641
+ }
11642
+ if (def.otherwise) arms.push(otherwise(wrapArm(def.otherwise, ids[armIndex++])));
11643
+ const unsafeDispatch = dispatch;
11644
+ const base2 = unsafeDispatch(build(def.selector), ...arms);
11645
+ return {
11646
+ ...base2,
11647
+ parse(input, pos, ctx) {
11648
+ dispatchStarts.push(pos);
11649
+ try {
11650
+ return base2.parse(input, pos, ctx);
11651
+ } finally {
11652
+ dispatchStarts.pop();
11653
+ }
11654
+ }
11655
+ };
11656
+ }
10918
11657
  case "many":
10919
11658
  return many(build(def.parser));
10920
11659
  case "oneOrMore":