parseman 0.39.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.
package/dist/index.cjs CHANGED
@@ -57,6 +57,7 @@ __export(index_exports, {
57
57
  cstBuildHost: () => cstBuildHost,
58
58
  dispatch: () => dispatch,
59
59
  duplicationFindingCount: () => duplicationFindingCount,
60
+ endsWith: () => endsWith,
60
61
  expect: () => expect,
61
62
  extractCharClasses: () => extractCharClasses,
62
63
  field: () => field,
@@ -79,8 +80,10 @@ __export(index_exports, {
79
80
  lineBreaksIn: () => lineBreaksIn,
80
81
  lineStartWithin: () => lineStartWithin,
81
82
  literal: () => literal,
83
+ makeWhen: () => makeWhen,
82
84
  makeWord: () => makeWord,
83
85
  many: () => many,
86
+ matches: () => matches,
84
87
  noTrivia: () => noTrivia,
85
88
  node: () => node,
86
89
  not: () => not,
@@ -97,6 +100,7 @@ __export(index_exports, {
97
100
  regex: () => regex,
98
101
  relativize: () => relativize,
99
102
  relativizeCST: () => relativizeCST,
103
+ routed: () => routed,
100
104
  rules: () => rules,
101
105
  run: () => run,
102
106
  runWithGrammarCoverage: () => runWithGrammarCoverage,
@@ -106,6 +110,7 @@ __export(index_exports, {
106
110
  shiftAbsolute: () => shiftAbsolute,
107
111
  siteToString: () => siteToString,
108
112
  skip: () => skip,
113
+ startsWith: () => startsWith,
109
114
  token: () => token,
110
115
  transform: () => transform,
111
116
  trivia: () => trivia,
@@ -171,6 +176,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
171
176
  return d.value.length === 0;
172
177
  case "keywords":
173
178
  return false;
179
+ case "routed":
180
+ return false;
174
181
  case "regex":
175
182
  try {
176
183
  const m = new RegExp(d.source).exec("");
@@ -1232,6 +1239,8 @@ function derive(c, seen) {
1232
1239
  case "not":
1233
1240
  case "peek":
1234
1241
  return deriveExpected2(def.parser);
1242
+ case "routed":
1243
+ return ["routed()"];
1235
1244
  case "lazy": {
1236
1245
  const name = c._ruleName;
1237
1246
  if (seen.has(c)) return name ? [name] : [];
@@ -2056,42 +2065,198 @@ function sequence(...parsers) {
2056
2065
  }
2057
2066
 
2058
2067
  // src/combinators/dispatch.ts
2059
- function when(key, parser2) {
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
+ }
2060
2169
  const keys = Array.isArray(key) ? [...key] : [key];
2061
2170
  if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2062
2171
  for (const item of keys) {
2063
2172
  if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2064
2173
  }
2065
- return { kind: "when", keys, parser: parser2 };
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);
2066
2178
  }
2067
2179
  function otherwise(parser2) {
2068
- return { kind: "otherwise", parser: 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
+ };
2069
2201
  }
2070
2202
  function dispatch(selector, ...arms) {
2203
+ if (parserUsesRouted(selector)) {
2204
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2205
+ }
2071
2206
  let fallback;
2207
+ let fallbackUsesRouted = false;
2208
+ const matchers = [];
2072
2209
  const cases = [];
2073
- const seen = /* @__PURE__ */ new Set();
2210
+ const seen = [];
2074
2211
  for (let i = 0; i < arms.length; i++) {
2075
2212
  const arm = arms[i];
2076
2213
  if (arm.kind === "otherwise") {
2077
2214
  if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2078
2215
  if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2079
2216
  fallback = arm.parser;
2217
+ fallbackUsesRouted = arm.usesRouted === true;
2080
2218
  continue;
2081
2219
  }
2082
- for (const key of arm.keys) {
2083
- if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2084
- seen.add(key);
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;
2085
2231
  }
2086
- cases.push({ keys: arm.keys, parser: arm.parser });
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
+ });
2087
2246
  }
2088
2247
  const meta = {
2089
2248
  firstSet: selector._meta.firstSet,
2090
- canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2249
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2091
2250
  isTrivia: false
2092
2251
  };
2093
2252
  const byKey = /* @__PURE__ */ new Map();
2094
- for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
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
+ }
2095
2260
  return {
2096
2261
  _tag: "dispatch",
2097
2262
  _meta: meta,
@@ -2099,18 +2264,35 @@ function dispatch(selector, ...arms) {
2099
2264
  tag: "dispatch",
2100
2265
  selector,
2101
2266
  cases,
2102
- ...fallback === void 0 ? {} : { otherwise: fallback }
2267
+ ...matchers.length === 0 ? {} : { matchers },
2268
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2103
2269
  },
2104
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);
2105
2275
  const selected = selector.parse(input, pos, ctx);
2106
2276
  if (!selected.ok) return selected;
2107
- const tail = byKey.get(selected.value) ?? fallback;
2108
- if (tail === void 0) {
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) {
2109
2279
  const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2110
2280
  return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2111
2281
  }
2112
- const mark = saveTriviaMark(ctx);
2113
- const result = tail.parse(input, selected.span.end, ctx);
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
+ }
2114
2296
  if (!result.ok) {
2115
2297
  rollbackTrivia(ctx, mark);
2116
2298
  return { ...result, committed: true };
@@ -2497,6 +2679,7 @@ function markUnusedValues(root) {
2497
2679
  case "dispatch":
2498
2680
  visit(def.selector, consumed);
2499
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);
2500
2683
  if (def.otherwise) visit(def.otherwise, consumed);
2501
2684
  return;
2502
2685
  case "node":
@@ -2775,7 +2958,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2775
2958
  case "choice":
2776
2959
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2777
2960
  case "dispatch":
2778
- return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
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);
2779
2962
  case "sepBy":
2780
2963
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2781
2964
  case "skip":
@@ -2844,7 +3027,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2844
3027
  case "choice":
2845
3028
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2846
3029
  case "dispatch":
2847
- return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
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);
2848
3031
  case "grammar":
2849
3032
  return parserHasTriviaSite(d.parser, seen);
2850
3033
  case "lazy": {
@@ -2871,7 +3054,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2871
3054
  case "choice":
2872
3055
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2873
3056
  case "dispatch":
2874
- return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
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);
2875
3058
  case "sepBy":
2876
3059
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2877
3060
  case "skip":
@@ -3273,7 +3456,7 @@ function leaf(root, fn) {
3273
3456
  }
3274
3457
 
3275
3458
  // src/version.ts
3276
- var PARSEMAN_VERSION = "0.39.0";
3459
+ var PARSEMAN_VERSION = "0.39.1";
3277
3460
 
3278
3461
  // src/cst/host-mode.ts
3279
3462
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3533,6 +3716,7 @@ function analyzeGatingRules(ruleMap, opts) {
3533
3716
  if (d.tag === "dispatch") {
3534
3717
  kids.push(d.selector);
3535
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);
3536
3720
  if (d.otherwise !== void 0) kids.push(d.otherwise);
3537
3721
  }
3538
3722
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
@@ -5056,13 +5240,25 @@ function escapeClassChar(ch2) {
5056
5240
  }
5057
5241
 
5058
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
+ }
5059
5255
  function children(def, winners) {
5060
5256
  switch (def.tag) {
5061
5257
  case "sequence":
5062
5258
  case "choice":
5063
5259
  return def.parsers;
5064
5260
  case "dispatch":
5065
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
5261
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
5066
5262
  case "many":
5067
5263
  case "oneOrMore":
5068
5264
  case "optional":
@@ -5105,6 +5301,7 @@ function children(def, winners) {
5105
5301
  function buildGrammarPlan(entry, winners) {
5106
5302
  const definitions = /* @__PURE__ */ new Map();
5107
5303
  const choices = /* @__PURE__ */ new WeakMap();
5304
+ const dispatches = /* @__PURE__ */ new WeakMap();
5108
5305
  const attempts = /* @__PURE__ */ new WeakMap();
5109
5306
  const labels = /* @__PURE__ */ new WeakMap();
5110
5307
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -5135,6 +5332,11 @@ function buildGrammarPlan(entry, winners) {
5135
5332
  choices.set(parser2, ids);
5136
5333
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5137
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
+ }
5138
5340
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path}`);
5139
5341
  if (parser2._def.tag === "label") {
5140
5342
  const id = `label:${path}`;
@@ -5147,7 +5349,7 @@ function buildGrammarPlan(entry, winners) {
5147
5349
  for (const root of roots) {
5148
5350
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
5149
5351
  }
5150
- 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 };
5151
5353
  }
5152
5354
 
5153
5355
  // src/compiler/scannable-run.ts
@@ -6521,6 +6723,7 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6521
6723
  case "literal":
6522
6724
  case "regex":
6523
6725
  case "keywords":
6726
+ case "routed":
6524
6727
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6525
6728
  case "choice": {
6526
6729
  const alts = [];
@@ -6871,7 +7074,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6871
7074
  case "choice":
6872
7075
  return false;
6873
7076
  case "dispatch":
6874
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
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);
6875
7078
  case "attempt":
6876
7079
  return false;
6877
7080
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6970,6 +7173,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6970
7173
  case "keywords":
6971
7174
  case "node":
6972
7175
  case "token":
7176
+ case "routed":
6973
7177
  case "leaf":
6974
7178
  return true;
6975
7179
  case "not":
@@ -6982,7 +7186,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6982
7186
  case "choice":
6983
7187
  return d.parsers.some((x) => capturesLeaf(x, seen));
6984
7188
  case "dispatch":
6985
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
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);
6986
7190
  case "sepBy":
6987
7191
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6988
7192
  case "many":
@@ -7460,6 +7664,7 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7460
7664
  for (const item of d.cases) {
7461
7665
  if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7462
7666
  }
7667
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7463
7668
  return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7464
7669
  }
7465
7670
  case "attempt":
@@ -7494,53 +7699,120 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7494
7699
  return false;
7495
7700
  }
7496
7701
  }
7497
- function emitDispatchCombinator(def, ctx, pos) {
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;
7498
7715
  const selector = emit(def.selector, ctx, pos);
7499
7716
  const outV = v(ctx, "_dval");
7500
7717
  const outE = v(ctx, "_dend");
7501
7718
  const keyV = v(ctx, "_dkey");
7502
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
+ ] : [],
7503
7728
  ...selector.stmts,
7504
7729
  `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7505
7730
  `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7506
7731
  ];
7507
7732
  let wroteBranch = false;
7508
- const emitSelectedTail = (parser2, keyword, condition) => {
7733
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7734
+ const parser3 = entry.parser;
7509
7735
  const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7510
7736
  stmts.push(head);
7511
7737
  ctx.indent++;
7512
- const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7738
+ const usesRouted = branchUsesRouted2(entry);
7739
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7740
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7513
7741
  const errMark = mayError ? v(ctx, "_derr") : null;
7514
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
+ }
7515
7751
  if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7516
7752
  if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7517
- const tail = emitTailFallible(parser2, ctx, selector.endVar);
7753
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7754
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7518
7755
  const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7519
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 }); `;
7520
7759
  stmts.push(
7521
7760
  ...tail.stmts,
7522
- `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7761
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7762
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7523
7763
  `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7524
- `${ind(ctx)}${outE} = ${tail.endVar}`
7764
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7765
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7525
7766
  );
7526
7767
  ctx.indent--;
7527
7768
  stmts.push(`${ind(ctx)}}`);
7528
7769
  };
7770
+ let coverageIndex = 0;
7529
7771
  for (const entry of def.cases) {
7530
- const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7531
- emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7772
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7773
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7532
7774
  wroteBranch = true;
7533
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
+ };
7534
7782
  if (def.otherwise) {
7535
- if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7536
- else emitSelectedTail(def.otherwise, "if", "true");
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++]);
7537
7787
  } else {
7788
+ emitMatcherTails();
7538
7789
  const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7539
7790
  const fail = failArrBody(ctx, expected, selector.endVar);
7540
7791
  stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7541
7792
  }
7542
7793
  return { stmts, valueVar: outV, endVar: outE };
7543
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
+ }
7544
7816
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7545
7817
  const superParser = def.parsers[superIndex];
7546
7818
  const regexDef = getCoreRegexDef(superParser);
@@ -8195,6 +8467,18 @@ function emitPeek(def, ctx, pos) {
8195
8467
  endVar: pos
8196
8468
  };
8197
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
+ }
8198
8482
  function escapeTokenLiteral(value) {
8199
8483
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8200
8484
  }
@@ -8660,7 +8944,7 @@ function emitDispatch(p, ctx, pos) {
8660
8944
  case "choice":
8661
8945
  return emitChoice(p, def, ctx, pos);
8662
8946
  case "dispatch":
8663
- return emitDispatchCombinator(def, ctx, pos);
8947
+ return emitDispatchCombinator(p, def, ctx, pos);
8664
8948
  case "attempt":
8665
8949
  return emitAttempt(p, def, ctx, pos);
8666
8950
  case "many":
@@ -8785,6 +9069,8 @@ function emitDispatch(p, ctx, pos) {
8785
9069
  return emitNot(def, ctx, pos);
8786
9070
  case "peek":
8787
9071
  return emitPeek(def, ctx, pos);
9072
+ case "routed":
9073
+ return emitRouted(ctx, pos);
8788
9074
  case "node":
8789
9075
  return emitNode(def, ctx, pos);
8790
9076
  case "scanTo":
@@ -8882,7 +9168,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8882
9168
  case "choice":
8883
9169
  return d.parsers.some((x) => hasNodeDef(x, seen));
8884
9170
  case "dispatch":
8885
- return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
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);
8886
9172
  case "sepBy":
8887
9173
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8888
9174
  case "scanTo":
@@ -8897,6 +9183,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8897
9183
  return false;
8898
9184
  }
8899
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
+ }
8900
9206
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8901
9207
  if (seen.has(p)) return false;
8902
9208
  seen.add(p);
@@ -8917,7 +9223,7 @@ function childrenOf2(def) {
8917
9223
  case "choice":
8918
9224
  return def.parsers;
8919
9225
  case "dispatch":
8920
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9226
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8921
9227
  case "many":
8922
9228
  case "attempt":
8923
9229
  case "optional":
@@ -8954,6 +9260,7 @@ function childrenOf2(def) {
8954
9260
  case "regex":
8955
9261
  case "keywords":
8956
9262
  case "guard":
9263
+ case "routed":
8957
9264
  case "unknown":
8958
9265
  return [];
8959
9266
  }
@@ -9661,7 +9968,7 @@ function childrenOf3(def) {
9661
9968
  case "choice":
9662
9969
  return def.parsers;
9663
9970
  case "dispatch":
9664
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9971
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9665
9972
  case "many":
9666
9973
  case "oneOrMore":
9667
9974
  case "optional":
@@ -9690,6 +9997,7 @@ function childrenOf3(def) {
9690
9997
  case "literal":
9691
9998
  case "regex":
9692
9999
  case "keywords":
10000
+ case "routed":
9693
10001
  case "guard":
9694
10002
  case "recover":
9695
10003
  case "unknown":
@@ -9714,6 +10022,16 @@ function repeatOpts(min, max, trailing) {
9714
10022
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9715
10023
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9716
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
+ }
9717
10035
  function serializeRuleMap(ruleMap, scanSkip) {
9718
10036
  try {
9719
10037
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9813,7 +10131,11 @@ function evalRuleMapIR(ir) {
9813
10131
  "choice",
9814
10132
  "dispatch",
9815
10133
  "when",
10134
+ "startsWith",
10135
+ "endsWith",
10136
+ "matches",
9816
10137
  "otherwise",
10138
+ "routed",
9817
10139
  "attempt",
9818
10140
  "many",
9819
10141
  "oneOrMore",
@@ -9849,7 +10171,11 @@ function evalRuleMapIR(ir) {
9849
10171
  choice,
9850
10172
  dispatch,
9851
10173
  when,
10174
+ startsWith,
10175
+ endsWith,
10176
+ matches,
9852
10177
  otherwise,
10178
+ routed,
9853
10179
  attempt,
9854
10180
  many,
9855
10181
  oneOrMore,
@@ -10037,8 +10363,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10037
10363
  }
10038
10364
  case "dispatch": {
10039
10365
  const arms = def.cases.map(
10040
- (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10366
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10041
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
+ }
10042
10373
  if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10043
10374
  return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10044
10375
  }
@@ -10058,6 +10389,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10058
10389
  return `not(${kid(def.parser)})`;
10059
10390
  case "peek":
10060
10391
  return `peek(${kid(def.parser)})`;
10392
+ case "routed":
10393
+ return "routed()";
10061
10394
  case "trivia":
10062
10395
  return `trivia(${kid(def.parser)})`;
10063
10396
  case "token":
@@ -11079,10 +11412,20 @@ function run(entry, input, options = {}) {
11079
11412
  }
11080
11413
 
11081
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
+ }
11082
11425
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
11083
11426
  function compiledGrammarCoverageDefinitions(grammar) {
11084
11427
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
11085
- 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"))) {
11086
11429
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
11087
11430
  }
11088
11431
  return definitions;
@@ -11262,10 +11605,54 @@ function coverageEntry(entry, collector, maps, trace) {
11262
11605
  return unsafeSequence(...def.parsers.map(build));
11263
11606
  }
11264
11607
  case "dispatch": {
11265
- const arms = def.cases.map((entry2) => when(entry2.keys, build(entry2.parser)));
11266
- if (def.otherwise) arms.push(otherwise(build(def.otherwise)));
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++])));
11267
11643
  const unsafeDispatch = dispatch;
11268
- return unsafeDispatch(build(def.selector), ...arms);
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
+ };
11269
11656
  }
11270
11657
  case "many":
11271
11658
  return many(build(def.parser));