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
@@ -323,6 +323,36 @@ function literal(value, opts = {}) {
323
323
  }
324
324
 
325
325
  // src/combinators/regex.ts
326
+ var firstSetAnalyzer = null;
327
+ function registerRegexAnalyzer(analyzer) {
328
+ firstSetAnalyzer = analyzer;
329
+ }
330
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
331
+ function regex(pattern, flags = "") {
332
+ const source = typeof pattern === "string" ? pattern : pattern.source;
333
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
334
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
335
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
336
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
337
+ return {
338
+ _tag: "regex",
339
+ _meta: meta,
340
+ _def: { tag: "regex", source, flags: resolvedFlags },
341
+ parse(input, pos, ctx) {
342
+ anchored.lastIndex = pos;
343
+ const m = anchored.exec(input);
344
+ if (m === null) {
345
+ return failAt(ctx, [`/${source}/`], pos);
346
+ }
347
+ const span = { start: pos, end: pos + m[0].length };
348
+ const leaf = { _tag: "leaf", value: m[0], span };
349
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
350
+ return { ok: true, value: m[0], span };
351
+ }
352
+ };
353
+ }
354
+
355
+ // src/combinators/regex-analyze.ts
326
356
  var import_regexp_tree = __toESM(require("regexp-tree"), 1);
