parseman 0.39.0 → 0.39.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -51,6 +51,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
51
51
  return d.value.length === 0;
52
52
  case "keywords":
53
53
  return false;
54
+ case "routed":
55
+ return false;
54
56
  case "regex":
55
57
  try {
56
58
  const m = new RegExp(d.source).exec("");
@@ -1112,6 +1114,8 @@ function derive(c, seen) {
1112
1114
  case "not":
1113
1115
  case "peek":
1114
1116
  return deriveExpected2(def.parser);
1117
+ case "routed":
1118
+ return ["routed()"];
1115
1119
  case "lazy": {
1116
1120
  const name = c._ruleName;
1117
1121
  if (seen.has(c)) return name ? [name] : [];
@@ -1936,42 +1940,198 @@ function sequence(...parsers) {
1936
1940
  }
1937
1941
 
1938
1942
  // src/combinators/dispatch.ts
1939
- function when(key, parser2) {
1943
+ function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
1944
+ if (seen.has(parser2)) return false;
1945
+ seen.add(parser2);
1946
+ const def = parser2._def;
1947
+ switch (def.tag) {
1948
+ case "routed":
1949
+ return true;
1950
+ case "sequence":
1951
+ case "choice":
1952
+ return def.parsers.some((entry) => parserUsesRouted(entry, seen));
1953
+ case "dispatch":
1954
+ return false;
1955
+ case "sepBy":
1956
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
1957
+ case "skip":
1958
+ return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
1959
+ case "grammar":
1960
+ return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
1961
+ case "scanTo":
1962
+ return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
1963
+ case "recover":
1964
+ return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
1965
+ case "lazy":
1966
+ try {
1967
+ return parserUsesRouted(def.thunk(), seen);
1968
+ } catch {
1969
+ return false;
1970
+ }
1971
+ case "many":
1972
+ case "oneOrMore":
1973
+ case "optional":
1974
+ case "attempt":
1975
+ case "transform":
1976
+ case "trivia":
1977
+ case "token":
1978
+ case "leaf":
1979
+ case "label":
1980
+ case "field":
1981
+ case "not":
1982
+ case "peek":
1983
+ case "node":
1984
+ case "withCtx":
1985
+ case "expect":
1986
+ return parserUsesRouted(def.parser, seen);
1987
+ default:
1988
+ return false;
1989
+ }
1990
+ }
1991
+ function branchUsesRouted(branch) {
1992
+ return branch.usesRouted === true || parserUsesRouted(branch.parser);
1993
+ }
1994
+ function asciiFoldKey(key) {
1995
+ let out = "";
1996
+ for (let i = 0; i < key.length; i++) {
1997
+ const c = key.charCodeAt(i);
1998
+ out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
1999
+ }
2000
+ return out;
2001
+ }
2002
+ function matchesDispatchMatcher(value, matcher) {
2003
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2004
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2005
+ switch (matcher.kind) {
2006
+ case "startsWith":
2007
+ return candidate.startsWith(expected);
2008
+ case "endsWith":
2009
+ return candidate.endsWith(expected);
2010
+ case "matches": {
2011
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2012
+ return new RegExp(matcher.value, flags).test(value);
2013
+ }
2014
+ }
2015
+ }
2016
+ function isStringMatcher(value) {
2017
+ if (typeof value !== "object" || value === null) return false;
2018
+ const kind = value.kind;
2019
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2020
+ }
2021
+ function fixedMatcher(kind, value) {
2022
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2023
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2024
+ return { kind, value };
2025
+ }
2026
+ function startsWith(prefix) {
2027
+ return fixedMatcher("startsWith", prefix);
2028
+ }
2029
+ function endsWith(suffix) {
2030
+ return fixedMatcher("endsWith", suffix);
2031
+ }
2032
+ function matches(pattern) {
2033
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2034
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2035
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2036
+ }
2037
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2038
+ }
2039
+ function when(key, parser2, opts = {}) {
2040
+ const usesRouted = parserUsesRouted(parser2);
2041
+ if (isStringMatcher(key)) {
2042
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2043
+ }
1940
2044
  const keys = Array.isArray(key) ? [...key] : [key];
1941
2045
  if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
1942
2046
  for (const item of keys) {
1943
2047
  if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
1944
2048
  }
1945
- return { kind: "when", keys, parser: parser2 };
2049
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2050
+ }
2051
+ function makeWhen(opts = {}) {
2052
+ return (key, parser2) => when(key, parser2, opts);
1946
2053
  }
1947
2054
  function otherwise(parser2) {
1948
- return { kind: "otherwise", parser: parser2 };
2055
+ return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2056
+ }
2057
+ function routed() {
2058
+ return {
2059
+ _tag: "routed",
2060
+ _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2061
+ _def: { tag: "routed" },
2062
+ parse(_input, pos, ctx) {
2063
+ const item = ctx._routed;
2064
+ if (item === void 0) {
2065
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2066
+ }
2067
+ if (pos !== item.span.start) {
2068
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2069
+ }
2070
+ if (cstCaptureActive(ctx)) {
2071
+ pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
2072
+ }
2073
+ return { ok: true, value: item.value, span: item.span };
2074
+ }
2075
+ };
1949
2076
  }
1950
2077
  function dispatch(selector, ...arms) {
2078
+ if (parserUsesRouted(selector)) {
2079
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2080
+ }
1951
2081
  let fallback;
2082
+ let fallbackUsesRouted = false;
2083
+ const matchers = [];
1952
2084
  const cases = [];
1953
- const seen = /* @__PURE__ */ new Set();
2085
+ const seen = [];
1954
2086
  for (let i = 0; i < arms.length; i++) {
1955
2087
  const arm = arms[i];
1956
2088
  if (arm.kind === "otherwise") {
1957
2089
  if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
1958
2090
  if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
1959
2091
  fallback = arm.parser;
2092
+ fallbackUsesRouted = arm.usesRouted === true;
1960
2093
  continue;
1961
2094
  }
1962
- for (const key of arm.keys) {
1963
- if (seen.has(key)) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
1964
- seen.add(key);
2095
+ if (arm.kind === "whenMatcher") {
2096
+ const matcher = arm.matcher;
2097
+ matchers.push({
2098
+ kind: matcher.kind,
2099
+ value: matcher.value,
2100
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2101
+ parser: arm.parser,
2102
+ caseInsensitive: arm.caseInsensitive,
2103
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2104
+ });
2105
+ continue;
1965
2106
  }
1966
- cases.push({ keys: arm.keys, parser: arm.parser });
2107
+ for (const key of arm.keys) {
2108
+ const folded = asciiFoldKey(key);
2109
+ for (const prior of seen) {
2110
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2111
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2112
+ }
2113
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2114
+ }
2115
+ cases.push({
2116
+ keys: arm.keys,
2117
+ parser: arm.parser,
2118
+ caseInsensitive: arm.caseInsensitive,
2119
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2120
+ });
1967
2121
  }
1968
2122
  const meta = {
1969
2123
  firstSet: selector._meta.firstSet,
1970
- canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2124
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
1971
2125
  isTrivia: false
1972
2126
  };
1973
2127
  const byKey = /* @__PURE__ */ new Map();
1974
- for (const entry of cases) for (const key of entry.keys) byKey.set(key, entry.parser);
2128
+ const byFoldedKey = /* @__PURE__ */ new Map();
2129
+ for (const entry of cases) {
2130
+ for (const key of entry.keys) {
2131
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2132
+ else byKey.set(key, entry);
2133
+ }
2134
+ }
1975
2135
  return {
1976
2136
  _tag: "dispatch",
1977
2137
  _meta: meta,
@@ -1979,18 +2139,35 @@ function dispatch(selector, ...arms) {
1979
2139
  tag: "dispatch",
1980
2140
  selector,
1981
2141
  cases,
1982
- ...fallback === void 0 ? {} : { otherwise: fallback }
2142
+ ...matchers.length === 0 ? {} : { matchers },
2143
+ ...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
1983
2144
  },
1984
2145
  parse(input, pos, ctx) {
2146
+ if (parserUsesRouted(selector)) {
2147
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2148
+ }
2149
+ const selectorMark = saveTriviaMark(ctx);
1985
2150
  const selected = selector.parse(input, pos, ctx);
1986
2151
  if (!selected.ok) return selected;
1987
- const tail = byKey.get(selected.value) ?? fallback;
1988
- if (tail === void 0) {
2152
+ const branch = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher)) ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
2153
+ if (branch === void 0) {
1989
2154
  const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
1990
2155
  return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
1991
2156
  }
1992
- const mark = saveTriviaMark(ctx);
1993
- const result = tail.parse(input, selected.span.end, ctx);
2157
+ const savedRouted = ctx._routed;
2158
+ const usesRouted = branchUsesRouted(branch);
2159
+ let mark = saveTriviaMark(ctx);
2160
+ if (usesRouted) {
2161
+ rollbackTrivia(ctx, selectorMark);
2162
+ mark = saveTriviaMark(ctx);
2163
+ ctx._routed = { value: selected.value, span: selected.span };
2164
+ }
2165
+ let result;
2166
+ try {
2167
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2168
+ } finally {
2169
+ if (usesRouted) ctx._routed = savedRouted;
2170
+ }
1994
2171
  if (!result.ok) {
1995
2172
  rollbackTrivia(ctx, mark);
1996
2173
  return { ...result, committed: true };
@@ -2377,6 +2554,7 @@ function markUnusedValues(root) {
2377
2554
  case "dispatch":
2378
2555
  visit(def.selector, consumed);
2379
2556
  for (const entry of def.cases) visit(entry.parser, consumed);
2557
+ if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
2380
2558
  if (def.otherwise) visit(def.otherwise, consumed);
2381
2559
  return;
2382
2560
  case "node":
@@ -2655,7 +2833,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2655
2833
  case "choice":
2656
2834
  return d.parsers.some((x) => parserHasOwnFields(x, seen));
2657
2835
  case "dispatch":
2658
- return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2836
+ return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasOwnFields(entry.parser, seen)) : false) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
2659
2837
  case "sepBy":
2660
2838
  return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
2661
2839
  case "skip":
@@ -2724,7 +2902,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2724
2902
  case "choice":
2725
2903
  return d.parsers.some((x) => parserHasTriviaSite(x, seen));
2726
2904
  case "dispatch":
2727
- return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2905
+ return parserHasTriviaSite(d.selector, seen) || d.cases.some((x) => parserHasTriviaSite(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasTriviaSite(entry.parser, seen)) : false) || (d.otherwise ? parserHasTriviaSite(d.otherwise, seen) : false);
2728
2906
  case "grammar":
2729
2907
  return parserHasTriviaSite(d.parser, seen);
2730
2908
  case "lazy": {
@@ -2751,7 +2929,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2751
2929
  case "choice":
2752
2930
  return d.parsers.some((x) => parserEnablesTriviaCapture(x, seen));
2753
2931
  case "dispatch":
2754
- return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2932
+ return parserEnablesTriviaCapture(d.selector, seen) || d.cases.some((x) => parserEnablesTriviaCapture(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserEnablesTriviaCapture(entry.parser, seen)) : false) || (d.otherwise ? parserEnablesTriviaCapture(d.otherwise, seen) : false);
2755
2933
  case "sepBy":
2756
2934
  return parserEnablesTriviaCapture(d.parser, seen) || parserEnablesTriviaCapture(d.separator, seen);
2757
2935
  case "skip":
@@ -3153,7 +3331,7 @@ function leaf(root, fn) {
3153
3331
  }
3154
3332
 
3155
3333
  // src/version.ts
3156
- var PARSEMAN_VERSION = "0.39.0";
3334
+ var PARSEMAN_VERSION = "0.39.1";
3157
3335
 
3158
3336
  // src/cst/host-mode.ts
3159
3337
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3413,6 +3591,7 @@ function analyzeGatingRules(ruleMap, opts) {
3413
3591
  if (d.tag === "dispatch") {
3414
3592
  kids.push(d.selector);
3415
3593
  for (const c of d.cases) kids.push(c.parser);
3594
+ if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3416
3595
  if (d.otherwise !== void 0) kids.push(d.otherwise);
3417
3596
  }
3418
3597
  if (Array.isArray(rec.skip)) kids.push(...rec.skip);
@@ -4936,13 +5115,25 @@ function escapeClassChar(ch2) {
4936
5115
  }
4937
5116
 
4938
5117
  // src/compiler/grammar-coverage-ids.ts
5118
+ function idPart(value) {
5119
+ return encodeURIComponent(value);
5120
+ }
5121
+ function dispatchArmIds(def, path) {
5122
+ const ids = [];
5123
+ for (const entry of def.cases) ids.push(`dispatch:${path}/when:${entry.keys.map(idPart).join("|")}`);
5124
+ for (const entry of def.matchers ?? []) {
5125
+ ids.push(`dispatch:${path}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5126
+ }
5127
+ if (def.otherwise) ids.push(`dispatch:${path}/otherwise`);
5128
+ return ids;
5129
+ }
4939
5130
  function children(def, winners) {
4940
5131
  switch (def.tag) {
4941
5132
  case "sequence":
4942
5133
  case "choice":
4943
5134
  return def.parsers;
4944
5135
  case "dispatch":
4945
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
5136
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
4946
5137
  case "many":
4947
5138
  case "oneOrMore":
4948
5139
  case "optional":
@@ -4985,6 +5176,7 @@ function children(def, winners) {
4985
5176
  function buildGrammarPlan(entry, winners) {
4986
5177
  const definitions = /* @__PURE__ */ new Map();
4987
5178
  const choices = /* @__PURE__ */ new WeakMap();
5179
+ const dispatches = /* @__PURE__ */ new WeakMap();
4988
5180
  const attempts = /* @__PURE__ */ new WeakMap();
4989
5181
  const labels = /* @__PURE__ */ new WeakMap();
4990
5182
  const rules2 = /* @__PURE__ */ new WeakMap();
@@ -5015,6 +5207,11 @@ function buildGrammarPlan(entry, winners) {
5015
5207
  choices.set(parser2, ids);
5016
5208
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5017
5209
  }
5210
+ if (parser2._def.tag === "dispatch") {
5211
+ const ids = dispatchArmIds(parser2._def, path);
5212
+ dispatches.set(parser2, ids);
5213
+ ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5214
+ }
5018
5215
  if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path}`);
5019
5216
  if (parser2._def.tag === "label") {
5020
5217
  const id = `label:${path}`;
@@ -5027,7 +5224,7 @@ function buildGrammarPlan(entry, winners) {
5027
5224
  for (const root of roots) {
5028
5225
  visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
5029
5226
  }
5030
- return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
5227
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, dispatches, attempts, labels, rules: rules2 };
5031
5228
  }
5032
5229
 
5033
5230
  // src/compiler/scannable-run.ts
@@ -6401,6 +6598,7 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
6401
6598
  case "literal":
6402
6599
  case "regex":
6403
6600
  case "keywords":
6601
+ case "routed":
6404
6602
  return { alts: [[seg(p._meta.firstSet, matchesEmpty(p))]] };
6405
6603
  case "choice": {
6406
6604
  const alts = [];
@@ -6751,7 +6949,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
6751
6949
  case "choice":
6752
6950
  return false;
6753
6951
  case "dispatch":
6754
- 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);
6952
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((x) => mayLeavePartialCapture(x.parser, seen) || capturesLeaf(x.parser, seen)) : false) || (d.otherwise ? mayLeavePartialCapture(d.otherwise, seen) || capturesLeaf(d.otherwise, seen) : false);
6755
6953
  case "attempt":
6756
6954
  return false;
6757
6955
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
@@ -6850,6 +7048,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6850
7048
  case "keywords":
6851
7049
  case "node":
6852
7050
  case "token":
7051
+ case "routed":
6853
7052
  case "leaf":
6854
7053
  return true;
6855
7054
  case "not":
@@ -6862,7 +7061,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
6862
7061
  case "choice":
6863
7062
  return d.parsers.some((x) => capturesLeaf(x, seen));
6864
7063
  case "dispatch":
6865
- return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
7064
+ return capturesLeaf(d.selector, seen) || d.cases.some((x) => capturesLeaf(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => capturesLeaf(entry.parser, seen)) : false) || (d.otherwise ? capturesLeaf(d.otherwise, seen) : false);
6866
7065
  case "sepBy":
6867
7066
  return capturesLeaf(d.parser, seen) || capturesLeaf(d.separator, seen);
6868
7067
  case "many":
@@ -7340,6 +7539,7 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7340
7539
  for (const item of d.cases) {
7341
7540
  if (mayRecordRecoveryError(item.parser, recovery, seen)) return true;
7342
7541
  }
7542
+ if (d.matchers?.some((item) => mayRecordRecoveryError(item.parser, recovery, seen))) return true;
7343
7543
  return d.otherwise ? mayRecordRecoveryError(d.otherwise, recovery, seen) : false;
7344
7544
  }
7345
7545
  case "attempt":
@@ -7374,53 +7574,120 @@ function mayRecordRecoveryError(p, recovery, seen = /* @__PURE__ */ new Set()) {
7374
7574
  return false;
7375
7575
  }
7376
7576
  }
7377
- function emitDispatchCombinator(def, ctx, pos) {
7577
+ function emitDispatchCombinator(parser2, def, ctx, pos) {
7578
+ if (parserUsesRouted2(def.selector)) {
7579
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
7580
+ }
7581
+ const coverageIds = ctx.coverage?.plan.dispatches.get(parser2) ?? [];
7582
+ const branchUsesRouted2 = (entry) => entry.usesRouted === true || parserUsesRouted2(entry.parser);
7583
+ const hasRoutedBranch = def.cases.some(branchUsesRouted2) || (def.matchers ? def.matchers.some(branchUsesRouted2) : false) || (def.otherwise ? def.otherwiseUsesRouted === true || parserUsesRouted2(def.otherwise) : false);
7584
+ const selectorLeafMark = hasRoutedBranch ? v(ctx, "_dsl") : null;
7585
+ const selectorRawMark = hasRoutedBranch ? v(ctx, "_dsr") : null;
7586
+ const selectorTriviaMark = hasRoutedBranch ? v(ctx, "_dst") : null;
7587
+ const selectorLogMark = hasRoutedBranch ? v(ctx, "_dsg") : null;
7588
+ const selectorFieldMark = hasRoutedBranch ? v(ctx, "_dsf") : null;
7589
+ const selectorErrorMark = hasRoutedBranch ? v(ctx, "_dse") : null;
7378
7590
  const selector = emit(def.selector, ctx, pos);
7379
7591
  const outV = v(ctx, "_dval");
7380
7592
  const outE = v(ctx, "_dend");
7381
7593
  const keyV = v(ctx, "_dkey");
7382
7594
  const stmts = [
7595
+ ...selectorLeafMark ? [
7596
+ `${ind(ctx)}const ${selectorLeafMark} = _ctx._cstLeaves?.length ?? 0`,
7597
+ `${ind(ctx)}const ${selectorRawMark} = _ctx._cstRawChildren?.length ?? 0`,
7598
+ `${ind(ctx)}const ${selectorTriviaMark} = _ctx._cstTriviaLog?.length ?? 0`,
7599
+ `${ind(ctx)}const ${selectorLogMark} = _ctx._triviaLog?.length ?? 0`,
7600
+ `${ind(ctx)}const ${selectorFieldMark} = _ctx._fields?.length ?? 0`,
7601
+ `${ind(ctx)}const ${selectorErrorMark} = _ctx._errors?.length ?? 0`
7602
+ ] : [],
7383
7603
  ...selector.stmts,
7384
7604
  `${ind(ctx)}const ${keyV} = ${selector.valueVar}`,
7385
7605
  `${ind(ctx)}let ${outV}, ${outE} = ${selector.endVar}`
7386
7606
  ];
7387
7607
  let wroteBranch = false;
7388
- const emitSelectedTail = (parser2, keyword, condition) => {
7608
+ const emitSelectedTail = (entry, keyword, condition, coverageId) => {
7609
+ const parser3 = entry.parser;
7389
7610
  const head = keyword === "else" ? `${ind(ctx)}else {` : `${ind(ctx)}${keyword} (${condition}) {`;
7390
7611
  stmts.push(head);
7391
7612
  ctx.indent++;
7392
- const mayError = mayRecordRecoveryError(parser2, !!ctx.recovery);
7613
+ const usesRouted = branchUsesRouted2(entry);
7614
+ const routedV = usesRouted ? v(ctx, "_drt") : null;
7615
+ const mayError = mayRecordRecoveryError(parser3, !!ctx.recovery);
7393
7616
  const errMark = mayError ? v(ctx, "_derr") : null;
7394
7617
  const logMark = ctx.activeTrivia ? v(ctx, "_dlog") : null;
7618
+ if (routedV) {
7619
+ stmts.push(
7620
+ `${ind(ctx)}const ${routedV} = _ctx._routed; _ctx._routed = { value: ${selector.valueVar}, span: { start: ${pos}, end: ${selector.endVar} } }`,
7621
+ ...selectorLeafMark ? [
7622
+ `${ind(ctx)}if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${selectorLeafMark}) _ctx._cstLeaves.length = ${selectorLeafMark}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${selectorRawMark}) _ctx._cstRawChildren.length = ${selectorRawMark}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${selectorTriviaMark}) _ctx._cstTriviaLog.length = ${selectorTriviaMark}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${selectorLogMark}) _ctx._triviaLog.length = ${selectorLogMark}; if (_ctx._fields && _ctx._fields.length !== ${selectorFieldMark}) _ctx._fields.length = ${selectorFieldMark}; if (_ctx._errors && _ctx._errors.length !== ${selectorErrorMark}) _ctx._errors.length = ${selectorErrorMark}`
7623
+ ] : []
7624
+ );
7625
+ }
7395
7626
  if (errMark) stmts.push(`${ind(ctx)}const ${errMark} = _ctx._errors?.length ?? 0`);
7396
7627
  if (logMark) stmts.push(`${ind(ctx)}const ${logMark} = _ctx._triviaLog?.length ?? 0`);
7397
- const tail = emitTailFallible(parser2, ctx, selector.endVar);
7628
+ const tail = emitTailFallible(parser3, ctx, usesRouted ? pos : selector.endVar);
7629
+ stmts.push(...coverageAttempt(ctx, coverageId, pos));
7398
7630
  const errorRollback = errMark ? `if (_ctx._errors && _ctx._errors.length !== ${errMark}) _ctx._errors.length = ${errMark}; ` : "";
7399
7631
  const logRollback = logMark ? `if (_ctx._triviaLog && _ctx._triviaLog.length !== ${logMark}) _ctx._triviaLog.length = ${logMark}; ` : "";
7632
+ const routedRollback = routedV ? `_ctx._routed = ${routedV}; ` : "";
7633
+ const coverageFailure = coverageId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageId)}, phase: 'failure', offset: _ctx._fe }); `;
7400
7634
  stmts.push(
7401
7635
  ...tail.stmts,
7402
- `${ind(ctx)}if (!${tail.okVar}) { ${errorRollback}${logRollback}${committedFailBody(ctx)} }`,
7636
+ `${ind(ctx)}if (!${tail.okVar}) { ${routedRollback}${errorRollback}${logRollback}${coverageFailure}${committedFailBody(ctx)} }`,
7637
+ ...routedV ? [`${ind(ctx)}_ctx._routed = ${routedV}`] : [],
7403
7638
  `${ind(ctx)}${outV} = [${selector.valueVar}, ${tail.valVar}]`,
7404
- `${ind(ctx)}${outE} = ${tail.endVar}`
7639
+ `${ind(ctx)}${outE} = ${tail.endVar}`,
7640
+ ...coverageHit(ctx, coverageId, pos, tail.endVar)
7405
7641
  );
7406
7642
  ctx.indent--;
7407
7643
  stmts.push(`${ind(ctx)}}`);
7408
7644
  };
7645
+ let coverageIndex = 0;
7409
7646
  for (const entry of def.cases) {
7410
- const condition = entry.keys.map((key) => `${keyV} === ${JSON.stringify(key)}`).join(" || ");
7411
- emitSelectedTail(entry.parser, wroteBranch ? "else if" : "if", condition);
7647
+ const condition = entry.keys.map((key) => emitDispatchKeyCondition(keyV, key, entry.caseInsensitive)).join(" || ");
7648
+ emitSelectedTail(entry, wroteBranch ? "else if" : "if", condition, coverageIds[coverageIndex++]);
7412
7649
  wroteBranch = true;
7413
7650
  }
7651
+ const emitMatcherTails = () => {
7652
+ for (const matcher of def.matchers ?? []) {
7653
+ emitSelectedTail(matcher, wroteBranch ? "else if" : "if", emitDispatchMatcherCondition(keyV, matcher), coverageIds[coverageIndex++]);
7654
+ wroteBranch = true;
7655
+ }
7656
+ };
7414
7657
  if (def.otherwise) {
7415
- if (wroteBranch) emitSelectedTail(def.otherwise, "else");
7416
- else emitSelectedTail(def.otherwise, "if", "true");
7658
+ emitMatcherTails();
7659
+ const otherwiseEntry = { parser: def.otherwise, usesRouted: def.otherwiseUsesRouted };
7660
+ if (wroteBranch) emitSelectedTail(otherwiseEntry, "else", void 0, coverageIds[coverageIndex++]);
7661
+ else emitSelectedTail(otherwiseEntry, "if", "true", coverageIds[coverageIndex++]);
7417
7662
  } else {
7663
+ emitMatcherTails();
7418
7664
  const expected = JSON.stringify(def.cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key))));
7419
7665
  const fail = failArrBody(ctx, expected, selector.endVar);
7420
7666
  stmts.push(wroteBranch ? `${ind(ctx)}else { ${fail} }` : `${ind(ctx)}${fail}`);
7421
7667
  }
7422
7668
  return { stmts, valueVar: outV, endVar: outE };
7423
7669
  }
7670
+ function emitDispatchKeyCondition(valueVar, key, caseInsensitive) {
7671
+ if (!caseInsensitive) return `${valueVar} === ${JSON.stringify(key)}`;
7672
+ const parts = [`${valueVar}.length === ${key.length}`];
7673
+ for (let i = 0; i < key.length; i++) {
7674
+ parts.push(foldEq(`${valueVar}.charCodeAt(${i})`, key.charCodeAt(i)));
7675
+ }
7676
+ return parts.join(" && ");
7677
+ }
7678
+ function emitDispatchMatcherCondition(valueVar, matcher) {
7679
+ if (matcher.kind === "matches") {
7680
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
7681
+ return `${new RegExp(matcher.value, flags).toString()}.test(${valueVar})`;
7682
+ }
7683
+ const text = matcher.value;
7684
+ const parts = [`${valueVar}.length >= ${text.length}`];
7685
+ const offset = matcher.kind === "endsWith" ? `${valueVar}.length - ${text.length}` : "0";
7686
+ for (let i = 0; i < text.length; i++) {
7687
+ parts.push(matcher.caseInsensitive ? foldEq(`${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`})`, text.charCodeAt(i)) : `${valueVar}.charCodeAt(${offset}${i === 0 ? "" : ` + ${i}`}) === ${text.charCodeAt(i)}`);
7688
+ }
7689
+ return parts.join(" && ");
7690
+ }
7424
7691
  function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
7425
7692
  const superParser = def.parsers[superIndex];
7426
7693
  const regexDef = getCoreRegexDef(superParser);
@@ -8075,6 +8342,18 @@ function emitPeek(def, ctx, pos) {
8075
8342
  endVar: pos
8076
8343
  };
8077
8344
  }
8345
+ function emitRouted(ctx, pos) {
8346
+ const item = v(ctx, "_rt");
8347
+ return {
8348
+ stmts: [
8349
+ `${ind(ctx)}const ${item} = _ctx._routed`,
8350
+ ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8351
+ ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8352
+ ],
8353
+ valueVar: `${item}.value`,
8354
+ endVar: `${item}.span.end`
8355
+ };
8356
+ }
8078
8357
  function escapeTokenLiteral(value) {
8079
8358
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8080
8359
  }
@@ -8540,7 +8819,7 @@ function emitDispatch(p, ctx, pos) {
8540
8819
  case "choice":
8541
8820
  return emitChoice(p, def, ctx, pos);
8542
8821
  case "dispatch":
8543
- return emitDispatchCombinator(def, ctx, pos);
8822
+ return emitDispatchCombinator(p, def, ctx, pos);
8544
8823
  case "attempt":
8545
8824
  return emitAttempt(p, def, ctx, pos);
8546
8825
  case "many":
@@ -8665,6 +8944,8 @@ function emitDispatch(p, ctx, pos) {
8665
8944
  return emitNot(def, ctx, pos);
8666
8945
  case "peek":
8667
8946
  return emitPeek(def, ctx, pos);
8947
+ case "routed":
8948
+ return emitRouted(ctx, pos);
8668
8949
  case "node":
8669
8950
  return emitNode(def, ctx, pos);
8670
8951
  case "scanTo":
@@ -8762,7 +9043,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8762
9043
  case "choice":
8763
9044
  return d.parsers.some((x) => hasNodeDef(x, seen));
8764
9045
  case "dispatch":
8765
- return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
9046
+ return hasNodeDef(d.selector, seen) || d.cases.some((x) => hasNodeDef(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => hasNodeDef(entry.parser, seen)) : false) || (d.otherwise ? hasNodeDef(d.otherwise, seen) : false);
8766
9047
  case "sepBy":
8767
9048
  return hasNodeDef(d.parser, seen) || hasNodeDef(d.separator, seen);
8768
9049
  case "scanTo":
@@ -8777,6 +9058,26 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
8777
9058
  return false;
8778
9059
  }
8779
9060
  }
9061
+ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9062
+ if (seen.has(p)) return false;
9063
+ seen.add(p);
9064
+ const d = p._def;
9065
+ switch (d.tag) {
9066
+ case "routed":
9067
+ return true;
9068
+ case "lazy": {
9069
+ try {
9070
+ return parserUsesRouted2(d.thunk(), seen);
9071
+ } catch {
9072
+ return false;
9073
+ }
9074
+ }
9075
+ case "dispatch":
9076
+ return false;
9077
+ default:
9078
+ return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9079
+ }
9080
+ }
8780
9081
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
8781
9082
  if (seen.has(p)) return false;
