parseman 0.44.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/analysis/choice-cost-gate.d.ts +158 -0
  2. package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
  3. package/dist/analysis/choice-cost-render.d.ts +88 -0
  4. package/dist/analysis/choice-cost-render.d.ts.map +1 -0
  5. package/dist/analysis/choice-cost.d.ts +383 -0
  6. package/dist/analysis/choice-cost.d.ts.map +1 -0
  7. package/dist/analysis/corpus.d.ts +73 -0
  8. package/dist/analysis/corpus.d.ts.map +1 -0
  9. package/dist/analysis/diagnose-render.d.ts +65 -0
  10. package/dist/analysis/diagnose-render.d.ts.map +1 -0
  11. package/dist/analysis/diagnose.d.ts +133 -0
  12. package/dist/analysis/diagnose.d.ts.map +1 -0
  13. package/dist/analysis/diagnostics.cjs +11272 -0
  14. package/dist/analysis/diagnostics.cjs.map +7 -0
  15. package/dist/analysis/diagnostics.d.ts +27 -0
  16. package/dist/analysis/diagnostics.d.ts.map +1 -0
  17. package/dist/analysis/diagnostics.js +11249 -0
  18. package/dist/analysis/diagnostics.js.map +7 -0
  19. package/dist/analysis/fix-render.d.ts +43 -0
  20. package/dist/analysis/fix-render.d.ts.map +1 -0
  21. package/dist/analysis/fix.d.ts +172 -0
  22. package/dist/analysis/fix.d.ts.map +1 -0
  23. package/dist/analysis/gating.d.ts +17 -9
  24. package/dist/analysis/gating.d.ts.map +1 -1
  25. package/dist/analysis/grammar.d.ts.map +1 -1
  26. package/dist/analysis/rebuild.d.ts +61 -0
  27. package/dist/analysis/rebuild.d.ts.map +1 -0
  28. package/dist/analysis/terminal.d.ts +142 -0
  29. package/dist/analysis/terminal.d.ts.map +1 -0
  30. package/dist/cli/index.cjs +12507 -0
  31. package/dist/cli/index.cjs.map +7 -0
  32. package/dist/cli/index.d.ts +3 -0
  33. package/dist/cli/index.d.ts.map +1 -0
  34. package/dist/cli/index.js +12484 -0
  35. package/dist/cli/index.js.map +7 -0
  36. package/dist/combinators/choice.d.ts.map +1 -1
  37. package/dist/combinators/dispatch.d.ts +19 -1
  38. package/dist/combinators/dispatch.d.ts.map +1 -1
  39. package/dist/combinators/expect.d.ts.map +1 -1
  40. package/dist/combinators/first-set.d.ts.map +1 -1
  41. package/dist/combinators/node.d.ts +19 -0
  42. package/dist/combinators/node.d.ts.map +1 -1
  43. package/dist/compiler/build-arity.d.ts +28 -0
  44. package/dist/compiler/build-arity.d.ts.map +1 -1
  45. package/dist/compiler/codegen.d.ts +80 -60
  46. package/dist/compiler/codegen.d.ts.map +1 -1
  47. package/dist/compiler/degradation.d.ts +123 -0
  48. package/dist/compiler/degradation.d.ts.map +1 -0
  49. package/dist/compiler/fields.d.ts.map +1 -1
  50. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
  51. package/dist/compiler/inline-build.d.ts.map +1 -1
  52. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  53. package/dist/compiler/linker.d.ts +45 -7
  54. package/dist/compiler/linker.d.ts.map +1 -1
  55. package/dist/compiler/module-hoist.d.ts +106 -0
  56. package/dist/compiler/module-hoist.d.ts.map +1 -0
  57. package/dist/coverage.d.ts +13 -0
  58. package/dist/coverage.d.ts.map +1 -1
  59. package/dist/cst/reflection.d.ts.map +1 -1
  60. package/dist/functional/run.d.ts.map +1 -1
  61. package/dist/index.cjs +1846 -610
  62. package/dist/index.cjs.map +4 -4
  63. package/dist/index.d.ts +11 -7
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1846 -610
  66. package/dist/index.js.map +4 -4
  67. package/dist/language-service/index.cjs +40 -30
  68. package/dist/language-service/index.cjs.map +3 -3
  69. package/dist/language-service/index.js +40 -30
  70. package/dist/language-service/index.js.map +3 -3
  71. package/dist/oracle/digest.d.ts +132 -6
  72. package/dist/oracle/digest.d.ts.map +1 -1
  73. package/dist/oracle/index.cjs +136 -305
  74. package/dist/oracle/index.cjs.map +4 -4
  75. package/dist/oracle/index.d.ts +43 -31
  76. package/dist/oracle/index.d.ts.map +1 -1
  77. package/dist/oracle/index.js +136 -305
  78. package/dist/oracle/index.js.map +4 -4
  79. package/dist/plugin/evaluator.d.ts +3 -0
  80. package/dist/plugin/evaluator.d.ts.map +1 -1
  81. package/dist/plugin/free-identifiers.d.ts +23 -0
  82. package/dist/plugin/free-identifiers.d.ts.map +1 -0
  83. package/dist/plugin/index.cjs +1529 -763
  84. package/dist/plugin/index.cjs.map +4 -4
  85. package/dist/plugin/index.d.ts +18 -0
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +1529 -763
  88. package/dist/plugin/index.js.map +4 -4
  89. package/dist/plugin/reducer-resolver.d.ts +57 -0
  90. package/dist/plugin/reducer-resolver.d.ts.map +1 -0
  91. package/dist/run/index.cjs +33 -30
  92. package/dist/run/index.cjs.map +2 -2
  93. package/dist/run/index.js +33 -30
  94. package/dist/run/index.js.map +2 -2
  95. package/dist/spec/index.cjs +222 -34
  96. package/dist/spec/index.cjs.map +4 -4
  97. package/dist/spec/index.js +222 -34
  98. package/dist/spec/index.js.map +4 -4
  99. package/dist/spec/model.d.ts.map +1 -1
  100. package/dist/types.d.ts +6 -1
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/package.json +23 -4
  104. package/dist/combinators/guard.d.ts +0 -8
  105. package/dist/combinators/guard.d.ts.map +0 -1
  106. package/dist/cst/offset-model.d.ts +0 -119
  107. package/dist/cst/offset-model.d.ts.map +0 -1
  108. package/dist/oracle/corpus.d.ts +0 -32
  109. package/dist/oracle/corpus.d.ts.map +0 -1
  110. package/dist/oracle/identity.d.ts +0 -117
  111. package/dist/oracle/identity.d.ts.map +0 -1
