parseman 0.13.0 → 0.14.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 (39) hide show
  1. package/dist/combinators/expect.d.ts.map +1 -1
  2. package/dist/combinators/node.d.ts +1 -1
  3. package/dist/combinators/node.d.ts.map +1 -1
  4. package/dist/combinators/parser.d.ts.map +1 -1
  5. package/dist/combinators/regex-analyze.d.ts +6 -0
  6. package/dist/combinators/regex-analyze.d.ts.map +1 -0
  7. package/dist/combinators/regex.d.ts +16 -1
  8. package/dist/combinators/regex.d.ts.map +1 -1
  9. package/dist/compiler/build-arity.d.ts.map +1 -1
  10. package/dist/compiler/codegen.d.ts +45 -0
  11. package/dist/compiler/codegen.d.ts.map +1 -1
  12. package/dist/compiler/inline-build.d.ts.map +1 -1
  13. package/dist/compiler/linker.d.ts +44 -0
  14. package/dist/compiler/linker.d.ts.map +1 -0
  15. package/dist/compiler/scannable-run.d.ts.map +1 -1
  16. package/dist/cst/offset-model.d.ts +119 -0
  17. package/dist/cst/offset-model.d.ts.map +1 -0
  18. package/dist/cst/relative-spans.d.ts +56 -0
  19. package/dist/cst/relative-spans.d.ts.map +1 -0
  20. package/dist/functional/doc.d.ts +20 -0
  21. package/dist/functional/doc.d.ts.map +1 -1
  22. package/dist/functional/run.d.ts +63 -0
  23. package/dist/functional/run.d.ts.map +1 -0
  24. package/dist/index.cjs +902 -112
  25. package/dist/index.cjs.map +4 -4
  26. package/dist/index.d.ts +9 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +902 -112
  29. package/dist/index.js.map +4 -4
  30. package/dist/plugin/evaluator.d.ts +2 -28
  31. package/dist/plugin/evaluator.d.ts.map +1 -1
  32. package/dist/plugin/index.cjs +710 -250
  33. package/dist/plugin/index.cjs.map +4 -4
  34. package/dist/plugin/index.d.ts.map +1 -1
  35. package/dist/plugin/index.js +710 -250
  36. package/dist/plugin/index.js.map +4 -4
  37. package/dist/types.d.ts +12 -3
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +3 -1
@@ -288,6 +288,36 @@ function literal(value, opts = {}) {
288
288
  }
289
289
 
290
290
  // src/combinators/regex.ts
291
+ var firstSetAnalyzer = null;
292
+ function registerRegexAnalyzer(analyzer) {
293
+ firstSetAnalyzer = analyzer;
294
+ }
295
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
296
+ function regex(pattern, flags = "") {
297
+ const source = typeof pattern === "string" ? pattern : pattern.source;
298
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
299
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
300
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
301
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
302
+ return {
303
+ _tag: "regex",
304
+ _meta: meta,
305
+ _def: { tag: "regex", source, flags: resolvedFlags },
306
+ parse(input, pos, ctx) {
307
+ anchored.lastIndex = pos;
308
+ const m = anchored.exec(input);
309
+ if (m === null) {
310
+ return failAt(ctx, [`/${source}/`], pos);
311
+ }
312
+ const span = { start: pos, end: pos + m[0].length };
313
+ const leaf = { _tag: "leaf", value: m[0], span };
314
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
315
+ return { ok: true, value: m[0], span };
316
+ }
317
+ };
318
+ }
319
+
320
+ // src/combinators/regex-analyze.ts
291
321
  import regexpTree from "regexp-tree";
