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.
@@ -59,6 +59,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
59
59
  return d.value.length === 0;
60
60
  case "keywords":
61
61
  return false;
62
+ case "routed":
63
+ return false;
62
64
  case "regex":
63
65
  try {
64
66
  const m = new RegExp(d.source).exec("");
@@ -1151,6 +1153,8 @@ function derive(c, seen) {
1151
1153
  case "not":
1152
1154
  case "peek":
1153
1155
  return deriveExpected2(def.parser);
1156
+ case "routed":
1157
+ return ["routed()"];
1154
1158
  case "lazy": {
1155
1159
  const name = c._ruleName;
1156
1160
  if (seen.has(c)) return name ? [name] : [];
@@ -1972,42 +1976,195 @@ function sequence(...parsers) {
1972
1976
  }
1973
1977
 
1974
1978
  // src/combinators/dispatch.ts
1975
- function when(key, parser2) {
1979
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
1980
+ if (seen.has(parser2)) return false;
1981
+ seen.add(parser2);
1982
+ const def = parser2._def;
1983
+ switch (def.tag) {
1984
+ case "routed":
1985
+ return true;
1986
+ case "sequence":
1987
+ case "choice":
1988
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
1989
+ case "dispatch":
1990
+ return false;
1991
+ case "sepBy":
1992
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
1993
+ case "skip":
1994
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
1995
+ case "grammar":
1996
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
1997
+ case "scanTo":
1998
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
1999
+ case "recover":
2000
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
2001
+ case "lazy":
2002
+ try {
2003
+ return parserUsesRouted(def.thunk(), seen);
2004
+ } catch {
2005
+ return false;
2006
+ }
2007
+ case "many":
2008
+ case "oneOrMore":
2009
+ case "optional":
2010
+ case "attempt":
2011
+ case "transform":
2012
+ case "trivia":
2013
+ case "token":
2014
+ case "leaf":
2015
+ case "label":
2016
+ case "field":
2017
+ case "not":
2018
+ case "peek":
2019
+ case "node":
2020
+ case "withCtx":
2021
+ case "expect":
2022
+ return parserUsesRouted(def.parser, seen);
2023
+ default:
2024
+ return false;
2025
+ }
2026
+ }
2027
+ function branchUsesRouted(branch) {
2028
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
2029
+ }
2030
+ function asciiFoldKey(key) {
2031
+ let out = "";
2032
+ for (let i = 0; i < key.length; i++) {
2033
+ const c = key.charCodeAt(i);
2034
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
2035
+ }
2036
+ return out;
2037
+ }
2038
+ function matchesDispatchMatcher(value, matcher) {
2039
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2040
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2041
+ switch (matcher.kind) {
2042
+ case "startsWith":
2043
+ return candidate.startsWith(expected);
2044
+ case "endsWith":
2045
+ return candidate.endsWith(expected);
2046
+ case "matches": {
2047
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2048
+ return new RegExp(matcher.value, flags).test(value);
2049
+ }
2050
+ }
2051
+ }
2052
+ function isStringMatcher(value) {
2053
+ if (typeof value !== "object" || value === null) return false;
2054
+ const kind = value.kind;
2055
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2056
+ }
2057
+ function fixedMatcher(kind, value) {
2058
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2059
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2060
+ return { kind, value };
2061
+ }
2062
+ function startsWith(prefix) {
2063
+ return fixedMatcher("startsWith", prefix);
2064
+ }
2065
+ function endsWith(suffix) {
2066
+ return fixedMatcher("endsWith", suffix);
2067
+ }
2068
+ function matches(pattern) {
2069
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2070
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2071
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2072
+ }
2073
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2074
+ }
2075
+ function when(key, parser2, opts = {}) {
2076
+ const usesRouted = parserUsesRouted(parser2);
2077
+ if (isStringMatcher(key)) {
2078
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2079
+ }
1976
2080
  const keys = Array.isArray(key) ? [...key] : [key];
1977
2081
  if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
1978
2082
  for (const item of keys) {
1979
2083
  if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
1980
2084
  }
1981
- return { kind: "when", keys, parser: parser2 };
2085
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
1982
2086
  }
1983
2087
  function otherwise(parser2) {
1984
- return { kind: "otherwise", parser: parser2 };
2088
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2089
+ }
2090
+ function routed() {
2091
+ return {
2092
+ _tag: "routed",
2093
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2094
+ _def: { tag: "routed" },
2095
+ parse(_input, pos, ctx) {
2096
+ const item = ctx._routed;
2097
+ if (item === void 0) {
2098
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2099
+ }
2100
+ if (pos !== item.span.start) {
2101
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2102
+ }
2103
+ if (cstCaptureActive(ctx)) {
2104
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2105
+ }
2106
+ return { ok: true, value: item.value, span: item.span };
2107
+ }
2108
+ };
1985
2109
  }
1986
2110
  function dispatch(selector, ...arms) {
2111
+ if (parserUsesRouted(selector)) {
2112
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2113
+ }
1987
2114
  let fallback;
2115
+ let fallbackUsesRouted = false;
2116
+ const matchers = [];
1988
2117
  const cases = [];
1989
- const seen = /* @__PURE__ */ new Set();
2118
+ const seen = [];
1990
2119
  for (let i = 0; i < arms.length; i++) {
1991
2120
  const arm = arms[i];
1992
2121
  if (arm.kind === "otherwise") {
1993
2122
  if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
1994
2123
  if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
1995
2124
  fallback = arm.parser;
2125
+ fallbackUsesRouted = arm.usesRouted === true;
1996
2126
  continue;
1997
2127
  }
1998
- for (const key of arm.keys) {
1999
- if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2000
- seen.add(key);
2128
+ if (arm.kind === "whenMatcher") {
2129
+ const matcher = arm.matcher;
2130
+ matchers.push({
2131
+ kind: matcher.kind,
2132
+ value: matcher.value,
2133
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2134
+ parser: arm.parser,
2135
+ caseInsensitive: arm.caseInsensitive,
2136
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2137
+ });
2138
+ continue;
2001
2139
  }
2002
- cases.push({ keys: arm.keys, parser: arm.parser });
2140
+ for (const key of arm.keys) {
2141
+ const folded = asciiFoldKey(key);
2142
+ for (const prior of seen) {
2143
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2144
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2145
+ }
2146
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2147
+ }
2148
+ cases.push({
2149
+ keys: arm.keys,
2150
+ parser: arm.parser,
2151
+ caseInsensitive: arm.caseInsensitive,
2152
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2153
+ });
2003
2154
  }
2004
2155
  const meta = {
2005
2156
  firstSet: selector._meta.firstSet,
2006
- canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2157
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2007
2158
  isTrivia: false
2008
2159
  };
2009
2160
  const byKey = /* @__PURE__ */ new Map();
2010
- for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2161
+ const byFoldedKey = /* @__PURE__ */ new Map();
2162
+ for (const entry of cases) {
2163
+ for (const key of entry.keys) {
2164
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2165
+ else byKey.set(key, entry);
2166
+ }
2167
+ }
2011
2168
  return {
2012
2169
  _tag: "dispatch",
2013
2170
  _meta: meta,
@@ -2015,18 +2172,35 @@ function dispatch(selector, ...arms) {
2015
2172
  tag: "dispatch",
2016
2173
  selector,
2017
2174
  cases,
2018
- ...fallback === void 0 ? {} : { otherwise: fallback }
2175
+ ...matchers.length === 0 ? {} : { matchers },
2176
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
2019
2177
  },
2020
2178
  parse(input, pos, ctx) {
2179
+ if (parserUsesRouted(selector)) {
2180
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2181
+ }
2182
+ const selectorMark = saveTriviaMark(ctx);
2021
2183
  const selected = selector.parse(input, pos, ctx);
2022
2184
  if (!selected.ok) return selected;
2023
- const tail = byKey.get(selected.value) ?? fallback;
2024
- if (tail === void 0) {
2185
+ const branch = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher)) ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
2186
+ if (branch === void 0) {
2025
2187
  const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2026
2188
  return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2027
2189
  }
2028
- const mark = saveTriviaMark(ctx);
2029
- const result = tail.parse(input, selected.span.end, ctx);
2190
+ const savedRouted = ctx._routed;
2191
+ const usesRouted = branchUsesRouted(branch);
2192
+ let mark = saveTriviaMark(ctx);
2193
+ if (usesRouted) {
2194
+ rollbackTrivia(ctx, selectorMark);
2195
+ mark = saveTriviaMark(ctx);
2196
+ ctx._routed = { value: selected.value, span: selected.span };
2197
+ }
2198
+ let result;
2199
+ try {
2200
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2201
+ } finally {
2202
+ if (usesRouted) ctx._routed = savedRouted;
2203
+ }
2030
2204
  if (!result.ok) {
2031
2205
  rollbackTrivia(ctx, mark);
2032
2206
  return { ...result, committed: true };
@@ -2376,6 +2550,7 @@ function markUnusedValues(root) {
2376
2550
  case "dispatch":
2377
2551
  visit(def.selector, consumed);
2378
2552
  for (const entry of def.cases) visit(entry.parser, consumed);
2553
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2379
2554
  if (def.otherwise) visit(def.otherwise, consumed);
2380
2555
  return;
2381
2556
  case "node":
@@ -2654,7 +2829,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2654
2829
  case "choice":
2655
2830
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2656
2831
  case "dispatch":
2657
- return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2832
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasOwnFields(entry.parser, seen)) : false) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2658
2833
  case "sepBy":
2659
2834
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2660
2835
  case "skip":
@@ -2723,7 +2898,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2723
2898
  case "choice":
2724
2899
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2725
2900
  case "dispatch":
2726
- return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2901
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasTriviaSite(entry.parser, seen)) : false) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2727
2902
  case "grammar":
2728
2903
  return parserHasTriviaSite(d.parser, seen);
2729
2904
  case "lazy": {
@@ -2750,7 +2925,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2750
2925
  case "choice":
2751
2926
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2752
2927
  case "dispatch":
2753
- return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2928
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserEnablesTriviaCapture(entry.parser, seen)) : false) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2754
2929
  case "sepBy":
2755
2930
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2756
2931
  case "skip":
@@ -2811,12 +2986,30 @@ function isCstChild(value) {
2811
2986
  function missingInferredType() {
2812
2987
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
2813
2988
  }
2989
+ function normalizeProject(project) {
2990
+ if (project === void 0) return void 0;
2991
+ if (!Number.isInteger(project) || project < 0) {
2992
+ throw new Error("node() project child must be a non-negative integer");
2993
+ }
2994
+ return project;
2995
+ }
2996
+ function projectChild(children2, project, type) {
2997
+ if (!(project in children2)) {
2998
+ throw new Error(`node(${JSON.stringify(type)}) project child ${project} was not captured`);
2999
+ }
3000
+ return unwrapChild(children2[project]);
3001
+ }
2814
3002
  function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2815
3003
  const hasExplicitType = typeof typeOrCombinator === "string";
2816
3004
  const type = hasExplicitType ? typeOrCombinator : void 0;
2817
3005
  const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
2818
- const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
2819
- const opts = hasExplicitType ? maybeOpts : buildOrOpts;
3006
+ const buildArg = hasExplicitType ? buildOrOpts : combinatorOrBuild;
3007
+ const build = typeof buildArg === "function" ? buildArg : void 0;
3008
+ const opts = typeof buildArg === "function" ? maybeOpts : buildArg ?? maybeOpts;
3009
+ const project = normalizeProject(opts?.project);
3010
+ if (project !== void 0 && build !== void 0) {
3011
+ throw new Error("node() options cannot combine project with a build callback");
3012
+ }
2820
3013
  const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
2821
3014
  const meta = {
2822
3015
  firstSet: combinator._meta.firstSet,
@@ -2830,11 +3023,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2830
3023
  if (unwrap && collapse) {
2831
3024
  throw new Error("node() options cannot set both unwrap and collapse");
2832
3025
  }
2833
- const def = unwrap || collapse || captureTrivia || trailingTrivia ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {} } : baseDef;
2834
- const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : true);
2835
- const clonesState = build ? buildReadsState(def) : true;
3026
+ if (project !== void 0 && (unwrap || collapse)) {
3027
+ throw new Error("node() options cannot combine project with unwrap or collapse");
3028
+ }
3029
+ 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;
3030
+ const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3031
+ const clonesState = build ? buildReadsState(def) : project === void 0;
2836
3032
  const hasOwnFields = parserHasOwnFields(combinator);
2837
- const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : true);
3033
+ const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === void 0);
2838
3034
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
2839
3035
  let failExpected;
