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