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.js CHANGED
@@ -51,8 +51,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
51
51
  return d.value.length === 0;
52
52
  case "keywords":
53
53
  return false;
54
+ // The routed token itself is the selector's match, never empty; a fallback can be.
54
55
  case "routed":
55
- return false;
56
+ return d.fallback === void 0 ? false : me(d.fallback);
56
57
  case "regex":
57
58
  try {
58
59
  const m = new RegExp(d.source).exec("");
@@ -1814,8 +1815,11 @@ function derive(c, seen) {
1814
1815
  case "not":
1815
1816
  case "peek":
1816
1817
  return deriveExpected2(def.parser);
1818
+ // A bare routed() can only fail as "there is no routed token here". With a
1819
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1820
+ // ones — the routed path either succeeds or is not taken.
1817
1821
  case "routed":
1818
- return ["routed()"];
1822
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1819
1823
  case "lazy": {
1820
1824
  const name = c._ruleName;
1821
1825
  if (seen.has(c)) return name ? [name] : [];
@@ -2005,7 +2009,7 @@ function detectStrategy(parsers) {
2005
2009
  function bareLeadingTermKey(term) {
2006
2010
  const d = term._def;
2007
2011
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
2008
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
2012
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
2009
2013
  return null;
2010
2014
  }
2011
2015
  function detectSharedPrefix(parsers) {
@@ -2263,17 +2267,21 @@ function makeWhen(opts = {}) {
2263
2267
  function otherwise(parser2) {
2264
2268
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2265
2269
  }
2266
- function routed() {
2270
+ function routed(fallback) {
2267
2271
  return {
2268
2272
  _tag: "routed",
2269
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2270
- _def: { tag: "routed" },
2273
+ _meta: {
2274
+ // The routed token is whatever the selector matched, so the first set stays
2275
+ // `any` even with a fallback — `any` already subsumes the fallback's.
2276
+ firstSet: { kind: "any" },
2277
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
2278
+ isTrivia: false
2279
+ },
2280
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2271
2281
  parse(_input, pos, ctx) {
2272
2282
  const item = ctx._routed;
2273
- if (item === void 0) {
2274
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2275
- }
2276
- if (pos !== item.span.start) {
2283
+ if (item === void 0 || pos !== item.span.start) {
2284
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2277
2285
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2278
2286
  }
2279
2287
  if (cstCaptureActive(ctx)) {
@@ -2817,12 +2825,12 @@ function markUnusedValues(root) {
2817
2825
  }
2818
2826
  };
2819
2827
  const resolveRoot = (c) => {
2820
- const guard2 = /* @__PURE__ */ new Set();
2828
+ const guard = /* @__PURE__ */ new Set();
2821
2829
  let cur = c;
2822
- while (!guard2.has(cur)) {
2830
+ while (!guard.has(cur)) {
2823
2831
  const d = cur._def;
2824
2832
  if (d.tag !== "lazy") return cur;
2825
- guard2.add(cur);
2833
+ guard.add(cur);
2826
2834
  try {
2827
2835
  cur = d.thunk();
2828
2836
  } catch {
@@ -2991,10 +2999,11 @@ function childrenOf(def) {
2991
2999
  return [def.main, def.skipped];
2992
3000
  case "scanTo":
2993
3001
  return [def.sentinel, ...def.skip];
3002
+ case "routed":
3003
+ return def.fallback ? [def.fallback] : [];
2994
3004
  case "literal":
2995
3005
  case "regex":
2996
3006
  case "keywords":
2997
- case "routed":
2998
3007
  case "guard":
2999
3008
  case "lazy":
3000
3009
  case "unknown":
@@ -3220,54 +3229,271 @@ function peek(combinator) {
3220
3229
  };
3221
3230
  }
3222
3231
 
3232
+ // src/compiler/degradation.ts
3233
+ function formatDegradation(d) {
3234
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
3235
+ }
3236
+ var DETAIL_CAP = 8;
3237
+ function formatDegradations(list) {
3238
+ const byCode = /* @__PURE__ */ new Map();
3239
+ for (const d of list) {
3240
+ const bucket = byCode.get(d.code);
3241
+ if (bucket) bucket.push(d);
3242
+ else byCode.set(d.code, [d]);
3243
+ }
3244
+ const out = [];
3245
+ for (const [code, items] of byCode) {
3246
+ for (const d of items.slice(0, DETAIL_CAP)) out.push(formatDegradation(d));
3247
+ if (items.length > DETAIL_CAP) {
3248
+ out.push(
3249
+ `[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.`
3250
+ );
3251
+ }
3252
+ }
3253
+ return out;
3254
+ }
3255
+ var _sinks = [];
3256
+ function beginDegradationCapture() {
3257
+ _sinks.push(/* @__PURE__ */ new Map());
3258
+ }
3259
+ function degradationCaptureDepth() {
3260
+ return _sinks.length;
3261
+ }
3262
+ function unwindDegradationCapture(depth) {
3263
+ const found = [];
3264
+ while (_sinks.length > depth) {
3265
+ const sink = _sinks.pop();
3266
+ found.unshift(...sink.values());
3267
+ }
3268
+ return found;
3269
+ }
3270
+ function resolveDegradationLevel(explicit) {
3271
+ if (explicit !== void 0) return explicit;
3272
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
3273
+ if (env === "off" || env === "warn" || env === "error") return env;
3274
+ return "warn";
3275
+ }
3276
+ function degradationKey(d) {
3277
+ return JSON.stringify([d.code, d.where, d.subject]);
3278
+ }
3279
+ function recordDegradation(d) {
3280
+ const level = resolveDegradationLevel();
3281
+ if (level === "off") return;
3282
+ const key = degradationKey(d);
3283
+ const sink = _sinks[_sinks.length - 1];
3284
+ if (sink) {
3285
+ if (!sink.has(key)) sink.set(key, d);
3286
+ return;
3287
+ }
3288
+ if (_immediate.has(key)) return;
3289
+ _immediate.add(key);
3290
+ if (level === "error") throw new Error(`parseman: degraded compilation path
3291
+ ${formatDegradation(d)}`);
3292
+ console.warn(formatDegradation(d));
3293
+ }
3294
+ var _immediate = /* @__PURE__ */ new Set();
3295
+ function beginCompileDegradationDrain() {
3296
+ if (resolveDegradationLevel() === "off" || _sinks.length > 0) return () => {
3297
+ };
3298
+ const depth = degradationCaptureDepth();
3299
+ beginDegradationCapture();
3300
+ return (report) => {
3301
+ const found = unwindDegradationCapture(depth);
3302
+ if (!report || found.length === 0) return;
3303
+ const fresh = found.filter((d) => {
3304
+ const k = degradationKey(d);
3305
+ if (_immediate.has(k)) return false;
3306
+ _immediate.add(k);
3307
+ return true;
3308
+ });
3309
+ if (fresh.length === 0) return;
3310
+ const lines = formatDegradations(fresh);
3311
+ if (resolveDegradationLevel() === "error") {
3312
+ throw new Error(`parseman: ${fresh.length} degraded compilation path(s)
3313
+ ${lines.join("\n")}`);
3314
+ }
3315
+ for (const l of lines) console.warn(l);
3316
+ };
3317
+ }
3318
+
3223
3319
  // src/compiler/build-arity.ts
3224
3320
  function cstOutputHost(build) {
3225
3321
  return build?._parsemanCstOutput === true;
3226
3322
  }
3227
- var PARAM_LIST_RE = (
3228
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
3229
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
3230
- );
3231
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
3323
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
3324
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
3325
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
3326
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
3232
3327
  function confirmedBuildArity(src) {
3233
3328
  const s = src.trim();
3234
- const m = PARAM_LIST_RE.exec(s);
3235
- if (!m) return null;
3236
- if (m[3] !== void 0) return 1;
3237
- const inner = (m[1] ?? m[2] ?? "").trim();
3238
- if (inner === "") return 0;
3239
- const parts = inner.split(",");
3329
+ if (NATIVE_CODE_RE.test(s)) return null;
3330
+ if (/\barguments\b/.test(s)) return null;
3331
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
3332
+ const inner = paramListText(s);
3333
+ if (inner === null) return null;
3334
+ if (inner.trim() === "") return 0;
3335
+ const parts = splitTopLevel(inner);
3336
+ if (parts === null) return null;
3240
3337
  for (const part of parts) {
3241
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
3338
+ if (!isConfirmableParam(part.trim())) return null;
3242
3339
  }
3243
- if (/\barguments\b/.test(s)) return null;
3244
3340
  return parts.length;
3245
3341
  }
3342
+ function paramListText(s) {
3343
+ const open = PARAM_LIST_OPEN_RE.exec(s);
3344
+ if (!open) return null;
3345
+ const start = open[0].length;
3346
+ const end = matchingParen(s, start);
3347
+ if (end === null) return null;
3348
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
3349
+ return s.slice(start, end);
3350
+ }
3351
+ function matchingParen(s, start) {
3352
+ let depth = 0;
3353
+ for (let i = start; i < s.length; i++) {
3354
+ const c = s[i];
3355
+ if (c === '"' || c === "'" || c === "`") {
3356
+ const close = skipString(s, i);
3357
+ if (close === null) return null;
3358
+ i = close;
3359
+ continue;
3360
+ }
3361
+ if (c === "(" || c === "[" || c === "{") depth++;
3362
+ else if (c === "]" || c === "}") {
3363
+ if (--depth < 0) return null;
3364
+ } else if (c === ")") {
3365
+ if (depth === 0) return i;
3366
+ depth--;
3367
+ }
3368
+ }
3369
+ return null;
3370
+ }
3371
+ function skipString(s, open) {
3372
+ const quote = s[open];
3373
+ for (let i = open + 1; i < s.length; i++) {
3374
+ if (s[i] === "\\") {
3375
+ i++;
3376
+ continue;
3377
+ }
3378
+ if (s[i] === quote) return i;
3379
+ }
3380
+ return null;
3381
+ }
3382
+ function splitTopLevel(inner) {
3383
+ const parts = [];
3384
+ let depth = 0;
3385
+ let angle = 0;
3386
+ let last = 0;
3387
+ for (let i = 0; i < inner.length; i++) {
3388
+ const c = inner[i];
3389
+ if (c === "=" && inner[i + 1] === ">") {
3390
+ i++;
3391
+ continue;
3392
+ }
3393
+ if (c === '"' || c === "'" || c === "`") {
3394
+ const close = skipString(inner, i);
3395
+ if (close === null) return null;
3396
+ i = close;
3397
+ continue;
3398
+ }
3399
+ if (c === "(" || c === "[" || c === "{") depth++;
3400
+ else if (c === ")" || c === "]" || c === "}") {
3401
+ if (--depth < 0) return null;
3402
+ } else if (c === "<") angle++;
3403
+ else if (c === ">") {
3404
+ if (--angle < 0) return null;
3405
+ } else if (c === "," && depth === 0 && angle === 0) {
3406
+ parts.push(inner.slice(last, i));
3407
+ last = i + 1;
3408
+ }
3409
+ }
3410
+ if (depth !== 0 || angle !== 0) return null;
3411
+ parts.push(inner.slice(last));
3412
+ return parts;
3413
+ }
3414
+ function isConfirmableParam(part) {
3415
+ const head = PARAM_HEAD_RE.exec(part);
3416
+ if (!head) return false;
3417
+ const rest = part.slice(head[0].length);
3418
+ if (rest === "") return true;
3419
+ if (rest[0] !== ":") return false;
3420
+ return !hasDefaultAssign(rest.slice(1));
3421
+ }
3422
+ function hasDefaultAssign(annotation) {
3423
+ for (let i = 0; i < annotation.length; i++) {
3424
+ if (annotation[i] !== "=") continue;
3425
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
3426
+ i++;
3427
+ continue;
3428
+ }
3429
+ const prev = annotation[i - 1];
3430
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
3431
+ return true;
3432
+ }
3433
+ return false;
3434
+ }
3435
+ function buildAnalysisSrc(def) {
3436
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
3437
+ }
3438
+ var RESOLVER_REASONS = {
3439
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
3440
+ "arguments": "its body references `arguments`",
3441
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
3442
+ "not-a-function": "the binding it names is not a function declaration",
3443
+ "unresolved-import": "its module could not be resolved or parsed",
3444
+ "not-found": "the name did not resolve to any binding",
3445
+ "computed": "it is a computed expression",
3446
+ "foreign-source": "it is named inside an imported factory, whose offsets the entry-file resolver cannot index"
3447
+ };
3448
+ function unconfirmableReason(def, src) {
3449
+ const declared = def.buildArityUnresolved;
3450
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
3451
+ const s = src.trim();
3452
+ const params = s.slice(0, s.indexOf(")") + 1);
3453
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
3454
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
3455
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
3456
+ return "it names a binding this build could not resolve to a function declaration";
3457
+ }
3458
+ return "its parameter list could not be parsed";
3459
+ }
3460
+ function confirmedArityForDef(def) {
3461
+ if (def.buildArity !== void 0) return def.buildArity;
3462
+ const src = buildAnalysisSrc(def);
3463
+ const arity = confirmedBuildArity(src);
3464
+ if (arity !== null) return arity;
3465
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
3466
+ recordDegradation({
3467
+ code: "build-arity-unconfirmed",
3468
+ severity: "warn",
3469
+ where: `node("${def.type ?? "<inferred>"}")`,
3470
+ subject,
3471
+ 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`,
3472
+ 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"
3473
+ });
3474
+ return null;
3475
+ }
3246
3476
  function buildReadsChildren(def) {
3247
3477
  if (!def.build) return true;
3248
- const src = def.buildSrc ?? def.build.toString();
3249
- const arity = confirmedBuildArity(src);
3478
+ const arity = confirmedArityForDef(def);
3250
3479
  if (arity === null) return true;
3251
3480
  return arity >= 1;
3252
3481
  }
3253
3482
  function buildReadsRaw(def) {
3254
3483
  if (!def.build) return true;
3255
- const src = def.buildSrc ?? def.build.toString();
3256
- const arity = confirmedBuildArity(src);
3484
+ const arity = confirmedArityForDef(def);
3257
3485
  if (arity === null) return true;
3258
3486
  return arity >= 4;
3259
3487
  }
3260
3488
  function buildReadsTrivia(def) {
3261
3489
  if (!def.build) return true;
3262
- const src = def.buildSrc ?? def.build.toString();
3263
- const arity = confirmedBuildArity(src);
3490
+ const arity = confirmedArityForDef(def);
3264
3491
  if (arity === null) return true;
3265
3492
  return arity >= 5;
3266
3493
  }
3267
3494
  function buildReadsState(def) {
3268
3495
  if (!def.build) return true;
3269
- const src = def.buildSrc ?? def.build.toString();
3270
- const arity = confirmedBuildArity(src);
3496
+ const arity = confirmedArityForDef(def);
3271
3497
  if (arity === null) return true;
3272
3498
  return arity >= 6;
3273
3499
  }
@@ -3275,8 +3501,7 @@ function buildReadsState(def) {
3275
3501
  // src/compiler/fields.ts
3276
3502
  function buildReadsFields(def) {
3277
3503
  if (!def.build) return true;
3278
- const src = def.buildSrc ?? def.build.toString();
3279
- const arity = confirmedBuildArity(src);
3504
+ const arity = confirmedArityForDef(def);
3280
3505
  if (arity === null) return true;
3281
3506
  return arity >= 2;
3282
3507
  }
@@ -3457,6 +3682,13 @@ function isCstChild(value) {
3457
3682
  function missingInferredType() {
3458
3683
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3459
3684
  }
3685
+ function normalizeBuildArity(arity) {
3686
+ if (arity === void 0) return void 0;
3687
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3688
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3689
+ }
3690
+ return arity;
3691
+ }
3460
3692
  function normalizeProject(project) {
3461
3693
  if (project === void 0) return void 0;
3462
3694
  if (!Number.isInteger(project) || project < 0) {
@@ -3492,13 +3724,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3492
3724
  const captureTrivia = opts?.captureTrivia === true;
3493
3725
  const trailingTrivia = opts?.trailingTrivia === true;
3494
3726
  const tags = opts?.tags;
3727
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3495
3728
  if (unwrap && collapse) {
3496
3729
  throw new Error("node() options cannot set both unwrap and collapse");
3497
3730
  }
3498
3731
  if (project !== void 0 && (unwrap || collapse)) {
3499
3732
  throw new Error("node() options cannot combine project with unwrap or collapse");
3500
3733
  }
3501
- 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;
3734
+ 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;
3502
3735
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3503
3736
  const clonesState = build ? buildReadsState(def) : project === void 0;
3504
3737
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3544,7 +3777,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3544
3777
  const cstOutput = cstOutputHost(ctx.build);
3545
3778
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3546
3779
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3547
- 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 };
3780
+ 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 };
3548
3781
  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 };
3549
3782
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3550
3783
  pushCstChild(ctx, built, rawEntry);
@@ -3739,7 +3972,7 @@ function leaf(root, fn) {
3739
3972
  }
3740
3973
 
3741
3974
  // src/version.ts
3742
- var PARSEMAN_VERSION = "0.44.0";
3975
+ var PARSEMAN_VERSION = "0.45.0";
3743
3976
 
3744
3977
  // src/cst/host-mode.ts
3745
3978
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -4614,7 +4847,7 @@ function findDivergentSlot(members, shapeOf) {
4614
4847
  const path = [];
4615
4848
  const defs = [];
4616
4849
  let cur = [...members];
4617
- for (let guard2 = 0; guard2 < 64; guard2++) {
4850
+ for (let guard = 0; guard < 64; guard++) {
4618
4851
  const d0 = cur[0]._def;
4619
4852
  const kidLists = cur.map((c) => childrenOf2(c._def));
4620
4853
  const arity = kidLists[0].length;
@@ -5573,7 +5806,15 @@ function children(def, winners) {
5573
5806
  let resolved;
5574
5807
  try {
5575
5808
  resolved = def.thunk();
5576
- } catch {
5809
+ } catch (e) {
5810
+ recordDegradation({
5811
+ code: "coverage-definitions-unavailable",
5812
+ severity: "warn",
5813
+ where: "<lazy rule reference>",
5814
+ subject: `unresolvable reference (${e.message})`,
5815
+ 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",
5816
+ otherwise: "the referenced rule and its whole subtree would contribute definitions"
5817
+ });
5577
5818
  return [];
5578
5819
  }
5579
5820
  const name = resolved._ruleName;
@@ -7074,27 +7315,53 @@ function emitScannableTerminal(shape, c) {
7074
7315
  }
7075
7316
 
7076
7317
  // src/compiler/inline-build.ts
7077
- 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*$/;
7318
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
7319
+ var ANY_P = String.raw`\w+${ANN}`;
7320
+ var MK_BUILD_RE = new RegExp(
7321
+ 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*$`
7322
+ );
7078
7323
  function escapeRegExp(s) {
7079
7324
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7080
7325
  }
7326
+ function identMkBuildRe(typeIdent) {
7327
+ return new RegExp(
7328
+ 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*$`
7329
+ );
7330
+ }
7081
7331
  function looseMkBuildRe(type) {
7082
7332
  return new RegExp(
7083
- 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*$`
7333
+ 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*$`
7084
7334
  );
7085
7335
  }
7336
+ var LOOKS_LIKE_MK_RE = /=>\s*(?:[\w$.]*\.)?mk\s*\(/;
7086
7337
  function analyzeMkInlineBuild(def) {
7087
7338
  if (!def.build) return null;
7088
7339
  if (def.type === void 0) return null;
7089
- const src = (def.buildSrc ?? def.build.toString()).trim();
7340
+ const src = buildAnalysisSrc(def).trim();
7090
7341
  const strict = src.match(MK_BUILD_RE);
7091
7342
  if (strict) {
7092
7343
  const mkType = strict[6];
7093
- return mkType === def.type ? def.type : null;
7344
+ if (mkType === def.type) return def.type;
7345
+ reportMkMiss(def.type, src, `it builds a "${mkType}" node, not "${def.type}"`);
7346
+ return null;
7094
7347
  }
7095
7348
  if (looseMkBuildRe(def.type).test(src)) return def.type;
7349
+ if (def.typeSrc !== void 0 && identMkBuildRe(def.typeSrc).test(src)) return def.type;
7350
+ if (LOOKS_LIKE_MK_RE.test(src)) {
7351
+ reportMkMiss(def.type, src, "its parameter list or argument order does not match `(children, fields, span, rawChildren, triviaLog) => mk(type, children, rawChildren, span, triviaLog)`");
7352
+ }
7096
7353
  return null;
7097
7354
  }
7355
+ function reportMkMiss(type, src, why) {
7356
+ recordDegradation({
7357
+ code: "mk-inline-missed",
7358
+ severity: "warn",
7359
+ where: `node("${type}")`,
7360
+ subject: `build reducer \`${src.split("\n")[0].slice(0, 80)}\``,
7361
+ fellBackTo: `looks like an inline-\`mk\` builder but ${why}, so every match pays a \`_build[n](...)\` call instead of an inlined object literal`,
7362
+ otherwise: "the node object would be constructed at the call site with no call frame"
7363
+ });
7364
+ }
7098
7365
  function emitInlineMkNodeExpr(type, chV, rawV, spanExpr, tlV) {
7099
7366
  return `{ _tag: 'node', type: ${JSON.stringify(type)}, span: ${spanExpr}, children: ${chV}, rawCount: ${rawV}.length, localTriviaLen: ${tlV}.length }`;
7100
7367
  }
@@ -7277,10 +7544,34 @@ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
7277
7544
  seen.delete(p);
7278
7545
  }
7279
7546
  }
7547
+ function tern(cond, whenTrue, whenFalse) {
7548
+ if (cond === "false") return whenFalse;
7549
+ if (cond === "true") return whenTrue;
7550
+ return `${cond} ? ${whenTrue} : ${whenFalse}`;
7551
+ }
7552
+ function notGate(cond) {
7553
+ if (cond === "false") return "true";
7554
+ if (cond === "true") return "false";
7555
+ return `!(${cond})`;
7556
+ }
7557
+ function orGate(a, b) {
7558
+ if (a === "false") return b;
7559
+ if (b === "false") return a;
7560
+ if (a === "true" || b === "true") return "true";
7561
+ return `${a} || ${b}`;
7562
+ }
7563
+ function andGate(a, b) {
7564
+ if (a === "false" || b === "false") return "false";
7565
+ if (a === "true") return b;
7566
+ if (b === "true") return a;
7567
+ return `${a} && ${b}`;
7568
+ }
7280
7569
  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 }";
7570
+ 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 } }";
7281
7571
  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 }";
7282
7572
  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] } }";
7283
7573
  var _loweringSink = null;
7574
+ var _inlineCapSink = null;
7284
7575
  function hasSelectedRootTrivia(ctx) {
7285
7576
  return ctx.rootTriviaClassified === true;
7286
7577
  }
@@ -8029,9 +8320,11 @@ function emitSeqValues(def, ctx, pos) {
8029
8320
  const markLog = v(ctx, "_mklg");
8030
8321
  const markRootLog = hasSelectedRootTrivia(ctx) ? v(ctx, "_mkrlg") : null;
8031
8322
  const scanEndV = v(ctx, "_sne");
8032
- const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? "(_ctx._triviaLog !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0" : "_ctx._triviaLog !== undefined ? 2 : 0";
8323
+ const logV = v(ctx, "_tlg");
8324
+ const capArg = ctx.noHoist ? "0" : hasSelectedRootTrivia(ctx) ? `(${logV} !== undefined || _ctx._rootTriviaLog !== undefined) ? 2 : 0` : `${logV} !== undefined ? 2 : 0`;
8033
8325
  stmts.push(
8034
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`,
8326
+ `${ind(ctx)}const ${logV} = _ctx._triviaLog`,
8327
+ `${ind(ctx)}const ${markLog} = ${logV} !== undefined ? ${logV}.length : 0`,
8035
8328
  ...markRootLog ? [`${ind(ctx)}const ${markRootLog} = _ctx._rootTriviaLog ? _ctx._rootTriviaLog.length : 0`] : [],
8036
8329
  `${ind(ctx)}const ${scanEndV} = ${trivFn}(input, ${curV}, _ctx, ${capArg})`,
8037
8330
  ...emitLineTrack(ctx, curV, scanEndV)
@@ -8041,7 +8334,7 @@ function emitSeqValues(def, ctx, pos) {
8041
8334
  const endAfterV = v(ctx, "_sea");
8042
8335
  stmts.push(
8043
8336
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
8044
- `${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};` : ""} }`
8337
+ `${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};` : ""} }`
8045
8338
  );
8046
8339
  valueVars.push(r2.valueVar);
8047
8340
  continue;
@@ -9113,9 +9406,18 @@ function emitPeek(def, ctx, pos) {
9113
9406
  endVar: pos
9114
9407
  };
9115
9408
  }
9116
- function emitRouted(ctx, pos) {
9409
+ function emitRouted(ctx, pos, def) {
9117
9410
  const local = ctx.routedLocal;
9411
+ const fallback = def.fallback;
9118
9412
  if (local !== void 0) {
9413
+ if (fallback !== void 0 && local.startVar !== pos) {
9414
+ return emitRoutedWithFallback(ctx, pos, fallback, {
9415
+ test: `${local.startVar} === ${pos}`,
9416
+ valExpr: local.valueVar,
9417
+ startExpr: local.startVar,
9418
+ endExpr: local.endVar
9419
+ });
9420
+ }
9119
9421
  return {
9120
9422
  stmts: [
9121
9423
  ...emitIfFail(ctx, `${local.startVar} !== ${pos}`, failBody(ctx, '"routed()"', pos)),
@@ -9126,9 +9428,19 @@ function emitRouted(ctx, pos) {
9126
9428
  };
9127
9429
  }
9128
9430
  const item = v(ctx, "_rt");
9431
+ const read = `${ind(ctx)}const ${item} = _ctx._routed`;
9432
+ if (fallback !== void 0) {
9433
+ const r = emitRoutedWithFallback(ctx, pos, fallback, {
9434
+ test: `${item} !== undefined && ${item}.span.start === ${pos}`,
9435
+ valExpr: `${item}.value`,
9436
+ startExpr: `${item}.span.start`,
9437
+ endExpr: `${item}.span.end`
9438
+ });
9439
+ return { ...r, stmts: [read, ...r.stmts] };
9440
+ }
9129
9441
  return {
9130
9442
  stmts: [
9131
- `${ind(ctx)}const ${item} = _ctx._routed`,
9443
+ read,
9132
9444
  ...emitIfFail(ctx, `${item} === undefined || ${item}.span.start !== ${pos}`, failBody(ctx, '"routed()"', pos)),
9133
9445
  ...emitLeafCapture(ctx, `${item}.value`, `${item}.span.start`, `${item}.span.end`)
9134
9446
  ],
@@ -9136,6 +9448,30 @@ function emitRouted(ctx, pos) {
9136
9448
  endVar: `${item}.span.end`
9137
9449
  };
9138
9450
  }
9451
+ function emitRoutedWithFallback(ctx, pos, fallback, routed2) {
9452
+ const valV = v(ctx, "_rtv");
9453
+ const endV = v(ctx, "_rte");
9454
+ const i = ind(ctx);
9455
+ ctx.indent++;
9456
+ const capture = emitLeafCapture(ctx, routed2.valExpr, routed2.startExpr, routed2.endExpr);
9457
+ const fb = emit(fallback, ctx, pos);
9458
+ const inner = ind(ctx);
9459
+ ctx.indent--;
9460
+ return {
9461
+ stmts: [
9462
+ `${i}let ${valV}, ${endV}`,
9463
+ `${i}if (${routed2.test}) {`,
9464
+ ...capture,
9465
+ `${inner}${valV} = ${routed2.valExpr}; ${endV} = ${routed2.endExpr}`,
9466
+ `${i}} else {`,
9467
+ ...fb.stmts,
9468
+ `${inner}${valV} = ${fb.valueVar}; ${endV} = ${fb.endVar}`,
9469
+ `${i}}`
9470
+ ],
9471
+ valueVar: valV,
9472
+ endVar: endV
9473
+ };
9474
+ }
9139
9475
  function escapeTokenLiteral(value) {
9140
9476
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
9141
9477
  }
@@ -9317,22 +9653,16 @@ function emitNode(def, ctx, pos) {
9317
9653
  const smk = structural ? v(ctx, "_smk") : null;
9318
9654
  if (structural) ctx.needsHostReads = true;
9319
9655
  const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
9320
- const pmV = v(ctx, "_pm"), recV = v(ctx, "_rec"), capV = v(ctx, "_cap");
9321
- const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
9322
- const profileRecognizer = recV;
9323
- const profileCapture = capV;
9324
- const cstMode = ctx.hostMode === "cst";
9325
- const directCstV = !structural && !cstMode && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
9326
- const directCstGate = directCstV === null ? "false" : profileCapture;
9656
+ const profileRecognizer = "false";
9657
+ const profileCapture = "false";
9327
9658
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
9328
9659
  const chNeededExpr = def.unwrap || def.collapse ? "true" : `(_ctx._pmReadsCh ??= (_ctx.build === undefined || _ctx.build._parsemanReadsChildren !== false || _ctx.build._parsemanCstCollapse !== undefined))`;
9329
- const chAlloc = def.unwrap || def.collapse ? `${profileRecognizer} ? undefined : []` : `${profileRecognizer} ? undefined : ((${profileCapture} || ${chNeededExpr}) ? [] : undefined)`;
9330
- 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)))` : ""}
9331
- ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9332
- ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
9333
- ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
9660
+ const chAlloc = def.unwrap || def.collapse ? tern(profileRecognizer, "undefined", "[]") : tern(profileRecognizer, "undefined", `${tern(orGate(profileCapture, chNeededExpr), "[]", "undefined")}`);
9661
+ const dcstAlloc = "undefined";
9662
+ 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))"))}` : ""}
9663
+ ${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)}`;
9334
9664
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
9335
- const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
9665
+ const fieldsOn = structural ? capFv ?? "false" : tern(profileRecognizer, "false", capturesFields ? "true" : "false");
9336
9666
  const sf = hasFields ? v(ctx, "_sf") : null;
9337
9667
  const fArr = hasFields ? v(ctx, "_fa") : null;
9338
9668
  const fObj = hasFields ? v(ctx, "_fields") : "undefined";
@@ -9341,7 +9671,7 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9341
9671
  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}` : ""}` : "";
9342
9672
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
9343
9673
  const preGuard = [];
9344
- if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
9674
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
9345
9675
  const gcV = v(ctx, "_ngc");
9346
9676
  const gExp = armStaticExpected(ctx, def.parser);
9347
9677
  const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(ctx, gExp, pos);
@@ -9352,7 +9682,6 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9352
9682
  }
9353
9683
  const stmts = [
9354
9684
  ...preGuard,
9355
- profHoist,
9356
9685
  allocStmt,
9357
9686
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
9358
9687
  `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}${installTrivia}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