2840
3036
  return {
@@ -2849,14 +3045,15 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2849
3045
  }
2850
3046
  return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
2851
3047
  }
2852
- const hostCst = build !== void 0 && cstOutputHost(ctx.build);
3048
+ const hasDirectValue = build !== void 0 || project !== void 0;
3049
+ const hostCst = hasDirectValue && cstOutputHost(ctx.build);
2853
3050
  const effTrivia = capturesTrivia || hostCst;
2854
3051
  const effFields = capturesFields || hostCst && hasOwnFields;
2855
3052
  const saved = beginCstNodeCapture(ctx);
2856
3053
  const savedFields = ctx._fields;
2857
3054
  ctx._fields = effFields ? [] : void 0;
2858
3055
  const savedMask = ctx._triviaCaptureMask;
2859
- if (build === void 0 && ctx.build?._parsemanTriviaKinds !== void 0 && def.type !== void 0) {
3056
+ if (build === void 0 && project === void 0 && ctx.build?._parsemanTriviaKinds !== void 0 && def.type !== void 0) {
2860
3057
  ctx._triviaCaptureMask = ctx.build._parsemanTriviaKinds(def.type);
2861
3058
  }
2862
3059
  if (!effTrivia) ctx.captureTrivia = false;
@@ -2874,7 +3071,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2874
3071
  const nodeType = def.type ?? missingInferredType();
2875
3072
  const cstOutput = cstOutputHost(ctx.build);
2876
3073
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
2877
- 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 };
3074
+ 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 };
2878
3075
  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 };