8782
9083
  seen.add(p);
@@ -8797,7 +9098,7 @@ function childrenOf2(def) {
8797
9098
  case "choice":
8798
9099
  return def.parsers;
8799
9100
  case "dispatch":
8800
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9101
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
8801
9102
  case "many":
8802
9103
  case "attempt":
8803
9104
  case "optional":
@@ -8834,6 +9135,7 @@ function childrenOf2(def) {
8834
9135
  case "regex":
8835
9136
  case "keywords":
8836
9137
  case "guard":
9138
+ case "routed":
8837
9139
  case "unknown":
8838
9140
  return [];
8839
9141
  }
@@ -9541,7 +9843,7 @@ function childrenOf3(def) {
9541
9843
  case "choice":
9542
9844
  return def.parsers;
9543
9845
  case "dispatch":
9544
- return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.otherwise ? [def.otherwise] : []];
9846
+ return [def.selector, ...def.cases.map((entry) => entry.parser), ...def.matchers ? def.matchers.map((entry) => entry.parser) : [], ...def.otherwise ? [def.otherwise] : []];
9545
9847
  case "many":
9546
9848
  case "oneOrMore":
9547
9849
  case "optional":
@@ -9570,6 +9872,7 @@ function childrenOf3(def) {
9570
9872
  case "literal":
9571
9873
  case "regex":
9572
9874
  case "keywords":
9875
+ case "routed":
9573
9876
  case "guard":
9574
9877
  case "recover":
9575
9878
  case "unknown":
@@ -9594,6 +9897,16 @@ function repeatOpts(min, max, trailing) {
9594
9897
  if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
9595
9898
  return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
9596
9899
  }
9900
+ function matcherExpr(entry) {
9901
+ switch (entry.kind) {
9902
+ case "startsWith":
9903
+ return `startsWith(${JSON.stringify(entry.value)})`;
9904
+ case "endsWith":
9905
+ return `endsWith(${JSON.stringify(entry.value)})`;
9906
+ case "matches":
9907
+ return `matches(${new RegExp(entry.value, entry.flags ?? "").toString()})`;
9908
+ }
9909
+ }
9597
9910
  function serializeRuleMap(ruleMap, scanSkip) {
9598
9911
  try {
9599
9912
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -9693,7 +10006,11 @@ function evalRuleMapIR(ir) {
9693
10006
  "choice",
9694
10007
  "dispatch",
9695
10008
  "when",
10009
+ "startsWith",
10010
+ "endsWith",
10011
+ "matches",
9696
10012
  "otherwise",
10013
+ "routed",
9697
10014
  "attempt",
9698
10015
  "many",
9699
10016
  "oneOrMore",
@@ -9729,7 +10046,11 @@ function evalRuleMapIR(ir) {
9729
10046
  choice,
9730
10047
  dispatch,
9731
10048
  when,
10049
+ startsWith,
10050
+ endsWith,
10051
+ matches,
9732
10052
  otherwise,
10053
+ routed,
9733
10054
  attempt,
9734
10055
  many,
9735
10056
  oneOrMore,
@@ -9917,8 +10238,13 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9917
10238
  }
9918
10239
  case "dispatch": {
9919
10240
  const arms = def.cases.map(
9920
- (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)})`
10241
+ (entry) => `when(${entry.keys.length === 1 ? JSON.stringify(entry.keys[0]) : JSON.stringify(entry.keys)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`
9921
10242
  );
10243
+ if (def.matchers) {
10244
+ for (const entry of def.matchers) {
10245
+ arms.push(`when(${matcherExpr(entry)}, ${kid(entry.parser)}${entry.caseInsensitive ? ", { caseInsensitive: true }" : ""})`);
10246
+ }
10247
+ }
9922
10248
  if (def.otherwise) arms.push(`otherwise(${kid(def.otherwise)})`);
9923
10249
  return `dispatch(${kid(def.selector)}${arms.length === 0 ? "" : `, ${arms.join(", ")}`})`;
9924
10250
  }
@@ -9938,6 +10264,8 @@ ${outerDecls.map((d) => " " + d).join("\n")}
9938
10264
  return `not(${kid(def.parser)})`;
9939
10265
  case "peek":
9940
10266
  return `peek(${kid(def.parser)})`;
10267
+ case "routed":
10268
+ return "routed()";
9941
10269
  case "trivia":
9942
10270
  return `trivia(${kid(def.parser)})`;
9943
10271
  case "token":
@@ -10959,10 +11287,20 @@ function run(entry, input, options = {}) {
10959
11287
  }
10960
11288
 
10961
11289
  // src/coverage.ts
11290
+ function dispatchMatcher(entry) {
11291
+ switch (entry.kind) {
11292
+ case "startsWith":
11293
+ return startsWith(entry.value);
11294
+ case "endsWith":
11295
+ return endsWith(entry.value);
11296
+ case "matches":
11297
+ return matches(new RegExp(entry.value, entry.flags));
11298
+ }
11299
+ }
10962
11300
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
10963
11301
  function compiledGrammarCoverageDefinitions(grammar) {
10964
11302
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
10965
- if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "label"))) {
11303
+ if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
10966
11304
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
10967
11305
  }
10968
11306
  return definitions;
@@ -11142,10 +11480,54 @@ function coverageEntry(entry, collector, maps, trace) {
11142
11480
  return unsafeSequence(...def.parsers.map(build));
11143
11481
  }
11144
11482
  case "dispatch": {
11145
- const arms = def.cases.map((entry2) => when(entry2.keys, build(entry2.parser)));
11146
- if (def.otherwise) arms.push(otherwise(build(def.otherwise)));
11483
+ const ids = maps.dispatches.get(parser2) ?? [];
11484
+ let armIndex = 0;
11485
+ const dispatchStarts = [];
11486
+ const wrapArm = (arm, id) => {
11487
+ const child = build(arm);
11488
+ return {
11489
+ _tag: child._tag,
11490
+ _meta: child._meta,
11491
+ _def: child._def,
11492
+ parse(input, pos, ctx) {
11493
+ const dispatchStart = dispatchStarts[dispatchStarts.length - 1] ?? pos;
11494
+ if (id) trace?.write({ id, phase: "attempt", offset: dispatchStart });
11495
+ const result = child.parse(input, pos, ctx);
11496
+ if (result.ok) {
11497
+ if (id) {
11498
+ collector.hit(id);
11499
+ trace?.write({ id, phase: "selected", offset: dispatchStart, end: result.span.end });
11500
+ trace?.write({ id, phase: "success", offset: dispatchStart, end: result.span.end });
11501
+ }
11502
+ } else if (id) {
11503
+ trace?.write({ id, phase: "failure", offset: result.span.end });
11504
+ }
11505
+ return result;
11506
+ }
11507
+ };
11508
+ };
11509
+ const arms = def.cases.map(
11510
+ (entry2) => when(entry2.keys, wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0)
11511
+ );
11512
+ if (def.matchers) {
11513
+ for (const entry2 of def.matchers) {
11514
+ arms.push(when(dispatchMatcher(entry2), wrapArm(entry2.parser, ids[armIndex++]), entry2.caseInsensitive ? { caseInsensitive: true } : void 0));
11515
+ }
11516
+ }
11517
+ if (def.otherwise) arms.push(otherwise(wrapArm(def.otherwise, ids[armIndex++])));
11147
11518
  const unsafeDispatch = dispatch;
11148
- return unsafeDispatch(build(def.selector), ...arms);
11519
+ const base2 = unsafeDispatch(build(def.selector), ...arms);
11520
+ return {
11521
+ ...base2,
11522
+ parse(input, pos, ctx) {
11523
+ dispatchStarts.push(pos);
11524
+ try {
11525
+ return base2.parse(input, pos, ctx);
11526
+ } finally {
11527
+ dispatchStarts.pop();
11528
+ }
11529
+ }
11530
+ };
11149
11531
  }
11150
11532
  case "many":
11151
11533
  return many(build(def.parser));
@@ -11593,6 +11975,7 @@ export {
11593
11975
  cstBuildHost,
11594
11976
  dispatch,
11595
11977
  duplicationFindingCount,
11978
+ endsWith,
11596
11979
  expect,
11597
11980
  extractCharClasses,
11598
11981
  field,
@@ -11615,8 +11998,10 @@ export {
11615
11998
  lineBreaksIn,
11616
11999
  lineStartWithin,
11617
12000
  literal,
12001
+ makeWhen,
11618
12002
  makeWord,
11619
12003
  many,
12004
+ matches,
11620
12005
  noTrivia,
11621
12006
  node,
11622
12007
  not,
@@ -11633,6 +12018,7 @@ export {
11633
12018
  regex,
11634
12019
  relativize,
11635
12020
  relativizeCST,
12021
+ routed,
11636
12022
  rules,
11637
12023
  run,
11638
12024
  runWithGrammarCoverage,
@@ -11642,6 +12028,7 @@ export {
11642
12028
  shiftAbsolute,
11643
12029
  siteToString,
11644
12030
  skip,
12031
+ startsWith,
11645
12032
  token,
11646
12033
  transform,
11647
12034
  trivia,