@@ -9369,21 +9698,13 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9369
9698
  }
9370
9699
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}${restoreTrivia}${sf ? `; _ctx._fields = ${sf}` : ""}`);
9371
9700
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
9372
- stmts.push(
9373
- `${i}if (_ctx._pmProfile) {`,
9374
- `${i} _ctx._pmProfile.nodes++`,
9375
- `${i} if (${profileCapture}) {`,
9376
- `${i} _ctx._pmProfile.childSlots += ${chV}.length; _ctx._pmProfile.rawSlots += ${rawV}.length; _ctx._pmProfile.triviaSlots += ${tlV}.length${fArr ? `; _ctx._pmProfile.fieldSlots += ${fArr} ? ${fArr}.length : 0` : ""}`,
9377
- `${i} }`,
9378
- `${i}}`
9379
- );
9380
9701
  let stV = "undefined";
9381
9702
  if (structural) {
9382
9703
  stV = v(ctx, "_nst");
9383
9704
  stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9384
9705
  } else if (clonesState) {
9385
9706
  stV = v(ctx, "_nst");
9386
- stmts.push(`${i}const ${stV} = !(${profileRecognizer} || ${profileCapture}) && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
9707
+ stmts.push(`${i}const ${stV} = ${andGate(notGate(orGate(profileRecognizer, profileCapture)), "_ctx.state !== undefined")} ? Object.assign({}, _ctx.state) : undefined`);
9387
9708
  }
