parseman 0.44.0 → 0.46.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.
Files changed (111) hide show
  1. package/dist/analysis/choice-cost-gate.d.ts +158 -0
  2. package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
  3. package/dist/analysis/choice-cost-render.d.ts +88 -0
  4. package/dist/analysis/choice-cost-render.d.ts.map +1 -0
  5. package/dist/analysis/choice-cost.d.ts +383 -0
  6. package/dist/analysis/choice-cost.d.ts.map +1 -0
  7. package/dist/analysis/corpus.d.ts +73 -0
  8. package/dist/analysis/corpus.d.ts.map +1 -0
  9. package/dist/analysis/diagnose-render.d.ts +65 -0
  10. package/dist/analysis/diagnose-render.d.ts.map +1 -0
  11. package/dist/analysis/diagnose.d.ts +133 -0
  12. package/dist/analysis/diagnose.d.ts.map +1 -0
  13. package/dist/analysis/diagnostics.cjs +11272 -0
  14. package/dist/analysis/diagnostics.cjs.map +7 -0
  15. package/dist/analysis/diagnostics.d.ts +27 -0
  16. package/dist/analysis/diagnostics.d.ts.map +1 -0
  17. package/dist/analysis/diagnostics.js +11249 -0
  18. package/dist/analysis/diagnostics.js.map +7 -0
  19. package/dist/analysis/fix-render.d.ts +43 -0
  20. package/dist/analysis/fix-render.d.ts.map +1 -0
  21. package/dist/analysis/fix.d.ts +172 -0
  22. package/dist/analysis/fix.d.ts.map +1 -0
  23. package/dist/analysis/gating.d.ts +17 -9
  24. package/dist/analysis/gating.d.ts.map +1 -1
  25. package/dist/analysis/grammar.d.ts.map +1 -1
  26. package/dist/analysis/rebuild.d.ts +61 -0
  27. package/dist/analysis/rebuild.d.ts.map +1 -0
  28. package/dist/analysis/terminal.d.ts +142 -0
  29. package/dist/analysis/terminal.d.ts.map +1 -0
  30. package/dist/cli/index.cjs +12507 -0
  31. package/dist/cli/index.cjs.map +7 -0
  32. package/dist/cli/index.d.ts +3 -0
  33. package/dist/cli/index.d.ts.map +1 -0
  34. package/dist/cli/index.js +12484 -0
  35. package/dist/cli/index.js.map +7 -0
  36. package/dist/combinators/choice.d.ts.map +1 -1
  37. package/dist/combinators/dispatch.d.ts +19 -1
  38. package/dist/combinators/dispatch.d.ts.map +1 -1
  39. package/dist/combinators/expect.d.ts.map +1 -1
  40. package/dist/combinators/first-set.d.ts.map +1 -1
  41. package/dist/combinators/node.d.ts +19 -0
  42. package/dist/combinators/node.d.ts.map +1 -1
  43. package/dist/compiler/build-arity.d.ts +28 -0
  44. package/dist/compiler/build-arity.d.ts.map +1 -1
  45. package/dist/compiler/codegen.d.ts +80 -60
  46. package/dist/compiler/codegen.d.ts.map +1 -1
  47. package/dist/compiler/degradation.d.ts +123 -0
  48. package/dist/compiler/degradation.d.ts.map +1 -0
  49. package/dist/compiler/fields.d.ts.map +1 -1
  50. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  51. package/dist/compiler/inline-build.d.ts.map +1 -1
  52. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  53. package/dist/compiler/linker.d.ts +45 -7
  54. package/dist/compiler/linker.d.ts.map +1 -1
  55. package/dist/compiler/module-hoist.d.ts +106 -0
  56. package/dist/compiler/module-hoist.d.ts.map +1 -0
  57. package/dist/coverage.d.ts +13 -0
  58. package/dist/coverage.d.ts.map +1 -1
  59. package/dist/cst/reflection.d.ts.map +1 -1
  60. package/dist/functional/run.d.ts.map +1 -1
  61. package/dist/index.cjs +1846 -610
  62. package/dist/index.cjs.map +4 -4
  63. package/dist/index.d.ts +11 -7
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1846 -610
  66. package/dist/index.js.map +4 -4
  67. package/dist/language-service/index.cjs +40 -30
  68. package/dist/language-service/index.cjs.map +3 -3
  69. package/dist/language-service/index.js +40 -30
  70. package/dist/language-service/index.js.map +3 -3
  71. package/dist/oracle/digest.d.ts +132 -6
  72. package/dist/oracle/digest.d.ts.map +1 -1
  73. package/dist/oracle/index.cjs +136 -305
  74. package/dist/oracle/index.cjs.map +4 -4
  75. package/dist/oracle/index.d.ts +43 -31
  76. package/dist/oracle/index.d.ts.map +1 -1
  77. package/dist/oracle/index.js +136 -305
  78. package/dist/oracle/index.js.map +4 -4
  79. package/dist/plugin/evaluator.d.ts +3 -0
  80. package/dist/plugin/evaluator.d.ts.map +1 -1
  81. package/dist/plugin/free-identifiers.d.ts +23 -0
  82. package/dist/plugin/free-identifiers.d.ts.map +1 -0
  83. package/dist/plugin/index.cjs +1529 -763
  84. package/dist/plugin/index.cjs.map +4 -4
  85. package/dist/plugin/index.d.ts +18 -0
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +1529 -763
  88. package/dist/plugin/index.js.map +4 -4
  89. package/dist/plugin/reducer-resolver.d.ts +57 -0
  90. package/dist/plugin/reducer-resolver.d.ts.map +1 -0
  91. package/dist/run/index.cjs +33 -30
  92. package/dist/run/index.cjs.map +2 -2
  93. package/dist/run/index.js +33 -30
  94. package/dist/run/index.js.map +2 -2
  95. package/dist/spec/index.cjs +222 -34
  96. package/dist/spec/index.cjs.map +4 -4
  97. package/dist/spec/index.js +222 -34
  98. package/dist/spec/index.js.map +4 -4
  99. package/dist/spec/model.d.ts.map +1 -1
  100. package/dist/types.d.ts +6 -1
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/package.json +23 -4
  104. package/dist/combinators/guard.d.ts +0 -8
  105. package/dist/combinators/guard.d.ts.map +0 -1
  106. package/dist/cst/offset-model.d.ts +0 -119
  107. package/dist/cst/offset-model.d.ts.map +0 -1
  108. package/dist/oracle/corpus.d.ts +0 -32
  109. package/dist/oracle/corpus.d.ts.map +0 -1
  110. package/dist/oracle/identity.d.ts +0 -117
  111. package/dist/oracle/identity.d.ts.map +0 -1
@@ -34,18 +34,18 @@ __export(plugin_exports, {
34
34
  transformMacro: () => transformMacro
35
35
  });
36
36
  module.exports = __toCommonJS(plugin_exports);
37
- var fs = __toESM(require("node:fs"), 1);
38
- var path = __toESM(require("node:path"), 1);
37
+ var fs2 = __toESM(require("node:fs"), 1);
38
+ var path2 = __toESM(require("node:path"), 1);
39
39
  var import_unplugin = require("unplugin");
40
- var import_oxc_parser2 = require("oxc-parser");
40
+ var import_oxc_parser4 = require("oxc-parser");
41
41
  var import_oxc_resolver = require("oxc-resolver");
42
42
  var import_magic_string = __toESM(require("magic-string"), 1);
43
43
 
44
44
  // src/combinators/first-set.ts
