parseman 0.39.0 → 0.40.0

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.
@@ -94,6 +94,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
94
94
  return d.value.length === 0;
95
95
  case "keywords":
96
96
  return false;
97
+ case "routed":
98
+ return false;
97
99
  case "regex":
98
100
  try {
99
101
  const m = new RegExp(d.source).exec("");
@@ -1186,6 +1188,8 @@ function derive(c, seen) {
1186
1188
  case "not":
1187
1189
  case "peek":
1188
1190
  return deriveExpected2(def.parser);
1191
+ case "routed":
1192
+ return ["routed()"];
1189
1193
  case "lazy": {
1190
1194
  const name = c._ruleName;
1191
1195
  if (seen.has(c)) return name ? [name] : [];
@@ -2007,42 +2011,195 @@ function sequence(...parsers) {
2007
2011
  }
2008
2012
 
2009
2013
  // src/combinators/dispatch.ts
2010
- function when(key, parser2) {
2014
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
2015
+ if (seen.has(parser2)) return false;
2016
+ seen.add(parser2);
2017
+ const def = parser2._def;
2018
+ switch (def.tag) {
2019
+ case "routed":
2020
+ return true;
2021
+ case "sequence":
2022
+ case "choice":
2023
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
2024
+ case "dispatch":
2025
+ return false;
2026
+ case "sepBy":
2027
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
2028
+ case "skip":
2029
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
2030
+ case "grammar":
2031
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
2032
+ case "scanTo":
2033
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
2034
+ case "recover":
2035
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
2036
+ case "lazy":
2037
+ try {
2038
+ return parserUsesRouted(def.thunk(), seen);
2039
+ } catch {
2040
+ return false;
2041
+ }
2042
+ case "many":
2043
+ case "oneOrMore":
2044
+ case "optional":
2045
+ case "attempt":
2046
+ case "transform":
2047
+ case "trivia":
2048
+ case "token":
2049
+ case "leaf":
2050
+ case "label":
2051
+ case "field":
2052
+ case "not":
2053
+ case "peek":
2054
+ case "node":
2055
+ case "withCtx":
2056
+ case "expect":
2057
+ return parserUsesRouted(def.parser, seen);
2058
+ default:
2059
+ return false;
2060
+ }
2061
+ }
2062
+ function branchUsesRouted(branch) {
2063
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
2064
+ }
2065
+ function asciiFoldKey(key) {
2066
+ let out = "";
2067
+ for (let i = 0; i < key.length; i++) {
2068
+ const c = key.charCodeAt(i);
2069
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
2070
+ }
2071
+ return out;
2072
+ }
2073
+ function matchesDispatchMatcher(value, matcher) {
2074
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2075
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2076
+ switch (matcher.kind) {
2077
+ case "startsWith":
2078
+ return candidate.startsWith(expected);
2079
+ case "endsWith":
2080
+ return candidate.endsWith(expected);
2081
+ case "matches": {
2082
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2083
+ return new RegExp(matcher.value, flags).test(value);
2084
+ }
2085
+ }
2086
+ }
2087
+ function isStringMatcher(value) {
2088
+ if (typeof value !== "object" || value === null) return false;
2089
+ const kind = value.kind;
2090
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2091
+ }
2092
+ function fixedMatcher(kind, value) {
2093
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2094
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2095
+ return { kind, value };
2096
+ }
2097
+ function startsWith(prefix) {
2098
+ return fixedMatcher("startsWith", prefix);
2099
+ }
2100
+ function endsWith(suffix) {
2101
+ return fixedMatcher("endsWith", suffix);
2102
+ }
2103
+ function matches(pattern) {
2104
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2105
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2106
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2107
+ }
2108
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2109
+ }
2110
+ function when(key, parser2, opts = {}) {
2111
+ const usesRouted = parserUsesRouted(parser2);
2112
+ if (isStringMatcher(key)) {
2113
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2114
+ }
2011
2115
  const keys = Array.isArray(key) ? [...key] : [key];
2012
2116
  if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2013
2117
  for (const item of keys) {
2014
2118
  if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2015
2119
  }
2016
- return { kind: "when", keys, parser: parser2 };
2120
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2017
2121
  }
2018
2122
  function otherwise(parser2) {
2019
- return { kind: "otherwise", parser: parser2 };
2123
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2124
+ }
2125
+ function routed() {
2126
+ return {
2127
+ _tag: "routed",
2128
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2129
+ _def: { tag: "routed" },
2130
+ parse(_input, pos, ctx) {
2131
+ const item = ctx._routed;
2132
+ if (item === void 0) {
2133
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2134
+ }
2135
+ if (pos !== item.span.start) {
2136
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2137
+ }
2138
+ if (cstCaptureActive(ctx)) {
2139
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2140
+ }
2141
+ return { ok: true, value: item.value, span: item.span };
2142
+ }
2143
+ };
2020
2144
  }
2021
2145
  function dispatch(selector, ...arms) {
2146
+ if (parserUsesRouted(selector)) {
2147
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2148
+ }
2022
2149
  let fallback;
2150
+ let fallbackUsesRouted = false;
2151
+ const matchers = [];
2023
2152
  const cases = [];
2024
- const seen = /* @__PURE__ */ new Set();
2153
+ const seen = [];
2025
2154
  for (let i = 0; i < arms.length; i++) {
2026
2155
  const arm = arms[i];
2027
2156
  if (arm.kind === "otherwise") {
2028
2157
  if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2029
2158
  if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2030
2159
  fallback = arm.parser;
2160
+ fallbackUsesRouted = arm.usesRouted === true;
2031
2161
  continue;
2032
2162
  }
2033
- for (const key of arm.keys) {
2034
- if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2035
- seen.add(key);
2163
+ if (arm.kind === "whenMatcher") {
2164
+ const matcher = arm.matcher;
2165
+ matchers.push({
2166
+ kind: matcher.kind,
2167
+ value: matcher.value,
2168
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2169
+ parser: arm.parser,
2170
+ caseInsensitive: arm.caseInsensitive,
2171
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2172
+ });
2173
+ continue;
2036
2174
  }
2037
- cases.push({ keys: arm.keys, parser: arm.parser });
2175
+ for (const key of arm.keys) {
2176
+ const folded = asciiFoldKey(key);
2177
+ for (const prior of seen) {
2178
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2179
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2180
+ }
2181
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2182
+ }
2183
+ cases.push({
2184
+ keys: arm.keys,
2185
+ parser: arm.parser,
2186
+ caseInsensitive: arm.caseInsensitive,
2187
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2188
+ });
2038
2189
  }
2039
2190
  const meta = {
2040
2191
  firstSet: selector._meta.firstSet,
2041
- canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2192
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2042
2193
  isTrivia: false
2043
2194
  };
2044
2195
  const byKey = /* @__PURE__ */ new Map();
2045
- for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2196
+ const byFoldedKey = /* @__PURE__ */ new Map();
2197
+ for (const entry of cases) {
2198
+ for (const key of entry.keys) {
2199
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2200
+ else byKey.set(key, entry);
2201
+ }
2202
+ }
2046
2203
  return {
2047
2204
  _tag: "dispatch",
2048
2205
  _meta: meta,
@@ -2050,18 +2207,35 @@ function dispatch(selector, ...arms) {
2050
2207
  tag: "dispatch",
2051
2208
  selector,
2052
2209
  cases,
2053
- ...fallback === void 0 ? {} : { otherwise: fallback }
2210
+ ...matchers.length === 0 ? {} : { matchers },
2211
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2054
2212
  },
2055
2213
  parse(input, pos, ctx) {
2214
+ if (parserUsesRouted(selector)) {
2215
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2216
+ }
2217
+ const selectorMark = saveTriviaMark(ctx);
2056
2218
  const selected = selector.parse(input, pos, ctx);
2057
2219
  if (!selected.ok) return selected;
2058
- const tail = byKey.get(selected.value) ?? fallback;
2059
- if (tail === void 0) {
2220
+ const branch = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher)) ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
2221
+ if (branch === void 0) {
2060
2222
  const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2061
2223
  return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2062
2224
  }
2063
- const mark = saveTriviaMark(ctx);
2064
- const result = tail.parse(input, selected.span.end, ctx);
2225
+ const savedRouted = ctx._routed;
2226
+ const usesRouted = branchUsesRouted(branch);
2227
+ let mark = saveTriviaMark(ctx);
2228
+ if (usesRouted) {
2229
+ rollbackTrivia(ctx, selectorMark);
2230
+ mark = saveTriviaMark(ctx);
2231
+ ctx._routed = { value: selected.value, span: selected.span };
2232
+ }
2233
+ let result;
2234
+ try {
2235
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2236
+ } finally {
2237
+ if (usesRouted) ctx._routed = savedRouted;
2238
+ }
2065
2239
  if (!result.ok) {
2066
2240
  rollbackTrivia(ctx, mark);
2067
2241
  return { ...result, committed: true };
@@ -2411,6 +2585,7 @@ function markUnusedValues(root) {
2411
2585
  case "dispatch":
2412
2586
  visit(def.selector, consumed);
2413
2587
  for (const entry of def.cases) visit(entry.parser, consumed);
2588
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2414
2589
  if (def.otherwise) visit(def.otherwise, consumed);
2415
2590
  return;
2416
2591
  case "node":
@@ -2689,7 +2864,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2689
2864
  case "choice":
2690
2865
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2691
2866
  case "dispatch":
2692
- return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2867
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasOwnFields(entry.parser, seen)) : false) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2693
2868
  case "sepBy":
2694
2869
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2695
2870
  case "skip":
@@ -2758,7 +2933,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2758
2933
  case "choice":
2759
2934
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2760
2935
  case "dispatch":
2761
- return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2936
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasTriviaSite(entry.parser, seen)) : false) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2762
2937
  case "grammar":
2763
2938
  return parserHasTriviaSite(d.parser, seen);
2764
2939
  case "lazy": {
@@ -2785,7 +2960,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2785
2960
  case "choice":
2786
2961
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2787
2962
  case "dispatch":
2788
- return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2963
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserEnablesTriviaCapture(entry.parser, seen)) : false) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2789
2964
  case "sepBy":
2790
2965
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2791
2966
  case "skip":
@@ -2846,12 +3021,30 @@ function isCstChild(value) {
2846
3021
  function missingInferredType() {
2847
3022
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
2848
3023
  }
3024
+ function normalizeProject(project) {
3025
+ if (project === void 0) return void 0;
3026
+ if (!Number.isInteger(project) || project < 0) {
3027
+ throw new Error("node() project child must be a non-negative integer");
3028
+ }
3029
+ return project;
3030
+ }
3031
+ function projectChild(children2, project, type) {
3032
+ if (!(project in children2)) {
3033
+ throw new Error(`node(${JSON.stringify(type)}) project child ${project} was not captured`);
3034
+ }
3035
+ return unwrapChild(children2[project]);
3036
+ }
2849
3037
  function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2850
3038
  const hasExplicitType = typeof typeOrCombinator === "string";
2851
3039
  const type = hasExplicitType ? typeOrCombinator : void 0;
2852
3040
  const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
2853
- const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
2854
- const opts = hasExplicitType ? maybeOpts : buildOrOpts;
3041
+ const buildArg = hasExplicitType ? buildOrOpts : combinatorOrBuild;
3042
+ const build = typeof buildArg === "function" ? buildArg : void 0;
3043
+ const opts = typeof buildArg === "function" ? maybeOpts : buildArg ?? maybeOpts;
3044
+ const project = normalizeProject(opts?.project);
3045
+ if (project !== void 0 && build !== void 0) {
3046
+ throw new Error("node() options cannot combine project with a build callback");
3047
+ }
2855
3048
  const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
2856
3049
  const meta = {
2857
3050
  firstSet: combinator._meta.firstSet,
@@ -2865,11 +3058,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2865
3058
  if (unwrap && collapse) {
2866
3059
  throw new Error("node() options cannot set both unwrap and collapse");
2867
3060
  }
2868
- const def = unwrap || collapse || captureTrivia || trailingTrivia ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {} } : baseDef;
2869
- const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : true);
2870
- const clonesState = build ? buildReadsState(def) : true;
3061
+ if (project !== void 0 && (unwrap || collapse)) {
3062
+ throw new Error("node() options cannot combine project with unwrap or collapse");
3063
+ }
3064
+ const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {} } : baseDef;
3065
+ const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3066
+ const clonesState = build ? buildReadsState(def) : project === void 0;
2871
3067
  const hasOwnFields = parserHasOwnFields(combinator);
2872
- const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : true);
3068
+ const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === void 0);
2873
3069
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
2874
3070
  let failExpected;