9388
9709
  if (sf && fArr) {
9389
9710
  const fe = v(ctx, "_fe");
@@ -9407,19 +9728,22 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
9407
9728
  if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
9408
9729
  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})`;
9409
9730
  const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
9410
- const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${hostBuildArgs})`;
9731
+ const hostBuildExpr = `_ctx.build(${hostBuildArgs})`;
9411
9732
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
9412
- 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;
9733
+ const hostCollapses = structural || cstOut;
9734
+ 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;
9413
9735
  const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
9414
9736
  const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
9415
9737
  const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
9416
- stmts.push(
9417
- `${i}const ${ndV} = (${profileRecognizer} || ${profileCapture}) ? undefined : (${finalExpr})`,
9418
- `${i}if (!(${profileRecognizer})) {`,
9419
- `${i} if (${sc}) ${sc}.push(${ndV})`,
9420
- `${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} })`,
9421
- `${i}}`
9422
- );
9738
+ const ndGate = orGate(profileRecognizer, profileCapture);
9739
+ const recGate = notGate(profileRecognizer);
9740
+ const pushIndent = recGate === "true" ? i : `${i} `;
9741
+ const pushCh = `${pushIndent}if (${sc}) ${sc}.push(${ndV})`;
9742
+ ctx.needsRawEntry = true;
9743
+ const pushRaw = `${pushIndent}if (${sr}) ${sr}.push(_rawEntry(${ndV}, input, ${pos}, ${endVar}${ctx.lineTracking ? `, ${nodeSpanV}` : ""}))`;
9744
+ stmts.push(`${i}const ${ndV} = ${tern(ndGate, "undefined", `(${finalExpr})`)}`);
9745
+ if (recGate === "true") stmts.push(pushCh, pushRaw);
9746
+ else stmts.push(`${i}if (${recGate}) {`, pushCh, pushRaw, `${i}}`);
9423
9747
  return { stmts, valueVar: ndV, endVar };