292
322
  function firstSetFromRegex(pattern) {
293
323
  try {
@@ -392,40 +422,6 @@ function extractFirstSet(node2) {
392
422
  return { firstSet: any(), canMatchNewline: true };
393
423
  }
394
424
  }
395
- function optimizeRegex(source, flags) {
396
- try {
397
- const result = regexpTree.optimize(`/${source}/${flags}`);
398
- const str = result.toString();
399
- const lastSlash = str.lastIndexOf("/");
400
- return str.slice(1, lastSlash);
401
- } catch {
402
- return source;
403
- }
404
- }
405
- function regex(pattern, flags = "") {
406
- const source = typeof pattern === "string" ? pattern : pattern.source;
407
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
408
- const optimizedSource = optimizeRegex(source, resolvedFlags);
409
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
410
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
411
- const meta = { firstSet, canMatchNewline, isTrivia: false };
412
- return {
413
- _tag: "regex",
414
- _meta: meta,
415
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
416
- parse(input, pos, ctx) {
417
- anchored.lastIndex = pos;
418
- const m = anchored.exec(input);
419
- if (m === null) {
420
- return failAt(ctx, [`/${source}/`], pos);
421
- }
422
- const span = { start: pos, end: pos + m[0].length };
423
- const leaf = { _tag: "leaf", value: m[0], span };
424
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
425
- return { ok: true, value: m[0], span };
426
- }
427
- };
428
- }
429
425
 
430
426
  // src/combinators/keywords.ts
431
427
  function word(str, boundary = "_0-9A-Za-z") {
@@ -1125,12 +1121,14 @@ function confirmedBuildArity(src) {
1125
1121
  return parts.length;
1126
1122
  }
1127
1123
  function buildReadsTrivia(def) {
1124
+ if (!def.build) return true;
1128
1125
  const src = def.buildSrc ?? def.build.toString();
1129
1126
  const arity = confirmedBuildArity(src);
1130
1127
  if (arity === null) return true;
1131
1128
  return arity >= 4;
1132
1129
  }
1133
1130
  function buildReadsState(def) {
1131
+ if (!def.build) return true;
1134
1132
  const src = def.buildSrc ?? def.build.toString();
1135
1133
  const arity = confirmedBuildArity(src);
1136
1134
  if (arity === null) return true;
@@ -1149,8 +1147,8 @@ function node(type, combinator, build, opts) {
1149
1147
  };
1150
1148
  const collapse = opts?.collapse === true;
1151
1149
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1152
- const capturesTrivia = buildReadsTrivia(def);
1153
- const clonesState = buildReadsState(def);
1150
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1151
+ const clonesState = build ? buildReadsState(def) : true;
1154
1152
  return {
1155
1153
  _tag: "node",
1156
1154
  _meta: meta,
@@ -1161,13 +1159,8 @@ function node(type, combinator, build, opts) {
1161
1159
  const r = combinator.parse(input, pos, ctx);
1162
1160
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1163
1161
  if (!r.ok) return r;
1164
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1165
- children,
1166
- rawChildren,
1167
- r.span,
1168
- triviaLog,
1169
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1170
- );
1162
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1163
+ const built = collapse && children.length === 1 ? collapseChild(children[0]) : build ? build(children, rawChildren, r.span, triviaLog, st) : ctx.build ? ctx.build(type, children, rawChildren, r.span, triviaLog, st) : { _tag: "node", type, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children };
1171
1164
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1172
1165
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1173
1166
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1310,8 +1303,14 @@ function staticExpected(c) {
1310
1303
  case "transform":
1311
1304
  case "not":
1312
1305
  return staticExpected(def.parser);
1313
- case "lazy":
1314
- return staticExpected(def.thunk());
1306
+ case "lazy": {
1307
+ const name = c._ruleName;
1308
+ try {
1309
+ return staticExpected(def.thunk());
1310
+ } catch {
1311
+ return name ? [name] : [];
1312
+ }
1313
+ }
1315
1314
  default:
1316
1315
  return [];
1317
1316
  }
@@ -1611,6 +1610,15 @@ function seqIsUnambiguous(parts) {
1611
1610
  const p = parts[k];
1612
1611
  const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1613
1612
  if (mandatory) hasMandatory = true;
1613
+ if (p.part === "group") {
1614
+ const exposure = groupPartExposure(p);
1615
+ if (exposure === "unsupported") {
1616
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1617
+ if (!trailingOnce) return false;
1618
+ } else if (exposure !== null) {
1619
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1620
+ }
1621
+ }
1614
1622
  const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1615
1623
  const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1616
1624
  if (skippableSingle || isUnboundedRepeat) {
@@ -1657,6 +1665,58 @@ function firstSetsDisjoint(a, b) {
1657
1665
  if (a === "any" || b === "any") return false;
1658
1666
  return classDisjoint(a, b);
1659
1667
  }
1668
+ function fixedClassSeq(shape) {
1669
+ if (shape.kind !== "seq") return null;
1670
+ const out = [];
1671
+ for (const p of shape.parts) {
1672
+ if (p.part === "lit") {
1673
+ if (p.optional) return null;
1674
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1675
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1676
+ out.push({ ranges: p.ranges, negated: p.negated });
1677
+ } else {
1678
+ return null;
1679
+ }
1680
+ }
1681
+ return out.length ? out : null;
1682
+ }
1683
+ function altFixedMutuallyExclusive(shape) {
1684
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1685
+ const lists = [];
1686
+ for (const arm of shape.arms) {
1687
+ const f = fixedClassSeq(arm);
1688
+ if (!f) return false;
1689
+ lists.push(f);
1690
+ }
1691
+ for (let i = 0; i < lists.length; i++) {
1692
+ for (let j = 0; j < lists.length; j++) {
1693
+ if (i === j) continue;
1694
+ const a = lists[i];
1695
+ const b = lists[j];
1696
+ if (a.length > b.length) continue;
1697
+ let disjointAt = false;
1698
+ for (let p = 0; p < a.length; p++) {
1699
+ if (classDisjoint(a[p], b[p])) {
1700
+ disjointAt = true;
1701
+ break;
1702
+ }
1703
+ }
1704
+ if (!disjointAt) return false;
1705
+ }
1706
+ }
1707
+ return true;
1708
+ }
1709
+ function groupPartExposure(part) {
1710
+ const innerWiggle = trailingBacktrackClass(part.inner);
1711
+ if (innerWiggle === "unsupported") return "unsupported";
1712
+ const requiredOnce = !part.unbounded && part.min === 1;
1713
+ if (requiredOnce) return innerWiggle;
1714
+ const dropExposed = shapeFirstAccept(part.inner);
1715
+ if (dropExposed === "any") return "unsupported";
1716
+ if (innerWiggle === null) return dropExposed;
1717
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1718
+ return unioned === "any" ? "unsupported" : unioned;
1719
+ }
1660
1720
  function trailingBacktrackClass(shape) {
1661
1721
  if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1662
1722
  if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
@@ -1668,18 +1728,10 @@ function trailingBacktrackClass(shape) {
1668
1728
  if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1669
1729
  return null;
1670
1730
  }
1671
- const innerWiggle = trailingBacktrackClass(last.inner);
1672
- if (innerWiggle === "unsupported") return "unsupported";
1673
- const requiredOnce = !last.unbounded && last.min === 1;
1674
- if (requiredOnce) return innerWiggle;
1675
- const dropExposed = shapeFirstAccept(last.inner);
1676
- if (dropExposed === "any") return "unsupported";
1677
- if (innerWiggle === null) return dropExposed;
1678
- const unioned = unionCharSets([innerWiggle, dropExposed]);
1679
- return unioned === "any" ? "unsupported" : unioned;
1731
+ return groupPartExposure(last);
1680
1732
  }
1681
1733
  if (shape.kind === "alt") {
1682
- if (!shape.disjoint) return "unsupported";
1734
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1683
1735
  const classes = shape.arms.map(trailingBacktrackClass);
1684
1736
  if (classes.some((c) => c === "unsupported")) return "unsupported";
1685
1737
  const concrete = classes.filter((c) => c !== null);
@@ -1819,7 +1871,7 @@ function seqFirstAccept(parts) {
1819
1871
  function groupInnerSafe(shape) {
1820
1872
  if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1821
1873
  if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1822
- if (shape.kind === "alt") return shape.disjoint;
1874
+ if (shape.kind === "alt") return true;
1823
1875
  return false;
1824
1876
  }
1825
1877
  function shapeFirstAccept(shape) {
@@ -1847,9 +1899,8 @@ function shapeFirstAccept(shape) {
1847
1899
  return shapeFirstAccept(shape.inner);
1848
1900
  // zero-width — doesn't change first-char acceptance
1849
1901
  case "alt": {
1850
- if (!shape.disjoint) return "any";
1851
- const sets = shape.firsts.filter((f) => f !== null);
1852
- return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1902
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
1903
+ return unionCharSets(sets);
1853
1904
  }
1854
1905
  }
1855
1906
  }
@@ -1890,6 +1941,120 @@ function parseScanShape(source) {
1890
1941
  }
1891
1942
  return parseScanShapeCore(source);
1892
1943
  }
1944
+ function splitDelimArms(body) {
1945
+ const arms = [];
1946
+ let depth = 0;
1947
+ let last = 0;
1948
+ let i = 0;
1949
+ while (i < body.length) {
1950
+ const ch = body[i];
1951
+ if (ch === "\\") {
1952
+ i += 2;
1953
+ continue;
1954
+ }
1955
+ if (ch === "[") {
1956
+ let j = i + 1;
1957
+ if (body[j] === "^") j++;
1958
+ while (j < body.length && body[j] !== "]") {
1959
+ if (body[j] === "\\") j += 2;
1960
+ else j++;
1961
+ }
1962
+ if (body[j] !== "]") return null;
1963
+ i = j + 1;
1964
+ continue;
1965
+ }
1966
+ if (ch === "(") {
1967
+ depth++;
1968
+ i++;
1969
+ continue;
1970
+ }
1971
+ if (ch === ")") {
1972
+ depth--;
1973
+ if (depth < 0) return null;
1974
+ i++;
1975
+ continue;
1976
+ }
1977
+ if (ch === "|" && depth === 0) {
1978
+ arms.push(body.slice(last, i));
1979
+ last = i + 1;
1980
+ }
1981
+ i++;
1982
+ }
1983
+ if (depth !== 0) return null;
1984
+ arms.push(body.slice(last));
1985
+ return arms;
1986
+ }
1987
+ function delimitedBodySound(body, close) {
1988
+ const l0 = close[0];
1989
+ if (close.length > 2) return false;
1990
+ const arms = splitDelimArms(body);
1991
+ if (!arms) return false;
1992
+ let bulkSeen = false;
1993
+ let guardSeen = false;
1994
+ for (const arm of arms) {
1995
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
1996
+ if (bulk) {
1997
+ if (bulkSeen) return false;
1998
+ const ranges = parseClassRanges(bulk[1]);
1999
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2000
+ bulkSeen = true;
2001
+ continue;
2002
+ }
2003
+ if (close.length === 2 && arm.endsWith(")")) {
2004
+ const idx = arm.indexOf("(?!");
2005
+ if (idx === -1) return false;
2006
+ const prefix = literalCodePoints(arm.slice(0, idx));
2007
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2008
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2009
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2010
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2011
+ guardSeen = true;
2012
+ continue;
2013
+ }
2014
+ return false;
2015
+ }
2016
+ return bulkSeen && guardSeen === (close.length === 2);
2017
+ }
2018
+ function parseDelimited(source) {
2019
+ if (source.includes("\\\\")) return null;
2020
+ let i = 0;
2021
+ while (i < source.length && source[i] !== "(") {
2022
+ i += source[i] === "\\" ? 2 : 1;
2023
+ }
2024
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2025
+ const openFrag = source.slice(0, i);
2026
+ let k = i + 3;
2027
+ let depth = 1;
2028
+ while (k < source.length && depth > 0) {
2029
+ const ch = source[k];
2030
+ if (ch === "\\") {
2031
+ k += 2;
2032
+ continue;
2033
+ }
2034
+ if (ch === "[") {
2035
+ let j = k + 1;
2036
+ if (source[j] === "^") j++;
2037
+ while (j < source.length && source[j] !== "]") {
2038
+ if (source[j] === "\\") j += 2;
2039
+ else j++;
2040
+ }
2041
+ if (source[j] !== "]") return null;
2042
+ k = j + 1;
2043
+ continue;
2044
+ }
2045
+ if (ch === "(") depth++;
2046
+ else if (ch === ")") depth--;
2047
+ k++;
2048
+ }
2049
+ if (depth !== 0) return null;
2050
+ const body = source.slice(i + 3, k - 1);
2051
+ if (source[k] !== "*") return null;
2052
+ const closeFrag = source.slice(k + 1);
2053
+ const open = literalCodePoints(openFrag);
2054
+ const close = literalCodePoints(closeFrag);
2055
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2056
+ return { kind: "delimited", open, close };
2057
+ }
1893
2058
  function parseScanShapeCore(source) {
1894
2059
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1895
2060
  if (m && !m[1].startsWith("^")) {
@@ -1914,14 +2079,8 @@ function parseScanShapeCore(source) {
1914
2079
  const stop = parseClassRanges(m[2]);
1915
2080
  if (open && stop) return { kind: "until", open, stop };
1916
2081
  }
1917
- if (!source.includes("\\\\")) {
1918
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1919
- if (m && m[1] && m[2]) {
1920
- const open = literalCodePoints(m[1]);
1921
- const close = literalCodePoints(m[2]);
1922
- if (open && close) return { kind: "delimited", open, close };
1923
- }
1924
- }
2082
+ const delim = parseDelimited(source);
2083
+ if (delim) return delim;
1925
2084
  const parts = parseSeqParts(source);
1926
2085
  if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1927
2086
  return null;
@@ -2376,6 +2535,7 @@ function looseMkBuildRe(type) {
2376
2535
  );
2377
2536
  }
2378
2537
  function analyzeMkInlineBuild(def) {
2538
+ if (!def.build) return null;
2379
2539
  const src = (def.buildSrc ?? def.build.toString()).trim();
2380
2540
  const strict = src.match(MK_BUILD_RE);
2381
2541
  if (strict) {
@@ -2473,6 +2633,15 @@ function v(ctx, prefix = "_v") {
2473
2633
  function ind(ctx) {
2474
2634
  return " ".repeat(ctx.indent);
2475
2635
  }
2636
+ function nsp(ctx) {
2637
+ return ctx.ns ?? "";
2638
+ }
2639
+ function mfRef(ctx) {
2640
+ return `${nsp(ctx)}_mf`;
2641
+ }
2642
+ function buildRef(ctx) {
2643
+ return `${nsp(ctx)}_build`;
2644
+ }
2476
2645
  function reindentStmts(stmts, targetLevels) {
2477
2646
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
2478
2647
  if (nonEmpty.length === 0) return stmts;
@@ -2489,7 +2658,7 @@ function failReturnArr(expectedArr, posExpr) {
2489
2658
  function hoistExpected(ctx, constArrSource) {
2490
2659
  let name = ctx.expectedMap.get(constArrSource);
2491
2660
  if (name === void 0) {
2492
- name = `_fx${ctx.expectedDecls.length}`;
2661
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2493
2662
  ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2494
2663
  ctx.expectedMap.set(constArrSource, name);
2495
2664
  }
@@ -2588,13 +2757,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2588
2757
  `${i}}`
2589
2758
  ];
2590
2759
  }
2591
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2760
+ function ensureRegexDecl(ctx, source, flags) {
2592
2761
  const f = "y" + flags.replace(/[gy]/g, "");
2593
- const key = `${optimizedSource}/${f}`;
2762
+ const key = `${source}/${f}`;
2594
2763
  let rName = ctx.regexMap.get(key);
2595
2764
  if (rName === void 0) {
2596
- rName = `_re${ctx.regexDecls.length}`;
2597
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2765
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2766
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2598
2767
  ctx.regexMap.set(key, rName);
2599
2768
  }
2600
2769
  return rName;
@@ -2603,7 +2772,7 @@ function ensureTriviaFn(ctx) {
2603
2772
  const trivia2 = ctx.activeTrivia;
2604
2773
  const existing = ctx.triviaFnNames.get(trivia2);
2605
2774
  if (existing) return existing;
2606
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2775
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2607
2776
  ctx.triviaFnNames.set(trivia2, fnName);
2608
2777
  ctx.triviaCaptureNames.set(trivia2, fnName);
2609
2778
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2625,7 +2794,7 @@ function ensureTriviaFn(ctx) {
2625
2794
  for (const arm of regexSpec.arms) {
2626
2795
  const def = arm.parser._def;
2627
2796
  if (def.tag !== "regex") break;
2628
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2797
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2629
2798
  }
2630
2799
  if (reNames.length === regexSpec.arms.length) {
2631
2800
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -2950,7 +3119,7 @@ function emitKeywords(def, ctx, pos) {
2950
3119
  const key = `${source}/${flags}`;
2951
3120
  let rName = ctx.regexMap.get(key);
2952
3121
  if (rName === void 0) {
2953
- rName = `_re${ctx.regexDecls.length}`;
3122
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2954
3123
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
2955
3124
  ctx.regexMap.set(key, rName);
2956
3125
  }
@@ -2984,11 +3153,11 @@ function emitRegex(def, ctx, pos) {
2984
3153
  }
2985
3154
  }
2986
3155
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2987
- const key = `${def.optimizedSource}/${flags}`;
3156
+ const key = `${def.source}/${flags}`;
2988
3157
  let rName = ctx.regexMap.get(key);
2989
3158
  if (rName === void 0) {
2990
- rName = `_re${ctx.regexDecls.length}`;
2991
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3159
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3160
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
2992
3161
  ctx.regexMap.set(key, rName);
2993
3162
  }
2994
3163
  const mv = v(ctx, "_m");
@@ -3165,7 +3334,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3165
3334
  const reKey = `${regexDef.source}/${cleanFlags}`;
3166
3335
  let reVar = ctx.regexMap.get(reKey);
3167
3336
  if (reVar === void 0) {
3168
- reVar = `_re${ctx.regexDecls.length}`;
3337
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3169
3338
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
3170
3339
  ctx.regexMap.set(reKey, reVar);
3171
3340
  }
@@ -3258,7 +3427,7 @@ function emitFirstMatch(def, ctx, pos) {
3258
3427
  if (gate) {
3259
3428
  const gateIdx = ctx.mapFns.length;
3260
3429
  ctx.mapFns.push(gate);
3261
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3430
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3262
3431
  }
3263
3432
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3264
3433
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -3325,7 +3494,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3325
3494
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
3326
3495
  const vv = v(ctx);
3327
3496
  return {
3328
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3497
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3329
3498
  valueVar: vv,
3330
3499
  endVar: endV
3331
3500
  };
@@ -3553,16 +3722,17 @@ function emitNot(def, ctx, pos) {
3553
3722
  };
3554
3723
  }
3555
3724
  function emitNode(def, ctx, pos) {
3556
- const mkType = analyzeMkInlineBuild(def);
3725
+ const structural = def.build === void 0;
3726
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
3557
3727
  let buildIdx = null;
3558
- if (!mkType) {
3728
+ if (!mkType && !structural) {
3559
3729
  buildIdx = ctx.buildFns.length;
3560
3730
  ctx.buildFns.push(def.build);
3561
3731
  ctx.buildSrcs.push(def.buildSrc ?? null);
3562
3732
  }
3563
3733
  const i = ind(ctx);
3564
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
3565
- const clonesState = buildReadsState(def);
3734
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3735
+ const clonesState = structural || buildReadsState(def);
3566
3736
  const chV = v(ctx, "_ch");
3567
3737
  const rawV = v(ctx, "_raw");
3568
3738
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -3585,7 +3755,8 @@ function emitNode(def, ctx, pos) {
3585
3755
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3586
3756
  }
3587
3757
  const ndV = v(ctx, "_nd");
3588
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3758
+ const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3759
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
3589
3760
  const finalExpr = def.collapse ? `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})` : ndExpr;
3590
3761
  stmts.push(
3591
3762
  `${i}const ${ndV} = ${finalExpr}`,
@@ -3610,8 +3781,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
3610
3781
  return { stmts, valueVar: vv, endVar: ev };
3611
3782
  }
3612
3783
  function emitLazy(p, def, ctx, pos) {
3784
+ const canonical = ctx.ruleNames?.get(p);
3613
3785
  const usage = ctx.lazyUsage;
3614
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3786
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3615
3787
  let resolved;
3616
3788
  try {
3617
3789
  resolved = def.thunk();
@@ -3621,7 +3793,7 @@ function emitLazy(p, def, ctx, pos) {
3621
3793
  return emit(resolved, ctx, pos);
3622
3794
  }
3623
3795
  if (!ctx.namedParsers.has(p)) {
3624
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3796
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
3625
3797
  ctx.namedParsers.set(p, fnName2);
3626
3798
  let resolved;
3627
3799
  try {
@@ -3739,7 +3911,7 @@ function emit(p, ctx, pos) {
3739
3911
  return {
3740
3912
  stmts: [
3741
3913
  ...inner.stmts,
3742
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3914
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3743
3915
  ],
3744
3916
  valueVar: mv,
3745
3917
  endVar: inner.endVar
@@ -3802,7 +3974,7 @@ function emit(p, ctx, pos) {
3802
3974
  const vv = v(ctx);
3803
3975
  return {
3804
3976
  stmts: [
3805
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3977
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3806
3978
  `${ind(ctx)}const ${vv} = null`
3807
3979
  ],
3808
3980
  valueVar: vv,
@@ -3835,7 +4007,7 @@ function emit(p, ctx, pos) {
3835
4007
  const ev = v(ctx, "_wce");
3836
4008
  return {
3837
4009
  stmts: [
3838
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4010
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3839
4011
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3840
4012
  // withCtx runs on a spread child ctx — copy its recorded failure back
3841
4013
  // and propagate the inner failure verbatim (interpreter parity).
@@ -3966,6 +4138,39 @@ function analyzeLazyUsageMulti(roots) {
3966
4138
  for (const root of roots) walk2(root);
3967
4139
  return { counts, recursive };
3968
4140
  }
4141
+ function ruleDependencies(ruleMap) {
4142
+ const nameOf = /* @__PURE__ */ new Map();
4143
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4144
+ const deps = /* @__PURE__ */ new Map();
4145
+ for (const [name, comb] of ruleMap) {
4146
+ const found = /* @__PURE__ */ new Set();
4147
+ const seen = /* @__PURE__ */ new Set();
4148
+ const walk2 = (p, isRoot) => {
4149
+ const def = p._def;
4150
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4151
+ if (!isRoot && boundary !== void 0) {
4152
+ found.add(boundary);
4153
+ return;
4154
+ }
4155
+ if (seen.has(p)) return;
4156
+ seen.add(p);
4157
+ if (def.tag === "lazy") {
4158
+ let resolved;
4159
+ try {
4160
+ resolved = def.thunk();
4161
+ } catch {
4162
+ return;
4163
+ }
4164
+ walk2(resolved, false);
4165
+ return;
4166
+ }
4167
+ for (const child of childrenOf(def)) walk2(child, false);
4168
+ };
4169
+ walk2(comb, true);
4170
+ deps.set(name, [...found]);
4171
+ }
4172
+ return deps;
4173
+ }
3969
4174
  function compile(combinator, mapFnSources) {
3970
4175
  const ctx = {
3971
4176
  vars: 0,
@@ -4071,6 +4276,15 @@ function compileRuleMap(ruleMap) {
4071
4276
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4072
4277
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule))
4073
4278
  };
4279
+ const seenNames = /* @__PURE__ */ new Map();
4280
+ const ruleNames = /* @__PURE__ */ new Map();
4281
+ for (const [key, rule] of ruleMap) {
4282
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4283
+ const n = seenNames.get(base) ?? 0;
4284
+ seenNames.set(base, n + 1);
4285
+ ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4286
+ }
4287
+ ctx.ruleNames = ruleNames;
4074
4288
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4075
4289
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4076
4290
  ` : "";
@@ -4080,8 +4294,8 @@ function compileRuleMap(ruleMap) {
4080
4294
  const mfCovered = ctx.mapFns.length === 0 || derivedSrcs !== void 0;
4081
4295
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4082
4296
  if (!canInline) return null;
4083
- const mfDecl = derivedSrcs?.length ? ` const _mf = [${derivedSrcs.join(", ")}]` : "";
4084
- const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4297
+ const mfDecl = derivedSrcs?.length ? ` const ${mfRef(ctx)} = [${derivedSrcs.join(", ")}]` : "";
4298
+ const buildDecl = buildSources?.length ? ` const ${buildRef(ctx)} = [${buildSources.join(", ")}]` : "";
4085
4299
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4086
4300
  const hoistedDecls = [
4087
4301
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
@@ -4114,6 +4328,150 @@ function compileRuleMap(ruleMap) {
4114
4328
  ].join("\n");
4115
4329
  return { keys: perEntry.map((e) => e.key), replacement };
4116
4330
  }
4331
+ function compileLinkable(ruleMapArg, ns) {
4332
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4333
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4334
+ const d = val._def;
4335
+ if (d.tag !== "lazy") return true;
4336
+ try {
4337
+ d.thunk();
4338
+ return true;
4339
+ } catch {
4340
+ return false;
4341
+ }
4342
+ });
4343
+ const ctx = {
4344
+ vars: 0,
4345
+ indent: 1,
4346
+ regexDecls: [],
4347
+ regexMap: /* @__PURE__ */ new Map(),
4348
+ expectedDecls: [],
4349
+ expectedMap: /* @__PURE__ */ new Map(),
4350
+ recordFail: true,
4351
+ mapFns: [],
4352
+ mapFnSrcs: [],
4353
+ buildFns: [],
4354
+ buildSrcs: [],
4355
+ runtimeParsers: [],
4356
+ needsCollator: false,
4357
+ namedParsers: /* @__PURE__ */ new Map(),
4358
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4359
+ triviaFnNames: /* @__PURE__ */ new Map(),
4360
+ namedFnDecls: [],
4361
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4362
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4363
+ ns
4364
+ };
4365
+ const seen = /* @__PURE__ */ new Map();
4366
+ const ruleNames = /* @__PURE__ */ new Map();
4367
+ const fnNameToKey = /* @__PURE__ */ new Map();
4368
+ for (const [key, rule] of ruleMap) {
4369
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4370
+ const n = seen.get(base) ?? 0;
4371
+ seen.set(base, n + 1);
4372
+ const fn = n === 0 ? base : `${base}$${n}`;
4373
+ ruleNames.set(rule, fn);
4374
+ fnNameToKey.set(fn, key);
4375
+ }
4376
+ ctx.ruleNames = ruleNames;
4377
+ {
4378
+ const scanned = /* @__PURE__ */ new Set();
4379
+ const scanExternal = (p) => {
4380
+ if (scanned.has(p)) return;
4381
+ scanned.add(p);
4382
+ const def = p._def;
4383
+ if (def.tag === "lazy") {
4384
+ let resolved;
4385
+ try {
4386
+ resolved = def.thunk();
4387
+ } catch {
4388
+ resolved = void 0;
4389
+ }
4390
+ if (resolved === void 0) {
4391
+ const name = p._ruleName;
4392
+ if (name && !ctx.namedParsers.has(p)) {
4393
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4394
+ ruleNames.set(p, fn);
4395
+ ctx.namedParsers.set(p, fn);
4396
+ }
4397
+ return;
4398
+ }
4399
+ scanExternal(resolved);
4400
+ return;
4401
+ }
4402
+ for (const child of childrenOf(def)) scanExternal(child);
4403
+ };
4404
+ for (const [, rule] of ruleMap) scanExternal(rule);
4405
+ }
4406
+ const perEntry = [];
4407
+ for (const [key, rule] of ruleMap) {
4408
+ const fn = ruleNames.get(rule);
4409
+ if (!ctx.namedParsers.has(rule)) {
4410
+ ctx.namedParsers.set(rule, fn);
4411
+ let resolved = rule;
4412
+ const def = rule._def;
4413
+ if (def.tag === "lazy") {
4414
+ try {
4415
+ resolved = def.thunk();
4416
+ } catch {
4417
+ return null;
4418
+ }
4419
+ }
4420
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4421
+ ctx.indent = 1;
4422
+ ctx.failLabel = "_pfail";
4423
+ ctx.recordFail = true;
4424
+ const body = emit(resolved, ctx, "_pos");
4425
+ ctx.indent = savedIndent;
4426
+ ctx.failLabel = savedFail;
4427
+ ctx.recordFail = savedRec;
4428
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4429
+ }
4430
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4431
+ }
4432
+ if (ctx.runtimeParsers.length !== 0) return null;
4433
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4434
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4435
+ const ruleFns = /* @__PURE__ */ new Map();
4436
+ const privateFns = [];
4437
+ for (const decl of ctx.namedFnDecls) {
4438
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4439
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4440
+ if (key !== void 0) ruleFns.set(key, decl);
4441
+ else privateFns.push(decl);
4442
+ }
4443
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4444
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4445
+ const prelude = [
4446
+ ...ctx.regexDecls,
4447
+ ...ctx.expectedDecls,
4448
+ mfDecl,
4449
+ buildDecl,
4450
+ ...privateFns
4451
+ ].filter(Boolean);
4452
+ const wrappers = /* @__PURE__ */ new Map();
4453
+ for (const { key, r } of perEntry) {
4454
+ wrappers.set(key, [
4455
+ `function(input, _pos, _ctx) {`,
4456
+ ` let pos = _pos`,
4457
+ ...r.stmts,
4458
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4459
+ `}`
4460
+ ].join("\n"));
4461
+ }
4462
+ return {
4463
+ ns,
4464
+ keys: perEntry.map((e) => e.key),
4465
+ prelude,
4466
+ ruleFns,
4467
+ wrappers,
4468
+ deps: ruleDependencies(ruleMap),
4469
+ needsEmptyTl: !!ctx.needsEmptyTl,
4470
+ needsCollator: ctx.needsCollator,
4471
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4472
+ buildFns: buildSrcs ? [] : ctx.buildFns
4473
+ };
4474
+ }
4117
4475
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4118
4476
  const bodyLines = [
4119
4477
  ` let pos = _pos`,
@@ -4152,6 +4510,48 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4152
4510
  ].join("\n");
4153
4511
  }
4154
4512
 
4513
+ // src/compiler/linker.ts
4514
+ function fusedBody(pieces) {
4515
+ const winner = /* @__PURE__ */ new Map();
4516
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4517
+ for (const [k, p] of winner) {
4518
+ for (const d of p.deps.get(k) ?? []) {
4519
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4520
+ }
4521
+ }
4522
+ const contributing = new Set(winner.values());
4523
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4524
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4525
+ const lines = [
4526
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4527
+ "const _pfFail = {}",
4528
+ "let _pfEnd",
4529
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4530
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4531
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4532
+ ...[...contributing].flatMap((p) => p.prelude),
4533
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4534
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4535
+ ];
4536
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4537
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4538
+ const env = {};
4539
+ for (const p of contributing) {
4540
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4541
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4542
+ }
4543
+ return { body, env };
4544
+ }
4545
+ function emitFusedSource(pieces) {
4546
+ const { body, env } = fusedBody(pieces);
4547
+ if (Object.keys(env).length > 0) {
4548
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4549
+ }
4550
+ return `/* @__PURE__ */ (() => {
4551
+ ${body}
4552
+ })()`;
4553
+ }
4554
+
4155
4555
  // src/combinators/scanTo.ts
4156
4556
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4157
4557
  const meta = {
@@ -4246,6 +4646,12 @@ function escapeClassChar(ch) {
4246
4646
  return ch.replace(/[\\\]^-]/g, "\\$&");
4247
4647
  }
4248
4648
 
4649
+ // src/cst/offset-model.ts
4650
+ var { isFinite } = Number;
4651
+
4652
+ // src/index.ts
4653
+ registerRegexAnalyzer(firstSetFromRegex);
4654
+
4249
4655
  // src/plugin/evaluator.ts
4250
4656
  function isWordFactory(v2) {
4251
4657
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
@@ -4345,16 +4751,18 @@ function exprToCombi(node2, scope, code, mfs) {
4345
4751
  }
4346
4752
  if (callee.name === "node" && code !== void 0) {
4347
4753
  const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
4348
- if (!typeArg || !parserArg || !buildArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || buildArg.type === "SpreadElement") return null;
4754
+ if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
4349
4755
  const typeVal = anyValue(typeArg, scope, code);
4350
4756
  if (typeof typeVal !== "string") return null;
4351
4757
  const inner = anyValue(parserArg, scope, code, mfs);
4352
4758
  if (!isCombinator(inner)) return null;
4353
- const buildSrc = code.slice(buildArg.start, buildArg.end);
4759
+ const be = buildArg;
4760
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
4761
+ const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
4354
4762
  const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
4355
4763
  try {
4356
- const combi = node(typeVal, inner, () => null, collapse ? { collapse: true } : void 0);
4357
- if (combi._def.tag === "node") combi._def.buildSrc = buildSrc;
4764
+ const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
4765
+ if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
4358
4766
  return combi;
4359
4767
  } catch {
4360
4768
  return null;
@@ -4540,91 +4948,24 @@ function evaluateCombinatorArray(node2, scope, code) {
4540
4948
  if (!val.every(isCombinator)) return null;
4541
4949
  return val;
4542
4950
  }
4543
- function asFragmentFactory(expr) {
4544
- if (expr.type !== "ArrowFunctionExpression" && expr.type !== "FunctionExpression" && expr.type !== "FunctionDeclaration") return null;
4545
- const fn = expr;
4546
- return factoryBodyAndReturn(fn) ? fn : null;
4547
- }
4548
- var unwrapParen = (e) => e && e.type === "ParenthesizedExpression" ? e.expression : e;
4549
- function factoryBodyAndReturn(fn) {
4550
- const body = fn.body;
4551
- if (!body) return null;
4552
- const b = unwrapParen(body);
4553
- if (b.type === "ObjectExpression") return { statements: [], retObj: b };
4554
- if (b.type === "BlockStatement") {
4555
- const statements = [];
4556
- for (const stmt of b.body) {
4557
- if (stmt.type === "ReturnStatement") {
4558
- const a = unwrapParen(stmt.argument);
4559
- if (!a || a.type !== "ObjectExpression") return null;
4560
- return { statements, retObj: a };
4561
- }
4562
- if (stmt.type === "VariableDeclaration") statements.push(stmt);
4563
- else return null;
4564
- }
4565
- }
4566
- return null;
4567
- }
4568
- function spreadFactoryCall(spread, resolve) {
4569
- const call = spread.argument;
4570
- if (!call || call.type !== "CallExpression") return null;
4571
- if (!call.callee || call.callee.type !== "Identifier" || !call.callee.name) return null;
4572
- const resolved = resolve(call.callee.name);
4573
- return resolved ? { fn: resolved.fn, args: call.arguments ?? [], code: resolved.code } : null;
4574
- }
4575
- function factoryCallScope(fn, args, outer) {
4576
- const s = new Map(outer);
4577
- const params = fn.params ?? [];
4578
- for (let i = 0; i < params.length; i++) {
4579
- const pn = params[i];
4580
- if (!pn || pn.type !== "Identifier" || !pn.name) continue;
4581
- const arg = args[i];
4582
- if (arg && arg.type === "Identifier" && arg.name && outer.has(arg.name)) {
4583
- s.set(pn.name, outer.get(arg.name));
4584
- }
4585
- }
4586
- return s;
4587
- }
4588
4951
  function propKey(prop) {
4589
4952
  if (prop.type !== "Property" || prop.computed || !prop.key) return null;
4590
4953
  if (prop.key.type === "Identifier") return prop.key.name ?? null;
4591
4954
  if (prop.key.type === "Literal") return String(prop.key.value);
4592
4955
  return null;
4593
4956
  }
4594
- function collectRuleKeys(retObj, resolve, seen) {
4957
+ function collectRuleKeys(retObj) {
4595
4958
  const out = [];
4596
4959
  for (const prop of retObj.properties) {
4597
- if (prop.type === "SpreadElement") {
4598
- const sf = spreadFactoryCall(prop, resolve);
4599
- if (!sf || seen.has(sf.fn)) return null;
4600
- const br = factoryBodyAndReturn(sf.fn);
4601
- if (!br) return null;
4602
- const nested = collectRuleKeys(br.retObj, resolve, new Set(seen).add(sf.fn));
4603
- if (!nested) return null;
4604
- out.push(...nested);
4605
- continue;
4606
- }
4607
4960
  const key = propKey(prop);
4608
4961
  if (!key) return null;
4609
4962
  out.push(key);
4610
4963
  }
4611
4964
  return out;
4612
4965
  }
4613
- function flattenRuleEntries(retObj, scope, code, resolve, seen) {
4966
+ function flattenRuleEntries(retObj, scope, code) {
4614
4967
  const out = [];
4615
4968
  for (const prop of retObj.properties) {
4616
- if (prop.type === "SpreadElement") {
4617
- const sf = spreadFactoryCall(prop, resolve);
4618
- if (!sf || seen.has(sf.fn)) return null;
4619
- const br = factoryBodyAndReturn(sf.fn);
4620
- if (!br) return null;
4621
- const fragScope = factoryCallScope(sf.fn, sf.args, scope);
4622
- if (!evalBodyStatements(br.statements, fragScope, sf.code)) return null;
4623
- const nested = flattenRuleEntries(br.retObj, fragScope, sf.code, resolve, new Set(seen).add(sf.fn));
4624
- if (!nested) return null;
4625
- out.push(...nested);
4626
- continue;
4627
- }
4628
4969
  const key = propKey(prop);
4629
4970
  if (!key) return null;
4630
4971
  out.push({ key, value: prop.value, scope, code });
@@ -4650,7 +4991,7 @@ function evalBodyStatements(statements, scope, code) {
4650
4991
  }
4651
4992
  return true;
4652
4993
  }
4653
- function evaluateParserFactory(factoryNode, scope, code, mapFnSources, resolveFactory = () => null) {
4994
+ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
4654
4995
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
4655
4996
  const factory = factoryNode;
4656
4997
  const params = factory.params;
@@ -4682,16 +5023,36 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources, resolveFa
4682
5023
  if (!returnExpr) return null;
4683
5024
  const retObj = returnExpr.type === "ParenthesizedExpression" ? returnExpr.expression : returnExpr;
4684
5025
  if (retObj.type !== "ObjectExpression") return null;
4685
- const keys = collectRuleKeys(retObj, resolveFactory, /* @__PURE__ */ new Set());
5026
+ const keys = collectRuleKeys(retObj);
4686
5027
  if (!keys) return null;
4687
5028
  const ruleRefs = /* @__PURE__ */ new Map();
5029
+ const tagRef = (r, name) => {
5030
+ r._ruleName = name;
5031
+ };
4688
5032
  for (const key of keys) {
4689
- if (!ruleRefs.has(key)) ruleRefs.set(key, ref());
4690
- }
5033
+ if (!ruleRefs.has(key)) {
5034
+ const r = ref();
5035
+ tagRef(r, key);
5036
+ ruleRefs.set(key, r);
5037
+ }
5038
+ }
5039
+ const externalRefs = /* @__PURE__ */ new Map();
5040
+ const gProxy = new Proxy(Object.fromEntries(ruleRefs), {
5041
+ get(target, prop) {
5042
+ if (typeof prop !== "string" || prop in target) return target[prop];
5043
+ let ext = externalRefs.get(prop);
5044
+ if (!ext) {
5045
+ ext = ref();
5046
+ tagRef(ext, prop);
5047
+ externalRefs.set(prop, ext);
5048
+ }
5049
+ return ext;
5050
+ }
5051
+ });
4691
5052
  const localScope = new Map(scope);
4692
- localScope.set(proxyName, Object.fromEntries(ruleRefs));
5053
+ localScope.set(proxyName, gProxy);
4693
5054
  if (!evalBodyStatements(statements, localScope, code)) return null;
4694
- const entries = flattenRuleEntries(retObj, localScope, code, resolveFactory, /* @__PURE__ */ new Set());
5055
+ const entries = flattenRuleEntries(retObj, localScope, code);
4695
5056
  if (!entries) return null;
4696
5057
  const finalByKey = /* @__PURE__ */ new Map();
4697
5058
  for (const e of entries) finalByKey.set(e.key, e);
@@ -4753,19 +5114,14 @@ function referencesAny(node2, names, scope) {
4753
5114
  }
4754
5115
 
4755
5116
  // src/plugin/index.ts
5117
+ import { createHash } from "node:crypto";
4756
5118
  var PARSEMAN_MODULE = "parseman";
4757
- var _fragmentResolver = null;
4758
- function getFragmentResolver() {
4759
- return _fragmentResolver ??= new ResolverFactory({
4760
- extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
4761
- // TS/NodeNext style: a `./x.js` import may point at `./x.ts` source.
4762
- extensionAlias: {
4763
- ".js": [".ts", ".tsx", ".js", ".jsx"],
4764
- ".mjs": [".mts", ".mjs"],
4765
- ".cjs": [".cts", ".cjs"]
4766
- },
4767
- conditionNames: ["source", "development", "import", "require", "default"],
4768
- mainFields: ["source", "module", "main"]
5119
+ var _compiledResolver = null;
5120
+ function getCompiledResolver() {
5121
+ return _compiledResolver ??= new ResolverFactory({
5122
+ extensions: [".js", ".mjs", ".cjs"],
5123
+ conditionNames: ["import", "require", "default"],
5124
+ mainFields: ["module", "main"]
4769
5125
  });
4770
5126
  }
4771
5127
  var _moduleParseCache = /* @__PURE__ */ new Map();
@@ -4812,6 +5168,77 @@ var plugin_default = createUnplugin((opts = {}) => ({
4812
5168
  return { code: result.code, map: result.map };
4813
5169
  }
4814
5170
  }));
5171
+ function exportLocalName(body, exported) {
5172
+ for (const st of body) {
5173
+ if (st.type !== "ExportNamedDeclaration") continue;
5174
+ const decl = st.declaration;
5175
+ if (decl?.type === "VariableDeclaration") {
5176
+ for (const d of decl.declarations ?? []) {
5177
+ const idn = d.id;
5178
+ if (idn?.type === "Identifier" && idn.name === exported) return exported;
5179
+ }
5180
+ }
5181
+ for (const sp of st.specifiers ?? []) {
5182
+ const exp = sp.exported?.name;
5183
+ const loc = sp.local?.name;
5184
+ if (exp === exported && loc) return loc;
5185
+ }
5186
+ }
5187
+ return null;
5188
+ }
5189
+ function topLevelInit(body, name) {
5190
+ for (const st of body) {
5191
+ const vd = st.type === "VariableDeclaration" ? st : st.type === "ExportNamedDeclaration" && st.declaration?.type === "VariableDeclaration" ? st.declaration : null;
5192
+ if (!vd) continue;
5193
+ for (const d of vd.declarations ?? []) {
5194
+ const idn = d.id;
5195
+ if (idn?.type === "Identifier" && idn.name === name && d.init) return d.init;
5196
+ }
5197
+ }
5198
+ return null;
5199
+ }
5200
+ function isComposedPiecesSymbol(n) {
5201
+ if (!n || n.type !== "CallExpression") return false;
5202
+ const callee = n.callee;
5203
+ const obj = callee?.object?.name;
5204
+ const prop = callee?.property?.name;
5205
+ const arg0 = n.arguments?.[0];
5206
+ return obj === "Symbol" && prop === "for" && arg0?.value === "parseman.composedPieces";
5207
+ }
5208
+ function findCarriedPiecesLiteral(root) {
5209
+ let found = null;
5210
+ const visit = (n) => {
5211
+ if (found || !n || typeof n !== "object") return;
5212
+ if (Array.isArray(n)) {
5213
+ for (const c of n) visit(c);
5214
+ return;
5215
+ }
5216
+ const node2 = n;
5217
+ if (node2.type === "CallExpression") {
5218
+ const callee = node2.callee;
5219
+ const obj = callee?.object?.name;
5220
+ const prop = callee?.property?.name;
5221
+ const args = node2.arguments;
5222
+ if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5223
+ const descriptor = args[2];
5224
+ for (const p of descriptor?.properties ?? []) {
5225
+ const key = p.key;
5226
+ if ((key?.name === "value" || key?.value === "value") && p.value) {
5227
+ found = { start: p.value.start, end: p.value.end };
5228
+ return;
5229
+ }
5230
+ }
5231
+ }
5232
+ }
5233
+ for (const k in node2) {
5234
+ if (k === "type" || k === "start" || k === "end") continue;
5235
+ const v2 = node2[k];
5236
+ if (v2 && typeof v2 === "object") visit(v2);
5237
+ }
5238
+ };
5239
+ visit(root);
5240
+ return found;
5241
+ }
4815
5242
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
4816
5243
  let result;
4817
5244
  try {
@@ -4849,63 +5276,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4849
5276
  }
4850
5277
  if (macroImports.length === 0) return null;
4851
5278
  const scope = /* @__PURE__ */ new Map();
4852
- const factories = /* @__PURE__ */ new Map();
4853
- const resolver = getFragmentResolver();
4854
- const importedFactoryCache = /* @__PURE__ */ new Map();
4855
- const OUTPUT_DIR = /([\\/])(dist|lib|build|out|output|compiled|cjs|esm|es)([\\/])/;
4856
- const resolveSourcePath = (spec) => {
4857
- let resolved = null;
4858
- try {
4859
- resolved = resolver.resolveFileSync(id, spec).path ?? null;
4860
- } catch {
4861
- }
4862
- if (!resolved) return null;
4863
- if (OUTPUT_DIR.test(resolved)) {
4864
- const srcCandidate = resolved.replace(OUTPUT_DIR, "$1src$3").replace(/\.(c|m)?js$/, ".ts");
4865
- try {
4866
- if (fs.statSync(srcCandidate).isFile()) return srcCandidate;
4867
- } catch {
4868
- }
4869
- }
4870
- return resolved;
4871
- };
4872
- const resolveImportedFactory = (name) => {
4873
- if (importedFactoryCache.has(name)) return importedFactoryCache.get(name);
4874
- let result2 = null;
4875
- const binding = importBindings.get(name);
4876
- if (binding) {
4877
- const filePath = resolveSourcePath(binding.source);
4878
- const mod = filePath ? parseModuleCached(filePath) : null;
4879
- if (mod) {
4880
- for (const st of mod.body) {
4881
- if (st.type !== "ExportNamedDeclaration" || !st.declaration) continue;
4882
- const decl = st.declaration;
4883
- if (decl.type === "VariableDeclaration") {
4884
- for (const d of decl.declarations ?? []) {
4885
- if (d.id?.type === "Identifier" && d.id.name === binding.imported && d.init) {
4886
- const fac = asFragmentFactory(d.init);
4887
- if (fac) {
4888
- result2 = { fn: fac, code: mod.src };
4889
- break;
4890
- }
4891
- }
4892
- }
4893
- } else if (decl.type === "FunctionDeclaration" && decl.id?.type === "Identifier" && decl.id.name === binding.imported) {
4894
- const fac = asFragmentFactory(decl);
4895
- if (fac) result2 = { fn: fac, code: mod.src };
4896
- }
4897
- if (result2) break;
4898
- }
4899
- }
4900
- }
4901
- importedFactoryCache.set(name, result2);
4902
- return result2;
4903
- };
4904
- const resolveFactory = (n) => {
4905
- const local = factories.get(n);
4906
- if (local) return { fn: local, code };
4907
- return resolveImportedFactory(n);
4908
- };
4909
5279
  const replacements = [];
4910
5280
  const warnings = [];
4911
5281
  let anyUnresolved = false;
@@ -4926,7 +5296,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4926
5296
  warn(init.start, `${label2}: rules() needs a factory argument`);
4927
5297
  return null;
4928
5298
  }
4929
- const ruleMap = evaluateParserFactory(factoryArg, scope, code, [], resolveFactory);
5299
+ const ruleMap = evaluateParserFactory(factoryArg, scope, code, []);
4930
5300
  if (!ruleMap) {
4931
5301
  warn(init.start, `${label2}: rules(...) factory isn't statically evaluable`);
4932
5302
  return null;
@@ -4939,6 +5309,89 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4939
5309
  return { replacement: compiled.replacement, ruleMap };
4940
5310
  };
4941
5311
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
5312
+ const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5313
+ const localRuleMaps = /* @__PURE__ */ new Map();
5314
+ const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5315
+ const serializePieces = (p) => {
5316
+ const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5317
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
5318
+ };
5319
+ const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5320
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
5321
+ const localComposedPieces = /* @__PURE__ */ new Map();
5322
+ const importedPiecesCache = /* @__PURE__ */ new Map();
5323
+ const importedPieces = (localName) => {
5324
+ if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5325
+ let result2 = null;
5326
+ const binding = importBindings.get(localName);
5327
+ if (binding) {
5328
+ let file = null;
5329
+ try {
5330
+ file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5331
+ } catch {
5332
+ }
5333
+ const mod = file ? parseModuleCached(file) : null;
5334
+ if (mod) {
5335
+ const localFor = exportLocalName(mod.body, binding.imported);
5336
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5337
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5338
+ if (literalRange) {
5339
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5340
+ try {
5341
+ result2 = new Function(`return (${literal2})`)();
5342
+ } catch {
5343
+ result2 = null;
5344
+ }
5345
+ }
5346
+ }
5347
+ }
5348
+ importedPiecesCache.set(localName, result2);
5349
+ return result2;
5350
+ };
5351
+ const argPieces = (arg, label2) => {
5352
+ if (isRulesCall(arg)) {
5353
+ const factory = arg.arguments[0];
5354
+ const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5355
+ const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5356
+ return p ? [p] : null;
5357
+ }
5358
+ if (arg.type === "Identifier") {
5359
+ const name = arg.name;
5360
+ const rm = localRuleMaps.get(name);
5361
+ if (rm) {
5362
+ const p = compileLinkable([...rm], nsFor(label2));
5363
+ return p ? [p] : null;
5364
+ }
5365
+ const composed = localComposedPieces.get(name);
5366
+ if (composed) return composed;
5367
+ return importedPieces(name);
5368
+ }
5369
+ return null;
5370
+ };
5371
+ const compileComposeCall = (init) => {
5372
+ const args = init.arguments;
5373
+ const arr = args[0];
5374
+ if (!arr || arr.type !== "ArrayExpression") {
5375
+ warn(init.start, "compose(): expected a static array of grammars/artifacts");
5376
+ return null;
5377
+ }
5378
+ const elements = arr.elements;
5379
+ const pieces = [];
5380
+ for (let i = 0; i < elements.length; i++) {
5381
+ const list = argPieces(elements[i], `compose${init.start}_${i}`);
5382
+ if (!list) {
5383
+ warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5384
+ return null;
5385
+ }
5386
+ pieces.push(...list);
5387
+ }
5388
+ try {
5389
+ return { replacement: emitFusedSource(pieces), pieces };
5390
+ } catch (e) {
5391
+ warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5392
+ return null;
5393
+ }
5394
+ };
4942
5395
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
4943
5396
  const refNames = /* @__PURE__ */ new Set();
4944
5397
  for (const stmt of body) {
@@ -5017,16 +5470,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5017
5470
  if (isRulesCall(init)) {
5018
5471
  const compiledRules = compileRulesFactory(init, varName);
5019
5472
  if (!compiledRules) continue;
5020
- replacements.push({
5021
- start: init.start,
5022
- end: init.end,
5023
- replacement: compiledRules.replacement
5024
- });
5473
+ localRuleMaps.set(varName, compiledRules.ruleMap);
5474
+ let replacement = compiledRules.replacement;
5475
+ if (exportPrefix) {
5476
+ const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
5477
+ if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5478
+ replacement = withCarriedPieces(replacement, [pieces]);
5479
+ }
5480
+ }
5481
+ replacements.push({ start: init.start, end: init.end, replacement });
5025
5482
  continue;
5026
5483
  }
5027
- const frag = asFragmentFactory(init);
5028
- if (frag) {
5029
- factories.set(varName, frag);
5484
+ if (isComposeCall(init)) {
5485
+ const fused = compileComposeCall(init);
5486
+ if (!fused) continue;
5487
+ localComposedPieces.set(varName, fused.pieces);
5488
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
5489
+ replacements.push({ start: init.start, end: init.end, replacement });
5030
5490
  continue;
5031
5491
  }
5032
5492
  const mapFnSources = [];