2875
3071
  return {
@@ -2884,14 +3080,15 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2884
3080
  }
2885
3081
  return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
2886
3082
  }
2887
- const hostCst = build !== void 0 && cstOutputHost(ctx.build);
3083
+ const hasDirectValue = build !== void 0 || project !== void 0;
3084
+ const hostCst = hasDirectValue && cstOutputHost(ctx.build);
2888
3085
  const effTrivia = capturesTrivia || hostCst;
2889
3086
  const effFields = capturesFields || hostCst && hasOwnFields;
2890
3087
  const saved = beginCstNodeCapture(ctx);
2891
3088
  const savedFields = ctx._fields;
2892
3089
  ctx._fields = effFields ? [] : void 0;
2893
3090
  const savedMask = ctx._triviaCaptureMask;
2894
- if (build === void 0 && ctx.build?._parsemanTriviaKinds !== void 0 && def.type !== void 0) {
3091
+ if (build === void 0 && project === void 0 && ctx.build?._parsemanTriviaKinds !== void 0 && def.type !== void 0) {
2895
3092
  ctx._triviaCaptureMask = ctx.build._parsemanTriviaKinds(def.type);
2896
3093
  }
2897
3094
  if (!effTrivia) ctx.captureTrivia = false;
@@ -2909,7 +3106,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2909
3106
  const nodeType = def.type ?? missingInferredType();
2910
3107
  const cstOutput = cstOutputHost(ctx.build);
2911
3108
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
2912
- const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, hostState) : build(children2, fields, r.span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, st) : { _tag: "node", type: nodeType, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
3109
+ const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, hostState) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, hostState) : build(children2, fields, r.span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, st) : { _tag: "node", type: nodeType, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
2913
3110
  const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : typeof built === "object" && built !== null ? input.slice(r.span.start, r.span.end) : "", span: r.span };
