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
package/dist/index.cjs CHANGED
@@ -21,7 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  GRAMMAR_COVERAGE_DEFINITIONS: () => GRAMMAR_COVERAGE_DEFINITIONS,
24
- OffsetIndex: () => OffsetIndex,
25
24
  absoluteSpanAt: () => absoluteSpanAt,
26
25
  absoluteSpanCST: () => absoluteSpanCST,
27
26
  absolutize: () => absolutize,
@@ -37,16 +36,12 @@ __export(index_exports, {
37
36
  applyEdit: () => applyEdit,
38
37
  attempt: () => attempt,
39
38
  balanced: () => balanced,
40
- blankLinesIn: () => blankLinesIn,
41
39
  buildLineIndex: () => buildLineIndex,
42
- buildOffsetIndex: () => buildOffsetIndex,
43
40
  buildRootTriviaIndex: () => buildRootTriviaIndex,
44
41
  buildTriviaIndex: () => buildTriviaIndex,
45
42
  charClassMembers: () => charClassMembers,
46
43
  choice: () => choice,
47
44
  classifiedTrivia: () => classifiedTrivia,
48
- collectLeafSlots: () => collectLeafSlots,
49
- commentsIn: () => commentsIn,
50
45
  compile: () => compile,
51
46
  compiledGrammarCoverageDefinitions: () => compiledGrammarCoverageDefinitions,
52
47
  completionsAt: () => completionsAt,
@@ -59,6 +54,7 @@ __export(index_exports, {
59
54
  createLineIndex: () => createLineIndex,
60
55
  createVisitor: () => createVisitor,
61
56
  cstBuildHost: () => cstBuildHost,
57
+ diagnoseGrammar: () => diagnoseGrammar,
62
58
  dispatch: () => dispatch,
63
59
  duplicationFindingCount: () => duplicationFindingCount,
64
60
  endsWith: () => endsWith,
@@ -68,21 +64,15 @@ __export(index_exports, {
68
64
  firstSetToString: () => firstSetToString,
69
65
  formatDuplicationFindings: () => formatDuplicationFindings,
70
66
  formatGatingWarnings: () => formatGatingWarnings,
71
- gapIsSignificant: () => gapIsSignificant,
72
- gapText: () => gapText,
67
+ formatGrammarDiagnosis: () => formatGrammarDiagnosis,
73
68
  gate: () => gate,
74
69
  grammarCoverageDefinitions: () => grammarCoverageDefinitions,
75
- guard: () => guard,
76
- indentMixed: () => indentMixed,
77
- indentWidth: () => indentWidth,
78
70
  isParseError: () => isParseError,
79
71
  keywordAlternationHazards: () => keywordAlternationHazards,
80
72
  keywordRegexShape: () => keywordRegexShape,
81
73
  keywords: () => keywords,
82
74
  label: () => label,
83
75
  leaf: () => leaf,
84
- lineBreaksIn: () => lineBreaksIn,
85
- lineStartWithin: () => lineStartWithin,
86
76
  literal: () => literal,
87
77
  makeWhen: () => makeWhen,
88
78
  makeWord: () => makeWord,
@@ -181,8 +171,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
181
171
  return d.value.length === 0;
182
172
  case "keywords":
183
173
  return false;
174
+ // The routed token itself is the selector's match, never empty; a fallback can be.
184
175
  case "routed":
185
- return false;
176
+ return d.fallback === void 0 ? false : me(d.fallback);
186
177
  case "regex":
187
178
  try {
188
179
  const m = new RegExp(d.source).exec("");
@@ -1944,8 +1935,11 @@ function derive(c, seen) {
1944
1935
  case "not":
1945
1936
  case "peek":
1946
1937
  return deriveExpected2(def.parser);
1938
+ // A bare routed() can only fail as "there is no routed token here". With a
1939
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1940
+ // ones — the routed path either succeeds or is not taken.
1947
1941
  case "routed":
1948
- return ["routed()"];
1942
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1949
1943
  case "lazy": {
1950
1944
  const name = c._ruleName;
1951
1945
  if (seen.has(c)) return name ? [name] : [];
@@ -2135,7 +2129,7 @@ function detectStrategy(parsers) {
2135
2129
  function bareLeadingTermKey(term) {
2136
2130
  const d = term._def;
2137
2131
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
2138
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
2132
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
2139
2133
  return null;
2140
2134
  }
2141
2135
  function detectSharedPrefix(parsers) {
@@ -2393,17 +2387,21 @@ function makeWhen(opts = {}) {
2393
2387
  function otherwise(parser2) {
2394
2388
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2395
2389
  }
2396
- function routed() {
2390
+ function routed(fallback) {
2397
2391
  return {
2398
2392
  _tag: "routed",
2399
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2400
- _def: { tag: "routed" },
2393
+ _meta: {
2394
+ // The routed token is whatever the selector matched, so the first set stays
2395
+ // `any` even with a fallback — `any` already subsumes the fallback's.
2396
+ firstSet: { kind: "any" },
2397
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
2398
+ isTrivia: false
2399
+ },
2400
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2401
2401
  parse(_input, pos, ctx) {
2402
2402
  const item = ctx._routed;
2403
- if (item === void 0) {
2404
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2405
- }
2406
- if (pos !== item.span.start) {
2403
+ if (item === void 0 || pos !== item.span.start) {
2404
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2407
2405
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2408
2406
  }
2409
2407
  if (cstCaptureActive(ctx)) {
@@ -2947,12 +2945,12 @@ function markUnusedValues(root) {
2947
2945
  }
2948
2946
  };
2949
2947
  const resolveRoot = (c) => {
2950
- const guard2 = /* @__PURE__ */ new Set();
2948
+ const guard = /* @__PURE__ */ new Set();
2951
2949
  let cur = c;
2952
- while (!guard2.has(cur)) {
2950
+ while (!guard.has(cur)) {
2953
2951
  const d = cur._def;
2954
2952
  if (d.tag !== "lazy") return cur;
2955
- guard2.add(cur);
2953
+ guard.add(cur);
2956
2954
  try {
2957
2955
  cur = d.thunk();
2958
2956
  } catch {
@@ -3121,10 +3119,11 @@ function childrenOf(def) {
3121
3119
  return [def.main, def.skipped];
3122
3120
  case "scanTo":
3123
3121
  return [def.sentinel, ...def.skip];
3122
+ case "routed":
3123
+ return def.fallback ? [def.fallback] : [];
3124
3124
  case "literal":
3125
3125
  case "regex":
3126
3126
  case "keywords":
3127
- case "routed":
3128
3127
  case "guard":
3129
3128
  case "lazy":
3130
3129
  case "unknown":
@@ -3350,54 +3349,271 @@ function peek(combinator) {
3350
3349
  };
3351
3350
  }
3352
3351
 
3352
+ // src/compiler/degradation.ts
3353
+ function formatDegradation(d) {
3354
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
3355
+ }
3356
+ var DETAIL_CAP = 8;
3357
+ function formatDegradations(list) {
3358
+ const byCode = /* @__PURE__ */ new Map();
3359
+ for (const d of list) {
3360
+ const bucket = byCode.get(d.code);
3361
+ if (bucket) bucket.push(d);
3362
+ else byCode.set(d.code, [d]);
3363
+ }
3364
+ const out = [];
3365
+ for (const [code, items] of byCode) {
3366
+ for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
3367
+ if (items.length > DETAIL_CAP) {
3368
+ out.push(
3369
+ `[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.`
3370
+ );
3371
+ }
3372
+ }
3373
+ return out;
3374
+ }
3375
+ var _sinks = [];
3376
+ function beginDegradationCapture() {
3377
+ _sinks.push(/* @__PURE__ */ new Map());
3378
+ }
3379
+ function degradationCaptureDepth() {
3380
+ return _sinks.length;
3381
+ }
3382
+ function unwindDegradationCapture(depth) {
3383
+ const found = [];
3384
+ while (_sinks.length > depth) {
3385
+ const sink = _sinks.pop();
3386
+ found.unshift(...sink.values());
3387
+ }
3388
+ return found;
3389
+ }
3390
+ function resolveDegradationLevel(explicit) {
3391
+ if (explicit !== void 0) return explicit;
3392
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
3393
+ if (env === "off" || env === "warn" || env === "error") return env;
3394
+ return "warn";
3395
+ }
3396
+ function degradationKey(d) {
3397
+ return JSON.stringify([d.code, d.where, d.subject]);
3398
+ }
3399
+ function recordDegradation(d) {
3400
+ const level = resolveDegradationLevel();
3401
+ if (level === "off") return;
3402
+ const key = degradationKey(d);
3403
+ const sink = _sinks[_sinks.length - 1];
3404
+ if (sink) {
3405
+ if (!sink.has(key)) sink.set(key, d);
3406
+ return;
3407
+ }
3408
+ if (_immediate.has(key)) return;
3409
+ _immediate.add(key);
3410
+ if (level === "error") throw new Error(`parseman: degraded compilation path
3411
+ ${formatDegradation(d)}`);
3412
+ console.warn(formatDegradation(d));
3413
+ }
3414
+ var _immediate = /* @__PURE__ */ new Set();
3415
+ function beginCompileDegradationDrain() {
3416
+ if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
3417
+ };
3418
+ const depth = degradationCaptureDepth();
3419
+ beginDegradationCapture();
3420
+ return (report) => {
3421
+ const found = unwindDegradationCapture(depth);
3422
+ if (!report || found.length === 0) return;
3423
+ const fresh = found.filter((d) => {
3424
+ const k = degradationKey(d);
3425
+ if (_immediate.has(k)) return false;
3426
+ _immediate.add(k);
3427
+ return true;
3428
+ });
3429
+ if (fresh.length === 0) return;
3430
+ const lines = formatDegradations(fresh);
3431
+ if (resolveDegradationLevel() === "error") {
3432
+ throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
3433
+ ${lines.join("\n")}`);
3434
+ }
3435
+ for (const l of lines) console.warn(l);
3436
+ };
3437
+ }
3438
+
3353
3439
  // src/compiler/build-arity.ts
3354
3440
  function cstOutputHost(build) {
3355
3441
  return build?._parsemanCstOutput === true;
3356
3442
  }
3357
- var PARAM_LIST_RE = (
3358
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
3359
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
3360
- );
3361
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
3443
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
3444
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
3445
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
3446
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
3362
3447
  function confirmedBuildArity(src) {
3363
3448
  const s = src.trim();
3364
- const m = PARAM_LIST_RE.exec(s);
3365
- if (!m) return null;
3366
- if (m[3] !== void 0) return 1;
3367
- const inner = (m[1] ?? m[2] ?? "").trim();
3368
- if (inner === "") return 0;
3369
- const parts = inner.split(",");
3449
+ if (NATIVE_CODE_RE.test(s)) return null;
3450
+ if (/\barguments\b/.test(s)) return null;
3451
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
3452
+ const inner = paramListText(s);
3453
+ if (inner === null) return null;
3454
+ if (inner.trim() === "") return 0;
3455
+ const parts = splitTopLevel(inner);
3456
+ if (parts === null) return null;
3370
3457
  for (const part of parts) {
3371
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
3458
+ if (!isConfirmableParam(part.trim())) return null;
3372
3459
  }
3373
- if (/\barguments\b/.test(s)) return null;
3374
3460
  return parts.length;
3375
3461
  }
3462
+ function paramListText(s) {
3463
+ const open = PARAM_LIST_OPEN_RE.exec(s);
3464
+ if (!open) return null;
3465
+ const start = open[0].length;
3466
+ const end = matchingParen(s, start);
3467
+ if (end === null) return null;
3468
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
3469
+ return s.slice(start, end);
3470
+ }
3471
+ function matchingParen(s, start) {
3472
+ let depth = 0;
3473
+ for (let i = start; i < s.length; i++) {
3474
+ const c = s[i];
3475
+ if (c === '"' || c === "'" || c === "`") {
3476
+ const close = skipString(s, i);
3477
+ if (close === null) return null;
3478
+ i = close;
3479
+ continue;
3480
+ }
3481
+ if (c === "(" || c === "[" || c === "{") depth++;
3482
+ else if (c === "]" || c === "}") {
3483
+ if (--depth < 0) return null;
3484
+ } else if (c === ")") {
3485
+ if (depth === 0) return i;
3486
+ depth--;
3487
+ }
3488
+ }
3489
+ return null;
3490
+ }
3491
+ function skipString(s, open) {
3492
+ const quote = s[open];
3493
+ for (let i = open + 1; i < s.length; i++) {
3494
+ if (s[i] === "\\") {
3495
+ i++;
3496
+ continue;
3497
+ }
3498
+ if (s[i] === quote) return i;
3499
+ }
3500
+ return null;
3501
+ }
3502
+ function splitTopLevel(inner) {
3503
+ const parts = [];
3504
+ let depth = 0;
3505
+ let angle = 0;
3506
+ let last = 0;
3507
+ for (let i = 0; i < inner.length; i++) {
3508
+ const c = inner[i];
3509
+ if (c === "=" && inner[i + 1] === ">") {
3510
+ i++;
3511
+ continue;
3512
+ }
3513
+ if (c === '"' || c === "'" || c === "`") {
3514
+ const close = skipString(inner, i);
3515
+ if (close === null) return null;
3516
+ i = close;
3517
+ continue;
3518
+ }
3519
+ if (c === "(" || c === "[" || c === "{") depth++;
3520
+ else if (c === ")" || c === "]" || c === "}") {
3521
+ if (--depth < 0) return null;
3522
+ } else if (c === "<") angle++;
3523
+ else if (c === ">") {
3524
+ if (--angle < 0) return null;
3525
+ } else if (c === "," && depth === 0 && angle === 0) {
3526
+ parts.push(inner.slice(last, i));
3527
+ last = i + 1;
3528
+ }
3529
+ }
3530
+ if (depth !== 0 || angle !== 0) return null;
3531
+ parts.push(inner.slice(last));
3532
+ return parts;
3533
+ }
3534
+ function isConfirmableParam(part) {
3535
+ const head = PARAM_HEAD_RE.exec(part);
3536
+ if (!head) return false;
3537
+ const rest = part.slice(head[0].length);
3538
+ if (rest === "") return true;
3539
+ if (rest[0] !== ":") return false;
3540
+ return !hasDefaultAssign(rest.slice(1));
3541
+ }
3542
+ function hasDefaultAssign(annotation) {
3543
+ for (let i = 0; i < annotation.length; i++) {
3544
+ if (annotation[i] !== "=") continue;
3545
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
3546
+ i++;
3547
+ continue;
3548
+ }
3549
+ const prev = annotation[i - 1];
3550
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
3551
+ return true;
3552
+ }
3553
+ return false;
3554
+ }
3555
+ function buildAnalysisSrc(def) {
3556
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
3557
+ }
3558
+ var RESOLVER_REASONS = {
3559
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
3560
+ "arguments": "its body references `arguments`",
3561
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
3562
+ "not-a-function": "the binding it names is not a function declaration",
3563
+ "unresolved-import": "its module could not be resolved or parsed",
3564
+ "not-found": "the name did not resolve to any binding",
3565
+ "computed": "it is a computed expression",
3566
+ "foreign-source": "it is named inside an imported factory, whose offsets the entry-file resolver cannot index"
3567
+ };
3568
+ function unconfirmableReason(def, src) {
3569
+ const declared = def.buildArityUnresolved;
3570
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
3571
+ const s = src.trim();
3572
+ const params = s.slice(0, s.indexOf(")") + 1);
3573
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
3574
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
3575
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
3576
+ return "it names a binding this build could not resolve to a function declaration";
3577
+ }
3578
+ return "its parameter list could not be parsed";
3579
+ }
3580
+ function confirmedArityForDef(def) {
3581
+ if (def.buildArity !== void 0) return def.buildArity;
3582
+ const src = buildAnalysisSrc(def);
3583
+ const arity = confirmedBuildArity(src);
3584
+ if (arity !== null) return arity;
3585
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
3586
+ recordDegradation({
3587
+ code: "build-arity-unconfirmed",
3588
+ severity: "warn",
3589
+ where: `node("${def.type ?? "<inferred>"}")`,
3590
+ subject,
3591
+ 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`,
3592
+ 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"
3593
+ });
3594
+ return null;
3595
+ }
3376
3596
  function buildReadsChildren(def) {
3377
3597
  if (!def.build) return true;
3378
- const src = def.buildSrc ?? def.build.toString();
3379
- const arity = confirmedBuildArity(src);
3598
+ const arity = confirmedArityForDef(def);
3380
3599
  if (arity === null) return true;
3381
3600
  return arity >= 1;
3382
3601
  }
3383
3602
  function buildReadsRaw(def) {
3384
3603
  if (!def.build) return true;
3385
- const src = def.buildSrc ?? def.build.toString();
3386
- const arity = confirmedBuildArity(src);
3604
+ const arity = confirmedArityForDef(def);
3387
3605
  if (arity === null) return true;
3388
3606
  return arity >= 4;
3389
3607
  }
3390
3608
  function buildReadsTrivia(def) {
3391
3609
  if (!def.build) return true;
3392
- const src = def.buildSrc ?? def.build.toString();
3393
- const arity = confirmedBuildArity(src);
3610
+ const arity = confirmedArityForDef(def);
3394
3611
  if (arity === null) return true;
3395
3612
  return arity >= 5;
3396
3613
  }
3397
3614
  function buildReadsState(def) {
3398
3615
  if (!def.build) return true;
3399
- const src = def.buildSrc ?? def.build.toString();
3400
- const arity = confirmedBuildArity(src);
3616
+ const arity = confirmedArityForDef(def);
3401
3617
  if (arity === null) return true;
3402
3618
  return arity >= 6;
3403
3619
  }
@@ -3405,8 +3621,7 @@ function buildReadsState(def) {
3405
3621
  // src/compiler/fields.ts
3406
3622
  function buildReadsFields(def) {
3407
3623
  if (!def.build) return true;
3408
- const src = def.buildSrc ?? def.build.toString();
3409
- const arity = confirmedBuildArity(src);
3624
+ const arity = confirmedArityForDef(def);
3410
3625
  if (arity === null) return true;
3411
3626
  return arity >= 2;
3412
3627
  }
@@ -3587,6 +3802,13 @@ function isCstChild(value) {
3587
3802
  function missingInferredType() {
3588
3803
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3589
3804
  }
3805
+ function normalizeBuildArity(arity) {
3806
+ if (arity === void 0) return void 0;
3807
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3808
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3809
+ }
3810
+ return arity;
3811
+ }
3590
3812
  function normalizeProject(project) {
3591
3813
  if (project === void 0) return void 0;
3592
3814
  if (!Number.isInteger(project) || project < 0) {
@@ -3622,13 +3844,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3622
3844
  const captureTrivia = opts?.captureTrivia === true;
3623
3845
  const trailingTrivia = opts?.trailingTrivia === true;
3624
3846
  const tags = opts?.tags;
3847
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3625
3848
  if (unwrap && collapse) {
3626
3849
  throw new Error("node() options cannot set both unwrap and collapse");
3627
3850
  }
3628
3851
  if (project !== void 0 && (unwrap || collapse)) {
3629
3852
  throw new Error("node() options cannot combine project with unwrap or collapse");
3630
3853
  }
3631
- 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;
3854
+ 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;
3632
3855
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3633
3856
  const clonesState = build ? buildReadsState(def) : project === void 0;
3634
3857
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3674,7 +3897,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3674
3897
  const cstOutput = cstOutputHost(ctx.build);
3675
3898
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3676
3899
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3677
- 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 };
3900
+ 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 };
3678
3901
  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 };
3679
3902
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3680
3903
  pushCstChild(ctx, built, rawEntry);
@@ -3869,7 +4092,7 @@ function leaf(root, fn) {
3869
4092
  }
3870
4093
 
3871
4094
  // src/version.ts
3872
- var PARSEMAN_VERSION = "0.44.0";
4095
+ var PARSEMAN_VERSION = "0.45.0";
3873
4096
 
3874
4097
  // src/cst/host-mode.ts
3875
4098
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -4744,7 +4967,7 @@ function findDivergentSlot(members, shapeOf) {
4744
4967
  const path = [];
4745
4968
  const defs = [];
4746
4969
  let cur = [...members];
4747
- for (let guard2 = 0; guard2 < 64; guard2++) {
4970
+ for (let guard = 0; guard < 64; guard++) {
4748
4971
  const d0 = cur[0]._def;
4749
4972
  const kidLists = cur.map((c) => childrenOf2(c._def));
4750
4973
  const arity = kidLists[0].length;
@@ -5703,7 +5926,15 @@ function children(def, winners) {
5703
5926
  let resolved;
5704
5927
  try {
5705
5928
  resolved = def.thunk();
5706
- } catch {
5929
+ } catch (e) {
5930
+ recordDegradation({
5931
+ code: "coverage-definitions-unavailable",
5932
+ severity: "warn",
5933
+ where: "<lazy rule reference>",
5934
+ subject: `unresolvable reference (${e.message})`,
5935
+ 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",
5936
+ otherwise: "the referenced rule and its whole subtree would contribute definitions"
5937
+ });
5707
5938
  return [];
5708
5939
  }
5709
5940
  const name = resolved._ruleName;
@@ -7204,27 +7435,53 @@ function emitScannableTerminal(shape, c) {
7204
7435
  }
7205
7436
 
7206
7437
  // src/compiler/inline-build.ts
7207
- 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*$/;
7438
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
7439
+ var ANY_P = String.raw`\w+${ANN}`;
7440
+ var MK_BUILD_RE = new RegExp(
7441
+ 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*$`
7442
+ );
7208
7443
  function escapeRegExp(s) {
7209
7444
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7210
7445
  }
7446
+ function identMkBuildRe(typeIdent) {
7447
+ return new RegExp(
7448
+ 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*$`
7449
+ );
7450
+ }
7211
7451
  function looseMkBuildRe(type) {
7212
7452
  return new RegExp(
7213
- 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*$`
7453
+ 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*$`
7214
7454
  );
7215
7455
  }
7456
+ var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
7216
7457
  function analyzeMkInlineBuild(def) {
7217
7458
  if (!def.build) return null;
7218
7459
  if (def.type === void 0) return null;
7219
- const src = (def.buildSrc ?? def.build.toString()).trim();
7460
+ const src = buildAnalysisSrc(def).trim();
7220
7461
  const strict = src.match(MK_BUILD_RE);
7221
7462
  if (strict) {
7222
7463
  const mkType = strict[6];
7223
- return mkType === def.type ? def.type : null;
7464
+ if (mkType === def.type) return def.type;
7465
+ reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
7466
+ return null;
7224
7467
  }
7225
7468
  if (looseMkBuildRe(def.type).test(src)) return def.type;
7469
+ if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
7470
+ if (LOOKS_LIKE_MK_RE.test(src)) {
7471
+ reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
7472
+ }
7226
7473
  return null;
7227
7474
  }
7475
+ function reportMkMiss(type, src, why) {
7476
+ recordDegradation({
7477
+ code: "mk-inline-missed",
7478
+ severity: "warn",
7479
+ where: `node("${type}")`,
7480
+ subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
7481
+ fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
7482
+ otherwise: "the node object would be constructed at the call site with no call frame"
7483
+ });
7484
+ }
7228
7485
  function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
7229
7486
  return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
7230
7487
  }
@@ -7407,10 +7664,34 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
7407
7664
  seen.delete(p);
7408
7665
  }
7409
7666
  }
7667
+ function tern(cond, whenTrue, whenFalse) {
7668
+ if (cond === "false") return whenFalse;
7669
+ if (cond === "true") return whenTrue;
7670
+ return `${cond} ? ${whenTrue} : ${whenFalse}`;
7671
+ }
7672
+ function notGate(cond) {
7673
+ if (cond === "false") return "true";
7674
+ if (cond === "true") return "false";
7675
+ return `!(${cond})`;
7676
+ }
7677
+ function orGate(a, b) {
7678
+ if (a === "false") return b;
7679
+ if (b === "false") return a;
7680
+ if (a === "true" || b === "true") return "true";
7681
+ return `${a} || ${b}`;
7682
+ }
7683
+ function andGate(a, b) {
7684
+ if (a === "false" || b === "false") return "false";
7685
+ if (a === "true") return b;
7686
+ if (b === "true") return a;
7687
+ return `${a} && ${b}`;
7688
+ }
7410
7689
  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 }";
7690
+ 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 } }";
7411
7691
  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 }";
7412
7692
  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] } }";
7413
7693
  var _loweringSink = null;
7694
+ var _inlineCapSink = null;
7414
7695
  function hasSelectedRootTrivia(ctx) {
7415
7696
  return ctx.rootTriviaClassified === true;
7416
7697
  }
@@ -8159,9 +8440,11 @@ function emitSeqValues(def, ctx, pos) {
8159
8440
  const markLog = v(ctx, "_mklg");
8160
8441
  const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
8161
8442
  const scanEndV = v(ctx, "_sne");
8162
- const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? "(_ctx._triviaLog !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0" : "_ctx._triviaLog !== undefined ? 2 : 0";
8443
+ const logV = v(ctx, "_tlg");
8444
+ const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
8163
8445
  stmts.push(
8164
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`,
8446
+ `${ind(ctx)}const ${logV} = _ctx._triviaLog`,
8447
+ `${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
8165
8448
  ...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
8166
8449
  `${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
8167
8450
  ...emitLineTrack(ctx, curV, scanEndV)
@@ -8171,7 +8454,7 @@ function emitSeqValues(def, ctx, pos) {
8171
8454
  const endAfterV = v(ctx, "_sea");
8172
8455
  stmts.push(
8173
8456
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
8174
- `${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};` : ""} }`
8457
+ `${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};` : ""} }`
8175
8458
  );
8176
8459
  valueVars.push(r2.valueVar);
8177
8460
  continue;
@@ -9243,9 +9526,18 @@ function emitPeek(def, ctx, pos) {
9243
9526
  endVar: pos
9244
9527
  };
9245
9528
  }
9246
- function emitRouted(ctx, pos) {
9529
+ function emitRouted(ctx, pos, def) {
9247
9530
  const local = ctx.routedLocal;
9531
+ const fallback = def.fallback;
9248
9532
  if (local !== void 0) {
9533
+ if (fallback !== void 0 && local.startVar !== pos) {
9534
+ return emitRoutedWithFallback(ctx, pos, fallback, {
9535
+ test: `${local.startVar} === ${pos}`,
9536
+ valExpr: local.valueVar,
9537
+ startExpr: local.startVar,
9538
+ endExpr: local.endVar
9539
+ });
9540
+ }
9249
9541
  return {
9250
9542
  stmts: [
9251
9543
  ...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
@@ -9256,9 +9548,19 @@ function emitRouted(ctx, pos) {
9256
9548
  };
9257
9549
  }
9258
9550
  const item = v(ctx, "_rt");
9551
+ const read = `${ind(ctx)}const ${item} = _ctx._routed`;
9552
+ if (fallback !== void 0) {
9553
+ const r = emitRoutedWithFallback(ctx, pos, fallback, {
9554
+ test: `${item} !== undefined && ${item}.span.start === ${pos}`,
9555
+ valExpr: `${item}.value`,
9556
+ startExpr: `${item}.span.start`,
9557
+ endExpr: `${item}.span.end`
9558
+ });
9559
+ return { ...r, stmts: [read, ...r.stmts] };
9560
+ }
9259
9561
  return {
9260
9562
  stmts: [
9261
- `${ind(ctx)}const ${item} = _ctx._routed`,
9563
+ read,
9262
9564
  ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
9263
9565
  ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
9264
9566
  ],
@@ -9266,6 +9568,30 @@ function emitRouted(ctx, pos) {
9266
9568
  endVar: `${item}.span.end`
9267
9569
  };
9268
9570
  }
9571
+ function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
9572
+ const valV = v(ctx, "_rtv");
9573
+ const endV = v(ctx, "_rte");
9574
+ const i = ind(ctx);
9575
+ ctx.indent++;
9576
+ const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
9577
+ const fb = emit(fallback, ctx, pos);
9578
+ const inner = ind(ctx);
9579
+ ctx.indent--;
9580
+ return {
9581
+ stmts: [
9582
+ `${i}let ${valV}, ${endV}`,
9583
+ `${i}if (${routed2.test}) {`,
9584
+ ...capture,
9585
+ `${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
9586
+ `${i}} else {`,
9587
+ ...fb.stmts,
9588
+ `${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
9589
+ `${i}}`
9590
+ ],
9591
+ valueVar: valV,
9592
+ endVar: endV
9593
+ };
9594
+ }
9269
9595
  function escapeTokenLiteral(value) {
9270
9596
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
9271
9597
  }
@@ -9447,22 +9773,16 @@ function emitNode(def, ctx, pos) {
9447
9773
  const smk = structural ? v(ctx, "_smk") : null;
9448
9774
  if (structural) ctx.needsHostReads = true;
9449
9775
  const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
9450
- const pmV = v(ctx, "_pm"), recV = v(ctx, "_rec"), capV = v(ctx, "_cap");
9451
- const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
9452
- const profileRecognizer = recV;
9453
- const profileCapture = capV;
9454
- const cstMode = ctx.hostMode === "cst";
9455
- const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
9456
- const directCstGate = directCstV === null ? "false" : profileCapture;
9776
+ const profileRecognizer = "false";
9777
+ const profileCapture = "false";
9457
9778
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
9458
9779
  const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
9459
- const chAlloc = def.unwrap || def.collapse ? `${profileRecognizer} ? undefined : []` : `${profileRecognizer} ? undefined : ((${profileCapture} || ${chNeededExpr}) ? [] : undefined)`;
9460
- 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)))` : ""}
9461
- ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9462
- ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9463
- ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
9780
+ const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
9781
+ const dcstAlloc = "undefined";
9782
+ 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))"))}` : ""}
9783
+ ${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)}`;
9464
9784
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
9465
- const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
9785
+ const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
9466
9786
  const sf = hasFields ? v(ctx, "_sf") : null;
9467
9787
  const fArr = hasFields ? v(ctx, "_fa") : null;
9468
9788
  const fObj = hasFields ? v(ctx, "_fields") : "undefined";
@@ -9471,7 +9791,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9471
9791
  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}` : ""}` : "";
9472
9792
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
9473
9793
  const preGuard = [];
9474
- if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
9794
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
9475
9795
  const gcV = v(ctx, "_ngc");
9476
9796
  const gExp = armStaticExpected(ctx, def.parser);
9477
9797
  const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
@@ -9482,7 +9802,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9482
9802
  }
9483
9803
  const stmts = [
9484
9804
  ...preGuard,
9485
- profHoist,
9486
9805
  allocStmt,
9487
9806
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
9488
9807
  `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
@@ -9499,21 +9818,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9499
9818
  }
9500
9819
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
9501
9820
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
9502
- stmts.push(
9503
- `${i}if (_ctx._pmProfile) {`,
9504
- `${i} _ctx._pmProfile.nodes++`,
9505
- `${i} if (${profileCapture}) {`,
9506
- `${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
9507
- `${i} }`,
9508
- `${i}}`
9509
- );
9510
9821
  let stV = "undefined";
9511
9822
  if (structural) {
9512
9823
  stV = v(ctx, "_nst");
9513
9824
  stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9514
9825
  } else if (clonesState) {
9515
9826
  stV = v(ctx, "_nst");
9516
- stmts.push(`${i}const ${stV} = !(${profileRecognizer} || ${profileCapture}) && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9827
+ stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
9517
9828
  }
9518
9829
  if (sf && fArr) {
9519
9830
  const fe = v(ctx, "_fe");
@@ -9537,19 +9848,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9537
9848
  if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
9538
9849
  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})`;
9539
9850
  const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
9540
- const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${hostBuildArgs})`;
9851
+ const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
9541
9852
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
9542
- 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;
9853
+ const hostCollapses = structural || cstOut;
9854
+ 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;
9543
9855
  const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
9544
9856
  const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
9545
9857
  const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
9546
- stmts.push(
9547
- `${i}const ${ndV} = (${profileRecognizer} || ${profileCapture}) ? undefined : (${finalExpr})`,
9548
- `${i}if (!(${profileRecognizer})) {`,
9549
- `${i} if (${sc}) ${sc}.push(${ndV})`,
9550
- `${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} })`,
9551
- `${i}}`
9552
- );
9858
+ const ndGate = orGate(profileRecognizer, profileCapture);
9859
+ const recGate = notGate(profileRecognizer);
9860
+ const pushIndent = recGate === "true" ? i : `${i} `;
9861
+ const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
9862
+ ctx.needsRawEntry = true;
9863
+ const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
9864
+ stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
9865
+ if (recGate === "true") stmts.push(pushCh, pushRaw);
9866
+ else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
9553
9867
  return { stmts, valueVar: ndV, endVar };
9554
9868
  }
9555
9869
  function emitRuntimeFallback(parser2, ctx, pos) {
@@ -9578,7 +9892,12 @@ function emitLazy(p, def, ctx, pos) {
9578
9892
  } catch {
9579
9893
  return emitRuntimeFallback(p, ctx, pos);
9580
9894
  }
9581
- return emit(resolved, ctx, pos);
9895
+ const cost = usage.sizes.get(resolved) ?? 1;
9896
+ if (cost <= ctx.inlineLeft) {
9897
+ ctx.inlineLeft -= cost;
9898
+ return emit(resolved, ctx, pos);
9899
+ }
9900
+ _inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
9582
9901
  }
9583
9902
  if (!ctx.namedParsers.has(p)) {
9584
9903
  const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
@@ -9593,6 +9912,9 @@ function emitLazy(p, def, ctx, pos) {
9593
9912
  const savedIndent = ctx.indent;
9594
9913
  const savedFailLabel = ctx.failLabel;
9595
9914
  const savedRecord = ctx.recordFail;
9915
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9916
+ ctx.inlineLeft = ctx.inlineMax;
9917
+ ctx.currentFnName = fnName2;
9596
9918
  const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
9597
9919
  const savedActiveRule = ctx.activeCoverageRuleId;
9598
9920
  const savedSuppressFailure = ctx.suppressCoverageFailure;
@@ -9607,6 +9929,8 @@ function emitLazy(p, def, ctx, pos) {
9607
9929
  ctx.indent = savedIndent;
9608
9930
  ctx.failLabel = savedFailLabel;
9609
9931
  ctx.recordFail = savedRecord;
9932
+ ctx.inlineLeft = savedInlineLeft;
9933
+ ctx.currentFnName = savedFnName;
9610
9934
  ctx.activeCoverageRuleId = savedActiveRule;
9611
9935
  ctx.suppressCoverageFailure = savedSuppressFailure;
9612
9936
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
@@ -9701,6 +10025,9 @@ function emit(p, ctx, pos) {
9701
10025
  const savedIndent = ctx.indent;
9702
10026
  const savedFailLabel = ctx.failLabel;
9703
10027
  const savedRecord = ctx.recordFail;
10028
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10029
+ ctx.inlineLeft = ctx.inlineMax;
10030
+ ctx.currentFnName = fnName;
9704
10031
  ctx.indent = 1;
9705
10032
  ctx.failLabel = "_pfail";
9706
10033
  ctx.recordFail = true;
@@ -9708,6 +10035,8 @@ function emit(p, ctx, pos) {
9708
10035
  ctx.indent = savedIndent;
9709
10036
  ctx.failLabel = savedFailLabel;
9710
10037
  ctx.recordFail = savedRecord;
10038
+ ctx.inlineLeft = savedInlineLeft;
10039
+ ctx.currentFnName = savedFnName;
9711
10040
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
9712
10041
  return instrument(emitNamedFnCall(ctx, fnName, pos));
9713
10042
  }
@@ -9901,7 +10230,7 @@ function emitDispatch(p, ctx, pos) {
9901
10230
  case "peek":
9902
10231
  return emitPeek(def, ctx, pos);
9903
10232
  case "routed":
9904
- return emitRouted(ctx, pos);
10233
+ return emitRouted(ctx, pos, def);
9905
10234
  case "node":
9906
10235
  return emitNode(def, ctx, pos);
9907
10236
  case "scanTo":
@@ -9936,6 +10265,9 @@ function emitDispatch(p, ctx, pos) {
9936
10265
  const savedIndent = ctx.indent;
9937
10266
  const savedFailLabel = ctx.failLabel;
9938
10267
  const savedRecord = ctx.recordFail;
10268
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10269
+ ctx.inlineLeft = ctx.inlineMax;
10270
+ ctx.currentFnName = fnName;
9939
10271
  ctx.indent = 1;
9940
10272
  ctx.failLabel = "_pfail";
9941
10273
  ctx.recordFail = true;
@@ -9943,6 +10275,8 @@ function emitDispatch(p, ctx, pos) {
9943
10275
  ctx.indent = savedIndent;
9944
10276
  ctx.failLabel = savedFailLabel;
9945
10277
  ctx.recordFail = savedRecord;
10278
+ ctx.inlineLeft = savedInlineLeft;
10279
+ ctx.currentFnName = savedFnName;
9946
10280
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
9947
10281
  }
9948
10282
  const fn = ctx.namedParsers.get(innerParser);
@@ -10101,12 +10435,16 @@ function childrenOf3(def) {
10101
10435
  return [def.parser, def.sentinel];
10102
10436
  case "scanTo":
10103
10437
  return [def.sentinel, ...def.skip];
10438
+ // A `routed()` fallback IS emitted at this site (emitRouted), so the usage
10439
+ // analysis must see the edge — otherwise a fallback shared by several routed()
10440
+ // sites is miscounted as single-use and inlined at each.
10441
+ case "routed":
10442
+ return def.fallback ? [def.fallback] : [];
10104
10443
  case "lazy":
10105
10444
  case "literal":
10106
10445
  case "regex":
10107
10446
  case "keywords":
10108
10447
  case "guard":
10109
- case "routed":
10110
10448
  case "unknown":
10111
10449
  return [];
10112
10450
  }
@@ -10127,6 +10465,17 @@ function isHoistableTag(tag) {
10127
10465
  return HOISTABLE_TAGS.has(tag);
10128
10466
  }
10129
10467
  var HOIST_MIN_SUBTREE = 3;
10468
+ var INLINE_MAX_NODES = 1e3;
10469
+ function resolveInlineMax(explicit) {
10470
+ if (explicit !== void 0) return explicit;
10471
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
10472
+ if (env !== void 0 && env !== "") {
10473
+ if (env === "off" || env === "infinity") return Infinity;
10474
+ const n = Number(env);
10475
+ if (Number.isFinite(n) && n >= 0) return n;
10476
+ }
10477
+ return INLINE_MAX_NODES;
10478
+ }
10130
10479
  function analyzeLazyUsage(root) {
10131
10480
  return analyzeLazyUsageMulti([root]);
10132
10481
  }
@@ -10232,102 +10581,6 @@ function ruleDependencies(ruleMap) {
10232
10581
  }
10233
10582
  return deps;
10234
10583
  }
10235
- function resolveGatingLevel(opt) {
10236
- const explicit = typeof opt === "string" ? opt : opt?.level;
10237
- if (explicit !== void 0) return explicit;
10238
- const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
10239
- if (env === "off" || env === "warn" || env === "error") return env;
10240
- return "warn";
10241
- }
10242
- function runGatingDiagnostic(combinator, opt) {
10243
- return reportGating(opt, (o) => analyzeGating(combinator, o));
10244
- }
10245
- function runGatingDiagnosticRules(ruleMap, opt) {
10246
- return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
10247
- }
10248
- function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
10249
- if (resolveGatingLevel(opt) === "off") return void 0;
10250
- const ruleMaps = getRuleMaps();
10251
- const opaque = getOpaque?.() ?? [];
10252
- const opaqueFindings = opaque.map((o) => ({
10253
- rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
10254
- kind: "opaque-artifact",
10255
- reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
10256
- }));
10257
- if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
10258
- if (opaqueFindings.length === 0) return void 0;
10259
- return reportGating(opt, () => ({
10260
- totalChoices: 0,
10261
- gated: 0,
10262
- recoverable: 0,
10263
- unanalysable: opaqueFindings,
10264
- ungated: [],
10265
- accepted: [],
10266
- deferred: [],
10267
- acceptedUnused: [],
10268
- choices: [],
10269
- antiPatterns: []
10270
- }));
10271
- }
10272
- const winners = /* @__PURE__ */ new Map();
10273
- for (const map of ruleMaps) for (const [name, rule] of map) {
10274
- if (rule._def.tag === "lazy") {
10275
- try {
10276
- rule._def.thunk();
10277
- } catch {
10278
- continue;
10279
- }
10280
- }
10281
- winners.set(name, rule);
10282
- }
10283
- const entries = [...winners];
10284
- return reportGating(opt, (o) => {
10285
- const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
10286
- const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
10287
- return {
10288
- ...fused,
10289
- ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
10290
- antiPatterns: [],
10291
- unanalysable: [...fused.unanalysable, ...opaqueFindings]
10292
- };
10293
- });
10294
- }
10295
- function reportGating(opt, analyze) {
10296
- const level = resolveGatingLevel(opt);
10297
- if (level === "off") return void 0;
10298
- const obj = opt !== null && typeof opt === "object" ? opt : void 0;
10299
- 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;
10300
- let report;
10301
- try {
10302
- report = analyze(analyzeOpts);
10303
- } catch (e) {
10304
- const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
10305
- const failed = {
10306
- totalChoices: 0,
10307
- gated: 0,
10308
- recoverable: 0,
10309
- unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
10310
- ungated: [],
10311
- accepted: [],
10312
- deferred: [],
10313
- acceptedUnused: [],
10314
- choices: [],
10315
- antiPatterns: []
10316
- };
10317
- const failLines = formatGatingWarnings(failed);
10318
- if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
10319
- ${failLines.join("\n")}`);
10320
- for (const l of failLines) console.warn(l);
10321
- return failed;
10322
- }
10323
- const lines = formatGatingWarnings(report);
10324
- if (lines.length > 0) {
10325
- 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)
10326
- ${lines.join("\n")}`);
10327
- for (const l of lines) console.warn(l);
10328
- }
10329
- return report;
10330
- }
10331
10584
  function resolveDuplicationLevel(opt) {
10332
10585
  const explicit = typeof opt === "string" ? opt : opt?.level;
10333
10586
  if (explicit !== void 0) return explicit;
@@ -10370,7 +10623,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
10370
10623
  return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
10371
10624
  }
10372
10625
  function compile(combinator, mapFnSources, opts) {
10373
- const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
10626
+ const drain = beginCompileDegradationDrain();
10627
+ let completed = false;
10628
+ try {
10629
+ const compiled = compileImpl(combinator, mapFnSources, opts);
10630
+ completed = true;
10631
+ return compiled;
10632
+ } finally {
10633
+ drain(completed);
10634
+ }
10635
+ }
10636
+ function compileImpl(combinator, mapFnSources, opts) {
10374
10637
  runDuplicationDiagnostic(combinator, opts?.duplication);
10375
10638
  markUnusedValues(combinator);
10376
10639
  const grammarTrivia = combinator._meta.grammarTrivia;
@@ -10378,9 +10641,12 @@ function compile(combinator, mapFnSources, opts) {
10378
10641
  const grammarScanSkip = combinator._meta.grammarScanSkip;
10379
10642
  const grammarHostMode = combinator._meta.grammarHostMode;
10380
10643
  const grammarTrackLines = combinator._meta.grammarTrackLines;
10644
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10381
10645
  const ctx = {
10382
10646
  vars: 0,
10383
10647
  indent: 1,
10648
+ inlineMax: _inlineMax,
10649
+ inlineLeft: _inlineMax,
10384
10650
  regexDecls: [],
10385
10651
  regexMap: /* @__PURE__ */ new Map(),
10386
10652
  expectedDecls: [],
@@ -10426,11 +10692,13 @@ function compile(combinator, mapFnSources, opts) {
10426
10692
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
10427
10693
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
10428
10694
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
10695
+ const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
10429
10696
  const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
10430
10697
  const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
10431
10698
  const source = [
10432
10699
  ...emptyTlDecls,
10433
10700
  ...hostReadsDecls,
10701
+ ...rawEntryDecls,
10434
10702
  ...lineTrackDecls,
10435
10703
  ...lineSpanDecls,
10436
10704
  ...ctx.regexDecls,
@@ -10445,6 +10713,7 @@ function compile(combinator, mapFnSources, opts) {
10445
10713
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
10446
10714
  ...emptyTlDecls,
10447
10715
  ...hostReadsDecls,
10716
+ ...rawEntryDecls,
10448
10717
  ...lineTrackDecls,
10449
10718
  ...lineSpanDecls,
10450
10719
  ...ctx.regexDecls,
@@ -10494,8 +10763,7 @@ function compile(combinator, mapFnSources, opts) {
10494
10763
  const compiledMeta = {
10495
10764
  source,
10496
10765
  inlineExpression,
10497
- ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
10498
- ...gatingReport === void 0 ? {} : { gating: gatingReport }
10766
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
10499
10767
  };
10500
10768
  if (!ctx.lineTracking) {
10501
10769
  return {
@@ -10561,7 +10829,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
10561
10829
  }
10562
10830
  function compileLinkable(ruleMapArg, ns, opts) {
10563
10831
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
10564
- if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
10565
10832
  if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
10566
10833
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
10567
10834
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
@@ -10580,9 +10847,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
10580
10847
  }
10581
10848
  });
10582
10849
  const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
10850
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10583
10851
  const ctx = {
10584
10852
  vars: 0,
10585
10853
  indent: 1,
10854
+ inlineMax: _inlineMax,
10855
+ inlineLeft: _inlineMax,
10586
10856
  regexDecls: [],
10587
10857
  regexMap: /* @__PURE__ */ new Map(),
10588
10858
  expectedDecls: [],
@@ -10678,6 +10948,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
10678
10948
  }
10679
10949
  }
10680
10950
  const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
10951
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10952
+ ctx.inlineLeft = ctx.inlineMax;
10953
+ ctx.currentFnName = fn;
10681
10954
  ctx.indent = 1;
10682
10955
  ctx.failLabel = "_pfail";
10683
10956
  ctx.recordFail = true;
@@ -10693,6 +10966,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
10693
10966
  ctx.indent = savedIndent;
10694
10967
  ctx.failLabel = savedFail;
10695
10968
  ctx.recordFail = savedRec;
10969
+ ctx.inlineLeft = savedInlineLeft;
10970
+ ctx.currentFnName = savedFnName;
10696
10971
  const ruleId = ctx.coverage?.plan.rules.get(rule);
10697
10972
  const stmts = ruleId === void 0 ? body.stmts : [
10698
10973
  `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
@@ -10777,6 +11052,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10777
11052
  deps: ruleDependencies(ruleMap),
10778
11053
  needsEmptyTl: !!ctx.needsEmptyTl,
10779
11054
  needsHostReads: !!ctx.needsHostReads,
11055
+ needsRawEntry: !!ctx.needsRawEntry,
10780
11056
  hostMode: ctx.hostMode ?? "ast",
10781
11057
  hostBranchElided: !!ctx.hostBranchElided,
10782
11058
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
@@ -10786,26 +11062,6 @@ function compileLinkable(ruleMapArg, ns, opts) {
10786
11062
  buildFns: buildSrcs ? [] : ctx.buildFns
10787
11063
  };
10788
11064
  }
10789
- function hasExternalRuleRef(ruleMap) {
10790
- const seen = /* @__PURE__ */ new Set();
10791
- const visit = (p) => {
10792
- if (seen.has(p)) return false;
10793
- seen.add(p);
10794
- const def = p._def;
10795
- if (def.tag === "lazy") {
10796
- let resolved;
10797
- try {
10798
- resolved = def.thunk();
10799
- } catch {
10800
- resolved = void 0;
10801
- }
10802
- if (resolved === void 0) return p._ruleName !== void 0;
10803
- return visit(resolved);
10804
- }
10805
- return childrenOf3(def).some(visit);
10806
- };
10807
- return ruleMap.some(([, rule]) => visit(rule));
10808
- }
10809
11065
  function hasSemanticReduction(roots, externalRefs) {
10810
11066
  const seen = /* @__PURE__ */ new Set();
10811
11067
  const visit = (parser2) => {
@@ -10851,14 +11107,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
10851
11107
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
10852
11108
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
10853
11109
  const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
11110
+ const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
10854
11111
  const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
10855
11112
  const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
10856
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
11113
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
10857
11114
  if (!needsWrapper) return innerFn;
10858
11115
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
10859
11116
  const hoistedDecls = [
10860
11117
  emptyTlDecl,
10861
11118
  hostReadsDecl,
11119
+ rawEntryDecl,
10862
11120
  lineTrackDecl,
10863
11121
  lineSpanDecl,
10864
11122
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10928,11 +11186,12 @@ function childrenOf4(def) {
10928
11186
  return [def.main, def.skipped];
10929
11187
  case "scanTo":
10930
11188
  return [def.sentinel, ...def.skip];
11189
+ case "routed":
11190
+ return def.fallback ? [def.fallback] : [];
10931
11191
  case "lazy":
10932
11192
  case "literal":
10933
11193
  case "regex":
10934
11194
  case "keywords":
10935
- case "routed":
10936
11195
  case "guard":
10937
11196
  case "recover":
10938
11197
  case "unknown":
@@ -11014,7 +11273,7 @@ function evalRuleMapIR(ir) {
11014
11273
  l._def.fnSrc = src;
11015
11274
  return l;
11016
11275
  };
11017
- const _nd = (type, child, src, opts, staticError) => {
11276
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
11018
11277
  if (staticError !== void 0 && staticError.length > 0) {
11019
11278
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
11020
11279
  }
@@ -11023,6 +11282,7 @@ function evalRuleMapIR(ir) {
11023
11282
  }), opts);
11024
11283
  n._def.buildSrc = src;
11025
11284
  if (staticError !== void 0) n._def.buildStaticError = staticError;
11285
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
11026
11286
  return n;
11027
11287
  };
11028
11288
  const _gch = (items) => {
@@ -11335,7 +11595,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11335
11595
  case "peek":
11336
11596
  return `peek(${kid(def.parser)})`;
11337
11597
  case "routed":
11338
- return "routed()";
11598
+ return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
11339
11599
  case "trivia":
11340
11600
  return `trivia(${kid(def.parser)})`;
11341
11601
  case "token":
@@ -11368,6 +11628,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11368
11628
  ...projectEntry === void 0 ? [] : [projectEntry],
11369
11629
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
11370
11630
  ...def.trailingTrivia ? ["trailingTrivia: true"] : [],
11631
+ // Declared or resolved arity. Carried so a re-lowered composed artifact does
11632
+ // not silently re-acquire the fail-open capture cost this module resolved away.
11633
+ ...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
11371
11634
  ...tagEntry === void 0 ? [] : [tagEntry]
11372
11635
  ];
11373
11636
  const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
@@ -11376,8 +11639,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11376
11639
  return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
11377
11640
  }
11378
11641
  if (def.buildSrc !== void 0) {
11379
- const staticError = def.buildStaticError === void 0 ? "" : `${opts ? "" : ", undefined"}, ${JSON.stringify(def.buildStaticError)}`;
11380
- return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${staticError})`;
11642
+ const tail = [];
11643
+ if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
11644
+ if (!opts) tail.push("undefined");
11645
+ tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
11646
+ }
11647
+ if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
11648
+ const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
11649
+ return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
11381
11650
  }
11382
11651
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
11383
11652
  }
@@ -11458,12 +11727,14 @@ function fusedBody(pieces) {
11458
11727
  const contributingPieces = [...contributing];
11459
11728
  const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
11460
11729
  const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
11730
+ const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
11461
11731
  const lines = [
11462
11732
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
11463
11733
  "const _pfFail = {}",
11464
11734
  "let _pfEnd",
11465
11735
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
11466
11736
  ...needsHostReads ? [HOST_READS_DECL] : [],
11737
+ ...needsRawEntry ? [RAW_ENTRY_DECL] : [],
11467
11738
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
11468
11739
  ...new Set(contributingPieces.flatMap((p) => p.prelude)),
11469
11740
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -11579,17 +11850,6 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
11579
11850
  function isIRPiece(p) {
11580
11851
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
11581
11852
  }
11582
- function once(fn) {
11583
- let done = false;
11584
- let value;
11585
- return () => {
11586
- if (!done) {
11587
- value = fn();
11588
- done = true;
11589
- }
11590
- return value;
11591
- };
11592
- }
11593
11853
  function carriedRuleMapsDetailed(carried) {
11594
11854
  const maps = [];
11595
11855
  const opaque = [];
@@ -11706,12 +11966,6 @@ function compose(items, opts) {
11706
11966
  const used = /* @__PURE__ */ new Set();
11707
11967
  const trivia2 = composingTriviaOf(items);
11708
11968
  const carried = items.flatMap((item) => itemCarried(item, used, trivia2, opts?.hostMode));
11709
- const detailed = once(() => carriedRuleMapsDetailed(carried));
11710
- runFusedGatingDiagnostic(
11711
- () => detailed().maps,
11712
- void 0,
11713
- () => detailed().opaque
11714
- );
11715
11969
  const pieces = carried.map((p) => materializePiece(p, trivia2, false, opts?.hostMode));
11716
11970
  const map = fuseRules(pieces);
11717
11971
  Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
@@ -11738,9 +11992,175 @@ function analyzeGrammarGating(grammar, opts) {
11738
11992
  kind: "opaque-artifact",
11739
11993
  reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices cannot be examined. Recompile the contributing grammar so it carries IR (the macro emits IR by default) to bring it back into analysis.`
11740
11994
  }));
11995
+ for (const o of opaque) {
11996
+ recordDegradation({
11997
+ code: "opaque-artifact",
11998
+ // `info`, not `warn`: the artifact is usually someone else's package, so the author
11999
+ // of THIS grammar has nothing to act on — but the blind spot still has to be counted.
12000
+ severity: "info",
12001
+ where: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
12002
+ subject: `precompiled artifact "${o.ns}"`,
12003
+ fellBackTo: `it carries compiled rule functions rather than re-lowerable IR, so its ${o.ruleNames.length > 0 ? `${o.ruleNames.length} rule(s)` : "rules"} were NOT analysed and a clean gating report must not be read as a pass`,
12004
+ otherwise: "its choices would be walked like any other rule. Recompile the contributing grammar so it carries IR (the macro emits IR by default)"
12005
+ });
12006
+ }
11741
12007
  return fromOpaque.length === 0 ? report : { ...report, unanalysable: [...report.unanalysable, ...fromOpaque] };
11742
12008
  }
11743
12009
 
12010
+ // src/analysis/diagnose.ts
12011
+ var isCombinator = (g) => typeof g === "object" && g !== null && "_def" in g;
12012
+ var isRuleEntries = (g) => Array.isArray(g);
12013
+ function diagnoseGrammar(grammar, opts) {
12014
+ const depth = degradationCaptureDepth();
12015
+ beginDegradationCapture();
12016
+ let report;
12017
+ let degradations;
12018
+ try {
12019
+ report = runAnalysis(grammar, opts);
12020
+ } catch (e) {
12021
+ const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
12022
+ report = emptyReport(`gating analysis threw \u2014 ${reason}`);
12023
+ } finally {
12024
+ degradations = unwindDegradationCapture(depth);
12025
+ }
12026
+ return assemble(report, degradations);
12027
+ }
12028
+ function formatGrammarDiagnosis(d) {
12029
+ const s = d.summary;
12030
+ const lines = [];
12031
+ lines.push(
12032
+ d.ok ? `parseman: grammar OK \u2014 ${s.gated}/${s.totalChoices} choice(s) gate on first char${s.recoverable > 0 ? `, ${s.recoverable} recoverable` : ""}${s.accepted > 0 ? `, ${s.accepted} accepted` : ""}${s.deferred > 0 ? `, ${s.deferred} deferred to the fusing artifact` : ""}.` : `parseman: grammar NOT OK \u2014 ${blockingOf(d).length} blocking finding(s) over ${s.totalChoices} examined choice(s).`
12033
+ );
12034
+ if (s.unanalysable > 0) {
12035
+ lines.push(
12036
+ ` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
12037
+ );
12038
+ }
12039
+ for (const f of d.findings) {
12040
+ lines.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
12041
+ for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, "\n ")}`);
12042
+ if (f.acceptKey !== void 0) {
12043
+ lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
12044
+ }
12045
+ }
12046
+ if (!d.ok && d.acceptSnapshot.length > 0) {
12047
+ lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
12048
+ }
12049
+ return lines;
12050
+ }
12051
+ function runAnalysis(grammar, opts) {
12052
+ if (isCombinator(grammar)) return analyzeGating(grammar, opts);
12053
+ if (isRuleEntries(grammar)) return analyzeGatingRules(grammar, opts);
12054
+ return analyzeGrammarGating(grammar, opts);
12055
+ }
12056
+ function emptyReport(reason) {
12057
+ return {
12058
+ totalChoices: 0,
12059
+ gated: 0,
12060
+ recoverable: 0,
12061
+ unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason }],
12062
+ ungated: [],
12063
+ accepted: [],
12064
+ deferred: [],
12065
+ acceptedUnused: [],
12066
+ choices: [],
12067
+ antiPatterns: []
12068
+ };
12069
+ }
12070
+ var blockingOf = (d) => d.findings.filter((f) => f.severity === "blocking");
12071
+ var SEVERITY_RANK = { blocking: 0, advisory: 1 };
12072
+ var CODE_RANK = {
12073
+ unanalysable: 0,
12074
+ "ungated-choice": 1,
12075
+ "anti-pattern": 2,
12076
+ degraded: 3,
12077
+ "stale-accept": 4
12078
+ };
12079
+ function assemble(report, degradations) {
12080
+ const findings = [];
12081
+ for (const u of report.unanalysable) {
12082
+ findings.push({
12083
+ id: u.rule,
12084
+ code: "unanalysable",
12085
+ severity: "blocking",
12086
+ rule: u.rule,
12087
+ message: `could not be examined [${u.kind}] \u2014 no verdict about it is available`,
12088
+ details: [u.reason]
12089
+ });
12090
+ }
12091
+ for (const c of report.ungated) {
12092
+ findings.push({
12093
+ id: c.id,
12094
+ code: "ungated-choice",
12095
+ severity: "blocking",
12096
+ rule: c.rule,
12097
+ message: `choice is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms`,
12098
+ details: [
12099
+ ...c.anyArms.map((a) => `arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
12100
+ fix: ${a.suggestion}`),
12101
+ ...c.overlaps.map((o) => `arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
12102
+ fix: ${o.suggestion}`)
12103
+ ],
12104
+ acceptKey: c.id
12105
+ });
12106
+ }
12107
+ for (const ap of report.antiPatterns) {
12108
+ findings.push({
12109
+ id: `${ap.rule}#arm${ap.armIndex}`,
12110
+ code: "anti-pattern",
12111
+ severity: "blocking",
12112
+ rule: ap.rule,
12113
+ message: `[${ap.kind}] ${ap.message}`,
12114
+ details: []
12115
+ });
12116
+ }
12117
+ for (const d of degradations) {
12118
+ findings.push({
12119
+ // `where` + `code` is the degradation channel's own identity; reuse it so the two
12120
+ // surfaces name the same thing the same way.
12121
+ id: `${d.code}@${d.where}`,
12122
+ code: "degraded",
12123
+ // Mirrors `DegradationSeverity`: 'warn' is a real cost the author can remove,
12124
+ // 'info' is a fact they cannot act on (someone else's package).
12125
+ severity: d.severity === "warn" ? "blocking" : "advisory",
12126
+ rule: d.where,
12127
+ message: formatDegradation(d),
12128
+ details: []
12129
+ });
12130
+ }
12131
+ for (const id of report.acceptedUnused) {
12132
+ findings.push({
12133
+ id,
12134
+ code: "stale-accept",
12135
+ severity: "advisory",
12136
+ rule: id,
12137
+ message: "accept entry matched no ungated choice \u2014 the grammar was fixed; prune this line",
12138
+ details: []
12139
+ });
12140
+ }
12141
+ findings.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity] || CODE_RANK[a.code] - CODE_RANK[b.code] || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
12142
+ return {
12143
+ schema: "parseman.diagnosis/1",
12144
+ ok: !findings.some((f) => f.severity === "blocking"),
12145
+ summary: {
12146
+ totalChoices: report.totalChoices,
12147
+ gated: report.gated,
12148
+ recoverable: report.recoverable,
12149
+ ungated: report.ungated.length,
12150
+ accepted: report.accepted.length,
12151
+ deferred: report.deferred.length,
12152
+ antiPatterns: report.antiPatterns.length,
12153
+ unanalysable: report.unanalysable.length,
12154
+ degraded: degradations.length,
12155
+ staleAccepts: report.acceptedUnused.length
12156
+ },
12157
+ findings,
12158
+ acceptSnapshot: report.ungated.map((c) => c.id).sort(),
12159
+ gating: report,
12160
+ degradations
12161
+ };
12162
+ }
12163
+
11744
12164
  // src/combinators/gate.ts
11745
12165
  function gate(predicate) {
11746
12166
  const meta = {
@@ -11760,9 +12180,6 @@ function gate(predicate) {
11760
12180
  };
11761
12181
  }
11762
12182
 
11763
- // src/combinators/guard.ts
11764
- var guard = gate;
11765
-
11766
12183
  // src/combinators/completions.ts
11767
12184
  function completionsAt(target, input, offset, options = {}) {
11768
12185
  const probe = { offset, best: null };
@@ -11989,9 +12406,9 @@ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, to
11989
12406
  const ctx = mkCtx(null, build, tolerant);
11990
12407
  const out = [];
11991
12408
  let pos = reStart;
11992
- let guard2 = 0;
12409
+ let guard = 0;
11993
12410
  while (pos < reEnd) {
11994
- if (++guard2 > reEnd - reStart + 2) return null;
12411
+ if (++guard > reEnd - reStart + 2) return null;
11995
12412
  if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
11996
12413
  if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
11997
12414
  out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
@@ -12284,6 +12701,35 @@ function parseDoc(registry, rootRule, input, opts = {}) {
12284
12701
  }
12285
12702
 
12286
12703
  // src/functional/run.ts
12704
+ var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
12705
+ var REMOVED_RUN_RESULT_FIELDS = [
12706
+ [
12707
+ "triviaMap",
12708
+ `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.`
12709
+ ],
12710
+ [
12711
+ "triviaLog",
12712
+ `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.`
12713
+ ],
12714
+ [
12715
+ "triviaKindLabels",
12716
+ `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\`.`
12717
+ ]
12718
+ ];
12719
+ function guardRemovedFields(result) {
12720
+ for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
12721
+ Object.defineProperty(result, name, {
12722
+ configurable: true,
12723
+ enumerable: false,
12724
+ get() {
12725
+ throw new TypeError(
12726
+ `RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
12727
+ );
12728
+ }
12729
+ });
12730
+ }
12731
+ return result;
12732
+ }
12287
12733
  var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
12288
12734
  function triviaKindLabelsFromRunnable(r) {
12289
12735
  if (!r) return void 0;
@@ -12386,37 +12832,11 @@ function runOnce(entry, input, options, phase, profileState) {
12386
12832
  unconsumedFrom
12387
12833
  };
12388
12834
  }
12389
- function clonePassState(state) {
12390
- if (state === null || typeof state !== "object") return state;
12391
- return Array.isArray(state) ? [...state] : { ...state };
12392
- }
12393
- function profilePass(entry, input, options, phase) {
12394
- const state = { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 };
12395
- const passOptions = options.state === void 0 ? options : { ...options, state: clonePassState(options.state) };
12396
- const start = performance.now();
12397
- const result = runOnce(entry, input, passOptions, phase, state);
12398
- const { phase: _phase, ...counts } = state;
12399
- return { result, profile: { ms: performance.now() - start, ...counts } };
12400
- }
12401
12835
  function run(entry, input, options = {}) {
12402
- if (!options.profile) return runOnce(entry, input, options);
12403
- if (typeof entry !== "function") {
12404
- throw new TypeError("run({ profile: true }) requires a compiled parser entry");
12405
- }
12406
- const recognizer = profilePass(entry, input, options, "recognizer");
12407
- const capture = profilePass(entry, input, options, "capture");
12408
- const host = profilePass(entry, input, options, "host");
12409
- if (recognizer.result.ok !== host.result.ok || capture.result.ok !== host.result.ok || recognizer.result.unconsumedFrom !== host.result.unconsumedFrom || capture.result.unconsumedFrom !== host.result.unconsumedFrom) {
12410
- throw new Error("run({ profile: true }) changed recognition; the grammar is not profile-safe");
12411
- }
12412
- return {
12413
- ...host.result,
12414
- profile: {
12415
- recognizer: recognizer.profile,
12416
- structuralCapture: capture.profile,
12417
- hostConstruction: host.profile
12418
- }
12419
- };
12836
+ if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
12837
+ throw new TypeError(
12838
+ "run({ profile: true }) is unavailable: profiling counters are no longer compiled into parser artifacts, and the interpreter does not implement them yet. Profiling is moving to interpreted mode."
12839
+ );
12420
12840
  }
12421
12841
 
12422
12842
  // src/coverage.ts
@@ -12433,7 +12853,7 @@ function dispatchMatcher(entry) {
12433
12853
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
12434
12854
  function compiledGrammarCoverageDefinitions(grammar) {
12435
12855
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
12436
- if (!Array.isArray(definitions) || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
12856
+ if (!Array.isArray(definitions) || definitions.length === 0 || !definitions.every((definition) => typeof definition === "object" && definition !== null && typeof definition.id === "string" && (definition.kind === "rule" || definition.kind === "choice-arm" || definition.kind === "dispatch-arm" || definition.kind === "label"))) {
12437
12857
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
12438
12858
  }
12439
12859
  return definitions;
@@ -12487,7 +12907,13 @@ function createGrammarCoverageCollector(definitions) {
12487
12907
  snapshot() {
12488
12908
  const hitList = [...hits].sort();
12489
12909
  const unhit = ordered.map((definition) => definition.id).filter((id) => !hits.has(id));
12490
- return { definitions: ordered, hits: hitList, unhit, ratio: ordered.length === 0 ? 1 : hitList.length / ordered.length };
12910
+ return {
12911
+ definitions: ordered,
12912
+ hits: hitList,
12913
+ unhit,
12914
+ ratio: hitList.length / ordered.length,
12915
+ measurable: ordered.length > 0
12916
+ };
12491
12917
  },
12492
12918
  reset() {
12493
12919
  hits.clear();
@@ -12807,13 +13233,13 @@ function buildTriviaIndex(root, input = "", opts) {
12807
13233
  }
12808
13234
 
12809
13235
  // src/cst/walk.ts
12810
- function isCombinator(value) {
13236
+ function isCombinator2(value) {
12811
13237
  return !!value && typeof value === "object" && "_def" in value && "_meta" in value && typeof value.parse === "function";
12812
13238
  }
12813
13239
  function reflectionFor(grammar) {
12814
13240
  const stamped = grammarReflectionOf(grammar);
12815
13241
  if (stamped) return stamped;
12816
- const entries = Object.entries(grammar).filter((entry) => isCombinator(entry[1]));
13242
+ const entries = Object.entries(grammar).filter((entry) => isCombinator2(entry[1]));
12817
13243
  return collectGrammarReflection(entries);
12818
13244
  }
12819
13245
  function createVisitor(grammar, spec) {
@@ -12851,196 +13277,6 @@ function createVisitor(grammar, spec) {
12851
13277
  };
12852
13278
  }
12853
13279
 
12854
- // src/cst/offset-model.ts
12855
- var { isFinite } = Number;
12856
- function floorIndex(arr, len, x) {
12857
- let lo = 0;
12858
- let hi = len - 1;
12859
- let ans = -1;
12860
- while (lo <= hi) {
12861
- const mid = lo + hi >>> 1;
12862
- if (arr[mid] <= x) {
12863
- ans = mid;
12864
- lo = mid + 1;
12865
- } else {
12866
- hi = mid - 1;
12867
- }
12868
- }
12869
- return ans;
12870
- }
12871
- var OffsetIndex = class {
12872
- /** Slot starts, sorted ascending. Parallel to `_ends`. */
12873
- _starts;
12874
- _ends;
12875
- count;
12876
- docStart;
12877
- docEnd;
12878
- constructor(slots, docStart = 0, docEnd) {
12879
- const sorted = [...slots].sort((a, b) => a.start - b.start);
12880
- const starts = [];
12881
- const ends = [];
12882
- let prevEnd = docStart;
12883
- for (const s of sorted) {
12884
- if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
12885
- throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
12886
- }
12887
- if (s.start < prevEnd) {
12888
- throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
12889
- }
12890
- starts.push(s.start);
12891
- ends.push(s.end);
12892
- prevEnd = s.end;
12893
- }
12894
- this._starts = starts;
12895
- this._ends = ends;
12896
- this.count = starts.length;
12897
- this.docStart = docStart;
12898
- this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
12899
- }
12900
- /** The slot at index `i` (0-based, source order), or undefined. */
12901
- slot(i) {
12902
- if (i < 0 || i >= this.count) return void 0;
12903
- return { start: this._starts[i], end: this._ends[i] };
12904
- }
12905
- /**
12906
- * Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
12907
- * `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
12908
- * An offset that lands inside a slot returns that slot's TRAILING gap index
12909
- * (i.e. the gap after it) — callers querying at slot boundaries get the
12910
- * intuitive answer via `gapBefore`/`gapAfter`.
12911
- */
12912
- gapIndexAt(offset) {
12913
- const i = floorIndex(this._starts, this.count, offset);
12914
- if (i < 0) return 0;
12915
- if (offset < this._ends[i]) return i + 1;
12916
- return i + 1;
12917
- }
12918
- /** The gap at index `i` in `[0, count]`. */
12919
- gap(i) {
12920
- if (i < 0 || i > this.count) return void 0;
12921
- const start = i === 0 ? this.docStart : this._ends[i - 1];
12922
- const end = i === this.count ? this.docEnd : this._starts[i];
12923
- return { start, end };
12924
- }
12925
- /**
12926
- * The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
12927
- * `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
12928
- * `docStart`). For a real slot start this is the gap preceding that slot; for an
12929
- * adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
12930
- */
12931
- gapBefore(offset) {
12932
- const e = floorIndex(this._ends, this.count, offset);
12933
- const start = e >= 0 ? this._ends[e] : this.docStart;
12934
- return { start, end: offset };
12935
- }
12936
- /**
12937
- * The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
12938
- * `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
12939
- */
12940
- gapAfter(offset) {
12941
- return { start: offset, end: this._ceilStart(offset) };
12942
- }
12943
- /** Least slot start `>= offset`, else `docEnd`. */
12944
- _ceilStart(offset) {
12945
- let lo = 0;
12946
- let hi = this.count - 1;
12947
- let ans = this.docEnd;
12948
- while (lo <= hi) {
12949
- const mid = lo + hi >>> 1;
12950
- if (this._starts[mid] >= offset) {
12951
- ans = this._starts[mid];
12952
- hi = mid - 1;
12953
- } else {
12954
- lo = mid + 1;
12955
- }
12956
- }
12957
- return ans;
12958
- }
12959
- /** Is this gap empty (adjacent slots, no trivia)? */
12960
- isEmpty(gap) {
12961
- return gap.end <= gap.start;
12962
- }
12963
- };
12964
- var NL = 10;
12965
- var CR = 13;
12966
- var SP = 32;
12967
- var TAB = 9;
12968
- function gapText(source, gap) {
12969
- return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
12970
- }
12971
- function lineBreaksIn(source, gap) {
12972
- let n = 0;
12973
- for (let i = gap.start; i < gap.end; i++) {
12974
- const c = source.charCodeAt(i);
12975
- if (c === NL) n++;
12976
- else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
12977
- }
12978
- return n;
12979
- }
12980
- function blankLinesIn(source, gap) {
12981
- return Math.max(0, lineBreaksIn(source, gap) - 1);
12982
- }
12983
- function lineStartWithin(source, gap) {
12984
- for (let i = gap.end - 1; i >= gap.start; i--) {
12985
- const c = source.charCodeAt(i);
12986
- if (c === NL || c === CR) return i + 1;
12987
- }
12988
- return gap.start;
12989
- }
12990
- function indentWidth(source, gap) {
12991
- const ls = lineStartWithin(source, gap);
12992
- if (ls === gap.start) return 0;
12993
- return gap.end - ls;
12994
- }
12995
- function indentMixed(source, gap) {
12996
- const ls = lineStartWithin(source, gap);
12997
- let sawSpace = false;
12998
- let sawTab = false;
12999
- for (let i = ls; i < gap.end; i++) {
13000
- const c = source.charCodeAt(i);
13001
- if (c === SP) sawSpace = true;
13002
- else if (c === TAB) sawTab = true;
13003
- }
13004
- return sawSpace && sawTab;
13005
- }
13006
- function commentsIn(source, gap, comment) {
13007
- const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
13008
- re.lastIndex = gap.start;
13009
- const out = [];
13010
- let m;
13011
- while ((m = re.exec(source)) && m.index < gap.end) {
13012
- if (m[0].length === 0) {
13013
- re.lastIndex++;
13014
- continue;
13015
- }
13016
- if (m.index + m[0].length > gap.end) break;
13017
- out.push({ start: m.index, end: m.index + m[0].length });
13018
- }
13019
- return out;
13020
- }
13021
- function gapIsSignificant(source, gap, comment) {
13022
- if (gap.end <= gap.start) return false;
13023
- if (lineBreaksIn(source, gap) > 0) return true;
13024
- if (comment && commentsIn(source, gap, comment).length > 0) return true;
13025
- return false;
13026
- }
13027
- function collectLeafSlots(root) {
13028
- const slots = [];
13029
- const visit = (n) => {
13030
- const kids = n.children;
13031
- if (kids && kids.length) {
13032
- for (const c of kids) visit(c);
13033
- } else if (n.span) {
13034
- slots.push({ start: n.span.start, end: n.span.end });
13035
- }
13036
- };
13037
- if (root) visit(root);
13038
- return slots;
13039
- }
13040
- function buildOffsetIndex(root, input = "") {
13041
- return new OffsetIndex(collectLeafSlots(root), 0, input.length);
13042
- }
13043
-
13044
13280
  // src/index.ts
13045
13281
  registerRegexAnalyzer(firstSetFromRegex);
13046
13282
  //# sourceMappingURL=index.cjs.map