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
@@ -1,16 +1,16 @@
1
1
  // src/plugin/index.ts
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
2
+ import * as fs2 from "node:fs";
3
+ import * as path2 from "node:path";
4
4
  import { createUnplugin } from "unplugin";
5
- import { parseSync as parseSync2 } from "oxc-parser";
5
+ import { parseSync as parseSync4 } from "oxc-parser";
6
6
  import { ResolverFactory } from "oxc-resolver";
7
7
  import MagicString from "magic-string";
8
8
 
9
9
  // src/combinators/first-set.ts
10
- function resolveNamedRef(p, resolve2) {
11
- if (resolve2 === void 0) return void 0;
10
+ function resolveNamedRef(p, resolve3) {
11
+ if (resolve3 === void 0) return void 0;
12
12
  const name = p._ruleName;
13
- return name === void 0 ? void 0 : resolve2(name);
13
+ return name === void 0 ? void 0 : resolve3(name);
14
14
  }
15
15
  function union(a, b) {
16
16
  if (a.kind === "any" || b.kind === "any") return { kind: "any" };
@@ -32,12 +32,12 @@ function fromChar(code) {
32
32
  return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
33
33
  }
34
34
  function startsFirstSet(combinator, input, pos) {
35
- const fs2 = combinator._meta.firstSet;
36
- if (fs2.kind === "any") return true;
37
- if (fs2.kind === "empty") return false;
35
+ const fs3 = combinator._meta.firstSet;
36
+ if (fs3.kind === "any") return true;
37
+ if (fs3.kind === "empty") return false;
38
38
  const code = input.codePointAt(pos);
39
39
  if (code === void 0) return false;
40
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
40
+ for (const r of fs3.ranges) if (code >= r.lo && code <= r.hi) return true;
41
41
  return false;
42
42
  }
43
43
  function fromRange(lo, hi) {
@@ -49,18 +49,19 @@ function any() {
49
49
  function empty() {
50
50
  return { kind: "empty" };
51
51
  }
52
- function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
52
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve3) {
53
53
  if (seen.has(p)) return true;
54
54
  seen.add(p);
55
- const me = (c) => matchesEmpty(c, seen, resolve2);
55
+ const me = (c) => matchesEmpty(c, seen, resolve3);
56
56
  const d = p._def;
57
57
  switch (d.tag) {
58
58
  case "literal":
59
59
  return d.value.length === 0;
60
60
  case "keywords":
61
61
  return false;
62
+ // The routed token itself is the selector's match, never empty; a fallback can be.
62
63
  case "routed":
63
- return false;
64
+ return d.fallback === void 0 ? false : me(d.fallback);
64
65
  case "regex":
65
66
  try {
66
67
  const m = new RegExp(d.source).exec("");
@@ -106,7 +107,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
106
107
  try {
107
108
  return me(d.thunk());
108
109
  } catch {
109
- const t = resolveNamedRef(p, resolve2);
110
+ const t = resolveNamedRef(p, resolve3);
110
111
  return t ? me(t) : true;
111
112
  }
112
113
  default:
@@ -132,29 +133,29 @@ function narrowBy(acc, constraint) {
132
133
  }
133
134
  return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
134
135
  }
135
- function applyAssertion(fs2, assertion) {
136
- if (assertion === null) return fs2;
137
- if (fs2.kind === "empty") return assertion;
138
- return narrowBy(fs2, assertion) ?? fs2;
136
+ function applyAssertion(fs3, assertion) {
137
+ if (assertion === null) return fs3;
138
+ if (fs3.kind === "empty") return assertion;
139
+ return narrowBy(fs3, assertion) ?? fs3;
139
140
  }
140
141
  function sequenceFirstSet(parsers) {
141
- let fs2 = empty();
142
+ let fs3 = empty();
142
143
  let assertion = null;
143
144
  for (const p of parsers) {
144
145
  if (isPositiveLookahead(p)) {
145
146
  assertion = narrowBy(assertion, p._meta.firstSet);
146
147
  continue;
147
148
  }
148
- if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
149
- if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
149
+ if (!isZeroWidthAssertion(p)) fs3 = union(fs3, p._meta.firstSet);
150
+ if (!matchesEmpty(p)) return applyAssertion(fs3, assertion);
150
151
  }
151
- return applyAssertion(fs2, assertion);
152
+ return applyAssertion(fs3, assertion);
152
153
  }
153
- function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
154
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve3) {
154
155
  if (seen.has(p)) return any();
155
156
  seen.add(p);
156
- const fs2 = (c) => firstSetOf(c, seen, resolve2);
157
- const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
157
+ const fs3 = (c) => firstSetOf(c, seen, resolve3);
158
+ const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve3);
158
159
  const d = p._def;
159
160
  switch (d.tag) {
160
161
  case "literal":
@@ -163,17 +164,17 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
163
164
  return p._meta.firstSet;
164
165
  // terminals: no refs, cached set is exact
165
166
  case "dispatch":
166
- return fs2(d.selector);
167
+ return fs3(d.selector);
167
168
  case "lazy":
168
169
  try {
169
- return fs2(d.thunk());
170
+ return fs3(d.thunk());
170
171
  } catch {
171
- const t = resolveNamedRef(p, resolve2);
172
- return t ? fs2(t) : any();
172
+ const t = resolveNamedRef(p, resolve3);
173
+ return t ? fs3(t) : any();
173
174
  }
174
175
  case "choice": {
175
176
  let out = empty();
176
- for (const arm of d.parsers) out = union(out, fs2(arm));
177
+ for (const arm of d.parsers) out = union(out, fs3(arm));
177
178
  return out;
178
179
  }
179
180
  case "sequence": {
@@ -181,17 +182,17 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
181
182
  let assertion = null;
182
183
  for (const term of d.parsers) {
183
184
  if (isPositiveLookahead(term)) {
184
- assertion = narrowBy(assertion, fs2(term));
185
+ assertion = narrowBy(assertion, fs3(term));
185
186
  continue;
186
187
  }
187
- if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
188
+ if (!isZeroWidthAssertion(term)) out = union(out, fs3(term));
188
189
  if (!empties(term)) return applyAssertion(out, assertion);
189
190
  }
190
191
  return applyAssertion(out, assertion);
191
192
  }
192
193
  case "peek": {
193
194
  const inner = d.parser;
194
- return empties(inner) ? any() : fs2(inner);
195
+ return empties(inner) ? any() : fs3(inner);
195
196
  }
196
197
  case "oneOrMore":
197
198
  case "many":
@@ -204,12 +205,12 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
204
205
  case "node":
205
206
  case "grammar":
206
207
  case "expect":
207
- return fs2(d.parser);
208
+ return fs3(d.parser);
208
209
  case "sepBy":
209
- return fs2(d.parser);
210
+ return fs3(d.parser);
210
211
  // both min 0 and min 1 start with the item
211
212
  case "skip":
212
- return fs2(d.main);
213
+ return fs3(d.main);
213
214
  default:
214
215
  return p._meta.firstSet;
215
216
  }
@@ -680,13 +681,13 @@ function shortScanner(source, flags) {
680
681
  return minOne && end === pos ? null : end;
681
682
  };
682
683
  }
683
- function asciiCaseFold(fs2) {
684
- if (fs2.kind !== "ranges") return fs2;
685
- const ranges = fs2.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
684
+ function asciiCaseFold(fs3) {
685
+ if (fs3.kind !== "ranges") return fs3;
686
+ const ranges = fs3.ranges.map((r) => ({ lo: r.lo, hi: r.hi }));
686
687
  const add = (lo, hi) => {
687
688
  if (lo <= hi) ranges.push({ lo, hi });
688
689
  };
689
- for (const r of fs2.ranges) {
690
+ for (const r of fs3.ranges) {
690
691
  const uLo = Math.max(r.lo, 65), uHi = Math.min(r.hi, 90);
691
692
  add(uLo + 32, uHi + 32);
692
693
  const lLo = Math.max(r.lo, 97), lHi = Math.min(r.hi, 122);
@@ -744,9 +745,9 @@ function charNode(set, nl) {
744
745
  return { t: "char", set, nl };
745
746
  }
746
747
  function rangesToSet(ranges) {
747
- let fs2 = empty();
748
- for (const [lo, hi] of ranges) fs2 = union(fs2, fromRange(lo, hi));
749
- return fs2;
748
+ let fs3 = empty();
749
+ for (const [lo, hi] of ranges) fs3 = union(fs3, fromRange(lo, hi));
750
+ return fs3;
750
751
  }
751
752
  function rangesHaveNewline(ranges) {
752
753
  return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
@@ -952,17 +953,17 @@ function firstSet(n) {
952
953
  case "rep":
953
954
  return firstSet(n.node);
954
955
  case "alt": {
955
- let fs2 = empty();
956
- for (const arm of n.arms) fs2 = union(fs2, firstSet(arm));
957
- return fs2;
956
+ let fs3 = empty();
957
+ for (const arm of n.arms) fs3 = union(fs3, firstSet(arm));
958
+ return fs3;
958
959
  }
959
960
  case "seq": {
960
- let fs2 = empty();
961
+ let fs3 = empty();
961
962
  for (const part of n.parts) {
962
- fs2 = union(fs2, firstSet(part));
963
+ fs3 = union(fs3, firstSet(part));
963
964
  if (!nullable(part)) break;
964
965
  }
965
- return fs2;
966
+ return fs3;
966
967
  }
967
968
  }
968
969
  }
@@ -1491,8 +1492,8 @@ function classifyTriviaArm(arm) {
1491
1492
  }
1492
1493
  const lc = /^(\\?.)\[\^\\n\\r\]\*$/.exec(arm);
1493
1494
  if (lc) {
1494
- const marker = lc[1];
1495
- return { kind: "untilLineBreak", code: (marker.length === 2 ? marker[1] : marker[0]).charCodeAt(0) };
1495
+ const marker2 = lc[1];
1496
+ return { kind: "untilLineBreak", code: (marker2.length === 2 ? marker2[1] : marker2[0]).charCodeAt(0) };
1496
1497
  }
1497
1498
  return null;
1498
1499
  }
@@ -1637,12 +1638,12 @@ function orSentinel(a, b) {
1637
1638
  }
1638
1639
  };
1639
1640
  }
1640
- function firstSetSentinel(fs2) {
1641
- if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
1642
- const ranges = fs2.ranges;
1641
+ function firstSetSentinel(fs3) {
1642
+ if (fs3.kind !== "ranges" || fs3.ranges.length === 0) return null;
1643
+ const ranges = fs3.ranges;
1643
1644
  return {
1644
1645
  _tag: "firstSetSentinel",
1645
- _meta: { firstSet: fs2, canMatchNewline: false, isTrivia: false },
1646
+ _meta: { firstSet: fs3, canMatchNewline: false, isTrivia: false },
1646
1647
  _def: { tag: "unknown" },
1647
1648
  parse(input, pos) {
1648
1649
  const code = pos < input.length ? input.codePointAt(pos) : void 0;
@@ -1684,11 +1685,11 @@ function sequence(...parsers) {
1684
1685
  let followSentinels;
1685
1686
  function parseTolerant(input, pos, ctx) {
1686
1687
  followSentinels ??= parsers.map((_, i) => {
1687
- const fs2 = parsers.slice(i + 1).reduce(
1688
+ const fs3 = parsers.slice(i + 1).reduce(
1688
1689
  (acc, p) => union(acc, firstSetOf(p)),
1689
1690
  { kind: "empty" }
1690
1691
  );
1691
- return firstSetSentinel(fs2);
1692
+ return firstSetSentinel(fs3);
1692
1693
  });
1693
1694
  const values = def.valueUnused ? void 0 : [];
1694
1695
  let cur = pos;
@@ -1831,8 +1832,11 @@ function derive(c, seen) {
1831
1832
  case "not":
1832
1833
  case "peek":
1833
1834
  return deriveExpected2(def.parser);
1835
+ // A bare routed() can only fail as "there is no routed token here". With a
1836
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1837
+ // ones — the routed path either succeeds or is not taken.
1834
1838
  case "routed":
1835
- return ["routed()"];
1839
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1836
1840
  case "lazy": {
1837
1841
  const name = c._ruleName;
1838
1842
  if (seen.has(c)) return name ? [name] : [];
@@ -2019,7 +2023,7 @@ function detectStrategy(parsers) {
2019
2023
  function bareLeadingTermKey(term) {
2020
2024
  const d = term._def;
2021
2025
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
2022
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
2026
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
2023
2027
  return null;
2024
2028
  }
2025
2029
  function detectSharedPrefix(parsers) {
@@ -2135,10 +2139,10 @@ function codesToFirstSet(codes) {
2135
2139
  ranges.push({ lo, hi });
2136
2140
  return { kind: "ranges", ranges };
2137
2141
  }
2138
- function inFirstSet(code, fs2) {
2139
- if (fs2.kind === "any") return true;
2140
- if (fs2.kind === "empty") return false;
2141
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
2142
+ function inFirstSet(code, fs3) {
2143
+ if (fs3.kind === "any") return true;
2144
+ if (fs3.kind === "empty") return false;
2145
+ for (const r of fs3.ranges) if (code >= r.lo && code <= r.hi) return true;
2142
2146
  return false;
2143
2147
  }
2144
2148
  function areDisjoint(sets) {
@@ -2151,9 +2155,9 @@ function areDisjoint(sets) {
2151
2155
  function buildAsciiDispatch(parsers) {
2152
2156
  const table = Array(128).fill(-1);
2153
2157
  for (let i = 0; i < parsers.length; i++) {
2154
- const fs2 = parsers[i]._meta.firstSet;
2155
- if (fs2.kind !== "ranges") continue;
2156
- for (const { lo, hi } of fs2.ranges) {
2158
+ const fs3 = parsers[i]._meta.firstSet;
2159
+ if (fs3.kind !== "ranges") continue;
2160
+ for (const { lo, hi } of fs3.ranges) {
2157
2161
  for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
2158
2162
  table[code] = i;
2159
2163
  }
@@ -2274,17 +2278,21 @@ function when(key, parser2, opts = {}) {
2274
2278
  function otherwise(parser2) {
2275
2279
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2276
2280
  }
2277
- function routed() {
2281
+ function routed(fallback) {
2278
2282
  return {
2279
2283
  _tag: "routed",
2280
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2281
- _def: { tag: "routed" },
2284
+ _meta: {
2285
+ // The routed token is whatever the selector matched, so the first set stays
2286
+ // `any` even with a fallback — `any` already subsumes the fallback's.
2287
+ firstSet: { kind: "any" },
2288
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
2289
+ isTrivia: false
2290
+ },
2291
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2282
2292
  parse(_input, pos, ctx) {
2283
2293
  const item = ctx._routed;
2284
- if (item === void 0) {
2285
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2286
- }
2287
- if (pos !== item.span.start) {
2294
+ if (item === void 0 || pos !== item.span.start) {
2295
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2288
2296
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2289
2297
  }
2290
2298
  if (cstCaptureActive(ctx)) {
@@ -2791,12 +2799,12 @@ function markUnusedValues(root) {
2791
2799
  }
2792
2800
  };
2793
2801
  const resolveRoot = (c) => {
2794
- const guard2 = /* @__PURE__ */ new Set();
2802
+ const guard = /* @__PURE__ */ new Set();
2795
2803
  let cur = c;
2796
- while (!guard2.has(cur)) {
2804
+ while (!guard.has(cur)) {
2797
2805
  const d = cur._def;
2798
2806
  if (d.tag !== "lazy") return cur;
2799
- guard2.add(cur);
2807
+ guard.add(cur);
2800
2808
  try {
2801
2809
  cur = d.thunk();
2802
2810
  } catch {
@@ -2938,10 +2946,11 @@ function childrenOf(def) {
2938
2946
  return [def.main, def.skipped];
2939
2947
  case "scanTo":
2940
2948
  return [def.sentinel, ...def.skip];
2949
+ case "routed":
2950
+ return def.fallback ? [def.fallback] : [];
2941
2951
  case "literal":
2942
2952
  case "regex":
2943
2953
  case "keywords":
2944
- case "routed":
2945
2954
  case "guard":
2946
2955
  case "lazy":
2947
2956
  case "unknown":
@@ -3164,54 +3173,276 @@ function peek(combinator) {
3164
3173
  };
3165
3174
  }
3166
3175
 
3176
+ // src/compiler/degradation.ts
3177
+ function formatDegradation(d) {
3178
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
3179
+ }
3180
+ var DETAIL_CAP = 8;
3181
+ function formatDegradations(list) {
3182
+ const byCode = /* @__PURE__ */ new Map();
3183
+ for (const d of list) {
3184
+ const bucket = byCode.get(d.code);
3185
+ if (bucket) bucket.push(d);
3186
+ else byCode.set(d.code, [d]);
3187
+ }
3188
+ const out = [];
3189
+ for (const [code, items] of byCode) {
3190
+ for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
3191
+ if (items.length > DETAIL_CAP) {
3192
+ out.push(
3193
+ `[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.`
3194
+ );
3195
+ }
3196
+ }
3197
+ return out;
3198
+ }
3199
+ var _sinks = [];
3200
+ function beginDegradationCapture() {
3201
+ _sinks.push(/* @__PURE__ */ new Map());
3202
+ }
3203
+ function endDegradationCapture() {
3204
+ const sink = _sinks.pop();
3205
+ return sink ? [...sink.values()] : [];
3206
+ }
3207
+ function degradationCaptureDepth() {
3208
+ return _sinks.length;
3209
+ }
3210
+ function unwindDegradationCapture(depth) {
3211
+ const found = [];
3212
+ while (_sinks.length > depth) {
3213
+ const sink = _sinks.pop();
3214
+ found.unshift(...sink.values());
3215
+ }
3216
+ return found;
3217
+ }
3218
+ function resolveDegradationLevel(explicit) {
3219
+ if (explicit !== void 0) return explicit;
3220
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
3221
+ if (env === "off" || env === "warn" || env === "error") return env;
3222
+ return "warn";
3223
+ }
3224
+ function degradationKey(d) {
3225
+ return JSON.stringify([d.code, d.where, d.subject]);
3226
+ }
3227
+ function recordDegradation(d) {
3228
+ const level = resolveDegradationLevel();
3229
+ if (level === "off") return;
3230
+ const key = degradationKey(d);
3231
+ const sink = _sinks[_sinks.length - 1];
3232
+ if (sink) {
3233
+ if (!sink.has(key)) sink.set(key, d);
3234
+ return;
3235
+ }
3236
+ if (_immediate.has(key)) return;
3237
+ _immediate.add(key);
3238
+ if (level === "error") throw new Error(`parseman: degraded compilation path
3239
+ ${formatDegradation(d)}`);
3240
+ console.warn(formatDegradation(d));
3241
+ }
3242
+ var _immediate = /* @__PURE__ */ new Set();
3243
+ function beginCompileDegradationDrain() {
3244
+ if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
3245
+ };
3246
+ const depth = degradationCaptureDepth();
3247
+ beginDegradationCapture();
3248
+ return (report) => {
3249
+ const found = unwindDegradationCapture(depth);
3250
+ if (!report || found.length === 0) return;
3251
+ const fresh = found.filter((d) => {
3252
+ const k = degradationKey(d);
3253
+ if (_immediate.has(k)) return false;
3254
+ _immediate.add(k);
3255
+ return true;
3256
+ });
3257
+ if (fresh.length === 0) return;
3258
+ const lines = formatDegradations(fresh);
3259
+ if (resolveDegradationLevel() === "error") {
3260
+ throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
3261
+ ${lines.join("\n")}`);
3262
+ }
3263
+ for (const l of lines) console.warn(l);
3264
+ };
3265
+ }
3266
+
3167
3267
  // src/compiler/build-arity.ts
3168
3268
  function cstOutputHost(build) {
3169
3269
  return build?._parsemanCstOutput === true;
3170
3270
  }
3171
- var PARAM_LIST_RE = (
3172
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
3173
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
3174
- );
3175
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
3271
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
3272
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
3273
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
3274
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
3176
3275
  function confirmedBuildArity(src) {
3177
3276
  const s = src.trim();
3178
- const m = PARAM_LIST_RE.exec(s);
3179
- if (!m) return null;
3180
- if (m[3] !== void 0) return 1;
3181
- const inner = (m[1] ?? m[2] ?? "").trim();
3182
- if (inner === "") return 0;
3183
- const parts = inner.split(",");
3277
+ if (NATIVE_CODE_RE.test(s)) return null;
3278
+ if (/\barguments\b/.test(s)) return null;
3279
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
3280
+ const inner = paramListText(s);
3281
+ if (inner === null) return null;
3282
+ if (inner.trim() === "") return 0;
3283
+ const parts = splitTopLevel(inner);
3284
+ if (parts === null) return null;
3184
3285
  for (const part of parts) {
3185
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
3286
+ if (!isConfirmableParam(part.trim())) return null;
3186
3287
  }
3187
- if (/\barguments\b/.test(s)) return null;
3188
3288
  return parts.length;
3189
3289
  }
3290
+ function paramListText(s) {
3291
+ const open = PARAM_LIST_OPEN_RE.exec(s);
3292
+ if (!open) return null;
3293
+ const start = open[0].length;
3294
+ const end = matchingParen(s, start);
3295
+ if (end === null) return null;
3296
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
3297
+ return s.slice(start, end);
3298
+ }
3299
+ function matchingParen(s, start) {
3300
+ let depth = 0;
3301
+ for (let i = start; i < s.length; i++) {
3302
+ const c = s[i];
3303
+ if (c === '"' || c === "'" || c === "`") {
3304
+ const close = skipString(s, i);
3305
+ if (close === null) return null;
3306
+ i = close;
3307
+ continue;
3308
+ }
3309
+ if (c === "(" || c === "[" || c === "{") depth++;
3310
+ else if (c === "]" || c === "}") {
3311
+ if (--depth < 0) return null;
3312
+ } else if (c === ")") {
3313
+ if (depth === 0) return i;
3314
+ depth--;
3315
+ }
3316
+ }
3317
+ return null;
3318
+ }
3319
+ function skipString(s, open) {
3320
+ const quote = s[open];
3321
+ for (let i = open + 1; i < s.length; i++) {
3322
+ if (s[i] === "\\") {
3323
+ i++;
3324
+ continue;
3325
+ }
3326
+ if (s[i] === quote) return i;
3327
+ }
3328
+ return null;
3329
+ }
3330
+ function splitTopLevel(inner) {
3331
+ const parts = [];
3332
+ let depth = 0;
3333
+ let angle = 0;
3334
+ let last = 0;
3335
+ for (let i = 0; i < inner.length; i++) {
3336
+ const c = inner[i];
3337
+ if (c === "=" && inner[i + 1] === ">") {
3338
+ i++;
3339
+ continue;
3340
+ }
3341
+ if (c === '"' || c === "'" || c === "`") {
3342
+ const close = skipString(inner, i);
3343
+ if (close === null) return null;
3344
+ i = close;
3345
+ continue;
3346
+ }
3347
+ if (c === "(" || c === "[" || c === "{") depth++;
3348
+ else if (c === ")" || c === "]" || c === "}") {
3349
+ if (--depth < 0) return null;
3350
+ } else if (c === "<") angle++;
3351
+ else if (c === ">") {
3352
+ if (--angle < 0) return null;
3353
+ } else if (c === "," && depth === 0 && angle === 0) {
3354
+ parts.push(inner.slice(last, i));
3355
+ last = i + 1;
3356
+ }
3357
+ }
3358
+ if (depth !== 0 || angle !== 0) return null;
3359
+ parts.push(inner.slice(last));
3360
+ return parts;
3361
+ }
3362
+ function isConfirmableParam(part) {
3363
+ const head = PARAM_HEAD_RE.exec(part);
3364
+ if (!head) return false;
3365
+ const rest = part.slice(head[0].length);
3366
+ if (rest === "") return true;
3367
+ if (rest[0] !== ":") return false;
3368
+ return !hasDefaultAssign(rest.slice(1));
3369
+ }
3370
+ function hasDefaultAssign(annotation) {
3371
+ for (let i = 0; i < annotation.length; i++) {
3372
+ if (annotation[i] !== "=") continue;
3373
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
3374
+ i++;
3375
+ continue;
3376
+ }
3377
+ const prev = annotation[i - 1];
3378
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
3379
+ return true;
3380
+ }
3381
+ return false;
3382
+ }
3383
+ function buildAnalysisSrc(def) {
3384
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
3385
+ }
3386
+ var RESOLVER_REASONS = {
3387
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
3388
+ "arguments": "its body references `arguments`",
3389
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
3390
+ "not-a-function": "the binding it names is not a function declaration",
3391
+ "unresolved-import": "its module could not be resolved or parsed",
3392
+ "not-found": "the name did not resolve to any binding",
3393
+ "computed": "it is a computed expression",
3394
+ "foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
3395
+ "ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
3396
+ };
3397
+ function unconfirmableReason(def, src) {
3398
+ const declared = def.buildArityUnresolved;
3399
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
3400
+ const s = src.trim();
3401
+ const params = s.slice(0, s.indexOf(")") + 1);
3402
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
3403
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
3404
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
3405
+ return "it names a binding this build could not resolve to a function declaration";
3406
+ }
3407
+ return "its parameter list could not be parsed";
3408
+ }
3409
+ function confirmedArityForDef(def) {
3410
+ if (def.buildArity !== void 0) return def.buildArity;
3411
+ const src = buildAnalysisSrc(def);
3412
+ const arity = confirmedBuildArity(src);
3413
+ if (arity !== null) return arity;
3414
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
3415
+ recordDegradation({
3416
+ code: "build-arity-unconfirmed",
3417
+ severity: "warn",
3418
+ where: `node("${def.type ?? "<inferred>"}")`,
3419
+ subject,
3420
+ 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`,
3421
+ 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"
3422
+ });
3423
+ return null;
3424
+ }
3190
3425
  function buildReadsChildren(def) {
3191
3426
  if (!def.build) return true;
3192
- const src = def.buildSrc ?? def.build.toString();
3193
- const arity = confirmedBuildArity(src);
3427
+ const arity = confirmedArityForDef(def);
3194
3428
  if (arity === null) return true;
3195
3429
  return arity >= 1;
3196
3430
  }
3197
3431
  function buildReadsRaw(def) {
3198
3432
  if (!def.build) return true;
3199
- const src = def.buildSrc ?? def.build.toString();
3200
- const arity = confirmedBuildArity(src);
3433
+ const arity = confirmedArityForDef(def);
3201
3434
  if (arity === null) return true;
3202
3435
  return arity >= 4;
3203
3436
  }
3204
3437
  function buildReadsTrivia(def) {
3205
3438
  if (!def.build) return true;
3206
- const src = def.buildSrc ?? def.build.toString();
3207
- const arity = confirmedBuildArity(src);
3439
+ const arity = confirmedArityForDef(def);
3208
3440
  if (arity === null) return true;
3209
3441
  return arity >= 5;
3210
3442
  }
3211
3443
  function buildReadsState(def) {
3212
3444
  if (!def.build) return true;
3213
- const src = def.buildSrc ?? def.build.toString();
3214
- const arity = confirmedBuildArity(src);
3445
+ const arity = confirmedArityForDef(def);
3215
3446
  if (arity === null) return true;
3216
3447
  return arity >= 6;
3217
3448
  }
@@ -3219,8 +3450,7 @@ function buildReadsState(def) {
3219
3450
  // src/compiler/fields.ts
3220
3451
  function buildReadsFields(def) {
3221
3452
  if (!def.build) return true;
3222
- const src = def.buildSrc ?? def.build.toString();
3223
- const arity = confirmedBuildArity(src);
3453
+ const arity = confirmedArityForDef(def);
3224
3454
  if (arity === null) return true;
3225
3455
  return arity >= 2;
3226
3456
  }
@@ -3401,6 +3631,13 @@ function isCstChild(value) {
3401
3631
  function missingInferredType() {
3402
3632
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3403
3633
  }
3634
+ function normalizeBuildArity(arity) {
3635
+ if (arity === void 0) return void 0;
3636
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3637
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3638
+ }
3639
+ return arity;
3640
+ }
3404
3641
  function normalizeProject(project) {
3405
3642
  if (project === void 0) return void 0;
3406
3643
  if (!Number.isInteger(project) || project < 0) {
@@ -3436,13 +3673,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3436
3673
  const captureTrivia = opts?.captureTrivia === true;
3437
3674
  const trailingTrivia = opts?.trailingTrivia === true;
3438
3675
  const tags = opts?.tags;
3676
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3439
3677
  if (unwrap && collapse) {
3440
3678
  throw new Error("node() options cannot set both unwrap and collapse");
3441
3679
  }
3442
3680
  if (project !== void 0 && (unwrap || collapse)) {
3443
3681
  throw new Error("node() options cannot combine project with unwrap or collapse");
3444
3682
  }
3445
- 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;
3683
+ 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;
3446
3684
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3447
3685
  const clonesState = build ? buildReadsState(def) : project === void 0;
3448
3686
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3488,7 +3726,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3488
3726
  const cstOutput = cstOutputHost(ctx.build);
3489
3727
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3490
3728
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3491
- 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 };
3729
+ 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 };
3492
3730
  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 };
3493
3731
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3494
3732
  pushCstChild(ctx, built, rawEntry);
@@ -3683,7 +3921,7 @@ function leaf(root, fn) {
3683
3921
  }
3684
3922
 
3685
3923
  // src/version.ts
3686
- var PARSEMAN_VERSION = "0.44.0";
3924
+ var PARSEMAN_VERSION = "0.46.0";
3687
3925
 
3688
3926
  // src/cst/host-mode.ts
3689
3927
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3703,346 +3941,11 @@ function assertHostModeCompatible(mode, build, hostBranchElided = true) {
3703
3941
  }
3704
3942
 
3705
3943
  // src/analysis/gating.ts
3706
- var isAny = (fs2) => fs2.kind === "any";
3707
- function intersects2(a, b) {
3708
- if (a.kind === "any" || b.kind === "any") return true;
3709
- if (a.kind === "empty" || b.kind === "empty") return false;
3710
- for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
3711
- return false;
3712
- }
3713
- function intersection(a, b) {
3714
- if (a.kind === "any") return b;
3715
- if (b.kind === "any") return a;
3716
- if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
3717
- const ranges = [];
3718
- for (const ra of a.ranges) for (const rb of b.ranges) {
3719
- const lo = Math.max(ra.lo, rb.lo);
3720
- const hi = Math.min(ra.hi, rb.hi);
3721
- if (lo <= hi) ranges.push({ lo, hi });
3722
- }
3723
- return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
3724
- }
3725
- function combine(sets) {
3726
- let acc = { kind: "empty" };
3727
- for (const s of sets) {
3728
- if (s.kind === "any") return { kind: "any" };
3729
- if (s.kind === "empty") continue;
3730
- acc = acc.kind === "ranges" ? { kind: "ranges", ranges: [...acc.ranges, ...s.ranges] } : s;
3731
- }
3732
- return acc;
3733
- }
3734
3944
  var ch = (c) => c >= 32 && c < 127 ? `'${String.fromCharCode(c)}'` : `\\u${c}`;
3735
- function firstSetToString(fs2) {
3736
- if (fs2.kind === "any") return "ANY";
3737
- if (fs2.kind === "empty") return "(empty)";
3738
- return fs2.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
3739
- }
3740
- var ruleNameOf2 = (p) => p._ruleName;
3741
- var SUGGESTIONS = {
3742
- "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.",
3743
- "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.",
3744
- "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.",
3745
- "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.",
3746
- "opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
3747
- "ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
3748
- };
3749
- function classifyBroadArm(arm, resolve2) {
3750
- const seen = /* @__PURE__ */ new Set();
3751
- const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
3752
- const walk = (p) => {
3753
- if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
3754
- seen.add(p);
3755
- const d = p._def;
3756
- switch (d.tag) {
3757
- case "literal":
3758
- case "regex":
3759
- case "keywords":
3760
- return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
3761
- case "not":
3762
- return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
3763
- // peek() CARRIES its body's first-set, so it is only broad when the body is
3764
- // (or the body is nullable) — walk in for the real cause.
3765
- case "peek":
3766
- return walk(d.parser);
3767
- case "scanTo":
3768
- return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
3769
- // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
3770
- // its own — it is the poison. `withCtx`/`recover` FORWARD their inner
3771
- // first-set (see firstSetOf), so a broad result comes from the inner parser:
3772
- // walk into it for a precise cause.
3773
- case "guard":
3774
- return { cause: "opaque-wrapper", detail: `opaque wrapper (${d.tag})` };
3775
- // attempt/withCtx/recover FORWARD their inner first-set (transparent for
3776
- // dispatch), so a broad result comes from the inner parser — walk into it.
3777
- case "attempt":
3778
- case "withCtx":
3779
- case "recover":
3780
- return walk(d.parser);
3781
- case "lazy": {
3782
- const name = p._ruleName;
3783
- let target;
3784
- try {
3785
- target = d.thunk();
3786
- } catch {
3787
- const bound = name !== void 0 ? resolve2?.(name) : void 0;
3788
- if (bound === void 0)
3789
- return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
3790
- target = bound;
3791
- }
3792
- const inner = walk(target);
3793
- if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
3794
- return inner;
3795
- }
3796
- case "optional":
3797
- case "many": {
3798
- const inner = walk(d.parser);
3799
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3800
- }
3801
- case "oneOrMore":
3802
- case "transform":
3803
- case "label":
3804
- case "field":
3805
- case "trivia":
3806
- case "token":
3807
- case "leaf":
3808
- case "node":
3809
- case "grammar":
3810
- case "expect":
3811
- return walk(d.parser);
3812
- case "skip":
3813
- return walk(d.main);
3814
- case "sequence": {
3815
- let sawNullablePrefix = false;
3816
- for (const t of d.parsers) {
3817
- if (t._def.tag === "not") {
3818
- sawNullablePrefix = true;
3819
- continue;
3820
- }
3821
- if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
3822
- const inner = walk(t);
3823
- if (sawNullablePrefix)
3824
- return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3825
- return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
3826
- }
3827
- if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
3828
- sawNullablePrefix = true;
3829
- continue;
3830
- }
3831
- return null;
3832
- }
3833
- return { cause: "broad-recognizer", detail: "sequence of only nullable/zero-width terms" };
3834
- }
3835
- case "choice": {
3836
- for (const a of d.parsers) {
3837
- const r = walk(a);
3838
- if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
3839
- }
3840
- return null;
3841
- }
3842
- case "dispatch":
3843
- return walk(d.selector);
3844
- default:
3845
- return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3846
- }
3847
- };
3848
- return walk(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
3849
- }
3850
- function peelToLeading(arm) {
3851
- let d = arm._def;
3852
- let cur = arm;
3853
- for (; ; ) {
3854
- if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label" || d.tag === "attempt") {
3855
- cur = d.parser;
3856
- d = cur._def;
3857
- continue;
3858
- }
3859
- break;
3860
- }
3861
- if (d.tag === "sequence" && d.parsers.length >= 1) return d.parsers[0];
3862
- return cur;
3863
- }
3864
- var KEYWORD_REGEX_RE = /^\^?[@#.-]?[A-Za-z][\w-]*(\(\?![^)]*\))?\$?$/;
3865
- function detectAntiPatterns(rule, arms) {
3866
- const out = [];
3867
- arms.forEach((arm, i) => {
3868
- const lead = peelToLeading(arm);
3869
- const ld = lead._def;
3870
- if (ld.tag === "not") {
3871
- const inner = ld.parser;
3872
- if (inner._def.tag === "not") {
3873
- out.push({
3874
- kind: "double-not",
3875
- rule,
3876
- armIndex: i,
3877
- 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."
3878
- });
3879
- } else {
3880
- out.push({
3881
- kind: "leading-not",
3882
- rule,
3883
- armIndex: i,
3884
- 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."
3885
- });
3886
- }
3887
- }
3888
- if (ld.tag === "regex" && KEYWORD_REGEX_RE.test(ld.source)) {
3889
- out.push({
3890
- kind: "keyword-regex",
3891
- rule,
3892
- armIndex: i,
3893
- 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).`
3894
- });
3895
- }
3896
- });
3897
- return out;
3898
- }
3899
- function analyzeGating(entry, opts) {
3900
- return analyzeGatingRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3901
- }
3902
- function analyzeGatingRules(ruleMap, opts) {
3903
- const raw = [];
3904
- const antiPatterns = [];
3905
- const unanalysable = [];
3906
- const seenUnanalysable = /* @__PURE__ */ new Set();
3907
- const visited = /* @__PURE__ */ new Set();
3908
- const noteUnanalysable = (u) => {
3909
- const key = `${u.rule}\0${u.kind}`;
3910
- if (seenUnanalysable.has(key)) return;
3911
- seenUnanalysable.add(key);
3912
- unanalysable.push(u);
3913
- };
3914
- const visit = (p, enclosingRule) => {
3915
- if (visited.has(p)) return;
3916
- visited.add(p);
3917
- const def = p?._def;
3918
- if (def === null || typeof def !== "object") {
3919
- noteUnanalysable(
3920
- typeof p === "function" ? {
3921
- rule: enclosingRule,
3922
- kind: "fused-rule",
3923
- 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."
3924
- } : {
3925
- rule: enclosingRule,
3926
- kind: "not-a-combinator",
3927
- reason: `value of type ${p === null ? "null" : typeof p} is not a combinator and carries no _def descriptor.`
3928
- }
3929
- );
3930
- return;
3931
- }
3932
- const d = def;
3933
- const rule = ruleNameOf2(p) ?? enclosingRule;
3934
- if (d.tag === "choice") {
3935
- raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3936
- antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
3937
- }
3938
- const rec = d;
3939
- const kids = [];
3940
- if (Array.isArray(rec.parsers)) kids.push(...rec.parsers);
3941
- for (const k of ["parser", "main", "skipped", "separator", "sentinel"])
3942
- if (rec[k]) kids.push(rec[k]);
3943
- if (d.tag === "dispatch") {
3944
- kids.push(d.selector);
3945
- for (const c of d.cases) kids.push(c.parser);
3946
- if (d.matchers) for (const c of d.matchers) kids.push(c.parser);
3947
- if (d.otherwise !== void 0) kids.push(d.otherwise);
3948
- }
3949
- if (Array.isArray(rec.skip)) kids.push(...rec.skip);
3950
- if (d.tag === "lazy") {
3951
- try {
3952
- kids.push(d.thunk());
3953
- } catch {
3954
- }
3955
- }
3956
- for (const k of kids) visit(k, rule);
3957
- };
3958
- for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name);
3959
- const perRule = /* @__PURE__ */ new Map();
3960
- for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3961
- const seenInRule = /* @__PURE__ */ new Map();
3962
- const accept = new Set(opts?.accept ?? []);
3963
- const usedAccept = /* @__PURE__ */ new Set();
3964
- const choices = raw.map(({ g, rule }) => {
3965
- const n = seenInRule.get(rule) ?? 0;
3966
- seenInRule.set(rule, n + 1);
3967
- const id = (perRule.get(rule) ?? 1) === 1 ? rule : `${rule}#${n}`;
3968
- const isAccepted = g.gates === "no" && accept.has(id);
3969
- if (isAccepted) usedAccept.add(id);
3970
- return { ...g, id, accepted: isAccepted };
3971
- });
3972
- const gated = choices.filter((c) => c.gates === "yes").length;
3973
- const recoverable = choices.filter((c) => c.gates === "recoverable").length;
3974
- const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
3975
- const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
3976
- const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
3977
- const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
3978
- return {
3979
- totalChoices: choices.length,
3980
- gated,
3981
- recoverable,
3982
- unanalysable,
3983
- ungated,
3984
- accepted,
3985
- deferred,
3986
- acceptedUnused,
3987
- choices,
3988
- antiPatterns
3989
- };
3990
- }
3991
- function analyzeChoice(p, d, rule, resolve2) {
3992
- const arms = d.parsers;
3993
- const shallow = arms.map((a) => a._meta.firstSet);
3994
- const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
3995
- const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
3996
- const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
3997
- return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
3998
- });
3999
- const overlaps = [];
4000
- for (let i = 0; i < deep.length; i++)
4001
- for (let j = i + 1; j < deep.length; j++)
4002
- if (!isAny(deep[i]) && !isAny(deep[j]) && intersects2(deep[i], deep[j]))
4003
- overlaps.push({
4004
- a: i,
4005
- b: j,
4006
- on: intersection(deep[i], deep[j]),
4007
- 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."
4008
- });
4009
- let gates;
4010
- if (d.disjoint) gates = "yes";
4011
- else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
4012
- else gates = "no";
4013
- const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
4014
- return {
4015
- rule,
4016
- strategy: d.strategy?.tag ?? "firstMatch",
4017
- gates,
4018
- deferred,
4019
- combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
4020
- anyArms,
4021
- overlaps
4022
- };
4023
- }
4024
- function formatGatingWarnings(report) {
4025
- const lines = [];
4026
- if (report.unanalysable.length > 0) {
4027
- lines.push(
4028
- `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.`
4029
- );
4030
- for (const u of report.unanalysable)
4031
- lines.push(` \xB7 ${u.rule} [${u.kind}]: ${u.reason}`);
4032
- }
4033
- for (const c of report.ungated) {
4034
- lines.push(`parseman gating: choice @ ${c.id} is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms.`);
4035
- for (const a of c.anyArms)
4036
- lines.push(` \xB7 arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
4037
- fix: ${a.suggestion}`);
4038
- for (const o of c.overlaps)
4039
- lines.push(` \xB7 arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
4040
- fix: ${o.suggestion}`);
4041
- lines.push(` (intentional? accept it in the gating snapshot: { accept: ['${c.id}'] }.)`);
4042
- }
4043
- for (const ap of report.antiPatterns)
4044
- lines.push(`parseman anti-pattern [${ap.kind}] @ ${ap.rule} arm[${ap.armIndex}]: ${ap.message}`);
4045
- return lines;
3945
+ function firstSetToString(fs3) {
3946
+ if (fs3.kind === "any") return "ANY";
3947
+ if (fs3.kind === "empty") return "(empty)";
3948
+ return fs3.ranges.map((r) => r.lo === r.hi ? ch(r.lo) : `${ch(r.lo)}-${ch(r.hi)}`).join(",");
4046
3949
  }
4047
3950
 
4048
3951
  // src/analysis/duplication.ts
@@ -4085,7 +3988,7 @@ function childrenOf2(d) {
4085
3988
  }
4086
3989
  }
4087
3990
  }
4088
- var ruleNameOf3 = (p) => p._ruleName;
3991
+ var ruleNameOf2 = (p) => p._ruleName;
4089
3992
  var fnKey = (src, fn) => src ?? (typeof fn === "function" ? String(fn) : "");
4090
3993
  function payloadKey(p, d) {
4091
3994
  switch (d.tag) {
@@ -4096,7 +3999,7 @@ function payloadKey(p, d) {
4096
3999
  case "keywords":
4097
4000
  return `keywords\0${[...d.words].join("")}\0${d.caseInsensitive}\0${d.boundary ?? ""}`;
4098
4001
  case "lazy": {
4099
- const n = ruleNameOf3(p);
4002
+ const n = ruleNameOf2(p);
4100
4003
  return n === void 0 ? `ref\0#${anonId(p)}` : `ref\0${n}`;
4101
4004
  }
4102
4005
  case "label":
@@ -4165,7 +4068,7 @@ var Interner = class {
4165
4068
  var MAX_RENDER = 160;
4166
4069
  function render(p, depth = 3) {
4167
4070
  const d = p._def;
4168
- const name = ruleNameOf3(p);
4071
+ const name = ruleNameOf2(p);
4169
4072
  switch (d.tag) {
4170
4073
  case "literal":
4171
4074
  return `literal('${d.value}')`;
@@ -4353,7 +4256,7 @@ function keywordAlternationHazards(words, boundary) {
4353
4256
  return out;
4354
4257
  }
4355
4258
  function analyzeDuplication(entry, opts) {
4356
- return analyzeDuplicationRules([[ruleNameOf3(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
4259
+ return analyzeDuplicationRules([[ruleNameOf2(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
4357
4260
  }
4358
4261
  function assertAnalyzable(ruleMap) {
4359
4262
  for (const [name, rule] of ruleMap) {
@@ -4391,11 +4294,11 @@ function analyzeDuplicationRules(ruleMap, opts) {
4391
4294
  return n;
4392
4295
  };
4393
4296
  const visited = /* @__PURE__ */ new Map();
4394
- const visit = (p, rule, path2, enclosingNode, enclosingReducer) => {
4297
+ const visit = (p, rule, path3, enclosingNode, enclosingReducer) => {
4395
4298
  if (visited.has(p)) return;
4396
4299
  const d = p._def;
4397
- const own = ruleNameOf3(p);
4398
- const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path2 };
4300
+ const own = ruleNameOf2(p);
4301
+ const here = own !== void 0 && own !== rule ? { rule: own, path: "" } : { rule, path: path3 };
4399
4302
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
4400
4303
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
4401
4304
  const innerReducer = enclosingReducer || d.tag === "node" && (d.build !== void 0 || d.buildSrc !== void 0) || d.tag === "transform" || d.tag === "leaf";
@@ -4407,12 +4310,12 @@ function analyzeDuplicationRules(ruleMap, opts) {
4407
4310
  if (d.tag === "lazy") {
4408
4311
  try {
4409
4312
  const target = d.thunk();
4410
- visit(target, ruleNameOf3(target) ?? ruleNameOf3(p) ?? here.rule, "", null, false);
4313
+ visit(target, ruleNameOf2(target) ?? ruleNameOf2(p) ?? here.rule, "", null, false);
4411
4314
  } catch {
4412
4315
  }
4413
4316
  }
4414
4317
  };
4415
- for (const [name, root] of ruleMap) visit(root, ruleNameOf3(root) ?? name, "", null, false);
4318
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf2(root) ?? name, "", null, false);
4416
4319
  const accept = new Set(opts?.accept ?? []);
4417
4320
  const used = /* @__PURE__ */ new Set();
4418
4321
  const keep = (f) => {
@@ -4429,10 +4332,10 @@ function analyzeDuplicationRules(ruleMap, opts) {
4429
4332
  const divergentNodes = findDivergentNodes(visited, shapeOf);
4430
4333
  const structureLoss = findStructureLoss(visited, opts?.resolveRef);
4431
4334
  const keywordRegexes = findKeywordRegexes(visited);
4432
- const subsume = (fs2) => {
4335
+ const subsume = (fs3) => {
4433
4336
  const covered = /* @__PURE__ */ new Set();
4434
4337
  const surviving = /* @__PURE__ */ new Set();
4435
- for (const f of [...fs2].sort((a, b) => b.savings - a.savings)) {
4338
+ for (const f of [...fs3].sort((a, b) => b.savings - a.savings)) {
4436
4339
  if (f._members.every((m) => covered.has(m))) continue;
4437
4340
  surviving.add(f.id);
4438
4341
  for (const m of f._members) markSubtree(m, covered);
@@ -4555,10 +4458,10 @@ function findNearDuplicates(visited, interner, shapeOf, minSize) {
4555
4458
  return out.sort((a, b) => b.savings - a.savings);
4556
4459
  }
4557
4460
  function findDivergentSlot(members, shapeOf) {
4558
- const path2 = [];
4461
+ const path3 = [];
4559
4462
  const defs = [];
4560
4463
  let cur = [...members];
4561
- for (let guard2 = 0; guard2 < 64; guard2++) {
4464
+ for (let guard = 0; guard < 64; guard++) {
4562
4465
  const d0 = cur[0]._def;
4563
4466
  const kidLists = cur.map((c) => childrenOf2(c._def));
4564
4467
  const arity = kidLists[0].length;
@@ -4570,14 +4473,14 @@ function findDivergentSlot(members, shapeOf) {
4570
4473
  }
4571
4474
  if (diff.length !== 1) break;
4572
4475
  const i = diff[0];
4573
- path2.push(i);
4476
+ path3.push(i);
4574
4477
  defs.push(d0);
4575
4478
  cur = kidLists.map((k) => k[i]);
4576
4479
  const nextArity = childrenOf2(cur[0]._def).length;
4577
4480
  const samePayload = cur.every((c) => payloadKey(c, c._def) === payloadKey(cur[0], cur[0]._def));
4578
4481
  if (!samePayload || nextArity === 0) break;
4579
4482
  }
4580
- if (path2.length === 0) return null;
4483
+ if (path3.length === 0) return null;
4581
4484
  let slotSize = 0;
4582
4485
  const sizeOf = (p) => {
4583
4486
  let n = 1;
@@ -4585,14 +4488,14 @@ function findDivergentSlot(members, shapeOf) {
4585
4488
  return n;
4586
4489
  };
4587
4490
  for (const c of cur) slotSize = Math.max(slotSize, sizeOf(c));
4588
- return { path: path2, defs, variants: cur, slotSize };
4491
+ return { path: path3, defs, variants: cur, slotSize };
4589
4492
  }
4590
- function renderWithHole(p, path2, depth = 0) {
4591
- if (depth === path2.length) return "\u2039slot\u203A";
4493
+ function renderWithHole(p, path3, depth = 0) {
4494
+ if (depth === path3.length) return "\u2039slot\u203A";
4592
4495
  const d = p._def;
4593
4496
  const kids = childrenOf2(d);
4594
- const idx = path2[depth];
4595
- const parts = kids.map((k, i) => i === idx ? renderWithHole(k, path2, depth + 1) : render(k, 2));
4497
+ const idx = path3[depth];
4498
+ const parts = kids.map((k, i) => i === idx ? renderWithHole(k, path3, depth + 1) : render(k, 2));
4596
4499
  switch (d.tag) {
4597
4500
  case "sequence":
4598
4501
  return `sequence(${parts.join(", ")})`;
@@ -4796,13 +4699,13 @@ function findRegexClasses(visited) {
4796
4699
  }
4797
4700
  return out.sort((a, b) => Number(b.drifted) - Number(a.drifted) || b.count - a.count);
4798
4701
  }
4799
- function intersects3(a, b) {
4702
+ function intersects2(a, b) {
4800
4703
  if (a.kind === "any" || b.kind === "any") return true;
4801
4704
  if (a.kind === "empty" || b.kind === "empty") return false;
4802
4705
  for (const ra of a.ranges) for (const rb of b.ranges) if (ra.lo <= rb.hi && rb.lo <= ra.hi) return true;
4803
4706
  return false;
4804
4707
  }
4805
- function intersection2(a, b) {
4708
+ function intersection(a, b) {
4806
4709
  if (a.kind === "any") return b;
4807
4710
  if (b.kind === "any") return a;
4808
4711
  if (a.kind === "empty" || b.kind === "empty") return { kind: "empty" };
@@ -4831,19 +4734,19 @@ function leadingRegex(arm) {
4831
4734
  const t = termsOf(arm)[0];
4832
4735
  return t !== void 0 && t._def.tag === "regex";
4833
4736
  }
4834
- function findOverlaps(visited, shapeOf, resolve2) {
4737
+ function findOverlaps(visited, shapeOf, resolve3) {
4835
4738
  const out = [];
4836
4739
  for (const v2 of visited.values()) {
4837
4740
  const d = v2.d;
4838
4741
  if (d.tag !== "choice") continue;
4839
4742
  const arms = d.parsers;
4840
- const fs2 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
4743
+ const fs3 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve3));
4841
4744
  const strategy = d.strategy;
4842
4745
  for (let i = 0; i < arms.length; i++) {
4843
4746
  for (let j = i + 1; j < arms.length; j++) {
4844
- const A = fs2[i], B = fs2[j];
4747
+ const A = fs3[i], B = fs3[j];
4845
4748
  if (A.kind === "any" || B.kind === "any") continue;
4846
- if (!intersects3(A, B)) continue;
4749
+ if (!intersects2(A, B)) continue;
4847
4750
  const ta = termsOf(arms[i]), tb = termsOf(arms[j]);
4848
4751
  let shared = 0;
4849
4752
  while (shared < ta.length && shared < tb.length && shapeOf(ta[shared]) === shapeOf(tb[shared])) shared++;
@@ -4855,7 +4758,7 @@ function findOverlaps(visited, shapeOf, resolve2) {
4855
4758
  site: v2.site,
4856
4759
  a: i,
4857
4760
  b: j,
4858
- on: intersection2(A, B),
4761
+ on: intersection(A, B),
4859
4762
  sharedLeadingTerms: shared,
4860
4763
  sharedPrefix: shared > 0 ? clamp(ta.slice(0, shared).map((t) => render(t, 2)).join(", ")) : null,
4861
4764
  handledByStrategy: handled,
@@ -5157,7 +5060,7 @@ function nestedNodeTypes(root, out, seen, isRoot) {
5157
5060
  for (const k of childrenOf2(d)) nestedNodeTypes(k, out, seen, false);
5158
5061
  }
5159
5062
  var MAX_LOST_TYPES = 8;
5160
- function findStructureLoss(visited, resolve2) {
5063
+ function findStructureLoss(visited, resolve3) {
5161
5064
  const nestedCache = /* @__PURE__ */ new Map();
5162
5065
  const nested = (p) => {
5163
5066
  const hit = nestedCache.get(p);
@@ -5174,18 +5077,18 @@ function findStructureLoss(visited, resolve2) {
5174
5077
  const arms = d.parsers;
5175
5078
  const resolved = arms.map(resolveArm);
5176
5079
  const nodeTypes = resolved.map(wrappingNodeType);
5177
- const fs2 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
5080
+ const fs3 = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve3));
5178
5081
  for (let i = 0; i < arms.length; i++) {
5179
5082
  if (d.gates[i] != null) continue;
5180
5083
  const type = nodeTypes[i] ?? null;
5181
5084
  if (type === null) continue;
5182
5085
  if (nested(resolved[i]).size !== 0) continue;
5183
- const A = fs2[i];
5086
+ const A = fs3[i];
5184
5087
  if (A.kind === "any") continue;
5185
5088
  for (let j = i + 1; j < arms.length; j++) {
5186
5089
  if (nodeTypes[j] !== type) continue;
5187
- const B = fs2[j];
5188
- if (B.kind === "any" || !intersects3(A, B)) continue;
5090
+ const B = fs3[j];
5091
+ if (B.kind === "any" || !intersects2(A, B)) continue;
5189
5092
  const lost = nested(resolved[j]);
5190
5093
  if (lost.size === 0) continue;
5191
5094
  const lostNodeTypes = [...lost].sort();
@@ -5196,7 +5099,7 @@ function findStructureLoss(visited, resolve2) {
5196
5099
  nodeType: type,
5197
5100
  earlier: i,
5198
5101
  later: j,
5199
- on: intersection2(A, B),
5102
+ on: intersection(A, B),
5200
5103
  lostNodeTypes: lostNodeTypes.slice(0, MAX_LOST_TYPES),
5201
5104
  earlierShape: clamp(render(resolved[i], 3)),
5202
5105
  laterShape: clamp(render(resolved[j], 3)),
@@ -5454,11 +5357,11 @@ function buildBalancedInterior(open, close, skips) {
5454
5357
  return combi;
5455
5358
  }
5456
5359
  function firstSetClassChars(c) {
5457
- const fs2 = c._meta.firstSet;
5458
- if (fs2.kind === "empty") return [];
5459
- if (fs2.kind !== "ranges") return null;
5360
+ const fs3 = c._meta.firstSet;
5361
+ if (fs3.kind === "empty") return [];
5362
+ if (fs3.kind !== "ranges") return null;
5460
5363
  const out = [];
5461
- for (const { lo, hi } of fs2.ranges) {
5364
+ for (const { lo, hi } of fs3.ranges) {
5462
5365
  if (hi - lo > 8) return null;
5463
5366
  for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5464
5367
  }
@@ -5472,13 +5375,13 @@ function escapeClassChar(ch2) {
5472
5375
  function idPart(value) {
5473
5376
  return encodeURIComponent(value);
5474
5377
  }
5475
- function dispatchArmIds(def, path2) {
5378
+ function dispatchArmIds(def, path3) {
5476
5379
  const ids = [];
5477
- for (const entry of def.cases) ids.push(`dispatch:${path2}/when:${entry.keys.map(idPart).join("|")}`);
5380
+ for (const entry of def.cases) ids.push(`dispatch:${path3}/when:${entry.keys.map(idPart).join("|")}`);
5478
5381
  for (const entry of def.matchers ?? []) {
5479
- ids.push(`dispatch:${path2}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5382
+ ids.push(`dispatch:${path3}/matcher:${entry.kind}:${idPart(entry.value)}${entry.kind === "matches" && entry.flags ? `:${idPart(entry.flags)}` : ""}`);
5480
5383
  }
5481
- if (def.otherwise) ids.push(`dispatch:${path2}/otherwise`);
5384
+ if (def.otherwise) ids.push(`dispatch:${path3}/otherwise`);
5482
5385
  return ids;
5483
5386
  }
5484
5387
  function children(def, winners) {
@@ -5517,7 +5420,15 @@ function children(def, winners) {
5517
5420
  let resolved;
5518
5421
  try {
5519
5422
  resolved = def.thunk();
5520
- } catch {
5423
+ } catch (e) {
5424
+ recordDegradation({
5425
+ code: "coverage-definitions-unavailable",
5426
+ severity: "warn",
5427
+ where: "<lazy rule reference>",
5428
+ subject: `unresolvable reference (${e.message})`,
5429
+ 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",
5430
+ otherwise: "the referenced rule and its whole subtree would contribute definitions"
5431
+ });
5521
5432
  return [];
5522
5433
  }
5523
5434
  const name = resolved._ruleName;
@@ -5547,7 +5458,7 @@ function buildGrammarPlan(entry, winners) {
5547
5458
  }
5548
5459
  }
5549
5460
  const seen = /* @__PURE__ */ new Set();
5550
- const visit = (parser2, path2) => {
5461
+ const visit = (parser2, path3) => {
5551
5462
  if (seen.has(parser2)) return;
5552
5463
  seen.add(parser2);
5553
5464
  const rule = winnerNames.get(parser2) ?? parser2._ruleName;
@@ -5557,22 +5468,22 @@ function buildGrammarPlan(entry, winners) {
5557
5468
  rules2.set(parser2, id);
5558
5469
  }
5559
5470
  if (parser2._def.tag === "choice") {
5560
- const ids = parser2._def.parsers.map((_, index) => `choice:${path2}/arm:${index}`);
5471
+ const ids = parser2._def.parsers.map((_, index) => `choice:${path3}/arm:${index}`);
5561
5472
  choices.set(parser2, ids);
5562
5473
  ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
5563
5474
  }
5564
5475
  if (parser2._def.tag === "dispatch") {
5565
- const ids = dispatchArmIds(parser2._def, path2);
5476
+ const ids = dispatchArmIds(parser2._def, path3);
5566
5477
  dispatches.set(parser2, ids);
5567
5478
  ids.forEach((id) => definitions.set(id, { id, kind: "dispatch-arm" }));
5568
5479
  }
5569
- if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
5480
+ if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path3}`);
5570
5481
  if (parser2._def.tag === "label") {
5571
- const id = `label:${path2}`;
5482
+ const id = `label:${path3}`;
5572
5483
  labels.set(parser2, [id]);
5573
5484
  definitions.set(id, { id, kind: "label" });
5574
5485
  }
5575
- children(parser2._def, winners).forEach((child, index) => visit(child, `${path2}/${parser2._def.tag}:${index}`));
5486
+ children(parser2._def, winners).forEach((child, index) => visit(child, `${path3}/${parser2._def.tag}:${index}`));
5576
5487
  };
5577
5488
  const roots = Array.isArray(entry) ? entry : [entry];
5578
5489
  for (const root of roots) {
@@ -5630,15 +5541,15 @@ function parseStringShape(source) {
5630
5541
  if (source.length < 3 || source[source.length - 1] !== q) return null;
5631
5542
  const inner = source.slice(1, -1);
5632
5543
  const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
5633
- const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5544
+ const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5634
5545
  let body;
5635
5546
  let esc;
5636
- let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
5547
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
5637
5548
  if (m) {
5638
5549
  body = m[1];
5639
5550
  esc = m[2];
5640
5551
  } else {
5641
- m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
5552
+ m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
5642
5553
  if (m) {
5643
5554
  esc = m[1];
5644
5555
  body = m[2];
@@ -6344,10 +6255,10 @@ var ALT_SWITCH_MIN_CASES = 3;
6344
6255
  function planAltSwitch(firsts) {
6345
6256
  const cases = [];
6346
6257
  let total = 0;
6347
- for (const fs2 of firsts) {
6348
- if (!fs2 || fs2.negated) return { kind: "if" };
6258
+ for (const fs3 of firsts) {
6259
+ if (!fs3 || fs3.negated) return { kind: "if" };
6349
6260
  const pts = [];
6350
- for (const [lo, hi] of fs2.ranges) {
6261
+ for (const [lo, hi] of fs3.ranges) {
6351
6262
  if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
6352
6263
  for (let cp = lo; cp <= hi; cp++) pts.push(cp);
6353
6264
  }
@@ -6422,8 +6333,8 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
6422
6333
  } else {
6423
6334
  const c0 = codeAt(start, firstChar);
6424
6335
  shape.arms.forEach((arm, k) => {
6425
- const fs2 = shape.firsts[k];
6426
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
6336
+ const fs3 = shape.firsts[k];
6337
+ const cond = fs3.negated ? `!(${classCond(c0, fs3.ranges)})` : `(${classCond(c0, fs3.ranges)})`;
6427
6338
  const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
6428
6339
  lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
6429
6340
  lines.push(...m.setup);
@@ -6840,27 +6751,53 @@ function emitScannableTerminal(shape, c) {
6840
6751
  }
6841
6752
 
6842
6753
  // src/compiler/inline-build.ts
6843
- 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*$/;
6754
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
6755
+ var ANY_P = String.raw`\w+${ANN}`;
6756
+ var MK_BUILD_RE = new RegExp(
6757
+ 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*$`
6758
+ );
6844
6759
  function escapeRegExp(s) {
6845
6760
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6846
6761
  }
6762
+ function identMkBuildRe(typeIdent) {
6763
+ return new RegExp(
6764
+ 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*$`
6765
+ );
6766
+ }
6847
6767
  function looseMkBuildRe(type) {
6848
6768
  return new RegExp(
6849
- 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*$`
6769
+ 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*$`
6850
6770
  );
6851
6771
  }
6772
+ var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
6852
6773
  function analyzeMkInlineBuild(def) {
6853
6774
  if (!def.build) return null;
6854
6775
  if (def.type === void 0) return null;
6855
- const src = (def.buildSrc ?? def.build.toString()).trim();
6776
+ const src = buildAnalysisSrc(def).trim();
6856
6777
  const strict = src.match(MK_BUILD_RE);
6857
6778
  if (strict) {
6858
6779
  const mkType = strict[6];
6859
- return mkType === def.type ? def.type : null;
6780
+ if (mkType === def.type) return def.type;
6781
+ reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
6782
+ return null;
6860
6783
  }
6861
6784
  if (looseMkBuildRe(def.type).test(src)) return def.type;
6785
+ if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
6786
+ if (LOOKS_LIKE_MK_RE.test(src)) {
6787
+ reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
6788
+ }
6862
6789
  return null;
6863
6790
  }
6791
+ function reportMkMiss(type, src, why) {
6792
+ recordDegradation({
6793
+ code: "mk-inline-missed",
6794
+ severity: "warn",
6795
+ where: `node("${type}")`,
6796
+ subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
6797
+ fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
6798
+ otherwise: "the node object would be constructed at the call site with no call frame"
6799
+ });
6800
+ }
6864
6801
  function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
6865
6802
  return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
6866
6803
  }
@@ -7043,7 +6980,30 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
7043
6980
  seen.delete(p);
7044
6981
  }
7045
6982
  }
6983
+ function tern(cond, whenTrue, whenFalse) {
6984
+ if (cond === "false") return whenFalse;
6985
+ if (cond === "true") return whenTrue;
6986
+ return `${cond} ? ${whenTrue} : ${whenFalse}`;
6987
+ }
6988
+ function notGate(cond) {
6989
+ if (cond === "false") return "true";
6990
+ if (cond === "true") return "false";
6991
+ return `!(${cond})`;
6992
+ }
6993
+ function orGate(a, b) {
6994
+ if (a === "false") return b;
6995
+ if (b === "false") return a;
6996
+ if (a === "true" || b === "true") return "true";
6997
+ return `${a} || ${b}`;
6998
+ }
6999
+ function andGate(a, b) {
7000
+ if (a === "false" || b === "false") return "false";
7001
+ if (a === "true") return b;
7002
+ if (b === "true") return a;
7003
+ return `${a} && ${b}`;
7004
+ }
7046
7005
  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 }";
7006
+ 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 } }";
7047
7007
  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 }";
7048
7008
  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] } }";
7049
7009
  var _loweringSink = null;
@@ -7055,6 +7015,27 @@ function endLoweringCapture() {
7055
7015
  _loweringSink = null;
7056
7016
  return misses;
7057
7017
  }
7018
+ var _inlineCapSink = null;
7019
+ function beginInlineCapCapture() {
7020
+ _inlineCapSink = [];
7021
+ }
7022
+ function endInlineCapCapture() {
7023
+ const sites = _inlineCapSink ?? [];
7024
+ _inlineCapSink = null;
7025
+ return sites;
7026
+ }
7027
+ function formatInlineCapSites(sites, max) {
7028
+ if (sites.length === 0) return [];
7029
+ const byFn = /* @__PURE__ */ new Map();
7030
+ for (const s of sites) {
7031
+ const cur = byFn.get(s.fn);
7032
+ if (cur) {
7033
+ cur.count += 1;
7034
+ cur.nodes += s.nodes;
7035
+ } else byFn.set(s.fn, { count: 1, nodes: s.nodes });
7036
+ }
7037
+ 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.`);
7038
+ }
7058
7039
  function hasSelectedRootTrivia(ctx) {
7059
7040
  return ctx.rootTriviaClassified === true;
7060
7041
  }
@@ -7338,10 +7319,10 @@ function ensureTriviaFn(ctx) {
7338
7319
  function ensureTriviaCaptureFn(ctx) {
7339
7320
  return ensureTriviaFn(ctx);
7340
7321
  }
7341
- function firstSetCond(codeVar, fs2) {
7342
- if (fs2.kind === "any") return "true";
7343
- if (fs2.kind === "empty") return "false";
7344
- return fs2.ranges.map(
7322
+ function firstSetCond(codeVar, fs3) {
7323
+ if (fs3.kind === "any") return "true";
7324
+ if (fs3.kind === "empty") return "false";
7325
+ return fs3.ranges.map(
7345
7326
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
7346
7327
  ).join(" || ");
7347
7328
  }
@@ -7354,10 +7335,10 @@ function planDisjointDispatch(parsers) {
7354
7335
  const cases = [];
7355
7336
  let total = 0;
7356
7337
  for (const p of parsers) {
7357
- const fs2 = p._meta.firstSet;
7358
- if (fs2.kind !== "ranges") return { kind: "if" };
7338
+ const fs3 = p._meta.firstSet;
7339
+ if (fs3.kind !== "ranges") return { kind: "if" };
7359
7340
  const pts = [];
7360
- for (const r of fs2.ranges) {
7341
+ for (const r of fs3.ranges) {
7361
7342
  if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
7362
7343
  for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
7363
7344
  }
@@ -7750,9 +7731,9 @@ function emitRegex(def, ctx, pos, canMatchNewline = true) {
7750
7731
  stmts.push(...emitLeafCapture(ctx, vv, pos, endVar));
7751
7732
  return { stmts, valueVar: vv, endVar };
7752
7733
  }
7753
- function syncSentinelExpr(fs2) {
7754
- if (fs2.kind !== "ranges" || fs2.ranges.length === 0) return null;
7755
- return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs2.ranges)} })`;
7734
+ function syncSentinelExpr(fs3) {
7735
+ if (fs3.kind !== "ranges" || fs3.ranges.length === 0) return null;
7736
+ return `_ctx._rec.sentinel({ kind: 'ranges', ranges: ${JSON.stringify(fs3.ranges)} })`;
7756
7737
  }
7757
7738
  function emitSeqValues(def, ctx, pos) {
7758
7739
  const startV = v(ctx, "_start");
@@ -7766,8 +7747,8 @@ function emitSeqValues(def, ctx, pos) {
7766
7747
  if (ctx.recovery) stmts.push(`${ind(ctx)}const ${syncInV} = _ctx._sync`);
7767
7748
  const publishSync = (i) => {
7768
7749
  if (!ctx.recovery) return "";
7769
- const fs2 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
7770
- const ref2 = syncSentinelExpr(fs2);
7750
+ const fs3 = def.parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" });
7751
+ const ref2 = syncSentinelExpr(fs3);
7771
7752
  return `${ind(ctx)}if (_ctx._tolerant) _ctx._sync = ${ref2 ?? syncInV}`;
7772
7753
  };
7773
7754
  for (let i = 0; i < def.parsers.length; i++) {
@@ -7803,9 +7784,11 @@ function emitSeqValues(def, ctx, pos) {
7803
7784
  const markLog = v(ctx, "_mklg");
7804
7785
  const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
7805
7786
  const scanEndV = v(ctx, "_sne");
7806
- const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? "(_ctx._triviaLog !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0" : "_ctx._triviaLog !== undefined ? 2 : 0";
7787
+ const logV = v(ctx, "_tlg");
7788
+ const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
7807
7789
  stmts.push(
7808
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`,
7790
+ `${ind(ctx)}const ${logV} = _ctx._triviaLog`,
7791
+ `${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
7809
7792
  ...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
7810
7793
  `${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
7811
7794
  ...emitLineTrack(ctx, curV, scanEndV)
@@ -7815,7 +7798,7 @@ function emitSeqValues(def, ctx, pos) {
7815
7798
  const endAfterV = v(ctx, "_sea");
7816
7799
  stmts.push(
7817
7800
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
7818
- `${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};` : ""} }`
7801
+ `${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};` : ""} }`
7819
7802
  );
7820
7803
  valueVars.push(r2.valueVar);
7821
7804
  continue;
@@ -7881,8 +7864,8 @@ function canMatchEmptyAtStart(p) {
7881
7864
  }
7882
7865
  }
7883
7866
  function needsFirstSetGuard(p) {
7884
- const fs2 = p._meta.firstSet;
7885
- return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
7867
+ const fs3 = p._meta.firstSet;
7868
+ return fs3.kind !== "any" && !canMatchEmptyAtStart(p);
7886
7869
  }
7887
7870
  function coverageHit(ctx, id, offset = "_pos", end) {
7888
7871
  return id === void 0 ? [] : [
@@ -8887,9 +8870,18 @@ function emitPeek(def, ctx, pos) {
8887
8870
  endVar: pos
8888
8871
  };
8889
8872
  }
8890
- function emitRouted(ctx, pos) {
8873
+ function emitRouted(ctx, pos, def) {
8891
8874
  const local = ctx.routedLocal;
8875
+ const fallback = def.fallback;
8892
8876
  if (local !== void 0) {
8877
+ if (fallback !== void 0 && local.startVar !== pos) {
8878
+ return emitRoutedWithFallback(ctx, pos, fallback, {
8879
+ test: `${local.startVar} === ${pos}`,
8880
+ valExpr: local.valueVar,
8881
+ startExpr: local.startVar,
8882
+ endExpr: local.endVar
8883
+ });
8884
+ }
8893
8885
  return {
8894
8886
  stmts: [
8895
8887
  ...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
@@ -8900,9 +8892,19 @@ function emitRouted(ctx, pos) {
8900
8892
  };
8901
8893
  }
8902
8894
  const item = v(ctx, "_rt");
8895
+ const read = `${ind(ctx)}const ${item} = _ctx._routed`;
8896
+ if (fallback !== void 0) {
8897
+ const r = emitRoutedWithFallback(ctx, pos, fallback, {
8898
+ test: `${item} !== undefined && ${item}.span.start === ${pos}`,
8899
+ valExpr: `${item}.value`,
8900
+ startExpr: `${item}.span.start`,
8901
+ endExpr: `${item}.span.end`
8902
+ });
8903
+ return { ...r, stmts: [read, ...r.stmts] };
8904
+ }
8903
8905
  return {
8904
8906
  stmts: [
8905
- `${ind(ctx)}const ${item} = _ctx._routed`,
8907
+ read,
8906
8908
  ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
8907
8909
  ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
8908
8910
  ],
@@ -8910,6 +8912,30 @@ function emitRouted(ctx, pos) {
8910
8912
  endVar: `${item}.span.end`
8911
8913
  };
8912
8914
  }
8915
+ function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
8916
+ const valV = v(ctx, "_rtv");
8917
+ const endV = v(ctx, "_rte");
8918
+ const i = ind(ctx);
8919
+ ctx.indent++;
8920
+ const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
8921
+ const fb = emit(fallback, ctx, pos);
8922
+ const inner = ind(ctx);
8923
+ ctx.indent--;
8924
+ return {
8925
+ stmts: [
8926
+ `${i}let ${valV}, ${endV}`,
8927
+ `${i}if (${routed2.test}) {`,
8928
+ ...capture,
8929
+ `${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
8930
+ `${i}} else {`,
8931
+ ...fb.stmts,
8932
+ `${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
8933
+ `${i}}`
8934
+ ],
8935
+ valueVar: valV,
8936
+ endVar: endV
8937
+ };
8938
+ }
8913
8939
  function escapeTokenLiteral(value) {
8914
8940
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
8915
8941
  }
@@ -9091,22 +9117,16 @@ function emitNode(def, ctx, pos) {
9091
9117
  const smk = structural ? v(ctx, "_smk") : null;
9092
9118
  if (structural) ctx.needsHostReads = true;
9093
9119
  const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
9094
- const pmV = v(ctx, "_pm"), recV = v(ctx, "_rec"), capV = v(ctx, "_cap");
9095
- const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
9096
- const profileRecognizer = recV;
9097
- const profileCapture = capV;
9098
- const cstMode = ctx.hostMode === "cst";
9099
- const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
9100
- const directCstGate = directCstV === null ? "false" : profileCapture;
9120
+ const profileRecognizer = "false";
9121
+ const profileCapture = "false";
9101
9122
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
9102
9123
  const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
9103
- const chAlloc = def.unwrap || def.collapse ? `${profileRecognizer} ? undefined : []` : `${profileRecognizer} ? undefined : ((${profileCapture} || ${chNeededExpr}) ? [] : undefined)`;
9104
- 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)))` : ""}
9105
- ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9106
- ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9107
- ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
9124
+ const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
9125
+ const dcstAlloc = "undefined";
9126
+ 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))"))}` : ""}
9127
+ ${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)}`;
9108
9128
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
9109
- const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
9129
+ const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
9110
9130
  const sf = hasFields ? v(ctx, "_sf") : null;
9111
9131
  const fArr = hasFields ? v(ctx, "_fa") : null;
9112
9132
  const fObj = hasFields ? v(ctx, "_fields") : "undefined";
@@ -9115,7 +9135,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9115
9135
  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}` : ""}` : "";
9116
9136
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
9117
9137
  const preGuard = [];
9118
- if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
9138
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
9119
9139
  const gcV = v(ctx, "_ngc");
9120
9140
  const gExp = armStaticExpected(ctx, def.parser);
9121
9141
  const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
@@ -9126,7 +9146,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9126
9146
  }
9127
9147
  const stmts = [
9128
9148
  ...preGuard,
9129
- profHoist,
9130
9149
  allocStmt,
9131
9150
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
9132
9151
  `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
@@ -9143,21 +9162,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9143
9162
  }
9144
9163
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
9145
9164
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
9146
- stmts.push(
9147
- `${i}if (_ctx._pmProfile) {`,
9148
- `${i} _ctx._pmProfile.nodes++`,
9149
- `${i} if (${profileCapture}) {`,
9150
- `${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
9151
- `${i} }`,
9152
- `${i}}`
9153
- );
9154
9165
  let stV = "undefined";
9155
9166
  if (structural) {
9156
9167
  stV = v(ctx, "_nst");
9157
9168
  stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9158
9169
  } else if (clonesState) {
9159
9170
  stV = v(ctx, "_nst");
9160
- stmts.push(`${i}const ${stV} = !(${profileRecognizer} || ${profileCapture}) && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9171
+ stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
9161
9172
  }
9162
9173
  if (sf && fArr) {
9163
9174
  const fe = v(ctx, "_fe");
@@ -9181,19 +9192,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9181
9192
  if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
9182
9193
  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})`;
9183
9194
  const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
9184
- const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${hostBuildArgs})`;
9195
+ const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
9185
9196
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
9186
- 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;
9197
+ const hostCollapses = structural || cstOut;
9198
+ 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;
9187
9199
  const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
9188
9200
  const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
9189
9201
  const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
9190
- stmts.push(
9191
- `${i}const ${ndV} = (${profileRecognizer} || ${profileCapture}) ? undefined : (${finalExpr})`,
9192
- `${i}if (!(${profileRecognizer})) {`,
9193
- `${i} if (${sc}) ${sc}.push(${ndV})`,
9194
- `${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} })`,
9195
- `${i}}`
9196
- );
9202
+ const ndGate = orGate(profileRecognizer, profileCapture);
9203
+ const recGate = notGate(profileRecognizer);
9204
+ const pushIndent = recGate === "true" ? i : `${i} `;
9205
+ const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
9206
+ ctx.needsRawEntry = true;
9207
+ const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
9208
+ stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
9209
+ if (recGate === "true") stmts.push(pushCh, pushRaw);
9210
+ else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
9197
9211
  return { stmts, valueVar: ndV, endVar };
9198
9212
  }
9199
9213
  function emitRuntimeFallback(parser2, ctx, pos) {
@@ -9222,7 +9236,12 @@ function emitLazy(p, def, ctx, pos) {
9222
9236
  } catch {
9223
9237
  return emitRuntimeFallback(p, ctx, pos);
9224
9238
  }
9225
- return emit(resolved, ctx, pos);
9239
+ const cost = usage.sizes.get(resolved) ?? 1;
9240
+ if (cost <= ctx.inlineLeft) {
9241
+ ctx.inlineLeft -= cost;
9242
+ return emit(resolved, ctx, pos);
9243
+ }
9244
+ _inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
9226
9245
  }
9227
9246
  if (!ctx.namedParsers.has(p)) {
9228
9247
  const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
@@ -9237,6 +9256,9 @@ function emitLazy(p, def, ctx, pos) {
9237
9256
  const savedIndent = ctx.indent;
9238
9257
  const savedFailLabel = ctx.failLabel;
9239
9258
  const savedRecord = ctx.recordFail;
9259
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9260
+ ctx.inlineLeft = ctx.inlineMax;
9261
+ ctx.currentFnName = fnName2;
9240
9262
  const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
9241
9263
  const savedActiveRule = ctx.activeCoverageRuleId;
9242
9264
  const savedSuppressFailure = ctx.suppressCoverageFailure;
@@ -9251,6 +9273,8 @@ function emitLazy(p, def, ctx, pos) {
9251
9273
  ctx.indent = savedIndent;
9252
9274
  ctx.failLabel = savedFailLabel;
9253
9275
  ctx.recordFail = savedRecord;
9276
+ ctx.inlineLeft = savedInlineLeft;
9277
+ ctx.currentFnName = savedFnName;
9254
9278
  ctx.activeCoverageRuleId = savedActiveRule;
9255
9279
  ctx.suppressCoverageFailure = savedSuppressFailure;
9256
9280
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
@@ -9345,6 +9369,9 @@ function emit(p, ctx, pos) {
9345
9369
  const savedIndent = ctx.indent;
9346
9370
  const savedFailLabel = ctx.failLabel;
9347
9371
  const savedRecord = ctx.recordFail;
9372
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9373
+ ctx.inlineLeft = ctx.inlineMax;
9374
+ ctx.currentFnName = fnName;
9348
9375
  ctx.indent = 1;
9349
9376
  ctx.failLabel = "_pfail";
9350
9377
  ctx.recordFail = true;
@@ -9352,6 +9379,8 @@ function emit(p, ctx, pos) {
9352
9379
  ctx.indent = savedIndent;
9353
9380
  ctx.failLabel = savedFailLabel;
9354
9381
  ctx.recordFail = savedRecord;
9382
+ ctx.inlineLeft = savedInlineLeft;
9383
+ ctx.currentFnName = savedFnName;
9355
9384
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
9356
9385
  return instrument(emitNamedFnCall(ctx, fnName, pos));
9357
9386
  }
@@ -9545,7 +9574,7 @@ function emitDispatch(p, ctx, pos) {
9545
9574
  case "peek":
9546
9575
  return emitPeek(def, ctx, pos);
9547
9576
  case "routed":
9548
- return emitRouted(ctx, pos);
9577
+ return emitRouted(ctx, pos, def);
9549
9578
  case "node":
9550
9579
  return emitNode(def, ctx, pos);
9551
9580
  case "scanTo":
@@ -9580,6 +9609,9 @@ function emitDispatch(p, ctx, pos) {
9580
9609
  const savedIndent = ctx.indent;
9581
9610
  const savedFailLabel = ctx.failLabel;
9582
9611
  const savedRecord = ctx.recordFail;
9612
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9613
+ ctx.inlineLeft = ctx.inlineMax;
9614
+ ctx.currentFnName = fnName;
9583
9615
  ctx.indent = 1;
9584
9616
  ctx.failLabel = "_pfail";
9585
9617
  ctx.recordFail = true;
@@ -9587,6 +9619,8 @@ function emitDispatch(p, ctx, pos) {
9587
9619
  ctx.indent = savedIndent;
9588
9620
  ctx.failLabel = savedFailLabel;
9589
9621
  ctx.recordFail = savedRecord;
9622
+ ctx.inlineLeft = savedInlineLeft;
9623
+ ctx.currentFnName = savedFnName;
9590
9624
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
9591
9625
  }
9592
9626
  const fn = ctx.namedParsers.get(innerParser);
@@ -9745,12 +9779,16 @@ function childrenOf3(def) {
9745
9779
  return [def.parser, def.sentinel];
9746
9780
  case "scanTo":
9747
9781
  return [def.sentinel, ...def.skip];
9782
+ // A `routed()` fallback IS emitted at this site (emitRouted), so the usage
9783
+ // analysis must see the edge — otherwise a fallback shared by several routed()
9784
+ // sites is miscounted as single-use and inlined at each.
9785
+ case "routed":
9786
+ return def.fallback ? [def.fallback] : [];
9748
9787
  case "lazy":
9749
9788
  case "literal":
9750
9789
  case "regex":
9751
9790
  case "keywords":
9752
9791
  case "guard":
9753
- case "routed":
9754
9792
  case "unknown":
9755
9793
  return [];
9756
9794
  }
@@ -9771,6 +9809,17 @@ function isHoistableTag(tag) {
9771
9809
  return HOISTABLE_TAGS.has(tag);
9772
9810
  }
9773
9811
  var HOIST_MIN_SUBTREE = 3;
9812
+ var INLINE_MAX_NODES = 1e3;
9813
+ function resolveInlineMax(explicit) {
9814
+ if (explicit !== void 0) return explicit;
9815
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
9816
+ if (env !== void 0 && env !== "") {
9817
+ if (env === "off" || env === "infinity") return Infinity;
9818
+ const n = Number(env);
9819
+ if (Number.isFinite(n) && n >= 0) return n;
9820
+ }
9821
+ return INLINE_MAX_NODES;
9822
+ }
9774
9823
  function analyzeLazyUsage(root) {
9775
9824
  return analyzeLazyUsageMulti([root]);
9776
9825
  }
@@ -9876,102 +9925,6 @@ function ruleDependencies(ruleMap) {
9876
9925
  }
9877
9926
  return deps;
9878
9927
  }
9879
- function resolveGatingLevel(opt) {
9880
- const explicit = typeof opt === "string" ? opt : opt?.level;
9881
- if (explicit !== void 0) return explicit;
9882
- const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
9883
- if (env === "off" || env === "warn" || env === "error") return env;
9884
- return "warn";
9885
- }
9886
- function runGatingDiagnostic(combinator, opt) {
9887
- return reportGating(opt, (o) => analyzeGating(combinator, o));
9888
- }
9889
- function runGatingDiagnosticRules(ruleMap, opt) {
9890
- return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
9891
- }
9892
- function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
9893
- if (resolveGatingLevel(opt) === "off") return void 0;
9894
- const ruleMaps = getRuleMaps();
9895
- const opaque = getOpaque?.() ?? [];
9896
- const opaqueFindings = opaque.map((o) => ({
9897
- rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
9898
- kind: "opaque-artifact",
9899
- reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
9900
- }));
9901
- if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
9902
- if (opaqueFindings.length === 0) return void 0;
9903
- return reportGating(opt, () => ({
9904
- totalChoices: 0,
9905
- gated: 0,
9906
- recoverable: 0,
9907
- unanalysable: opaqueFindings,
9908
- ungated: [],
9909
- accepted: [],
9910
- deferred: [],
9911
- acceptedUnused: [],
9912
- choices: [],
9913
- antiPatterns: []
9914
- }));
9915
- }
9916
- const winners = /* @__PURE__ */ new Map();
9917
- for (const map of ruleMaps) for (const [name, rule] of map) {
9918
- if (rule._def.tag === "lazy") {
9919
- try {
9920
- rule._def.thunk();
9921
- } catch {
9922
- continue;
9923
- }
9924
- }
9925
- winners.set(name, rule);
9926
- }
9927
- const entries = [...winners];
9928
- return reportGating(opt, (o) => {
9929
- const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
9930
- const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
9931
- return {
9932
- ...fused,
9933
- ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
9934
- antiPatterns: [],
9935
- unanalysable: [...fused.unanalysable, ...opaqueFindings]
9936
- };
9937
- });
9938
- }
9939
- function reportGating(opt, analyze) {
9940
- const level = resolveGatingLevel(opt);
9941
- if (level === "off") return void 0;
9942
- const obj = opt !== null && typeof opt === "object" ? opt : void 0;
9943
- 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;
9944
- let report;
9945
- try {
9946
- report = analyze(analyzeOpts);
9947
- } catch (e) {
9948
- const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
9949
- const failed = {
9950
- totalChoices: 0,
9951
- gated: 0,
9952
- recoverable: 0,
9953
- unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
9954
- ungated: [],
9955
- accepted: [],
9956
- deferred: [],
9957
- acceptedUnused: [],
9958
- choices: [],
9959
- antiPatterns: []
9960
- };
9961
- const failLines = formatGatingWarnings(failed);
9962
- if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
9963
- ${failLines.join("\n")}`);
9964
- for (const l of failLines) console.warn(l);
9965
- return failed;
9966
- }
9967
- const lines = formatGatingWarnings(report);
9968
- if (lines.length > 0) {
9969
- 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)
9970
- ${lines.join("\n")}`);
9971
- for (const l of lines) console.warn(l);
9972
- }
9973
- return report;
9974
- }
9975
9928
  function resolveDuplicationLevel(opt) {
9976
9929
  const explicit = typeof opt === "string" ? opt : opt?.level;
9977
9930
  if (explicit !== void 0) return explicit;
@@ -10014,7 +9967,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
10014
9967
  return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
10015
9968
  }
10016
9969
  function compile(combinator, mapFnSources, opts) {
10017
- const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
9970
+ const drain = beginCompileDegradationDrain();
9971
+ let completed = false;
9972
+ try {
9973
+ const compiled = compileImpl(combinator, mapFnSources, opts);
9974
+ completed = true;
9975
+ return compiled;
9976
+ } finally {
9977
+ drain(completed);
9978
+ }
9979
+ }
9980
+ function compileImpl(combinator, mapFnSources, opts) {
10018
9981
  runDuplicationDiagnostic(combinator, opts?.duplication);
10019
9982
  markUnusedValues(combinator);
10020
9983
  const grammarTrivia = combinator._meta.grammarTrivia;
@@ -10022,9 +9985,12 @@ function compile(combinator, mapFnSources, opts) {
10022
9985
  const grammarScanSkip = combinator._meta.grammarScanSkip;
10023
9986
  const grammarHostMode = combinator._meta.grammarHostMode;
10024
9987
  const grammarTrackLines = combinator._meta.grammarTrackLines;
9988
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10025
9989
  const ctx = {
10026
9990
  vars: 0,
10027
9991
  indent: 1,
9992
+ inlineMax: _inlineMax,
9993
+ inlineLeft: _inlineMax,
10028
9994
  regexDecls: [],
10029
9995
  regexMap: /* @__PURE__ */ new Map(),
10030
9996
  expectedDecls: [],
@@ -10070,11 +10036,13 @@ function compile(combinator, mapFnSources, opts) {
10070
10036
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
10071
10037
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
10072
10038
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
10039
+ const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
10073
10040
  const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
10074
10041
  const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
10075
10042
  const source = [
10076
10043
  ...emptyTlDecls,
10077
10044
  ...hostReadsDecls,
10045
+ ...rawEntryDecls,
10078
10046
  ...lineTrackDecls,
10079
10047
  ...lineSpanDecls,
10080
10048
  ...ctx.regexDecls,
@@ -10089,6 +10057,7 @@ function compile(combinator, mapFnSources, opts) {
10089
10057
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
10090
10058
  ...emptyTlDecls,
10091
10059
  ...hostReadsDecls,
10060
+ ...rawEntryDecls,
10092
10061
  ...lineTrackDecls,
10093
10062
  ...lineSpanDecls,
10094
10063
  ...ctx.regexDecls,
@@ -10138,8 +10107,7 @@ function compile(combinator, mapFnSources, opts) {
10138
10107
  const compiledMeta = {
10139
10108
  source,
10140
10109
  inlineExpression,
10141
- ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
10142
- ...gatingReport === void 0 ? {} : { gating: gatingReport }
10110
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
10143
10111
  };
10144
10112
  if (!ctx.lineTracking) {
10145
10113
  return {
@@ -10204,7 +10172,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
10204
10172
  return `Object.assign(${fnSource}, { _meta: {${labels === void 0 ? "" : ` triviaKindLabels: ${JSON.stringify(labels)},`}${classified === void 0 ? "" : " rootTriviaClassified: true,"} } })`;
10205
10173
  }
10206
10174
  function compileRuleMap(ruleMap, opts) {
10207
- runGatingDiagnosticRules(ruleMap, opts?.gating);
10208
10175
  runDuplicationDiagnosticRules(ruleMap, opts?.duplication);
10209
10176
  for (const [, rule] of ruleMap) markUnusedValues(rule);
10210
10177
  const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
@@ -10213,9 +10180,12 @@ function compileRuleMap(ruleMap, opts) {
10213
10180
  const grammarScanSkip = opts?.scanSkip ?? ruleMap.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
10214
10181
  const grammarHostMode = opts?.hostMode ?? ruleMap.map(([, r]) => r._meta.grammarHostMode).find(Boolean);
10215
10182
  const grammarTrackLines = opts?.trackLines === true || ruleMap.some(([, r]) => r._meta.grammarTrackLines === true);
10183
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10216
10184
  const ctx = {
10217
10185
  vars: 0,
10218
10186
  indent: 1,
10187
+ inlineMax: _inlineMax,
10188
+ inlineLeft: _inlineMax,
10219
10189
  regexDecls: [],
10220
10190
  regexMap: /* @__PURE__ */ new Map(),
10221
10191
  expectedDecls: [],
@@ -10281,6 +10251,7 @@ function compileRuleMap(ruleMap, opts) {
10281
10251
  const hoistedDecls = [
10282
10252
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
10283
10253
  ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
10254
+ ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "",
10284
10255
  ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "",
10285
10256
  ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "",
10286
10257
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10353,7 +10324,6 @@ function compileRuleMap(ruleMap, opts) {
10353
10324
  }
10354
10325
  function compileLinkable(ruleMapArg, ns, opts) {
10355
10326
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
10356
- if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
10357
10327
  if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
10358
10328
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
10359
10329
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
@@ -10372,9 +10342,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
10372
10342
  }
10373
10343
  });
10374
10344
  const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
10345
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10375
10346
  const ctx = {
10376
10347
  vars: 0,
10377
10348
  indent: 1,
10349
+ inlineMax: _inlineMax,
10350
+ inlineLeft: _inlineMax,
10378
10351
  regexDecls: [],
10379
10352
  regexMap: /* @__PURE__ */ new Map(),
10380
10353
  expectedDecls: [],
@@ -10470,6 +10443,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
10470
10443
  }
10471
10444
  }
10472
10445
  const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
10446
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10447
+ ctx.inlineLeft = ctx.inlineMax;
10448
+ ctx.currentFnName = fn;
10473
10449
  ctx.indent = 1;
10474
10450
  ctx.failLabel = "_pfail";
10475
10451
  ctx.recordFail = true;
@@ -10485,6 +10461,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
10485
10461
  ctx.indent = savedIndent;
10486
10462
  ctx.failLabel = savedFail;
10487
10463
  ctx.recordFail = savedRec;
10464
+ ctx.inlineLeft = savedInlineLeft;
10465
+ ctx.currentFnName = savedFnName;
10488
10466
  const ruleId = ctx.coverage?.plan.rules.get(rule);
10489
10467
  const stmts = ruleId === void 0 ? body.stmts : [
10490
10468
  `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
@@ -10569,6 +10547,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10569
10547
  deps: ruleDependencies(ruleMap),
10570
10548
  needsEmptyTl: !!ctx.needsEmptyTl,
10571
10549
  needsHostReads: !!ctx.needsHostReads,
10550
+ needsRawEntry: !!ctx.needsRawEntry,
10572
10551
  hostMode: ctx.hostMode ?? "ast",
10573
10552
  hostBranchElided: !!ctx.hostBranchElided,
10574
10553
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
@@ -10643,14 +10622,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
10643
10622
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
10644
10623
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
10645
10624
  const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
10625
+ const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
10646
10626
  const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
10647
10627
  const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
10648
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
10628
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
10649
10629
  if (!needsWrapper) return innerFn;
10650
10630
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
10651
10631
  const hoistedDecls = [
10652
10632
  emptyTlDecl,
10653
10633
  hostReadsDecl,
10634
+ rawEntryDecl,
10654
10635
  lineTrackDecl,
10655
10636
  lineSpanDecl,
10656
10637
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10720,18 +10701,19 @@ function childrenOf4(def) {
10720
10701
  return [def.main, def.skipped];
10721
10702
  case "scanTo":
10722
10703
  return [def.sentinel, ...def.skip];
10704
+ case "routed":
10705
+ return def.fallback ? [def.fallback] : [];
10723
10706
  case "lazy":
10724
10707
  case "literal":
10725
10708
  case "regex":
10726
10709
  case "keywords":
10727
- case "routed":
10728
10710
  case "guard":
10729
10711
  case "recover":
10730
10712
  case "unknown":
10731
10713
  return [];
10732
10714
  }
10733
10715
  }
10734
- var ruleNameOf4 = (c) => c._ruleName;
10716
+ var ruleNameOf3 = (c) => c._ruleName;
10735
10717
  function lazyTarget(c) {
10736
10718
  if (c._def.tag !== "lazy") return null;
10737
10719
  try {
@@ -10806,7 +10788,7 @@ function evalRuleMapIR(ir) {
10806
10788
  l._def.fnSrc = src;
10807
10789
  return l;
10808
10790
  };
10809
- const _nd = (type, child, src, opts, staticError) => {
10791
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
10810
10792
  if (staticError !== void 0 && staticError.length > 0) {
10811
10793
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
10812
10794
  }
@@ -10815,6 +10797,7 @@ function evalRuleMapIR(ir) {
10815
10797
  }), opts);
10816
10798
  n._def.buildSrc = src;
10817
10799
  if (staticError !== void 0) n._def.buildStaticError = staticError;
10800
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
10818
10801
  return n;
10819
10802
  };
10820
10803
  const _gch = (items) => {
@@ -10967,7 +10950,7 @@ var Serializer = class {
10967
10950
  if (seen.has(c)) return;
10968
10951
  seen.add(c);
10969
10952
  if (c._def.tag === "lazy") {
10970
- if (ruleNameOf4(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
10953
+ if (ruleNameOf3(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
10971
10954
  const target = lazyTarget(c);
10972
10955
  if (target) this.assertNoRuleRef(target, seen);
10973
10956
  return;
@@ -10978,7 +10961,7 @@ var Serializer = class {
10978
10961
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
10979
10962
  * `g[name]` ref — only the top-level entry is unwrapped. */
10980
10963
  body(c) {
10981
- return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
10964
+ return c._def.tag === "lazy" && ruleNameOf3(c) !== void 0 ? lazyTarget(c) ?? c : c;
10982
10965
  }
10983
10966
  run() {
10984
10967
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
@@ -11020,7 +11003,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11020
11003
  analyze(c, active) {
11021
11004
  const def = c._def;
11022
11005
  if (def.tag === "lazy") {
11023
- if (ruleNameOf4(c) !== void 0) return;
11006
+ if (ruleNameOf3(c) !== void 0) return;
11024
11007
  const target = lazyTarget(c);
11025
11008
  if (!target) throw new Unserializable("unresolved unnamed ref");
11026
11009
  if (active.has(target)) {
@@ -11072,7 +11055,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11072
11055
  const kid = (x) => this.ref(x, selfOf);
11073
11056
  switch (def.tag) {
11074
11057
  case "lazy": {
11075
- const name = this.localRuleNames.get(c) ?? ruleNameOf4(c);
11058
+ const name = this.localRuleNames.get(c) ?? ruleNameOf3(c);
11076
11059
  if (name !== void 0) return `g[${JSON.stringify(name)}]`;
11077
11060
  const target = lazyTarget(c);
11078
11061
  if (selfOf && target === selfOf.comb) return selfOf.var;
@@ -11127,7 +11110,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11127
11110
  case "peek":
11128
11111
  return `peek(${kid(def.parser)})`;
11129
11112
  case "routed":
11130
- return "routed()";
11113
+ return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
11131
11114
  case "trivia":
11132
11115
  return `trivia(${kid(def.parser)})`;
11133
11116
  case "token":
@@ -11160,6 +11143,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11160
11143
  ...projectEntry === void 0 ? [] : [projectEntry],
11161
11144
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
11162
11145
  ...def.trailingTrivia ? ["trailingTrivia: true"] : [],
11146
+ // Declared or resolved arity. Carried so a re-lowered composed artifact does
11147
+ // not silently re-acquire the fail-open capture cost this module resolved away.
11148
+ ...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
11163
11149
  ...tagEntry === void 0 ? [] : [tagEntry]
11164
11150
  ];
11165
11151
  const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
@@ -11168,8 +11154,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11168
11154
  return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
11169
11155
  }
11170
11156
  if (def.buildSrc !== void 0) {
11171
- const staticError = def.buildStaticError === void 0 ? "" : `${opts ? "" : ", undefined"}, ${JSON.stringify(def.buildStaticError)}`;
11172
- return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${staticError})`;
11157
+ const tail = [];
11158
+ if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
11159
+ if (!opts) tail.push("undefined");
11160
+ tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
11161
+ }
11162
+ if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
11163
+ const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
11164
+ return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
11173
11165
  }
11174
11166
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
11175
11167
  }
@@ -11233,7 +11225,7 @@ function pickPieces(pieces, names) {
11233
11225
  const filt = (m, pc) => new Map([...m].filter(([k]) => keep.has(k) && winner.get(k) === pc));
11234
11226
  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);
11235
11227
  }
11236
- function fusedBody(pieces) {
11228
+ function fusedBody(pieces, hoist) {
11237
11229
  for (const p of pieces) {
11238
11230
  if (p.v === void 0) {
11239
11231
  throw new Error(
@@ -11257,12 +11249,14 @@ function fusedBody(pieces) {
11257
11249
  const contributingPieces = [...contributing];
11258
11250
  const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
11259
11251
  const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
11252
+ const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
11260
11253
  const lines = [
11261
11254
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
11262
11255
  "const _pfFail = {}",
11263
11256
  "let _pfEnd",
11264
11257
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
11265
11258
  ...needsHostReads ? [HOST_READS_DECL] : [],
11259
+ ...needsRawEntry ? [RAW_ENTRY_DECL] : [],
11266
11260
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
11267
11261
  ...new Set(contributingPieces.flatMap((p) => p.prelude)),
11268
11262
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -11271,8 +11265,7 @@ function fusedBody(pieces) {
11271
11265
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
11272
11266
  const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
11273
11267
  const { mode, elided } = hostModeOfPieces(pieces);
11274
- const rawBody = [
11275
- ...lines,
11268
+ const tailSrc = [
11276
11269
  "const _map = {",
11277
11270
  wrapperEntries.join(",\n"),
11278
11271
  "}",
@@ -11288,6 +11281,7 @@ function fusedBody(pieces) {
11288
11281
  "}",
11289
11282
  "return _map"
11290
11283
  ].join("\n");
11284
+ const rawBody = [...lines, tailSrc].join("\n");
11291
11285
  const ANY2 = { kind: "any" };
11292
11286
  const EMPTY3 = { kind: "empty" };
11293
11287
  const recipes = /* @__PURE__ */ new Map();
@@ -11296,8 +11290,8 @@ function fusedBody(pieces) {
11296
11290
  for (const [k, p] of winner) {
11297
11291
  const r = p.firstSetRecipes?.get(k);
11298
11292
  if (r) recipes.set(k, r);
11299
- const fs2 = p.firstSets?.get(k);
11300
- if (fs2) shallow.set(k, fs2);
11293
+ const fs3 = p.firstSets?.get(k);
11294
+ if (fs3) shallow.set(k, fs3);
11301
11295
  const nu = p.nullable?.get(k);
11302
11296
  if (nu !== void 0) nullable2.set(k, nu);
11303
11297
  }
@@ -11320,25 +11314,26 @@ function fusedBody(pieces) {
11320
11314
  for (let iter = 0; iter <= recipes.size; iter++) {
11321
11315
  let changed = false;
11322
11316
  for (const [k, r] of recipes) {
11323
- const fs2 = resolveRecipe(r);
11324
- if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
11325
- finalFS.set(k, fs2);
11317
+ const fs3 = resolveRecipe(r);
11318
+ if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs3)) {
11319
+ finalFS.set(k, fs3);
11326
11320
  changed = true;
11327
11321
  }
11328
11322
  }
11329
11323
  if (!changed) break;
11330
11324
  }
11331
- const body = rawBody.replace(
11325
+ const resolveFS = (src) => src.replace(
11332
11326
  // Rule name + code-point var are both JS identifiers (rule names are validated
11333
11327
  // at compile time — see assertRuleName in codegen), so an identifier class
11334
11328
  // matches every well-formed placeholder.
11335
11329
  /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
11336
11330
  (_m, name, codevar) => {
11337
- const fs2 = finalFS.get(name);
11338
- if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
11339
- return `(${firstSetCond(codevar, fs2)})`;
11331
+ const fs3 = finalFS.get(name);
11332
+ if (!fs3 || fs3.kind === "any" || fs3.kind === "empty") return "true";
11333
+ return `(${firstSetCond(codevar, fs3)})`;
11340
11334
  }
11341
11335
  );
11336
+ const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
11342
11337
  const env = {};
11343
11338
  for (const p of contributingPieces) {
11344
11339
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -11356,8 +11351,8 @@ function hostModeOfPieces(pieces) {
11356
11351
  }
11357
11352
  return { mode, elided: elided.length > 0 };
11358
11353
  }
11359
- function emitFusedSource(pieces) {
11360
- const { body, env } = fusedBody(pieces);
11354
+ function emitFusedSource(pieces, hoist) {
11355
+ const { body, env } = fusedBody(pieces, hoist);
11361
11356
  if (Object.keys(env).length > 0) {
11362
11357
  throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
11363
11358
  }
@@ -11368,17 +11363,6 @@ ${body}
11368
11363
  function isIRPiece(p) {
11369
11364
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
11370
11365
  }
11371
- function once(fn) {
11372
- let done = false;
11373
- let value;
11374
- return () => {
11375
- if (!done) {
11376
- value = fn();
11377
- done = true;
11378
- }
11379
- return value;
11380
- };
11381
- }
11382
11366
  function materializePiece(p, trivia2, captureTerminals = false, hostMode) {
11383
11367
  if (!isIRPiece(p)) return p;
11384
11368
  const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, {
@@ -11391,6 +11375,34 @@ function materializePiece(p, trivia2, captureTerminals = false, hostMode) {
11391
11375
  return pieces;
11392
11376
  }
11393
11377
 
11378
+ // src/functional/run.ts
11379
+ var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
11380
+ var REMOVED_RUN_RESULT_FIELDS = [
11381
+ [
11382
+ "triviaMap",
11383
+ `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.`
11384
+ ],
11385
+ [
11386
+ "triviaLog",
11387
+ `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.`
11388
+ ],
11389
+ [
11390
+ "triviaKindLabels",
11391
+ `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\`.`
11392
+ ]
11393
+ ];
11394
+
11395
+ // src/analysis/choice-cost-render.ts
11396
+ var ESC = "\x1B";
11397
+ var ANSI = {
11398
+ dim: `${ESC}[2m`,
11399
+ bold: `${ESC}[1m`,
11400
+ red: `${ESC}[31m`,
11401
+ yellow: `${ESC}[33m`,
11402
+ cyan: `${ESC}[36m`,
11403
+ reset: `${ESC}[0m`
11404
+ };
11405
+
11394
11406
  // src/combinators/gate.ts
11395
11407
  function gate(predicate) {
11396
11408
  const meta = {
@@ -11410,9 +11422,6 @@ function gate(predicate) {
11410
11422
  };
11411
11423
  }
11412
11424
 
11413
- // src/cst/offset-model.ts
11414
- var { isFinite } = Number;
11415
-
11416
11425
  // src/index.ts
11417
11426
  registerRegexAnalyzer(firstSetFromRegex);
11418
11427
 
@@ -11605,6 +11614,12 @@ function stripTsFromSource(node2, code) {
11605
11614
  }
11606
11615
  return out + code.slice(cur, end);
11607
11616
  }
11617
+ var _reducers = null;
11618
+ var _entrySource = null;
11619
+ function setReducerResolver(r, entrySource = null) {
11620
+ _reducers = r;
11621
+ _entrySource = entrySource;
11622
+ }
11608
11623
  function isWordFactory(v2) {
11609
11624
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
11610
11625
  }
@@ -11836,9 +11851,12 @@ function staticNodeOptionsFromValue(value) {
11836
11851
  } else if (name === "tags") {
11837
11852
  if (!Array.isArray(v2) || !v2.every((item) => typeof item === "string")) return STATIC_NODE_OPTIONS_FAILED;
11838
11853
  opts.tags = v2;
11854
+ } else if (name === "buildArity") {
11855
+ if (typeof v2 !== "number" || !Number.isInteger(v2) || v2 < 0 || v2 > 6) return STATIC_NODE_OPTIONS_FAILED;
11856
+ opts.buildArity = v2;
11839
11857
  }
11840
11858
  }
11841
- return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11859
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 || opts.buildArity !== void 0 ? opts : void 0;
11842
11860
  }
11843
11861
  function staticNodeOptions(expr, scope) {
11844
11862
  const unwrapped = unwrapStaticExpr(expr);
@@ -11864,9 +11882,13 @@ function staticNodeOptions(expr, scope) {
11864
11882
  const tags = staticStringArray(p.value, scope);
11865
11883
  if (tags === void 0) return STATIC_NODE_OPTIONS_FAILED;
11866
11884
  opts.tags = tags;
11885
+ } else if (name === "buildArity") {
11886
+ const arity = staticLiteralValue(p.value);
11887
+ if (typeof arity !== "number" || !Number.isInteger(arity) || arity < 0 || arity > 6) return STATIC_NODE_OPTIONS_FAILED;
11888
+ opts.buildArity = arity;
11867
11889
  }
11868
11890
  }
11869
- return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11891
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 || opts.buildArity !== void 0 ? opts : void 0;
11870
11892
  }
11871
11893
  function exprToCombi(node2, scope, code, mfs) {
11872
11894
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -11962,6 +11984,18 @@ function exprToCombi(node2, scope, code, mfs) {
11962
11984
  const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
11963
11985
  if (combi._def.tag === "node" && buildSrc !== void 0) {
11964
11986
  combi._def.buildSrc = buildSrc;
11987
+ if (explicitType !== void 0 && firstArg.type === "Identifier") {
11988
+ combi._def.typeSrc = firstArg.name;
11989
+ }
11990
+ const resolved = _reducers?.resolve(buildSrc, be.start, code);
11991
+ if (resolved) {
11992
+ if (resolved.src !== null) combi._def.buildSigSrc = resolved.src;
11993
+ if (_entrySource !== null && code !== _entrySource && resolved.src !== null && be.type === "Identifier") {
11994
+ combi._def.buildSrc = resolved.src;
11995
+ }
11996
+ if (combi._def.buildArity === void 0 && resolved.arity !== null) combi._def.buildArity = resolved.arity;
11997
+ if (resolved.reason !== void 0) combi._def.buildArityUnresolved = resolved.reason;
11998
+ }
11965
11999
  const staticError = directBuilderUnsupportedBindings(buildSrc);
11966
12000
  if (staticError.length > 0) combi._def.buildStaticError = staticError;
11967
12001
  }
@@ -12379,9 +12413,9 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
12379
12413
  r._ruleName = name;
12380
12414
  if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
12381
12415
  };
12382
- const ruleNameOf5 = (r) => r._ruleName;
12416
+ const ruleNameOf4 = (r) => r._ruleName;
12383
12417
  const isNamedRuleRefForAnotherRule2 = (r, key) => {
12384
- const name = r._def.tag === "lazy" ? ruleNameOf5(r) : void 0;
12418
+ const name = r._def.tag === "lazy" ? ruleNameOf4(r) : void 0;
12385
12419
  return name !== void 0 && name !== key;
12386
12420
  };
12387
12421
  for (const key of keys) {
@@ -12472,6 +12506,692 @@ function referencesAny(node2, names, scope) {
12472
12506
  return false;
12473
12507
  }
12474
12508
 
12509
+ // src/plugin/reducer-resolver.ts
12510
+ import * as fs from "node:fs";
12511
+ import * as path from "node:path";
12512
+ import { createRequire } from "node:module";
12513
+ import { parseSync as parseSync2 } from "oxc-parser";
12514
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
12515
+ var SCOPE_TYPES = /* @__PURE__ */ new Set([
12516
+ ...FUNCTION_TYPES,
12517
+ "BlockStatement",
12518
+ "ForStatement",
12519
+ "ForInStatement",
12520
+ "ForOfStatement",
12521
+ "CatchClause",
12522
+ "ClassDeclaration",
12523
+ "ClassExpression",
12524
+ "StaticBlock"
12525
+ ]);
12526
+ function newScope(start, end, parent, isFunction) {
12527
+ const s = { start, end, bindings: /* @__PURE__ */ new Map(), children: [], parent, isFunction };
12528
+ parent?.children.push(s);
12529
+ return s;
12530
+ }
12531
+ function patternNames(pat, out) {
12532
+ if (!pat || typeof pat !== "object") return;
12533
+ const n = pat;
12534
+ switch (n.type) {
12535
+ case "Identifier":
12536
+ out.push(n.name);
12537
+ return;
12538
+ case "AssignmentPattern":
12539
+ return patternNames(n.left, out);
12540
+ case "RestElement":
12541
+ return patternNames(n.argument, out);
12542
+ case "ArrayPattern":
12543
+ for (const el of n.elements ?? []) patternNames(el, out);
12544
+ return;
12545
+ case "ObjectPattern":
12546
+ for (const p of n.properties ?? []) patternNames(p.type === "RestElement" ? p.argument : p.value, out);
12547
+ return;
12548
+ // A TS parameter property / assignment target wrapper.
12549
+ case "TSParameterProperty":
12550
+ return patternNames(n.parameter, out);
12551
+ default:
12552
+ return;
12553
+ }
12554
+ }
12555
+ function declare(scope, name, b) {
12556
+ if (!scope.bindings.has(name)) scope.bindings.set(name, b);
12557
+ }
12558
+ function functionScope(scope) {
12559
+ let s = scope;
12560
+ while (!s.isFunction && s.parent) s = s.parent;
12561
+ return s;
12562
+ }
12563
+ function buildScopeTree(body, moduleEnd) {
12564
+ const root = newScope(0, moduleEnd, null, true);
12565
+ const declarePattern = (scope, pat, kind, init) => {
12566
+ const names = [];
12567
+ patternNames(pat, names);
12568
+ const target = kind === "var" ? functionScope(scope) : scope;
12569
+ for (const name of names) {
12570
+ declare(target, name, init !== void 0 && names.length === 1 ? { kind, init } : { kind });
12571
+ }
12572
+ };
12573
+ const walk = (n, scope) => {
12574
+ if (!n || typeof n !== "object") return;
12575
+ if (Array.isArray(n)) {
12576
+ for (const item of n) walk(item, scope);
12577
+ return;
12578
+ }
12579
+ const node2 = n;
12580
+ if (typeof node2.type !== "string") return;
12581
+ switch (node2.type) {
12582
+ case "VariableDeclaration": {
12583
+ const kind = node2.kind ?? "var";
12584
+ for (const d of node2.declarations ?? []) {
12585
+ declarePattern(scope, d.id, kind, d.init ?? void 0);
12586
+ }
12587
+ break;
12588
+ }
12589
+ case "FunctionDeclaration":
12590
+ if (node2.id) declare(functionScope(scope), node2.id.name, { kind: "function", init: node2 });
12591
+ break;
12592
+ case "ClassDeclaration":
12593
+ if (node2.id) declare(scope, node2.id.name, { kind: "class" });
12594
+ break;
12595
+ case "ImportDeclaration": {
12596
+ const specifier = node2.source?.value ?? "";
12597
+ for (const sp of node2.specifiers ?? []) {
12598
+ const local = sp.local?.name;
12599
+ if (!local) continue;
12600
+ const imported = sp.type === "ImportDefaultSpecifier" ? "default" : sp.type === "ImportNamespaceSpecifier" ? "*" : sp.imported?.name ?? local;
12601
+ declare(scope, local, { kind: "import", importRef: { specifier, imported } });
12602
+ }
12603
+ break;
12604
+ }
12605
+ default:
12606
+ break;
12607
+ }
12608
+ if (SCOPE_TYPES.has(node2.type)) {
12609
+ const isFn = FUNCTION_TYPES.has(node2.type);
12610
+ const inner = newScope(node2.start, node2.end, scope, isFn);
12611
+ if (isFn) {
12612
+ for (const p of node2.params ?? []) declarePattern(inner, p, "param");
12613
+ if (node2.type === "FunctionExpression" && node2.id) {
12614
+ declare(inner, node2.id.name, { kind: "function", init: node2 });
12615
+ }
12616
+ }
12617
+ if (node2.type === "CatchClause" && node2.param) declarePattern(inner, node2.param, "catch");
12618
+ for (const key of Object.keys(node2)) {
12619
+ if (key === "type" || key === "start" || key === "end") continue;
12620
+ walk(node2[key], inner);
12621
+ }
12622
+ return;
12623
+ }
12624
+ for (const key of Object.keys(node2)) {
12625
+ if (key === "type" || key === "start" || key === "end") continue;
12626
+ walk(node2[key], scope);
12627
+ }
12628
+ };
12629
+ for (const stmt of body) walk(stmt, root);
12630
+ return root;
12631
+ }
12632
+ function scopeAt(root, offset) {
12633
+ let cur = root;
12634
+ for (; ; ) {
12635
+ const next = cur.children.find((c) => offset >= c.start && offset <= c.end);
12636
+ if (!next) return cur;
12637
+ cur = next;
12638
+ }
12639
+ }
12640
+ function lookup(scope, name) {
12641
+ for (let s = scope; s; s = s.parent) {
12642
+ const b = s.bindings.get(name);
12643
+ if (b) return { binding: b, scope: s };
12644
+ }
12645
+ return null;
12646
+ }
12647
+ function markReassignments(body, root) {
12648
+ const walk = (n) => {
12649
+ if (!n || typeof n !== "object") return;
12650
+ if (Array.isArray(n)) {
12651
+ for (const item of n) walk(item);
12652
+ return;
12653
+ }
12654
+ const node2 = n;
12655
+ if (typeof node2.type !== "string") return;
12656
+ if (node2.type === "AssignmentExpression" || node2.type === "UpdateExpression") {
12657
+ const target = node2.type === "UpdateExpression" ? node2.argument : node2.left;
12658
+ const names = [];
12659
+ if (target) patternNames(target, names);
12660
+ for (const name of names) {
12661
+ const hit = lookup(scopeAt(root, node2.start), name);
12662
+ if (hit) hit.binding.reassigned = true;
12663
+ }
12664
+ }
12665
+ for (const key of Object.keys(node2)) {
12666
+ if (key === "type" || key === "start" || key === "end") continue;
12667
+ walk(node2[key]);
12668
+ }
12669
+ };
12670
+ for (const stmt of body) walk(stmt);
12671
+ }
12672
+ function functionArity(fn) {
12673
+ const params = fn.params ?? [];
12674
+ let count = 0;
12675
+ for (const p of params) {
12676
+ if (p.type === "RestElement") return { arity: null, reason: "rest-parameter" };
12677
+ if (p.type === "Identifier" && p.name === "this") continue;
12678
+ count++;
12679
+ }
12680
+ if (fn.type !== "ArrowFunctionExpression" && referencesArguments(fn.body)) {
12681
+ return { arity: null, reason: "arguments" };
12682
+ }
12683
+ return { arity: count };
12684
+ }
12685
+ function referencesArguments(n) {
12686
+ if (!n || typeof n !== "object") return false;
12687
+ if (Array.isArray(n)) return n.some(referencesArguments);
12688
+ const node2 = n;
12689
+ if (typeof node2.type !== "string") return false;
12690
+ if (node2.type === "Identifier" && node2.name === "arguments") return true;
12691
+ if (node2.type === "FunctionDeclaration" || node2.type === "FunctionExpression") return false;
12692
+ for (const key of Object.keys(node2)) {
12693
+ if (key === "type" || key === "start" || key === "end") continue;
12694
+ if (referencesArguments(node2[key])) return true;
12695
+ }
12696
+ return false;
12697
+ }
12698
+ var _moduleCache = /* @__PURE__ */ new Map();
12699
+ function analyseModuleFile(file) {
12700
+ let mtimeMs;
12701
+ try {
12702
+ mtimeMs = fs.statSync(file).mtimeMs;
12703
+ } catch {
12704
+ return null;
12705
+ }
12706
+ const hit = _moduleCache.get(file);
12707
+ if (hit && hit.mtimeMs === mtimeMs) return hit.mod;
12708
+ let mod = null;
12709
+ try {
12710
+ const src = fs.readFileSync(file, "utf8");
12711
+ const r = parseSync2(file, src);
12712
+ if (r.errors.length === 0) {
12713
+ const body = r.program.body;
12714
+ const scopes = buildScopeTree(body, src.length);
12715
+ markReassignments(body, scopes);
12716
+ mod = { file, src, body, scopes };
12717
+ }
12718
+ } catch {
12719
+ }
12720
+ _moduleCache.set(file, { mtimeMs, mod });
12721
+ return mod;
12722
+ }
12723
+ var SOURCE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".jsx"];
12724
+ function resolveImport(fromFile, specifier) {
12725
+ if (specifier.startsWith(".") || path.isAbsolute(specifier)) {
12726
+ const base = path.resolve(path.dirname(fromFile), specifier);
12727
+ const ext = path.extname(base);
12728
+ const stem = ext ? base.slice(0, -ext.length) : base;
12729
+ const candidates = [
12730
+ ...ext ? [base] : [],
12731
+ // `./x.js` in TS source usually means `./x.ts`.
12732
+ ...SOURCE_EXTS.map((e) => `${stem}${e}`),
12733
+ ...SOURCE_EXTS.map((e) => path.join(base, `index${e}`))
12734
+ ];
12735
+ for (const c of candidates) {
12736
+ try {
12737
+ if (fs.statSync(c).isFile()) return fs.realpathSync(c);
12738
+ } catch {
12739
+ }
12740
+ }
12741
+ return null;
12742
+ }
12743
+ try {
12744
+ return fs.realpathSync(createRequire(fromFile).resolve(specifier));
12745
+ } catch {
12746
+ return null;
12747
+ }
12748
+ }
12749
+ function declarationBinds(decl, name) {
12750
+ if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") {
12751
+ return decl.id?.name === name;
12752
+ }
12753
+ if (decl.type === "VariableDeclaration") {
12754
+ const names = [];
12755
+ for (const d of decl.declarations ?? []) patternNames(d.id, names);
12756
+ return names.includes(name);
12757
+ }
12758
+ return false;
12759
+ }
12760
+ function exportedBinding(mod, name, seen) {
12761
+ for (const stmt of mod.body) {
12762
+ if (stmt.type === "ExportDefaultDeclaration" && name === "default") {
12763
+ const d = stmt.declaration;
12764
+ if (FUNCTION_TYPES.has(d.type)) return { mod, binding: { kind: "const", init: d } };
12765
+ if (d.type === "Identifier") {
12766
+ const hit2 = lookup(mod.scopes, d.name);
12767
+ return hit2 ? { mod, binding: hit2.binding } : null;
12768
+ }
12769
+ return null;
12770
+ }
12771
+ if (stmt.type === "ExportNamedDeclaration") {
12772
+ const decl = stmt.declaration;
12773
+ if (decl && declarationBinds(decl, name)) {
12774
+ const hit2 = lookup(mod.scopes, name);
12775
+ if (hit2 && hit2.scope === mod.scopes) return { mod, binding: hit2.binding };
12776
+ }
12777
+ for (const sp of stmt.specifiers ?? []) {
12778
+ const exported = sp.exported?.name ?? "";
12779
+ if (exported !== name) continue;
12780
+ const local = sp.local?.name ?? exported;
12781
+ const from = stmt.source?.value;
12782
+ if (from !== void 0) return followReExport(mod, from, local, seen);
12783
+ const hit2 = lookup(mod.scopes, local);
12784
+ return hit2 ? { mod, binding: hit2.binding } : null;
12785
+ }
12786
+ }
12787
+ if (stmt.type === "ExportAllDeclaration" && !stmt.exported) {
12788
+ const from = stmt.source?.value;
12789
+ if (from === void 0) continue;
12790
+ const found = followReExport(mod, from, name, seen);
12791
+ if (found) return found;
12792
+ }
12793
+ }
12794
+ const hit = lookup(mod.scopes, name);
12795
+ return hit && hit.scope === mod.scopes ? { mod, binding: hit.binding } : null;
12796
+ }
12797
+ function followReExport(mod, specifier, name, seen) {
12798
+ const file = resolveImport(mod.file, specifier);
12799
+ if (!file || seen.has(`${file}\0${name}`)) return null;
12800
+ seen.add(`${file}\0${name}`);
12801
+ const next = analyseModuleFile(file);
12802
+ return next ? exportedBinding(next, name, seen) : null;
12803
+ }
12804
+ var MAX_HOPS = 12;
12805
+ var IDENT_RE = /^[A-Za-z_$][\w$]*$/;
12806
+ var MEMBER_RE = /^([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)$/;
12807
+ function createReducerResolver(entryFile, body, src) {
12808
+ const scopes = buildScopeTree(body, src.length);
12809
+ markReassignments(body, scopes);
12810
+ const entry = { file: entryFile, src, body, scopes };
12811
+ const registered = /* @__PURE__ */ new Map();
12812
+ const ambiguous = /* @__PURE__ */ new Set();
12813
+ const fromBinding = (mod, binding, memberName, hops, seen) => {
12814
+ if (hops > MAX_HOPS) return { arity: null, src: null, reason: "not-found" };
12815
+ if (binding.reassigned) return { arity: null, src: null, reason: "reassigned" };
12816
+ if (binding.kind === "param" || binding.kind === "catch" || binding.kind === "class") {
12817
+ return { arity: null, src: null, reason: "not-a-function" };
12818
+ }
12819
+ if (binding.kind === "import" && binding.importRef) {
12820
+ const { specifier, imported } = binding.importRef;
12821
+ if (imported === "*" && memberName === void 0) return { arity: null, src: null, reason: "not-a-function" };
12822
+ const file = resolveImport(mod.file, specifier);
12823
+ if (!file) return { arity: null, src: null, reason: "unresolved-import" };
12824
+ const next = analyseModuleFile(file);
12825
+ if (!next) return { arity: null, src: null, reason: "unresolved-import" };
12826
+ const wanted = imported === "*" ? memberName : imported;
12827
+ if (wanted === void 0) return { arity: null, src: null, reason: "not-a-function" };
12828
+ const found = exportedBinding(next, wanted, seen);
12829
+ if (!found) return { arity: null, src: null, reason: "not-found" };
12830
+ return fromBinding(found.mod, found.binding, void 0, hops + 1, seen);
12831
+ }
12832
+ const init = binding.init;
12833
+ if (!init) return { arity: null, src: null, reason: "not-a-function" };
12834
+ if (init.type === "Identifier") {
12835
+ const hit = lookup(scopeAt(mod.scopes, init.start), init.name);
12836
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12837
+ return fromBinding(mod, hit.binding, void 0, hops + 1, seen);
12838
+ }
12839
+ if (init.type === "ComputedMemberExpression" || init.computed === true && init.type.endsWith("MemberExpression")) {
12840
+ return { arity: null, src: null, reason: "computed" };
12841
+ }
12842
+ if (init.type === "StaticMemberExpression" || init.type === "MemberExpression") {
12843
+ const obj = init.object;
12844
+ const prop = init.property;
12845
+ if (obj?.type === "Identifier" && prop?.type === "Identifier") {
12846
+ const hit = lookup(scopeAt(mod.scopes, init.start), obj.name);
12847
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12848
+ return fromBinding(mod, hit.binding, prop.name, hops + 1, seen);
12849
+ }
12850
+ return { arity: null, src: null, reason: "computed" };
12851
+ }
12852
+ if (!FUNCTION_TYPES.has(init.type)) return { arity: null, src: null, reason: "not-a-function" };
12853
+ const { arity, reason } = functionArity(init);
12854
+ return { arity, src: mod.src.slice(init.start, init.end), ...reason ? { reason } : {} };
12855
+ };
12856
+ return {
12857
+ register(file) {
12858
+ const mod = analyseModuleFile(file);
12859
+ if (!mod) return false;
12860
+ if (mod.src === src) return true;
12861
+ const prior = registered.get(mod.src);
12862
+ if (prior !== void 0 && prior.file !== mod.file) {
12863
+ ambiguous.add(mod.src);
12864
+ registered.delete(mod.src);
12865
+ return false;
12866
+ }
12867
+ if (!ambiguous.has(mod.src)) registered.set(mod.src, mod);
12868
+ return !ambiguous.has(mod.src);
12869
+ },
12870
+ resolve(exprSrc, offset, offsetSrc) {
12871
+ const text = exprSrc.trim();
12872
+ const ident = IDENT_RE.test(text) ? text : null;
12873
+ const member = ident ? null : MEMBER_RE.exec(text);
12874
+ if (!ident && !member) return null;
12875
+ const mod = offsetSrc === src ? entry : registered.get(offsetSrc);
12876
+ if (!mod) {
12877
+ return { arity: null, src: null, reason: ambiguous.has(offsetSrc) ? "ambiguous-source" : "foreign-source" };
12878
+ }
12879
+ const name = ident ?? member[1];
12880
+ const memberName = ident ? void 0 : member[2];
12881
+ const hit = lookup(scopeAt(mod.scopes, offset), name);
12882
+ if (!hit) return { arity: null, src: null, reason: "not-found" };
12883
+ return fromBinding(mod, hit.binding, memberName, 0, /* @__PURE__ */ new Set());
12884
+ }
12885
+ };
12886
+ }
12887
+
12888
+ // src/plugin/free-identifiers.ts
12889
+ import { parseSync as parseSync3 } from "oxc-parser";
12890
+ var isNode = (v2) => typeof v2 === "object" && v2 !== null && typeof v2.type === "string";
12891
+ var TYPE_KEYS = /* @__PURE__ */ new Set([
12892
+ "typeAnnotation",
12893
+ "returnType",
12894
+ "typeParameters",
12895
+ "typeArguments",
12896
+ "superTypeArguments",
12897
+ "typeName",
12898
+ "implements",
12899
+ "extends",
12900
+ "accessibility"
12901
+ ]);
12902
+ var TS_VALUE_WRAPPERS = /* @__PURE__ */ new Set([
12903
+ "TSAsExpression",
12904
+ "TSSatisfiesExpression",
12905
+ "TSNonNullExpression",
12906
+ "TSInstantiationExpression",
12907
+ "TSTypeAssertion"
12908
+ ]);
12909
+ var HOST_GLOBALS = /* @__PURE__ */ new Set([
12910
+ ...Object.getOwnPropertyNames(globalThis),
12911
+ "undefined",
12912
+ "NaN",
12913
+ "Infinity",
12914
+ "globalThis",
12915
+ "arguments",
12916
+ "window",
12917
+ "document",
12918
+ "self",
12919
+ "navigator",
12920
+ "location",
12921
+ "top",
12922
+ "parent",
12923
+ "require",
12924
+ "module",
12925
+ "exports",
12926
+ "__dirname",
12927
+ "__filename"
12928
+ ]);
12929
+ function bindPattern(pattern, out) {
12930
+ if (!isNode(pattern)) return;
12931
+ switch (pattern.type) {
12932
+ case "Identifier":
12933
+ case "BindingIdentifier":
12934
+ out.add(String(pattern.name));
12935
+ return;
12936
+ case "ObjectPattern":
12937
+ for (const prop of pattern.properties ?? []) {
12938
+ if (!isNode(prop)) continue;
12939
+ if (prop.type === "RestElement" || prop.type === "BindingRestElement") bindPattern(prop.argument, out);
12940
+ else bindPattern(prop.value, out);
12941
+ }
12942
+ return;
12943
+ case "ArrayPattern":
12944
+ for (const el of pattern.elements ?? []) bindPattern(el, out);
12945
+ return;
12946
+ case "AssignmentPattern":
12947
+ bindPattern(pattern.left, out);
12948
+ return;
12949
+ case "RestElement":
12950
+ case "BindingRestElement":
12951
+ bindPattern(pattern.argument, out);
12952
+ return;
12953
+ case "TSParameterProperty":
12954
+ bindPattern(pattern.parameter, out);
12955
+ return;
12956
+ default:
12957
+ return;
12958
+ }
12959
+ }
12960
+ function collectBindings(node2, out) {
12961
+ if (Array.isArray(node2)) {
12962
+ for (const child of node2) collectBindings(child, out);
12963
+ return;
12964
+ }
12965
+ if (!isNode(node2)) return;
12966
+ switch (node2.type) {
12967
+ case "ImportSpecifier":
12968
+ case "ImportDefaultSpecifier":
12969
+ case "ImportNamespaceSpecifier":
12970
+ if (isNode(node2.local)) out.add(String(node2.local.name));
12971
+ break;
12972
+ case "VariableDeclarator":
12973
+ bindPattern(node2.id, out);
12974
+ break;
12975
+ case "FunctionDeclaration":
12976
+ case "FunctionExpression":
12977
+ case "TSDeclareFunction":
12978
+ case "ArrowFunctionExpression":
12979
+ if (isNode(node2.id)) out.add(String(node2.id.name));
12980
+ for (const p of node2.params?.items ?? node2.params ?? []) bindPattern(p, out);
12981
+ if (isNode(node2.params) && node2.params.rest) bindPattern(node2.params.rest, out);
12982
+ break;
12983
+ case "ClassDeclaration":
12984
+ case "ClassExpression":
12985
+ if (isNode(node2.id)) out.add(String(node2.id.name));
12986
+ break;
12987
+ case "CatchClause":
12988
+ if (node2.param) {
12989
+ bindPattern(isNode(node2.param) && node2.param.pattern ? node2.param.pattern : node2.param, out);
12990
+ }
12991
+ break;
12992
+ default:
12993
+ break;
12994
+ }
12995
+ for (const key of Object.keys(node2)) {
12996
+ if (key === "type" || key === "start" || key === "end") continue;
12997
+ collectBindings(node2[key], out);
12998
+ }
12999
+ }
13000
+ var FUNCTION_TYPES2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
13001
+ function describeTopLevel(stmt) {
13002
+ const declared = /* @__PURE__ */ new Set();
13003
+ let target = stmt;
13004
+ let prefix = "";
13005
+ let exported = false;
13006
+ if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
13007
+ exported = true;
13008
+ prefix = "export ";
13009
+ if (!isNode(stmt.declaration)) return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}{ \u2026 }` };
13010
+ target = stmt.declaration;
13011
+ }
13012
+ if (target.type === "VariableDeclaration") {
13013
+ const decls = (target.declarations ?? []).filter(isNode);
13014
+ for (const d of decls) bindPattern(d.id, declared);
13015
+ const declaresOnlyFunctions = decls.length > 0 && decls.every((d) => isNode(d.init) && FUNCTION_TYPES2.has(d.init.type));
13016
+ const name = [...declared][0];
13017
+ return {
13018
+ declared,
13019
+ exported,
13020
+ declaresOnlyFunctions,
13021
+ label: name ? `${prefix}${String(target.kind)} ${name}` : null
13022
+ };
13023
+ }
13024
+ if (target.type === "FunctionDeclaration" && isNode(target.id)) {
13025
+ declared.add(String(target.id.name));
13026
+ return { declared, exported, declaresOnlyFunctions: true, label: `${prefix}function ${String(target.id.name)}` };
13027
+ }
13028
+ if (target.type === "ClassDeclaration" && isNode(target.id)) {
13029
+ declared.add(String(target.id.name));
13030
+ return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}class ${String(target.id.name)}` };
13031
+ }
13032
+ return { declared, exported, declaresOnlyFunctions: false, label: null };
13033
+ }
13034
+ function findFreeIdentifiers(code, filename) {
13035
+ let parsed;
13036
+ try {
13037
+ parsed = parseSync3(filename, code);
13038
+ } catch {
13039
+ return [];
13040
+ }
13041
+ if (parsed.errors.length > 0) return [];
13042
+ const bound = /* @__PURE__ */ new Set();
13043
+ collectBindings(parsed.program.body, bound);
13044
+ const lineStarts = [0];
13045
+ for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
13046
+ const positionOf = (offset) => {
13047
+ let lo = 0;
13048
+ let hi = lineStarts.length - 1;
13049
+ while (lo < hi) {
13050
+ const mid = lo + hi + 1 >> 1;
13051
+ if (lineStarts[mid] <= offset) lo = mid;
13052
+ else hi = mid - 1;
13053
+ }
13054
+ return { line: lo + 1, column: offset - lineStarts[lo] + 1 };
13055
+ };
13056
+ const statements = parsed.program.body.filter(isNode);
13057
+ const tops = statements.map(describeTopLevel);
13058
+ const candidates = [];
13059
+ const seen = /* @__PURE__ */ new Set();
13060
+ const readIn = /* @__PURE__ */ new Map();
13061
+ const visit = (node2, stmt, insideFunction) => {
13062
+ if (Array.isArray(node2)) {
13063
+ for (const child of node2) visit(child, stmt, insideFunction);
13064
+ return;
13065
+ }
13066
+ if (!isNode(node2)) return;
13067
+ const type = node2.type;
13068
+ if (type === "ImportDeclaration" || type === "ExportAllDeclaration") return;
13069
+ if (type === "MetaProperty" || type === "PrivateIdentifier") return;
13070
+ if (type === "ExportNamedDeclaration" && node2.source) return;
13071
+ if (type.startsWith("TS")) {
13072
+ if (TS_VALUE_WRAPPERS.has(type)) visit(node2.expression, stmt, insideFunction);
13073
+ return;
13074
+ }
13075
+ if (type === "Identifier" || type === "IdentifierReference") {
13076
+ const name = String(node2.name);
13077
+ let readers = readIn.get(name);
13078
+ if (!readers) readIn.set(name, readers = /* @__PURE__ */ new Set());
13079
+ readers.add(stmt);
13080
+ if (bound.has(name) || HOST_GLOBALS.has(name)) return;
13081
+ const offset = typeof node2.start === "number" ? node2.start : 0;
13082
+ const { line, column } = positionOf(offset);
13083
+ const key = `${name}@${line}:${column}`;
13084
+ if (seen.has(key)) return;
13085
+ seen.add(key);
13086
+ candidates.push({ name, line, column, enclosing: tops[stmt].label, stmt, insideFunction });
13087
+ return;
13088
+ }
13089
+ const nowInsideFunction = insideFunction || FUNCTION_TYPES2.has(type);
13090
+ for (const key of Object.keys(node2)) {
13091
+ if (key === "type" || key === "start" || key === "end") continue;
13092
+ if (TYPE_KEYS.has(key)) continue;
13093
+ if (key === "key" && node2.computed !== true) continue;
13094
+ if (key === "property" && node2.computed !== true) continue;
13095
+ if (key === "label") continue;
13096
+ if (key === "exported") continue;
13097
+ if (key === "imported") continue;
13098
+ visit(node2[key], stmt, nowInsideFunction);
13099
+ }
13100
+ };
13101
+ for (let i = 0; i < statements.length; i++) visit(statements[i], i, false);
13102
+ return candidates.filter((c) => {
13103
+ const top = tops[c.stmt];
13104
+ if (!c.insideFunction || top.exported || !top.declaresOnlyFunctions || top.declared.size === 0) return true;
13105
+ for (const name of top.declared) {
13106
+ const readers = readIn.get(name);
13107
+ if (readers && [...readers].some((i) => i !== c.stmt)) return true;
13108
+ }
13109
+ return false;
13110
+ }).map(({ name, line, column, enclosing }) => ({ name, line, column, enclosing }));
13111
+ }
13112
+
13113
+ // src/compiler/module-hoist.ts
13114
+ var marker = (id) => `/*@pmh:${id}@*/`;
13115
+ var MARKER_RE = /\/\*@pmh:(\d+)@\*\/\n?/g;
13116
+ var HOIST_MARKER_PROBE = /\/\*@pmh:\d+@\*\//;
13117
+ var DECL_RE = /^(?:const|let|var|function\*?)\s+([A-Za-z_$][\w$]*)/;
13118
+ var ALWAYS_LOCAL = /* @__PURE__ */ new Set(["_map", "_k", "_env"]);
13119
+ function declaredNames(text) {
13120
+ const out = [];
13121
+ for (const line of text.split("\n")) {
13122
+ const m = DECL_RE.exec(line);
13123
+ if (m) out.push(m[1]);
13124
+ }
13125
+ return out;
13126
+ }
13127
+ function createModuleHoist() {
13128
+ const texts = [];
13129
+ const claim = (decls) => decls.map((text) => {
13130
+ const id = texts.length;
13131
+ texts.push(text);
13132
+ return marker(id);
13133
+ });
13134
+ const finalize = () => {
13135
+ const nameTexts = /* @__PURE__ */ new Map();
13136
+ const textCount = /* @__PURE__ */ new Map();
13137
+ const textNames = /* @__PURE__ */ new Map();
13138
+ const order = [];
13139
+ for (const text of texts) {
13140
+ const seen = textCount.get(text);
13141
+ textCount.set(text, (seen ?? 0) + 1);
13142
+ if (seen === void 0) {
13143
+ order.push(text);
13144
+ const names = declaredNames(text);
13145
+ textNames.set(text, names);
13146
+ for (const n of names) {
13147
+ const s = nameTexts.get(n);
13148
+ if (s) s.add(text);
13149
+ else nameTexts.set(n, /* @__PURE__ */ new Set([text]));
13150
+ }
13151
+ }
13152
+ }
13153
+ const hoisted = new Set(
13154
+ order.filter(
13155
+ (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)
13156
+ )
13157
+ );
13158
+ for (; ; ) {
13159
+ const localNames = [];
13160
+ for (const [n, ts] of nameTexts) {
13161
+ if (ts.size !== 1 || !hoisted.has([...ts][0])) localNames.push(n);
13162
+ }
13163
+ let changed = false;
13164
+ for (const text of [...hoisted]) {
13165
+ if (localNames.some((n) => new RegExp(`\\b${escapeName(n)}\\b`).test(text))) {
13166
+ hoisted.delete(text);
13167
+ changed = true;
13168
+ }
13169
+ }
13170
+ if (!changed) break;
13171
+ }
13172
+ const preludeTexts = order.filter((t) => hoisted.has(t));
13173
+ const prelude = preludeTexts.length === 0 ? "" : `${preludeTexts.join("\n")}
13174
+ `;
13175
+ const removed = texts.reduce((n, t) => hoisted.has(t) ? n + t.length + 1 : n, 0);
13176
+ return {
13177
+ prelude,
13178
+ savedBytes: removed - prelude.length,
13179
+ hoistedNames: preludeTexts.flatMap((t) => textNames.get(t)),
13180
+ resolve: (src) => src.replace(MARKER_RE, (m, idStr) => {
13181
+ const text = texts[Number(idStr)];
13182
+ if (text === void 0) throw new Error(`parseman: unknown hoist marker ${m}`);
13183
+ if (hoisted.has(text)) return "";
13184
+ return m.endsWith("\n") ? `${text}
13185
+ ` : text;
13186
+ })
13187
+ };
13188
+ };
13189
+ return { claim, finalize };
13190
+ }
13191
+ function escapeName(n) {
13192
+ return n.replace(/\$/g, "\\$");
13193
+ }
13194
+
12475
13195
  // src/plugin/index.ts
12476
13196
  import { createHash } from "node:crypto";
12477
13197
  var PARSEMAN_MODULE = "parseman";
@@ -12497,37 +13217,37 @@ function getCompiledResolver() {
12497
13217
  }
12498
13218
  var PRIVATE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
12499
13219
  function nearestPackageRoot(file) {
12500
- let dir = path.dirname(file);
13220
+ let dir = path2.dirname(file);
12501
13221
  for (; ; ) {
12502
13222
  try {
12503
- if (fs.statSync(path.join(dir, "package.json")).isFile()) return fs.realpathSync(dir);
13223
+ if (fs2.statSync(path2.join(dir, "package.json")).isFile()) return fs2.realpathSync(dir);
12504
13224
  } catch {
12505
13225
  }
12506
- const parent = path.dirname(dir);
13226
+ const parent = path2.dirname(dir);
12507
13227
  if (parent === dir) return null;
12508
13228
  dir = parent;
12509
13229
  }
12510
13230
  }
12511
13231
  function isWithinPackageRoot(file, root) {
12512
- const relative2 = path.relative(root, file);
12513
- return relative2 !== ".." && !relative2.startsWith(`..${path.sep}`) && !path.isAbsolute(relative2);
13232
+ const relative2 = path2.relative(root, file);
13233
+ return relative2 !== ".." && !relative2.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative2);
12514
13234
  }
12515
13235
  function resolvePrivateSourceModule(from, specifier) {
12516
- if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return null;
13236
+ if (!specifier.startsWith(".") && !path2.isAbsolute(specifier)) return null;
12517
13237
  const packageRoot = nearestPackageRoot(from);
12518
13238
  if (!packageRoot) return null;
12519
- const base = path.resolve(path.dirname(from), specifier);
12520
- const ext = path.extname(base);
13239
+ const base = path2.resolve(path2.dirname(from), specifier);
13240
+ const ext = path2.extname(base);
12521
13241
  const candidates = [
12522
13242
  base,
12523
13243
  ...ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base.slice(0, -ext.length)}${next}`) : [],
12524
13244
  ...!ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base}${next}`) : [],
12525
- ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path.join(base, next)) : []
13245
+ ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path2.join(base, next)) : []
12526
13246
  ];
12527
13247
  for (const candidate of candidates) {
12528
13248
  try {
12529
- if (!fs.statSync(candidate).isFile()) continue;
12530
- const real = fs.realpathSync(candidate);
13249
+ if (!fs2.statSync(candidate).isFile()) continue;
13250
+ const real = fs2.realpathSync(candidate);
12531
13251
  if (isWithinPackageRoot(real, packageRoot)) return real;
12532
13252
  } catch {
12533
13253
  }
@@ -12535,7 +13255,7 @@ function resolvePrivateSourceModule(from, specifier) {
12535
13255
  return null;
12536
13256
  }
12537
13257
  function resolveGrammarModule(from, specifier) {
12538
- if (PRIVATE_SOURCE_EXTENSIONS.has(path.extname(specifier))) {
13258
+ if (PRIVATE_SOURCE_EXTENSIONS.has(path2.extname(specifier))) {
12539
13259
  const file2 = resolvePrivateSourceModule(from, specifier);
12540
13260
  return file2 ? { file: file2, source: true } : null;
12541
13261
  }
@@ -12556,7 +13276,7 @@ var _moduleParseCache = /* @__PURE__ */ new Map();
12556
13276
  function parseModuleCached(filePath) {
12557
13277
  let mtimeMs;
12558
13278
  try {
12559
- mtimeMs = fs.statSync(filePath).mtimeMs;
13279
+ mtimeMs = fs2.statSync(filePath).mtimeMs;
12560
13280
  } catch {
12561
13281
  return null;
12562
13282
  }
@@ -12564,8 +13284,8 @@ function parseModuleCached(filePath) {
12564
13284
  if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
12565
13285
  let parsed = null;
12566
13286
  try {
12567
- const src = fs.readFileSync(filePath, "utf8");
12568
- const r = parseSync2(filePath, src);
13287
+ const src = fs2.readFileSync(filePath, "utf8");
13288
+ const r = parseSync4(filePath, src);
12569
13289
  if (r.errors.length === 0) parsed = { body: r.program.body, src };
12570
13290
  } catch {
12571
13291
  }
@@ -12693,7 +13413,7 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12693
13413
  if (_sourceLowering.has(file)) return null;
12694
13414
  let mtimeMs;
12695
13415
  try {
12696
- mtimeMs = fs.statSync(file).mtimeMs;
13416
+ mtimeMs = fs2.statSync(file).mtimeMs;
12697
13417
  } catch {
12698
13418
  return null;
12699
13419
  }
@@ -12703,10 +13423,10 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12703
13423
  _sourceLowering.add(file);
12704
13424
  let parsedModule = null;
12705
13425
  try {
12706
- const source = fs.readFileSync(file, "utf8");
13426
+ const source = fs2.readFileSync(file, "utf8");
12707
13427
  const transformed = transformMacro(source, file, moduleAliases, warnUnloweredRegex, recovery);
12708
13428
  if (!transformed || transformed.warnings.length > 0) return null;
12709
- const parsed = parseSync2(file, transformed.code);
13429
+ const parsed = parseSync4(file, transformed.code);
12710
13430
  parsedModule = parsed.errors.length === 0 ? { body: parsed.program.body, src: transformed.code } : null;
12711
13431
  return parsedModule;
12712
13432
  } catch {
@@ -12717,9 +13437,20 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
12717
13437
  }
12718
13438
  }
12719
13439
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false, grammarCoverage = false) {
13440
+ const depth = degradationCaptureDepth();
13441
+ try {
13442
+ return transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage);
13443
+ } finally {
13444
+ setReducerResolver(null);
13445
+ endLoweringCapture();
13446
+ endInlineCapCapture();
13447
+ for (const d of unwindDegradationCapture(depth)) console.warn(formatDegradation(d));
13448
+ }
13449
+ }
13450
+ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage) {
12720
13451
  let result;
12721
13452
  try {
12722
- result = parseSync2(id, code);
13453
+ result = parseSync4(id, code);
12723
13454
  } catch {
12724
13455
  return null;
12725
13456
  }
@@ -12776,6 +13507,8 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12776
13507
  }
12777
13508
  }
12778
13509
  }
13510
+ const reducerResolver = createReducerResolver(id, body, code);
13511
+ setReducerResolver(reducerResolver, code);
12779
13512
  const topLevelFunction = (moduleBody, name) => {
12780
13513
  for (const st of moduleBody) {
12781
13514
  const decl = st.type === "ExportNamedDeclaration" ? st.declaration : st;
@@ -12870,13 +13603,18 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12870
13603
  scope: factoryScope,
12871
13604
  imported: true
12872
13605
  });
13606
+ reducerResolver.register(file);
12873
13607
  }
12874
13608
  const scope = /* @__PURE__ */ new Map();
12875
13609
  const replacements = [];
12876
13610
  const warnings = [];
13611
+ const moduleHoist = grammarCoverage ? void 0 : createModuleHoist();
12877
13612
  beginLoweringCapture();
13613
+ beginInlineCapCapture();
13614
+ beginDegradationCapture();
12878
13615
  let anyUnresolved = false;
12879
13616
  let keepMacroImport = false;
13617
+ const exportedFactories = [];
12880
13618
  let runtimeComposeFallback = false;
12881
13619
  let ruleMapHolderCounter = 0;
12882
13620
  const lineOf = (pos) => {
@@ -12990,7 +13728,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12990
13728
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
12991
13729
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
12992
13730
  ).join(", ")}])`;
12993
- 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: [] }`;
13731
+ 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: [] }`;
12994
13732
  };
12995
13733
  const isSpread = (it) => "__spreadLocal" in it;
12996
13734
  const isIR = (it) => "ir" in it && !("ruleFns" in it);
@@ -13001,9 +13739,19 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13001
13739
  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})`;
13002
13740
  const withGrammarReflection = (grammarExpr, reflection) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.grammarReflection'), { value: ${grammarReflectionSource(reflection)}, enumerable: false })`;
13003
13741
  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 })`;
13004
- const emittedCoverageDefinitions = (source) => {
13742
+ const emittedCoverageDefinitions = (source, where) => {
13005
13743
  const ids = /* @__PURE__ */ new Set();
13006
13744
  for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
13745
+ if (grammarCoverage && ids.size === 0) {
13746
+ recordDegradation({
13747
+ code: "coverage-definitions-unavailable",
13748
+ severity: "warn",
13749
+ where,
13750
+ subject: "generated coverage hooks",
13751
+ 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",
13752
+ otherwise: "the emitted hook IDs would form the coverage denominator"
13753
+ });
13754
+ }
13007
13755
  return [...ids].sort().map((id2) => ({
13008
13756
  id: id2,
13009
13757
  kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : id2.startsWith("dispatch:") ? "dispatch-arm" : "choice-arm"
@@ -13072,26 +13820,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13072
13820
  }
13073
13821
  return out;
13074
13822
  };
13075
- const carriedRuleMapsDetailed = (items) => {
13076
- const maps = [];
13077
- const opaque = [];
13078
- const add = (it) => {
13079
- if (isIR(it)) {
13080
- maps.push(evalRuleMapIR(it.ir));
13081
- return;
13082
- }
13083
- const o = it;
13084
- opaque.push({
13085
- ns: o.ns ?? "<unknown>",
13086
- ruleNames: o.ruleFns instanceof Map ? [...o.ruleFns.keys()] : []
13087
- });
13088
- };
13089
- for (const it of items) {
13090
- if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
13091
- else add(it);
13092
- }
13093
- return { maps, opaque };
13094
- };
13095
13823
  const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
13096
13824
  const sources = [];
13097
13825
  const add = (item) => {
@@ -13242,16 +13970,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13242
13970
  carried.push(...r.carried);
13243
13971
  importedFactories.push(...r.importedFactories ?? []);
13244
13972
  }
13245
- const detailed = once(() => carriedRuleMapsDetailed(carried));
13246
- runFusedGatingDiagnostic(
13247
- () => detailed().maps,
13248
- void 0,
13249
- () => detailed().opaque
13250
- );
13251
13973
  const pieces = materializeCarried(carried, composing, false, cHostMode);
13252
13974
  try {
13253
13975
  return {
13254
- replacement: emitFusedSource(pieces),
13976
+ replacement: emitFusedSource(pieces, moduleHoist),
13255
13977
  carried,
13256
13978
  ...composing ? { trivia: composing } : {},
13257
13979
  ...importedFactories.length ? { importedFactories } : {}
@@ -13304,12 +14026,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13304
14026
  }
13305
14027
  try {
13306
14028
  const localNs = nsFor(`composeLeaf${init.start}`);
13307
- const detailedLeaf = once(() => carriedRuleMapsDetailed(carried));
13308
- runFusedGatingDiagnostic(
13309
- () => [...detailedLeaf().maps, [...localRules]],
13310
- void 0,
13311
- () => detailedLeaf().opaque
13312
- );
13313
14029
  const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
13314
14030
  if (!plainLocalPiece) {
13315
14031
  warn(init.start, "composeLeaf(): local rules could not be statically compiled");
@@ -13325,9 +14041,9 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13325
14041
  warn(init.start, "composeLeaf(): every pre-final grammar must explicitly prove recognition-only");
13326
14042
  return null;
13327
14043
  }
13328
- const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece]));
14044
+ const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece], moduleHoist));
13329
14045
  return {
13330
- replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)),
14046
+ replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement, `${id} composeLeaf()`)),
13331
14047
  ...importedFactories.length ? { importedFactories } : {}
13332
14048
  };
13333
14049
  } catch (e) {
@@ -13407,7 +14123,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13407
14123
  const refEntry = scope.get(varName);
13408
14124
  const refCombi = refEntry?.combi ?? null;
13409
14125
  if (refCombi) {
13410
- const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
14126
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
13411
14127
  if (compiled2.inlineExpression === null) {
13412
14128
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
13413
14129
  continue;
@@ -13438,7 +14154,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13438
14154
  const source = compiledRules.replacement ?? code.slice(init.start, init.end);
13439
14155
  let replacement = withCoverageDefinitions(
13440
14156
  source,
13441
- compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
14157
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source, `${id} rules()`)
13442
14158
  );
13443
14159
  if (compiledRules.replacement !== null) {
13444
14160
  replacement = withHostMode(replacement, compiledRules.hostMode, compiledRules.hostBranchElided);
@@ -13463,7 +14179,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13463
14179
  localComposedCarried.set(varName, fused.carried);
13464
14180
  if (fused.trivia) localComposedTrivia.set(varName, fused.trivia);
13465
14181
  const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
13466
- replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) });
14182
+ replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement, `${id} compose()`)) });
13467
14183
  markUsedImportedFactories(fused.importedFactories);
13468
14184
  continue;
13469
14185
  }
@@ -13510,11 +14226,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13510
14226
  scope.set(varName, staticValue);
13511
14227
  continue;
13512
14228
  }
13513
- if (factoryDecls.has(varName)) continue;
14229
+ if (factoryDecls.has(varName)) {
14230
+ if (exportPrefix) exportedFactories.push({ name: varName, pos: init.start });
14231
+ continue;
14232
+ }
13514
14233
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
13515
14234
  continue;
13516
14235
  }
13517
- const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
14236
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
13518
14237
  if (compiled.inlineExpression === null) {
13519
14238
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
13520
14239
  continue;
@@ -13581,6 +14300,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13581
14300
  for (const imp of macroImports) {
13582
14301
  imp.fullyResolved = !anyUnresolved && !keepMacroImport;
13583
14302
  }
14303
+ if (exportedFactories.length > 0 && macroImports.some((imp) => imp.fullyResolved)) {
14304
+ const which = exportedFactories.map((f) => ` - ${id}:${lineOf(f.pos)} \u2014 export const ${f.name}`).join("\n");
14305
+ throw new Error(
14306
+ `${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:
14307
+ ${which}
14308
+ Drop the \`export\`. The factory is macro-only; \`rules(${exportedFactories[0].name})\` is lowered in place and needs no runtime value.`
14309
+ );
14310
+ }
13584
14311
  const ms = new MagicString(code);
13585
14312
  const replacementRanges = runtimeComposeFallback ? [] : replacements.map(({ start, end }) => ({ start, end }));
13586
14313
  const isInsideReplacement = (start, end) => replacementRanges.some((r) => start >= r.start && end <= r.end);
@@ -13629,8 +14356,21 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13629
14356
  }
13630
14357
  }
13631
14358
  const applied = runtimeComposeFallback ? [] : replacements;
14359
+ const hoisted = applied.length > 0 ? moduleHoist?.finalize() : void 0;
13632
14360
  for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
13633
- ms.overwrite(start, end, replacement);
14361
+ ms.overwrite(start, end, hoisted ? hoisted.resolve(replacement) : replacement);
14362
+ }
14363
+ if (hoisted !== void 0 && hoisted.prelude !== "") {
14364
+ const claiming = applied.filter((r) => HOIST_MARKER_PROBE.test(r.replacement));
14365
+ if (claiming.length === 0) {
14366
+ throw new Error(`${id} \u2014 internal: a module-hoist prelude was produced but no applied replacement carries a hoist marker`);
14367
+ }
14368
+ const firstStart = Math.min(...claiming.map((r) => r.start));
14369
+ const anchor = body.find((s) => s.start <= firstStart && firstStart < s.end);
14370
+ if (anchor === void 0) {
14371
+ throw new Error(`${id} \u2014 internal: no top-level statement encloses macro replacement at ${firstStart}`);
14372
+ }
14373
+ ms.appendLeft(anchor.start, hoisted.prelude);
13634
14374
  }
13635
14375
  if (applied.length > 0) {
13636
14376
  ms.prepend(
@@ -13640,12 +14380,38 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13640
14380
  `
13641
14381
  );
13642
14382
  }
14383
+ if (applied.length > 0) {
14384
+ let sourceFree = null;
14385
+ const freeInSource = () => sourceFree ??= new Set(findFreeIdentifiers(code, id).map((f) => f.name));
14386
+ const free = findFreeIdentifiers(ms.toString(), id).filter((f) => !freeInSource().has(f.name));
14387
+ if (free.length > 0) {
14388
+ const macroProvided = free.filter((f) => allNames.has(f.name));
14389
+ const where = (f) => ` - ${id}:${f.line}:${f.column} \u2014 \`${f.name}\`` + (f.enclosing ? `, inside \`${f.enclosing}\`` : "");
14390
+ const cause = macroProvided.length > 0 ? `
14391
+ ${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.` : "";
14392
+ throw new Error(
14393
+ `${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):
14394
+ ${free.map(where).join("\n")}${cause}`
14395
+ );
14396
+ }
14397
+ }
14398
+ for (const line of formatInlineCapSites(endInlineCapCapture(), resolveInlineMax())) warnings.push(`${id}: ${line}`);
13643
14399
  const unlowered = endLoweringCapture();
13644
14400
  if (warnUnloweredRegex) {
13645
14401
  for (const src of unlowered) {
13646
14402
  warnings.push(`${id}: regex ${src} did not lower to a fast charCodeAt scan (RegExp.exec fallback)`);
13647
14403
  }
13648
14404
  }
14405
+ setReducerResolver(null);
14406
+ const degradations = endDegradationCapture();
14407
+ if (degradations.length > 0) {
14408
+ const lines = formatDegradations(degradations);
14409
+ if (resolveDegradationLevel() === "error") {
14410
+ throw new Error(`parseman: ${degradations.length} degraded compilation path(s) in ${id}
14411
+ ${lines.join("\n")}`);
14412
+ }
14413
+ for (const l of lines) warnings.push(`${id}: ${l}`);
14414
+ }
13649
14415
  return {
13650
14416
  code: ms.toString(),
13651
14417
  map: ms.generateMap({ hires: true }),