2914
3111
  if (saved.buf !== void 0 || saved.ch !== void 0) {
2915
3112
  pushCstChild(ctx, built, rawEntry);
@@ -3160,7 +3357,7 @@ function leaf(root, fn) {
3160
3357
  }
3161
3358
 
3162
3359
  // src/version.ts
3163
- var PARSEMAN_VERSION = "0.39.0";
3360
+ var PARSEMAN_VERSION = "0.40.0";
3164
3361
 
3165
3362
  // src/cst/host-mode.ts
3166
3363
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3420,6 +3617,7 @@ function analyzeGatingRules(ruleMap, opts) {
3420
3617
  if (d.tag === "dispatch") {
3421
3618
  kids.push(d.selector);
3422
3619
  for (const c of d.cases) kids.push(c.parser);
3620
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3423
3621
  if (d.otherwise !== void 0) kids.push(d.otherwise);
3424
3622
  }
3425
3623
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
@@ -4943,13 +5141,25 @@ function escapeClassChar(ch2) {
4943
5141
  }
4944
5142
 
4945
5143
  // src/compiler/grammar-coverage-ids.ts
5144
+ function idPart(value) {
5145
+ return encodeURIComponent(value);
5146
+ }
5147
+ function dispatchArmIds(def, path2) {
5148
+ const ids = [];
5149
+ for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5150
+ for (const entry of def.matchers ?? []) {
5151
+ ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5152
+ }
5153
+ if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5154
+ return ids;
5155
+ }
4946
5156
  function children(def, winners) {
4947
5157
  switch (def.tag) {
4948
5158
  case "sequence":
4949
5159
  case "choice":
4950
5160
  return def.parsers;
4951
5161
  case "dispatch":
4952
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
5162
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4953
5163
  case "many":
4954
5164
  case "oneOrMore":
4955
5165
  case "optional":
@@ -4992,6 +5202,7 @@ function children(def, winners) {
4992
5202
  function buildGrammarPlan(entry, winners) {
4993
5203
  const definitions = /* @__PURE__ */ new Map();
4994
5204
  const choices = /* @__PURE__ */ new WeakMap();
5205
+ const dispatches = /* @__PURE__ */ new WeakMap();
4995
5206
  const attempts = /* @__PURE__ */ new WeakMap();
4996
5207
  const labels = /* @__PURE__ */ new WeakMap();
4997
5208
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -5022,6 +5233,11 @@ function buildGrammarPlan(entry, winners) {
5022
5233
  choices.set(parser2, ids);
5023
5234
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5024
5235
  }
5236
+ if (parser2._def.tag === "dispatch") {
5237
+ const ids = dispatchArmIds(parser2._def, path2);
5238
+ dispatches.set(parser2, ids);
5239
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5240
+ }
5025
5241
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
5026
5242
  if (parser2._def.tag === "label") {
5027
5243
  const id = `label:${path2}`;
@@ -5034,7 +5250,7 @@ function buildGrammarPlan(entry, winners) {
5034
5250
  for (const root of roots) {
5035
5251
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
5036
5252
  }
5037
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5253
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
5038
5254
  }
5039
5255
 
5040
5256
  // src/compiler/scannable-run.ts
@@ -6408,6 +6624,7 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6408
6624
  case "literal":
6409
6625
  case "regex":
6410
6626
  case "keywords":
6627
+ case "routed":
6411
6628
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6412
6629
  case "choice": {
6413
6630
  const alts = [];
@@ -6766,7 +6983,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6766
6983
  case "choice":
6767
6984
  return false;
6768
6985
  case "dispatch":
6769
- 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);
6986
+ 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);
6770
6987
  case "attempt":
6771
6988
  return false;
6772
6989
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6865,6 +7082,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6865
7082
  case "keywords":
6866
7083
  case "node":
6867
7084
  case "token":
7085
+ case "routed":
6868
7086
  case "leaf":
6869
7087
  return true;
6870
7088
  case "not":
@@ -6877,7 +7095,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6877
7095
  case "choice":
6878
7096
  return d.parsers.some((x) => capturesLeaf(x, seen));
6879
7097
  case "dispatch":
6880
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
7098
+ 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);
6881
7099
  case "sepBy":
6882
7100
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6883
7101
  case "many":
@@ -7355,6 +7573,7 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7355
7573
  for (const item of d.cases) {
7356
7574
  if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7357
7575
  }
7576
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7358
7577
  return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7359
7578
  }
7360
7579
  case "attempt":
@@ -7389,53 +7608,120 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7389
7608
  return false;
7390
7609
  }
7391
7610
  }
7392
- function emitDispatchCombinator(def, ctx, pos) {
7611
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7612
+ if (parserUsesRouted2(def.selector)) {
7613
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7614
+ }
7615
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7616
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7617
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7618
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7619
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7620
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7621
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7622
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7623
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7393
7624
  const selector = emit(def.selector, ctx, pos);
7394
7625
  const outV = v(ctx, "_dval");
7395
7626
  const outE = v(ctx, "_dend");
7396
7627
  const keyV = v(ctx, "_dkey");
7397
7628
  const stmts = [
7629
+ ...selectorLeafMark ? [
7630
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7631
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7632
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7633
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7634
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7635
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7636
+ ] : [],
7398
7637
  ...selector.stmts,
7399
7638
  `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7400
7639
  `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7401
7640
  ];
7402
7641
  let wroteBranch = false;
7403
- const emitSelectedTail = (parser2, keyword, condition) => {
7642
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7643
+ const parser3 = entry.parser;
7404
7644
  const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7405
7645
  stmts.push(head);
7406
7646
  ctx.indent++;
7407
- const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7647
+ const usesRouted = branchUsesRouted2(entry);
7648
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7649
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7408
7650
  const errMark = mayError ? v(ctx, "_derr") : null;
7409
7651
  const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7652
+ if (routedV) {
7653
+ stmts.push(
7654
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7655
+ ...selectorLeafMark ? [
7656
+ `${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}`
7657
+ ] : []
7658
+ );
7659
+ }
7410
7660
  if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7411
7661
  if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7412
- const tail = emitTailFallible(parser2, ctx, selector.endVar);
7662
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7663
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7413
7664
  const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7414
7665
  const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7666
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7667
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7415
7668
  stmts.push(
7416
7669
  ...tail.stmts,
7417
- `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7670
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7671
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7418
7672
  `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7419
- `${ind(ctx)}${outE} = ${tail.endVar}`
7673
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7674
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7420
7675
  );
7421
7676
  ctx.indent--;
7422
7677
  stmts.push(`${ind(ctx)}}`);
7423
7678
  };
7679
+ let coverageIndex = 0;
7424
7680
  for (const entry of def.cases) {
7425
- const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7426
- emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7681
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7682
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7427
7683
  wroteBranch = true;
7428
7684
  }
7685
+ const emitMatcherTails = () => {
7686
+ for (const matcher of def.matchers ?? []) {
7687
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7688
+ wroteBranch = true;
7689
+ }
7690
+ };
7429
7691
  if (def.otherwise) {
7430
- if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7431
- else emitSelectedTail(def.otherwise, "if", "true");
7692
+ emitMatcherTails();
7693
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7694
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7695
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7432
7696
  } else {
7697
+ emitMatcherTails();
7433
7698
  const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7434
7699
  const fail = failArrBody(ctx, expected, selector.endVar);
7435
7700
  stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7436
7701
  }
7437
7702
  return { stmts, valueVar: outV, endVar: outE };
7438
7703
  }
7704
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7705
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7706
+ const parts = [`${valueVar}.length === ${key.length}`];
7707
+ for (let i = 0; i < key.length; i++) {
7708
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7709
+ }
7710
+ return parts.join(" && ");
7711
+ }
7712
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7713
+ if (matcher.kind === "matches") {
7714
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7715
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7716
+ }
7717
+ const text = matcher.value;
7718
+ const parts = [`${valueVar}.length >= ${text.length}`];
7719
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7720
+ for (let i = 0; i < text.length; i++) {
7721
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7722
+ }
7723
+ return parts.join(" && ");
7724
+ }
7439
7725
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7440
7726
  const superParser = def.parsers[superIndex];
