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
@@ -48,8 +48,9 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
48
48
  return d.value.length === 0;
49
49
  case "keywords":
50
50
  return false;
51
+ // The routed token itself is the selector's match, never empty; a fallback can be.
51
52
  case "routed":
52
- return false;
53
+ return d.fallback === void 0 ? false : me(d.fallback);
53
54
  case "regex":
54
55
  try {
55
56
  const m = new RegExp(d.source).exec("");
@@ -851,10 +852,11 @@ function childrenOf(def) {
851
852
  return [def.main, def.skipped];
852
853
  case "scanTo":
853
854
  return [def.sentinel, ...def.skip];
855
+ case "routed":
856
+ return def.fallback ? [def.fallback] : [];
854
857
  case "literal":
855
858
  case "regex":
856
859
  case "keywords":
857
- case "routed":
858
860
  case "guard":
859
861
  case "lazy":
860
862
  case "unknown":
@@ -1163,8 +1165,11 @@ function derive(c, seen) {
1163
1165
  case "not":
1164
1166
  case "peek":
1165
1167
  return deriveExpected2(def.parser);
1168
+ // A bare routed() can only fail as "there is no routed token here". With a
1169
+ // fallback, THAT is the branch that can fail, so its expectations are the useful
1170
+ // ones — the routed path either succeeds or is not taken.
1166
1171
  case "routed":
1167
- return ["routed()"];
1172
+ return def.fallback === void 0 ? ["routed()"] : deriveExpected2(def.fallback);
1168
1173
  case "lazy": {
1169
1174
  const name = c._ruleName;
1170
1175
  if (seen.has(c)) return name ? [name] : [];
@@ -1779,7 +1784,7 @@ function detectStrategy(parsers) {
1779
1784
  function bareLeadingTermKey(term) {
1780
1785
  const d = term._def;
1781
1786
  if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
1782
- if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
1787
+ if (d.tag === "regex") return `R:${JSON.stringify([d.source, d.flags])}`;
1783
1788
  return null;
1784
1789
  }
1785
1790
  function detectSharedPrefix(parsers) {
@@ -2632,6 +2637,37 @@ function scanTo(sentinel, { skip: skip2, raw = false, orEOF = false } = {}) {
2632
2637
  };
2633
2638
  }
2634
2639
 
2640
+ // src/compiler/degradation.ts
2641
+ function formatDegradation(d) {
2642
+ return `[parseman] degraded [${d.code}] ${d.where}: ${d.subject} \u2014 ${d.fellBackTo}; otherwise ${d.otherwise}`;
2643
+ }
2644
+ var _sinks = [];
2645
+ function resolveDegradationLevel(explicit) {
2646
+ if (explicit !== void 0) return explicit;
2647
+ const env = typeof process !== "undefined" ? process.env?.PARSEMAN_DEGRADATION : void 0;
2648
+ if (env === "off" || env === "warn" || env === "error") return env;
2649
+ return "warn";
2650
+ }
2651
+ function degradationKey(d) {
2652
+ return JSON.stringify([d.code, d.where, d.subject]);
2653
+ }
2654
+ function recordDegradation(d) {
2655
+ const level = resolveDegradationLevel();
2656
+ if (level === "off") return;
2657
+ const key = degradationKey(d);
2658
+ const sink = _sinks[_sinks.length - 1];
2659
+ if (sink) {
2660
+ if (!sink.has(key)) sink.set(key, d);
2661
+ return;
2662
+ }
2663
+ if (_immediate.has(key)) return;
2664
+ _immediate.add(key);
2665
+ if (level === "error") throw new Error(`parseman: degraded compilation path
2666
+ ${formatDegradation(d)}`);
2667
+ console.warn(formatDegradation(d));
2668
+ }
2669
+ var _immediate = /* @__PURE__ */ new Set();
2670
+
2635
2671
  // src/cst/trivia-entries.ts
2636
2672
  var EMPTY_INDICES = Object.freeze([]);
2637
2673
  var EMPTY_GAPS = Object.freeze([]);
@@ -2661,45 +2697,183 @@ var SELECTED_ROOT_FINISH = [
2661
2697
  function cstOutputHost(build) {
2662
2698
  return build?._parsemanCstOutput === true;
2663
2699
  }
2664
- var PARAM_LIST_RE = (
2665
- // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
2666
- /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
2667
- );
2668
- var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
2700
+ var SINGLE_IDENT_ARROW_RE = /^[A-Za-z_$][\w$]*\s*=>/;
2701
+ var PARAM_LIST_OPEN_RE = /^(?:function\b[^(]*)?\(/;
2702
+ var NATIVE_CODE_RE = /\{\s*\[native code\]\s*\}/;
2703
+ var PARAM_HEAD_RE = /^[A-Za-z_$][\w$]*\s*\??\s*/;
2669
2704
  function confirmedBuildArity(src) {
2670
2705
  const s = src.trim();
2671
- const m = PARAM_LIST_RE.exec(s);
2672
- if (!m) return null;
2673
- if (m[3] !== void 0) return 1;
2674
- const inner = (m[1] ?? m[2] ?? "").trim();
2675
- if (inner === "") return 0;
2676
- const parts = inner.split(",");
2706
+ if (NATIVE_CODE_RE.test(s)) return null;
2707
+ if (/\barguments\b/.test(s)) return null;
2708
+ if (SINGLE_IDENT_ARROW_RE.test(s)) return 1;
2709
+ const inner = paramListText(s);
2710
+ if (inner === null) return null;
2711
+ if (inner.trim() === "") return 0;
2712
+ const parts = splitTopLevel(inner);
2713
+ if (parts === null) return null;
2677
2714
  for (const part of parts) {
2678
- if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
2715
+ if (!isConfirmableParam(part.trim())) return null;
2679
2716
  }
2680
- if (/\barguments\b/.test(s)) return null;
2681
2717
  return parts.length;
2682
2718
  }
2719
+ function paramListText(s) {
2720
+ const open = PARAM_LIST_OPEN_RE.exec(s);
2721
+ if (!open) return null;
2722
+ const start = open[0].length;
2723
+ const end = matchingParen(s, start);
2724
+ if (end === null) return null;
2725
+ if (!s.startsWith("function") && !/^\s*=>/.test(s.slice(end + 1))) return null;
2726
+ return s.slice(start, end);
2727
+ }
2728
+ function matchingParen(s, start) {
2729
+ let depth = 0;
2730
+ for (let i = start; i < s.length; i++) {
2731
+ const c = s[i];
2732
+ if (c === '"' || c === "'" || c === "`") {
2733
+ const close = skipString(s, i);
2734
+ if (close === null) return null;
2735
+ i = close;
2736
+ continue;
2737
+ }
2738
+ if (c === "(" || c === "[" || c === "{") depth++;
2739
+ else if (c === "]" || c === "}") {
2740
+ if (--depth < 0) return null;
2741
+ } else if (c === ")") {
2742
+ if (depth === 0) return i;
2743
+ depth--;
2744
+ }
2745
+ }
2746
+ return null;
2747
+ }
2748
+ function skipString(s, open) {
2749
+ const quote2 = s[open];
2750
+ for (let i = open + 1; i < s.length; i++) {
2751
+ if (s[i] === "\\") {
2752
+ i++;
2753
+ continue;
2754
+ }
2755
+ if (s[i] === quote2) return i;
2756
+ }
2757
+ return null;
2758
+ }
2759
+ function splitTopLevel(inner) {
2760
+ const parts = [];
2761
+ let depth = 0;
2762
+ let angle = 0;
2763
+ let last = 0;
2764
+ for (let i = 0; i < inner.length; i++) {
2765
+ const c = inner[i];
2766
+ if (c === "=" && inner[i + 1] === ">") {
2767
+ i++;
2768
+ continue;
2769
+ }
2770
+ if (c === '"' || c === "'" || c === "`") {
2771
+ const close = skipString(inner, i);
2772
+ if (close === null) return null;
2773
+ i = close;
2774
+ continue;
2775
+ }
2776
+ if (c === "(" || c === "[" || c === "{") depth++;
2777
+ else if (c === ")" || c === "]" || c === "}") {
2778
+ if (--depth < 0) return null;
2779
+ } else if (c === "<") angle++;
2780
+ else if (c === ">") {
2781
+ if (--angle < 0) return null;
2782
+ } else if (c === "," && depth === 0 && angle === 0) {
2783
+ parts.push(inner.slice(last, i));
2784
+ last = i + 1;
2785
+ }
2786
+ }
2787
+ if (depth !== 0 || angle !== 0) return null;
2788
+ parts.push(inner.slice(last));
2789
+ return parts;
2790
+ }
2791
+ function isConfirmableParam(part) {
2792
+ const head = PARAM_HEAD_RE.exec(part);
2793
+ if (!head) return false;
2794
+ const rest = part.slice(head[0].length);
2795
+ if (rest === "") return true;
2796
+ if (rest[0] !== ":") return false;
2797
+ return !hasDefaultAssign(rest.slice(1));
2798
+ }
2799
+ function hasDefaultAssign(annotation) {
2800
+ for (let i = 0; i < annotation.length; i++) {
2801
+ if (annotation[i] !== "=") continue;
2802
+ if (annotation[i + 1] === ">" || annotation[i + 1] === "=") {
2803
+ i++;
2804
+ continue;
2805
+ }
2806
+ const prev = annotation[i - 1];
2807
+ if (prev === "<" || prev === ">" || prev === "!" || prev === "=") continue;
2808
+ return true;
2809
+ }
2810
+ return false;
2811
+ }
2812
+ function buildAnalysisSrc(def) {
2813
+ return def.buildSigSrc ?? def.buildSrc ?? def.build.toString();
2814
+ }
2815
+ var RESOLVER_REASONS = {
2816
+ "rest-parameter": "its parameter list uses a rest parameter, so the declared arity is unbounded",
2817
+ "arguments": "its body references `arguments`",
2818
+ "reassigned": "the binding it names is reassigned, so the function read here is not decidable",
2819
+ "not-a-function": "the binding it names is not a function declaration",
2820
+ "unresolved-import": "its module could not be resolved or parsed",
2821
+ "not-found": "the name did not resolve to any binding",
2822
+ "computed": "it is a computed expression",
2823
+ "foreign-source": "it is named inside an imported factory, whose offsets the entry-file resolver cannot index"
2824
+ };
2825
+ function unconfirmableReason(def, src) {
2826
+ const declared = def.buildArityUnresolved;
2827
+ if (declared !== void 0 && RESOLVER_REASONS[declared]) return RESOLVER_REASONS[declared];
2828
+ const s = src.trim();
2829
+ const params = s.slice(0, s.indexOf(")") + 1);
2830
+ if (params.includes("...")) return RESOLVER_REASONS["rest-parameter"];
2831
+ if (/\barguments\b/.test(s)) return RESOLVER_REASONS["arguments"];
2832
+ if (/^[A-Za-z_$][\w$]*(\s*\.\s*[A-Za-z_$][\w$]*)?$/.test(s)) {
2833
+ return "it names a binding this build could not resolve to a function declaration";
2834
+ }
2835
+ return "its parameter list could not be parsed";
2836
+ }
2837
+ function confirmedArityForDef(def) {
2838
+ if (def.buildArity !== void 0) return def.buildArity;
2839
+ const src = buildAnalysisSrc(def);
2840
+ const arity = confirmedBuildArity(src);
2841
+ if (arity !== null) return arity;
2842
+ const subject = `build reducer \`${(def.buildSrc ?? "<runtime fn>").trim().split("\n")[0].slice(0, 60)}\``;
2843
+ recordDegradation({
2844
+ code: "build-arity-unconfirmed",
2845
+ severity: "warn",
2846
+ where: `node("${def.type ?? "<inferred>"}")`,
2847
+ subject,
2848
+ 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`,
2849
+ 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"
2850
+ });
2851
+ return null;
2852
+ }
2683
2853
  function buildReadsTrivia(def) {
2684
2854
  if (!def.build) return true;
2685
- const src = def.buildSrc ?? def.build.toString();
2686
- const arity = confirmedBuildArity(src);
2855
+ const arity = confirmedArityForDef(def);
2687
2856
  if (arity === null) return true;
2688
2857
  return arity >= 5;
2689
2858
  }
2690
2859
  function buildReadsState(def) {
2691
2860
  if (!def.build) return true;
2692
- const src = def.buildSrc ?? def.build.toString();
2693
- const arity = confirmedBuildArity(src);
2861
+ const arity = confirmedArityForDef(def);
2694
2862
  if (arity === null) return true;
2695
2863
  return arity >= 6;
2696
2864
  }
2697
2865
 
2866
+ // src/compiler/inline-build.ts
2867
+ var ANN = String.raw`\s*\??\s*(?::[^,)=]+)?`;
2868
+ var ANY_P = String.raw`\w+${ANN}`;
2869
+ var MK_BUILD_RE = new RegExp(
2870
+ 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*$`
2871
+ );
2872
+
2698
2873
  // src/compiler/fields.ts
2699
2874
  function buildReadsFields(def) {
2700
2875
  if (!def.build) return true;
2701
- const src = def.buildSrc ?? def.build.toString();
2702
- const arity = confirmedBuildArity(src);
2876
+ const arity = confirmedArityForDef(def);
2703
2877
  if (arity === null) return true;
2704
2878
  return arity >= 2;
2705
2879
  }
@@ -2950,17 +3124,21 @@ function when(key, parser2, opts = {}) {
2950
3124
  function otherwise(parser2) {
2951
3125
  return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
2952
3126
  }
2953
- function routed() {
3127
+ function routed(fallback) {
2954
3128
  return {
2955
3129
  _tag: "routed",
2956
- _meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
2957
- _def: { tag: "routed" },
3130
+ _meta: {
3131
+ // The routed token is whatever the selector matched, so the first set stays
3132
+ // `any` even with a fallback — `any` already subsumes the fallback's.
3133
+ firstSet: { kind: "any" },
3134
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
3135
+ isTrivia: false
3136
+ },
3137
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2958
3138
  parse(_input, pos, ctx) {
2959
3139
  const item = ctx._routed;
2960
- if (item === void 0) {
2961
- return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2962
- }
2963
- if (pos !== item.span.start) {
3140
+ if (item === void 0 || pos !== item.span.start) {
3141
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2964
3142
  return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2965
3143
  }
2966
3144
  if (cstCaptureActive(ctx)) {
@@ -3163,6 +3341,13 @@ function isCstChild(value) {
3163
3341
  function missingInferredType() {
3164
3342
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
3165
3343
  }
3344
+ function normalizeBuildArity(arity) {
3345
+ if (arity === void 0) return void 0;
3346
+ if (!Number.isInteger(arity) || arity < 0 || arity > 6) {
3347
+ throw new Error("node() buildArity must be an integer 0..6 (children, fields, span, rawChildren, triviaLog, state)");
3348
+ }
3349
+ return arity;
3350
+ }
3166
3351
  function normalizeProject(project) {
3167
3352
  if (project === void 0) return void 0;
3168
3353
  if (!Number.isInteger(project) || project < 0) {
@@ -3198,13 +3383,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3198
3383
  const captureTrivia = opts?.captureTrivia === true;
3199
3384
  const trailingTrivia = opts?.trailingTrivia === true;
3200
3385
  const tags = opts?.tags;
3386
+ const buildArity = normalizeBuildArity(opts?.buildArity);
3201
3387
  if (unwrap && collapse) {
3202
3388
  throw new Error("node() options cannot set both unwrap and collapse");
3203
3389
  }
3204
3390
  if (project !== void 0 && (unwrap || collapse)) {
3205
3391
  throw new Error("node() options cannot combine project with unwrap or collapse");
3206
3392
  }
3207
- 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;
3393
+ 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;
3208
3394
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3209
3395
  const clonesState = build ? buildReadsState(def) : project === void 0;
3210
3396
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3250,7 +3436,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3250
3436
  const cstOutput = cstOutputHost(ctx.build);
3251
3437
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3252
3438
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3253
- 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 };
3439
+ 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 };
3254
3440
  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 };
3255
3441
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3256
3442
  pushCstChild(ctx, built, rawEntry);
@@ -3405,7 +3591,7 @@ function evalRuleMapIR(ir) {
3405
3591
  l._def.fnSrc = src;
3406
3592
  return l;
3407
3593
  };
3408
- const _nd = (type, child, src, opts, staticError) => {
3594
+ const _nd = (type, child, src, opts, staticError, sigSrc) => {
3409
3595
  if (staticError !== void 0 && staticError.length > 0) {
3410
3596
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
3411
3597
  }
@@ -3414,6 +3600,7 @@ function evalRuleMapIR(ir) {
3414
3600
  }), opts);
3415
3601
  n._def.buildSrc = src;
3416
3602
  if (staticError !== void 0) n._def.buildStaticError = staticError;
3603
+ if (sigSrc !== void 0) n._def.buildSigSrc = sigSrc;
3417
3604
  return n;
3418
3605
  };
3419
3606
  const _gch = (items) => {
@@ -3746,7 +3933,7 @@ var Builder = class {
3746
3933
  case "peek":
3747
3934
  return { kind: "peek", item: this.walk(def.parser) };
3748
3935
  case "routed":
3749
- return { kind: "empty" };
3936
+ return def.fallback === void 0 ? { kind: "empty" } : this.walk(def.fallback);
3750
3937
  // Transparent semantic wrappers — render the inner syntax.
3751
3938
  case "transform":
3752
3939
  case "token":