9424
9748
  }
9425
9749
  function emitRuntimeFallback(parser2, ctx, pos) {
@@ -9448,7 +9772,12 @@ function emitLazy(p, def, ctx, pos) {
9448
9772
  } catch {
9449
9773
  return emitRuntimeFallback(p, ctx, pos);
9450
9774
  }
9451
- return emit(resolved, ctx, pos);
9775
+ const cost = usage.sizes.get(resolved) ?? 1;
9776
+ if (cost <= ctx.inlineLeft) {
9777
+ ctx.inlineLeft -= cost;
9778
+ return emit(resolved, ctx, pos);
9779
+ }
9780
+ _inlineCapSink?.push({ fn: ctx.currentFnName ?? "<root>", nodes: cost });
9452
9781
  }
9453
9782
  if (!ctx.namedParsers.has(p)) {
9454
9783
  const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
@@ -9463,6 +9792,9 @@ function emitLazy(p, def, ctx, pos) {
9463
9792
  const savedIndent = ctx.indent;
9464
9793
  const savedFailLabel = ctx.failLabel;
9465
9794
  const savedRecord = ctx.recordFail;
9795
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9796
+ ctx.inlineLeft = ctx.inlineMax;
9797
+ ctx.currentFnName = fnName2;
9466
9798
  const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
9467
9799
  const savedActiveRule = ctx.activeCoverageRuleId;
9468
9800
  const savedSuppressFailure = ctx.suppressCoverageFailure;
@@ -9477,6 +9809,8 @@ function emitLazy(p, def, ctx, pos) {
9477
9809
  ctx.indent = savedIndent;
9478
9810
  ctx.failLabel = savedFailLabel;
9479
9811
  ctx.recordFail = savedRecord;
9812
+ ctx.inlineLeft = savedInlineLeft;
9813
+ ctx.currentFnName = savedFnName;
9480
9814
  ctx.activeCoverageRuleId = savedActiveRule;
9481
9815
  ctx.suppressCoverageFailure = savedSuppressFailure;
9482
9816
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
@@ -9571,6 +9905,9 @@ function emit(p, ctx, pos) {
9571
9905
  const savedIndent = ctx.indent;
9572
9906
  const savedFailLabel = ctx.failLabel;
9573
9907
  const savedRecord = ctx.recordFail;
9908
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
9909
+ ctx.inlineLeft = ctx.inlineMax;
9910
+ ctx.currentFnName = fnName;
9574
9911
  ctx.indent = 1;
9575
9912
  ctx.failLabel = "_pfail";
9576
9913
  ctx.recordFail = true;
@@ -9578,6 +9915,8 @@ function emit(p, ctx, pos) {
9578
9915
  ctx.indent = savedIndent;
9579
9916
  ctx.failLabel = savedFailLabel;
9580
9917
  ctx.recordFail = savedRecord;
9918
+ ctx.inlineLeft = savedInlineLeft;
9919
+ ctx.currentFnName = savedFnName;
9581
9920
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
9582
9921
  return instrument(emitNamedFnCall(ctx, fnName, pos));
9583
9922
  }
@@ -9771,7 +10110,7 @@ function emitDispatch(p, ctx, pos) {
9771
10110
  case "peek":
9772
10111
  return emitPeek(def, ctx, pos);
9773
10112
  case "routed":
9774
- return emitRouted(ctx, pos);
10113
+ return emitRouted(ctx, pos, def);
9775
10114
  case "node":
9776
10115
  return emitNode(def, ctx, pos);
9777
10116
  case "scanTo":
@@ -9806,6 +10145,9 @@ function emitDispatch(p, ctx, pos) {
9806
10145
  const savedIndent = ctx.indent;
9807
10146
  const savedFailLabel = ctx.failLabel;
9808
10147
  const savedRecord = ctx.recordFail;
10148
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10149
+ ctx.inlineLeft = ctx.inlineMax;
10150
+ ctx.currentFnName = fnName;
9809
10151
  ctx.indent = 1;
9810
10152
  ctx.failLabel = "_pfail";
9811
10153
  ctx.recordFail = true;
@@ -9813,6 +10155,8 @@ function emitDispatch(p, ctx, pos) {
9813
10155
  ctx.indent = savedIndent;
9814
10156
  ctx.failLabel = savedFailLabel;
9815
10157
  ctx.recordFail = savedRecord;
10158
+ ctx.inlineLeft = savedInlineLeft;
10159
+ ctx.currentFnName = savedFnName;
9816
10160
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
9817
10161
  }
9818
10162
  const fn = ctx.namedParsers.get(innerParser);
@@ -9971,12 +10315,16 @@ function childrenOf3(def) {
9971
10315
  return [def.parser, def.sentinel];
9972
10316
  case "scanTo":
9973
10317
  return [def.sentinel, ...def.skip];
10318
+ // A `routed()` fallback IS emitted at this site (emitRouted), so the usage
10319
+ // analysis must see the edge — otherwise a fallback shared by several routed()
10320
+ // sites is miscounted as single-use and inlined at each.
10321
+ case "routed":
10322
+ return def.fallback ? [def.fallback] : [];
9974
10323
  case "lazy":
9975
10324
  case "literal":
9976
10325
  case "regex":
9977
10326
  case "keywords":
9978
10327
  case "guard":
9979
- case "routed":
9980
10328
  case "unknown":
9981
10329
  return [];
9982
10330
  }
@@ -9997,6 +10345,17 @@ function isHoistableTag(tag) {
9997
10345
  return HOISTABLE_TAGS.has(tag);
9998
10346
  }
9999
10347
  var HOIST_MIN_SUBTREE = 3;
10348
+ var INLINE_MAX_NODES = 1e3;
10349
+ function resolveInlineMax(explicit) {
10350
+ if (explicit !== void 0) return explicit;
10351
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_MAX_INLINE : void 0;
10352
+ if (env !== void 0 && env !== "") {
10353
+ if (env === "off" || env === "infinity") return Infinity;
10354
+ const n = Number(env);
10355
+ if (Number.isFinite(n) && n >= 0) return n;
10356
+ }
10357
+ return INLINE_MAX_NODES;
10358
+ }
10000
10359
  function analyzeLazyUsage(root) {
10001
10360
  return analyzeLazyUsageMulti([root]);
10002
10361
  }
@@ -10102,102 +10461,6 @@ function ruleDependencies(ruleMap) {
10102
10461
  }
10103
10462
  return deps;
10104
10463
  }
10105
- function resolveGatingLevel(opt) {
10106
- const explicit = typeof opt === "string" ? opt : opt?.level;
10107
- if (explicit !== void 0) return explicit;
10108
- const env = typeof process !== "undefined" ? process.env?.PARSEMAN_GATING : void 0;
10109
- if (env === "off" || env === "warn" || env === "error") return env;
10110
- return "warn";
10111
- }
10112
- function runGatingDiagnostic(combinator, opt) {
10113
- return reportGating(opt, (o) => analyzeGating(combinator, o));
10114
- }
10115
- function runGatingDiagnosticRules(ruleMap, opt) {
10116
- return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
10117
- }
10118
- function runFusedGatingDiagnostic(getRuleMaps, opt, getOpaque) {
10119
- if (resolveGatingLevel(opt) === "off") return void 0;
10120
- const ruleMaps = getRuleMaps();
10121
- const opaque = getOpaque?.() ?? [];
10122
- const opaqueFindings = opaque.map((o) => ({
10123
- rule: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
10124
- kind: "opaque-artifact",
10125
- reason: `precompiled artifact "${o.ns}" carries compiled rule functions, not re-lowerable IR \u2014 its choices were not examined at this fuse.`
10126
- }));
10127
- if (!ruleMaps.some((map) => hasExternalRuleRef(map))) {
10128
- if (opaqueFindings.length === 0) return void 0;
10129
- return reportGating(opt, () => ({
10130
- totalChoices: 0,
10131
- gated: 0,
10132
- recoverable: 0,
10133
- unanalysable: opaqueFindings,
10134
- ungated: [],
10135
- accepted: [],
10136
- deferred: [],
10137
- acceptedUnused: [],
10138
- choices: [],
10139
- antiPatterns: []
10140
- }));
10141
- }
10142
- const winners = /* @__PURE__ */ new Map();
10143
- for (const map of ruleMaps) for (const [name, rule] of map) {
10144
- if (rule._def.tag === "lazy") {
10145
- try {
10146
- rule._def.thunk();
10147
- } catch {
10148
- continue;
10149
- }
10150
- }
10151
- winners.set(name, rule);
10152
- }
10153
- const entries = [...winners];
10154
- return reportGating(opt, (o) => {
10155
- const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
10156
- const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
10157
- return {
10158
- ...fused,
10159
- ungated: fused.ungated.filter((c) => deferredHere.has(c.id)),
10160
- antiPatterns: [],
10161
- unanalysable: [...fused.unanalysable, ...opaqueFindings]
10162
- };
10163
- });
10164
- }
10165
- function reportGating(opt, analyze) {
10166
- const level = resolveGatingLevel(opt);
10167
- if (level === "off") return void 0;
10168
- const obj = opt !== null && typeof opt === "object" ? opt : void 0;
10169
- 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;
10170
- let report;
10171
- try {
10172
- report = analyze(analyzeOpts);
10173
- } catch (e) {
10174
- const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
10175
- const failed = {
10176
- totalChoices: 0,
10177
- gated: 0,
10178
- recoverable: 0,
10179
- unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason: `gating analysis threw \u2014 ${reason}` }],
10180
- ungated: [],
10181
- accepted: [],
10182
- deferred: [],
10183
- acceptedUnused: [],
10184
- choices: [],
10185
- antiPatterns: []
10186
- };
10187
- const failLines = formatGatingWarnings(failed);
10188
- if (level === "error") throw new Error(`parseman: gating analysis FAILED (no verdict available)
10189
- ${failLines.join("\n")}`);
10190
- for (const l of failLines) console.warn(l);
10191
- return failed;
10192
- }
10193
- const lines = formatGatingWarnings(report);
10194
- if (lines.length > 0) {
10195
- 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)
10196
- ${lines.join("\n")}`);
10197
- for (const l of lines) console.warn(l);
10198
- }
10199
- return report;
10200
- }
10201
10464
  function resolveDuplicationLevel(opt) {
10202
10465
  const explicit = typeof opt === "string" ? opt : opt?.level;
10203
10466
  if (explicit !== void 0) return explicit;
@@ -10240,7 +10503,17 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
10240
10503
  return reportDuplication(opt, (o) => analyzeDuplicationRules(ruleMap, o));
10241
10504
  }
10242
10505
  function compile(combinator, mapFnSources, opts) {
10243
- const gatingReport = runGatingDiagnostic(combinator, opts?.gating);
10506
+ const drain = beginCompileDegradationDrain();
10507
+ let completed = false;
10508
+ try {
10509
+ const compiled = compileImpl(combinator, mapFnSources, opts);
10510
+ completed = true;
10511
+ return compiled;
10512
+ } finally {
10513
+ drain(completed);
10514
+ }
10515
+ }
10516
+ function compileImpl(combinator, mapFnSources, opts) {
10244
10517
  runDuplicationDiagnostic(combinator, opts?.duplication);
10245
10518
  markUnusedValues(combinator);
10246
10519
  const grammarTrivia = combinator._meta.grammarTrivia;
@@ -10248,9 +10521,12 @@ function compile(combinator, mapFnSources, opts) {
10248
10521
  const grammarScanSkip = combinator._meta.grammarScanSkip;
10249
10522
  const grammarHostMode = combinator._meta.grammarHostMode;
10250
10523
  const grammarTrackLines = combinator._meta.grammarTrackLines;
10524
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10251
10525
  const ctx = {
10252
10526
  vars: 0,
10253
10527
  indent: 1,
10528
+ inlineMax: _inlineMax,
10529
+ inlineLeft: _inlineMax,
10254
10530
  regexDecls: [],
10255
10531
  regexMap: /* @__PURE__ */ new Map(),
10256
10532
  expectedDecls: [],
@@ -10296,11 +10572,13 @@ function compile(combinator, mapFnSources, opts) {
10296
10572
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
10297
10573
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
10298
10574
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
10575
+ const rawEntryDecls = ctx.needsRawEntry ? [RAW_ENTRY_DECL] : [];
10299
10576
  const lineTrackDecls = ctx.needsLineTrack ? [LINE_TRACK_DECL] : [];
10300
10577
  const lineSpanDecls = ctx.needsLineSpan ? [LINE_SPAN_DECL] : [];
10301
10578
  const source = [
10302
10579
  ...emptyTlDecls,
10303
10580
  ...hostReadsDecls,
10581
+ ...rawEntryDecls,
10304
10582
  ...lineTrackDecls,
10305
10583
  ...lineSpanDecls,
10306
10584
  ...ctx.regexDecls,
@@ -10315,6 +10593,7 @@ function compile(combinator, mapFnSources, opts) {
10315
10593
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
10316
10594
  ...emptyTlDecls,
10317
10595
  ...hostReadsDecls,
10596
+ ...rawEntryDecls,
10318
10597
  ...lineTrackDecls,
10319
10598
  ...lineSpanDecls,
10320
10599
  ...ctx.regexDecls,
@@ -10364,8 +10643,7 @@ function compile(combinator, mapFnSources, opts) {
10364
10643
  const compiledMeta = {
10365
10644
  source,
10366
10645
  inlineExpression,
10367
- ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
10368
- ...gatingReport === void 0 ? {} : { gating: gatingReport }
10646
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
10369
10647
  };
10370
10648
  if (!ctx.lineTracking) {
10371
10649
  return {
@@ -10431,7 +10709,6 @@ function publicRuleWrapperSource(rule, fnSource, ambientTriviaKindLabels, ambien
10431
10709
  }
10432
10710
  function compileLinkable(ruleMapArg, ns, opts) {
10433
10711
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
10434
- if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
10435
10712
  if (opts?.duplication !== void 0) runDuplicationDiagnosticRules(ruleMapArg, opts.duplication);
10436
10713
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
10437
10714
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
@@ -10450,9 +10727,12 @@ function compileLinkable(ruleMapArg, ns, opts) {
10450
10727
  }
10451
10728
  });
10452
10729
  const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
10730
+ const _inlineMax = resolveInlineMax(opts?.maxInline);
10453
10731
  const ctx = {
10454
10732
  vars: 0,
10455
10733
  indent: 1,
10734
+ inlineMax: _inlineMax,
10735
+ inlineLeft: _inlineMax,
10456
10736
  regexDecls: [],
10457
10737
  regexMap: /* @__PURE__ */ new Map(),
10458
10738
  expectedDecls: [],
@@ -10548,6 +10828,9 @@ function compileLinkable(ruleMapArg, ns, opts) {
10548
10828
  }
10549
10829
  }
10550
10830
  const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
10831
+ const savedInlineLeft = ctx.inlineLeft, savedFnName = ctx.currentFnName;
10832
+ ctx.inlineLeft = ctx.inlineMax;
10833
+ ctx.currentFnName = fn;
10551
10834
  ctx.indent = 1;
10552
10835
  ctx.failLabel = "_pfail";
10553
10836
  ctx.recordFail = true;
@@ -10563,6 +10846,8 @@ function compileLinkable(ruleMapArg, ns, opts) {
10563
10846
  ctx.indent = savedIndent;
10564
10847
  ctx.failLabel = savedFail;
10565
10848
  ctx.recordFail = savedRec;
10849
+ ctx.inlineLeft = savedInlineLeft;
10850
+ ctx.currentFnName = savedFnName;
10566
10851
  const ruleId = ctx.coverage?.plan.rules.get(rule);
10567
10852
  const stmts = ruleId === void 0 ? body.stmts : [
10568
10853
  `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
@@ -10647,6 +10932,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10647
10932
  deps: ruleDependencies(ruleMap),
10648
10933
  needsEmptyTl: !!ctx.needsEmptyTl,
10649
10934
  needsHostReads: !!ctx.needsHostReads,
10935
+ needsRawEntry: !!ctx.needsRawEntry,
10650
10936
  hostMode: ctx.hostMode ?? "ast",
10651
10937
  hostBranchElided: !!ctx.hostBranchElided,
10652
10938
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
@@ -10656,26 +10942,6 @@ function compileLinkable(ruleMapArg, ns, opts) {
10656
10942
  buildFns: buildSrcs ? [] : ctx.buildFns
10657
10943
  };
10658
10944
  }
10659
- function hasExternalRuleRef(ruleMap) {
10660
- const seen = /* @__PURE__ */ new Set();
10661
- const visit = (p) => {
10662
- if (seen.has(p)) return false;
10663
- seen.add(p);
10664
- const def = p._def;
10665
- if (def.tag === "lazy") {
10666
- let resolved;
10667
- try {
10668
- resolved = def.thunk();
10669
- } catch {
10670
- resolved = void 0;
10671
- }
10672
- if (resolved === void 0) return p._ruleName !== void 0;
10673
- return visit(resolved);
10674
- }
10675
- return childrenOf3(def).some(visit);
10676
- };
10677
- return ruleMap.some(([, rule]) => visit(rule));
10678
- }
10679
10945
  function hasSemanticReduction(roots, externalRefs) {
10680
10946
  const seen = /* @__PURE__ */ new Set();
10681
10947
  const visit = (parser2) => {
@@ -10721,14 +10987,16 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootR
10721
10987
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
10722
10988
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
10723
10989
  const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
10990
+ const rawEntryDecl = ctx.needsRawEntry ? ` ${RAW_ENTRY_DECL}` : "";
10724
10991
  const lineTrackDecl = ctx.needsLineTrack ? ` ${LINE_TRACK_DECL}` : "";
10725
10992
  const lineSpanDecl = ctx.needsLineSpan ? ` ${LINE_SPAN_DECL}` : "";
10726
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!lineTrackDecl || !!lineSpanDecl;
10993
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl || !!rawEntryDecl || !!lineTrackDecl || !!lineSpanDecl;
10727
10994
  if (!needsWrapper) return innerFn;
10728
10995
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
10729
10996
  const hoistedDecls = [
10730
10997
  emptyTlDecl,
10731
10998
  hostReadsDecl,
10999
+ rawEntryDecl,
10732
11000
  lineTrackDecl,
10733
11001
  lineSpanDecl,
10734
11002
  ...ctx.regexDecls.map((d) => ` ${d}`),
@@ -10798,11 +11066,12 @@ function childrenOf4(def) {
10798
11066
  return [def.main, def.skipped];
10799
11067
  case "scanTo":
10800
11068
  return [def.sentinel, ...def.skip];
11069
+ case "routed":
11070
+ return def.fallback ? [def.fallback] : [];
10801
11071
  case "lazy":
10802
11072
  case "literal":
10803
11073
  case "regex":
10804
11074
  case "keywords":
10805
- case "routed":
10806
11075
  case "guard":
10807
11076
  case "recover":
10808
11077
  case "unknown":
@@ -10884,7 +11153,7 @@ function evalRuleMapIR(ir) {
10884
11153
  l._def.fnSrc = src;
10885
11154
  return l;
10886
11155
  };
10887
- const _nd = (type, child, src, opts, staticError) => {
11156
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
10888
11157
  if (staticError !== void 0 && staticError.length > 0) {
10889
11158
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
10890
11159
  }
@@ -10893,6 +11162,7 @@ function evalRuleMapIR(ir) {
10893
11162
  }), opts);
10894
11163
  n._def.buildSrc = src;
10895
11164
  if (staticError !== void 0) n._def.buildStaticError = staticError;
11165
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
10896
11166
  return n;
10897
11167
  };
10898
11168
  const _gch = (items) => {
@@ -11205,7 +11475,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11205
11475
  case "peek":
11206
11476
  return `peek(${kid(def.parser)})`;
11207
11477
  case "routed":
11208
- return "routed()";
11478
+ return def.fallback === void 0 ? "routed()" : `routed(${kid(def.fallback)})`;
11209
11479
  case "trivia":
11210
11480
  return `trivia(${kid(def.parser)})`;
11211
11481
  case "token":
@@ -11238,6 +11508,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11238
11508
  ...projectEntry === void 0 ? [] : [projectEntry],
11239
11509
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
11240
11510
  ...def.trailingTrivia ? ["trailingTrivia: true"] : [],
11511
+ // Declared or resolved arity. Carried so a re-lowered composed artifact does
11512
+ // not silently re-acquire the fail-open capture cost this module resolved away.
11513
+ ...def.buildArity === void 0 ? [] : [`buildArity: ${def.buildArity}`],
11241
11514
  ...tagEntry === void 0 ? [] : [tagEntry]
11242
11515
  ];
11243
11516
  const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
@@ -11246,8 +11519,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
11246
11519
  return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
11247
11520
  }
11248
11521
  if (def.buildSrc !== void 0) {
11249
- const staticError = def.buildStaticError === void 0 ? "" : `${opts ? "" : ", undefined"}, ${JSON.stringify(def.buildStaticError)}`;
11250
- return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${staticError})`;
11522
+ const tail = [];
11523
+ if (def.buildStaticError !== void 0 || def.buildSigSrc !== void 0) {
11524
+ if (!opts) tail.push("undefined");
11525
+ tail.push(def.buildStaticError === void 0 ? "undefined" : JSON.stringify(def.buildStaticError));
11526
+ }
11527
+ if (def.buildSigSrc !== void 0) tail.push(JSON.stringify(def.buildSigSrc));
11528
+ const trailing = tail.length > 0 ? `, ${tail.join(", ")}` : "";
11529
+ return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts}${trailing})`;
11251
11530
  }
11252
11531
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
11253
11532
  }
@@ -11328,12 +11607,14 @@ function fusedBody(pieces) {
11328
11607
  const contributingPieces = [...contributing];
11329
11608
  const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
11330
11609
  const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
11610
+ const needsRawEntry = contributingPieces.some((p) => p.needsRawEntry);
11331
11611
  const lines = [
11332
11612
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
11333
11613
  "const _pfFail = {}",
11334
11614
  "let _pfEnd",
11335
11615
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
11336
11616
  ...needsHostReads ? [HOST_READS_DECL] : [],
11617
+ ...needsRawEntry ? [RAW_ENTRY_DECL] : [],
11337
11618
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
11338
11619
  ...new Set(contributingPieces.flatMap((p) => p.prelude)),
11339
11620
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -11449,17 +11730,6 @@ var COMPOSED_COVERAGE_RULES = /* @__PURE__ */ Symbol.for("parseman.composedCover
11449
11730
  function isIRPiece(p) {
11450
11731
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
11451
11732
  }
11452
- function once(fn) {
11453
- let done = false;
11454
- let value;
11455
- return () => {
11456
- if (!done) {
11457
- value = fn();
11458
- done = true;
11459
- }
11460
- return value;
11461
- };
11462
- }
11463
11733
  function carriedRuleMapsDetailed(carried) {
11464
11734
  const maps = [];
11465
11735
  const opaque = [];
@@ -11576,12 +11846,6 @@ function compose(items, opts) {
11576
11846
  const used = /* @__PURE__ */ new Set();
11577
11847
  const trivia2 = composingTriviaOf(items);
11578
11848
  const carried = items.flatMap((item) => itemCarried(item, used, trivia2, opts?.hostMode));
11579
- const detailed = once(() => carriedRuleMapsDetailed(carried));
11580
- runFusedGatingDiagnostic(
11581
- () => detailed().maps,
11582
- void 0,
11583
- () => detailed().opaque
11584
- );
11585
11849
  const pieces = carried.map((p) => materializePiece(p, trivia2, false, opts?.hostMode));
11586
11850
  const map = fuseRules(pieces);
11587
11851
  Object.defineProperty(map, COMPOSED_PIECES, { value: carried, enumerable: false });
@@ -11608,9 +11872,175 @@ function analyzeGrammarGating(grammar, opts) {
11608
11872
  kind: "opaque-artifact",
11609
11873
  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.`
11610
11874
  }));
11875
+ for (const o of opaque) {
11876
+ recordDegradation({
11877
+ code: "opaque-artifact",
11878
+ // `info`, not `warn`: the artifact is usually someone else's package, so the author
11879
+ // of THIS grammar has nothing to act on — but the blind spot still has to be counted.
11880
+ severity: "info",
11881
+ where: o.ruleNames.length > 0 ? o.ruleNames.join(", ") : `<artifact ${o.ns}>`,
11882
+ subject: `precompiled artifact "${o.ns}"`,
11883
+ 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`,
11884
+ otherwise: "its choices would be walked like any other rule. Recompile the contributing grammar so it carries IR (the macro emits IR by default)"
11885
+ });
11886
+ }
11611
11887
  return fromOpaque.length === 0 ? report : { ...report, unanalysable: [...report.unanalysable, ...fromOpaque] };
11612
11888
  }
11613
11889
 
11890
+ // src/analysis/diagnose.ts
11891
+ var isCombinator = (g) => typeof g === "object" && g !== null && "_def" in g;
11892
+ var isRuleEntries = (g) => Array.isArray(g);
11893
+ function diagnoseGrammar(grammar, opts) {
11894
+ const depth = degradationCaptureDepth();
11895
+ beginDegradationCapture();
11896
+ let report;
11897
+ let degradations;
11898
+ try {
11899
+ report = runAnalysis(grammar, opts);
11900
+ } catch (e) {
11901
+ const reason = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
11902
+ report = emptyReport(`gating analysis threw \u2014 ${reason}`);
11903
+ } finally {
11904
+ degradations = unwindDegradationCapture(depth);
11905
+ }
11906
+ return assemble(report, degradations);
11907
+ }
11908
+ function formatGrammarDiagnosis(d) {
11909
+ const s = d.summary;
11910
+ const lines = [];
11911
+ lines.push(
11912
+ 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).`
11913
+ );
11914
+ if (s.unanalysable > 0) {
11915
+ lines.push(
11916
+ ` ${s.unanalysable} rule(s) UNANALYSABLE \u2014 THIS REPORT IS PARTIAL. An empty finding list below does NOT mean the grammar is clean.`
11917
+ );
11918
+ }
11919
+ for (const f of d.findings) {
11920
+ lines.push(`${f.severity === "blocking" ? "\u2717" : "\xB7"} [${f.code}] ${f.id}: ${f.message}`);
11921
+ for (const detail of f.details) lines.push(` ${detail.replace(/\n/g, "\n ")}`);
11922
+ if (f.acceptKey !== void 0) {
11923
+ lines.push(` intentional? add to the gating snapshot: { accept: ['${f.acceptKey}'] }`);
11924
+ }
11925
+ }
11926
+ if (!d.ok && d.acceptSnapshot.length > 0) {
11927
+ lines.push(` accept-all snapshot: { accept: [${d.acceptSnapshot.map((i) => `'${i}'`).join(", ")}] }`);
11928
+ }
11929
+ return lines;
11930
+ }
11931
+ function runAnalysis(grammar, opts) {
11932
+ if (isCombinator(grammar)) return analyzeGating(grammar, opts);
11933
+ if (isRuleEntries(grammar)) return analyzeGatingRules(grammar, opts);
11934
+ return analyzeGrammarGating(grammar, opts);
11935
+ }
11936
+ function emptyReport(reason) {
11937
+ return {
11938
+ totalChoices: 0,
11939
+ gated: 0,
11940
+ recoverable: 0,
11941
+ unanalysable: [{ rule: "<whole grammar>", kind: "not-a-combinator", reason }],
11942
+ ungated: [],
11943
+ accepted: [],
11944
+ deferred: [],
11945
+ acceptedUnused: [],
11946
+ choices: [],
11947
+ antiPatterns: []
11948
+ };
11949
+ }
11950
+ var blockingOf = (d) => d.findings.filter((f) => f.severity === "blocking");
11951
+ var SEVERITY_RANK = { blocking: 0, advisory: 1 };
11952
+ var CODE_RANK = {
11953
+ unanalysable: 0,
11954
+ "ungated-choice": 1,
11955
+ "anti-pattern": 2,
11956
+ degraded: 3,
11957
+ "stale-accept": 4
11958
+ };
11959
+ function assemble(report, degradations) {
11960
+ const findings = [];
11961
+ for (const u of report.unanalysable) {
11962
+ findings.push({
11963
+ id: u.rule,
11964
+ code: "unanalysable",
11965
+ severity: "blocking",
11966
+ rule: u.rule,
11967
+ message: `could not be examined [${u.kind}] \u2014 no verdict about it is available`,
11968
+ details: [u.reason]
11969
+ });
11970
+ }
11971
+ for (const c of report.ungated) {
11972
+ findings.push({
11973
+ id: c.id,
11974
+ code: "ungated-choice",
11975
+ severity: "blocking",
11976
+ rule: c.rule,
11977
+ message: `choice is UNGATED [${c.strategy}] \u2014 no first-char dispatch; every position speculatively enters doomed arms`,
11978
+ details: [
11979
+ ...c.anyArms.map((a) => `arm[${a.index}] first-set ANY (${a.cause}): ${a.detail}
11980
+ fix: ${a.suggestion}`),
11981
+ ...c.overlaps.map((o) => `arm[${o.a}] \u2229 arm[${o.b}] overlap on ${firstSetToString(o.on)}
11982
+ fix: ${o.suggestion}`)
11983
+ ],
11984
+ acceptKey: c.id
11985
+ });
11986
+ }
11987
+ for (const ap of report.antiPatterns) {
11988
+ findings.push({
11989
+ id: `${ap.rule}#arm${ap.armIndex}`,
11990
+ code: "anti-pattern",
11991
+ severity: "blocking",
11992
+ rule: ap.rule,
11993
+ message: `[${ap.kind}] ${ap.message}`,
11994
+ details: []
11995
+ });
11996
+ }
11997
+ for (const d of degradations) {
11998
+ findings.push({
11999
+ // `where` + `code` is the degradation channel's own identity; reuse it so the two
12000
+ // surfaces name the same thing the same way.
12001
+ id: `${d.code}@${d.where}`,
12002
+ code: "degraded",
12003
+ // Mirrors `DegradationSeverity`: 'warn' is a real cost the author can remove,
12004
+ // 'info' is a fact they cannot act on (someone else's package).
12005
+ severity: d.severity === "warn" ? "blocking" : "advisory",
12006
+ rule: d.where,
12007
+ message: formatDegradation(d),
12008
+ details: []
12009
+ });
12010
+ }
12011
+ for (const id of report.acceptedUnused) {
12012
+ findings.push({
12013
+ id,
12014
+ code: "stale-accept",
12015
+ severity: "advisory",
12016
+ rule: id,
12017
+ message: "accept entry matched no ungated choice \u2014 the grammar was fixed; prune this line",
12018
+ details: []
12019
+ });
12020
+ }
12021
+ 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));
12022
+ return {
12023
+ schema: "parseman.diagnosis/1",
12024
+ ok: !findings.some((f) => f.severity === "blocking"),
12025
+ summary: {
12026
+ totalChoices: report.totalChoices,
12027
+ gated: report.gated,
12028
+ recoverable: report.recoverable,
12029
+ ungated: report.ungated.length,
12030
+ accepted: report.accepted.length,
12031
+ deferred: report.deferred.length,
12032
+ antiPatterns: report.antiPatterns.length,
12033
+ unanalysable: report.unanalysable.length,
12034
+ degraded: degradations.length,
12035
+ staleAccepts: report.acceptedUnused.length
12036
+ },
12037
+ findings,
12038
+ acceptSnapshot: report.ungated.map((c) => c.id).sort(),
12039
+ gating: report,
12040
+ degradations
12041
+ };
12042
+ }
12043
+
11614
12044
  // src/combinators/gate.ts