7441
7727
  const regexDef = getCoreRegexDef(superParser);
@@ -8090,6 +8376,18 @@ function emitPeek(def, ctx, pos) {
8090
8376
  endVar: pos
8091
8377
  };
8092
8378
  }
8379
+ function emitRouted(ctx, pos) {
8380
+ const item = v(ctx, "_rt");
8381
+ return {
8382
+ stmts: [
8383
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8384
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8385
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8386
+ ],
8387
+ valueVar: `${item}.value`,
8388
+ endVar: `${item}.span.end`
8389
+ };
8390
+ }
8093
8391
  function escapeTokenLiteral(value) {
8094
8392
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8095
8393
  }
@@ -8230,14 +8528,20 @@ function emitLeaf(def, ctx, pos) {
8230
8528
  endVar: inner.endVar
8231
8529
  };
8232
8530
  }
8531
+ function emitNodeProjectExpr(type, project, chV) {
8532
+ const child = `${chV}[${project}]`;
8533
+ const missing = `(() => { throw new Error(${JSON.stringify(`node(${JSON.stringify(type)}) project child ${project} was not captured`)}) })()`;
8534
+ const value = `(${child} !== null && typeof ${child} === 'object' && ${child}._tag === 'leaf' ? ${child}.value : ${child})`;
8535
+ return `(${project} in ${chV} ? ${value} : ${missing})`;
8536
+ }
8233
8537
  function emitNode(def, ctx, pos) {
8234
8538
  if (def.type === void 0) {
8235
8539
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
8236
8540
  }
8237
- const structural = def.build === void 0;
8238
- const mkType = structural ? null : analyzeMkInlineBuild(def);
8541
+ const structural = def.build === void 0 && def.project === void 0;
8542
+ const mkType = structural || def.project !== void 0 ? null : analyzeMkInlineBuild(def);
8239
8543
  let buildIdx = null;
8240
- if (!mkType && !structural) {
8544
+ if (!mkType && !structural && def.project === void 0) {
8241
8545
  buildIdx = ctx.buildFns.length;
8242
8546
  ctx.buildFns.push(def.build);
8243
8547
  ctx.buildSrcs.push(def.buildSrc ?? null);
@@ -8245,12 +8549,13 @@ function emitNode(def, ctx, pos) {
8245
8549
  const i = ind(ctx);
8246
8550
  const cstOut = ctx.hostMode === "cst" && !structural;
8247
8551
  if (!structural && !cstOut) ctx.hostBranchElided = true;
8248
- const capturesTrivia = cstOut || mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && buildReadsTrivia(def);
8249
- const clonesState = !structural && (cstOut || buildReadsState(def));
8250
- const capturesChildren = !structural && (cstOut || mkType !== null || def.unwrap || def.collapse || buildReadsChildren(def));
8251
- const capturesRaw = !structural && (cstOut || mkType !== null || buildReadsRaw(def));
8552
+ const hasProject = def.project !== void 0;
8553
+ const capturesTrivia = cstOut || mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && !hasProject && buildReadsTrivia(def);
8554
+ const clonesState = !structural && (cstOut || !hasProject && buildReadsState(def));
8555
+ const capturesChildren = !structural && (cstOut || mkType !== null || def.unwrap || def.collapse || def.project !== void 0 || buildReadsChildren(def));
8556
+ const capturesRaw = !structural && (cstOut || mkType !== null || def.project === void 0 && buildReadsRaw(def));
8252
8557
  const hasFields = parserHasOwnFields(def.parser);
8253
- const capturesFields = hasFields && !structural && (cstOut || buildReadsFields(def));
8558
+ const capturesFields = hasFields && !structural && (cstOut || !hasProject && buildReadsFields(def));
8254
8559
  const innerEnablesTriviaCapture = parserEnablesTriviaCapture(def.parser);
8255
8560
  const chV = v(ctx, "_ch");
8256
8561
  const rawV = v(ctx, "_raw");
@@ -8348,7 +8653,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
8348
8653
  );
8349
8654
  }
8350
8655
  const ndV = v(ctx, "_nd");
8351
- const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV})`;
8656
+ const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : def.project !== void 0 ? emitNodeProjectExpr(def.type, def.project, chV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV})`;
8352
8657
  const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV})`;
8353
8658
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
8354
8659
  const hostCollapseExpr = structural ? `_ctx.build !== undefined && _ctx.build._parsemanCstCollapse !== undefined && ${chV}.length === 1 && ${rawV}.length === 1 && _ctx.build._parsemanCstCollapse(${JSON.stringify(def.type)}, ${chV}[0], ${chV}, ${rawV}) ? ${chV}[0] : (${ndExpr})` : ndExpr;
@@ -8555,7 +8860,7 @@ function emitDispatch(p, ctx, pos) {
8555
8860
  case "choice":
8556
8861
  return emitChoice(p, def, ctx, pos);
8557
8862
  case "dispatch":
8558
- return emitDispatchCombinator(def, ctx, pos);
8863
+ return emitDispatchCombinator(p, def, ctx, pos);
8559
8864
  case "attempt":
8560
8865
  return emitAttempt(p, def, ctx, pos);
8561
8866
  case "many":
@@ -8680,6 +8985,8 @@ function emitDispatch(p, ctx, pos) {
8680
8985
  return emitNot(def, ctx, pos);
8681
8986
  case "peek":
8682
8987
  return emitPeek(def, ctx, pos);
8988
+ case "routed":
8989
+ return emitRouted(ctx, pos);
8683
8990
  case "node":
8684
8991
  return emitNode(def, ctx, pos);
8685
8992
  case "scanTo":
@@ -8777,7 +9084,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8777
9084
  case "choice":
8778
9085
  return d.parsers.some((x) => hasNodeDef(x, seen));
8779
9086
  case "dispatch":
8780
- return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
9087
+ 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);
8781
9088
  case "sepBy":
8782
9089
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8783
9090
  case "scanTo":
@@ -8792,6 +9099,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8792
9099
  return false;
8793
9100
  }
8794
9101
  }
9102
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9103
+ if (seen.has(p)) return false;
9104
+ seen.add(p);
9105
+ const d = p._def;
9106
+ switch (d.tag) {
9107
+ case "routed":
9108
+ return true;
9109
+ case "lazy": {
9110
+ try {
9111
+ return parserUsesRouted2(d.thunk(), seen);
9112
+ } catch {
9113
+ return false;
9114
+ }
9115
+ }
9116
+ case "dispatch":
9117
+ return false;
9118
+ default:
9119
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9120
+ }
9121
+ }
8795
9122
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8796
9123
  if (seen.has(p)) return false;
8797
9124
  seen.add(p);
@@ -8812,7 +9139,7 @@ function childrenOf2(def) {
8812
9139
  case "choice":
8813
9140
  return def.parsers;
8814
9141
  case "dispatch":
8815
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9142
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8816
9143
  case "many":
8817
9144
  case "attempt":
8818
9145
  case "optional":
@@ -8849,6 +9176,7 @@ function childrenOf2(def) {
8849
9176
  case "regex":
8850
9177
  case "keywords":
8851
9178
  case "guard":
9179
+ case "routed":
8852
9180
  case "unknown":
8853
9181
  return [];
8854
9182
  }
@@ -9682,7 +10010,7 @@ function childrenOf3(def) {
9682
10010
  case "choice":
9683
10011
  return def.parsers;
9684
10012
  case "dispatch":
9685
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
10013
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9686
10014
  case "many":
9687
10015
  case "oneOrMore":
9688
10016
  case "optional":
@@ -9711,6 +10039,7 @@ function childrenOf3(def) {
9711
10039
  case "literal":
9712
10040
  case "regex":
9713
10041
  case "keywords":
10042
+ case "routed":
9714
10043
  case "guard":
9715
10044
  case "recover":
9716
10045
  case "unknown":
@@ -9735,6 +10064,20 @@ function repeatOpts(min, max, trailing) {
9735
10064
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9736
10065
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9737
10066
  }
10067
+ function projectOpt(project) {
10068
+ if (project === void 0) return void 0;
10069
+ return `project: ${project}`;
10070
+ }
10071
+ function matcherExpr(entry) {
10072
+ switch (entry.kind) {
10073
+ case "startsWith":
10074
+ return `startsWith(${JSON.stringify(entry.value)})`;
10075
+ case "endsWith":
10076
+ return `endsWith(${JSON.stringify(entry.value)})`;
10077
+ case "matches":
10078
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
10079
+ }
10080
+ }
9738
10081
  function serializeRuleMap(ruleMap, scanSkip) {
9739
10082
  try {
9740
10083
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9834,7 +10177,11 @@ function evalRuleMapIR(ir) {
9834
10177
  "choice",
9835
10178
  "dispatch",
9836
10179
  "when",
10180
+ "startsWith",
10181
+ "endsWith",
10182
+ "matches",
9837
10183
  "otherwise",
10184
+ "routed",
9838
10185
  "attempt",
9839
10186
  "many",
9840
10187
  "oneOrMore",
@@ -9870,7 +10217,11 @@ function evalRuleMapIR(ir) {
9870
10217
  choice,
9871
10218
  dispatch,
9872
10219
  when,
10220
+ startsWith,
10221
+ endsWith,
10222
+ matches,
9873
10223
  otherwise,
10224
+ routed,
9874
10225
  attempt,
9875
10226
  many,
9876
10227
  oneOrMore,
@@ -10058,8 +10409,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10058
10409
  }
10059
10410
  case "dispatch": {
10060
10411
  const arms = def.cases.map(
10061
- (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10412
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10062
10413
  );
10414
+ if (def.matchers) {
10415
+ for (const entry of def.matchers) {
10416
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10417
+ }
10418
+ }
10063
10419
  if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10064
10420
  return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10065
10421
  }
@@ -10079,6 +10435,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10079
10435
  return `not(${kid(def.parser)})`;
10080
10436
  case "peek":
10081
10437
  return `peek(${kid(def.parser)})`;
10438
+ case "routed":
10439
+ return "routed()";
10082
10440
  case "trivia":
10083
10441
  return `trivia(${kid(def.parser)})`;
10084
10442
  case "token":
@@ -10103,9 +10461,11 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10103
10461
  }
10104
10462
  case "node": {
10105
10463
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
10464
+ const projectEntry = projectOpt(def.project);
10106
10465
  const optEntries = [
10107
10466
  ...def.unwrap ? ["unwrap: true"] : [],
10108
10467
  ...def.collapse ? ["collapse: true"] : [],
10468
+ ...projectEntry === void 0 ? [] : [projectEntry],
10109
10469
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
10110
10470
  ...def.trailingTrivia ? ["trailingTrivia: true"] : []
10111
10471
  ];
@@ -10548,6 +10908,9 @@ function stripTsFromSource(node2, code) {
10548
10908
  function isWordFactory(v2) {
10549
10909
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10550
10910
  }
10911
+ function isWhenFactory(v2) {
10912
+ return !!v2 && typeof v2 === "object" && v2.tag === "whenFactory";
10913
+ }
10551
10914
  function isStaticValueEntry(v2) {
10552
10915
  return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10553
10916
  }
@@ -10570,6 +10933,23 @@ function wordFactoryFromArgs(args, scope, code, mfs) {
10570
10933
  if (typeof caseInsensitive !== "boolean") return null;
10571
10934
  return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10572
10935
  }
10936
+ function dispatchWhenOptions(v2) {
10937
+ if (v2 === void 0) return {};
10938
+ if (typeof v2 !== "object" || v2 === null || Array.isArray(v2)) return null;
10939
+ for (const key of Object.keys(v2)) {
10940
+ if (key !== "caseInsensitive") return null;
10941
+ }
10942
+ const caseInsensitive = "caseInsensitive" in v2 ? v2.caseInsensitive : false;
10943
+ if (typeof caseInsensitive !== "boolean") return null;
10944
+ return { caseInsensitive };
10945
+ }
10946
+ function whenFactoryFromArgs(args, scope, code, mfs) {
10947
+ const [optsArg, extra] = args;
10948
+ if (optsArg?.type === "SpreadElement" || extra !== void 0) return null;
10949
+ const rawOpts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10950
+ const opts = dispatchWhenOptions(rawOpts);
10951
+ return opts === null ? null : { tag: "whenFactory", caseInsensitive: opts.caseInsensitive ?? false };
10952
+ }
10573
10953
  var SUPPORTED = {
10574
10954
  literal: (...a) => literal(a[0], a[1]),
10575
10955
  regex: (...a) => regex(a[0], a[1]),
@@ -10585,6 +10965,7 @@ var SUPPORTED = {
10585
10965
  field: (...a) => field(a[0], a[1]),
10586
10966
  noTrivia: (...a) => noTrivia(a[0]),
10587
10967
  token: (...a) => token(a[0]),
10968
+ routed: () => routed(),
10588
10969
  leaf: (...a) => leaf(a[0], a[1]),
10589
10970
  expect: (...a) => expect(a[0], a[1])
10590
10971
  };
@@ -10628,6 +11009,18 @@ function dispatchArmValue(node2, scope, code, mfs) {
10628
11009
  if (node2.type !== "CallExpression") return null;
10629
11010
  const callee = node2.callee;
10630
11011
  if (callee.type !== "Identifier") return null;
11012
+ const factory = scope.get(callee.name);
11013
+ if (isWhenFactory(factory)) {
11014
+ const [keyArg, parserArg, extraArg] = node2.arguments;
11015
+ if (!keyArg || !parserArg || extraArg !== void 0 || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
11016
+ const key = anyValue(keyArg, scope, code, mfs);
11017
+ const parserValue = anyValue(parserArg, scope, code, mfs);
11018
+ if (!isCombinator(parserValue)) return null;
11019
+ const opts = { caseInsensitive: factory.caseInsensitive };
11020
+ if (typeof key === "string") return when(key, parserValue, opts);
11021
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
11022
+ return null;
11023
+ }
10631
11024
  if (callee.name === "otherwise") {
10632
11025
  const [parserArg] = node2.arguments;
10633
11026
  if (!parserArg || parserArg.type === "SpreadElement") return null;
@@ -10636,13 +11029,16 @@ function dispatchArmValue(node2, scope, code, mfs) {
10636
11029
  return otherwise(parserValue);
10637
11030
  }
10638
11031
  if (callee.name === "when") {
10639
- const [keyArg, parserArg] = node2.arguments;
10640
- if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
11032
+ const [keyArg, parserArg, optsArg] = node2.arguments;
11033
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10641
11034
  const key = anyValue(keyArg, scope, code, mfs);
10642
11035
  const parserValue = anyValue(parserArg, scope, code, mfs);
11036
+ const opts = dispatchWhenOptions(optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs));
11037
+ if (opts === null) return null;
10643
11038
  if (!isCombinator(parserValue)) return null;
10644
- if (typeof key === "string") return when(key, parserValue);
10645
- if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue);
11039
+ if (typeof key === "string") return when(key, parserValue, opts);
11040
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
11041
+ if (isDispatchMatcher(key)) return when(key, parserValue, opts);
10646
11042
  }
10647
11043
  return null;
10648
11044
  }
@@ -10653,7 +11049,16 @@ function isDispatchArm(v2) {
10653
11049
  if (!v2 || typeof v2 !== "object") return false;
10654
11050
  const rec = v2;
10655
11051
  if (rec.kind === "otherwise") return isCombinator(rec.parser);
10656
- return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && isCombinator(rec.parser);
11052
+ if (rec.kind === "whenMatcher") {
11053
+ return isDispatchMatcher(rec.matcher) && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11054
+ }
11055
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11056
+ }
11057
+ function isDispatchMatcher(v2) {
11058
+ if (!v2 || typeof v2 !== "object") return false;
11059
+ const rec = v2;
11060
+ if ((rec.kind === "startsWith" || rec.kind === "endsWith") && typeof rec.value === "string") return true;
11061
+ return rec.kind === "matches" && typeof rec.value === "string" && typeof rec.flags === "string";
10657
11062
  }
10658
11063
  function scopeGet(scope, name, mfs) {
10659
11064
  const entry = scope.get(name);
@@ -10665,6 +11070,18 @@ function scopeGet(scope, name, mfs) {
10665
11070
  if (isCombinator(entry)) return entry;
10666
11071
  return null;
10667
11072
  }
11073
+ function staticLiteralValue(expr) {
11074
+ const val = expr;
11075
+ return val.type === "Literal" || val.type === "BooleanLiteral" || val.type === "NumericLiteral" ? val.value : void 0;
11076
+ }
11077
+ function staticNodeProject(expr) {
11078
+ const literalValue = staticLiteralValue(expr);
11079
+ if (typeof literalValue === "number" && Number.isInteger(literalValue) && literalValue >= 0) {
11080
+ return { ok: true, value: literalValue };
11081
+ }
11082
+ if (typeof literalValue === "number" || literalValue !== void 0) return { ok: false };
11083
+ return { ok: false };
11084
+ }
10668
11085
  function staticNodeOptions(expr) {
10669
11086
  if (expr.type !== "ObjectExpression") return void 0;
10670
11087
  const opts = {};
@@ -10674,11 +11091,14 @@ function staticNodeOptions(expr) {
10674
11091
  const key = p.key;
10675
11092
  const name = key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
10676
11093
  if (name === "unwrap" || name === "collapse" || name === "captureTrivia" || name === "trailingTrivia") {
10677
- const val = p.value;
10678
- if ((val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true) opts[name] = true;
11094
+ if (staticLiteralValue(p.value) === true) opts[name] = true;
11095
+ } else if (name === "project") {
11096
+ const project = staticNodeProject(p.value);
11097
+ if (project?.ok === false) return { project: -1 };
11098
+ if (project !== void 0) opts.project = project.value;
10679
11099
  }
10680
11100
  }
10681
- return opts.unwrap || opts.collapse || opts.captureTrivia || opts.trailingTrivia ? opts : void 0;
11101
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia ? opts : void 0;
10682
11102
  }
10683
11103
  function exprToCombi(node2, scope, code, mfs) {
10684
11104
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -10753,9 +11173,10 @@ function exprToCombi(node2, scope, code, mfs) {
10753
11173
  const inner = anyValue(parserArg, scope, code, mfs);
10754
11174
  if (!isCombinator(inner)) return null;
10755
11175
  const be = buildArg;
10756
- const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
11176
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && be.type !== "ObjectExpression" && !(be.type === "Identifier" && be.name === "undefined");
10757
11177
  const buildSrc = hasBuild ? stripTsFromSource(be, code) : void 0;
10758
- const opts = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticNodeOptions(optsArg) : void 0;
11178
+ const optionsArg = hasBuild ? optsArg : be?.type === "ObjectExpression" ? buildArg : optsArg;
11179
+ const opts = optionsArg !== void 0 && optionsArg.type !== "SpreadElement" ? staticNodeOptions(optionsArg) : void 0;
10759
11180
  try {
10760
11181
  const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
10761
11182
  if (combi._def.tag === "node" && buildSrc !== void 0) {
@@ -11039,9 +11460,27 @@ function anyValue(node2, scope, code, mfs) {
11039
11460
  if (callee.type === "Identifier" && callee.name === "makeWord") {
11040
11461
  return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
11041
11462
  }
11463
+ if (callee.type === "Identifier" && callee.name === "makeWhen") {
11464
+ return whenFactoryFromArgs(node2.arguments, scope, code, mfs);
11465
+ }
11466
+ if (callee.type === "Identifier" && (callee.name === "startsWith" || callee.name === "endsWith")) {
11467
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11468
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11469
+ if (typeof value !== "string") return null;
11470
+ return callee.name === "startsWith" ? startsWith(value) : endsWith(value);
11471
+ }
11472
+ if (callee.type === "Identifier" && callee.name === "matches") {
11473
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11474
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11475
+ if (!(value instanceof RegExp)) return null;
11476
+ return matches(value);
11477
+ }
11042
11478
  if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11043
11479
  return dispatchArmValue(node2, scope, code, mfs);
11044
11480
  }
11481
+ if (callee.type === "Identifier" && isWhenFactory(scope.get(callee.name))) {
11482
+ return dispatchArmValue(node2, scope, code, mfs);
11483
+ }
11045
11484
  return exprToCombi(node2, scope, code, mfs);
11046
11485
  }
11047
11486
  return null;
@@ -11052,6 +11491,12 @@ function evaluateWordFactory(node2, scope, code) {
11052
11491
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
11053
11492
  return wordFactoryFromArgs(node2.arguments, scope, code);
11054
11493
  }
11494
+ function evaluateWhenFactory(node2, scope, code) {
11495
+ if (node2.type !== "CallExpression") return null;
11496
+ const callee = node2.callee;
11497
+ if (callee.type !== "Identifier" || callee.name !== "makeWhen") return null;
11498
+ return whenFactoryFromArgs(node2.arguments, scope, code);
11499
+ }
11055
11500
  function evaluateExpr(node2, scope, code, mapFnSources) {
11056
11501
  return exprToCombi(node2, scope, code, mapFnSources);
11057
11502
  }
@@ -11632,7 +12077,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11632
12077
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
11633
12078
  return [...ids].sort().map((id2) => ({
11634
12079
  id: id2,
11635
- kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
12080
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
11636
12081
  }));
11637
12082
  };
11638
12083
  const localComposedCarried = /* @__PURE__ */ new Map();
@@ -12078,6 +12523,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12078
12523
  if (wordFactory) {
12079
12524
  ;
12080
12525
  scope.set(varName, wordFactory);
12526
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12527
+ continue;
12528
+ }
12529
+ const whenFactory = evaluateWhenFactory(init, scope, code);
12530
+ if (whenFactory) {
12531
+ ;
12532
+ scope.set(varName, whenFactory);
12533
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12081
12534
  continue;
12082
12535
  }
12083
12536
  const combiArray = evaluateCombinatorArray(init, scope, code);