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