@@ -82,8 +82,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
82
82
  return d.value.length === 0;
83
83
  case "keywords":
84
84
  return false;
85
+ // The routed token itself is the selector's match, never empty; a fallback can be.
85
86
  case "routed":
86
- return false;
87
+ return d.fallback === void 0 ? false : me(d.fallback);
87
88
  case "regex":
88
89
  try {
89
90
  const m = new RegExp(d.source).exec("");
@@ -885,10 +886,11 @@ function childrenOf(def) {
885
886
  return [def.main, def.skipped];
886
887
  case "scanTo":
887
888
  return [def.sentinel, ...def.skip];
889
+ case "routed":
890
+ return def.fallback ? [def.fallback] : [];
888
891
  case "literal":
889
892
  case "regex":
890
893
  case "keywords":
891
- case "routed":
892
894
  case "guard":
893
895
  case "lazy":
894
896
  case "unknown":
@@ -1197,8 +1199,11 @@ function derive(c, seen) {
1197
1199
  case "not":
1198
1200
  case "peek":
1199
1201
  return deriveExpected2(def.parser);
1202
+ // A bare routed() can only fail as "there is no routed token here". With a
1203
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1204
+ // ones — the routed path either succeeds or is not taken.
1200
1205
  case "routed":
1201
- return ["routed()"];
1206
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1202
1207
  case "lazy": {
1203
1208
  const name = c._ruleName;
1204
1209
  if (seen.has(c)) return name ? [name] : [];
@@ -1813,7 +1818,7 @@ function detectStrategy(parsers) {
1813
1818
  function bareLeadingTermKey(term) {
1814
1819
  const d = term._def;
1815
1820
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
1816
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
1821
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
1817
1822
  return null;
1818
1823
  }
1819
1824
  function detectSharedPrefix(parsers) {
@@ -2666,6 +2671,37 @@ function scanTo(sentinel, { skip: skip2, raw = false, orEOF = false } = {}) {
2666
2671
  };
2667
2672
  }
2668
2673
 
2674
+ // src/compiler/degradation.ts
2675
+ function formatDegradation(d) {
2676
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
2677
+ }
2678
+ var _sinks = [];
2679
+ function resolveDegradationLevel(explicit) {
2680
+ if (explicit !== void 0) return explicit;
2681
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
2682
+ if (env === "off" || env === "warn" || env === "error") return env;
2683
+ return "warn";
2684
+ }
2685
+ function degradationKey(d) {
2686
+ return JSON.stringify([d.code, d.where, d.subject]);
2687
+ }
2688
+ function recordDegradation(d) {
2689
+ const level = resolveDegradationLevel();
2690
+ if (level === "off") return;
2691
+ const key = degradationKey(d);
2692
+ const sink = _sinks[_sinks.length - 1];
2693
+ if (sink) {
2694
+ if (!sink.has(key)) sink.set(key, d);
2695
+ return;
2696
+ }
2697
+ if (_immediate.has(key)) return;
2698
+ _immediate.add(key);
2699
+ if (level === "error") throw new Error(`parseman: degraded compilation path
2700
+ ${formatDegradation(d)}`);
2701
+ console.warn(formatDegradation(d));
2702
+ }
2703
+ var _immediate = /* @__PURE__ */ new Set();
2704
+
2669
2705
  // src/cst/trivia-entries.ts
2670
2706
  var EMPTY_INDICES = Object.freeze([]);
2671
2707
  var EMPTY_GAPS = Object.freeze([]);
@@ -2695,45 +2731,184 @@ var SELECTED_ROOT_FINISH = [
2695
2731
  function cstOutputHost(build) {
2696
2732
  return build?._parsemanCstOutput === true;
2697
2733
  }
2698
- var PARAM_LIST_RE = (
2699
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
2700
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
2701
- );
2702
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
2734
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
2735
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
2736
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
2737
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
2703
2738
  function confirmedBuildArity(src) {
2704
2739
  const s = src.trim();
2705
- const m = PARAM_LIST_RE.exec(s);
2706
- if (!m) return null;
2707
- if (m[3] !== void 0) return 1;
2708
- const inner = (m[1] ?? m[2] ?? "").trim();
2709
- if (inner === "") return 0;
2710
- const parts = inner.split(",");
2740
+ if (NATIVE_CODE_RE.test(s)) return null;
2741
+ if (/\barguments\b/.test(s)) return null;
2742
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
2743
+ const inner = paramListText(s);
2744
+ if (inner === null) return null;
2745
+ if (inner.trim() === "") return 0;
2746
+ const parts = splitTopLevel(inner);
2747
+ if (parts === null) return null;
2711
2748
  for (const part of parts) {
2712
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
2749
+ if (!isConfirmableParam(part.trim())) return null;
2713
2750
  }
2714
- if (/\barguments\b/.test(s)) return null;
2715
2751
  return parts.length;
2716
2752
  }
2753
+ function paramListText(s) {
2754
+ const open = PARAM_LIST_OPEN_RE.exec(s);
2755
+ if (!open) return null;
2756
+ const start = open[0].length;
2757
+ const end = matchingParen(s, start);
2758
+ if (end === null) return null;
2759
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
2760
+ return s.slice(start, end);
2761
+ }
2762
+ function matchingParen(s, start) {
2763
+ let depth = 0;
2764
+ for (let i = start; i < s.length; i++) {
2765
+ const c = s[i];
2766
+ if (c === '"' || c === "'" || c === "`") {
2767
+ const close = skipString(s, i);
2768
+ if (close === null) return null;
2769
+ i = close;
2770
+ continue;
2771
+ }
2772
+ if (c === "(" || c === "[" || c === "{") depth++;
2773
+ else if (c === "]" || c === "}") {
2774
+ if (--depth < 0) return null;
2775
+ } else if (c === ")") {
2776
+ if (depth === 0) return i;
2777
+ depth--;
2778
+ }
2779
+ }
2780
+ return null;
2781
+ }
2782
+ function skipString(s, open) {
2783
+ const quote2 = s[open];
2784
+ for (let i = open + 1; i < s.length; i++) {
2785
+ if (s[i] === "\\") {
2786
+ i++;
2787
+ continue;
2788
+ }
2789
+ if (s[i] === quote2) return i;
2790
+ }
2791
+ return null;
2792
+ }
2793
+ function splitTopLevel(inner) {
2794
+ const parts = [];
2795
+ let depth = 0;
2796
+ let angle = 0;
2797
+ let last = 0;
2798
+ for (let i = 0; i < inner.length; i++) {
2799
+ const c = inner[i];
2800
+ if (c === "=" && inner[i + 1] === ">") {
2801
+ i++;
2802
+ continue;
2803
+ }
2804
+ if (c === '"' || c === "'" || c === "`") {
2805
+ const close = skipString(inner, i);
2806
+ if (close === null) return null;
2807
+ i = close;
2808
+ continue;
2809
+ }
2810
+ if (c === "(" || c === "[" || c === "{") depth++;
2811
+ else if (c === ")" || c === "]" || c === "}") {
2812
+ if (--depth < 0) return null;
2813
+ } else if (c === "<") angle++;
2814
+ else if (c === ">") {
2815
+ if (--angle < 0) return null;
2816
+ } else if (c === "," && depth === 0 && angle === 0) {
2817
+ parts.push(inner.slice(last, i));
2818
+ last = i + 1;
2819
+ }
2820
+ }
2821
+ if (depth !== 0 || angle !== 0) return null;
2822
+ parts.push(inner.slice(last));
2823
+ return parts;
2824
+ }
2825
+ function isConfirmableParam(part) {
2826
+ const head = PARAM_HEAD_RE.exec(part);
2827
+ if (!head) return false;
2828
+ const rest = part.slice(head[0].length);
2829
+ if (rest === "") return true;
2830
+ if (rest[0] !== ":") return false;
2831
+ return !hasDefaultAssign(rest.slice(1));
2832
+ }
2833
+ function hasDefaultAssign(annotation) {
2834
+ for (let i = 0; i < annotation.length; i++) {
2835
+ if (annotation[i] !== "=") continue;
2836
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
2837
+ i++;
2838
+ continue;
2839
+ }
2840
+ const prev = annotation[i - 1];
2841
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
2842
+ return true;
2843
+ }
2844
+ return false;
2845
+ }
2846
+ function buildAnalysisSrc(def) {
2847
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
2848
+ }
2849
+ var RESOLVER_REASONS = {
2850
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
2851
+ "arguments": "its body references `arguments`",
2852
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
2853
+ "not-a-function": "the binding it names is not a function declaration",
2854
+ "unresolved-import": "its module could not be resolved or parsed",
2855
+ "not-found": "the name did not resolve to any binding",
2856
+ "computed": "it is a computed expression",
2857
+ "foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
2858
+ "ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
2859
+ };
2860
+ function unconfirmableReason(def, src) {
2861
+ const declared = def.buildArityUnresolved;
2862
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
2863
+ const s = src.trim();
2864
+ const params = s.slice(0, s.indexOf(")") + 1);
2865
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
2866
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
2867
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
2868
+ return "it names a binding this build could not resolve to a function declaration";
2869
+ }
2870
+ return "its parameter list could not be parsed";
2871
+ }
2872
+ function confirmedArityForDef(def) {
2873
+ if (def.buildArity !== void 0) return def.buildArity;
2874
+ const src = buildAnalysisSrc(def);
2875
+ const arity = confirmedBuildArity(src);
2876
+ if (arity !== null) return arity;
2877
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
2878
+ recordDegradation({
2879
+ code: "build-arity-unconfirmed",
2880
+ severity: "warn",
2881
+ where: `node("${def.type ?? "<inferred>"}")`,
2882
+ subject,
2883
+ 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`,
2884
+ 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"
2885
+ });
2886
+ return null;
2887
+ }
2717
2888
  function buildReadsTrivia(def) {
2718
2889
  if (!def.build) return true;
2719
- const src = def.buildSrc ?? def.build.toString();
2720
- const arity = confirmedBuildArity(src);
2890
+ const arity = confirmedArityForDef(def);
2721
2891
  if (arity === null) return true;
2722
2892
  return arity >= 5;
2723
2893
  }
2724
2894
  function buildReadsState(def) {
2725
2895
  if (!def.build) return true;
2726
- const src = def.buildSrc ?? def.build.toString();
2727
- const arity = confirmedBuildArity(src);
2896
+ const arity = confirmedArityForDef(def);
2728
2897
  if (arity === null) return true;
2729
2898
  return arity >= 6;
2730
2899
  }
2731
2900
 
2901
+ // src/compiler/inline-build.ts
2902
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
2903
+ var ANY_P = String.raw`\w+${ANN}`;
2904
+ var MK_BUILD_RE = new RegExp(
2905
+ 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*$`
2906
+ );
2907
+
2732
2908
  // src/compiler/fields.ts
2733
2909
  function buildReadsFields(def) {
2734
2910
  if (!def.build) return true;
2735
- const src = def.buildSrc ?? def.build.toString();
2736
- const arity = confirmedBuildArity(src);
2911
+ const arity = confirmedArityForDef(def);
2737
2912
  if (arity === null) return true;
2738
2913
  return arity >= 2;
2739
2914
  }
@@ -2984,17 +3159,21 @@ function when(key, parser2, opts = {}) {
2984
3159
  function otherwise(parser2) {
2985
3160
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2986
3161
  }
2987
- function routed() {
3162
+ function routed(fallback) {
2988
3163
  return {
2989
3164
  _tag: "routed",
2990
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2991
- _def: { tag: "routed" },
3165
+ _meta: {
3166
+ // The routed token is whatever the selector matched, so the first set stays
3167
+ // `any` even with a fallback — `any` already subsumes the fallback's.
3168
+ firstSet: { kind: "any" },
3169
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
3170
+ isTrivia: false
3171
+ },
3172
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2992
3173
  parse(_input, pos, ctx) {
2993
3174
  const item = ctx._routed;
2994
- if (item === void 0) {
2995
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2996
- }
2997
- if (pos !== item.span.start) {
3175
+ if (item === void 0 || pos !== item.span.start) {
3176
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2998
3177
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2999
3178
  }
3000
3179
  if (cstCaptureActive(ctx)) {
@@ -3197,6 +3376,13 @@ function isCstChild(value) {
3197
3376
  function missingInferredType() {
3198
3377
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3199
3378
  }
3379
+ function normalizeBuildArity(arity) {
3380
+ if (arity === void 0) return void 0;
3381
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3382
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3383
+ }
3384
+ return arity;
3385
+ }
3200
3386
  function normalizeProject(project) {
3201
3387
  if (project === void 0) return void 0;
3202
3388
  if (!Number.isInteger(project) || project < 0) {
@@ -3232,13 +3418,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3232
3418
  const captureTrivia = opts?.captureTrivia === true;
3233
3419
  const trailingTrivia = opts?.trailingTrivia === true;
3234
3420
  const tags = opts?.tags;
3421
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3235
3422
  if (unwrap && collapse) {
3236
3423
  throw new Error("node() options cannot set both unwrap and collapse");
3237
3424
  }
3238
3425
  if (project !== void 0 && (unwrap || collapse)) {
3239
3426
  throw new Error("node() options cannot combine project with unwrap or collapse");
3240
3427
  }
3241
- 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;
3428
+ 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;
3242
3429
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3243
3430
  const clonesState = build ? buildReadsState(def) : project === void 0;
3244
3431
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3284,7 +3471,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3284
3471
  const cstOutput = cstOutputHost(ctx.build);
3285
3472
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3286
3473
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3287
- const built = unwrap && children.length === 1 ? unwrapChild(children[0]) : collapse && children.length === 1 ? children[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children[0], children, rawChildren) ? children[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags) : build(children, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children, 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 };
3474
+ const built = unwrap && children.length === 1 ? unwrapChild(children[0]) : collapse && children.length === 1 ? children[0] : (cstOutput || !build && project === void 0) && ctx.build?._parsemanCstCollapse && children.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children[0], children, rawChildren) ? children[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children, fields, span, rawChildren, triviaLog, hostState, tags) : build(children, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children, 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 };
3288
3475
  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 };
3289
3476
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3290
3477
  pushCstChild(ctx, built, rawEntry);
@@ -3439,7 +3626,7 @@ function evalRuleMapIR(ir) {
3439
3626
  l._def.fnSrc = src;
3440
3627
  return l;
3441
3628
  };
3442
- const _nd = (type, child, src, opts, staticError) => {
3629
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
3443
3630
  if (staticError !== void 0 && staticError.length > 0) {
3444
3631
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
3445
3632
  }
@@ -3448,6 +3635,7 @@ function evalRuleMapIR(ir) {
3448
3635
  }), opts);
3449
3636
  n._def.buildSrc = src;
3450
3637
  if (staticError !== void 0) n._def.buildStaticError = staticError;
3638
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
3451
3639
  return n;
3452
3640
  };
3453
3641
  const _gch = (items) => {
@@ -3780,7 +3968,7 @@ var Builder = class {
3780
3968
  case "peek":
3781
3969
  return { kind: "peek", item: this.walk(def.parser) };
3782
3970
  case "routed":
3783
- return { kind: "empty" };
3971
+ return def.fallback === void 0 ? { kind: "empty" } : this.walk(def.fallback);
3784
3972
  // Transparent semantic wrappers — render the inner syntax.
3785
3973
  case "transform":
3786
3974
  case "token":