2879
3076
  if (saved.buf !== void 0 || saved.ch !== void 0) {
2880
3077
  pushCstChild(ctx, built, rawEntry);
@@ -3125,7 +3322,7 @@ function leaf(root, fn) {
3125
3322
  }
3126
3323
 
3127
3324
  // src/version.ts
3128
- var PARSEMAN_VERSION = "0.39.0";
3325
+ var PARSEMAN_VERSION = "0.40.0";
3129
3326
 
3130
3327
  // src/cst/host-mode.ts
3131
3328
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3385,6 +3582,7 @@ function analyzeGatingRules(ruleMap, opts) {
3385
3582
  if (d.tag === "dispatch") {
3386
3583
  kids.push(d.selector);
3387
3584
  for (const c of d.cases) kids.push(c.parser);
3585
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3388
3586
  if (d.otherwise !== void 0) kids.push(d.otherwise);
3389
3587
  }
3390
3588
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
@@ -4908,13 +5106,25 @@ function escapeClassChar(ch2) {
4908
5106
  }
4909
5107
 
4910
5108
  // src/compiler/grammar-coverage-ids.ts
5109
+ function idPart(value) {
5110
+ return encodeURIComponent(value);
5111
+ }
5112
+ function dispatchArmIds(def, path2) {
5113
+ const ids = [];
5114
+ for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5115
+ for (const entry of def.matchers ?? []) {
5116
+ ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5117
+ }
5118
+ if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5119
+ return ids;
5120
+ }
4911
5121
  function children(def, winners) {
4912
5122
  switch (def.tag) {
4913
5123
  case "sequence":
4914
5124
  case "choice":
4915
5125
  return def.parsers;
4916
5126
  case "dispatch":
4917
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
5127
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4918
5128
  case "many":
4919
5129
  case "oneOrMore":
4920
5130
  case "optional":
@@ -4957,6 +5167,7 @@ function children(def, winners) {
4957
5167
  function buildGrammarPlan(entry, winners) {
4958
5168
  const definitions = /* @__PURE__ */ new Map();
4959
5169
  const choices = /* @__PURE__ */ new WeakMap();
5170
+ const dispatches = /* @__PURE__ */ new WeakMap();
4960
5171
  const attempts = /* @__PURE__ */ new WeakMap();
4961
5172
  const labels = /* @__PURE__ */ new WeakMap();
4962
5173
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -4987,6 +5198,11 @@ function buildGrammarPlan(entry, winners) {
4987
5198
  choices.set(parser2, ids);
4988
5199
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
4989
5200
  }
5201
+ if (parser2._def.tag === "dispatch") {
5202
+ const ids = dispatchArmIds(parser2._def, path2);
5203
+ dispatches.set(parser2, ids);
5204
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5205
+ }
4990
5206
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
4991
5207
  if (parser2._def.tag === "label") {
4992
5208
  const id = `label:${path2}`;
@@ -4999,7 +5215,7 @@ function buildGrammarPlan(entry, winners) {
4999
5215
  for (const root of roots) {
5000
5216
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
5001
5217
  }
5002
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5218
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
5003
5219
  }
5004
5220
 
5005
5221
  // src/compiler/scannable-run.ts
@@ -6373,6 +6589,7 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6373
6589
  case "literal":
6374
6590
  case "regex":
6375
6591
  case "keywords":
6592
+ case "routed":
6376
6593
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6377
6594
  case "choice": {
6378
6595
  const alts = [];
@@ -6731,7 +6948,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6731
6948
  case "choice":
6732
6949
  return false;
6733
6950
  case "dispatch":
6734
- 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);
6951
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6735
6952
  case "attempt":
6736
6953
  return false;
6737
6954
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6830,6 +7047,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6830
7047
  case "keywords":
6831
7048
  case "node":
6832
7049
  case "token":
7050
+ case "routed":
6833
7051
  case "leaf":
6834
7052
  return true;
6835
7053
  case "not":
@@ -6842,7 +7060,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6842
7060
  case "choice":
6843
7061
  return d.parsers.some((x) => capturesLeaf(x, seen));
6844
7062
  case "dispatch":
6845
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
7063
+ 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);
6846
7064
  case "sepBy":
6847
7065
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6848
7066
  case "many":
@@ -7320,6 +7538,7 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7320
7538
  for (const item of d.cases) {
7321
7539
  if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7322
7540
  }
7541
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7323
7542
  return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7324
7543
  }
7325
7544
  case "attempt":
@@ -7354,53 +7573,120 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7354
7573
  return false;
7355
7574
  }
7356
7575
  }
7357
- function emitDispatchCombinator(def, ctx, pos) {
7576
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7577
+ if (parserUsesRouted2(def.selector)) {
7578
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7579
+ }
7580
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7581
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7582
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7583
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7584
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7585
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7586
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7587
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7588
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7358
7589
  const selector = emit(def.selector, ctx, pos);
7359
7590
  const outV = v(ctx, "_dval");
7360
7591
  const outE = v(ctx, "_dend");
7361
7592
  const keyV = v(ctx, "_dkey");
7362
7593
  const stmts = [
7594
+ ...selectorLeafMark ? [
7595
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7596
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7597
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7598
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7599
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7600
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7601
+ ] : [],
7363
7602
  ...selector.stmts,
7364
7603
  `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7365
7604
  `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7366
7605
  ];
7367
7606
  let wroteBranch = false;
7368
- const emitSelectedTail = (parser2, keyword, condition) => {
7607
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7608
+ const parser3 = entry.parser;
7369
7609
  const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7370
7610
  stmts.push(head);
7371
7611
  ctx.indent++;
7372
- const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7612
+ const usesRouted = branchUsesRouted2(entry);
7613
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7614
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7373
7615
  const errMark = mayError ? v(ctx, "_derr") : null;
7374
7616
  const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7617
+ if (routedV) {
7618
+ stmts.push(
7619
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7620
+ ...selectorLeafMark ? [
7621
+ `${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}`
7622
+ ] : []
7623
+ );
7624
+ }
7375
7625
  if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7376
7626
  if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7377
- const tail = emitTailFallible(parser2, ctx, selector.endVar);
7627
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7628
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7378
7629
  const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7379
7630
  const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7631
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7632
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7380
7633
  stmts.push(
7381
7634
  ...tail.stmts,
7382
- `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7635
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7636
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7383
7637
  `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7384
- `${ind(ctx)}${outE} = ${tail.endVar}`
7638
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7639
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7385
7640
  );
7386
7641
  ctx.indent--;
7387
7642
  stmts.push(`${ind(ctx)}}`);
7388
7643
  };
7644
+ let coverageIndex = 0;
7389
7645
  for (const entry of def.cases) {
7390
- const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7391
- emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7646
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7647
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7392
7648
  wroteBranch = true;
7393
7649
  }
7650
+ const emitMatcherTails = () => {
7651
+ for (const matcher of def.matchers ?? []) {
7652
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7653
+ wroteBranch = true;
7654
+ }
7655
+ };
7394
7656
  if (def.otherwise) {
7395
- if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7396
- else emitSelectedTail(def.otherwise, "if", "true");
7657
+ emitMatcherTails();
7658
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7659
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7660
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7397
7661
  } else {
7662
+ emitMatcherTails();
7398
7663
  const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7399
7664
  const fail = failArrBody(ctx, expected, selector.endVar);
7400
7665
  stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7401
7666
  }
7402
7667
  return { stmts, valueVar: outV, endVar: outE };
7403
7668
  }
7669
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7670
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7671
+ const parts = [`${valueVar}.length === ${key.length}`];
7672
+ for (let i = 0; i < key.length; i++) {
7673
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7674
+ }
7675
+ return parts.join(" && ");
7676
+ }
7677
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7678
+ if (matcher.kind === "matches") {
7679
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7680
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7681
+ }
7682
+ const text = matcher.value;
7683
+ const parts = [`${valueVar}.length >= ${text.length}`];
7684
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7685
+ for (let i = 0; i < text.length; i++) {
7686
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7687
+ }
7688
+ return parts.join(" && ");
7689
+ }
7404
7690
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7405
7691
  const superParser = def.parsers[superIndex];
7406
7692
  const regexDef = getCoreRegexDef(superParser);
@@ -8055,6 +8341,18 @@ function emitPeek(def, ctx, pos) {
8055
8341
  endVar: pos
8056
8342
  };
8057
8343
  }