327
357
  function firstSetFromRegex(pattern) {
328
358
  try {
@@ -427,40 +457,6 @@ function extractFirstSet(node2) {
427
457
  return { firstSet: any(), canMatchNewline: true };
428
458
  }
429
459
  }
430
- function optimizeRegex(source, flags) {
431
- try {
432
- const result = import_regexp_tree.default.optimize(`/${source}/${flags}`);
433
- const str = result.toString();
434
- const lastSlash = str.lastIndexOf("/");
435
- return str.slice(1, lastSlash);
436
- } catch {
437
- return source;
438
- }
439
- }
440
- function regex(pattern, flags = "") {
441
- const source = typeof pattern === "string" ? pattern : pattern.source;
442
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
443
- const optimizedSource = optimizeRegex(source, resolvedFlags);
444
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
445
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
446
- const meta = { firstSet, canMatchNewline, isTrivia: false };
447
- return {
448
- _tag: "regex",
449
- _meta: meta,
450
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
451
- parse(input, pos, ctx) {
452
- anchored.lastIndex = pos;
453
- const m = anchored.exec(input);
454
- if (m === null) {
455
- return failAt(ctx, [`/${source}/`], pos);
456
- }
457
- const span = { start: pos, end: pos + m[0].length };
458
- const leaf = { _tag: "leaf", value: m[0], span };
459
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
460
- return { ok: true, value: m[0], span };
461
- }
462
- };
463
- }
464
460
 
465
461
  // src/combinators/keywords.ts
466
462
  function word(str, boundary = "_0-9A-Za-z") {
@@ -1160,12 +1156,14 @@ function confirmedBuildArity(src) {
1160
1156
  return parts.length;
1161
1157
  }
1162
1158
  function buildReadsTrivia(def) {
1159
+ if (!def.build) return true;
1163
1160
  const src = def.buildSrc ?? def.build.toString();
1164
1161
  const arity = confirmedBuildArity(src);
1165
1162
  if (arity === null) return true;
1166
1163
  return arity >= 4;
1167
1164
  }
1168
1165
  function buildReadsState(def) {
1166
+ if (!def.build) return true;
1169
1167
  const src = def.buildSrc ?? def.build.toString();
1170
1168
  const arity = confirmedBuildArity(src);
1171
1169
  if (arity === null) return true;
@@ -1184,8 +1182,8 @@ function node(type, combinator, build, opts) {
1184
1182
  };
1185
1183
  const collapse = opts?.collapse === true;
1186
1184
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1187
- const capturesTrivia = buildReadsTrivia(def);
1188
- const clonesState = buildReadsState(def);
1185
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1186
+ const clonesState = build ? buildReadsState(def) : true;
1189
1187
  return {
1190
1188
  _tag: "node",
1191
1189
  _meta: meta,
@@ -1196,13 +1194,8 @@ function node(type, combinator, build, opts) {
1196
1194
  const r = combinator.parse(input, pos, ctx);
1197
1195
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1198
1196
  if (!r.ok) return r;
1199
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1200
- children,
1201
- rawChildren,
1202
- r.span,
1203
- triviaLog,
1204
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1205
- );
1197
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1198
+ 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 };
1206
1199
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1207
1200
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1208
1201
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1345,8 +1338,14 @@ function staticExpected(c) {
1345
1338
  case "transform":
1346
1339
  case "not":
1347
1340
  return staticExpected(def.parser);
1348
- case "lazy":
1349
- return staticExpected(def.thunk());
1341
+ case "lazy": {
1342
+ const name = c._ruleName;
1343
+ try {
1344
+ return staticExpected(def.thunk());
1345
+ } catch {
1346
+ return name ? [name] : [];
1347
+ }
1348
+ }
1350
1349
  default:
1351
1350
  return [];
1352
1351
  }
@@ -1646,6 +1645,15 @@ function seqIsUnambiguous(parts) {
1646
1645
  const p = parts[k];
1647
1646
  const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1648
1647
  if (mandatory) hasMandatory = true;
1648
+ if (p.part === "group") {
1649
+ const exposure = groupPartExposure(p);
1650
+ if (exposure === "unsupported") {
1651
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1652
+ if (!trailingOnce) return false;
1653
+ } else if (exposure !== null) {
1654
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1655
+ }
1656
+ }
1649
1657
  const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1650
1658
  const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1651
1659
  if (skippableSingle || isUnboundedRepeat) {
@@ -1692,6 +1700,58 @@ function firstSetsDisjoint(a, b) {
1692
1700
  if (a === "any" || b === "any") return false;
1693
1701
  return classDisjoint(a, b);
1694
1702
  }
1703
+ function fixedClassSeq(shape) {
1704
+ if (shape.kind !== "seq") return null;
1705
+ const out = [];
1706
+ for (const p of shape.parts) {
1707
+ if (p.part === "lit") {
1708
+ if (p.optional) return null;
1709
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1710
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1711
+ out.push({ ranges: p.ranges, negated: p.negated });
1712
+ } else {
1713
+ return null;
1714
+ }
1715
+ }
1716
+ return out.length ? out : null;
1717
+ }
1718
+ function altFixedMutuallyExclusive(shape) {
1719
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1720
+ const lists = [];
1721
+ for (const arm of shape.arms) {
1722
+ const f = fixedClassSeq(arm);
1723
+ if (!f) return false;
1724
+ lists.push(f);
1725
+ }
1726
+ for (let i = 0; i < lists.length; i++) {
1727
+ for (let j = 0; j < lists.length; j++) {
1728
+ if (i === j) continue;
1729
+ const a = lists[i];
1730
+ const b = lists[j];
1731
+ if (a.length > b.length) continue;
1732
+ let disjointAt = false;
1733
+ for (let p = 0; p < a.length; p++) {
1734
+ if (classDisjoint(a[p], b[p])) {
1735
+ disjointAt = true;
1736
+ break;
1737
+ }
1738
+ }
1739
+ if (!disjointAt) return false;
1740
+ }
1741
+ }
1742
+ return true;
1743
+ }
1744
+ function groupPartExposure(part) {
1745
+ const innerWiggle = trailingBacktrackClass(part.inner);
1746
+ if (innerWiggle === "unsupported") return "unsupported";
1747
+ const requiredOnce = !part.unbounded && part.min === 1;
1748
+ if (requiredOnce) return innerWiggle;
1749
+ const dropExposed = shapeFirstAccept(part.inner);
1750
+ if (dropExposed === "any") return "unsupported";
1751
+ if (innerWiggle === null) return dropExposed;
1752
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1753
+ return unioned === "any" ? "unsupported" : unioned;
1754
+ }
1695
1755
  function trailingBacktrackClass(shape) {
1696
1756
  if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1697
1757
  if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
@@ -1703,18 +1763,10 @@ function trailingBacktrackClass(shape) {
1703
1763
  if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1704
1764
  return null;
1705
1765
  }
1706
- const innerWiggle = trailingBacktrackClass(last.inner);
1707
- if (innerWiggle === "unsupported") return "unsupported";
1708
- const requiredOnce = !last.unbounded && last.min === 1;
1709
- if (requiredOnce) return innerWiggle;
1710
- const dropExposed = shapeFirstAccept(last.inner);
1711
- if (dropExposed === "any") return "unsupported";
1712
- if (innerWiggle === null) return dropExposed;
1713
- const unioned = unionCharSets([innerWiggle, dropExposed]);
1714
- return unioned === "any" ? "unsupported" : unioned;
1766
+ return groupPartExposure(last);
1715
1767
  }
1716
1768
  if (shape.kind === "alt") {
1717
- if (!shape.disjoint) return "unsupported";
1769
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1718
1770
  const classes = shape.arms.map(trailingBacktrackClass);
1719
1771
  if (classes.some((c) => c === "unsupported")) return "unsupported";
1720
1772
  const concrete = classes.filter((c) => c !== null);
@@ -1854,7 +1906,7 @@ function seqFirstAccept(parts) {
1854
1906
  function groupInnerSafe(shape) {
1855
1907
  if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1856
1908
  if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1857
- if (shape.kind === "alt") return shape.disjoint;
1909
+ if (shape.kind === "alt") return true;
1858
1910
  return false;
1859
1911
  }
1860
1912
  function shapeFirstAccept(shape) {
@@ -1882,9 +1934,8 @@ function shapeFirstAccept(shape) {
1882
1934
  return shapeFirstAccept(shape.inner);
1883
1935
  // zero-width — doesn't change first-char acceptance
1884
1936
  case "alt": {
1885
- if (!shape.disjoint) return "any";
1886
- const sets = shape.firsts.filter((f) => f !== null);
1887
- return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1937
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
1938
+ return unionCharSets(sets);
1888
1939
  }
1889
1940
  }
1890
1941
  }
@@ -1925,6 +1976,120 @@ function parseScanShape(source) {
1925
1976
  }
1926
1977
  return parseScanShapeCore(source);
1927
1978
  }
1979
+ function splitDelimArms(body) {
1980
+ const arms = [];
1981
+ let depth = 0;
1982
+ let last = 0;
1983
+ let i = 0;
1984
+ while (i < body.length) {
1985
+ const ch = body[i];
1986
+ if (ch === "\\") {
1987
+ i += 2;
1988
+ continue;
1989
+ }
1990
+ if (ch === "[") {
1991
+ let j = i + 1;
1992
+ if (body[j] === "^") j++;
1993
+ while (j < body.length && body[j] !== "]") {
1994
+ if (body[j] === "\\") j += 2;
1995
+ else j++;
1996
+ }
1997
+ if (body[j] !== "]") return null;
1998
+ i = j + 1;
1999
+ continue;
2000
+ }
2001
+ if (ch === "(") {
2002
+ depth++;
2003
+ i++;
2004
+ continue;
2005
+ }
2006
+ if (ch === ")") {
2007
+ depth--;
2008
+ if (depth < 0) return null;
2009
+ i++;
2010
+ continue;
2011
+ }
2012
+ if (ch === "|" && depth === 0) {
2013
+ arms.push(body.slice(last, i));
2014
+ last = i + 1;
2015
+ }
2016
+ i++;
2017
+ }
2018
+ if (depth !== 0) return null;
2019
+ arms.push(body.slice(last));
2020
+ return arms;
2021
+ }
2022
+ function delimitedBodySound(body, close) {
2023
+ const l0 = close[0];
2024
+ if (close.length > 2) return false;
2025
+ const arms = splitDelimArms(body);
2026
+ if (!arms) return false;
2027
+ let bulkSeen = false;
2028
+ let guardSeen = false;
2029
+ for (const arm of arms) {
2030
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
2031
+ if (bulk) {
2032
+ if (bulkSeen) return false;
2033
+ const ranges = parseClassRanges(bulk[1]);
2034
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2035
+ bulkSeen = true;
2036
+ continue;
2037
+ }
2038
+ if (close.length === 2 && arm.endsWith(")")) {
2039
+ const idx = arm.indexOf("(?!");
2040
+ if (idx === -1) return false;
2041
+ const prefix = literalCodePoints(arm.slice(0, idx));
2042
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2043
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2044
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2045
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2046
+ guardSeen = true;
2047
+ continue;
2048
+ }
2049
+ return false;
2050
+ }
2051
+ return bulkSeen && guardSeen === (close.length === 2);
2052
+ }
2053
+ function parseDelimited(source) {
2054
+ if (source.includes("\\\\")) return null;
2055
+ let i = 0;
2056
+ while (i < source.length && source[i] !== "(") {
2057
+ i += source[i] === "\\" ? 2 : 1;
2058
+ }
2059
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2060
+ const openFrag = source.slice(0, i);
2061
+ let k = i + 3;
2062
+ let depth = 1;
2063
+ while (k < source.length && depth > 0) {
2064
+ const ch = source[k];
2065
+ if (ch === "\\") {
2066
+ k += 2;
2067
+ continue;
2068
+ }
2069
+ if (ch === "[") {
2070
+ let j = k + 1;
2071
+ if (source[j] === "^") j++;
2072
+ while (j < source.length && source[j] !== "]") {
2073
+ if (source[j] === "\\") j += 2;
2074
+ else j++;
2075
+ }
2076
+ if (source[j] !== "]") return null;
2077
+ k = j + 1;
2078
+ continue;
2079
+ }
2080
+ if (ch === "(") depth++;
2081
+ else if (ch === ")") depth--;
2082
+ k++;
2083
+ }
2084
+ if (depth !== 0) return null;
2085
+ const body = source.slice(i + 3, k - 1);
2086
+ if (source[k] !== "*") return null;
2087
+ const closeFrag = source.slice(k + 1);
2088
+ const open = literalCodePoints(openFrag);
2089
+ const close = literalCodePoints(closeFrag);
2090
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2091
+ return { kind: "delimited", open, close };
2092
+ }
1928
2093
  function parseScanShapeCore(source) {
1929
2094
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1930
2095
  if (m && !m[1].startsWith("^")) {
@@ -1949,14 +2114,8 @@ function parseScanShapeCore(source) {
1949
2114
  const stop = parseClassRanges(m[2]);
1950
2115
  if (open && stop) return { kind: "until", open, stop };
1951
2116
  }
1952
- if (!source.includes("\\\\")) {
1953
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1954
- if (m && m[1] && m[2]) {
1955
- const open = literalCodePoints(m[1]);
1956
- const close = literalCodePoints(m[2]);
1957
- if (open && close) return { kind: "delimited", open, close };
1958
- }
1959
- }
2117
+ const delim = parseDelimited(source);
2118
+ if (delim) return delim;
1960
2119
  const parts = parseSeqParts(source);
1961
2120
  if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1962
2121
  return null;
@@ -2411,6 +2570,7 @@ function looseMkBuildRe(type) {
2411
2570
  );
2412
2571
  }
2413
2572
  function analyzeMkInlineBuild(def) {
2573
+ if (!def.build) return null;
2414
2574
  const src = (def.buildSrc ?? def.build.toString()).trim();
2415
2575
  const strict = src.match(MK_BUILD_RE);
2416
2576
  if (strict) {
@@ -2508,6 +2668,15 @@ function v(ctx, prefix = "_v") {
2508
2668
  function ind(ctx) {
2509
2669
  return " ".repeat(ctx.indent);
2510
2670
  }
2671
+ function nsp(ctx) {
2672
+ return ctx.ns ?? "";
2673
+ }
2674
+ function mfRef(ctx) {
2675
+ return `${nsp(ctx)}_mf`;
2676
+ }
2677
+ function buildRef(ctx) {
2678
+ return `${nsp(ctx)}_build`;
2679
+ }
2511
2680
  function reindentStmts(stmts, targetLevels) {
2512
2681
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
2513
2682
  if (nonEmpty.length === 0) return stmts;
@@ -2524,7 +2693,7 @@ function failReturnArr(expectedArr, posExpr) {
2524
2693
  function hoistExpected(ctx, constArrSource) {
2525
2694
  let name = ctx.expectedMap.get(constArrSource);
2526
2695
  if (name === void 0) {
2527
- name = `_fx${ctx.expectedDecls.length}`;
2696
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2528
2697
  ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2529
2698
  ctx.expectedMap.set(constArrSource, name);
2530
2699
  }
@@ -2623,13 +2792,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2623
2792
  `${i}}`
2624
2793
  ];
2625
2794
  }
2626
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2795
+ function ensureRegexDecl(ctx, source, flags) {
2627
2796
  const f = "y" + flags.replace(/[gy]/g, "");
2628
- const key = `${optimizedSource}/${f}`;
2797
+ const key = `${source}/${f}`;
2629
2798
  let rName = ctx.regexMap.get(key);
2630
2799
  if (rName === void 0) {
2631
- rName = `_re${ctx.regexDecls.length}`;
2632
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2800
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2801
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2633
2802
  ctx.regexMap.set(key, rName);
2634
2803
  }
2635
2804
  return rName;
@@ -2638,7 +2807,7 @@ function ensureTriviaFn(ctx) {
2638
2807
  const trivia2 = ctx.activeTrivia;
2639
2808
  const existing = ctx.triviaFnNames.get(trivia2);
2640
2809
  if (existing) return existing;
2641
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2810
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2642
2811
  ctx.triviaFnNames.set(trivia2, fnName);
2643
2812
  ctx.triviaCaptureNames.set(trivia2, fnName);
2644
2813
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2660,7 +2829,7 @@ function ensureTriviaFn(ctx) {
2660
2829
  for (const arm of regexSpec.arms) {
2661
2830
  const def = arm.parser._def;
2662
2831
  if (def.tag !== "regex") break;
2663
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2832
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2664
2833
  }
2665
2834
  if (reNames.length === regexSpec.arms.length) {
2666
2835
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -2985,7 +3154,7 @@ function emitKeywords(def, ctx, pos) {
2985
3154
  const key = `${source}/${flags}`;
2986
3155
  let rName = ctx.regexMap.get(key);
2987
3156
  if (rName === void 0) {
2988
- rName = `_re${ctx.regexDecls.length}`;
3157
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2989
3158
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
2990
3159
  ctx.regexMap.set(key, rName);
2991
3160
  }
@@ -3019,11 +3188,11 @@ function emitRegex(def, ctx, pos) {
3019
3188
  }
3020
3189
  }
3021
3190
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3022
- const key = `${def.optimizedSource}/${flags}`;
3191
+ const key = `${def.source}/${flags}`;
3023
3192
  let rName = ctx.regexMap.get(key);
3024
3193
  if (rName === void 0) {
3025
- rName = `_re${ctx.regexDecls.length}`;
3026
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3194
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3195
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
3027
3196
  ctx.regexMap.set(key, rName);
3028
3197
  }
3029
3198
  const mv = v(ctx, "_m");
@@ -3200,7 +3369,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3200
3369
  const reKey = `${regexDef.source}/${cleanFlags}`;
3201
3370
  let reVar = ctx.regexMap.get(reKey);
3202
3371
  if (reVar === void 0) {
3203
- reVar = `_re${ctx.regexDecls.length}`;
3372
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3204
3373
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
3205
3374
  ctx.regexMap.set(reKey, reVar);
3206
3375
  }
@@ -3293,7 +3462,7 @@ function emitFirstMatch(def, ctx, pos) {
3293
3462
  if (gate) {
3294
3463
  const gateIdx = ctx.mapFns.length;
3295
3464
  ctx.mapFns.push(gate);
3296
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3465
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3297
3466
  }
3298
3467
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3299
3468
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -3360,7 +3529,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3360
3529
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
3361
3530
  const vv = v(ctx);
3362
3531
  return {
3363
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3532
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3364
3533
  valueVar: vv,
3365
3534
  endVar: endV
3366
3535
  };
@@ -3588,16 +3757,17 @@ function emitNot(def, ctx, pos) {
3588
3757
  };
3589
3758
  }
3590
3759
  function emitNode(def, ctx, pos) {
3591
- const mkType = analyzeMkInlineBuild(def);
3760
+ const structural = def.build === void 0;
3761
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
3592
3762
  let buildIdx = null;
3593
- if (!mkType) {
3763
+ if (!mkType && !structural) {
3594
3764
  buildIdx = ctx.buildFns.length;
3595
3765
  ctx.buildFns.push(def.build);
3596
3766
  ctx.buildSrcs.push(def.buildSrc ?? null);
3597
3767
  }
3598
3768
  const i = ind(ctx);
3599
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
3600
- const clonesState = buildReadsState(def);
3769
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3770
+ const clonesState = structural || buildReadsState(def);
3601
3771
  const chV = v(ctx, "_ch");
3602
3772
  const rawV = v(ctx, "_raw");
3603
3773
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -3620,7 +3790,8 @@ function emitNode(def, ctx, pos) {
3620
3790
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3621
3791
  }
3622
3792
  const ndV = v(ctx, "_nd");
3623
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3793
+ 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})`;
3794
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
3624
3795
  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;
3625
3796
  stmts.push(
3626
3797
  `${i}const ${ndV} = ${finalExpr}`,
@@ -3645,8 +3816,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
3645
3816
  return { stmts, valueVar: vv, endVar: ev };
3646
3817
  }
3647
3818
  function emitLazy(p, def, ctx, pos) {
3819
+ const canonical = ctx.ruleNames?.get(p);
3648
3820
  const usage = ctx.lazyUsage;
3649
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3821
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3650
3822
  let resolved;
3651
3823
  try {
3652
3824
  resolved = def.thunk();
@@ -3656,7 +3828,7 @@ function emitLazy(p, def, ctx, pos) {
3656
3828
  return emit(resolved, ctx, pos);
3657
3829
  }
3658
3830
  if (!ctx.namedParsers.has(p)) {
3659
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3831
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
3660
3832
  ctx.namedParsers.set(p, fnName2);
3661
3833
  let resolved;
3662
3834
  try {
@@ -3774,7 +3946,7 @@ function emit(p, ctx, pos) {
3774
3946
  return {
3775
3947
  stmts: [
3776
3948
  ...inner.stmts,
3777
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3949
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3778
3950
  ],
3779
3951
  valueVar: mv,
3780
3952
  endVar: inner.endVar
@@ -3837,7 +4009,7 @@ function emit(p, ctx, pos) {
3837
4009
  const vv = v(ctx);
3838
4010
  return {
3839
4011
  stmts: [
3840
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
4012
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3841
4013
  `${ind(ctx)}const ${vv} = null`
3842
4014
  ],
3843
4015
  valueVar: vv,
@@ -3870,7 +4042,7 @@ function emit(p, ctx, pos) {
3870
4042
  const ev = v(ctx, "_wce");
3871
4043
  return {
3872
4044
  stmts: [
3873
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4045
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3874
4046
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3875
4047
  // withCtx runs on a spread child ctx — copy its recorded failure back
3876
4048
  // and propagate the inner failure verbatim (interpreter parity).
@@ -4001,6 +4173,39 @@ function analyzeLazyUsageMulti(roots) {
4001
4173
  for (const root of roots) walk2(root);
4002
4174
  return { counts, recursive };
4003
4175
  }
4176
+ function ruleDependencies(ruleMap) {
4177
+ const nameOf = /* @__PURE__ */ new Map();
4178
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4179
+ const deps = /* @__PURE__ */ new Map();
4180
+ for (const [name, comb] of ruleMap) {
4181
+ const found = /* @__PURE__ */ new Set();
4182
+ const seen = /* @__PURE__ */ new Set();
4183
+ const walk2 = (p, isRoot) => {
4184
+ const def = p._def;
4185
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4186
+ if (!isRoot && boundary !== void 0) {
4187
+ found.add(boundary);
4188
+ return;
4189
+ }
4190
+ if (seen.has(p)) return;
4191
+ seen.add(p);
4192
+ if (def.tag === "lazy") {
4193
+ let resolved;
4194
+ try {
4195
+ resolved = def.thunk();
4196
+ } catch {
4197
+ return;
4198
+ }
4199
+ walk2(resolved, false);
4200
+ return;
4201
+ }
4202
+ for (const child of childrenOf(def)) walk2(child, false);
4203
+ };
4204
+ walk2(comb, true);
4205
+ deps.set(name, [...found]);
4206
+ }
4207
+ return deps;
4208
+ }
4004
4209
  function compile(combinator, mapFnSources) {
4005
4210
  const ctx = {
4006
4211
  vars: 0,
@@ -4106,6 +4311,15 @@ function compileRuleMap(ruleMap) {
4106
4311
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4107
4312
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule))
4108
4313
  };
4314
+ const seenNames = /* @__PURE__ */ new Map();
4315
+ const ruleNames = /* @__PURE__ */ new Map();
4316
+ for (const [key, rule] of ruleMap) {
4317
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4318
+ const n = seenNames.get(base) ?? 0;
4319
+ seenNames.set(base, n + 1);
4320
+ ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4321
+ }
4322
+ ctx.ruleNames = ruleNames;
4109
4323
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4110
4324
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4111
4325
  ` : "";
@@ -4115,8 +4329,8 @@ function compileRuleMap(ruleMap) {
4115
4329
  const mfCovered = ctx.mapFns.length === 0 || derivedSrcs !== void 0;
4116
4330
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4117
4331
  if (!canInline) return null;
4118
- const mfDecl = derivedSrcs?.length ? ` const _mf = [${derivedSrcs.join(", ")}]` : "";
4119
- const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4332
+ const mfDecl = derivedSrcs?.length ? ` const ${mfRef(ctx)} = [${derivedSrcs.join(", ")}]` : "";
4333
+ const buildDecl = buildSources?.length ? ` const ${buildRef(ctx)} = [${buildSources.join(", ")}]` : "";
4120
4334
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4121
4335
  const hoistedDecls = [
4122
4336
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
@@ -4149,6 +4363,150 @@ function compileRuleMap(ruleMap) {
4149
4363
  ].join("\n");
4150
4364
  return { keys: perEntry.map((e) => e.key), replacement };
4151
4365
  }
4366
+ function compileLinkable(ruleMapArg, ns) {
4367
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4368
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4369
+ const d = val._def;
4370
+ if (d.tag !== "lazy") return true;
4371
+ try {
4372
+ d.thunk();
4373
+ return true;
4374
+ } catch {
4375
+ return false;
4376
+ }
4377
+ });
4378
+ const ctx = {
4379
+ vars: 0,
4380
+ indent: 1,
4381
+ regexDecls: [],
4382
+ regexMap: /* @__PURE__ */ new Map(),
4383
+ expectedDecls: [],
4384
+ expectedMap: /* @__PURE__ */ new Map(),
4385
+ recordFail: true,
4386
+ mapFns: [],
4387
+ mapFnSrcs: [],
4388
+ buildFns: [],
4389
+ buildSrcs: [],
4390
+ runtimeParsers: [],
4391
+ needsCollator: false,
4392
+ namedParsers: /* @__PURE__ */ new Map(),
4393
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4394
+ triviaFnNames: /* @__PURE__ */ new Map(),
4395
+ namedFnDecls: [],
4396
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4397
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4398
+ ns
4399
+ };
4400
+ const seen = /* @__PURE__ */ new Map();
4401
+ const ruleNames = /* @__PURE__ */ new Map();
4402
+ const fnNameToKey = /* @__PURE__ */ new Map();
4403
+ for (const [key, rule] of ruleMap) {
4404
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4405
+ const n = seen.get(base) ?? 0;
4406
+ seen.set(base, n + 1);
4407
+ const fn = n === 0 ? base : `${base}$${n}`;
4408
+ ruleNames.set(rule, fn);
4409
+ fnNameToKey.set(fn, key);
4410
+ }
4411
+ ctx.ruleNames = ruleNames;
4412
+ {
4413
+ const scanned = /* @__PURE__ */ new Set();
4414
+ const scanExternal = (p) => {
4415
+ if (scanned.has(p)) return;
4416
+ scanned.add(p);
4417
+ const def = p._def;
4418
+ if (def.tag === "lazy") {
4419
+ let resolved;
4420
+ try {
4421
+ resolved = def.thunk();
4422
+ } catch {
4423
+ resolved = void 0;
4424
+ }
4425
+ if (resolved === void 0) {
4426
+ const name = p._ruleName;
4427
+ if (name && !ctx.namedParsers.has(p)) {
4428
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4429
+ ruleNames.set(p, fn);
4430
+ ctx.namedParsers.set(p, fn);
4431
+ }
4432
+ return;
4433
+ }
4434
+ scanExternal(resolved);
4435
+ return;
4436
+ }
4437
+ for (const child of childrenOf(def)) scanExternal(child);
4438
+ };
4439
+ for (const [, rule] of ruleMap) scanExternal(rule);
4440
+ }
4441
+ const perEntry = [];
4442
+ for (const [key, rule] of ruleMap) {
4443
+ const fn = ruleNames.get(rule);
4444
+ if (!ctx.namedParsers.has(rule)) {
4445
+ ctx.namedParsers.set(rule, fn);
4446
+ let resolved = rule;
4447
+ const def = rule._def;
4448
+ if (def.tag === "lazy") {
4449
+ try {
4450
+ resolved = def.thunk();
4451
+ } catch {
4452
+ return null;
4453
+ }
4454
+ }
4455
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4456
+ ctx.indent = 1;
4457
+ ctx.failLabel = "_pfail";
4458
+ ctx.recordFail = true;
4459
+ const body = emit(resolved, ctx, "_pos");
4460
+ ctx.indent = savedIndent;
4461
+ ctx.failLabel = savedFail;
4462
+ ctx.recordFail = savedRec;
4463
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4464
+ }
4465
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4466
+ }
4467
+ if (ctx.runtimeParsers.length !== 0) return null;
4468
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4469
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4470
+ const ruleFns = /* @__PURE__ */ new Map();
4471
+ const privateFns = [];
4472
+ for (const decl of ctx.namedFnDecls) {
4473
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4474
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4475
+ if (key !== void 0) ruleFns.set(key, decl);
4476
+ else privateFns.push(decl);
4477
+ }
4478
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4479
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4480
+ const prelude = [
4481
+ ...ctx.regexDecls,
4482
+ ...ctx.expectedDecls,
4483
+ mfDecl,
4484
+ buildDecl,
4485
+ ...privateFns
4486
+ ].filter(Boolean);
4487
+ const wrappers = /* @__PURE__ */ new Map();
4488
+ for (const { key, r } of perEntry) {
4489
+ wrappers.set(key, [
4490
+ `function(input, _pos, _ctx) {`,
4491
+ ` let pos = _pos`,
4492
+ ...r.stmts,
4493
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4494
+ `}`
4495
+ ].join("\n"));
4496
+ }
4497
+ return {
4498
+ ns,
4499
+ keys: perEntry.map((e) => e.key),
4500
+ prelude,
4501
+ ruleFns,
4502
+ wrappers,
4503
+ deps: ruleDependencies(ruleMap),
4504
+ needsEmptyTl: !!ctx.needsEmptyTl,
4505
+ needsCollator: ctx.needsCollator,
4506
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4507
+ buildFns: buildSrcs ? [] : ctx.buildFns
4508
+ };
4509
+ }
4152
4510
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4153
4511
  const bodyLines = [
4154
4512
  ` let pos = _pos`,
@@ -4187,6 +4545,48 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4187
4545
  ].join("\n");
4188
4546
  }
4189
4547
 
4548
+ // src/compiler/linker.ts
4549
+ function fusedBody(pieces) {
4550
+ const winner = /* @__PURE__ */ new Map();
4551
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4552
+ for (const [k, p] of winner) {
4553
+ for (const d of p.deps.get(k) ?? []) {
4554
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4555
+ }
4556
+ }
4557
+ const contributing = new Set(winner.values());
4558
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4559
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4560
+ const lines = [
4561
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4562
+ "const _pfFail = {}",
4563
+ "let _pfEnd",
4564
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4565
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4566
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4567
+ ...[...contributing].flatMap((p) => p.prelude),
4568
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4569
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4570
+ ];
4571
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4572
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4573
+ const env = {};
4574
+ for (const p of contributing) {
4575
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4576
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4577
+ }
4578
+ return { body, env };
4579
+ }
4580
+ function emitFusedSource(pieces) {
4581
+ const { body, env } = fusedBody(pieces);
4582
+ if (Object.keys(env).length > 0) {
4583
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4584
+ }
4585
+ return `/* @__PURE__ */ (() => {
4586
+ ${body}
4587
+ })()`;
4588
+ }
4589
+
4190
4590
  // src/combinators/scanTo.ts
4191
4591
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4192
4592
  const meta = {
@@ -4281,6 +4681,12 @@ function escapeClassChar(ch) {
4281
4681
  return ch.replace(/[\\\]^-]/g, "\\$&");
4282
4682
  }
4283
4683
 
4684
+ // src/cst/offset-model.ts
4685
+ var { isFinite } = Number;
4686
+
4687
+ // src/index.ts
4688
+ registerRegexAnalyzer(firstSetFromRegex);
4689
+
4284
4690
  // src/plugin/evaluator.ts
4285
4691
  function isWordFactory(v2) {
4286
4692
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
@@ -4380,16 +4786,18 @@ function exprToCombi(node2, scope, code, mfs) {
4380
4786
  }
4381
4787
  if (callee.name === "node" && code !== void 0) {
4382
4788
  const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
4383
- if (!typeArg || !parserArg || !buildArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || buildArg.type === "SpreadElement") return null;
4789
+ if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
4384
4790
  const typeVal = anyValue(typeArg, scope, code);
4385
4791
  if (typeof typeVal !== "string") return null;
4386
4792
  const inner = anyValue(parserArg, scope, code, mfs);
4387
4793
  if (!isCombinator(inner)) return null;
4388
- const buildSrc = code.slice(buildArg.start, buildArg.end);
4794
+ const be = buildArg;
4795
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
4796
+ const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
4389
4797
  const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
4390
4798
  try {
4391
- const combi = node(typeVal, inner, () => null, collapse ? { collapse: true } : void 0);
4392
- if (combi._def.tag === "node") combi._def.buildSrc = buildSrc;
4799
+ const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
4800
+ if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
4393
4801
  return combi;
4394
4802
  } catch {
4395
4803
  return null;
@@ -4575,91 +4983,24 @@ function evaluateCombinatorArray(node2, scope, code) {
4575
4983
  if (!val.every(isCombinator)) return null;
4576
4984
  return val;
4577
4985
  }
4578
- function asFragmentFactory(expr) {
4579
- if (expr.type !== "ArrowFunctionExpression" && expr.type !== "FunctionExpression" && expr.type !== "FunctionDeclaration") return null;
4580
- const fn = expr;
4581
- return factoryBodyAndReturn(fn) ? fn : null;
4582
- }
4583
- var unwrapParen = (e) => e && e.type === "ParenthesizedExpression" ? e.expression : e;
4584
- function factoryBodyAndReturn(fn) {
4585
- const body = fn.body;
4586
- if (!body) return null;
4587
- const b = unwrapParen(body);
4588
- if (b.type === "ObjectExpression") return { statements: [], retObj: b };
4589
- if (b.type === "BlockStatement") {
4590
- const statements = [];
4591
- for (const stmt of b.body) {
4592
- if (stmt.type === "ReturnStatement") {
4593
- const a = unwrapParen(stmt.argument);
4594
- if (!a || a.type !== "ObjectExpression") return null;
4595
- return { statements, retObj: a };
4596
- }
4597
- if (stmt.type === "VariableDeclaration") statements.push(stmt);
4598
- else return null;
4599
- }
4600
- }
4601
- return null;
4602
- }
4603
- function spreadFactoryCall(spread, resolve) {
4604
- const call = spread.argument;
4605
- if (!call || call.type !== "CallExpression") return null;
4606
- if (!call.callee || call.callee.type !== "Identifier" || !call.callee.name) return null;
4607
- const resolved = resolve(call.callee.name);
4608
- return resolved ? { fn: resolved.fn, args: call.arguments ?? [], code: resolved.code } : null;
4609
- }
4610
- function factoryCallScope(fn, args, outer) {
4611
- const s = new Map(outer);
4612
- const params = fn.params ?? [];
4613
- for (let i = 0; i < params.length; i++) {
4614
- const pn = params[i];
4615
- if (!pn || pn.type !== "Identifier" || !pn.name) continue;
4616
- const arg = args[i];
4617
- if (arg && arg.type === "Identifier" && arg.name && outer.has(arg.name)) {
4618
- s.set(pn.name, outer.get(arg.name));
4619
- }
4620
- }
4621
- return s;
4622
- }
4623
4986
  function propKey(prop) {
4624
4987
  if (prop.type !== "Property" || prop.computed || !prop.key) return null;
4625
4988
  if (prop.key.type === "Identifier") return prop.key.name ?? null;
4626
4989
  if (prop.key.type === "Literal") return String(prop.key.value);
4627
4990
  return null;
4628
4991
  }
4629
- function collectRuleKeys(retObj, resolve, seen) {
4992
+ function collectRuleKeys(retObj) {
4630
4993
  const out = [];
4631
4994
  for (const prop of retObj.properties) {
4632
- if (prop.type === "SpreadElement") {
4633
- const sf = spreadFactoryCall(prop, resolve);
4634
- if (!sf || seen.has(sf.fn)) return null;
4635
- const br = factoryBodyAndReturn(sf.fn);
4636
- if (!br) return null;
4637
- const nested = collectRuleKeys(br.retObj, resolve, new Set(seen).add(sf.fn));
4638
- if (!nested) return null;
4639
- out.push(...nested);
4640
- continue;
4641
- }
4642
4995
  const key = propKey(prop);
4643
4996
  if (!key) return null;
4644
4997
  out.push(key);
4645
4998
  }
4646
4999
  return out;
4647
5000
  }
4648
- function flattenRuleEntries(retObj, scope, code, resolve, seen) {
5001
+ function flattenRuleEntries(retObj, scope, code) {
4649
5002
  const out = [];
4650
5003
  for (const prop of retObj.properties) {
4651
- if (prop.type === "SpreadElement") {
4652
- const sf = spreadFactoryCall(prop, resolve);
4653
- if (!sf || seen.has(sf.fn)) return null;
4654
- const br = factoryBodyAndReturn(sf.fn);
4655
- if (!br) return null;
4656
- const fragScope = factoryCallScope(sf.fn, sf.args, scope);
4657
- if (!evalBodyStatements(br.statements, fragScope, sf.code)) return null;
4658
- const nested = flattenRuleEntries(br.retObj, fragScope, sf.code, resolve, new Set(seen).add(sf.fn));
4659
- if (!nested) return null;
4660
- out.push(...nested);
4661
- continue;
4662
- }
4663
5004
  const key = propKey(prop);
4664
5005
  if (!key) return null;
4665
5006
  out.push({ key, value: prop.value, scope, code });
@@ -4685,7 +5026,7 @@ function evalBodyStatements(statements, scope, code) {
4685
5026
  }
4686
5027
  return true;
4687
5028
  }
4688
- function evaluateParserFactory(factoryNode, scope, code, mapFnSources, resolveFactory = () => null) {
5029
+ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
4689
5030
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
4690
5031
  const factory = factoryNode;
4691
5032
  const params = factory.params;
@@ -4717,16 +5058,36 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources, resolveFa
4717
5058
  if (!returnExpr) return null;
4718
5059
  const retObj = returnExpr.type === "ParenthesizedExpression" ? returnExpr.expression : returnExpr;
4719
5060
  if (retObj.type !== "ObjectExpression") return null;
4720
- const keys = collectRuleKeys(retObj, resolveFactory, /* @__PURE__ */ new Set());
5061
+ const keys = collectRuleKeys(retObj);
4721
5062
  if (!keys) return null;
4722
5063
  const ruleRefs = /* @__PURE__ */ new Map();
5064
+ const tagRef = (r, name) => {
5065
+ r._ruleName = name;
5066
+ };
4723
5067
  for (const key of keys) {
4724
- if (!ruleRefs.has(key)) ruleRefs.set(key, ref());
4725
- }
5068
+ if (!ruleRefs.has(key)) {
5069
+ const r = ref();
5070
+ tagRef(r, key);
5071
+ ruleRefs.set(key, r);
5072
+ }
5073
+ }
5074
+ const externalRefs = /* @__PURE__ */ new Map();
5075
+ const gProxy = new Proxy(Object.fromEntries(ruleRefs), {
5076
+ get(target, prop) {
5077
+ if (typeof prop !== "string" || prop in target) return target[prop];
5078
+ let ext = externalRefs.get(prop);
5079
+ if (!ext) {
5080
+ ext = ref();
5081
+ tagRef(ext, prop);
5082
+ externalRefs.set(prop, ext);
5083
+ }
5084
+ return ext;
5085
+ }
5086
+ });
4726
5087
  const localScope = new Map(scope);
4727
- localScope.set(proxyName, Object.fromEntries(ruleRefs));
5088
+ localScope.set(proxyName, gProxy);
4728
5089
  if (!evalBodyStatements(statements, localScope, code)) return null;
4729
- const entries = flattenRuleEntries(retObj, localScope, code, resolveFactory, /* @__PURE__ */ new Set());
5090
+ const entries = flattenRuleEntries(retObj, localScope, code);
4730
5091
  if (!entries) return null;
4731
5092
  const finalByKey = /* @__PURE__ */ new Map();
4732
5093
  for (const e of entries) finalByKey.set(e.key, e);
@@ -4788,19 +5149,14 @@ function referencesAny(node2, names, scope) {
4788
5149
  }
4789
5150
 
4790
5151
  // src/plugin/index.ts
5152
+ var import_node_crypto = require("node:crypto");
4791
5153
  var PARSEMAN_MODULE = "parseman";
4792
- var _fragmentResolver = null;
4793
- function getFragmentResolver() {
4794
- return _fragmentResolver ??= new import_oxc_resolver.ResolverFactory({
4795
- extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
4796
- // TS/NodeNext style: a `./x.js` import may point at `./x.ts` source.
4797
- extensionAlias: {
4798
- ".js": [".ts", ".tsx", ".js", ".jsx"],
4799
- ".mjs": [".mts", ".mjs"],
4800
- ".cjs": [".cts", ".cjs"]
4801
- },
4802
- conditionNames: ["source", "development", "import", "require", "default"],
4803
- mainFields: ["source", "module", "main"]
5154
+ var _compiledResolver = null;
5155
+ function getCompiledResolver() {
5156
+ return _compiledResolver ??= new import_oxc_resolver.ResolverFactory({
5157
+ extensions: [".js", ".mjs", ".cjs"],
5158
+ conditionNames: ["import", "require", "default"],
5159
+ mainFields: ["module", "main"]
4804
5160
  });
4805
5161
  }
4806
5162
  var _moduleParseCache = /* @__PURE__ */ new Map();
@@ -4847,6 +5203,77 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
4847
5203
  return { code: result.code, map: result.map };
4848
5204
  }
4849
5205
  }));
5206
+ function exportLocalName(body, exported) {
5207
+ for (const st of body) {
5208
+ if (st.type !== "ExportNamedDeclaration") continue;
5209
+ const decl = st.declaration;
5210
+ if (decl?.type === "VariableDeclaration") {
5211
+ for (const d of decl.declarations ?? []) {
5212
+ const idn = d.id;
5213
+ if (idn?.type === "Identifier" && idn.name === exported) return exported;
5214
+ }
5215
+ }
5216
+ for (const sp of st.specifiers ?? []) {
5217
+ const exp = sp.exported?.name;
5218
+ const loc = sp.local?.name;
5219
+ if (exp === exported && loc) return loc;
5220
+ }
5221
+ }
5222
+ return null;
5223
+ }
5224
+ function topLevelInit(body, name) {
5225
+ for (const st of body) {
5226
+ const vd = st.type === "VariableDeclaration" ? st : st.type === "ExportNamedDeclaration" && st.declaration?.type === "VariableDeclaration" ? st.declaration : null;
5227
+ if (!vd) continue;
5228
+ for (const d of vd.declarations ?? []) {
5229
+ const idn = d.id;
5230
+ if (idn?.type === "Identifier" && idn.name === name && d.init) return d.init;
5231
+ }
5232
+ }
5233
+ return null;
5234
+ }
5235
+ function isComposedPiecesSymbol(n) {
5236
+ if (!n || n.type !== "CallExpression") return false;
5237
+ const callee = n.callee;
5238
+ const obj = callee?.object?.name;
5239
+ const prop = callee?.property?.name;
5240
+ const arg0 = n.arguments?.[0];
5241
+ return obj === "Symbol" && prop === "for" && arg0?.value === "parseman.composedPieces";
5242
+ }
5243
+ function findCarriedPiecesLiteral(root) {
5244
+ let found = null;
5245
+ const visit = (n) => {
5246
+ if (found || !n || typeof n !== "object") return;
5247
+ if (Array.isArray(n)) {
5248
+ for (const c of n) visit(c);
5249
+ return;
5250
+ }
5251
+ const node2 = n;
5252
+ if (node2.type === "CallExpression") {
5253
+ const callee = node2.callee;
5254
+ const obj = callee?.object?.name;
5255
+ const prop = callee?.property?.name;
5256
+ const args = node2.arguments;
5257
+ if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5258
+ const descriptor = args[2];
5259
+ for (const p of descriptor?.properties ?? []) {
5260
+ const key = p.key;
5261
+ if ((key?.name === "value" || key?.value === "value") && p.value) {
5262
+ found = { start: p.value.start, end: p.value.end };
5263
+ return;
5264
+ }
5265
+ }
5266
+ }
5267
+ }
5268
+ for (const k in node2) {
5269
+ if (k === "type" || k === "start" || k === "end") continue;
5270
+ const v2 = node2[k];
5271
+ if (v2 && typeof v2 === "object") visit(v2);
5272
+ }
5273
+ };
5274
+ visit(root);
5275
+ return found;
5276
+ }
4850
5277
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
4851
5278
  let result;
4852
5279
  try {
@@ -4884,63 +5311,6 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4884
5311
  }
4885
5312
  if (macroImports.length === 0) return null;
4886
5313
  const scope = /* @__PURE__ */ new Map();
4887
- const factories = /* @__PURE__ */ new Map();
4888
- const resolver = getFragmentResolver();
4889
- const importedFactoryCache = /* @__PURE__ */ new Map();
4890
- const OUTPUT_DIR = /([\\/])(dist|lib|build|out|output|compiled|cjs|esm|es)([\\/])/;
4891
- const resolveSourcePath = (spec) => {
4892
- let resolved = null;
4893
- try {
4894
- resolved = resolver.resolveFileSync(id, spec).path ?? null;
4895
- } catch {
4896
- }
4897
- if (!resolved) return null;
4898
- if (OUTPUT_DIR.test(resolved)) {
4899
- const srcCandidate = resolved.replace(OUTPUT_DIR, "$1src$3").replace(/\.(c|m)?js$/, ".ts");
4900
- try {
4901
- if (fs.statSync(srcCandidate).isFile()) return srcCandidate;
4902
- } catch {
4903
- }
4904
- }
4905
- return resolved;
4906
- };
4907
- const resolveImportedFactory = (name) => {
4908
- if (importedFactoryCache.has(name)) return importedFactoryCache.get(name);
4909
- let result2 = null;
4910
- const binding = importBindings.get(name);
4911
- if (binding) {
4912
- const filePath = resolveSourcePath(binding.source);
4913
- const mod = filePath ? parseModuleCached(filePath) : null;
4914
- if (mod) {
4915
- for (const st of mod.body) {
4916
- if (st.type !== "ExportNamedDeclaration" || !st.declaration) continue;
4917
- const decl = st.declaration;
4918
- if (decl.type === "VariableDeclaration") {
4919
- for (const d of decl.declarations ?? []) {
4920
- if (d.id?.type === "Identifier" && d.id.name === binding.imported && d.init) {
4921
- const fac = asFragmentFactory(d.init);
4922
- if (fac) {
4923
- result2 = { fn: fac, code: mod.src };
4924
- break;
4925
- }
4926
- }
4927
- }
4928
- } else if (decl.type === "FunctionDeclaration" && decl.id?.type === "Identifier" && decl.id.name === binding.imported) {
4929
- const fac = asFragmentFactory(decl);
4930
- if (fac) result2 = { fn: fac, code: mod.src };
4931
- }
4932
- if (result2) break;
4933
- }
4934
- }
4935
- }
4936
- importedFactoryCache.set(name, result2);
4937
- return result2;
4938
- };
4939
- const resolveFactory = (n) => {
4940
- const local = factories.get(n);
4941
- if (local) return { fn: local, code };
4942
- return resolveImportedFactory(n);
4943
- };
4944
5314
  const replacements = [];
4945
5315
  const warnings = [];
4946
5316
  let anyUnresolved = false;
@@ -4961,7 +5331,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4961
5331
  warn(init.start, `${label2}: rules() needs a factory argument`);
4962
5332
  return null;
4963
5333
  }
4964
- const ruleMap = evaluateParserFactory(factoryArg, scope, code, [], resolveFactory);
5334
+ const ruleMap = evaluateParserFactory(factoryArg, scope, code, []);
4965
5335
  if (!ruleMap) {
4966
5336
  warn(init.start, `${label2}: rules(...) factory isn't statically evaluable`);
4967
5337
  return null;
@@ -4974,6 +5344,89 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4974
5344
  return { replacement: compiled.replacement, ruleMap };
4975
5345
  };
4976
5346
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
5347
+ const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5348
+ const localRuleMaps = /* @__PURE__ */ new Map();
5349
+ const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5350
+ const serializePieces = (p) => {
5351
+ const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5352
+ 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: [] }`;
5353
+ };
5354
+ const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5355
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
5356
+ const localComposedPieces = /* @__PURE__ */ new Map();
5357
+ const importedPiecesCache = /* @__PURE__ */ new Map();
5358
+ const importedPieces = (localName) => {
5359
+ if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5360
+ let result2 = null;
5361
+ const binding = importBindings.get(localName);
5362
+ if (binding) {
5363
+ let file = null;
5364
+ try {
5365
+ file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5366
+ } catch {
5367
+ }
5368
+ const mod = file ? parseModuleCached(file) : null;
5369
+ if (mod) {
5370
+ const localFor = exportLocalName(mod.body, binding.imported);
5371
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5372
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5373
+ if (literalRange) {
5374
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5375
+ try {
5376
+ result2 = new Function(`return (${literal2})`)();
5377
+ } catch {
5378
+ result2 = null;
5379
+ }
5380
+ }
5381
+ }
5382
+ }
5383
+ importedPiecesCache.set(localName, result2);
5384
+ return result2;
5385
+ };
5386
+ const argPieces = (arg, label2) => {
5387
+ if (isRulesCall(arg)) {
5388
+ const factory = arg.arguments[0];
5389
+ const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5390
+ const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5391
+ return p ? [p] : null;
5392
+ }
5393
+ if (arg.type === "Identifier") {
5394
+ const name = arg.name;
5395
+ const rm = localRuleMaps.get(name);
5396
+ if (rm) {
5397
+ const p = compileLinkable([...rm], nsFor(label2));
5398
+ return p ? [p] : null;
5399
+ }
5400
+ const composed = localComposedPieces.get(name);
5401
+ if (composed) return composed;
5402
+ return importedPieces(name);
5403
+ }
5404
+ return null;
5405
+ };
5406
+ const compileComposeCall = (init) => {
5407
+ const args = init.arguments;
5408
+ const arr = args[0];
5409
+ if (!arr || arr.type !== "ArrayExpression") {
5410
+ warn(init.start, "compose(): expected a static array of grammars/artifacts");
5411
+ return null;
5412
+ }
5413
+ const elements = arr.elements;
5414
+ const pieces = [];
5415
+ for (let i = 0; i < elements.length; i++) {
5416
+ const list = argPieces(elements[i], `compose${init.start}_${i}`);
5417
+ if (!list) {
5418
+ warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5419
+ return null;
5420
+ }
5421
+ pieces.push(...list);
5422
+ }
5423
+ try {
5424
+ return { replacement: emitFusedSource(pieces), pieces };
5425
+ } catch (e) {
5426
+ warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5427
+ return null;
5428
+ }
5429
+ };
4977
5430
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
4978
5431
  const refNames = /* @__PURE__ */ new Set();
4979
5432
  for (const stmt of body) {
@@ -5052,16 +5505,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5052
5505
  if (isRulesCall(init)) {
5053
5506
  const compiledRules = compileRulesFactory(init, varName);
5054
5507
  if (!compiledRules) continue;
5055
- replacements.push({
5056
- start: init.start,
5057
- end: init.end,
5058
- replacement: compiledRules.replacement
5059
- });
5508
+ localRuleMaps.set(varName, compiledRules.ruleMap);
5509
+ let replacement = compiledRules.replacement;
5510
+ if (exportPrefix) {
5511
+ const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
5512
+ if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5513
+ replacement = withCarriedPieces(replacement, [pieces]);
5514
+ }
5515
+ }
5516
+ replacements.push({ start: init.start, end: init.end, replacement });
5060
5517
  continue;
5061
5518
  }
5062
- const frag = asFragmentFactory(init);
5063
- if (frag) {
5064
- factories.set(varName, frag);
5519
+ if (isComposeCall(init)) {
5520
+ const fused = compileComposeCall(init);
5521
+ if (!fused) continue;
5522
+ localComposedPieces.set(varName, fused.pieces);
5523
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
5524
+ replacements.push({ start: init.start, end: init.end, replacement });
5065
5525
  continue;
5066
5526
  }
5067
5527
  const mapFnSources = [];