11615
12045
  function gate(predicate) {
11616
12046
  const meta = {
@@ -11630,9 +12060,6 @@ function gate(predicate) {
11630
12060
  };
11631
12061
  }
11632
12062
 
11633
- // src/combinators/guard.ts
11634
- var guard = gate;
11635
-
11636
12063
  // src/combinators/completions.ts
11637
12064
  function completionsAt(target, input, offset, options = {}) {
11638
12065
  const probe = { offset, best: null };
@@ -11859,9 +12286,9 @@ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build, to
11859
12286
  const ctx = mkCtx(null, build, tolerant);
11860
12287
  const out = [];
11861
12288
  let pos = reStart;
11862
- let guard2 = 0;
12289
+ let guard = 0;
11863
12290
  while (pos < reEnd) {
11864
- if (++guard2 > reEnd - reStart + 2) return null;
12291
+ if (++guard > reEnd - reStart + 2) return null;
11865
12292
  if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
11866
12293
  if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
11867
12294
  out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
@@ -12154,6 +12581,35 @@ function parseDoc(registry, rootRule, input, opts = {}) {
12154
12581
  }
12155
12582
 
12156
12583
  // src/functional/run.ts
12584
+ var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
12585
+ var REMOVED_RUN_RESULT_FIELDS = [
12586
+ [
12587
+ "triviaMap",
12588
+ `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.`
12589
+ ],
12590
+ [
12591
+ "triviaLog",
12592
+ `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.`
12593
+ ],
12594
+ [
12595
+ "triviaKindLabels",
12596
+ `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\`.`
12597
+ ]
12598
+ ];
12599
+ function guardRemovedFields(result) {
12600
+ for (const [name, detail] of REMOVED_RUN_RESULT_FIELDS) {
12601
+ Object.defineProperty(result, name, {
12602
+ configurable: true,
12603
+ enumerable: false,
12604
+ get() {
12605
+ throw new TypeError(
12606
+ `RunResult.${name} was REMOVED in parseman 0.44.0 and has no default replacement. ${detail}`
12607
+ );
12608
+ }
12609
+ });
12610
+ }
12611
+ return result;
12612
+ }
12157
12613
  var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
12158
12614
  function triviaKindLabelsFromRunnable(r) {
12159
12615
  if (!r) return void 0;
@@ -12256,37 +12712,11 @@ function runOnce(entry, input, options, phase, profileState) {
12256
12712
  unconsumedFrom
12257
12713
  };
12258
12714
  }
12259
- function clonePassState(state) {
12260
- if (state === null || typeof state !== "object") return state;
12261
- return Array.isArray(state) ? [...state] : { ...state };
12262
- }
12263
- function profilePass(entry, input, options, phase) {
12264
- const state = { phase, nodes: 0, childSlots: 0, rawSlots: 0, triviaSlots: 0, fieldSlots: 0, hostCalls: 0 };
12265
- const passOptions = options.state === void 0 ? options : { ...options, state: clonePassState(options.state) };
12266
- const start = performance.now();
12267
- const result = runOnce(entry, input, passOptions, phase, state);
12268
- const { phase: _phase, ...counts } = state;
12269
- return { result, profile: { ms: performance.now() - start, ...counts } };
12270
- }
12271
12715
  function run(entry, input, options = {}) {
12272
- if (!options.profile) return runOnce(entry, input, options);
12273
- if (typeof entry !== "function") {
12274
- throw new TypeError("run({ profile: true }) requires a compiled parser entry");
12275
- }
12276
- const recognizer = profilePass(entry, input, options, "recognizer");
12277
- const capture = profilePass(entry, input, options, "capture");
12278
- const host = profilePass(entry, input, options, "host");
12279
- 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) {
12280
- throw new Error("run({ profile: true }) changed recognition; the grammar is not profile-safe");
12281
- }
12282
- return {
12283
- ...host.result,
12284
- profile: {
12285
- recognizer: recognizer.profile,
12286
- structuralCapture: capture.profile,
12287
- hostConstruction: host.profile
12288
- }
12289
- };
12716
+ if (!options.profile) return guardRemovedFields(runOnce(entry, input, options));
12717
+ throw new TypeError(
12718
+ "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."
12719
+ );
12290
12720
  }
12291
12721
 
12292
12722
  // src/coverage.ts
@@ -12303,7 +12733,7 @@ function dispatchMatcher(entry) {
12303
12733
  var GRAMMAR_COVERAGE_DEFINITIONS = /* @__PURE__ */ Symbol.for("parseman.grammarCoverageDefinitions");
12304
12734
  function compiledGrammarCoverageDefinitions(grammar) {
12305
12735
  const definitions = grammar[GRAMMAR_COVERAGE_DEFINITIONS];
12306
- 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"))) {
12736
+ 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"))) {
12307
12737
  throw new TypeError("grammar has no coverage definitions; enable grammarCoverage for this macro build");
12308
12738
  }
12309
12739
  return definitions;
@@ -12357,7 +12787,13 @@ function createGrammarCoverageCollector(definitions) {
12357
12787
  snapshot() {
12358
12788
  const hitList = [...hits].sort();
12359
12789
  const unhit = ordered.map((definition) => definition.id).filter((id) => !hits.has(id));
12360
- return { definitions: ordered, hits: hitList, unhit, ratio: ordered.length === 0 ? 1 : hitList.length / ordered.length };
12790
+ return {
12791
+ definitions: ordered,
12792
+ hits: hitList,
12793
+ unhit,
12794
+ ratio: hitList.length / ordered.length,
12795
+ measurable: ordered.length > 0
12796
+ };
12361
12797
  },
12362
12798
  reset() {
12363
12799
  hits.clear();
@@ -12677,13 +13113,13 @@ function buildTriviaIndex(root, input = "", opts) {
12677
13113
  }
12678
13114
 
12679
13115
  // src/cst/walk.ts
12680
- function isCombinator(value) {
13116
+ function isCombinator2(value) {
12681
13117
  return !!value && typeof value === "object" && "_def" in value && "_meta" in value && typeof value.parse === "function";
12682
13118
  }
12683
13119
  function reflectionFor(grammar) {
12684
13120
  const stamped = grammarReflectionOf(grammar);
12685
13121
  if (stamped) return stamped;
12686
- const entries = Object.entries(grammar).filter((entry) => isCombinator(entry[1]));
13122
+ const entries = Object.entries(grammar).filter((entry) => isCombinator2(entry[1]));
12687
13123
  return collectGrammarReflection(entries);
12688
13124
  }
12689
13125
  function createVisitor(grammar, spec) {
@@ -12721,201 +13157,10 @@ function createVisitor(grammar, spec) {
12721
13157
  };
12722
13158
  }
12723
13159
 
12724
- // src/cst/offset-model.ts
12725
- var { isFinite } = Number;
12726
- function floorIndex(arr, len, x) {
12727
- let lo = 0;
12728
- let hi = len - 1;
12729
- let ans = -1;
12730
- while (lo <= hi) {
12731
- const mid = lo + hi >>> 1;
12732
- if (arr[mid] <= x) {
12733
- ans = mid;
12734
- lo = mid + 1;
12735
- } else {
12736
- hi = mid - 1;
12737
- }
12738
- }
12739
- return ans;
12740
- }
12741
- var OffsetIndex = class {
12742
- /** Slot starts, sorted ascending. Parallel to `_ends`. */
12743
- _starts;
12744
- _ends;
12745
- count;
12746
- docStart;
12747
- docEnd;
12748
- constructor(slots, docStart = 0, docEnd) {
12749
- const sorted = [...slots].sort((a, b) => a.start - b.start);
12750
- const starts = [];
12751
- const ends = [];
12752
- let prevEnd = docStart;
12753
- for (const s of sorted) {
12754
- if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
12755
- throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
12756
- }
12757
- if (s.start < prevEnd) {
12758
- throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
12759
- }
12760
- starts.push(s.start);
12761
- ends.push(s.end);
12762
- prevEnd = s.end;
12763
- }
12764
- this._starts = starts;
12765
- this._ends = ends;
12766
- this.count = starts.length;
12767
- this.docStart = docStart;
12768
- this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
12769
- }
12770
- /** The slot at index `i` (0-based, source order), or undefined. */
12771
- slot(i) {
12772
- if (i < 0 || i >= this.count) return void 0;
12773
- return { start: this._starts[i], end: this._ends[i] };
12774
- }
12775
- /**
12776
- * Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
12777
- * `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
12778
- * An offset that lands inside a slot returns that slot's TRAILING gap index
12779
- * (i.e. the gap after it) — callers querying at slot boundaries get the
12780
- * intuitive answer via `gapBefore`/`gapAfter`.
12781
- */
12782
- gapIndexAt(offset) {
12783
- const i = floorIndex(this._starts, this.count, offset);
12784
- if (i < 0) return 0;
12785
- if (offset < this._ends[i]) return i + 1;
12786
- return i + 1;
12787
- }
12788
- /** The gap at index `i` in `[0, count]`. */
12789
- gap(i) {
12790
- if (i < 0 || i > this.count) return void 0;
12791
- const start = i === 0 ? this.docStart : this._ends[i - 1];
12792
- const end = i === this.count ? this.docEnd : this._starts[i];
12793
- return { start, end };
12794
- }
12795
- /**
12796
- * The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
12797
- * `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
12798
- * `docStart`). For a real slot start this is the gap preceding that slot; for an
12799
- * adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
12800
- */
12801
- gapBefore(offset) {
12802
- const e = floorIndex(this._ends, this.count, offset);
12803
- const start = e >= 0 ? this._ends[e] : this.docStart;
12804
- return { start, end: offset };
12805
- }
12806
- /**
12807
- * The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
12808
- * `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
12809
- */
12810
- gapAfter(offset) {
12811
- return { start: offset, end: this._ceilStart(offset) };
12812
- }
12813
- /** Least slot start `>= offset`, else `docEnd`. */
12814
- _ceilStart(offset) {
12815
- let lo = 0;
12816
- let hi = this.count - 1;
12817
- let ans = this.docEnd;
12818
- while (lo <= hi) {
12819
- const mid = lo + hi >>> 1;
12820
- if (this._starts[mid] >= offset) {
12821
- ans = this._starts[mid];
12822
- hi = mid - 1;
12823
- } else {
12824
- lo = mid + 1;
12825
- }
12826
- }
12827
- return ans;
12828
- }
12829
- /** Is this gap empty (adjacent slots, no trivia)? */
12830
- isEmpty(gap) {
12831
- return gap.end <= gap.start;
12832
- }
12833
- };
12834
- var NL = 10;
12835
- var CR = 13;
12836
- var SP = 32;
12837
- var TAB = 9;
12838
- function gapText(source, gap) {
12839
- return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
12840
- }
12841
- function lineBreaksIn(source, gap) {
12842
- let n = 0;
12843
- for (let i = gap.start; i < gap.end; i++) {
12844
- const c = source.charCodeAt(i);
12845
- if (c === NL) n++;
12846
- else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
12847
- }
12848
- return n;
12849
- }
12850
- function blankLinesIn(source, gap) {
12851
- return Math.max(0, lineBreaksIn(source, gap) - 1);
12852
- }
12853
- function lineStartWithin(source, gap) {
12854
- for (let i = gap.end - 1; i >= gap.start; i--) {
12855
- const c = source.charCodeAt(i);
12856
- if (c === NL || c === CR) return i + 1;
12857
- }
12858
- return gap.start;
12859
- }
12860
- function indentWidth(source, gap) {
12861
- const ls = lineStartWithin(source, gap);
12862
- if (ls === gap.start) return 0;
12863
- return gap.end - ls;
12864
- }
12865
- function indentMixed(source, gap) {
12866
- const ls = lineStartWithin(source, gap);
12867
- let sawSpace = false;
12868
- let sawTab = false;
12869
- for (let i = ls; i < gap.end; i++) {
12870
- const c = source.charCodeAt(i);
12871
- if (c === SP) sawSpace = true;
12872
- else if (c === TAB) sawTab = true;
12873
- }
12874
- return sawSpace && sawTab;
12875
- }
12876
- function commentsIn(source, gap, comment) {
12877
- const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
12878
- re.lastIndex = gap.start;
12879
- const out = [];
12880
- let m;
12881
- while ((m = re.exec(source)) && m.index < gap.end) {
12882
- if (m[0].length === 0) {
12883
- re.lastIndex++;
12884
- continue;
12885
- }
12886
- if (m.index + m[0].length > gap.end) break;
12887
- out.push({ start: m.index, end: m.index + m[0].length });
12888
- }
12889
- return out;
12890
- }
12891
- function gapIsSignificant(source, gap, comment) {
12892
- if (gap.end <= gap.start) return false;
12893
- if (lineBreaksIn(source, gap) > 0) return true;
12894
- if (comment && commentsIn(source, gap, comment).length > 0) return true;
12895
- return false;
12896
- }
12897
- function collectLeafSlots(root) {
12898
- const slots = [];
12899
- const visit = (n) => {
12900
- const kids = n.children;
12901
- if (kids && kids.length) {
12902
- for (const c of kids) visit(c);
12903
- } else if (n.span) {
12904
- slots.push({ start: n.span.start, end: n.span.end });
12905
- }
12906
- };
12907
- if (root) visit(root);
12908
- return slots;
12909
- }
12910
- function buildOffsetIndex(root, input = "") {
12911
- return new OffsetIndex(collectLeafSlots(root), 0, input.length);
12912
- }
12913
-
12914
13160
  // src/index.ts
12915
13161
  registerRegexAnalyzer(firstSetFromRegex);
12916
13162
  export {
12917
13163
  GRAMMAR_COVERAGE_DEFINITIONS,
12918
- OffsetIndex,
12919
13164
  absoluteSpanAt,
12920
13165
  absoluteSpanCST,
12921
13166
  absolutize,
@@ -12931,16 +13176,12 @@ export {
12931
13176
  applyEdit,
12932
13177
  attempt,
12933
13178
  balanced,
12934
- blankLinesIn,
12935
13179
  buildLineIndex,
12936
- buildOffsetIndex,
12937
13180
  buildRootTriviaIndex,
12938
13181
  buildTriviaIndex,
12939
13182
  charClassMembers,
12940
13183
  choice,
12941
13184
  classifiedTrivia,
12942
- collectLeafSlots,
12943
- commentsIn,
12944
13185
  compile,
12945
13186
  compiledGrammarCoverageDefinitions,
12946
13187
  completionsAt,
@@ -12953,6 +13194,7 @@ export {
12953
13194
  createLineIndex,
12954
13195
  createVisitor,
12955
13196
  cstBuildHost,
13197
+ diagnoseGrammar,
12956
13198
  dispatch,
12957
13199
  duplicationFindingCount,
12958
13200
  endsWith,
@@ -12962,21 +13204,15 @@ export {
12962
13204
  firstSetToString,
12963
13205
  formatDuplicationFindings,
12964
13206
  formatGatingWarnings,
12965
- gapIsSignificant,
12966
- gapText,
13207
+ formatGrammarDiagnosis,
12967
13208
  gate,
12968
13209
  grammarCoverageDefinitions,
12969
- guard,
12970
- indentMixed,
12971
- indentWidth,
12972
13210
  isParseError,
12973
13211
  keywordAlternationHazards,
12974
13212
  keywordRegexShape,
12975
13213
  keywords,
12976
13214
  label,
12977
13215
  leaf,
12978
- lineBreaksIn,
12979
- lineStartWithin,
12980
13216
  literal,
12981
13217
  makeWhen,
12982
13218
  makeWord,