8344
+ function emitRouted(ctx, pos) {
8345
+ const item = v(ctx, "_rt");
8346
+ return {
8347
+ stmts: [
8348
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8349
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8350
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8351
+ ],
8352
+ valueVar: `${item}.value`,
8353
+ endVar: `${item}.span.end`
8354
+ };
8355
+ }
8058
8356
  function escapeTokenLiteral(value) {
8059
8357
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8060
8358
  }
@@ -8195,14 +8493,20 @@ function emitLeaf(def, ctx, pos) {
8195
8493
  endVar: inner.endVar
8196
8494
  };
8197
8495
  }
8496
+ function emitNodeProjectExpr(type, project, chV) {
8497
+ const child = `${chV}[${project}]`;
8498
+ const missing = `(() => { throw new Error(${JSON.stringify(`node(${JSON.stringify(type)}) project child ${project} was not captured`)}) })()`;
8499
+ const value = `(${child} !== null && typeof ${child} === 'object' && ${child}._tag === 'leaf' ? ${child}.value : ${child})`;
8500
+ return `(${project} in ${chV} ? ${value} : ${missing})`;
8501
+ }
8198
8502
  function emitNode(def, ctx, pos) {
8199
8503
  if (def.type === void 0) {
8200
8504
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
8201
8505
  }
8202
- const structural = def.build === void 0;
8203
- const mkType = structural ? null : analyzeMkInlineBuild(def);
8506
+ const structural = def.build === void 0 && def.project === void 0;
8507
+ const mkType = structural || def.project !== void 0 ? null : analyzeMkInlineBuild(def);
8204
8508
  let buildIdx = null;
8205
- if (!mkType && !structural) {
8509
+ if (!mkType && !structural && def.project === void 0) {
8206
8510
  buildIdx = ctx.buildFns.length;
8207
8511
  ctx.buildFns.push(def.build);
8208
8512
  ctx.buildSrcs.push(def.buildSrc ?? null);
@@ -8210,12 +8514,13 @@ function emitNode(def, ctx, pos) {
8210
8514
  const i = ind(ctx);
8211
8515
  const cstOut = ctx.hostMode === "cst" && !structural;
8212
8516
  if (!structural && !cstOut) ctx.hostBranchElided = true;
8213
- const capturesTrivia = cstOut || mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && buildReadsTrivia(def);
8214
- const clonesState = !structural && (cstOut || buildReadsState(def));
8215
- const capturesChildren = !structural && (cstOut || mkType !== null || def.unwrap || def.collapse || buildReadsChildren(def));
8216
- const capturesRaw = !structural && (cstOut || mkType !== null || buildReadsRaw(def));
8517
+ const hasProject = def.project !== void 0;
8518
+ const capturesTrivia = cstOut || mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && !hasProject && buildReadsTrivia(def);
8519
+ const clonesState = !structural && (cstOut || !hasProject && buildReadsState(def));
8520
+ const capturesChildren = !structural && (cstOut || mkType !== null || def.unwrap || def.collapse || def.project !== void 0 || buildReadsChildren(def));
8521
+ const capturesRaw = !structural && (cstOut || mkType !== null || def.project === void 0 && buildReadsRaw(def));
8217
8522
  const hasFields = parserHasOwnFields(def.parser);
8218
- const capturesFields = hasFields && !structural && (cstOut || buildReadsFields(def));
8523
+ const capturesFields = hasFields && !structural && (cstOut || !hasProject && buildReadsFields(def));
8219
8524
  const innerEnablesTriviaCapture = parserEnablesTriviaCapture(def.parser);
8220
8525
  const chV = v(ctx, "_ch");
8221
8526
  const rawV = v(ctx, "_raw");
@@ -8313,7 +8618,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
8313
8618
  );
8314
8619
  }
8315
8620
  const ndV = v(ctx, "_nd");
8316
- 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})`;
8621
+ 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})`;
8317
8622
  const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV})`;
8318
8623
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
8319
8624
  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;
@@ -8520,7 +8825,7 @@ function emitDispatch(p, ctx, pos) {
8520
8825
  case "choice":
8521
8826
  return emitChoice(p, def, ctx, pos);
8522
8827
  case "dispatch":
8523
- return emitDispatchCombinator(def, ctx, pos);
8828
+ return emitDispatchCombinator(p, def, ctx, pos);
8524
8829
  case "attempt":
8525
8830
  return emitAttempt(p, def, ctx, pos);
8526
8831
  case "many":
@@ -8645,6 +8950,8 @@ function emitDispatch(p, ctx, pos) {
8645
8950
  return emitNot(def, ctx, pos);
8646
8951
  case "peek":
8647
8952
  return emitPeek(def, ctx, pos);
8953
+ case "routed":
8954
+ return emitRouted(ctx, pos);
8648
8955
  case "node":
8649
8956
  return emitNode(def, ctx, pos);
8650
8957
  case "scanTo":
@@ -8742,7 +9049,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8742
9049
  case "choice":
8743
9050
  return d.parsers.some((x) => hasNodeDef(x, seen));
8744
9051
  case "dispatch":
8745
- return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
9052
+ 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);
8746
9053
  case "sepBy":
8747
9054
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8748
9055
  case "scanTo":
@@ -8757,6 +9064,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8757
9064
  return false;
8758
9065
  }
8759
9066
  }
9067
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9068
+ if (seen.has(p)) return false;
9069
+ seen.add(p);
9070
+ const d = p._def;
9071
+ switch (d.tag) {
9072
+ case "routed":
9073
+ return true;
9074
+ case "lazy": {
9075
+ try {
9076
+ return parserUsesRouted2(d.thunk(), seen);
9077
+ } catch {
9078
+ return false;
9079
+ }
9080
+ }
9081
+ case "dispatch":
9082
+ return false;
9083
+ default:
9084
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9085
+ }
9086
+ }
8760
9087
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8761
9088
  if (seen.has(p)) return false;
8762
9089
  seen.add(p);
@@ -8777,7 +9104,7 @@ function childrenOf2(def) {
8777
9104
  case "choice":
8778
9105
  return def.parsers;
8779
9106
  case "dispatch":
8780
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9107
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8781
9108
  case "many":
8782
9109
  case "attempt":
8783
9110
  case "optional":
@@ -8814,6 +9141,7 @@ function childrenOf2(def) {
8814
9141
  case "regex":
8815
9142
  case "keywords":
8816
9143
  case "guard":
9144
+ case "routed":
8817
9145
  case "unknown":
8818
9146
  return [];
8819
9147
  }
@@ -9647,7 +9975,7 @@ function childrenOf3(def) {
9647
9975
  case "choice":
9648
9976
  return def.parsers;
9649
9977
  case "dispatch":
9650
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9978
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9651
9979
  case "many":
9652
9980
  case "oneOrMore":
9653
9981
  case "optional":
@@ -9676,6 +10004,7 @@ function childrenOf3(def) {
9676
10004
  case "literal":
9677
10005
  case "regex":
9678
10006
  case "keywords":
10007
+ case "routed":
9679
10008
  case "guard":
9680
10009
  case "recover":
9681
10010
  case "unknown":
@@ -9700,6 +10029,20 @@ function repeatOpts(min, max, trailing) {
9700
10029
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9701
10030
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9702
10031
  }
10032
+ function projectOpt(project) {
10033
+ if (project === void 0) return void 0;
10034
+ return `project: ${project}`;
10035
+ }
10036
+ function matcherExpr(entry) {
10037
+ switch (entry.kind) {
10038
+ case "startsWith":
10039
+ return `startsWith(${JSON.stringify(entry.value)})`;
10040
+ case "endsWith":
10041
+ return `endsWith(${JSON.stringify(entry.value)})`;
10042
+ case "matches":
10043
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
10044
+ }
10045
+ }
9703
10046
  function serializeRuleMap(ruleMap, scanSkip) {
9704
10047
  try {
9705
10048
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9799,7 +10142,11 @@ function evalRuleMapIR(ir) {
9799
10142
  "choice",
9800
10143
  "dispatch",
9801
10144
  "when",
10145
+ "startsWith",
10146
+ "endsWith",
10147
+ "matches",
9802
10148
  "otherwise",
10149
+ "routed",
9803
10150
  "attempt",
9804
10151
  "many",
9805
10152
  "oneOrMore",
@@ -9835,7 +10182,11 @@ function evalRuleMapIR(ir) {
9835
10182
  choice,
9836
10183
  dispatch,
9837
10184
  when,
10185
+ startsWith,
10186
+ endsWith,
10187
+ matches,
9838
10188
  otherwise,
10189
+ routed,
9839
10190
  attempt,
9840
10191
  many,
9841
10192
  oneOrMore,
@@ -10023,8 +10374,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10023
10374
  }
10024
10375
  case "dispatch": {
10025
10376
  const arms = def.cases.map(
10026
- (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10377
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
10027
10378
  );
10379
+ if (def.matchers) {
10380
+ for (const entry of def.matchers) {
10381
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10382
+ }
10383
+ }
10028
10384
  if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
10029
10385
  return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
10030
10386
  }
@@ -10044,6 +10400,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10044
10400
  return `not(${kid(def.parser)})`;
10045
10401
  case "peek":
10046
10402
  return `peek(${kid(def.parser)})`;
10403
+ case "routed":
10404
+ return "routed()";
10047
10405
  case "trivia":
10048
10406
  return `trivia(${kid(def.parser)})`;
10049
10407
  case "token":
@@ -10068,9 +10426,11 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10068
10426
  }
10069
10427
  case "node": {
10070
10428
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
10429
+ const projectEntry = projectOpt(def.project);
10071
10430
  const optEntries = [
10072
10431
  ...def.unwrap ? ["unwrap: true"] : [],
10073
10432
  ...def.collapse ? ["collapse: true"] : [],
10433
+ ...projectEntry === void 0 ? [] : [projectEntry],
10074
10434
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
10075
10435
  ...def.trailingTrivia ? ["trailingTrivia: true"] : []
10076
10436
  ];
@@ -10513,6 +10873,9 @@ function stripTsFromSource(node2, code) {
10513
10873
  function isWordFactory(v2) {
10514
10874
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
10515
10875
  }
10876
+ function isWhenFactory(v2) {
10877
+ return !!v2 && typeof v2 === "object" && v2.tag === "whenFactory";
10878
+ }
10516
10879
  function isStaticValueEntry(v2) {
10517
10880
  return !!v2 && typeof v2 === "object" && "value" in v2 && "mfSrcs" in v2;
10518
10881
  }
@@ -10535,6 +10898,23 @@ function wordFactoryFromArgs(args, scope, code, mfs) {
10535
10898
  if (typeof caseInsensitive !== "boolean") return null;
10536
10899
  return { tag: "wordFactory", boundary: "_0-9A-Za-z", caseInsensitive };
10537
10900
  }
10901
+ function dispatchWhenOptions(v2) {
10902
+ if (v2 === void 0) return {};
10903
+ if (typeof v2 !== "object" || v2 === null || Array.isArray(v2)) return null;
10904
+ for (const key of Object.keys(v2)) {
10905
+ if (key !== "caseInsensitive") return null;
10906
+ }
10907
+ const caseInsensitive = "caseInsensitive" in v2 ? v2.caseInsensitive : false;
10908
+ if (typeof caseInsensitive !== "boolean") return null;
10909
+ return { caseInsensitive };
10910
+ }
10911
+ function whenFactoryFromArgs(args, scope, code, mfs) {
10912
+ const [optsArg, extra] = args;
10913
+ if (optsArg?.type === "SpreadElement" || extra !== void 0) return null;
10914
+ const rawOpts = optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs);
10915
+ const opts = dispatchWhenOptions(rawOpts);
10916
+ return opts === null ? null : { tag: "whenFactory", caseInsensitive: opts.caseInsensitive ?? false };
10917
+ }
10538
10918
  var SUPPORTED = {
10539
10919
  literal: (...a) => literal(a[0], a[1]),
10540
10920
  regex: (...a) => regex(a[0], a[1]),
@@ -10550,6 +10930,7 @@ var SUPPORTED = {
10550
10930
  field: (...a) => field(a[0], a[1]),
10551
10931
  noTrivia: (...a) => noTrivia(a[0]),
10552
10932
  token: (...a) => token(a[0]),
10933
+ routed: () => routed(),
10553
10934
  leaf: (...a) => leaf(a[0], a[1]),
10554
10935
  expect: (...a) => expect(a[0], a[1])
10555
10936
  };
@@ -10593,6 +10974,18 @@ function dispatchArmValue(node2, scope, code, mfs) {
10593
10974
  if (node2.type !== "CallExpression") return null;
10594
10975
  const callee = node2.callee;
10595
10976
  if (callee.type !== "Identifier") return null;
10977
+ const factory = scope.get(callee.name);
10978
+ if (isWhenFactory(factory)) {
10979
+ const [keyArg, parserArg, extraArg] = node2.arguments;
10980
+ if (!keyArg || !parserArg || extraArg !== void 0 || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10981
+ const key = anyValue(keyArg, scope, code, mfs);
10982
+ const parserValue = anyValue(parserArg, scope, code, mfs);
10983
+ if (!isCombinator(parserValue)) return null;
10984
+ const opts = { caseInsensitive: factory.caseInsensitive };
10985
+ if (typeof key === "string") return when(key, parserValue, opts);
10986
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
10987
+ return null;
10988
+ }
10596
10989
  if (callee.name === "otherwise") {
10597
10990
  const [parserArg] = node2.arguments;
10598
10991
  if (!parserArg || parserArg.type === "SpreadElement") return null;
@@ -10601,13 +10994,16 @@ function dispatchArmValue(node2, scope, code, mfs) {
10601
10994
  return otherwise(parserValue);
10602
10995
  }
10603
10996
  if (callee.name === "when") {
10604
- const [keyArg, parserArg] = node2.arguments;
10605
- if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
10997
+ const [keyArg, parserArg, optsArg] = node2.arguments;
10998
+ if (!keyArg || !parserArg || keyArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || optsArg?.type === "SpreadElement") return null;
10606
10999
  const key = anyValue(keyArg, scope, code, mfs);
10607
11000
  const parserValue = anyValue(parserArg, scope, code, mfs);
11001
+ const opts = dispatchWhenOptions(optsArg === void 0 ? void 0 : anyValue(optsArg, scope, code, mfs));
11002
+ if (opts === null) return null;
10608
11003
  if (!isCombinator(parserValue)) return null;
10609
- if (typeof key === "string") return when(key, parserValue);
10610
- if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue);
11004
+ if (typeof key === "string") return when(key, parserValue, opts);
11005
+ if (Array.isArray(key) && key.every((item) => typeof item === "string")) return when(key, parserValue, opts);
11006
+ if (isDispatchMatcher(key)) return when(key, parserValue, opts);
10611
11007
  }
10612
11008
  return null;
10613
11009
  }
@@ -10618,7 +11014,16 @@ function isDispatchArm(v2) {
10618
11014
  if (!v2 || typeof v2 !== "object") return false;
10619
11015
  const rec = v2;
10620
11016
  if (rec.kind === "otherwise") return isCombinator(rec.parser);
10621
- return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && isCombinator(rec.parser);
11017
+ if (rec.kind === "whenMatcher") {
11018
+ return isDispatchMatcher(rec.matcher) && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11019
+ }
11020
+ return rec.kind === "when" && Array.isArray(rec.keys) && rec.keys.every((key) => typeof key === "string") && typeof rec.caseInsensitive === "boolean" && isCombinator(rec.parser);
11021
+ }
11022
+ function isDispatchMatcher(v2) {
11023
+ if (!v2 || typeof v2 !== "object") return false;
11024
+ const rec = v2;
11025
+ if ((rec.kind === "startsWith" || rec.kind === "endsWith") && typeof rec.value === "string") return true;
11026
+ return rec.kind === "matches" && typeof rec.value === "string" && typeof rec.flags === "string";
10622
11027
  }
10623
11028
  function scopeGet(scope, name, mfs) {
10624
11029
  const entry = scope.get(name);
@@ -10630,6 +11035,18 @@ function scopeGet(scope, name, mfs) {
10630
11035
  if (isCombinator(entry)) return entry;
10631
11036
  return null;
10632
11037
  }
11038
+ function staticLiteralValue(expr) {
11039
+ const val = expr;
11040
+ return val.type === "Literal" || val.type === "BooleanLiteral" || val.type === "NumericLiteral" ? val.value : void 0;
11041
+ }
11042
+ function staticNodeProject(expr) {
11043
+ const literalValue = staticLiteralValue(expr);
11044
+ if (typeof literalValue === "number" && Number.isInteger(literalValue) && literalValue >= 0) {
11045
+ return { ok: true, value: literalValue };
11046
+ }
11047
+ if (typeof literalValue === "number" || literalValue !== void 0) return { ok: false };
11048
+ return { ok: false };
11049
+ }
10633
11050
  function staticNodeOptions(expr) {
10634
11051
  if (expr.type !== "ObjectExpression") return void 0;
10635
11052
  const opts = {};
@@ -10639,11 +11056,14 @@ function staticNodeOptions(expr) {
10639
11056
  const key = p.key;
10640
11057
  const name = key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
10641
11058
  if (name === "unwrap" || name === "collapse" || name === "captureTrivia" || name === "trailingTrivia") {
10642
- const val = p.value;
10643
- if ((val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true) opts[name] = true;
11059
+ if (staticLiteralValue(p.value) === true) opts[name] = true;
11060
+ } else if (name === "project") {
11061
+ const project = staticNodeProject(p.value);
11062
+ if (project?.ok === false) return { project: -1 };
11063
+ if (project !== void 0) opts.project = project.value;
10644
11064
  }
10645
11065
  }
10646
- return opts.unwrap || opts.collapse || opts.captureTrivia || opts.trailingTrivia ? opts : void 0;
11066
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia ? opts : void 0;
10647
11067
  }
10648
11068
  function exprToCombi(node2, scope, code, mfs) {
10649
11069
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -10718,9 +11138,10 @@ function exprToCombi(node2, scope, code, mfs) {
10718
11138
  const inner = anyValue(parserArg, scope, code, mfs);
10719
11139
  if (!isCombinator(inner)) return null;
10720
11140
  const be = buildArg;
10721
- const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
11141
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && be.type !== "ObjectExpression" && !(be.type === "Identifier" && be.name === "undefined");
10722
11142
  const buildSrc = hasBuild ? stripTsFromSource(be, code) : void 0;
10723
- const opts = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticNodeOptions(optsArg) : void 0;
11143
+ const optionsArg = hasBuild ? optsArg : be?.type === "ObjectExpression" ? buildArg : optsArg;
11144
+ const opts = optionsArg !== void 0 && optionsArg.type !== "SpreadElement" ? staticNodeOptions(optionsArg) : void 0;
10724
11145
  try {
10725
11146
  const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
10726
11147
  if (combi._def.tag === "node" && buildSrc !== void 0) {
@@ -11004,9 +11425,27 @@ function anyValue(node2, scope, code, mfs) {
11004
11425
  if (callee.type === "Identifier" && callee.name === "makeWord") {
11005
11426
  return wordFactoryFromArgs(node2.arguments, scope, code, mfs);
11006
11427
  }
11428
+ if (callee.type === "Identifier" && callee.name === "makeWhen") {
11429
+ return whenFactoryFromArgs(node2.arguments, scope, code, mfs);
11430
+ }
11431
+ if (callee.type === "Identifier" && (callee.name === "startsWith" || callee.name === "endsWith")) {
11432
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11433
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11434
+ if (typeof value !== "string") return null;
11435
+ return callee.name === "startsWith" ? startsWith(value) : endsWith(value);
11436
+ }
11437
+ if (callee.type === "Identifier" && callee.name === "matches") {
11438
+ if (node2.arguments.length !== 1 || node2.arguments[0]?.type === "SpreadElement") return null;
11439
+ const value = anyValue(node2.arguments[0], scope, code, mfs);
11440
+ if (!(value instanceof RegExp)) return null;
11441
+ return matches(value);
11442
+ }
11007
11443
  if (callee.type === "Identifier" && (callee.name === "when" || callee.name === "otherwise")) {
11008
11444
  return dispatchArmValue(node2, scope, code, mfs);
11009
11445
  }
11446
+ if (callee.type === "Identifier" && isWhenFactory(scope.get(callee.name))) {
11447
+ return dispatchArmValue(node2, scope, code, mfs);
11448
+ }
11010
11449
  return exprToCombi(node2, scope, code, mfs);
11011
11450
  }
11012
11451
  return null;
@@ -11017,6 +11456,12 @@ function evaluateWordFactory(node2, scope, code) {
11017
11456
  if (callee.type !== "Identifier" || callee.name !== "makeWord") return null;
11018
11457
  return wordFactoryFromArgs(node2.arguments, scope, code);
11019
11458
  }
11459
+ function evaluateWhenFactory(node2, scope, code) {
11460
+ if (node2.type !== "CallExpression") return null;
11461
+ const callee = node2.callee;
11462
+ if (callee.type !== "Identifier" || callee.name !== "makeWhen") return null;
11463
+ return whenFactoryFromArgs(node2.arguments, scope, code);
11464
+ }
11020
11465
  function evaluateExpr(node2, scope, code, mapFnSources) {
11021
11466
  return exprToCombi(node2, scope, code, mapFnSources);
11022
11467
  }
@@ -11597,7 +12042,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
11597
12042
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
11598
12043
  return [...ids].sort().map((id2) => ({
11599
12044
  id: id2,
11600
- kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
12045
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
11601
12046
  }));
11602
12047
  };
11603
12048
  const localComposedCarried = /* @__PURE__ */ new Map();
@@ -12043,6 +12488,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12043
12488
  if (wordFactory) {
12044
12489
  ;
12045
12490
  scope.set(varName, wordFactory);
12491
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12492
+ continue;
12493
+ }
12494
+ const whenFactory = evaluateWhenFactory(init, scope, code);
12495
+ if (whenFactory) {
12496
+ ;
12497
+ scope.set(varName, whenFactory);
12498
+ replacements.push({ start: init.start, end: init.end, replacement: "undefined" });
12046
12499
  continue;
12047
12500
  }
12048
12501
  const combiArray = evaluateCombinatorArray(init, scope, code);