45
- function resolveNamedRef(p, resolve2) {
46
- if (resolve2 === void 0) return void 0;
45
+ function resolveNamedRef(p, resolve3) {
46
+ if (resolve3 === void 0) return void 0;
47
47
  const name = p._ruleName;
48
- return name === void 0 ? void 0 : resolve2(name);
48
+ return name === void 0 ? void 0 : resolve3(name);
49
49
  }
50
50
  function union(a, b) {
51
51
  if (a.kind === "any" || b.kind === "any") return { kind: "any" };
@@ -67,12 +67,12 @@ function fromChar(code) {
67
67
  return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
68
68
  }
69
69
  function startsFirstSet(combinator, input, pos) {
70
- const fs2 = combinator._meta.firstSet;
71
- if (fs2.kind === "any") return true;
72
- if (fs2.kind === "empty") return false;
70
+ const fs3 = combinator._meta.firstSet;
71
+ if (fs3.kind === "any") return true;
72
+ if (fs3.kind === "empty") return false;
73
73
  const code = input.codePointAt(pos);
74
74
  if (code === void 0) return false;
75
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
75
+ for (const r of fs3.ranges) if (code >= r.lo && code <= r.hi) return true;
76
76
  return false;
77
77
  }
78
78
  function fromRange(lo, hi) {
@@ -84,18 +84,19 @@ function any() {
84
84
  function empty() {
85
85
  return { kind: "empty" };
86
86
  }
87
- function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
87
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve3) {
88
88
  if (seen.has(p)) return true;
89
89
  seen.add(p);
90
- const me = (c) => matchesEmpty(c, seen, resolve2);
90
+ const me = (c) => matchesEmpty(c, seen, resolve3);
91
91
  const d = p._def;
92
92
  switch (d.tag) {
93
93
  case "literal":
94
94
  return d.value.length === 0;
95
95
  case "keywords":
96
96
  return false;
97
+ // The routed token itself is the selector's match, never empty; a fallback can be.
97
98
  case "routed":
98
- return false;
99
+ return d.fallback === void 0 ? false : me(d.fallback);
99
100
  case "regex":
100
101
  try {
101
102
  const m = new RegExp(d.source).exec("");
@@ -141,7 +142,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
141
142
  try {
142
143
  return me(d.thunk());
143
144
  } catch {
144
- const t = resolveNamedRef(p, resolve2);
145
+ const t = resolveNamedRef(p, resolve3);
145
146
  return t ? me(t) : true;
146
147
  }
147
148
  default:
@@ -167,29 +168,29 @@ function narrowBy(acc, constraint) {
167
168
  }
168
169
  return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
169
170
  }
170
- function applyAssertion(fs2, assertion) {
171
- if (assertion === null) return fs2;
172
- if (fs2.kind === "empty") return assertion;
173
- return narrowBy(fs2, assertion) ?? fs2;
171
+ function applyAssertion(fs3, assertion) {
172
+ if (assertion === null) return fs3;
173
+ if (fs3.kind === "empty") return assertion;
174
+ return narrowBy(fs3, assertion) ?? fs3;
174
175
  }
175
176
  function sequenceFirstSet(parsers) {
176
- let fs2 = empty();
177
+ let fs3 = empty();
177
178
  let assertion = null;
178
179
  for (const p of parsers) {
179
180
  if (isPositiveLookahead(p)) {
180
181
  assertion = narrowBy(assertion, p._meta.firstSet);
181
182
  continue;
182
183
  }
183
- if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
184
- if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
184
+ if (!isZeroWidthAssertion(p)) fs3 = union(fs3, p._meta.firstSet);
185
+ if (!matchesEmpty(p)) return applyAssertion(fs3, assertion);
185
186
  }
186
- return applyAssertion(fs2, assertion);
187
+ return applyAssertion(fs3, assertion);
187
188
  }
188
- function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
189
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve3) {
189
190
  if (seen.has(p)) return any();
190
191
  seen.add(p);
191
- const fs2 = (c) => firstSetOf(c, seen, resolve2);
192
- const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
192
+ const fs3 = (c) => firstSetOf(c, seen, resolve3);
193
+ const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve3);
193
194
  const d = p._def;
194
195
  switch (d.tag) {
195
196
  case "literal":
@@ -198,17 +199,17 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
198
199
  return p._meta.firstSet;
199
200
  // terminals: no refs, cached set is exact
200
201
  case "dispatch":
201
- return fs2(d.selector);
202
+ return fs3(d.selector);
202
203
  case "lazy":
203
204
  try {
204
- return fs2(d.thunk());
205
+ return fs3(d.thunk());
205
206
  } catch {
206
- const t = resolveNamedRef(p, resolve2);
207
- return t ? fs2(t) : any();
207
+ const t = resolveNamedRef(p, resolve3);
208
+ return t ? fs3(t) : any();
208
209
  }
209
210
  case "choice": {
210
211
  let out = empty();
211
- for (const arm of d.parsers) out = union(out, fs2(arm));
212
+ for (const arm of d.parsers) out = union(out, fs3(arm));
212
213
  return out;
213
214
  }
214
215
  case "sequence": {
@@ -216,17 +217,17 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
216
217
  let assertion = null;
217
218
  for (const term of d.parsers) {
218
219
  if (isPositiveLookahead(term)) {
219
- assertion = narrowBy(assertion, fs2(term));
220
+ assertion = narrowBy(assertion, fs3(term));
220
221
  continue;
221
222
  }
222
- if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
223
+ if (!isZeroWidthAssertion(term)) out = union(out, fs3(term));
223
224
  if (!empties(term)) return applyAssertion(out, assertion);
224
225
  }
225
226
  return applyAssertion(out, assertion);
226
227
  }
227
228
  case "peek": {
228
229
  const inner = d.parser;
229
- return empties(inner) ? any() : fs2(inner);
230
+ return empties(inner) ? any() : fs3(inner);
230
231
  }
231
232
  case "oneOrMore":
232
233
  case "many":
@@ -239,12 +240,12 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
239
240
  case "node":
240
241
  case "grammar":
241
242
  case "expect":
242
- return fs2(d.parser);
243
+ return fs3(d.parser);
243
244
  case "sepBy":
244
- return fs2(d.parser);
245
+ return fs3(d.parser);
245
246
  // both min 0 and min 1 start with the item
246
247
  case "skip":
247
- return fs2(d.main);
248
+ return fs3(d.main);
248
249
  default:
249
250
  return p._meta.firstSet;
250
251
  }
@@ -715,13 +716,13 @@ function shortScanner(source, flags) {
715
716
  return minOne && end === pos ? null : end;
716
717
  };
717
718
  }
718
- function asciiCaseFold(fs2) {
719
- if (fs2.kind !== "ranges") return fs2;
720
- const ranges = fs2.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
719
+ function asciiCaseFold(fs3) {
720
+ if (fs3.kind !== "ranges") return fs3;
721
+ const ranges = fs3.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
721
722
  const add = (lo, hi) => {
722
723
  if (lo <= hi) ranges.push({ lo, hi });
723
724
  };
724
- for (const r of fs2.ranges) {
725
+ for (const r of fs3.ranges) {
725
726
  const uLo = Math.max(r.lo, 65), uHi = Math.min(r.hi, 90);
726
727
  add(uLo + 32, uHi + 32);
727
728
  const lLo = Math.max(r.lo, 97), lHi = Math.min(r.hi, 122);
@@ -779,9 +780,9 @@ function charNode(set, nl) {
779
780
  return { t: "char", set, nl };
780
781
  }
781
782
  function rangesToSet(ranges) {
782
- let fs2 = empty();
783
- for (const [lo, hi] of ranges) fs2 = union(fs2, fromRange(lo, hi));
784
- return fs2;
783
+ let fs3 = empty();
784
+ for (const [lo, hi] of ranges) fs3 = union(fs3, fromRange(lo, hi));
785
+ return fs3;
785
786
  }
786
787
  function rangesHaveNewline(ranges) {
787
788
  return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
@@ -987,17 +988,17 @@ function firstSet(n) {
987
988
  case "rep":
988
989
  return firstSet(n.node);
989
990
  case "alt": {
990
- let fs2 = empty();
991
- for (const arm of n.arms) fs2 = union(fs2, firstSet(arm));
992
- return fs2;
991
+ let fs3 = empty();
992
+ for (const arm of n.arms) fs3 = union(fs3, firstSet(arm));
993
+ return fs3;
993
994
  }
994
995
  case "seq": {
995
- let fs2 = empty();
996
+ let fs3 = empty();
996
997
  for (const part of n.parts) {
997
- fs2 = union(fs2, firstSet(part));
998
+ fs3 = union(fs3, firstSet(part));
998
999
  if (!nullable(part)) break;
999
1000
  }
1000
- return fs2;
1001
+ return fs3;
1001
1002
  }
1002
1003
  }
1003
1004
  }
@@ -1526,8 +1527,8 @@ function classifyTriviaArm(arm) {
1526
1527
  }
1527
1528
  const lc = /^(\\?.)\[\^\\n\\r\]\*$/.exec(arm);
1528
1529
  if (lc) {
1529
- const marker = lc[1];
1530
- return { kind: "untilLineBreak", code: (marker.length === 2 ? marker[1] : marker[0]).charCodeAt(0) };
1530
+ const marker2 = lc[1];
1531
+ return { kind: "untilLineBreak", code: (marker2.length === 2 ? marker2[1] : marker2[0]).charCodeAt(0) };
1531
1532
  }
1532
1533
  return null;
1533
1534
  }
@@ -1672,12 +1673,12 @@ function orSentinel(a, b) {
1672
1673
  }
1673
1674
  };
1674
1675
  }
1675
- function firstSetSentinel(fs2) {
1676
- if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
1677
- const ranges = fs2.ranges;
1676
+ function firstSetSentinel(fs3) {
1677
+ if (fs3.kind !== "ranges" || fs3.ranges.length === 0) return null;
1678
+ const ranges = fs3.ranges;
1678
1679
  return {
1679
1680
  _tag: "firstSetSentinel",
1680
- _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
1681
+ _meta: { firstSet: fs3, canMatchNewline: false, isTrivia: false },
1681
1682
  _def: { tag: "unknown" },
1682
1683
  parse(input, pos) {
1683
1684
  const code = pos < input.length ? input.codePointAt(pos) : void 0;
@@ -1719,11 +1720,11 @@ function sequence(...parsers) {
1719
1720
  let followSentinels;
1720
1721
  function parseTolerant(input, pos, ctx) {
1721
1722
  followSentinels ??= parsers.map((_, i) => {
1722
- const fs2 = parsers.slice(i + 1).reduce(
1723
+ const fs3 = parsers.slice(i + 1).reduce(
1723
1724
  (acc, p) => union(acc, firstSetOf(p)),
1724
1725
  { kind: "empty" }
1725
1726
  );
1726
- return firstSetSentinel(fs2);
1727
+ return firstSetSentinel(fs3);
1727
1728
  });
1728
1729
  const values = def.valueUnused ? void 0 : [];
1729
1730
  let cur = pos;
@@ -1866,8 +1867,11 @@ function derive(c, seen) {
1866
1867
  case "not":
1867
1868
  case "peek":
1868
1869
  return deriveExpected2(def.parser);
1870
+ // A bare routed() can only fail as "there is no routed token here". With a
1871
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1872
+ // ones — the routed path either succeeds or is not taken.
1869
1873
  case "routed":
1870
- return ["routed()"];
1874
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1871
1875
  case "lazy": {
1872
1876
  const name = c._ruleName;
1873
1877
  if (seen.has(c)) return name ? [name] : [];
@@ -2054,7 +2058,7 @@ function detectStrategy(parsers) {
2054
2058
  function bareLeadingTermKey(term) {
2055
2059
  const d = term._def;
2056
2060
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
2057
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
2061
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
2058
2062
  return null;
2059
2063
  }
2060
2064
  function detectSharedPrefix(parsers) {
@@ -2170,10 +2174,10 @@ function codesToFirstSet(codes) {
2170
2174
  ranges.push({ lo, hi });
2171
2175
  return { kind: "ranges", ranges };
2172
2176
  }
2173
- function inFirstSet(code, fs2) {
2174
- if (fs2.kind === "any") return true;
2175
- if (fs2.kind === "empty") return false;
2176
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
2177
+ function inFirstSet(code, fs3) {
2178
+ if (fs3.kind === "any") return true;
2179
+ if (fs3.kind === "empty") return false;
2180
+ for (const r of fs3.ranges) if (code >= r.lo && code <= r.hi) return true;
2177
2181
  return false;
2178
2182
  }
2179
2183
  function areDisjoint(sets) {
@@ -2186,9 +2190,9 @@ function areDisjoint(sets) {
2186
2190
  function buildAsciiDispatch(parsers) {
2187
2191
  const table = Array(128).fill(-1);
2188
2192
  for (let i = 0; i < parsers.length; i++) {
2189
- const fs2 = parsers[i]._meta.firstSet;
2190
- if (fs2.kind !== "ranges") continue;
2191
- for (const { lo, hi } of fs2.ranges) {
2193
+ const fs3 = parsers[i]._meta.firstSet;
2194
+ if (fs3.kind !== "ranges") continue;
2195
+ for (const { lo, hi } of fs3.ranges) {
2192
2196
  for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
2193
2197
  table[code] = i;
2194
2198
  }
@@ -2309,17 +2313,21 @@ function when(key, parser2, opts = {}) {
2309
2313
  function otherwise(parser2) {
2310
2314
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2311
2315
  }
2312
- function routed() {
2316
+ function routed(fallback) {
2313
2317
  return {
2314
2318
  _tag: "routed",
2315
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2316
- _def: { tag: "routed" },
2319
+ _meta: {
2320
+ // The routed token is whatever the selector matched, so the first set stays
2321
+ // `any` even with a fallback — `any` already subsumes the fallback's.
2322
+ firstSet: { kind: "any" },
2323
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
2324
+ isTrivia: false
2325
+ },
2326
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2317
2327
  parse(_input, pos, ctx) {
2318
2328
  const item = ctx._routed;
2319
- if (item === void 0) {
2320
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2321
- }
2322
- if (pos !== item.span.start) {
2329
+ if (item === void 0 || pos !== item.span.start) {
2330
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2323
2331
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2324
2332
  }
2325
2333
  if (cstCaptureActive(ctx)) {
@@ -2826,12 +2834,12 @@ function markUnusedValues(root) {
2826
2834
  }
2827
2835
  };
2828
2836
  const resolveRoot = (c) => {
2829
- const guard2 = /* @__PURE__ */ new Set();
2837
+ const guard = /* @__PURE__ */ new Set();
2830
2838
  let cur = c;
2831
- while (!guard2.has(cur)) {
2839
+ while (!guard.has(cur)) {
2832
2840
  const d = cur._def;
2833
2841
  if (d.tag !== "lazy") return cur;
2834
- guard2.add(cur);
2842
+ guard.add(cur);
2835
2843
  try {
2836
2844
  cur = d.thunk();
2837
2845
  } catch {
@@ -2973,10 +2981,11 @@ function childrenOf(def) {
2973
2981
  return [def.main, def.skipped];
2974
2982
  case "scanTo":
2975
2983
  return [def.sentinel, ...def.skip];
2984
+ case "routed":
2985
+ return def.fallback ? [def.fallback] : [];
2976
2986
  case "literal":
2977
2987
  case "regex":
2978
2988
  case "keywords":
2979
- case "routed":
2980
2989
  case "guard":
2981
2990
  case "lazy":
2982
2991
  case "unknown":
@@ -3199,54 +3208,276 @@ function peek(combinator) {
3199
3208
  };
3200
3209
  }
3201
3210
 
3211
+ // src/compiler/degradation.ts
3212
+ function formatDegradation(d) {
3213
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
3214
+ }
3215
+ var DETAIL_CAP = 8;
3216
+ function formatDegradations(list) {
3217
+ const byCode = /* @__PURE__ */ new Map();
3218
+ for (const d of list) {
3219
+ const bucket = byCode.get(d.code);
3220
+ if (bucket) bucket.push(d);
3221
+ else byCode.set(d.code, [d]);
3222
+ }
3223
+ const out = [];
3224
+ for (const [code, items] of byCode) {
3225
+ for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
3226
+ if (items.length > DETAIL_CAP) {
3227
+ out.push(
3228
+ `[parseman] degraded [${code}] +${items.length - DETAIL_CAP} more site(s) not listed (${items.length} total). Set PARSEMAN_DEGRADATION=error to fail the build on these.`
3229
+ );
3230
+ }
3231
+ }
3232
+ return out;
3233
+ }
3234
+ var _sinks = [];
3235
+ function beginDegradationCapture() {
3236
+ _sinks.push(/* @__PURE__ */ new Map());
3237
+ }
3238
+ function endDegradationCapture() {
3239
+ const sink = _sinks.pop();
3240
+ return sink ? [...sink.values()] : [];
3241
+ }
3242
+ function degradationCaptureDepth() {
3243
+ return _sinks.length;
3244
+ }
3245
+ function unwindDegradationCapture(depth) {
3246
+ const found = [];
3247
+ while (_sinks.length > depth) {
3248
+ const sink = _sinks.pop();
3249
+ found.unshift(...sink.values());
3250
+ }
3251
+ return found;
3252
+ }
3253
+ function resolveDegradationLevel(explicit) {
3254
+ if (explicit !== void 0) return explicit;
3255
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
3256
+ if (env === "off" || env === "warn" || env === "error") return env;
3257
+ return "warn";
3258
+ }
3259
+ function degradationKey(d) {
3260
+ return JSON.stringify([d.code, d.where, d.subject]);
3261
+ }
3262
+ function recordDegradation(d) {
3263
+ const level = resolveDegradationLevel();
3264
+ if (level === "off") return;
3265
+ const key = degradationKey(d);
3266
+ const sink = _sinks[_sinks.length - 1];
3267
+ if (sink) {
3268
+ if (!sink.has(key)) sink.set(key, d);
3269
+ return;
3270
+ }
3271
+ if (_immediate.has(key)) return;
3272
+ _immediate.add(key);
3273
+ if (level === "error") throw new Error(`parseman: degraded compilation path
3274
+ ${formatDegradation(d)}`);
3275
+ console.warn(formatDegradation(d));
3276
+ }
3277
+ var _immediate = /* @__PURE__ */ new Set();
3278
+ function beginCompileDegradationDrain() {
3279
+ if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
3280
+ };
3281
+ const depth = degradationCaptureDepth();
3282
+ beginDegradationCapture();
3283
+ return (report) => {
3284
+ const found = unwindDegradationCapture(depth);
3285
+ if (!report || found.length === 0) return;
3286
+ const fresh = found.filter((d) => {
3287
+ const k = degradationKey(d);
3288
+ if (_immediate.has(k)) return false;
3289
+ _immediate.add(k);
3290
+ return true;
3291
+ });
3292
+ if (fresh.length === 0) return;
3293
+ const lines = formatDegradations(fresh);
3294
+ if (resolveDegradationLevel() === "error") {
3295
+ throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
3296
+ ${lines.join("\n")}`);
3297
+ }
3298
+ for (const l of lines) console.warn(l);
3299
+ };
3300
+ }
3301
+
3202
3302
  // src/compiler/build-arity.ts
3203
3303
  function cstOutputHost(build) {
3204
3304
  return build?._parsemanCstOutput === true;
3205
3305
  }
3206
- var PARAM_LIST_RE = (
3207
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
3208
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
3209
- );
3210
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
3306
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
3307
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
3308
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
3309
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
3211
3310
  function confirmedBuildArity(src) {
3212
3311
  const s = src.trim();
3213
- const m = PARAM_LIST_RE.exec(s);
3214
- if (!m) return null;
3215
- if (m[3] !== void 0) return 1;
3216
- const inner = (m[1] ?? m[2] ?? "").trim();
3217
- if (inner === "") return 0;
3218
- const parts = inner.split(",");
3312
+ if (NATIVE_CODE_RE.test(s)) return null;
3313
+ if (/\barguments\b/.test(s)) return null;
3314
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
3315
+ const inner = paramListText(s);
3316
+ if (inner === null) return null;
3317
+ if (inner.trim() === "") return 0;
3318
+ const parts = splitTopLevel(inner);
3319
+ if (parts === null) return null;
3219
3320
  for (const part of parts) {
3220
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
3321
+ if (!isConfirmableParam(part.trim())) return null;
3221
3322
  }
3222
- if (/\barguments\b/.test(s)) return null;
3223
3323
  return parts.length;
3224
3324
  }
3325
+ function paramListText(s) {
3326
+ const open = PARAM_LIST_OPEN_RE.exec(s);
3327
+ if (!open) return null;
3328
+ const start = open[0].length;
3329
+ const end = matchingParen(s, start);
3330
+ if (end === null) return null;
3331
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
3332
+ return s.slice(start, end);
3333
+ }
3334
+ function matchingParen(s, start) {
3335
+ let depth = 0;
3336
+ for (let i = start; i < s.length; i++) {
3337
+ const c = s[i];
3338
+ if (c === '"' || c === "'" || c === "`") {
3339
+ const close = skipString(s, i);
3340
+ if (close === null) return null;
3341
+ i = close;
3342
+ continue;
3343
+ }
3344
+ if (c === "(" || c === "[" || c === "{") depth++;
3345
+ else if (c === "]" || c === "}") {
3346
+ if (--depth < 0) return null;
3347
+ } else if (c === ")") {
3348
+ if (depth === 0) return i;
3349
+ depth--;
3350
+ }
3351
+ }
3352
+ return null;
3353
+ }
3354
+ function skipString(s, open) {
3355
+ const quote = s[open];
3356
+ for (let i = open + 1; i < s.length; i++) {
3357
+ if (s[i] === "\\") {
3358
+ i++;
3359
+ continue;
3360
+ }
3361
+ if (s[i] === quote) return i;
3362
+ }
3363
+ return null;
3364
+ }
3365
+ function splitTopLevel(inner) {
3366
+ const parts = [];
3367
+ let depth = 0;
3368
+ let angle = 0;
3369
+ let last = 0;
3370
+ for (let i = 0; i < inner.length; i++) {
3371
+ const c = inner[i];
3372
+ if (c === "=" && inner[i + 1] === ">") {
3373
+ i++;
3374
+ continue;
3375
+ }
3376
+ if (c === '"' || c === "'" || c === "`") {
3377
+ const close = skipString(inner, i);
3378
+ if (close === null) return null;
3379
+ i = close;
3380
+ continue;
3381
+ }
3382
+ if (c === "(" || c === "[" || c === "{") depth++;
3383
+ else if (c === ")" || c === "]" || c === "}") {
3384
+ if (--depth < 0) return null;
3385
+ } else if (c === "<") angle++;
3386
+ else if (c === ">") {
3387
+ if (--angle < 0) return null;
3388
+ } else if (c === "," && depth === 0 && angle === 0) {
3389
+ parts.push(inner.slice(last, i));
3390
+ last = i + 1;
3391
+ }
3392
+ }
3393
+ if (depth !== 0 || angle !== 0) return null;
3394
+ parts.push(inner.slice(last));
3395
+ return parts;
3396
+ }
3397
+ function isConfirmableParam(part) {
3398
+ const head = PARAM_HEAD_RE.exec(part);
3399
+ if (!head) return false;
3400
+ const rest = part.slice(head[0].length);
3401
+ if (rest === "") return true;
3402
+ if (rest[0] !== ":") return false;
3403
+ return !hasDefaultAssign(rest.slice(1));
3404
+ }
3405
+ function hasDefaultAssign(annotation) {
3406
+ for (let i = 0; i < annotation.length; i++) {
3407
+ if (annotation[i] !== "=") continue;
3408
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
3409
+ i++;
3410
+ continue;
3411
+ }
3412
+ const prev = annotation[i - 1];
3413
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
3414
+ return true;
3415
+ }
3416
+ return false;
3417
+ }
3418
+ function buildAnalysisSrc(def) {
3419
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
3420
+ }
3421
+ var RESOLVER_REASONS = {
3422
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
3423
+ "arguments": "its body references `arguments`",
3424
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
3425
+ "not-a-function": "the binding it names is not a function declaration",
3426
+ "unresolved-import": "its module could not be resolved or parsed",
3427
+ "not-found": "the name did not resolve to any binding",
3428
+ "computed": "it is a computed expression",
3429
+ "foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
3430
+ "ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
3431
+ };
3432
+ function unconfirmableReason(def, src) {
3433
+ const declared = def.buildArityUnresolved;
3434
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
3435
+ const s = src.trim();
3436
+ const params = s.slice(0, s.indexOf(")") + 1);
3437
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
3438
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
3439
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
3440
+ return "it names a binding this build could not resolve to a function declaration";
3441
+ }
3442
+ return "its parameter list could not be parsed";
3443
+ }
3444
+ function confirmedArityForDef(def) {
3445
+ if (def.buildArity !== void 0) return def.buildArity;
3446
+ const src = buildAnalysisSrc(def);
3447
+ const arity = confirmedBuildArity(src);
3448
+ if (arity !== null) return arity;
3449
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
3450
+ recordDegradation({
3451
+ code: "build-arity-unconfirmed",
3452
+ severity: "warn",
3453
+ where: `node("${def.type ?? "<inferred>"}")`,
3454
+ subject,
3455
+ fellBackTo: `could not confirm its declared arity \u2014 ${unconfirmableReason(def, src)} \u2014 so this node captures children, fields and raw children, logs trivia, and clones \`_ctx.state\` on every match`,
3456
+ otherwise: "only the tiers the reducer actually declares would be captured (arity >= 1 children, >= 2 fields, >= 4 raw, >= 5 trivia, >= 6 state). Declare it with `node(..., { buildArity: n })` if it cannot be derived"
3457
+ });
3458
+ return null;
3459
+ }
3225
3460
  function buildReadsChildren(def) {
3226
3461
  if (!def.build) return true;
3227
- const src = def.buildSrc ?? def.build.toString();
3228
- const arity = confirmedBuildArity(src);
3462
+ const arity = confirmedArityForDef(def);
3229
3463
  if (arity === null) return true;
3230
3464
  return arity >= 1;
3231
3465
  }
3232
3466
  function buildReadsRaw(def) {
3233
3467
  if (!def.build) return true;
3234
- const src = def.buildSrc ?? def.build.toString();
3235
- const arity = confirmedBuildArity(src);
3468
+ const arity = confirmedArityForDef(def);
3236
3469
  if (arity === null) return true;
3237
3470
  return arity >= 4;
3238
3471
  }
3239
3472
  function buildReadsTrivia(def) {
3240
3473
  if (!def.build) return true;
3241
- const src = def.buildSrc ?? def.build.toString();
3242
- const arity = confirmedBuildArity(src);
3474
+ const arity = confirmedArityForDef(def);
3243
3475
  if (arity === null) return true;
3244
3476
  return arity >= 5;
3245
3477
  }
3246
3478
  function buildReadsState(def) {
3247
3479
  if (!def.build) return true;
3248
- const src = def.buildSrc ?? def.build.toString();
3249
- const arity = confirmedBuildArity(src);
3480
+ const arity = confirmedArityForDef(def);
3250
3481
  if (arity === null) return true;
3251
3482
  return arity >= 6;
3252
3483
  }
@@ -3254,8 +3485,7 @@ function buildReadsState(def) {
3254
3485
  // src/compiler/fields.ts
3255
3486
  function buildReadsFields(def) {
3256
3487
  if (!def.build) return true;
3257
- const src = def.buildSrc ?? def.build.toString();
3258
- const arity = confirmedBuildArity(src);
3488
+ const arity = confirmedArityForDef(def);
3259
3489
  if (arity === null) return true;
3260
3490
  return arity >= 2;
3261
3491
  }
@@ -3436,6 +3666,13 @@ function isCstChild(value) {
3436
3666
  function missingInferredType() {
3437
3667
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3438
3668
  }
3669
+ function normalizeBuildArity(arity) {
3670
+ if (arity === void 0) return void 0;
3671
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3672
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3673
+ }
3674
+ return arity;
3675
+ }
3439
3676
  function normalizeProject(project) {
3440
3677
  if (project === void 0) return void 0;
3441
3678
  if (!Number.isInteger(project) || project < 0) {
@@ -3471,13 +3708,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3471
3708
  const captureTrivia = opts?.captureTrivia === true;
3472
3709
  const trailingTrivia = opts?.trailingTrivia === true;
3473
3710
  const tags = opts?.tags;
3711
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3474
3712
  if (unwrap && collapse) {
3475
3713
  throw new Error("node() options cannot set both unwrap and collapse");
3476
3714
  }
3477
3715
  if (project !== void 0 && (unwrap || collapse)) {
3478
3716
  throw new Error("node() options cannot combine project with unwrap or collapse");
3479
3717
  }
3480
- const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
3718
+ const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || buildArity !== void 0 || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...buildArity !== void 0 ? { buildArity } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
3481
3719
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3482
3720
  const clonesState = build ? buildReadsState(def) : project === void 0;
3483
3721
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3523,7 +3761,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3523
3761
  const cstOutput = cstOutputHost(ctx.build);
3524
3762
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3525
3763
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3526
- 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, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
3764
+ const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : (cstOutput || !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, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
3527
3765
  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 };
3528
3766
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3529
3767
  pushCstChild(ctx, built, rawEntry);
@@ -3718,7 +3956,7 @@ function leaf(root, fn) {
3718
3956
  }
3719
3957
 
3720
3958
  // src/version.ts
3721
- var PARSEMAN_VERSION = "0.44.0";
3959
+ var PARSEMAN_VERSION = "0.46.0";
3722
3960
 
3723
3961
  // src/cst/host-mode.ts
3724
3962
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3738,346 +3976,11 @@ function assertHostModeCompatible(mode, build, hostBranchElided = true) {
3738
3976
  }
3739
3977
 
3740
3978
  // src/analysis/gating.ts
3741
- var isAny = (fs2) => fs2.kind === "any";
3742
- function intersects2(a, b) {
3743
- if (a.kind === "any" || b.kind === "any") return true;
3744
- if (a.kind === "empty" || b.kind === "empty") return false;
3745
- for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
3746
- return false;
3747
- }
3748
- function intersection(a, b) {
3749
- if (a.kind === "any") return b;
3750
- if (b.kind === "any") return a;
3751
- if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
3752
- const ranges = [];
3753
- for (const ra of a.ranges) for (const rb of b.ranges) {
3754
- const lo = Math.max(ra.lo, rb.lo);
3755
- const hi = Math.min(ra.hi, rb.hi);
3756
- if (lo <= hi) ranges.push({ lo, hi });
3757
- }
3758
- return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
3759
- }
3760
- function combine(sets) {
3761
- let acc = { kind: "empty" };
3762
- for (const s of sets) {
3763
- if (s.kind === "any") return { kind: "any" };
3764
- if (s.kind === "empty") continue;
3765
- acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
3766
- }
3767
- return acc;
3768
- }
3769
3979
  var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
3770
- function firstSetToString(fs2) {
3771
- if (fs2.kind === "any") return "ANY";
3772
- if (fs2.kind === "empty") return "(empty)";
3773
- return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3774
- }
3775
- var ruleNameOf2 = (p) => p._ruleName;
3776
- var SUGGESTIONS = {
3777
- "broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
3778
- "leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating \u2014 use peek(X), which is zero-width AND carries X first-set.",
3779
- "nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char. A plain sepBy(item, sep) is ALSO nullable \u2014 pass { min: 1 } for a list that cannot be empty.",
3780
- "cross-artifact-ref": "parseman >=0.32.0 resolves a g.Foo ref first-set at fuse time; if still ANY the target rule is itself ungated \u2014 analyze it and give it a concrete non-nullable lead.",
3781
- "opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
3782
- "ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
3783
- };
3784
- function classifyBroadArm(arm, resolve2) {
3785
- const seen = /* @__PURE__ */ new Set();
3786
- const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
3787
- const walk = (p) => {
3788
- if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
3789
- seen.add(p);
3790
- const d = p._def;
3791
- switch (d.tag) {
3792
- case "literal":
3793
- case "regex":
3794
- case "keywords":
3795
- return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
3796
- case "not":
3797
- return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
3798
- // peek() CARRIES its body's first-set, so it is only broad when the body is
3799
- // (or the body is nullable) — walk in for the real cause.
3800
- case "peek":
3801
- return walk(d.parser);
3802
- case "scanTo":
3803
- return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
3804
- // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
3805
- // its own — it is the poison. `withCtx`/`recover` FORWARD their inner
3806
- // first-set (see firstSetOf), so a broad result comes from the inner parser:
3807
- // walk into it for a precise cause.
3808
- case "guard":
3809
- return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
3810
- // attempt/withCtx/recover FORWARD their inner first-set (transparent for
3811
- // dispatch), so a broad result comes from the inner parser — walk into it.
3812
- case "attempt":
3813
- case "withCtx":
3814
- case "recover":
3815
- return walk(d.parser);
3816
- case "lazy": {
3817
- const name = p._ruleName;
3818
- let target;
3819
- try {
3820
- target = d.thunk();
3821
- } catch {
3822
- const bound = name !== void 0 ? resolve2?.(name) : void 0;
3823
- if (bound === void 0)
3824
- return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
3825
- target = bound;
3826
- }
3827
- const inner = walk(target);
3828
- if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
3829
- return inner;
3830
- }
3831
- case "optional":
3832
- case "many": {
3833
- const inner = walk(d.parser);
3834
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3835
- }
3836
- case "oneOrMore":
3837
- case "transform":
3838
- case "label":
3839
- case "field":
3840
- case "trivia":
3841
- case "token":
3842
- case "leaf":
3843
- case "node":
3844
- case "grammar":
3845
- case "expect":
3846
- return walk(d.parser);
3847
- case "skip":
3848
- return walk(d.main);
3849
- case "sequence": {
3850
- let sawNullablePrefix = false;
3851
- for (const t of d.parsers) {
3852
- if (t._def.tag === "not") {
3853
- sawNullablePrefix = true;
3854
- continue;
3855
- }
3856
- if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
3857
- const inner = walk(t);
3858
- if (sawNullablePrefix)
3859
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3860
- return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
3861
- }
3862
- if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
3863
- sawNullablePrefix = true;
3864
- continue;
3865
- }
3866
- return null;
3867
- }
3868
- return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
3869
- }
3870
- case "choice": {
3871
- for (const a of d.parsers) {
3872
- const r = walk(a);
3873
- if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
3874
- }
3875
- return null;
3876
- }
3877
- case "dispatch":
3878
- return walk(d.selector);
3879
- default:
3880
- return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3881
- }
3882
- };
3883
- return walk(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
3884
- }
3885
- function peelToLeading(arm) {
3886
- let d = arm._def;
3887
- let cur = arm;
3888
- for (; ; ) {
3889
- if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
3890
- cur = d.parser;
3891
- d = cur._def;
3892
- continue;
3893
- }
3894
- break;
3895
- }
3896
- if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
3897
- return cur;
3898
- }
3899
- var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
3900
- function detectAntiPatterns(rule, arms) {
3901
- const out = [];
3902
- arms.forEach((arm, i) => {
3903
- const lead = peelToLeading(arm);
3904
- const ld = lead._def;
3905
- if (ld.tag === "not") {
3906
- const inner = ld.parser;
3907
- if (inner._def.tag === "not") {
3908
- out.push({
3909
- kind: "double-not",
3910
- rule,
3911
- armIndex: i,
3912
- message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Prefer letting the arm lead with its consuming terminal; where a real positive lookahead IS needed, use peek(X) \u2014 zero-width like not(not(X)) but it CARRIES X's first-set, so the arm still dispatches."
3913
- });
3914
- } else {
3915
- out.push({
3916
- kind: "leading-not",
3917
- rule,
3918
- armIndex: i,
3919
- message: "a leading not(...) on a choice arm hand-rolls gating and poisons the choice first-set (not() is ANY). Reorder so a consuming terminal leads; keep not(...) as a TRAILING boundary only."
3920
- });
3921
- }
3922
- }
3923
- if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
3924
- out.push({
3925
- kind: "keyword-regex",
3926
- rule,
3927
- armIndex: i,
3928
- message: `regex(/${ld.source}/) leads this arm but is a keyword \u2014 use word('\u2026', boundary) / keywords([\u2026]) for an EXACT resolvable first-set (they lower to the same charCodeAt scan).`
3929
- });
3930
- }
3931
- });
3932
- return out;
3933
- }
3934
- function analyzeGating(entry, opts) {
3935
- return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3936
- }
3937
- function analyzeGatingRules(ruleMap, opts) {
3938
- const raw = [];
3939
- const antiPatterns = [];
3940
- const unanalysable = [];
3941
- const seenUnanalysable = /* @__PURE__ */ new Set();
3942
- const visited = /* @__PURE__ */ new Set();
3943
- const noteUnanalysable = (u) => {
3944
- const key = `${u.rule}\0${u.kind}`;
3945
- if (seenUnanalysable.has(key)) return;
3946
- seenUnanalysable.add(key);
3947
- unanalysable.push(u);
3948
- };
3949
- const visit = (p, enclosingRule) => {
3950
- if (visited.has(p)) return;
3951
- visited.add(p);
3952
- const def = p?._def;
3953
- if (def === null || typeof def !== "object") {
3954
- noteUnanalysable(
3955
- typeof p === "function" ? {
3956
- rule: enclosingRule,
3957
- kind: "fused-rule",
3958
- reason: "fused rule function \u2014 a compose()/fuse result has no combinator graph. Analyze the composed grammar itself (analyzeGrammarGating) so the carried IR is re-lowered first."
3959
- } : {
3960
- rule: enclosingRule,
3961
- kind: "not-a-combinator",
3962
- reason: `value of type ${p === null ? "null" : typeof p} is not a combinator and carries no _def descriptor.`
3963
- }
3964
- );
3965
- return;
3966
- }
3967
- const d = def;
3968
- const rule = ruleNameOf2(p) ?? enclosingRule;
3969
- if (d.tag === "choice") {
3970
- raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3971
- antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
3972
- }
3973
- const rec = d;
3974
- const kids = [];
3975
- if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3976
- for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3977
- if (rec[k]) kids.push(rec[k]);
3978
- if (d.tag === "dispatch") {
3979
- kids.push(d.selector);
3980
- for (const c of d.cases) kids.push(c.parser);
3981
- if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3982
- if (d.otherwise !== void 0) kids.push(d.otherwise);
3983
- }
3984
- if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3985
- if (d.tag === "lazy") {
3986
- try {
3987
- kids.push(d.thunk());
3988
- } catch {
3989
- }
3990
- }
3991
- for (const k of kids) visit(k, rule);
3992
- };
3993
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3994
- const perRule = /* @__PURE__ */ new Map();
3995
- for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3996
- const seenInRule = /* @__PURE__ */ new Map();
3997
- const accept = new Set(opts?.accept ?? []);
3998
- const usedAccept = /* @__PURE__ */ new Set();
3999
- const choices = raw.map(({ g, rule }) => {
4000
- const n = seenInRule.get(rule) ?? 0;
4001
- seenInRule.set(rule, n + 1);
4002
- const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
4003
- const isAccepted = g.gates === "no" && accept.has(id);
4004
- if (isAccepted) usedAccept.add(id);
4005
- return { ...g, id, accepted: isAccepted };
4006
- });
4007
- const gated = choices.filter((c) => c.gates === "yes").length;
4008
- const recoverable = choices.filter((c) => c.gates === "recoverable").length;
4009
- const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
4010
- const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
4011
- const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
4012
- const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
4013
- return {
4014
- totalChoices: choices.length,
4015
- gated,
4016
- recoverable,
4017
- unanalysable,
4018
- ungated,
4019
- accepted,
4020
- deferred,
4021
- acceptedUnused,
4022
- choices,
4023
- antiPatterns
4024
- };
4025
- }
4026
- function analyzeChoice(p, d, rule, resolve2) {
4027
- const arms = d.parsers;
4028
- const shallow = arms.map((a) => a._meta.firstSet);
4029
- const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
4030
- const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
4031
- const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
4032
- return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
4033
- });
4034
- const overlaps = [];
4035
- for (let i = 0; i < deep.length; i++)
4036
- for (let j = i + 1; j < deep.length; j++)
4037
- if (!isAny(deep[i]) && !isAny(deep[j]) && intersects2(deep[i], deep[j]))
4038
- overlaps.push({
4039
- a: i,
4040
- b: j,
4041
- on: intersection(deep[i], deep[j]),
4042
- suggestion: "arms share a first char \u2014 left-factor. parseman auto-detects the sharedPrefix strategy for bare sequences (choice.ts); make the arms bare sequences with a common leading terminal, or restructure."
4043
- });
4044
- let gates;
4045
- if (d.disjoint) gates = "yes";
4046
- else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
4047
- else gates = "no";
4048
- const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
4049
- return {
4050
- rule,
4051
- strategy: d.strategy?.tag ?? "firstMatch",
4052
- gates,
4053
- deferred,
4054
- combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
4055
- anyArms,
4056
- overlaps
4057
- };
4058
- }
4059
- function formatGatingWarnings(report) {
4060
- const lines = [];
4061
- if (report.unanalysable.length > 0) {
4062
- lines.push(
4063
- `parseman gating: ${report.unanalysable.length} rule(s) UNANALYSABLE \u2014 this report is PARTIAL (${report.totalChoices} choice(s) examined). An empty finding list below does NOT mean the grammar is clean.`
4064
- );
4065
- for (const u of report.unanalysable)
4066
- lines.push(` \xB7 ${u.rule} [${u.kind}]: ${u.reason}`);
4067
- }
4068
- for (const c of report.ungated) {
4069
- lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
4070
- for (const a of c.anyArms)
4071
- lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
4072
- fix: ${a.suggestion}`);
4073
- for (const o of c.overlaps)
4074
- lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
4075
- fix: ${o.suggestion}`);
4076
- lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
4077
- }
4078
- for (const ap of report.antiPatterns)
4079
- lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
4080
- return lines;
3980
+ function firstSetToString(fs3) {
3981
+ if (fs3.kind === "any") return "ANY";
3982
+ if (fs3.kind === "empty") return "(empty)";
3983
+ return fs3.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
4081
3984
  }
4082
3985
 
4083
3986
  // src/analysis/duplication.ts
@@ -4120,7 +4023,7 @@ function childrenOf2(d) {
4120
4023
  }
4121
4024
  }
4122
4025
  }
4123
- var ruleNameOf3 = (p) => p._ruleName;
4026
+ var ruleNameOf2 = (p) => p._ruleName;
4124
4027
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
4125
4028
  function payloadKey(p, d) {
4126
4029
  switch (d.tag) {
@@ -4131,7 +4034,7 @@ function payloadKey(p, d) {
4131
4034
  case "keywords":
4132
4035
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
4133
4036
  case "lazy": {
4134
- const n = ruleNameOf3(p);
4037
+ const n = ruleNameOf2(p);
4135
4038
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
4136
4039
  }
4137
4040
  case "label":
@@ -4200,7 +4103,7 @@ var Interner = class {
4200
4103
  var MAX_RENDER = 160;
4201
4104
  function render(p, depth = 3) {
4202
4105
  const d = p._def;
4203
- const name = ruleNameOf3(p);
4106
+ const name = ruleNameOf2(p);
4204
4107
  switch (d.tag) {
4205
4108
  case "literal":
4206
4109
  return `literal('${d.value}')`;
@@ -4388,7 +4291,7 @@ function keywordAlternationHazards(words, boundary) {
4388
4291
  return out;
4389
4292
  }
4390
4293
  function analyzeDuplication(entry, opts) {
4391
- return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
4294
+ return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
4392
4295
  }
4393
4296
  function assertAnalyzable(ruleMap) {
4394
4297
  for (const [name, rule] of ruleMap) {
@@ -4426,11 +4329,11 @@ function analyzeDuplicationRules(ruleMap, opts) {
4426
4329
  return n;
4427
4330
  };
4428
4331
  const visited = /* @__PURE__ */ new Map();
4429
- const visit = (p, rule, path2, enclosingNode, enclosingReducer) => {
4332
+ const visit = (p, rule, path3, enclosingNode, enclosingReducer) => {
4430
4333
  if (visited.has(p)) return;
4431
4334
  const d = p._def;
4432
- const own = ruleNameOf3(p);
4433
- const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path2 };
4335
+ const own = ruleNameOf2(p);
4336
+ const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path3 };
4434
4337
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
4435
4338
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
4436
4339
  const innerReducer = enclosingReducer || d.tag === "node" && (d.build !== void 0 || d.buildSrc !== void 0) || d.tag === "transform" || d.tag === "leaf";
@@ -4442,12 +4345,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
4442
4345
  if (d.tag === "lazy") {
4443
4346
  try {
4444
4347
  const target = d.thunk();
4445
- visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
4348
+ visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
4446
4349
  } catch {
4447
4350
  }
4448
4351
  }
4449
4352
  };
4450
- for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
4353
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
4451
4354
  const accept = new Set(opts?.accept ?? []);
4452
4355
  const used = /* @__PURE__ */ new Set();
4453
4356
  const keep = (f) => {
@@ -4464,10 +4367,10 @@ function analyzeDuplicationRules(ruleMap, opts) {
4464
4367
  const divergentNodes = findDivergentNodes(visited, shapeOf);
4465
4368
  const structureLoss = findStructureLoss(visited, opts?.resolveRef);
4466
4369
  const keywordRegexes = findKeywordRegexes(visited);
4467
- const subsume = (fs2) => {
4370
+ const subsume = (fs3) => {
4468
4371
  const covered = /* @__PURE__ */ new Set();
4469
4372
  const surviving = /* @__PURE__ */ new Set();
4470
- for (const f of [...fs2].sort((a, b) => b.savings - a.savings)) {
4373
+ for (const f of [...fs3].sort((a, b) => b.savings - a.savings)) {
4471
4374
  if (f._members.every((m) => covered.has(m))) continue;
4472
4375
  surviving.add(f.id);
4473
4376
  for (const m of f._members) markSubtree(m, covered);
@@ -4590,10 +4493,10 @@ function findNearDuplicates(visited, interner, shapeOf, minSize) {
4590
4493
  return out.sort((a, b) => b.savings - a.savings);
4591
4494
  }
4592
4495
  function findDivergentSlot(members, shapeOf) {
4593
- const path2 = [];
4496
+ const path3 = [];
4594
4497
  const defs = [];
4595
4498
  let cur = [...members];
4596
- for (let guard2 = 0; guard2 < 64; guard2++) {
4499
+ for (let guard = 0; guard < 64; guard++) {
4597
4500
  const d0 = cur[0]._def;
4598
4501
  const kidLists = cur.map((c) => childrenOf2(c._def));
4599
4502
  const arity = kidLists[0].length;
@@ -4605,14 +4508,14 @@ function findDivergentSlot(members, shapeOf) {
4605
4508
  }
4606
4509
  if (diff.length !== 1) break;
4607
4510
  const i = diff[0];
4608
- path2.push(i);
4511
+ path3.push(i);
4609
4512
  defs.push(d0);
4610
4513
  cur = kidLists.map((k) => k[i]);
4611
4514
  const nextArity = childrenOf2(cur[0]._def).length;
4612
4515
  const samePayload = cur.every((c) => payloadKey(c, c._def) === payloadKey(cur[0], cur[0]._def));
4613
4516
  if (!samePayload || nextArity === 0) break;
4614
4517
  }
4615
- if (path2.length === 0) return null;
4518
+ if (path3.length === 0) return null;
4616
4519
  let slotSize = 0;
4617
4520
  const sizeOf = (p) => {
4618
4521
  let n = 1;
@@ -4620,14 +4523,14 @@ function findDivergentSlot(members, shapeOf) {
4620
4523
  return n;
4621
4524
  };
4622
4525
  for (const c of cur) slotSize = Math.max(slotSize, sizeOf(c));
4623
- return { path: path2, defs, variants: cur, slotSize };
4526
+ return { path: path3, defs, variants: cur, slotSize };
4624
4527
  }
4625
- function renderWithHole(p, path2, depth = 0) {
4626
- if (depth === path2.length) return "\u2039slot\u203A";
4528
+ function renderWithHole(p, path3, depth = 0) {
4529
+ if (depth === path3.length) return "\u2039slot\u203A";
4627
4530
  const d = p._def;
4628
4531
  const kids = childrenOf2(d);
4629
- const idx = path2[depth];
4630
- const parts = kids.map((k, i) => i === idx ? renderWithHole(k, path2, depth + 1) : render(k, 2));
4532
+ const idx = path3[depth];
4533
+ const parts = kids.map((k, i) => i === idx ? renderWithHole(k, path3, depth + 1) : render(k, 2));
4631
4534
  switch (d.tag) {
4632
4535
  case "sequence":
4633
4536
  return `sequence(${parts.join(", ")})`;
@@ -4831,13 +4734,13 @@ function findRegexClasses(visited) {
4831
4734
  }
4832
4735
  return out.sort((a, b) => Number(b.drifted) - Number(a.drifted) || b.count - a.count);
4833
4736
  }
4834
- function intersects3(a, b) {
4737
+ function intersects2(a, b) {
4835
4738
  if (a.kind === "any" || b.kind === "any") return true;
4836
4739
  if (a.kind === "empty" || b.kind === "empty") return false;
4837
4740
  for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
4838
4741
  return false;
4839
4742
  }
4840
- function intersection2(a, b) {
4743
+ function intersection(a, b) {
4841
4744
  if (a.kind === "any") return b;
4842
4745
  if (b.kind === "any") return a;
4843
4746
  if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
@@ -4866,19 +4769,19 @@ function leadingRegex(arm) {
4866
4769
  const t = termsOf(arm)[0];
4867
4770
  return t !== void 0 && t._def.tag === "regex";
4868
4771
  }
4869
- function findOverlaps(visited, shapeOf, resolve2) {
4772
+ function findOverlaps(visited, shapeOf, resolve3) {
4870
4773
  const out = [];
4871
4774
  for (const v2 of visited.values()) {
4872
4775
  const d = v2.d;
4873
4776
  if (d.tag !== "choice") continue;
4874
4777
  const arms = d.parsers;
4875
- const fs2 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
4778
+ const fs3 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve3));
4876
4779
  const strategy = d.strategy;
4877
4780
  for (let i = 0; i < arms.length; i++) {
4878
4781
  for (let j = i + 1; j < arms.length; j++) {
4879
- const A = fs2[i], B = fs2[j];
4782
+ const A = fs3[i], B = fs3[j];
4880
4783
  if (A.kind === "any" || B.kind === "any") continue;
4881
- if (!intersects3(A, B)) continue;
4784
+ if (!intersects2(A, B)) continue;
4882
4785
  const ta = termsOf(arms[i]), tb = termsOf(arms[j]);
4883
4786
  let shared = 0;
4884
4787
  while (shared < ta.length && shared < tb.length && shapeOf(ta[shared]) === shapeOf(tb[shared])) shared++;
@@ -4890,7 +4793,7 @@ function findOverlaps(visited, shapeOf, resolve2) {
4890
4793
  site: v2.site,
4891
4794
  a: i,
4892
4795
  b: j,
4893
- on: intersection2(A, B),
4796
+ on: intersection(A, B),
4894
4797
  sharedLeadingTerms: shared,
4895
4798
  sharedPrefix: shared > 0 ? clamp(ta.slice(0, shared).map((t) => render(t, 2)).join(", ")) : null,
4896
4799
  handledByStrategy: handled,
@@ -5192,7 +5095,7 @@ function nestedNodeTypes(root, out, seen, isRoot) {
5192
5095
  for (const k of childrenOf2(d)) nestedNodeTypes(k, out, seen, false);
5193
5096
  }
5194
5097
  var MAX_LOST_TYPES = 8;
5195
- function findStructureLoss(visited, resolve2) {
5098
+ function findStructureLoss(visited, resolve3) {
5196
5099
  const nestedCache = /* @__PURE__ */ new Map();
5197
5100
  const nested = (p) => {
5198
5101
  const hit = nestedCache.get(p);
@@ -5209,18 +5112,18 @@ function findStructureLoss(visited, resolve2) {
5209
5112
  const arms = d.parsers;
5210
5113
  const resolved = arms.map(resolveArm);
5211
5114
  const nodeTypes = resolved.map(wrappingNodeType);
5212
- const fs2 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
5115
+ const fs3 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve3));
5213
5116
  for (let i = 0; i < arms.length; i++) {
5214
5117
  if (d.gates[i] != null) continue;
5215
5118
  const type = nodeTypes[i] ?? null;
5216
5119
  if (type === null) continue;
5217
5120
  if (nested(resolved[i]).size !== 0) continue;
5218
- const A = fs2[i];
5121
+ const A = fs3[i];
5219
5122
  if (A.kind === "any") continue;
5220
5123
  for (let j = i + 1; j < arms.length; j++) {
5221
5124
  if (nodeTypes[j] !== type) continue;
5222
- const B = fs2[j];
5223
- if (B.kind === "any" || !intersects3(A, B)) continue;
5125
+ const B = fs3[j];
5126
+ if (B.kind === "any" || !intersects2(A, B)) continue;
5224
5127
  const lost = nested(resolved[j]);
5225
5128
  if (lost.size === 0) continue;
5226
5129
  const lostNodeTypes = [...lost].sort();
@@ -5231,7 +5134,7 @@ function findStructureLoss(visited, resolve2) {
5231
5134
  nodeType: type,
5232
5135
  earlier: i,
5233
5136
  later: j,
5234
- on: intersection2(A, B),
5137
+ on: intersection(A, B),
5235
5138
  lostNodeTypes: lostNodeTypes.slice(0, MAX_LOST_TYPES),
5236
5139
  earlierShape: clamp(render(resolved[i], 3)),
5237
5140
  laterShape: clamp(render(resolved[j], 3)),
@@ -5489,11 +5392,11 @@ function buildBalancedInterior(open, close, skips) {
5489
5392
  return combi;
5490
5393
  }
5491
5394
  function firstSetClassChars(c) {
5492
- const fs2 = c._meta.firstSet;
5493
- if (fs2.kind === "empty") return [];
5494
- if (fs2.kind !== "ranges") return null;
5395
+ const fs3 = c._meta.firstSet;
5396
+ if (fs3.kind === "empty") return [];
5397
+ if (fs3.kind !== "ranges") return null;
5495
5398
  const out = [];
5496
- for (const { lo, hi } of fs2.ranges) {
5399
+ for (const { lo, hi } of fs3.ranges) {
5497
5400
  if (hi - lo > 8) return null;
5498
5401
  for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5499
5402
  }
@@ -5507,13 +5410,13 @@ function escapeClassChar(ch2) {
5507
5410
  function idPart(value) {
5508
5411
  return encodeURIComponent(value);
5509
5412
  }
5510
- function dispatchArmIds(def, path2) {
5413
+ function dispatchArmIds(def, path3) {
5511
5414
  const ids = [];
5512
- for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5415
+ for (const entry of def.cases) ids.push(`dispatch:${path3}/when:${entry.keys.map(idPart).join("|")}`);
5513
5416
  for (const entry of def.matchers ?? []) {
5514
- ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5417
+ ids.push(`dispatch:${path3}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5515
5418
  }
5516
- if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5419
+ if (def.otherwise) ids.push(`dispatch:${path3}/otherwise`);
5517
5420
  return ids;
5518
5421
  }
5519
5422
  function children(def, winners) {
@@ -5552,7 +5455,15 @@ function children(def, winners) {
5552
5455
  let resolved;
5553
5456
  try {
5554
5457
  resolved = def.thunk();
5555
- } catch {
5458
+ } catch (e) {
5459
+ recordDegradation({
5460
+ code: "coverage-definitions-unavailable",
5461
+ severity: "warn",
5462
+ where: "<lazy rule reference>",
5463
+ subject: `unresolvable reference (${e.message})`,
5464
+ fellBackTo: "the coverage walk stopped at this reference, so every definition below it is ABSENT from the denominator \u2014 which raises the reported ratio rather than lowering it",
5465
+ otherwise: "the referenced rule and its whole subtree would contribute definitions"
5466
+ });
5556
5467
  return [];
5557
5468
  }
5558
5469
  const name = resolved._ruleName;
@@ -5582,7 +5493,7 @@ function buildGrammarPlan(entry, winners) {
5582
5493
  }
5583
5494
  }
5584
5495
  const seen = /* @__PURE__ */ new Set();
5585
- const visit = (parser2, path2) => {
5496
+ const visit = (parser2, path3) => {
5586
5497
  if (seen.has(parser2)) return;
5587
5498
  seen.add(parser2);
5588
5499
  const rule = winnerNames.get(parser2) ?? parser2._ruleName;
@@ -5592,22 +5503,22 @@ function buildGrammarPlan(entry, winners) {
5592
5503
  rules2.set(parser2, id);
5593
5504
  }
5594
5505
  if (parser2._def.tag === "choice") {
5595
- const ids = parser2._def.parsers.map((_, index) => `choice:${path2}/arm:${index}`);
5506
+ const ids = parser2._def.parsers.map((_, index) => `choice:${path3}/arm:${index}`);
5596
5507
  choices.set(parser2, ids);
5597
5508
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5598
5509
  }
5599
5510
  if (parser2._def.tag === "dispatch") {
5600
- const ids = dispatchArmIds(parser2._def, path2);
5511
+ const ids = dispatchArmIds(parser2._def, path3);
5601
5512
  dispatches.set(parser2, ids);
5602
5513
  ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5603
5514
  }
5604
- if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
5515
+ if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path3}`);
5605
5516
  if (parser2._def.tag === "label") {
5606
- const id = `label:${path2}`;
5517
+ const id = `label:${path3}`;
5607
5518
  labels.set(parser2, [id]);
5608
5519
  definitions.set(id, { id, kind: "label" });
5609
5520
  }
5610
- children(parser2._def, winners).forEach((child, index) => visit(child, `${path2}/${parser2._def.tag}:${index}`));
5521
+ children(parser2._def, winners).forEach((child, index) => visit(child, `${path3}/${parser2._def.tag}:${index}`));
5611
5522
  };
5612
5523
  const roots = Array.isArray(entry) ? entry : [entry];
5613
5524
  for (const root of roots) {
@@ -5665,15 +5576,15 @@ function parseStringShape(source) {
5665
5576
  if (source.length < 3 || source[source.length - 1] !== q) return null;
5666
5577
  const inner = source.slice(1, -1);
5667
5578
  const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
5668
- const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5579
+ const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5669
5580
  let body;
5670
5581
  let esc;
5671
- let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
5582
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
5672
5583
  if (m) {
5673
5584
  body = m[1];
5674
5585
  esc = m[2];
5675
5586
  } else {
5676
- m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
5587
+ m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
5677
5588
  if (m) {
5678
5589
  esc = m[1];
5679
5590
  body = m[2];
@@ -6379,10 +6290,10 @@ var ALT_SWITCH_MIN_CASES = 3;
6379
6290
  function planAltSwitch(firsts) {
6380
6291
  const cases = [];
6381
6292
  let total = 0;
6382
- for (const fs2 of firsts) {
6383
- if (!fs2 || fs2.negated) return { kind: "if" };
6293
+ for (const fs3 of firsts) {
6294
+ if (!fs3 || fs3.negated) return { kind: "if" };
6384
6295
  const pts = [];
6385
- for (const [lo, hi] of fs2.ranges) {
6296
+ for (const [lo, hi] of fs3.ranges) {
6386
6297
  if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
6387
6298
  for (let cp = lo; cp <= hi; cp++) pts.push(cp);
6388
6299
  }
@@ -6457,8 +6368,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
6457
6368
  } else {
6458
6369
  const c0 = codeAt(start, firstChar);
6459
6370
  shape.arms.forEach((arm, k) => {
6460
- const fs2 = shape.firsts[k];
6461
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
6371
+ const fs3 = shape.firsts[k];
6372
+ const cond = fs3.negated ? `!(${classCond(c0, fs3.ranges)})` : `(${classCond(c0, fs3.ranges)})`;
6462
6373
  const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
6463
6374
  lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
6464
6375
  lines.push(...m.setup);
@@ -6875,27 +6786,53 @@ function emitScannableTerminal(shape, c) {
6875
6786
  }
6876
6787
 
6877
6788
  // src/compiler/inline-build.ts
6878
- var MK_BUILD_RE = /^\(\s*(\w+)\s*,\s*\w+\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$/;
6789
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
6790
+ var ANY_P = String.raw`\w+${ANN}`;
6791
+ var MK_BUILD_RE = new RegExp(
6792
+ String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
6793
+ );
6879
6794
  function escapeRegExp(s) {
6880
6795
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6881
6796
  }
6797
+ function identMkBuildRe(typeIdent) {
6798
+ return new RegExp(
6799
+ String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*(?:[\w$.]*\.)?mk\s*\(\s*${escapeRegExp(typeIdent)}\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
6800
+ );
6801
+ }
6882
6802
  function looseMkBuildRe(type) {
6883
6803
  return new RegExp(
6884
- String.raw`^\(\s*(\w+)\s*,\s*\w+\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
6804
+ String.raw`^\(\s*(\w+)${ANN}\s*,\s*${ANY_P}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*,\s*(\w+)${ANN}\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
6885
6805
  );
6886
6806
  }
6807
+ var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
6887
6808
  function analyzeMkInlineBuild(def) {
6888
6809
  if (!def.build) return null;
6889
6810
  if (def.type === void 0) return null;
6890
- const src = (def.buildSrc ?? def.build.toString()).trim();
6811
+ const src = buildAnalysisSrc(def).trim();
6891
6812
  const strict = src.match(MK_BUILD_RE);
6892
6813
  if (strict) {
6893
6814
  const mkType = strict[6];
6894
- return mkType === def.type ? def.type : null;
6815
+ if (mkType === def.type) return def.type;
6816
+ reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
6817
+ return null;
6895
6818
  }
6896
6819
  if (looseMkBuildRe(def.type).test(src)) return def.type;
6820
+ if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
6821
+ if (LOOKS_LIKE_MK_RE.test(src)) {
6822
+ reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
6823
+ }
6897
6824
  return null;
6898
6825
  }
6826
+ function reportMkMiss(type, src, why) {
6827
+ recordDegradation({
6828
+ code: "mk-inline-missed",
6829
+ severity: "warn",
6830
+ where: `node("${type}")`,
6831
+ subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
6832
+ fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
6833
+ otherwise: "the node object would be constructed at the call site with no call frame"
6834
+ });
6835
+ }
6899
6836
  function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
6900
6837
  return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
6901
6838
  }
@@ -7078,7 +7015,30 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
7078
7015
  seen.delete(p);
7079
7016
  }
7080
7017
  }
7018
+ function tern(cond, whenTrue, whenFalse) {
7019
+ if (cond === "false") return whenFalse;
7020
+ if (cond === "true") return whenTrue;
7021
+ return `${cond} ? ${whenTrue} : ${whenFalse}`;
7022
+ }
7023
+ function notGate(cond) {
7024
+ if (cond === "false") return "true";
7025
+ if (cond === "true") return "false";
7026
+ return `!(${cond})`;
7027
+ }
7028
+ function orGate(a, b) {
7029
+ if (a === "false") return b;
7030
+ if (b === "false") return a;
7031
+ if (a === "true" || b === "true") return "true";
7032
+ return `${a} || ${b}`;
7033
+ }
7034
+ function andGate(a, b) {
7035
+ if (a === "false" || b === "false") return "false";
7036
+ if (a === "true") return b;
7037
+ if (b === "true") return a;
7038
+ return `${a} && ${b}`;
7039
+ }
7081
7040
  var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
7041
+ var RAW_ENTRY_DECL = "const _rawEntry = (v, input, s, e, span) => (typeof v === 'object' && v !== null && (v._tag === 'node' || v._tag === 'leaf' || v._tag === 'parseError')) ? v : { _tag: 'leaf', value: typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''), span: span ?? { start: s, end: e } }";
7082
7042
  var LINE_TRACK_DECL = "const _trackLines = (_ctx, input, start, end) => { const from = _ctx._lineScannedTo ?? 0; if (end <= from) return; for (let i = from; i < end; i++) if (input.charCodeAt(i) === 10) _ctx._lineStarts.push(i + 1); _ctx._lineScannedTo = end }";
7083
7043
  var LINE_SPAN_DECL = "const _lineCol = (_ctx, offset) => { const starts = _ctx._lineStarts; let lo = 0, hi = starts.length - 1; while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (starts[mid] <= offset) lo = mid; else hi = mid - 1 } return [lo + 1, offset - starts[lo] + 1] }\nconst _spanLines = (_ctx, start, end) => { const s = _lineCol(_ctx, start), e = _lineCol(_ctx, end); return { start, end, startLine: s[0], startColumn: s[1], endLine: e[0], endColumn: e[1] } }";
7084
7044
  var _loweringSink = null;
@@ -7090,6 +7050,27 @@ function endLoweringCapture() {
7090
7050
  _loweringSink = null;
7091
7051
  return misses;
7092
7052
  }
7053
+ var _inlineCapSink = null;
7054
+ function beginInlineCapCapture() {
7055
+ _inlineCapSink = [];
7056
+ }
7057
+ function endInlineCapCapture() {
7058
+ const sites = _inlineCapSink ?? [];
7059
+ _inlineCapSink = null;
7060
+ return sites;
7061
+ }
7062
+ function formatInlineCapSites(sites, max) {
7063
+ if (sites.length === 0) return [];
7064
+ const byFn = /* @__PURE__ */ new Map();
7065
+ for (const s of sites) {
7066
+ const cur = byFn.get(s.fn);
7067
+ if (cur) {
7068
+ cur.count += 1;
7069
+ cur.nodes += s.nodes;
7070
+ } else byFn.set(s.fn, { count: 1, nodes: s.nodes });
7071
+ }
7072
+ return [...byFn].map(([fn, { count, nodes }]) => `[parseman] inline-cap ${fn}: inline budget of ${max} node(s) spent \u2014 ${count} single-use ref(s) totalling ~${nodes} node(s) became called functions instead of inline bodies. Raise it with maxInline (or PARSEMAN_MAX_INLINE) if this rule is hot.`);
7073
+ }
7093
7074
  function hasSelectedRootTrivia(ctx) {
7094
7075
  return ctx.rootTriviaClassified === true;
7095
7076
  }
@@ -7373,10 +7354,10 @@ function ensureTriviaFn(ctx) {
7373
7354
  function ensureTriviaCaptureFn(ctx) {
7374
7355
  return ensureTriviaFn(ctx);
7375
7356
  }
7376
- function firstSetCond(codeVar, fs2) {
7377
- if (fs2.kind === "any") return "true";
7378
- if (fs2.kind === "empty") return "false";
7379
- return fs2.ranges.map(
7357
+ function firstSetCond(codeVar, fs3) {
7358
+ if (fs3.kind === "any") return "true";
7359
+ if (fs3.kind === "empty") return "false";
7360
+ return fs3.ranges.map(
7380
7361
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
7381
7362
  ).join(" || ");
7382
7363
  }
@@ -7389,10 +7370,10 @@ function planDisjointDispatch(parsers) {
7389
7370
  const cases = [];
7390
7371
  let total = 0;
7391
7372
  for (const p of parsers) {
7392
- const fs2 = p._meta.firstSet;
7393
- if (fs2.kind !== "ranges") return { kind: "if" };
7373
+ const fs3 = p._meta.firstSet;
7374
+ if (fs3.kind !== "ranges") return { kind: "if" };
7394
7375
  const pts = [];
7395
- for (const r of fs2.ranges) {
7376
+ for (const r of fs3.ranges) {
7396
7377
  if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
7397
7378
  for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
7398
7379
  }
@@ -7785,9 +7766,9 @@ function emitRegex(def, ctx, pos, canMatchNewline = true) {
7785
7766
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
7786
7767
  return { stmts, valueVar: vv, endVar };
7787
7768
  }
7788
- function syncSentinelExpr(fs2) {
7789
- if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
7790
- return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
7769
+ function syncSentinelExpr(fs3) {
7770
+ if (fs3.kind !== "ranges" || fs3.ranges.length === 0) return null;
7771
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs3.ranges)} })`;
7791
7772
  }
7792
7773
  function emitSeqValues(def, ctx, pos) {
7793
7774
  const startV = v(ctx, "_start");
@@ -7801,8 +7782,8 @@ function emitSeqValues(def, ctx, pos) {
7801
7782
  if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
7802
7783
  const publishSync = (i) => {
7803
7784
  if (!ctx.recovery) return "";
7804
- const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
7805
- const ref2 = syncSentinelExpr(fs2);
7785
+ const fs3 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
7786
+ const ref2 = syncSentinelExpr(fs3);
7806
7787
  return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
7807
7788
  };
7808
7789
  for (let i = 0; i < def.parsers.length; i++) {
@@ -7838,9 +7819,11 @@ function emitSeqValues(def, ctx, pos) {
7838
7819
  const markLog = v(ctx, "_mklg");
7839
7820
  const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
7840
7821
  const scanEndV = v(ctx, "_sne");
7841
- const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? "(_ctx._triviaLog !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0" : "_ctx._triviaLog !== undefined ? 2 : 0";
7822
+ const logV = v(ctx, "_tlg");
7823
+ const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
7842
7824
  stmts.push(
7843
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`,
7825
+ `${ind(ctx)}const ${logV} = _ctx._triviaLog`,
7826
+ `${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
7844
7827
  ...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
7845
7828
  `${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
7846
7829
  ...emitLineTrack(ctx, curV, scanEndV)
@@ -7850,7 +7833,7 @@ function emitSeqValues(def, ctx, pos) {
7850
7833
  const endAfterV = v(ctx, "_sea");
7851
7834
  stmts.push(
7852
7835
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
7853
- `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog};${markRootLog ? ` if (_ctx._rootTriviaLog && _ctx._rootTriviaLog.length !== ${markRootLog}) _ctx._rootTriviaLog.length = ${markRootLog};` : ""} }`
7836
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) ${curV} = ${endAfterV}; else { if (${logV} !== undefined && ${logV}.length !== ${markLog}) ${logV}.length = ${markLog};${markRootLog ? ` if (_ctx._rootTriviaLog && _ctx._rootTriviaLog.length !== ${markRootLog}) _ctx._rootTriviaLog.length = ${markRootLog};` : ""} }`
7854
7837
  );
7855
7838
  valueVars.push(r2.valueVar);
7856
7839
  continue;
@@ -7916,8 +7899,8 @@ function canMatchEmptyAtStart(p) {
7916
7899
  }
7917
7900
  }
7918
7901
  function needsFirstSetGuard(p) {
7919
- const fs2 = p._meta.firstSet;
7920
- return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
7902
+ const fs3 = p._meta.firstSet;
7903
+ return fs3.kind !== "any" && !canMatchEmptyAtStart(p);
7921
7904
  }
7922
7905
  function coverageHit(ctx, id, offset = "_pos", end) {
7923
7906
  return id === void 0 ? [] : [
@@ -8922,9 +8905,18 @@ function emitPeek(def, ctx, pos) {
8922
8905
  endVar: pos
8923
8906
  };
8924
8907
  }
8925
- function emitRouted(ctx, pos) {
8908
+ function emitRouted(ctx, pos, def) {
8926
8909
  const local = ctx.routedLocal;
8910
+ const fallback = def.fallback;
8927
8911
  if (local !== void 0) {
8912
+ if (fallback !== void 0 && local.startVar !== pos) {
8913
+ return emitRoutedWithFallback(ctx, pos, fallback, {
8914
+ test: `${local.startVar} === ${pos}`,
8915
+ valExpr: local.valueVar,
8916
+ startExpr: local.startVar,
8917
+ endExpr: local.endVar
8918
+ });
8919
+ }
8928
8920
  return {
8929
8921
  stmts: [
8930
8922
  ...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
@@ -8935,9 +8927,19 @@ function emitRouted(ctx, pos) {
8935
8927
  };
8936
8928
  }
8937
8929
  const item = v(ctx, "_rt");
8930
+ const read = `${ind(ctx)}const ${item} = _ctx._routed`;
8931
+ if (fallback !== void 0) {
8932
+ const r = emitRoutedWithFallback(ctx, pos, fallback, {
8933
+ test: `${item} !== undefined && ${item}.span.start === ${pos}`,
8934
+ valExpr: `${item}.value`,
8935
+ startExpr: `${item}.span.start`,
8936
+ endExpr: `${item}.span.end`
8937
+ });
8938
+ return { ...r, stmts: [read, ...r.stmts] };
8939
+ }
8938
8940
  return {
8939
8941
  stmts: [
8940
- `${ind(ctx)}const ${item} = _ctx._routed`,
8942
+ read,
8941
8943
  ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8942
8944
  ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8943
8945
  ],
@@ -8945,6 +8947,30 @@ function emitRouted(ctx, pos) {
8945
8947
  endVar: `${item}.span.end`
8946
8948
  };
8947
8949
  }
8950
+ function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
8951
+ const valV = v(ctx, "_rtv");
8952
+ const endV = v(ctx, "_rte");
8953
+ const i = ind(ctx);
8954
+ ctx.indent++;
8955
+ const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
8956
+ const fb = emit(fallback, ctx, pos);
8957
+ const inner = ind(ctx);
8958
+ ctx.indent--;
8959
+ return {
8960
+ stmts: [
8961
+ `${i}let ${valV}, ${endV}`,
8962
+ `${i}if (${routed2.test}) {`,
8963
+ ...capture,
8964
+ `${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
8965
+ `${i}} else {`,
8966
+ ...fb.stmts,
8967
+ `${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
8968
+ `${i}}`
8969
+ ],
8970
+ valueVar: valV,
8971
+ endVar: endV
8972
+ };
8973
+ }
8948
8974
  function escapeTokenLiteral(value) {
8949
8975
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8950
8976
  }
@@ -9126,22 +9152,16 @@ function emitNode(def, ctx, pos) {
9126
9152
  const smk = structural ? v(ctx, "_smk") : null;
9127
9153
  if (structural) ctx.needsHostReads = true;
9128
9154
  const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
9129
- const pmV = v(ctx, "_pm"), recV = v(ctx, "_rec"), capV = v(ctx, "_cap");
9130
- const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
9131
- const profileRecognizer = recV;
9132
- const profileCapture = capV;
9133
- const cstMode = ctx.hostMode === "cst";
9134
- const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
9135
- const directCstGate = directCstV === null ? "false" : profileCapture;
9155
+ const profileRecognizer = "false";
9156
+ const profileCapture = "false";
9136
9157
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
9137
9158
  const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
9138
- const chAlloc = def.unwrap || def.collapse ? `${profileRecognizer} ? undefined : []` : `${profileRecognizer} ? undefined : ((${profileCapture} || ${chNeededExpr}) ? [] : undefined)`;
9139
- const allocStmt = structural ? `${i}const ${capTLv} = !(${profileRecognizer}) && (${profileCapture} || ${structuralCapturesTrivia ? "true" : hostTriviaGate}), ${capSTv} = !(${profileRecognizer} || ${profileCapture}) && (_ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6)))${capFv ? `, ${capFv} = !(${profileRecognizer}) && (${profileCapture} || (_ctx.build !== undefined && _hostReads(_ctx.build, 2)))` : ""}
9140
- ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9141
- ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9142
- ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
9159
+ const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
9160
+ const dcstAlloc = "undefined";
9161
+ const allocStmt = structural ? `${i}const ${capTLv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, structuralCapturesTrivia ? "true" : hostTriviaGate))}, ${capSTv} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "(_ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6)))")}${capFv ? `, ${capFv} = ${andGate(notGate(profileRecognizer), orGate(profileCapture, "(_ctx.build !== undefined && _hostReads(_ctx.build, 2))"))}` : ""}
9162
+ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${tern(profileRecognizer, "undefined", "[]")}, ${tlV} = ${tern(profileRecognizer, "undefined", innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`)}` : capturesTrivia ? `${i}const ${capTLv} = ${notGate(profileRecognizer)}, ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}, ${tlV} = ${tern(profileRecognizer, "undefined", "[]")}` : `${i}const ${chV} = ${tern(profileRecognizer, "undefined", capturesChildren ? "[]" : dcstAlloc)}, ${rawV} = ${tern(profileRecognizer, "undefined", capturesRaw ? "[]" : dcstAlloc)}`;
9143
9163
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
9144
- const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
9164
+ const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
9145
9165
  const sf = hasFields ? v(ctx, "_sf") : null;
9146
9166
  const fArr = hasFields ? v(ctx, "_fa") : null;
9147
9167
  const fObj = hasFields ? v(ctx, "_fields") : "undefined";
@@ -9150,7 +9170,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9150
9170
  const installTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${capTLv ?? "false"}; _ctx._cstTriviaLog = ${innerTl}${smk ? `; _ctx._triviaCaptureMask = ${capTLv} && _ctx.build !== undefined && _ctx.build._parsemanTriviaKinds !== undefined ? _ctx.build._parsemanTriviaKinds(${JSON.stringify(def.type)}) : ${smk}` : ""}` : "";
9151
9171
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
9152
9172
  const preGuard = [];
9153
- if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
9173
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
9154
9174
  const gcV = v(ctx, "_ngc");
9155
9175
  const gExp = armStaticExpected(ctx, def.parser);
9156
9176
  const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
@@ -9161,7 +9181,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9161
9181
  }
9162
9182
  const stmts = [
9163
9183
  ...preGuard,
9164
- profHoist,
9165
9184
  allocStmt,
9166
9185
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
9167
9186
  `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
@@ -9178,21 +9197,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9178
9197
  }
9179
9198
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
9180
9199
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
9181
- stmts.push(
9182
- `${i}if (_ctx._pmProfile) {`,
9183
- `${i} _ctx._pmProfile.nodes++`,
9184
- `${i} if (${profileCapture}) {`,
9185
- `${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
9186
- `${i} }`,
9187
- `${i}}`
9188
- );
9189
9200
  let stV = "undefined";
9190
9201
  if (structural) {
9191
9202
  stV = v(ctx, "_nst");
9192
9203
  stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9193
9204
  } else if (clonesState) {
9194
9205
  stV = v(ctx, "_nst");
9195
- stmts.push(`${i}const ${stV} = !(${profileRecognizer} || ${profileCapture}) && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9206
+ stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
9196
9207
  }
9197
9208
  if (sf && fArr) {
9198
9209
  const fe = v(ctx, "_fe");
@@ -9216,19 +9227,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9216
9227
  if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
9217
9228
  const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: ${nodeSpanV}, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, nodeSpanV, tlV) : def.project !== void 0 ? emitNodeProjectExpr(def.type, def.project, chV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV})`;
9218
9229
  const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
9219
- const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${hostBuildArgs})`;
9230
+ const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
9220
9231
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
9221
- 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;
9232
+ const hostCollapses = structural || cstOut;
9233
+ const hostCollapseExpr = hostCollapses ? `_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;
9222
9234
  const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
9223
9235
  const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
9224
9236
  const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
9225
- stmts.push(
9226
- `${i}const ${ndV} = (${profileRecognizer} || ${profileCapture}) ? undefined : (${finalExpr})`,
9227
- `${i}if (!(${profileRecognizer})) {`,
9228
- `${i} if (${sc}) ${sc}.push(${ndV})`,
9229
- `${i} if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'parseError')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : (typeof ${ndV} === 'object' && ${ndV} !== null ? input.slice(${pos}, ${endVar}) : ''), span: ${nodeSpanV} })`,
9230
- `${i}}`
9231
- );
9237
+ const ndGate = orGate(profileRecognizer, profileCapture);
9238
+ const recGate = notGate(profileRecognizer);
9239
+ const pushIndent = recGate === "true" ? i : `${i} `;
9240
+ const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
9241
+ ctx.needsRawEntry = true;
9242
+ const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
9243
+ stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
9244
+ if (recGate === "true") stmts.push(pushCh, pushRaw);
9245
+ else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
9232
9246
  return { stmts, valueVar: ndV, endVar };
9233
9247
  }
9234
9248
  function emitRuntimeFallback(parser2, ctx, pos) {
@@ -9257,7 +9271,12 @@ function emitLazy(p, def, ctx, pos) {
9257
9271
  } catch {
9258
9272
  return emitRuntimeFallback(p, ctx, pos);
9259
9273
  }
9260
- return emit(resolved, ctx, pos);
9274
+ const cost = usage.sizes.get(resolved) ?? 1;
9275
+ if (cost <= ctx.inlineLeft) {
9276
+ ctx.inlineLeft -= cost;
9277
+ return emit(resolved, ctx, pos);
9278
+ }
9279
+ _inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
9261
9280
  }
9262
9281
  if (!ctx.namedParsers.has(p)) {
9263
9282
  const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
@@ -9272,6 +9291,9 @@ function emitLazy(p, def, ctx, pos) {
9272
9291
  const savedIndent = ctx.indent;
9273
9292
  const savedFailLabel = ctx.failLabel;
9274
9293
  const savedRecord = ctx.recordFail;
9294
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9295
+ ctx.inlineLeft = ctx.inlineMax;
9296
+ ctx.currentFnName = fnName2;
9275
9297
  const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
9276
9298
  const savedActiveRule = ctx.activeCoverageRuleId;
9277
9299
  const savedSuppressFailure = ctx.suppressCoverageFailure;
@@ -9286,6 +9308,8 @@ function emitLazy(p, def, ctx, pos) {
9286
9308
  ctx.indent = savedIndent;
9287
9309
  ctx.failLabel = savedFailLabel;
9288
9310
  ctx.recordFail = savedRecord;
9311
+ ctx.inlineLeft = savedInlineLeft;
9312
+ ctx.currentFnName = savedFnName;
9289
9313
  ctx.activeCoverageRuleId = savedActiveRule;
9290
9314
  ctx.suppressCoverageFailure = savedSuppressFailure;
9291
9315
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
@@ -9380,6 +9404,9 @@ function emit(p, ctx, pos) {
9380
9404
  const savedIndent = ctx.indent;
9381
9405
  const savedFailLabel = ctx.failLabel;
9382
9406
  const savedRecord = ctx.recordFail;
9407
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9408
+ ctx.inlineLeft = ctx.inlineMax;
9409
+ ctx.currentFnName = fnName;
9383
9410
  ctx.indent = 1;
9384
9411
  ctx.failLabel = "_pfail";
9385
9412
  ctx.recordFail = true;
@@ -9387,6 +9414,8 @@ function emit(p, ctx, pos) {
9387
9414
  ctx.indent = savedIndent;
9388
9415
  ctx.failLabel = savedFailLabel;
9389
9416
  ctx.recordFail = savedRecord;
9417
+ ctx.inlineLeft = savedInlineLeft;
9418
+ ctx.currentFnName = savedFnName;
9390
9419
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
9391
9420
  return instrument(emitNamedFnCall(ctx, fnName, pos));
9392
9421
  }
@@ -9580,7 +9609,7 @@ function emitDispatch(p, ctx, pos) {
9580
9609
  case "peek":
9581
9610
  return emitPeek(def, ctx, pos);
9582
9611
  case "routed":
9583
- return emitRouted(ctx, pos);
9612
+ return emitRouted(ctx, pos, def);
9584
9613
  case "node":
9585
9614
  return emitNode(def, ctx, pos);
9586
9615
  case "scanTo":
@@ -9615,6 +9644,9 @@ function emitDispatch(p, ctx, pos) {
9615
9644
  const savedIndent = ctx.indent;
9616
9645
  const savedFailLabel = ctx.failLabel;
9617
9646
  const savedRecord = ctx.recordFail;
9647
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9648
+ ctx.inlineLeft = ctx.inlineMax;
9649
+ ctx.currentFnName = fnName;
9618
9650
  ctx.indent = 1;
9619
9651
  ctx.failLabel = "_pfail";
9620
9652
  ctx.recordFail = true;
@@ -9622,6 +9654,8 @@ function emitDispatch(p, ctx, pos) {
9622
9654
  ctx.indent = savedIndent;
9623
9655
  ctx.failLabel = savedFailLabel;
9624
9656
  ctx.recordFail = savedRecord;
9657
+ ctx.inlineLeft = savedInlineLeft;
9658
+ ctx.currentFnName = savedFnName;
9625
9659
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
9626
9660
  }
9627
9661
  const fn = ctx.namedParsers.get(innerParser);
@@ -9780,12 +9814,16 @@ function childrenOf3(def) {
9780
9814
  return [def.parser, def.sentinel];
9781
9815
  case "scanTo":
9782
9816
  return [def.sentinel, ...def.skip];
9817
+ // A `routed()` fallback IS emitted at this site (emitRouted), so the usage
9818
+ // analysis must see the edge — otherwise a fallback shared by several routed()
9819
+ // sites is miscounted as single-use and inlined at each.
9820
+ case "routed":
9821
+ return def.fallback ? [def.fallback] : [];
9783
9822
  case "lazy":
9784
9823
  case "literal":
9785
9824
  case "regex":
9786
9825
  case "keywords":
9787
9826
  case "guard":
9788
- case "routed":
9789
9827
  case "unknown":
9790
9828
  return [];
9791
9829
  }
@@ -9806,6 +9844,17 @@ function isHoistableTag(tag) {
9806
9844
  return HOISTABLE_TAGS.has(tag);
9807
9845
  }
9808
9846
  var HOIST_MIN_SUBTREE = 3;
9847
+ var INLINE_MAX_NODES = 1e3;
9848
+ function resolveInlineMax(explicit) {
9849
+ if (explicit !== void 0) return explicit;
9850
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
9851
+ if (env !== void 0 && env !== "") {
9852
+ if (env === "off" || env === "infinity") return Infinity;
9853
+ const n = Number(env);
9854
+ if (Number.isFinite(n) && n >= 0) return n;
9855
+ }
9856
+ return INLINE_MAX_NODES;
9857
+ }
9809
9858
  function analyzeLazyUsage(root) {
9810
9859
  return analyzeLazyUsageMulti([root]);
9811
9860
  }
@@ -9911,102 +9960,6 @@ function ruleDependencies(ruleMap) {
9911
9960
  }
9912
9961
  return deps;
9913
9962
  }
9914
- function resolveGatingLevel(opt) {
9915
- const explicit = typeof opt === "string" ? opt : opt?.level;
9916
- if (explicit !== void 0) return explicit;
9917
- const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
9918
- if (env === "off" || env === "warn" || env === "error") return env;
9919
- return "warn";
9920
- }
9921
- function runGatingDiagnostic(combinator, opt) {
9922
- return reportGating(opt, (o) => analyzeGating(combinator, o));
9923
- }
9924
- function runGatingDiagnosticRules(ruleMap, opt) {
9925
- return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
9926
- }
9927
- function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
9928
- if (resolveGatingLevel(opt) === "off") return void 0;
9929
- const ruleMaps = getRuleMaps();
9930
- const opaque = getOpaque?.() ?? [];
9931
- const opaqueFindings = opaque.map((o) => ({
9932
- rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
9933
- kind: "opaque-artifact",
9934
- reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
9935
- }));
9936
- if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
9937
- if (opaqueFindings.length === 0) return void 0;
9938
- return reportGating(opt, () => ({
9939
- totalChoices: 0,
9940
- gated: 0,
9941
- recoverable: 0,
9942
- unanalysable: opaqueFindings,
9943
- ungated: [],
9944
- accepted: [],
9945
- deferred: [],
9946
- acceptedUnused: [],
9947
- choices: [],
9948
- antiPatterns: []
9949
- }));
9950
- }
9951
- const winners = /* @__PURE__ */ new Map();
9952
- for (const map of ruleMaps) for (const [name, rule] of map) {
9953
- if (rule._def.tag === "lazy") {
9954
- try {
9955
- rule._def.thunk();
9956
- } catch {
9957
- continue;
9958
- }
9959
- }
9960
- winners.set(name, rule);
9961
- }
9962
- const entries = [...winners];
9963
- return reportGating(opt, (o) => {
9964
- const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
9965
- const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
9966
- return {
9967
- ...fused,
9968
- ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
9969
- antiPatterns: [],
9970
- unanalysable: [...fused.unanalysable, ...opaqueFindings]
9971
- };
9972
- });
9973
- }
9974
- function reportGating(opt, analyze) {
9975
- const level = resolveGatingLevel(opt);
9976
- if (level === "off") return void 0;
9977
- const obj = opt !== null && typeof opt === "object" ? opt : void 0;
9978
- const analyzeOpts = obj?.accept !== void 0 || obj?.entryName !== void 0 ? { ...obj.accept !== void 0 ? { accept: obj.accept } : {}, ...obj.entryName !== void 0 ? { entryName: obj.entryName } : {} } : void 0;
9979
- let report;
9980
- try {
9981
- report = analyze(analyzeOpts);
9982
- } catch (e) {
9983
- const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
9984
- const failed = {
9985
- totalChoices: 0,
9986
- gated: 0,
9987
- recoverable: 0,
9988
- unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
9989
- ungated: [],
9990
- accepted: [],
9991
- deferred: [],
9992
- acceptedUnused: [],
9993
- choices: [],
9994
- antiPatterns: []
9995
- };
9996
- const failLines = formatGatingWarnings(failed);
9997
- if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
9998
- ${failLines.join("\n")}`);
9999
- for (const l of failLines) console.warn(l);
10000
- return failed;
10001
- }
10002
- const lines = formatGatingWarnings(report);
10003
- if (lines.length > 0) {
10004
- if (level === "error") throw new Error(`parseman: ${report.ungated.length} ungated hot choice(s) / ${report.antiPatterns.length} anti-pattern(s) / ${report.unanalysable.length} unanalysable rule(s)
10005
- ${lines.join("\n")}`);
10006
- for (const l of lines) console.warn(l);
10007
- }
10008
- return report;
10009
- }
10010
9963
  function resolveDuplicationLevel(opt) {
10011
9964
  const explicit = typeof opt === "string" ? opt : opt?.level;
10012
9965
  if (explicit !== void 0) return explicit;
@@ -10049,7 +10002,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
10049
10002
  return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
10050
10003
  }
10051
10004
  function compile(combinator, mapFnSources, opts) {
10052
- const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
10005
+ const drain = beginCompileDegradationDrain();
10006
+ let completed = false;
10007
+ try {
10008
+ const compiled = compileImpl(combinator, mapFnSources, opts);
10009
+ completed = true;
10010
+ return compiled;
10011
+ } finally {
10012
+ drain(completed);
10013
+ }
10014
+ }
10015
+ function compileImpl(combinator, mapFnSources, opts) {
10053
10016
  runDuplicationDiagnostic(combinator, opts?.duplication);
10054
10017
  markUnusedValues(combinator);
10055
10018
  const grammarTrivia = combinator._meta.grammarTrivia;
@@ -10057,9 +10020,12 @@ function compile(combinator, mapFnSources, opts) {
10057
10020
  const grammarScanSkip = combinator._meta.grammarScanSkip;
10058
10021
  const grammarHostMode = combinator._meta.grammarHostMode;
10059
10022
  const grammarTrackLines = combinator._meta.grammarTrackLines;
10023
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10060
10024
  const ctx = {
10061
10025
  vars: 0,
10062
10026
  indent: 1,
10027
+ inlineMax: _inlineMax,
10028
+ inlineLeft: _inlineMax,
10063
10029
  regexDecls: [],
10064
10030
  regexMap: /* @__PURE__ */ new Map(),
10065
10031
  expectedDecls: [],
@@ -10105,11 +10071,13 @@ function compile(combinator, mapFnSources, opts) {
10105
10071
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
10106
10072
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
10107
10073
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
10074
+ const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
10108
10075
  const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
10109
10076
  const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
10110
10077
  const source = [
10111
10078
  ...emptyTlDecls,
10112
10079
  ...hostReadsDecls,
10080
+ ...rawEntryDecls,
10113
10081
  ...lineTrackDecls,
10114
10082
  ...lineSpanDecls,
10115
10083
  ...ctx.regexDecls,
@@ -10124,6 +10092,7 @@ function compile(combinator, mapFnSources, opts) {
10124
10092
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
10125
10093
  ...emptyTlDecls,
10126
10094
  ...hostReadsDecls,
10095
+ ...rawEntryDecls,
10127
10096
  ...lineTrackDecls,
10128
10097
  ...lineSpanDecls,
10129
10098
  ...ctx.regexDecls,
@@ -10173,8 +10142,7 @@ function compile(combinator, mapFnSources, opts) {
10173
10142
  const compiledMeta = {
10174
10143
  source,
10175
10144
  inlineExpression,
10176
- ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
10177
- ...gatingReport === void 0 ? {} : { gating: gatingReport }
10145
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
10178
10146
  };
10179
10147
  if (!ctx.lineTracking) {
10180
10148
  return {
@@ -10239,7 +10207,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
10239
10207
  return `Object.assign(${fnSource}, { _meta: {${labels === void 0 ? "" : ` triviaKindLabels: ${JSON.stringify(labels)},`}${classified === void 0 ? "" : " rootTriviaClassified: true,"} } })`;
10240
10208
  }
10241
10209
  function compileRuleMap(ruleMap, opts) {
10242
- runGatingDiagnosticRules(ruleMap, opts?.gating);
10243
10210
  runDuplicationDiagnosticRules(ruleMap, opts?.duplication);
10244
10211
  for (const [, rule] of ruleMap) markUnusedValues(rule);
10245
10212
  const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
@@ -10248,9 +10215,12 @@ function compileRuleMap(ruleMap, opts) {
10248
10215
  const grammarScanSkip = opts?.scanSkip ?? ruleMap.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
10249
10216
  const grammarHostMode = opts?.hostMode ?? ruleMap.map(([, r]) => r._meta.grammarHostMode).find(Boolean);
10250
10217
  const grammarTrackLines = opts?.trackLines === true || ruleMap.some(([, r]) => r._meta.grammarTrackLines === true);
10218
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10251
10219
  const ctx = {
10252
10220
  vars: 0,
10253
10221
  indent: 1,
10222
+ inlineMax: _inlineMax,
10223
+ inlineLeft: _inlineMax,
10254
10224
  regexDecls: [],
10255
10225
  regexMap: /* @__PURE__ */ new Map(),
10256
10226
  expectedDecls: [],
@@ -10316,6 +10286,7 @@ function compileRuleMap(ruleMap, opts) {
10316
10286
  const hoistedDecls = [
10317
10287
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
10318
10288
  ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
10289
+ ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "",
10319
10290
  ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "",
10320
10291
  ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "",
10321
10292
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10388,7 +10359,6 @@ function compileRuleMap(ruleMap, opts) {
10388
10359
  }
10389
10360
  function compileLinkable(ruleMapArg, ns, opts) {
10390
10361
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
10391
- if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
10392
10362
  if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
10393
10363
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
10394
10364
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
@@ -10407,9 +10377,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
10407
10377
  }
10408
10378
  });
10409
10379
  const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
10380
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10410
10381
  const ctx = {
10411
10382
  vars: 0,
10412
10383
  indent: 1,
10384
+ inlineMax: _inlineMax,
10385
+ inlineLeft: _inlineMax,
10413
10386
  regexDecls: [],
10414
10387
  regexMap: /* @__PURE__ */ new Map(),
10415
10388
  expectedDecls: [],
@@ -10505,6 +10478,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
10505
10478
  }
10506
10479
  }
10507
10480
  const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
10481
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10482
+ ctx.inlineLeft = ctx.inlineMax;
10483
+ ctx.currentFnName = fn;
10508
10484
  ctx.indent = 1;
10509
10485
  ctx.failLabel = "_pfail";
10510
10486
  ctx.recordFail = true;
@@ -10520,6 +10496,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
10520
10496
  ctx.indent = savedIndent;
10521
10497
  ctx.failLabel = savedFail;
10522
10498
  ctx.recordFail = savedRec;
10499
+ ctx.inlineLeft = savedInlineLeft;
10500
+ ctx.currentFnName = savedFnName;
10523
10501
  const ruleId = ctx.coverage?.plan.rules.get(rule);
10524
10502
  const stmts = ruleId === void 0 ? body.stmts : [
10525
10503
  `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
@@ -10604,6 +10582,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10604
10582
  deps: ruleDependencies(ruleMap),
10605
10583
  needsEmptyTl: !!ctx.needsEmptyTl,
10606
10584
  needsHostReads: !!ctx.needsHostReads,
10585
+ needsRawEntry: !!ctx.needsRawEntry,
10607
10586
  hostMode: ctx.hostMode ?? "ast",
10608
10587
  hostBranchElided: !!ctx.hostBranchElided,
10609
10588
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
@@ -10678,14 +10657,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
10678
10657
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
10679
10658
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
10680
10659
  const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
10660
+ const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
10681
10661
  const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
10682
10662
  const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
10683
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
10663
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
10684
10664
  if (!needsWrapper) return innerFn;
10685
10665
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
10686
10666
  const hoistedDecls = [
10687
10667
  emptyTlDecl,
10688
10668
  hostReadsDecl,
10669
+ rawEntryDecl,
10689
10670
  lineTrackDecl,
10690
10671
  lineSpanDecl,
10691
10672
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10755,18 +10736,19 @@ function childrenOf4(def) {
10755
10736
  return [def.main, def.skipped];
10756
10737
  case "scanTo":
10757
10738
  return [def.sentinel, ...def.skip];
10739
+ case "routed":
10740
+ return def.fallback ? [def.fallback] : [];
10758
10741
  case "lazy":
10759
10742
  case "literal":
10760
10743
  case "regex":
10761
10744
  case "keywords":
10762
- case "routed":
10763
10745
  case "guard":
10764
10746
  case "recover":
10765
10747
  case "unknown":
10766
10748
  return [];
10767
10749
  }
10768
10750
  }
10769
- var ruleNameOf4 = (c) => c._ruleName;
10751
+ var ruleNameOf3 = (c) => c._ruleName;
10770
10752
  function lazyTarget(c) {
10771
10753
  if (c._def.tag !== "lazy") return null;
10772
10754
  try {
@@ -10841,7 +10823,7 @@ function evalRuleMapIR(ir) {
10841
10823
  l._def.fnSrc = src;
10842
10824
  return l;
10843
10825
  };
10844
- const _nd = (type, child, src, opts, staticError) => {
10826
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
10845
10827
  if (staticError !== void 0 && staticError.length > 0) {
10846
10828
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
10847
10829
  }
@@ -10850,6 +10832,7 @@ function evalRuleMapIR(ir) {
10850
10832
  }), opts);
10851
10833
  n._def.buildSrc = src;
10852
10834
  if (staticError !== void 0) n._def.buildStaticError = staticError;
10835
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
10853
10836
  return n;
10854
10837
  };
10855
10838
  const _gch = (items) => {
@@ -11002,7 +10985,7 @@ var Serializer = class {
11002
10985
  if (seen.has(c)) return;
11003
10986
  seen.add(c);
11004
10987
  if (c._def.tag === "lazy") {
11005
- if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
10988
+ if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
11006
10989
  const target = lazyTarget(c);
11007
10990
  if (target) this.assertNoRuleRef(target, seen);
11008
10991
  return;
@@ -11013,7 +10996,7 @@ var Serializer = class {
11013
10996
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
11014
10997
  * `g[name]` ref — only the top-level entry is unwrapped. */
11015
10998
  body(c) {
11016
- return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
10999
+ return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
11017
11000
  }
11018
11001
  run() {
11019
11002
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -11055,7 +11038,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11055
11038
  analyze(c, active) {
11056
11039
  const def = c._def;
11057
11040
  if (def.tag === "lazy") {
11058
- if (ruleNameOf4(c) !== void 0) return;
11041
+ if (ruleNameOf3(c) !== void 0) return;
11059
11042
  const target = lazyTarget(c);
11060
11043
  if (!target) throw new Unserializable("unresolved unnamed ref");
11061
11044
  if (active.has(target)) {
@@ -11107,7 +11090,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11107
11090
  const kid = (x) => this.ref(x, selfOf);
11108
11091
  switch (def.tag) {
11109
11092
  case "lazy": {
11110
- const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
11093
+ const name = this.localRuleNames.get(c) ?? ruleNameOf3(c);
11111
11094
  if (name !== void 0) return `g[${JSON.stringify(name)}]`;
11112
11095
  const target = lazyTarget(c);
11113
11096
  if (selfOf && target === selfOf.comb) return selfOf.var;
@@ -11162,7 +11145,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11162
11145
  case "peek":
11163
11146
  return `peek(${kid(def.parser)})`;
11164
11147
  case "routed":
11165
- return "routed()";
11148
+ return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
11166
11149
  case "trivia":
11167
11150
  return `trivia(${kid(def.parser)})`;
11168
11151
  case "token":
@@ -11195,6 +11178,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11195
11178
  ...projectEntry === void 0 ? [] : [projectEntry],
11196
11179
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
11197
11180
  ...def.trailingTrivia ? ["trailingTrivia: true"] : [],
11181
+ // Declared or resolved arity. Carried so a re-lowered composed artifact does
11182
+ // not silently re-acquire the fail-open capture cost this module resolved away.
11183
+ ...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
11198
11184
  ...tagEntry === void 0 ? [] : [tagEntry]
11199
11185
  ];
11200
11186
  const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
@@ -11203,8 +11189,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11203
11189
  return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
11204
11190
  }
11205
11191
  if (def.buildSrc !== void 0) {
11206
- const staticError = def.buildStaticError === void 0 ? "" : `${opts ? "" : ", undefined"}, ${JSON.stringify(def.buildStaticError)}`;
11207
- return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${staticError})`;
11192
+ const tail = [];
11193
+ if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
11194
+ if (!opts) tail.push("undefined");
11195
+ tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
11196
+ }
11197
+ if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
11198
+ const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
11199
+ return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
11208
11200
  }
11209
11201
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
11210
11202
  }
@@ -11268,7 +11260,7 @@ function pickPieces(pieces, names) {
11268
11260
  const filt = (m, pc) => new Map([...m].filter(([k]) => keep.has(k) && winner.get(k) === pc));
11269
11261
  return pieces.map((pc) => ({ ...pc, keys: pc.keys.filter((k) => keep.has(k) && winner.get(k) === pc), ruleFns: filt(pc.ruleFns, pc), wrappers: filt(pc.wrappers, pc), deps: filt(pc.deps, pc), nodeMeta: filt(pc.nodeMeta, pc) })).filter((pc) => pc.keys.length > 0);
11270
11262
  }
11271
- function fusedBody(pieces) {
11263
+ function fusedBody(pieces, hoist) {
11272
11264
  for (const p of pieces) {
11273
11265
  if (p.v === void 0) {
11274
11266
  throw new Error(
@@ -11292,12 +11284,14 @@ function fusedBody(pieces) {
11292
11284
  const contributingPieces = [...contributing];
11293
11285
  const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
11294
11286
  const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
11287
+ const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
11295
11288
  const lines = [
11296
11289
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
11297
11290
  "const _pfFail = {}",
11298
11291
  "let _pfEnd",
11299
11292
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
11300
11293
  ...needsHostReads ? [HOST_READS_DECL] : [],
11294
+ ...needsRawEntry ? [RAW_ENTRY_DECL] : [],
11301
11295
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
11302
11296
  ...new Set(contributingPieces.flatMap((p) => p.prelude)),
11303
11297
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -11306,8 +11300,7 @@ function fusedBody(pieces) {
11306
11300
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
11307
11301
  const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
11308
11302
  const { mode, elided } = hostModeOfPieces(pieces);
11309
- const rawBody = [
11310
- ...lines,
11303
+ const tailSrc = [
11311
11304
  "const _map = {",
11312
11305
  wrapperEntries.join(",\n"),
11313
11306
  "}",
@@ -11323,6 +11316,7 @@ function fusedBody(pieces) {
11323
11316
  "}",
11324
11317
  "return _map"
11325
11318
  ].join("\n");
11319
+ const rawBody = [...lines, tailSrc].join("\n");
11326
11320
  const ANY2 = { kind: "any" };
11327
11321
  const EMPTY3 = { kind: "empty" };
11328
11322
  const recipes = /* @__PURE__ */ new Map();
@@ -11331,8 +11325,8 @@ function fusedBody(pieces) {
11331
11325
  for (const [k, p] of winner) {
11332
11326
  const r = p.firstSetRecipes?.get(k);
11333
11327
  if (r) recipes.set(k, r);
11334
- const fs2 = p.firstSets?.get(k);
11335
- if (fs2) shallow.set(k, fs2);
11328
+ const fs3 = p.firstSets?.get(k);
11329
+ if (fs3) shallow.set(k, fs3);
11336
11330
  const nu = p.nullable?.get(k);
11337
11331
  if (nu !== void 0) nullable2.set(k, nu);
11338
11332
  }
@@ -11355,25 +11349,26 @@ function fusedBody(pieces) {
11355
11349
  for (let iter = 0; iter <= recipes.size; iter++) {
11356
11350
  let changed = false;
11357
11351
  for (const [k, r] of recipes) {
11358
- const fs2 = resolveRecipe(r);
11359
- if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
11360
- finalFS.set(k, fs2);
11352
+ const fs3 = resolveRecipe(r);
11353
+ if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs3)) {
11354
+ finalFS.set(k, fs3);
11361
11355
  changed = true;
11362
11356
  }
11363
11357
  }
11364
11358
  if (!changed) break;
11365
11359
  }
11366
- const body = rawBody.replace(
11360
+ const resolveFS = (src) => src.replace(
11367
11361
  // Rule name + code-point var are both JS identifiers (rule names are validated
11368
11362
  // at compile time — see assertRuleName in codegen), so an identifier class
11369
11363
  // matches every well-formed placeholder.
11370
11364
  /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
11371
11365
  (_m, name, codevar) => {
11372
- const fs2 = finalFS.get(name);
11373
- if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
11374
- return `(${firstSetCond(codevar, fs2)})`;
11366
+ const fs3 = finalFS.get(name);
11367
+ if (!fs3 || fs3.kind === "any" || fs3.kind === "empty") return "true";
11368
+ return `(${firstSetCond(codevar, fs3)})`;
11375
11369
  }
11376
11370
  );
11371
+ const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
11377
11372
  const env = {};
11378
11373
  for (const p of contributingPieces) {
11379
11374
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -11391,8 +11386,8 @@ function hostModeOfPieces(pieces) {
11391
11386
  }
11392
11387
  return { mode, elided: elided.length > 0 };
11393
11388
  }
11394
- function emitFusedSource(pieces) {
11395
- const { body, env } = fusedBody(pieces);
11389
+ function emitFusedSource(pieces, hoist) {
11390
+ const { body, env } = fusedBody(pieces, hoist);
11396
11391
  if (Object.keys(env).length > 0) {
11397
11392
  throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
11398
11393
  }
@@ -11403,17 +11398,6 @@ ${body}
11403
11398
  function isIRPiece(p) {
11404
11399
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
11405
11400
  }
11406
- function once(fn) {
11407
- let done = false;
11408
- let value;
11409
- return () => {
11410
- if (!done) {
11411
- value = fn();
11412
- done = true;
11413
- }
11414
- return value;
11415
- };
11416
- }
11417
11401
  function materializePiece(p, trivia2, captureTerminals = false, hostMode) {
11418
11402
  if (!isIRPiece(p)) return p;
11419
11403
  const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, {
@@ -11426,6 +11410,34 @@ function materializePiece(p, trivia2, captureTerminals = false, hostMode) {
11426
11410
  return pieces;
11427
11411
  }
11428
11412
 
11413
+ // src/functional/run.ts
11414
+ var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
11415
+ var REMOVED_RUN_RESULT_FIELDS = [
11416
+ [
11417
+ "triviaMap",
11418
+ `It was a dense root-trivia index built on every parse; root trivia is now an OPT-IN sparse capture, because most grammars paid for an index they never read. Migration: name the trivia labels your grammar defines \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia\`, whose \`.index\` carries the labels and gap lookups \`triviaMap\` exposed.`
11419
+ ],
11420
+ [
11421
+ "triviaLog",
11422
+ `It was the flat \`start, end[, kindIndex]\` log every other trivia view was derived from, and it is no longer allocated at all \u2014 not empty, absent. Migration: request the labels you need \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia.rows\`. Do NOT reuse a stride-2/3 reader: rows are a different width and a different unit (one row per SELECTED marker, not one per trivia chunk), so an old loop reads confidently wrong offsets rather than failing.`
11423
+ ],
11424
+ [
11425
+ "triviaKindLabels",
11426
+ `It was already optional, so nothing ever flagged its disappearance \u2014 and \`triviaKindMask(undefined, \u2026)\` means "capture everything", so reading it now silently WIDENS capture instead of erroring. Migration: request labels \u2014 ${SELECT_EXAMPLE} \u2014 then the set you asked for is \`RunResult.rootTrivia.select\` and the resolved table is \`RunResult.rootTrivia.index.labels\`.`
11427
+ ]
11428
+ ];
11429
+
11430
+ // src/analysis/choice-cost-render.ts
11431
+ var ESC = "\x1B";
11432
+ var ANSI = {
11433
+ dim: `${ESC}[2m`,
11434
+ bold: `${ESC}[1m`,
11435
+ red: `${ESC}[31m`,
11436
+ yellow: `${ESC}[33m`,
11437
+ cyan: `${ESC}[36m`,
11438
+ reset: `${ESC}[0m`
11439
+ };
11440
+
11429
11441
  // src/combinators/gate.ts
11430
11442
  function gate(predicate) {
11431
11443
  const meta = {
@@ -11445,9 +11457,6 @@ function gate(predicate) {
11445
11457
  };
11446
11458
  }
11447
11459
 
11448
- // src/cst/offset-model.ts
11449
- var { isFinite } = Number;
11450
-
11451
11460
  // src/index.ts
11452
11461
  registerRegexAnalyzer(firstSetFromRegex);
11453
11462
 
@@ -11640,6 +11649,12 @@ function stripTsFromSource(node2, code) {
11640
11649
  }
11641
11650
  return out + code.slice(cur, end);
11642
11651
  }
11652
+ var _reducers = null;
11653
+ var _entrySource = null;
11654
+ function setReducerResolver(r, entrySource = null) {
11655
+ _reducers = r;
11656
+ _entrySource = entrySource;
11657
+ }
11643
11658
  function isWordFactory(v2) {
11644
11659
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
11645
11660
  }
@@ -11871,9 +11886,12 @@ function staticNodeOptionsFromValue(value) {
11871
11886
  } else if (name === "tags") {
11872
11887
  if (!Array.isArray(v2) || !v2.every((item) => typeof item === "string")) return STATIC_NODE_OPTIONS_FAILED;
11873
11888
  opts.tags = v2;
11889
+ } else if (name === "buildArity") {
11890
+ if (typeof v2 !== "number" || !Number.isInteger(v2) || v2 < 0 || v2 > 6) return STATIC_NODE_OPTIONS_FAILED;
11891
+ opts.buildArity = v2;
11874
11892
  }
11875
11893
  }
11876
- return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11894
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 || opts.buildArity !== void 0 ? opts : void 0;
11877
11895
  }
11878
11896
  function staticNodeOptions(expr, scope) {
11879
11897
  const unwrapped = unwrapStaticExpr(expr);
@@ -11899,9 +11917,13 @@ function staticNodeOptions(expr, scope) {
11899
11917
  const tags = staticStringArray(p.value, scope);
11900
11918
  if (tags === void 0) return STATIC_NODE_OPTIONS_FAILED;
11901
11919
  opts.tags = tags;
11920
+ } else if (name === "buildArity") {
11921
+ const arity = staticLiteralValue(p.value);
11922
+ if (typeof arity !== "number" || !Number.isInteger(arity) || arity < 0 || arity > 6) return STATIC_NODE_OPTIONS_FAILED;
11923
+ opts.buildArity = arity;
11902
11924
  }
11903
11925
  }
11904
- return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11926
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 || opts.buildArity !== void 0 ? opts : void 0;
11905
11927
  }
11906
11928
  function exprToCombi(node2, scope, code, mfs) {
11907
11929
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -11997,6 +12019,18 @@ function exprToCombi(node2, scope, code, mfs) {
11997
12019
  const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
11998
12020
  if (combi._def.tag === "node" && buildSrc !== void 0) {
11999
12021
  combi._def.buildSrc = buildSrc;
12022
+ if (explicitType !== void 0 && firstArg.type === "Identifier") {
12023
+ combi._def.typeSrc = firstArg.name;
12024
+ }
12025
+ const resolved = _reducers?.resolve(buildSrc, be.start, code);
12026
+ if (resolved) {
12027
+ if (resolved.src !== null) combi._def.buildSigSrc = resolved.src;
12028
+ if (_entrySource !== null && code !== _entrySource && resolved.src !== null && be.type === "Identifier") {
12029
+ combi._def.buildSrc = resolved.src;
12030
+ }
12031
+ if (combi._def.buildArity === void 0 && resolved.arity !== null) combi._def.buildArity = resolved.arity;
12032
+ if (resolved.reason !== void 0) combi._def.buildArityUnresolved = resolved.reason;
12033
+ }
12000
12034
  const staticError = directBuilderUnsupportedBindings(buildSrc);
12001
12035
  if (staticError.length > 0) combi._def.buildStaticError = staticError;
12002
12036
  }
@@ -12414,9 +12448,9 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
12414
12448
  r._ruleName = name;
12415
12449
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
12416
12450
  };
12417
- const ruleNameOf5 = (r) => r._ruleName;
12451
+ const ruleNameOf4 = (r) => r._ruleName;
12418
12452
  const isNamedRuleRefForAnotherRule2 = (r, key) => {
12419
- const name = r._def.tag === "lazy" ? ruleNameOf5(r) : void 0;
12453
+ const name = r._def.tag === "lazy" ? ruleNameOf4(r) : void 0;
12420
12454
  return name !== void 0 && name !== key;
12421
12455
  };
12422
12456
  for (const key of keys) {
@@ -12507,6 +12541,692 @@ function referencesAny(node2, names, scope) {
12507
12541
  return false;
12508
12542
  }
12509
12543
 
12544
+ // src/plugin/reducer-resolver.ts
12545
+ var fs = __toESM(require("node:fs"), 1);
12546
+ var path = __toESM(require("node:path"), 1);
12547
+ var import_node_module = require("node:module");
12548
+ var import_oxc_parser2 = require("oxc-parser");
12549
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
12550
+ var SCOPE_TYPES = /* @__PURE__ */ new Set([
12551
+ ...FUNCTION_TYPES,
12552
+ "BlockStatement",
12553
+ "ForStatement",
12554
+ "ForInStatement",
12555
+ "ForOfStatement",
12556
+ "CatchClause",
12557
+ "ClassDeclaration",
12558
+ "ClassExpression",
12559
+ "StaticBlock"
12560
+ ]);
12561
+ function newScope(start, end, parent, isFunction) {
12562
+ const s = { start, end, bindings: /* @__PURE__ */ new Map(), children: [], parent, isFunction };
12563
+ parent?.children.push(s);
12564
+ return s;
12565
+ }
12566
+ function patternNames(pat, out) {
12567
+ if (!pat || typeof pat !== "object") return;
12568
+ const n = pat;
12569
+ switch (n.type) {
12570
+ case "Identifier":
12571
+ out.push(n.name);
12572
+ return;
12573
+ case "AssignmentPattern":
12574
+ return patternNames(n.left, out);
12575
+ case "RestElement":
12576
+ return patternNames(n.argument, out);
12577
+ case "ArrayPattern":
12578
+ for (const el of n.elements ?? []) patternNames(el, out);
12579
+ return;
12580
+ case "ObjectPattern":
12581
+ for (const p of n.properties ?? []) patternNames(p.type === "RestElement" ? p.argument : p.value, out);
12582
+ return;
12583
+ // A TS parameter property / assignment target wrapper.
12584
+ case "TSParameterProperty":
12585
+ return patternNames(n.parameter, out);
12586
+ default:
12587
+ return;
12588
+ }
12589
+ }
12590
+ function declare(scope, name, b) {
12591
+ if (!scope.bindings.has(name)) scope.bindings.set(name, b);
12592
+ }
12593
+ function functionScope(scope) {
12594
+ let s = scope;
12595
+ while (!s.isFunction && s.parent) s = s.parent;
12596
+ return s;
12597
+ }
12598
+ function buildScopeTree(body, moduleEnd) {
12599
+ const root = newScope(0, moduleEnd, null, true);
12600
+ const declarePattern = (scope, pat, kind, init) => {
12601
+ const names = [];
12602
+ patternNames(pat, names);
12603
+ const target = kind === "var" ? functionScope(scope) : scope;
12604
+ for (const name of names) {
12605
+ declare(target, name, init !== void 0 && names.length === 1 ? { kind, init } : { kind });
12606
+ }
12607
+ };
12608
+ const walk = (n, scope) => {
12609
+ if (!n || typeof n !== "object") return;
12610
+ if (Array.isArray(n)) {
12611
+ for (const item of n) walk(item, scope);
12612
+ return;
12613
+ }
12614
+ const node2 = n;
12615
+ if (typeof node2.type !== "string") return;
12616
+ switch (node2.type) {
12617
+ case "VariableDeclaration": {
12618
+ const kind = node2.kind ?? "var";
12619
+ for (const d of node2.declarations ?? []) {
12620
+ declarePattern(scope, d.id, kind, d.init ?? void 0);
12621
+ }
12622
+ break;
12623
+ }
12624
+ case "FunctionDeclaration":
12625
+ if (node2.id) declare(functionScope(scope), node2.id.name, { kind: "function", init: node2 });
12626
+ break;
12627
+ case "ClassDeclaration":
12628
+ if (node2.id) declare(scope, node2.id.name, { kind: "class" });
12629
+ break;
12630
+ case "ImportDeclaration": {
12631
+ const specifier = node2.source?.value ?? "";
12632
+ for (const sp of node2.specifiers ?? []) {
12633
+ const local = sp.local?.name;
12634
+ if (!local) continue;
12635
+ const imported = sp.type === "ImportDefaultSpecifier" ? "default" : sp.type === "ImportNamespaceSpecifier" ? "*" : sp.imported?.name ?? local;
12636
+ declare(scope, local, { kind: "import", importRef: { specifier, imported } });
12637
+ }
12638
+ break;
12639
+ }
12640
+ default:
12641
+ break;
12642
+ }
12643
+ if (SCOPE_TYPES.has(node2.type)) {
12644
+ const isFn = FUNCTION_TYPES.has(node2.type);
12645
+ const inner = newScope(node2.start, node2.end, scope, isFn);
12646
+ if (isFn) {
12647
+ for (const p of node2.params ?? []) declarePattern(inner, p, "param");
12648
+ if (node2.type === "FunctionExpression" && node2.id) {
12649
+ declare(inner, node2.id.name, { kind: "function", init: node2 });
12650
+ }
12651
+ }
12652
+ if (node2.type === "CatchClause" && node2.param) declarePattern(inner, node2.param, "catch");
12653
+ for (const key of Object.keys(node2)) {
12654
+ if (key === "type" || key === "start" || key === "end") continue;
12655
+ walk(node2[key], inner);
12656
+ }
12657
+ return;
12658
+ }
12659
+ for (const key of Object.keys(node2)) {
12660
+ if (key === "type" || key === "start" || key === "end") continue;
12661
+ walk(node2[key], scope);
12662
+ }
12663
+ };
12664
+ for (const stmt of body) walk(stmt, root);
12665
+ return root;
12666
+ }
12667
+ function scopeAt(root, offset) {
12668
+ let cur = root;
12669
+ for (; ; ) {
12670
+ const next = cur.children.find((c) => offset >= c.start && offset <= c.end);
12671
+ if (!next) return cur;
12672
+ cur = next;
12673
+ }
12674
+ }
12675
+ function lookup(scope, name) {
12676
+ for (let s = scope; s; s = s.parent) {
12677
+ const b = s.bindings.get(name);
12678
+ if (b) return { binding: b, scope: s };
12679
+ }
12680
+ return null;
12681
+ }
12682
+ function markReassignments(body, root) {
12683
+ const walk = (n) => {
12684
+ if (!n || typeof n !== "object") return;
12685
+ if (Array.isArray(n)) {
12686
+ for (const item of n) walk(item);
12687
+ return;
12688
+ }
12689
+ const node2 = n;
12690
+ if (typeof node2.type !== "string") return;
12691
+ if (node2.type === "AssignmentExpression" || node2.type === "UpdateExpression") {
12692
+ const target = node2.type === "UpdateExpression" ? node2.argument : node2.left;
12693
+ const names = [];
12694
+ if (target) patternNames(target, names);
12695
+ for (const name of names) {
12696
+ const hit = lookup(scopeAt(root, node2.start), name);
12697
+ if (hit) hit.binding.reassigned = true;
12698
+ }
12699
+ }
12700
+ for (const key of Object.keys(node2)) {
12701
+ if (key === "type" || key === "start" || key === "end") continue;
12702
+ walk(node2[key]);
12703
+ }
12704
+ };
12705
+ for (const stmt of body) walk(stmt);
12706
+ }
12707
+ function functionArity(fn) {
12708
+ const params = fn.params ?? [];
12709
+ let count = 0;
12710
+ for (const p of params) {
12711
+ if (p.type === "RestElement") return { arity: null, reason: "rest-parameter" };
12712
+ if (p.type === "Identifier" && p.name === "this") continue;
12713
+ count++;
12714
+ }
12715
+ if (fn.type !== "ArrowFunctionExpression" && referencesArguments(fn.body)) {
12716
+ return { arity: null, reason: "arguments" };
12717
+ }
12718
+ return { arity: count };
12719
+ }
12720
+ function referencesArguments(n) {
12721
+ if (!n || typeof n !== "object") return false;
12722
+ if (Array.isArray(n)) return n.some(referencesArguments);
12723
+ const node2 = n;
12724
+ if (typeof node2.type !== "string") return false;
12725
+ if (node2.type === "Identifier" && node2.name === "arguments") return true;
12726
+ if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression") return false;
12727
+ for (const key of Object.keys(node2)) {
12728
+ if (key === "type" || key === "start" || key === "end") continue;
12729
+ if (referencesArguments(node2[key])) return true;
12730
+ }
12731
+ return false;
12732
+ }
12733
+ var _moduleCache = /* @__PURE__ */ new Map();
12734
+ function analyseModuleFile(file) {
12735
+ let mtimeMs;
12736
+ try {
12737
+ mtimeMs = fs.statSync(file).mtimeMs;
12738
+ } catch {
12739
+ return null;
12740
+ }
12741
+ const hit = _moduleCache.get(file);
12742
+ if (hit && hit.mtimeMs === mtimeMs) return hit.mod;
12743
+ let mod = null;
12744
+ try {
12745
+ const src = fs.readFileSync(file, "utf8");
12746
+ const r = (0, import_oxc_parser2.parseSync)(file, src);
12747
+ if (r.errors.length === 0) {
12748
+ const body = r.program.body;
12749
+ const scopes = buildScopeTree(body, src.length);
12750
+ markReassignments(body, scopes);
12751
+ mod = { file, src, body, scopes };
12752
+ }
12753
+ } catch {
12754
+ }
12755
+ _moduleCache.set(file, { mtimeMs, mod });
12756
+ return mod;
12757
+ }
12758
+ var SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".jsx"];
12759
+ function resolveImport(fromFile, specifier) {
12760
+ if (specifier.startsWith(".") || path.isAbsolute(specifier)) {
12761
+ const base = path.resolve(path.dirname(fromFile), specifier);
12762
+ const ext = path.extname(base);
12763
+ const stem = ext ? base.slice(0, -ext.length) : base;
12764
+ const candidates = [
12765
+ ...ext ? [base] : [],
12766
+ // `./x.js` in TS source usually means `./x.ts`.
12767
+ ...SOURCE_EXTS.map((e) => `${stem}${e}`),
12768
+ ...SOURCE_EXTS.map((e) => path.join(base, `index${e}`))
12769
+ ];
12770
+ for (const c of candidates) {
12771
+ try {
12772
+ if (fs.statSync(c).isFile()) return fs.realpathSync(c);
12773
+ } catch {
12774
+ }
12775
+ }
12776
+ return null;
12777
+ }
12778
+ try {
12779
+ return fs.realpathSync((0, import_node_module.createRequire)(fromFile).resolve(specifier));
12780
+ } catch {
12781
+ return null;
12782
+ }
12783
+ }
12784
+ function declarationBinds(decl, name) {
12785
+ if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") {
12786
+ return decl.id?.name === name;
12787
+ }
12788
+ if (decl.type === "VariableDeclaration") {
12789
+ const names = [];
12790
+ for (const d of decl.declarations ?? []) patternNames(d.id, names);
12791
+ return names.includes(name);
12792
+ }
12793
+ return false;
12794
+ }
12795
+ function exportedBinding(mod, name, seen) {
12796
+ for (const stmt of mod.body) {
12797
+ if (stmt.type === "ExportDefaultDeclaration" && name === "default") {
12798
+ const d = stmt.declaration;
12799
+ if (FUNCTION_TYPES.has(d.type)) return { mod, binding: { kind: "const", init: d } };
12800
+ if (d.type === "Identifier") {
12801
+ const hit2 = lookup(mod.scopes, d.name);
12802
+ return hit2 ? { mod, binding: hit2.binding } : null;
12803
+ }
12804
+ return null;
12805
+ }
12806
+ if (stmt.type === "ExportNamedDeclaration") {
12807
+ const decl = stmt.declaration;
12808
+ if (decl && declarationBinds(decl, name)) {
12809
+ const hit2 = lookup(mod.scopes, name);
12810
+ if (hit2 && hit2.scope === mod.scopes) return { mod, binding: hit2.binding };
12811
+ }
12812
+ for (const sp of stmt.specifiers ?? []) {
12813
+ const exported = sp.exported?.name ?? "";
12814
+ if (exported !== name) continue;
12815
+ const local = sp.local?.name ?? exported;
12816
+ const from = stmt.source?.value;
12817
+ if (from !== void 0) return followReExport(mod, from, local, seen);
12818
+ const hit2 = lookup(mod.scopes, local);
12819
+ return hit2 ? { mod, binding: hit2.binding } : null;
12820
+ }
12821
+ }
12822
+ if (stmt.type === "ExportAllDeclaration" && !stmt.exported) {
12823
+ const from = stmt.source?.value;
12824
+ if (from === void 0) continue;
12825
+ const found = followReExport(mod, from, name, seen);
12826
+ if (found) return found;
12827
+ }
12828
+ }
12829
+ const hit = lookup(mod.scopes, name);
12830
+ return hit && hit.scope === mod.scopes ? { mod, binding: hit.binding } : null;
12831
+ }
12832
+ function followReExport(mod, specifier, name, seen) {
12833
+ const file = resolveImport(mod.file, specifier);
12834
+ if (!file || seen.has(`${file}\0${name}`)) return null;
12835
+ seen.add(`${file}\0${name}`);
12836
+ const next = analyseModuleFile(file);
12837
+ return next ? exportedBinding(next, name, seen) : null;
12838
+ }
12839
+ var MAX_HOPS = 12;
12840
+ var IDENT_RE = /^[A-Za-z_$][\w$]*$/;
12841
+ var MEMBER_RE = /^([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)$/;
12842
+ function createReducerResolver(entryFile, body, src) {
12843
+ const scopes = buildScopeTree(body, src.length);
12844
+ markReassignments(body, scopes);
12845
+ const entry = { file: entryFile, src, body, scopes };
12846
+ const registered = /* @__PURE__ */ new Map();
12847
+ const ambiguous = /* @__PURE__ */ new Set();
12848
+ const fromBinding = (mod, binding, memberName, hops, seen) => {
12849
+ if (hops > MAX_HOPS) return { arity: null, src: null, reason: "not-found" };
12850
+ if (binding.reassigned) return { arity: null, src: null, reason: "reassigned" };
12851
+ if (binding.kind === "param" || binding.kind === "catch" || binding.kind === "class") {
12852
+ return { arity: null, src: null, reason: "not-a-function" };
12853
+ }
12854
+ if (binding.kind === "import" && binding.importRef) {
12855
+ const { specifier, imported } = binding.importRef;
12856
+ if (imported === "*" && memberName === void 0) return { arity: null, src: null, reason: "not-a-function" };
12857
+ const file = resolveImport(mod.file, specifier);
12858
+ if (!file) return { arity: null, src: null, reason: "unresolved-import" };
12859
+ const next = analyseModuleFile(file);
12860
+ if (!next) return { arity: null, src: null, reason: "unresolved-import" };
12861
+ const wanted = imported === "*" ? memberName : imported;
12862
+ if (wanted === void 0) return { arity: null, src: null, reason: "not-a-function" };
12863
+ const found = exportedBinding(next, wanted, seen);
12864
+ if (!found) return { arity: null, src: null, reason: "not-found" };
12865
+ return fromBinding(found.mod, found.binding, void 0, hops + 1, seen);
12866
+ }
12867
+ const init = binding.init;
12868
+ if (!init) return { arity: null, src: null, reason: "not-a-function" };
12869
+ if (init.type === "Identifier") {
12870
+ const hit = lookup(scopeAt(mod.scopes, init.start), init.name);
12871
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12872
+ return fromBinding(mod, hit.binding, void 0, hops + 1, seen);
12873
+ }
12874
+ if (init.type === "ComputedMemberExpression" || init.computed === true && init.type.endsWith("MemberExpression")) {
12875
+ return { arity: null, src: null, reason: "computed" };
12876
+ }
12877
+ if (init.type === "StaticMemberExpression" || init.type === "MemberExpression") {
12878
+ const obj = init.object;
12879
+ const prop = init.property;
12880
+ if (obj?.type === "Identifier" && prop?.type === "Identifier") {
12881
+ const hit = lookup(scopeAt(mod.scopes, init.start), obj.name);
12882
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12883
+ return fromBinding(mod, hit.binding, prop.name, hops + 1, seen);
12884
+ }
12885
+ return { arity: null, src: null, reason: "computed" };
12886
+ }
12887
+ if (!FUNCTION_TYPES.has(init.type)) return { arity: null, src: null, reason: "not-a-function" };
12888
+ const { arity, reason } = functionArity(init);
12889
+ return { arity, src: mod.src.slice(init.start, init.end), ...reason ? { reason } : {} };
12890
+ };
12891
+ return {
12892
+ register(file) {
12893
+ const mod = analyseModuleFile(file);
12894
+ if (!mod) return false;
12895
+ if (mod.src === src) return true;
12896
+ const prior = registered.get(mod.src);
12897
+ if (prior !== void 0 && prior.file !== mod.file) {
12898
+ ambiguous.add(mod.src);
12899
+ registered.delete(mod.src);
12900
+ return false;
12901
+ }
12902
+ if (!ambiguous.has(mod.src)) registered.set(mod.src, mod);
12903
+ return !ambiguous.has(mod.src);
12904
+ },
12905
+ resolve(exprSrc, offset, offsetSrc) {
12906
+ const text = exprSrc.trim();
12907
+ const ident = IDENT_RE.test(text) ? text : null;
12908
+ const member = ident ? null : MEMBER_RE.exec(text);
12909
+ if (!ident && !member) return null;
12910
+ const mod = offsetSrc === src ? entry : registered.get(offsetSrc);
12911
+ if (!mod) {
12912
+ return { arity: null, src: null, reason: ambiguous.has(offsetSrc) ? "ambiguous-source" : "foreign-source" };
12913
+ }
12914
+ const name = ident ?? member[1];
12915
+ const memberName = ident ? void 0 : member[2];
12916
+ const hit = lookup(scopeAt(mod.scopes, offset), name);
12917
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12918
+ return fromBinding(mod, hit.binding, memberName, 0, /* @__PURE__ */ new Set());
12919
+ }
12920
+ };
12921
+ }
12922
+
12923
+ // src/plugin/free-identifiers.ts
12924
+ var import_oxc_parser3 = require("oxc-parser");
12925
+ var isNode = (v2) => typeof v2 === "object" && v2 !== null && typeof v2.type === "string";
12926
+ var TYPE_KEYS = /* @__PURE__ */ new Set([
12927
+ "typeAnnotation",
12928
+ "returnType",
12929
+ "typeParameters",
12930
+ "typeArguments",
12931
+ "superTypeArguments",
12932
+ "typeName",
12933
+ "implements",
12934
+ "extends",
12935
+ "accessibility"
12936
+ ]);
12937
+ var TS_VALUE_WRAPPERS = /* @__PURE__ */ new Set([
12938
+ "TSAsExpression",
12939
+ "TSSatisfiesExpression",
12940
+ "TSNonNullExpression",
12941
+ "TSInstantiationExpression",
12942
+ "TSTypeAssertion"
12943
+ ]);
12944
+ var HOST_GLOBALS = /* @__PURE__ */ new Set([
12945
+ ...Object.getOwnPropertyNames(globalThis),
12946
+ "undefined",
12947
+ "NaN",
12948
+ "Infinity",
12949
+ "globalThis",
12950
+ "arguments",
12951
+ "window",
12952
+ "document",
12953
+ "self",
12954
+ "navigator",
12955
+ "location",
12956
+ "top",
12957
+ "parent",
12958
+ "require",
12959
+ "module",
12960
+ "exports",
12961
+ "__dirname",
12962
+ "__filename"
12963
+ ]);
12964
+ function bindPattern(pattern, out) {
12965
+ if (!isNode(pattern)) return;
12966
+ switch (pattern.type) {
12967
+ case "Identifier":
12968
+ case "BindingIdentifier":
12969
+ out.add(String(pattern.name));
12970
+ return;
12971
+ case "ObjectPattern":
12972
+ for (const prop of pattern.properties ?? []) {
12973
+ if (!isNode(prop)) continue;
12974
+ if (prop.type === "RestElement" || prop.type === "BindingRestElement") bindPattern(prop.argument, out);
12975
+ else bindPattern(prop.value, out);
12976
+ }
12977
+ return;
12978
+ case "ArrayPattern":
12979
+ for (const el of pattern.elements ?? []) bindPattern(el, out);
12980
+ return;
12981
+ case "AssignmentPattern":
12982
+ bindPattern(pattern.left, out);
12983
+ return;
12984
+ case "RestElement":
12985
+ case "BindingRestElement":
12986
+ bindPattern(pattern.argument, out);
12987
+ return;
12988
+ case "TSParameterProperty":
12989
+ bindPattern(pattern.parameter, out);
12990
+ return;
12991
+ default:
12992
+ return;
12993
+ }
12994
+ }
12995
+ function collectBindings(node2, out) {
12996
+ if (Array.isArray(node2)) {
12997
+ for (const child of node2) collectBindings(child, out);
12998
+ return;
12999
+ }
13000
+ if (!isNode(node2)) return;
13001
+ switch (node2.type) {
13002
+ case "ImportSpecifier":
13003
+ case "ImportDefaultSpecifier":
13004
+ case "ImportNamespaceSpecifier":
13005
+ if (isNode(node2.local)) out.add(String(node2.local.name));
13006
+ break;
13007
+ case "VariableDeclarator":
13008
+ bindPattern(node2.id, out);
13009
+ break;
13010
+ case "FunctionDeclaration":
13011
+ case "FunctionExpression":
13012
+ case "TSDeclareFunction":
13013
+ case "ArrowFunctionExpression":
13014
+ if (isNode(node2.id)) out.add(String(node2.id.name));
13015
+ for (const p of node2.params?.items ?? node2.params ?? []) bindPattern(p, out);
13016
+ if (isNode(node2.params) && node2.params.rest) bindPattern(node2.params.rest, out);
13017
+ break;
13018
+ case "ClassDeclaration":
13019
+ case "ClassExpression":
13020
+ if (isNode(node2.id)) out.add(String(node2.id.name));
13021
+ break;
13022
+ case "CatchClause":
13023
+ if (node2.param) {
13024
+ bindPattern(isNode(node2.param) && node2.param.pattern ? node2.param.pattern : node2.param, out);
13025
+ }
13026
+ break;
13027
+ default:
13028
+ break;
13029
+ }
13030
+ for (const key of Object.keys(node2)) {
13031
+ if (key === "type" || key === "start" || key === "end") continue;
13032
+ collectBindings(node2[key], out);
13033
+ }
13034
+ }
13035
+ var FUNCTION_TYPES2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
13036
+ function describeTopLevel(stmt) {
13037
+ const declared = /* @__PURE__ */ new Set();
13038
+ let target = stmt;
13039
+ let prefix = "";
13040
+ let exported = false;
13041
+ if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
13042
+ exported = true;
13043
+ prefix = "export ";
13044
+ if (!isNode(stmt.declaration)) return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}{ \u2026 }` };
13045
+ target = stmt.declaration;
13046
+ }
13047
+ if (target.type === "VariableDeclaration") {
13048
+ const decls = (target.declarations ?? []).filter(isNode);
13049
+ for (const d of decls) bindPattern(d.id, declared);
13050
+ const declaresOnlyFunctions = decls.length > 0 && decls.every((d) => isNode(d.init) && FUNCTION_TYPES2.has(d.init.type));
13051
+ const name = [...declared][0];
13052
+ return {
13053
+ declared,
13054
+ exported,
13055
+ declaresOnlyFunctions,
13056
+ label: name ? `${prefix}${String(target.kind)} ${name}` : null
13057
+ };
13058
+ }
13059
+ if (target.type === "FunctionDeclaration" && isNode(target.id)) {
13060
+ declared.add(String(target.id.name));
13061
+ return { declared, exported, declaresOnlyFunctions: true, label: `${prefix}function ${String(target.id.name)}` };
13062
+ }
13063
+ if (target.type === "ClassDeclaration" && isNode(target.id)) {
13064
+ declared.add(String(target.id.name));
13065
+ return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}class ${String(target.id.name)}` };
13066
+ }
13067
+ return { declared, exported, declaresOnlyFunctions: false, label: null };
13068
+ }
13069
+ function findFreeIdentifiers(code, filename) {
13070
+ let parsed;
13071
+ try {
13072
+ parsed = (0, import_oxc_parser3.parseSync)(filename, code);
13073
+ } catch {
13074
+ return [];
13075
+ }
13076
+ if (parsed.errors.length > 0) return [];
13077
+ const bound = /* @__PURE__ */ new Set();
13078
+ collectBindings(parsed.program.body, bound);
13079
+ const lineStarts = [0];
13080
+ for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
13081
+ const positionOf = (offset) => {
13082
+ let lo = 0;
13083
+ let hi = lineStarts.length - 1;
13084
+ while (lo < hi) {
13085
+ const mid = lo + hi + 1 >> 1;
13086
+ if (lineStarts[mid] <= offset) lo = mid;
13087
+ else hi = mid - 1;
13088
+ }
13089
+ return { line: lo + 1, column: offset - lineStarts[lo] + 1 };
13090
+ };
13091
+ const statements = parsed.program.body.filter(isNode);
13092
+ const tops = statements.map(describeTopLevel);
13093
+ const candidates = [];
13094
+ const seen = /* @__PURE__ */ new Set();
13095
+ const readIn = /* @__PURE__ */ new Map();
13096
+ const visit = (node2, stmt, insideFunction) => {
13097
+ if (Array.isArray(node2)) {
13098
+ for (const child of node2) visit(child, stmt, insideFunction);
13099
+ return;
13100
+ }
13101
+ if (!isNode(node2)) return;
13102
+ const type = node2.type;
13103
+ if (type === "ImportDeclaration" || type === "ExportAllDeclaration") return;
13104
+ if (type === "MetaProperty" || type === "PrivateIdentifier") return;
13105
+ if (type === "ExportNamedDeclaration" && node2.source) return;
13106
+ if (type.startsWith("TS")) {
13107
+ if (TS_VALUE_WRAPPERS.has(type)) visit(node2.expression, stmt, insideFunction);
13108
+ return;
13109
+ }
13110
+ if (type === "Identifier" || type === "IdentifierReference") {
13111
+ const name = String(node2.name);
13112
+ let readers = readIn.get(name);
13113
+ if (!readers) readIn.set(name, readers = /* @__PURE__ */ new Set());
13114
+ readers.add(stmt);
13115
+ if (bound.has(name) || HOST_GLOBALS.has(name)) return;
13116
+ const offset = typeof node2.start === "number" ? node2.start : 0;
13117
+ const { line, column } = positionOf(offset);
13118
+ const key = `${name}@${line}:${column}`;
13119
+ if (seen.has(key)) return;
13120
+ seen.add(key);
13121
+ candidates.push({ name, line, column, enclosing: tops[stmt].label, stmt, insideFunction });
13122
+ return;
13123
+ }
13124
+ const nowInsideFunction = insideFunction || FUNCTION_TYPES2.has(type);
13125
+ for (const key of Object.keys(node2)) {
13126
+ if (key === "type" || key === "start" || key === "end") continue;
13127
+ if (TYPE_KEYS.has(key)) continue;
13128
+ if (key === "key" && node2.computed !== true) continue;
13129
+ if (key === "property" && node2.computed !== true) continue;
13130
+ if (key === "label") continue;
13131
+ if (key === "exported") continue;
13132
+ if (key === "imported") continue;
13133
+ visit(node2[key], stmt, nowInsideFunction);
13134
+ }
13135
+ };
13136
+ for (let i = 0; i < statements.length; i++) visit(statements[i], i, false);
13137
+ return candidates.filter((c) => {
13138
+ const top = tops[c.stmt];
13139
+ if (!c.insideFunction || top.exported || !top.declaresOnlyFunctions || top.declared.size === 0) return true;
13140
+ for (const name of top.declared) {
13141
+ const readers = readIn.get(name);
13142
+ if (readers && [...readers].some((i) => i !== c.stmt)) return true;
13143
+ }
13144
+ return false;
13145
+ }).map(({ name, line, column, enclosing }) => ({ name, line, column, enclosing }));
13146
+ }
13147
+
13148
+ // src/compiler/module-hoist.ts
13149
+ var marker = (id) => `/*@pmh:${id}@*/`;
13150
+ var MARKER_RE = /\/\*@pmh:(\d+)@\*\/\n?/g;
13151
+ var HOIST_MARKER_PROBE = /\/\*@pmh:\d+@\*\//;
13152
+ var DECL_RE = /^(?:const|let|var|function\*?)\s+([A-Za-z_$][\w$]*)/;
13153
+ var ALWAYS_LOCAL = /* @__PURE__ */ new Set(["_map", "_k", "_env"]);
13154
+ function declaredNames(text) {
13155
+ const out = [];
13156
+ for (const line of text.split("\n")) {
13157
+ const m = DECL_RE.exec(line);
13158
+ if (m) out.push(m[1]);
13159
+ }
13160
+ return out;
13161
+ }
13162
+ function createModuleHoist() {
13163
+ const texts = [];
13164
+ const claim = (decls) => decls.map((text) => {
13165
+ const id = texts.length;
13166
+ texts.push(text);
13167
+ return marker(id);
13168
+ });
13169
+ const finalize = () => {
13170
+ const nameTexts = /* @__PURE__ */ new Map();
13171
+ const textCount = /* @__PURE__ */ new Map();
13172
+ const textNames = /* @__PURE__ */ new Map();
13173
+ const order = [];
13174
+ for (const text of texts) {
13175
+ const seen = textCount.get(text);
13176
+ textCount.set(text, (seen ?? 0) + 1);
13177
+ if (seen === void 0) {
13178
+ order.push(text);
13179
+ const names = declaredNames(text);
13180
+ textNames.set(text, names);
13181
+ for (const n of names) {
13182
+ const s = nameTexts.get(n);
13183
+ if (s) s.add(text);
13184
+ else nameTexts.set(n, /* @__PURE__ */ new Set([text]));
13185
+ }
13186
+ }
13187
+ }
13188
+ const hoisted = new Set(
13189
+ order.filter(
13190
+ (text) => (textCount.get(text) ?? 0) >= 2 && textNames.get(text).length > 0 && textNames.get(text).every((n) => !ALWAYS_LOCAL.has(n) && nameTexts.get(n).size === 1)
13191
+ )
13192
+ );
13193
+ for (; ; ) {
13194
+ const localNames = [];
13195
+ for (const [n, ts] of nameTexts) {
13196
+ if (ts.size !== 1 || !hoisted.has([...ts][0])) localNames.push(n);
13197
+ }
13198
+ let changed = false;
13199
+ for (const text of [...hoisted]) {
13200
+ if (localNames.some((n) => new RegExp(`\\b${escapeName(n)}\\b`).test(text))) {
13201
+ hoisted.delete(text);
13202
+ changed = true;
13203
+ }
13204
+ }
13205
+ if (!changed) break;
13206
+ }
13207
+ const preludeTexts = order.filter((t) => hoisted.has(t));
13208
+ const prelude = preludeTexts.length === 0 ? "" : `${preludeTexts.join("\n")}
13209
+ `;
13210
+ const removed = texts.reduce((n, t) => hoisted.has(t) ? n + t.length + 1 : n, 0);
13211
+ return {
13212
+ prelude,
13213
+ savedBytes: removed - prelude.length,
13214
+ hoistedNames: preludeTexts.flatMap((t) => textNames.get(t)),
13215
+ resolve: (src) => src.replace(MARKER_RE, (m, idStr) => {
13216
+ const text = texts[Number(idStr)];
13217
+ if (text === void 0) throw new Error(`parseman: unknown hoist marker ${m}`);
13218
+ if (hoisted.has(text)) return "";
13219
+ return m.endsWith("\n") ? `${text}
13220
+ ` : text;
13221
+ })
13222
+ };
13223
+ };
13224
+ return { claim, finalize };
13225
+ }
13226
+ function escapeName(n) {
13227
+ return n.replace(/\$/g, "\\$");
13228
+ }
13229
+
12510
13230
  // src/plugin/index.ts
12511
13231
  var import_node_crypto = require("node:crypto");
12512
13232
  var PARSEMAN_MODULE = "parseman";
@@ -12532,37 +13252,37 @@ function getCompiledResolver() {
12532
13252
  }
12533
13253
  var PRIVATE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
12534
13254
  function nearestPackageRoot(file) {
12535
- let dir = path.dirname(file);
13255
+ let dir = path2.dirname(file);
12536
13256
  for (; ; ) {
12537
13257
  try {
12538
- if (fs.statSync(path.join(dir, "package.json")).isFile()) return fs.realpathSync(dir);
13258
+ if (fs2.statSync(path2.join(dir, "package.json")).isFile()) return fs2.realpathSync(dir);
12539
13259
  } catch {
12540
13260
  }
12541
- const parent = path.dirname(dir);
13261
+ const parent = path2.dirname(dir);
12542
13262
  if (parent === dir) return null;
12543
13263
  dir = parent;
12544
13264
  }
12545
13265
  }
12546
13266
  function isWithinPackageRoot(file, root) {
12547
- const relative2 = path.relative(root, file);
12548
- return relative2 !== ".." && !relative2.startsWith(`..${path.sep}`) && !path.isAbsolute(relative2);
13267
+ const relative2 = path2.relative(root, file);
13268
+ return relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
12549
13269
  }
12550
13270
  function resolvePrivateSourceModule(from, specifier) {
12551
- if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return null;
13271
+ if (!specifier.startsWith(".") && !path2.isAbsolute(specifier)) return null;
12552
13272
  const packageRoot = nearestPackageRoot(from);
12553
13273
  if (!packageRoot) return null;
12554
- const base = path.resolve(path.dirname(from), specifier);
12555
- const ext = path.extname(base);
13274
+ const base = path2.resolve(path2.dirname(from), specifier);
13275
+ const ext = path2.extname(base);
12556
13276
  const candidates = [
12557
13277
  base,
12558
13278
  ...ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base.slice(0, -ext.length)}${next}`) : [],
12559
13279
  ...!ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base}${next}`) : [],
12560
- ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path.join(base, next)) : []
13280
+ ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path2.join(base, next)) : []
12561
13281
  ];
12562
13282
  for (const candidate of candidates) {
12563
13283
  try {
12564
- if (!fs.statSync(candidate).isFile()) continue;
12565
- const real = fs.realpathSync(candidate);
13284
+ if (!fs2.statSync(candidate).isFile()) continue;
13285
+ const real = fs2.realpathSync(candidate);
12566
13286
  if (isWithinPackageRoot(real, packageRoot)) return real;
12567
13287
  } catch {
12568
13288
  }
@@ -12570,7 +13290,7 @@ function resolvePrivateSourceModule(from, specifier) {
12570
13290
  return null;
12571
13291
  }
12572
13292
  function resolveGrammarModule(from, specifier) {
12573
- if (PRIVATE_SOURCE_EXTENSIONS.has(path.extname(specifier))) {
13293
+ if (PRIVATE_SOURCE_EXTENSIONS.has(path2.extname(specifier))) {
12574
13294
  const file2 = resolvePrivateSourceModule(from, specifier);
12575
13295
  return file2 ? { file: file2, source: true } : null;
12576
13296
  }
@@ -12591,7 +13311,7 @@ var _moduleParseCache = /* @__PURE__ */ new Map();
12591
13311
  function parseModuleCached(filePath) {
12592
13312
  let mtimeMs;
12593
13313
  try {
12594
- mtimeMs = fs.statSync(filePath).mtimeMs;
13314
+ mtimeMs = fs2.statSync(filePath).mtimeMs;
12595
13315
  } catch {
12596
13316
  return null;
12597
13317
  }
@@ -12599,8 +13319,8 @@ function parseModuleCached(filePath) {
12599
13319
  if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
12600
13320
  let parsed = null;
12601
13321
  try {
12602
- const src = fs.readFileSync(filePath, "utf8");
12603
- const r = (0, import_oxc_parser2.parseSync)(filePath, src);
13322
+ const src = fs2.readFileSync(filePath, "utf8");
13323
+ const r = (0, import_oxc_parser4.parseSync)(filePath, src);
12604
13324
  if (r.errors.length === 0) parsed = { body: r.program.body, src };
12605
13325
  } catch {
12606
13326
  }
@@ -12728,7 +13448,7 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12728
13448
  if (_sourceLowering.has(file)) return null;
12729
13449
  let mtimeMs;
12730
13450
  try {
12731
- mtimeMs = fs.statSync(file).mtimeMs;
13451
+ mtimeMs = fs2.statSync(file).mtimeMs;
12732
13452
  } catch {
12733
13453
  return null;
12734
13454
  }
@@ -12738,10 +13458,10 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12738
13458
  _sourceLowering.add(file);
12739
13459
  let parsedModule = null;
12740
13460
  try {
12741
- const source = fs.readFileSync(file, "utf8");
13461
+ const source = fs2.readFileSync(file, "utf8");
12742
13462
  const transformed = transformMacro(source, file, moduleAliases, warnUnloweredRegex, recovery);
12743
13463
  if (!transformed || transformed.warnings.length > 0) return null;
12744
- const parsed = (0, import_oxc_parser2.parseSync)(file, transformed.code);
13464
+ const parsed = (0, import_oxc_parser4.parseSync)(file, transformed.code);
12745
13465
  parsedModule = parsed.errors.length === 0 ? { body: parsed.program.body, src: transformed.code } : null;
12746
13466
  return parsedModule;
12747
13467
  } catch {
@@ -12752,9 +13472,20 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12752
13472
  }
12753
13473
  }
12754
13474
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false, grammarCoverage = false) {
13475
+ const depth = degradationCaptureDepth();
13476
+ try {
13477
+ return transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage);
13478
+ } finally {
13479
+ setReducerResolver(null);
13480
+ endLoweringCapture();
13481
+ endInlineCapCapture();
13482
+ for (const d of unwindDegradationCapture(depth)) console.warn(formatDegradation(d));
13483
+ }
13484
+ }
13485
+ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage) {
12755
13486
  let result;
12756
13487
  try {
12757
- result = (0, import_oxc_parser2.parseSync)(id, code);
13488
+ result = (0, import_oxc_parser4.parseSync)(id, code);
12758
13489
  } catch {
12759
13490
  return null;
12760
13491
  }
@@ -12811,6 +13542,8 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12811
13542
  }
12812
13543
  }
12813
13544
  }
13545
+ const reducerResolver = createReducerResolver(id, body, code);
13546
+ setReducerResolver(reducerResolver, code);
12814
13547
  const topLevelFunction = (moduleBody, name) => {
12815
13548
  for (const st of moduleBody) {
12816
13549
  const decl = st.type === "ExportNamedDeclaration" ? st.declaration : st;
@@ -12905,13 +13638,18 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12905
13638
  scope: factoryScope,
12906
13639
  imported: true
12907
13640
  });
13641
+ reducerResolver.register(file);
12908
13642
  }
12909
13643
  const scope = /* @__PURE__ */ new Map();
12910
13644
  const replacements = [];
12911
13645
  const warnings = [];
13646
+ const moduleHoist = grammarCoverage ? void 0 : createModuleHoist();
12912
13647
  beginLoweringCapture();
13648
+ beginInlineCapCapture();
13649
+ beginDegradationCapture();
12913
13650
  let anyUnresolved = false;
12914
13651
  let keepMacroImport = false;
13652
+ const exportedFactories = [];
12915
13653
  let runtimeComposeFallback = false;
12916
13654
  let ruleMapHolderCounter = 0;
12917
13655
  const lineOf = (pos) => {
@@ -13025,7 +13763,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13025
13763
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
13026
13764
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
13027
13765
  ).join(", ")}])`;
13028
- return `{ v: ${JSON.stringify(p.v)}, ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, nullable: ${p.nullable ? mapLit(p.nullable) : "new Map()"}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, nodeMeta: ${mapLit(p.nodeMeta)}, hostMode: ${JSON.stringify(p.hostMode ?? "ast")}, hostBranchElided: ${p.hostBranchElided === true}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
13766
+ return `{ v: ${JSON.stringify(p.v)}, ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, nullable: ${p.nullable ? mapLit(p.nullable) : "new Map()"}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, nodeMeta: ${mapLit(p.nodeMeta)}, hostMode: ${JSON.stringify(p.hostMode ?? "ast")}, hostBranchElided: ${p.hostBranchElided === true}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, needsRawEntry: ${p.needsRawEntry}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
13029
13767
  };
13030
13768
  const isSpread = (it) => "__spreadLocal" in it;
13031
13769
  const isIR = (it) => "ir" in it && !("ruleFns" in it);
@@ -13036,9 +13774,19 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13036
13774
  const withHostMode = (grammarExpr, mode, elided) => `/* @__PURE__ */ (m => { for (const k of Object.keys(m)) { Object.defineProperty(m[k], Symbol.for('parseman.fusedHostMode'), { value: ${JSON.stringify(mode)}, enumerable: false }); Object.defineProperty(m[k], Symbol.for('parseman.fusedHostElided'), { value: ${JSON.stringify(elided)}, enumerable: false }) } Object.defineProperty(m, Symbol.for('parseman.fusedHostMode'), { value: ${JSON.stringify(mode)}, enumerable: false }); Object.defineProperty(m, Symbol.for('parseman.fusedHostElided'), { value: ${JSON.stringify(elided)}, enumerable: false }); return m })(${grammarExpr})`;
13037
13775
  const withGrammarReflection = (grammarExpr, reflection) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.grammarReflection'), { value: ${grammarReflectionSource(reflection)}, enumerable: false })`;
13038
13776
  const withCoverageDefinitions = (grammarExpr, definitions) => !grammarCoverage ? grammarExpr : `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.grammarCoverageDefinitions'), { value: Object.freeze(${JSON.stringify(definitions)}.map(Object.freeze)), enumerable: false })`;
13039
- const emittedCoverageDefinitions = (source) => {
13777
+ const emittedCoverageDefinitions = (source, where) => {
13040
13778
  const ids = /* @__PURE__ */ new Set();
13041
13779
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
13780
+ if (grammarCoverage && ids.size === 0) {
13781
+ recordDegradation({
13782
+ code: "coverage-definitions-unavailable",
13783
+ severity: "warn",
13784
+ where,
13785
+ subject: "generated coverage hooks",
13786
+ fellBackTo: "no coverage definitions could be read out of the generated source, so the grammar carries an EMPTY definition set \u2014 which is no measurement, not full coverage",
13787
+ otherwise: "the emitted hook IDs would form the coverage denominator"
13788
+ });
13789
+ }
13042
13790
  return [...ids].sort().map((id2) => ({
13043
13791
  id: id2,
13044
13792
  kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
@@ -13107,26 +13855,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13107
13855
  }
13108
13856
  return out;
13109
13857
  };
13110
- const carriedRuleMapsDetailed = (items) => {
13111
- const maps = [];
13112
- const opaque = [];
13113
- const add = (it) => {
13114
- if (isIR(it)) {
13115
- maps.push(evalRuleMapIR(it.ir));
13116
- return;
13117
- }
13118
- const o = it;
13119
- opaque.push({
13120
- ns: o.ns ?? "<unknown>",
13121
- ruleNames: o.ruleFns instanceof Map ? [...o.ruleFns.keys()] : []
13122
- });
13123
- };
13124
- for (const it of items) {
13125
- if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
13126
- else add(it);
13127
- }
13128
- return { maps, opaque };
13129
- };
13130
13858
  const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
13131
13859
  const sources = [];
13132
13860
  const add = (item) => {
@@ -13277,16 +14005,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13277
14005
  carried.push(...r.carried);
13278
14006
  importedFactories.push(...r.importedFactories ?? []);
13279
14007
  }
13280
- const detailed = once(() => carriedRuleMapsDetailed(carried));
13281
- runFusedGatingDiagnostic(
13282
- () => detailed().maps,
13283
- void 0,
13284
- () => detailed().opaque
13285
- );
13286
14008
  const pieces = materializeCarried(carried, composing, false, cHostMode);
13287
14009
  try {
13288
14010
  return {
13289
- replacement: emitFusedSource(pieces),
14011
+ replacement: emitFusedSource(pieces, moduleHoist),
13290
14012
  carried,
13291
14013
  ...composing ? { trivia: composing } : {},
13292
14014
  ...importedFactories.length ? { importedFactories } : {}
@@ -13339,12 +14061,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13339
14061
  }
13340
14062
  try {
13341
14063
  const localNs = nsFor(`composeLeaf${init.start}`);
13342
- const detailedLeaf = once(() => carriedRuleMapsDetailed(carried));
13343
- runFusedGatingDiagnostic(
13344
- () => [...detailedLeaf().maps, [...localRules]],
13345
- void 0,
13346
- () => detailedLeaf().opaque
13347
- );
13348
14064
  const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
13349
14065
  if (!plainLocalPiece) {
13350
14066
  warn(init.start, "composeLeaf(): local rules could not be statically compiled");
@@ -13360,9 +14076,9 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13360
14076
  warn(init.start, "composeLeaf(): every pre-final grammar must explicitly prove recognition-only");
13361
14077
  return null;
13362
14078
  }
13363
- const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece]));
14079
+ const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece], moduleHoist));
13364
14080
  return {
13365
- replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)),
14081
+ replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement, `${id} composeLeaf()`)),
13366
14082
  ...importedFactories.length ? { importedFactories } : {}
13367
14083
  };
13368
14084
  } catch (e) {
@@ -13442,7 +14158,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13442
14158
  const refEntry = scope.get(varName);
13443
14159
  const refCombi = refEntry?.combi ?? null;
13444
14160
  if (refCombi) {
13445
- const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
14161
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
13446
14162
  if (compiled2.inlineExpression === null) {
13447
14163
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
13448
14164
  continue;
@@ -13473,7 +14189,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13473
14189
  const source = compiledRules.replacement ?? code.slice(init.start, init.end);
13474
14190
  let replacement = withCoverageDefinitions(
13475
14191
  source,
13476
- compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
14192
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source, `${id} rules()`)
13477
14193
  );
13478
14194
  if (compiledRules.replacement !== null) {
13479
14195
  replacement = withHostMode(replacement, compiledRules.hostMode, compiledRules.hostBranchElided);
@@ -13498,7 +14214,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13498
14214
  localComposedCarried.set(varName, fused.carried);
13499
14215
  if (fused.trivia) localComposedTrivia.set(varName, fused.trivia);
13500
14216
  const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
13501
- replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) });
14217
+ replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement, `${id} compose()`)) });
13502
14218
  markUsedImportedFactories(fused.importedFactories);
13503
14219
  continue;
13504
14220
  }
@@ -13545,11 +14261,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13545
14261
  scope.set(varName, staticValue);
13546
14262
  continue;
13547
14263
  }
13548
- if (factoryDecls.has(varName)) continue;
14264
+ if (factoryDecls.has(varName)) {
14265
+ if (exportPrefix) exportedFactories.push({ name: varName, pos: init.start });
14266
+ continue;
14267
+ }
13549
14268
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
13550
14269
  continue;
13551
14270
  }
13552
- const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
14271
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
13553
14272
  if (compiled.inlineExpression === null) {
13554
14273
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
13555
14274
  continue;
@@ -13616,6 +14335,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13616
14335
  for (const imp of macroImports) {
13617
14336
  imp.fullyResolved = !anyUnresolved && !keepMacroImport;
13618
14337
  }
14338
+ if (exportedFactories.length > 0 && macroImports.some((imp) => imp.fullyResolved)) {
14339
+ const which = exportedFactories.map((f) => ` - ${id}:${lineOf(f.pos)} \u2014 export const ${f.name}`).join("\n");
14340
+ throw new Error(
14341
+ `${id} \u2014 a rules() factory cannot be exported; lowering leaves its body verbatim, so the export would ship a binding that throws ReferenceError on first call:
14342
+ ${which}
14343
+ Drop the \`export\`. The factory is macro-only; \`rules(${exportedFactories[0].name})\` is lowered in place and needs no runtime value.`
14344
+ );
14345
+ }
13619
14346
  const ms = new import_magic_string.default(code);
13620
14347
  const replacementRanges = runtimeComposeFallback ? [] : replacements.map(({ start, end }) => ({ start, end }));
13621
14348
  const isInsideReplacement = (start, end) => replacementRanges.some((r) => start >= r.start && end <= r.end);
@@ -13664,8 +14391,21 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13664
14391
  }
13665
14392
  }
13666
14393
  const applied = runtimeComposeFallback ? [] : replacements;
14394
+ const hoisted = applied.length > 0 ? moduleHoist?.finalize() : void 0;
13667
14395
  for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
13668
- ms.overwrite(start, end, replacement);
14396
+ ms.overwrite(start, end, hoisted ? hoisted.resolve(replacement) : replacement);
14397
+ }
14398
+ if (hoisted !== void 0 && hoisted.prelude !== "") {
14399
+ const claiming = applied.filter((r) => HOIST_MARKER_PROBE.test(r.replacement));
14400
+ if (claiming.length === 0) {
14401
+ throw new Error(`${id} \u2014 internal: a module-hoist prelude was produced but no applied replacement carries a hoist marker`);
14402
+ }
14403
+ const firstStart = Math.min(...claiming.map((r) => r.start));
14404
+ const anchor = body.find((s) => s.start <= firstStart && firstStart < s.end);
14405
+ if (anchor === void 0) {
14406
+ throw new Error(`${id} \u2014 internal: no top-level statement encloses macro replacement at ${firstStart}`);
14407
+ }
14408
+ ms.appendLeft(anchor.start, hoisted.prelude);
13669
14409
  }
13670
14410
  if (applied.length > 0) {
13671
14411
  ms.prepend(
@@ -13675,12 +14415,38 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13675
14415
  `
13676
14416
  );
13677
14417
  }
14418
+ if (applied.length > 0) {
14419
+ let sourceFree = null;
14420
+ const freeInSource = () => sourceFree ??= new Set(findFreeIdentifiers(code, id).map((f) => f.name));
14421
+ const free = findFreeIdentifiers(ms.toString(), id).filter((f) => !freeInSource().has(f.name));
14422
+ if (free.length > 0) {
14423
+ const macroProvided = free.filter((f) => allNames.has(f.name));
14424
+ const where = (f) => ` - ${id}:${f.line}:${f.column} \u2014 \`${f.name}\`` + (f.enclosing ? `, inside \`${f.enclosing}\`` : "");
14425
+ const cause = macroProvided.length > 0 ? `
14426
+ ${macroProvided.length === free.length ? "All" : `${macroProvided.length}`} of these came from the \`with { type: 'macro' }\` import, which was removed because every macro declaration lowered. Something still names them verbatim \u2014 a declaration parseman left as text rather than compiling. Make that declaration macro-buildable, or stop exporting it so lowering can drop it.` : "";
14427
+ throw new Error(
14428
+ `${id} \u2014 parseman will not emit this module: ${free.length} identifier(s) are read but bound by nothing, so it would throw ReferenceError at runtime (positions are in the EMITTED module):
14429
+ ${free.map(where).join("\n")}${cause}`
14430
+ );
14431
+ }
14432
+ }
14433
+ for (const line of formatInlineCapSites(endInlineCapCapture(), resolveInlineMax())) warnings.push(`${id}: ${line}`);
13678
14434
  const unlowered = endLoweringCapture();
13679
14435
  if (warnUnloweredRegex) {
13680
14436
  for (const src of unlowered) {
13681
14437
  warnings.push(`${id}: regex ${src} did not lower to a fast charCodeAt scan (RegExp.exec fallback)`);
13682
14438
  }
13683
14439
  }
14440
+ setReducerResolver(null);
14441
+ const degradations = endDegradationCapture();
14442
+ if (degradations.length > 0) {
14443
+ const lines = formatDegradations(degradations);
14444
+ if (resolveDegradationLevel() === "error") {
14445
+ throw new Error(`parseman: ${degradations.length} degraded compilation path(s) in ${id}
14446
+ ${lines.join("\n")}`);
14447
+ }
14448
+ for (const l of lines) warnings.push(`${id}: ${l}`);
14449
+ }
13684
14450
  return {
13685
14451
  code: ms.toString(),
13686
14452
  map: ms.generateMap({ hires: true }),