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
package/dist/index.js CHANGED
@@ -251,6 +251,36 @@ function literal(value, opts = {}) {
251
251
  }
252
252
 
253
253
  // src/combinators/regex.ts
254
+ var firstSetAnalyzer = null;
255
+ function registerRegexAnalyzer(analyzer) {
256
+ firstSetAnalyzer = analyzer;
257
+ }
258
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
259
+ function regex(pattern, flags = "") {
260
+ const source = typeof pattern === "string" ? pattern : pattern.source;
261
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
262
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
263
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
264
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
265
+ return {
266
+ _tag: "regex",
267
+ _meta: meta,
268
+ _def: { tag: "regex", source, flags: resolvedFlags },
269
+ parse(input, pos, ctx) {
270
+ anchored.lastIndex = pos;
271
+ const m = anchored.exec(input);
272
+ if (m === null) {
273
+ return failAt(ctx, [`/${source}/`], pos);
274
+ }
275
+ const span = { start: pos, end: pos + m[0].length };
276
+ const leaf = { _tag: "leaf", value: m[0], span };
277
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
278
+ return { ok: true, value: m[0], span };
279
+ }
280
+ };
281
+ }
282
+
283
+ // src/combinators/regex-analyze.ts
254
284
  import regexpTree from "regexp-tree";
255
285
  function firstSetFromRegex(pattern) {
256
286
  try {
@@ -355,40 +385,6 @@ function extractFirstSet(node2) {
355
385
  return { firstSet: any(), canMatchNewline: true };
356
386
  }
357
387
  }
358
- function optimizeRegex(source, flags) {
359
- try {
360
- const result = regexpTree.optimize(`/${source}/${flags}`);
361
- const str = result.toString();
362
- const lastSlash = str.lastIndexOf("/");
363
- return str.slice(1, lastSlash);
364
- } catch {
365
- return source;
366
- }
367
- }
368
- function regex(pattern, flags = "") {
369
- const source = typeof pattern === "string" ? pattern : pattern.source;
370
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
371
- const optimizedSource = optimizeRegex(source, resolvedFlags);
372
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
373
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
374
- const meta = { firstSet, canMatchNewline, isTrivia: false };
375
- return {
376
- _tag: "regex",
377
- _meta: meta,
378
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
379
- parse(input, pos, ctx) {
380
- anchored.lastIndex = pos;
381
- const m = anchored.exec(input);
382
- if (m === null) {
383
- return failAt(ctx, [`/${source}/`], pos);
384
- }
385
- const span = { start: pos, end: pos + m[0].length };
386
- const leaf = { _tag: "leaf", value: m[0], span };
387
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
388
- return { ok: true, value: m[0], span };
389
- }
390
- };
391
- }
392
388
 
393
389
  // src/combinators/keywords.ts
394
390
  function word(str, boundary = "_0-9A-Za-z") {
@@ -1086,7 +1082,9 @@ function rules(factory) {
1086
1082
  if (typeof key !== "string") return void 0;
1087
1083
  const record = target;
1088
1084
  if (!(key in record)) {
1089
- record[key] = ref();
1085
+ const r = ref();
1086
+ r._ruleName = key;
1087
+ record[key] = r;
1090
1088
  }
1091
1089
  return record[key];
1092
1090
  }
@@ -1151,12 +1149,14 @@ function confirmedBuildArity(src) {
1151
1149
  return parts.length;
1152
1150
  }
1153
1151
  function buildReadsTrivia(def) {
1152
+ if (!def.build) return true;
1154
1153
  const src = def.buildSrc ?? def.build.toString();
1155
1154
  const arity = confirmedBuildArity(src);
1156
1155
  if (arity === null) return true;
1157
1156
  return arity >= 4;
1158
1157
  }
1159
1158
  function buildReadsState(def) {
1159
+ if (!def.build) return true;
1160
1160
  const src = def.buildSrc ?? def.build.toString();
1161
1161
  const arity = confirmedBuildArity(src);
1162
1162
  if (arity === null) return true;
@@ -1175,8 +1175,8 @@ function node(type, combinator, build, opts) {
1175
1175
  };
1176
1176
  const collapse = opts?.collapse === true;
1177
1177
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1178
- const capturesTrivia = buildReadsTrivia(def);
1179
- const clonesState = buildReadsState(def);
1178
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1179
+ const clonesState = build ? buildReadsState(def) : true;
1180
1180
  return {
1181
1181
  _tag: "node",
1182
1182
  _meta: meta,
@@ -1187,13 +1187,8 @@ function node(type, combinator, build, opts) {
1187
1187
  const r = combinator.parse(input, pos, ctx);
1188
1188
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1189
1189
  if (!r.ok) return r;
1190
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1191
- children,
1192
- rawChildren,
1193
- r.span,
1194
- triviaLog,
1195
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1196
- );
1190
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1191
+ 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 };
1197
1192
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1198
1193
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1199
1194
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1368,8 +1363,14 @@ function staticExpected(c) {
1368
1363
  case "transform":
1369
1364
  case "not":
1370
1365
  return staticExpected(def.parser);
1371
- case "lazy":
1372
- return staticExpected(def.thunk());
1366
+ case "lazy": {
1367
+ const name = c._ruleName;
1368
+ try {
1369
+ return staticExpected(def.thunk());
1370
+ } catch {
1371
+ return name ? [name] : [];
1372
+ }
1373
+ }
1373
1374
  default:
1374
1375
  return [];
1375
1376
  }
@@ -1669,6 +1670,15 @@ function seqIsUnambiguous(parts) {
1669
1670
  const p = parts[k];
1670
1671
  const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1671
1672
  if (mandatory) hasMandatory = true;
1673
+ if (p.part === "group") {
1674
+ const exposure = groupPartExposure(p);
1675
+ if (exposure === "unsupported") {
1676
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1677
+ if (!trailingOnce) return false;
1678
+ } else if (exposure !== null) {
1679
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1680
+ }
1681
+ }
1672
1682
  const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1673
1683
  const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1674
1684
  if (skippableSingle || isUnboundedRepeat) {
@@ -1715,6 +1725,58 @@ function firstSetsDisjoint(a, b) {
1715
1725
  if (a === "any" || b === "any") return false;
1716
1726
  return classDisjoint(a, b);
1717
1727
  }
1728
+ function fixedClassSeq(shape) {
1729
+ if (shape.kind !== "seq") return null;
1730
+ const out = [];
1731
+ for (const p of shape.parts) {
1732
+ if (p.part === "lit") {
1733
+ if (p.optional) return null;
1734
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1735
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1736
+ out.push({ ranges: p.ranges, negated: p.negated });
1737
+ } else {
1738
+ return null;
1739
+ }
1740
+ }
1741
+ return out.length ? out : null;
1742
+ }
1743
+ function altFixedMutuallyExclusive(shape) {
1744
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1745
+ const lists = [];
1746
+ for (const arm of shape.arms) {
1747
+ const f = fixedClassSeq(arm);
1748
+ if (!f) return false;
1749
+ lists.push(f);
1750
+ }
1751
+ for (let i = 0; i < lists.length; i++) {
1752
+ for (let j = 0; j < lists.length; j++) {
1753
+ if (i === j) continue;
1754
+ const a = lists[i];
1755
+ const b = lists[j];
1756
+ if (a.length > b.length) continue;
1757
+ let disjointAt = false;
1758
+ for (let p = 0; p < a.length; p++) {
1759
+ if (classDisjoint(a[p], b[p])) {
1760
+ disjointAt = true;
1761
+ break;
1762
+ }
1763
+ }
1764
+ if (!disjointAt) return false;
1765
+ }
1766
+ }
1767
+ return true;
1768
+ }
1769
+ function groupPartExposure(part) {
1770
+ const innerWiggle = trailingBacktrackClass(part.inner);
1771
+ if (innerWiggle === "unsupported") return "unsupported";
1772
+ const requiredOnce = !part.unbounded && part.min === 1;
1773
+ if (requiredOnce) return innerWiggle;
1774
+ const dropExposed = shapeFirstAccept(part.inner);
1775
+ if (dropExposed === "any") return "unsupported";
1776
+ if (innerWiggle === null) return dropExposed;
1777
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1778
+ return unioned === "any" ? "unsupported" : unioned;
1779
+ }
1718
1780
  function trailingBacktrackClass(shape) {
1719
1781
  if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1720
1782
  if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
@@ -1726,18 +1788,10 @@ function trailingBacktrackClass(shape) {
1726
1788
  if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1727
1789
  return null;
1728
1790
  }
1729
- const innerWiggle = trailingBacktrackClass(last.inner);
1730
- if (innerWiggle === "unsupported") return "unsupported";
1731
- const requiredOnce = !last.unbounded && last.min === 1;
1732
- if (requiredOnce) return innerWiggle;
1733
- const dropExposed = shapeFirstAccept(last.inner);
1734
- if (dropExposed === "any") return "unsupported";
1735
- if (innerWiggle === null) return dropExposed;
1736
- const unioned = unionCharSets([innerWiggle, dropExposed]);
1737
- return unioned === "any" ? "unsupported" : unioned;
1791
+ return groupPartExposure(last);
1738
1792
  }
1739
1793
  if (shape.kind === "alt") {
1740
- if (!shape.disjoint) return "unsupported";
1794
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1741
1795
  const classes = shape.arms.map(trailingBacktrackClass);
1742
1796
  if (classes.some((c) => c === "unsupported")) return "unsupported";
1743
1797
  const concrete = classes.filter((c) => c !== null);
@@ -1877,7 +1931,7 @@ function seqFirstAccept(parts) {
1877
1931
  function groupInnerSafe(shape) {
1878
1932
  if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1879
1933
  if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1880
- if (shape.kind === "alt") return shape.disjoint;
1934
+ if (shape.kind === "alt") return true;
1881
1935
  return false;
1882
1936
  }
1883
1937
  function shapeFirstAccept(shape) {
@@ -1905,9 +1959,8 @@ function shapeFirstAccept(shape) {
1905
1959
  return shapeFirstAccept(shape.inner);
1906
1960
  // zero-width — doesn't change first-char acceptance
1907
1961
  case "alt": {
1908
- if (!shape.disjoint) return "any";
1909
- const sets = shape.firsts.filter((f) => f !== null);
1910
- return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1962
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
1963
+ return unionCharSets(sets);
1911
1964
  }
1912
1965
  }
1913
1966
  }
@@ -1948,6 +2001,120 @@ function parseScanShape(source) {
1948
2001
  }
1949
2002
  return parseScanShapeCore(source);
1950
2003
  }
2004
+ function splitDelimArms(body) {
2005
+ const arms = [];
2006
+ let depth = 0;
2007
+ let last = 0;
2008
+ let i = 0;
2009
+ while (i < body.length) {
2010
+ const ch = body[i];
2011
+ if (ch === "\\") {
2012
+ i += 2;
2013
+ continue;
2014
+ }
2015
+ if (ch === "[") {
2016
+ let j = i + 1;
2017
+ if (body[j] === "^") j++;
2018
+ while (j < body.length && body[j] !== "]") {
2019
+ if (body[j] === "\\") j += 2;
2020
+ else j++;
2021
+ }
2022
+ if (body[j] !== "]") return null;
2023
+ i = j + 1;
2024
+ continue;
2025
+ }
2026
+ if (ch === "(") {
2027
+ depth++;
2028
+ i++;
2029
+ continue;
2030
+ }
2031
+ if (ch === ")") {
2032
+ depth--;
2033
+ if (depth < 0) return null;
2034
+ i++;
2035
+ continue;
2036
+ }
2037
+ if (ch === "|" && depth === 0) {
2038
+ arms.push(body.slice(last, i));
2039
+ last = i + 1;
2040
+ }
2041
+ i++;
2042
+ }
2043
+ if (depth !== 0) return null;
2044
+ arms.push(body.slice(last));
2045
+ return arms;
2046
+ }
2047
+ function delimitedBodySound(body, close) {
2048
+ const l0 = close[0];
2049
+ if (close.length > 2) return false;
2050
+ const arms = splitDelimArms(body);
2051
+ if (!arms) return false;
2052
+ let bulkSeen = false;
2053
+ let guardSeen = false;
2054
+ for (const arm of arms) {
2055
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
2056
+ if (bulk) {
2057
+ if (bulkSeen) return false;
2058
+ const ranges = parseClassRanges(bulk[1]);
2059
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2060
+ bulkSeen = true;
2061
+ continue;
2062
+ }
2063
+ if (close.length === 2 && arm.endsWith(")")) {
2064
+ const idx = arm.indexOf("(?!");
2065
+ if (idx === -1) return false;
2066
+ const prefix = literalCodePoints(arm.slice(0, idx));
2067
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2068
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2069
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2070
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2071
+ guardSeen = true;
2072
+ continue;
2073
+ }
2074
+ return false;
2075
+ }
2076
+ return bulkSeen && guardSeen === (close.length === 2);
2077
+ }
2078
+ function parseDelimited(source) {
2079
+ if (source.includes("\\\\")) return null;
2080
+ let i = 0;
2081
+ while (i < source.length && source[i] !== "(") {
2082
+ i += source[i] === "\\" ? 2 : 1;
2083
+ }
2084
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2085
+ const openFrag = source.slice(0, i);
2086
+ let k = i + 3;
2087
+ let depth = 1;
2088
+ while (k < source.length && depth > 0) {
2089
+ const ch = source[k];
2090
+ if (ch === "\\") {
2091
+ k += 2;
2092
+ continue;
2093
+ }
2094
+ if (ch === "[") {
2095
+ let j = k + 1;
2096
+ if (source[j] === "^") j++;
2097
+ while (j < source.length && source[j] !== "]") {
2098
+ if (source[j] === "\\") j += 2;
2099
+ else j++;
2100
+ }
2101
+ if (source[j] !== "]") return null;
2102
+ k = j + 1;
2103
+ continue;
2104
+ }
2105
+ if (ch === "(") depth++;
2106
+ else if (ch === ")") depth--;
2107
+ k++;
2108
+ }
2109
+ if (depth !== 0) return null;
2110
+ const body = source.slice(i + 3, k - 1);
2111
+ if (source[k] !== "*") return null;
2112
+ const closeFrag = source.slice(k + 1);
2113
+ const open = literalCodePoints(openFrag);
2114
+ const close = literalCodePoints(closeFrag);
2115
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2116
+ return { kind: "delimited", open, close };
2117
+ }
1951
2118
  function parseScanShapeCore(source) {
1952
2119
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1953
2120
  if (m && !m[1].startsWith("^")) {
@@ -1972,14 +2139,8 @@ function parseScanShapeCore(source) {
1972
2139
  const stop = parseClassRanges(m[2]);
1973
2140
  if (open && stop) return { kind: "until", open, stop };
1974
2141
  }
1975
- if (!source.includes("\\\\")) {
1976
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1977
- if (m && m[1] && m[2]) {
1978
- const open = literalCodePoints(m[1]);
1979
- const close = literalCodePoints(m[2]);
1980
- if (open && close) return { kind: "delimited", open, close };
1981
- }
1982
- }
2142
+ const delim = parseDelimited(source);
2143
+ if (delim) return delim;
1983
2144
  const parts = parseSeqParts(source);
1984
2145
  if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1985
2146
  return null;
@@ -2434,6 +2595,7 @@ function looseMkBuildRe(type) {
2434
2595
  );
2435
2596
  }
2436
2597
  function analyzeMkInlineBuild(def) {
2598
+ if (!def.build) return null;
2437
2599
  const src = (def.buildSrc ?? def.build.toString()).trim();
2438
2600
  const strict = src.match(MK_BUILD_RE);
2439
2601
  if (strict) {
@@ -2531,6 +2693,15 @@ function v(ctx, prefix = "_v") {
2531
2693
  function ind(ctx) {
2532
2694
  return " ".repeat(ctx.indent);
2533
2695
  }
2696
+ function nsp(ctx) {
2697
+ return ctx.ns ?? "";
2698
+ }
2699
+ function mfRef(ctx) {
2700
+ return `${nsp(ctx)}_mf`;
2701
+ }
2702
+ function buildRef(ctx) {
2703
+ return `${nsp(ctx)}_build`;
2704
+ }
2534
2705
  function reindentStmts(stmts, targetLevels) {
2535
2706
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
2536
2707
  if (nonEmpty.length === 0) return stmts;
@@ -2547,7 +2718,7 @@ function failReturnArr(expectedArr, posExpr) {
2547
2718
  function hoistExpected(ctx, constArrSource) {
2548
2719
  let name = ctx.expectedMap.get(constArrSource);
2549
2720
  if (name === void 0) {
2550
- name = `_fx${ctx.expectedDecls.length}`;
2721
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2551
2722
  ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2552
2723
  ctx.expectedMap.set(constArrSource, name);
2553
2724
  }
@@ -2646,13 +2817,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2646
2817
  `${i}}`
2647
2818
  ];
2648
2819
  }
2649
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2820
+ function ensureRegexDecl(ctx, source, flags) {
2650
2821
  const f = "y" + flags.replace(/[gy]/g, "");
2651
- const key = `${optimizedSource}/${f}`;
2822
+ const key = `${source}/${f}`;
2652
2823
  let rName = ctx.regexMap.get(key);
2653
2824
  if (rName === void 0) {
2654
- rName = `_re${ctx.regexDecls.length}`;
2655
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2825
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2826
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2656
2827
  ctx.regexMap.set(key, rName);
2657
2828
  }
2658
2829
  return rName;
@@ -2661,7 +2832,7 @@ function ensureTriviaFn(ctx) {
2661
2832
  const trivia2 = ctx.activeTrivia;
2662
2833
  const existing = ctx.triviaFnNames.get(trivia2);
2663
2834
  if (existing) return existing;
2664
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2835
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2665
2836
  ctx.triviaFnNames.set(trivia2, fnName);
2666
2837
  ctx.triviaCaptureNames.set(trivia2, fnName);
2667
2838
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2683,7 +2854,7 @@ function ensureTriviaFn(ctx) {
2683
2854
  for (const arm of regexSpec.arms) {
2684
2855
  const def = arm.parser._def;
2685
2856
  if (def.tag !== "regex") break;
2686
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2857
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2687
2858
  }
2688
2859
  if (reNames.length === regexSpec.arms.length) {
2689
2860
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -3008,7 +3179,7 @@ function emitKeywords(def, ctx, pos) {
3008
3179
  const key = `${source}/${flags}`;
3009
3180
  let rName = ctx.regexMap.get(key);
3010
3181
  if (rName === void 0) {
3011
- rName = `_re${ctx.regexDecls.length}`;
3182
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3012
3183
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
3013
3184
  ctx.regexMap.set(key, rName);
3014
3185
  }
@@ -3042,11 +3213,11 @@ function emitRegex(def, ctx, pos) {
3042
3213
  }
3043
3214
  }
3044
3215
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3045
- const key = `${def.optimizedSource}/${flags}`;
3216
+ const key = `${def.source}/${flags}`;
3046
3217
  let rName = ctx.regexMap.get(key);
3047
3218
  if (rName === void 0) {
3048
- rName = `_re${ctx.regexDecls.length}`;
3049
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3219
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3220
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
3050
3221
  ctx.regexMap.set(key, rName);
3051
3222
  }
3052
3223
  const mv = v(ctx, "_m");
@@ -3223,7 +3394,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3223
3394
  const reKey = `${regexDef.source}/${cleanFlags}`;
3224
3395
  let reVar = ctx.regexMap.get(reKey);
3225
3396
  if (reVar === void 0) {
3226
- reVar = `_re${ctx.regexDecls.length}`;
3397
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3227
3398
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
3228
3399
  ctx.regexMap.set(reKey, reVar);
3229
3400
  }
@@ -3316,7 +3487,7 @@ function emitFirstMatch(def, ctx, pos) {
3316
3487
  if (gate) {
3317
3488
  const gateIdx = ctx.mapFns.length;
3318
3489
  ctx.mapFns.push(gate);
3319
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3490
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3320
3491
  }
3321
3492
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3322
3493
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -3383,7 +3554,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3383
3554
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
3384
3555
  const vv = v(ctx);
3385
3556
  return {
3386
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3557
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3387
3558
  valueVar: vv,
3388
3559
  endVar: endV
3389
3560
  };
@@ -3611,16 +3782,17 @@ function emitNot(def, ctx, pos) {
3611
3782
  };
3612
3783
  }
3613
3784
  function emitNode(def, ctx, pos) {
3614
- const mkType = analyzeMkInlineBuild(def);
3785
+ const structural = def.build === void 0;
3786
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
3615
3787
  let buildIdx = null;
3616
- if (!mkType) {
3788
+ if (!mkType && !structural) {
3617
3789
  buildIdx = ctx.buildFns.length;
3618
3790
  ctx.buildFns.push(def.build);
3619
3791
  ctx.buildSrcs.push(def.buildSrc ?? null);
3620
3792
  }
3621
3793
  const i = ind(ctx);
3622
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
3623
- const clonesState = buildReadsState(def);
3794
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3795
+ const clonesState = structural || buildReadsState(def);
3624
3796
  const chV = v(ctx, "_ch");
3625
3797
  const rawV = v(ctx, "_raw");
3626
3798
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -3643,7 +3815,8 @@ function emitNode(def, ctx, pos) {
3643
3815
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3644
3816
  }
3645
3817
  const ndV = v(ctx, "_nd");
3646
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3818
+ 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})`;
3819
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
3647
3820
  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;
3648
3821
  stmts.push(
3649
3822
  `${i}const ${ndV} = ${finalExpr}`,
@@ -3668,8 +3841,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
3668
3841
  return { stmts, valueVar: vv, endVar: ev };
3669
3842
  }
3670
3843
  function emitLazy(p, def, ctx, pos) {
3844
+ const canonical = ctx.ruleNames?.get(p);
3671
3845
  const usage = ctx.lazyUsage;
3672
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3846
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3673
3847
  let resolved;
3674
3848
  try {
3675
3849
  resolved = def.thunk();
@@ -3679,7 +3853,7 @@ function emitLazy(p, def, ctx, pos) {
3679
3853
  return emit(resolved, ctx, pos);
3680
3854
  }
3681
3855
  if (!ctx.namedParsers.has(p)) {
3682
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3856
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
3683
3857
  ctx.namedParsers.set(p, fnName2);
3684
3858
  let resolved;
3685
3859
  try {
@@ -3797,7 +3971,7 @@ function emit(p, ctx, pos) {
3797
3971
  return {
3798
3972
  stmts: [
3799
3973
  ...inner.stmts,
3800
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3974
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3801
3975
  ],
3802
3976
  valueVar: mv,
3803
3977
  endVar: inner.endVar
@@ -3860,7 +4034,7 @@ function emit(p, ctx, pos) {
3860
4034
  const vv = v(ctx);
3861
4035
  return {
3862
4036
  stmts: [
3863
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
4037
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3864
4038
  `${ind(ctx)}const ${vv} = null`
3865
4039
  ],
3866
4040
  valueVar: vv,
@@ -3893,7 +4067,7 @@ function emit(p, ctx, pos) {
3893
4067
  const ev = v(ctx, "_wce");
3894
4068
  return {
3895
4069
  stmts: [
3896
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4070
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3897
4071
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3898
4072
  // withCtx runs on a spread child ctx — copy its recorded failure back
3899
4073
  // and propagate the inner failure verbatim (interpreter parity).
@@ -4024,6 +4198,39 @@ function analyzeLazyUsageMulti(roots) {
4024
4198
  for (const root of roots) walk2(root);
4025
4199
  return { counts, recursive };
4026
4200
  }
4201
+ function ruleDependencies(ruleMap) {
4202
+ const nameOf = /* @__PURE__ */ new Map();
4203
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4204
+ const deps = /* @__PURE__ */ new Map();
4205
+ for (const [name, comb] of ruleMap) {
4206
+ const found = /* @__PURE__ */ new Set();
4207
+ const seen = /* @__PURE__ */ new Set();
4208
+ const walk2 = (p, isRoot) => {
4209
+ const def = p._def;
4210
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4211
+ if (!isRoot && boundary !== void 0) {
4212
+ found.add(boundary);
4213
+ return;
4214
+ }
4215
+ if (seen.has(p)) return;
4216
+ seen.add(p);
4217
+ if (def.tag === "lazy") {
4218
+ let resolved;
4219
+ try {
4220
+ resolved = def.thunk();
4221
+ } catch {
4222
+ return;
4223
+ }
4224
+ walk2(resolved, false);
4225
+ return;
4226
+ }
4227
+ for (const child of childrenOf(def)) walk2(child, false);
4228
+ };
4229
+ walk2(comb, true);
4230
+ deps.set(name, [...found]);
4231
+ }
4232
+ return deps;
4233
+ }
4027
4234
  function compile(combinator, mapFnSources) {
4028
4235
  const ctx = {
4029
4236
  vars: 0,
@@ -4107,6 +4314,150 @@ function compile(combinator, mapFnSources) {
4107
4314
  }
4108
4315
  };
4109
4316
  }
4317
+ function compileLinkable(ruleMapArg, ns) {
4318
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4319
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4320
+ const d = val._def;
4321
+ if (d.tag !== "lazy") return true;
4322
+ try {
4323
+ d.thunk();
4324
+ return true;
4325
+ } catch {
4326
+ return false;
4327
+ }
4328
+ });
4329
+ const ctx = {
4330
+ vars: 0,
4331
+ indent: 1,
4332
+ regexDecls: [],
4333
+ regexMap: /* @__PURE__ */ new Map(),
4334
+ expectedDecls: [],
4335
+ expectedMap: /* @__PURE__ */ new Map(),
4336
+ recordFail: true,
4337
+ mapFns: [],
4338
+ mapFnSrcs: [],
4339
+ buildFns: [],
4340
+ buildSrcs: [],
4341
+ runtimeParsers: [],
4342
+ needsCollator: false,
4343
+ namedParsers: /* @__PURE__ */ new Map(),
4344
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4345
+ triviaFnNames: /* @__PURE__ */ new Map(),
4346
+ namedFnDecls: [],
4347
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4348
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4349
+ ns
4350
+ };
4351
+ const seen = /* @__PURE__ */ new Map();
4352
+ const ruleNames = /* @__PURE__ */ new Map();
4353
+ const fnNameToKey = /* @__PURE__ */ new Map();
4354
+ for (const [key, rule] of ruleMap) {
4355
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4356
+ const n = seen.get(base) ?? 0;
4357
+ seen.set(base, n + 1);
4358
+ const fn = n === 0 ? base : `${base}$${n}`;
4359
+ ruleNames.set(rule, fn);
4360
+ fnNameToKey.set(fn, key);
4361
+ }
4362
+ ctx.ruleNames = ruleNames;
4363
+ {
4364
+ const scanned = /* @__PURE__ */ new Set();
4365
+ const scanExternal = (p) => {
4366
+ if (scanned.has(p)) return;
4367
+ scanned.add(p);
4368
+ const def = p._def;
4369
+ if (def.tag === "lazy") {
4370
+ let resolved;
4371
+ try {
4372
+ resolved = def.thunk();
4373
+ } catch {
4374
+ resolved = void 0;
4375
+ }
4376
+ if (resolved === void 0) {
4377
+ const name = p._ruleName;
4378
+ if (name && !ctx.namedParsers.has(p)) {
4379
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4380
+ ruleNames.set(p, fn);
4381
+ ctx.namedParsers.set(p, fn);
4382
+ }
4383
+ return;
4384
+ }
4385
+ scanExternal(resolved);
4386
+ return;
4387
+ }
4388
+ for (const child of childrenOf(def)) scanExternal(child);
4389
+ };
4390
+ for (const [, rule] of ruleMap) scanExternal(rule);
4391
+ }
4392
+ const perEntry = [];
4393
+ for (const [key, rule] of ruleMap) {
4394
+ const fn = ruleNames.get(rule);
4395
+ if (!ctx.namedParsers.has(rule)) {
4396
+ ctx.namedParsers.set(rule, fn);
4397
+ let resolved = rule;
4398
+ const def = rule._def;
4399
+ if (def.tag === "lazy") {
4400
+ try {
4401
+ resolved = def.thunk();
4402
+ } catch {
4403
+ return null;
4404
+ }
4405
+ }
4406
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4407
+ ctx.indent = 1;
4408
+ ctx.failLabel = "_pfail";
4409
+ ctx.recordFail = true;
4410
+ const body = emit(resolved, ctx, "_pos");
4411
+ ctx.indent = savedIndent;
4412
+ ctx.failLabel = savedFail;
4413
+ ctx.recordFail = savedRec;
4414
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4415
+ }
4416
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4417
+ }
4418
+ if (ctx.runtimeParsers.length !== 0) return null;
4419
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4420
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4421
+ const ruleFns = /* @__PURE__ */ new Map();
4422
+ const privateFns = [];
4423
+ for (const decl of ctx.namedFnDecls) {
4424
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4425
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4426
+ if (key !== void 0) ruleFns.set(key, decl);
4427
+ else privateFns.push(decl);
4428
+ }
4429
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4430
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4431
+ const prelude = [
4432
+ ...ctx.regexDecls,
4433
+ ...ctx.expectedDecls,
4434
+ mfDecl,
4435
+ buildDecl,
4436
+ ...privateFns
4437
+ ].filter(Boolean);
4438
+ const wrappers = /* @__PURE__ */ new Map();
4439
+ for (const { key, r } of perEntry) {
4440
+ wrappers.set(key, [
4441
+ `function(input, _pos, _ctx) {`,
4442
+ ` let pos = _pos`,
4443
+ ...r.stmts,
4444
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4445
+ `}`
4446
+ ].join("\n"));
4447
+ }
4448
+ return {
4449
+ ns,
4450
+ keys: perEntry.map((e) => e.key),
4451
+ prelude,
4452
+ ruleFns,
4453
+ wrappers,
4454
+ deps: ruleDependencies(ruleMap),
4455
+ needsEmptyTl: !!ctx.needsEmptyTl,
4456
+ needsCollator: ctx.needsCollator,
4457
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4458
+ buildFns: buildSrcs ? [] : ctx.buildFns
4459
+ };
4460
+ }
4110
4461
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4111
4462
  const bodyLines = [
4112
4463
  ` let pos = _pos`,
@@ -4145,6 +4496,87 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4145
4496
  ].join("\n");
4146
4497
  }
4147
4498
 
4499
+ // src/compiler/linker.ts
4500
+ var _nsCounter = 0;
4501
+ function linkable(rulesMap, ns) {
4502
+ const pieces = compileLinkable([...Object.entries(rulesMap)], ns ?? `_lk${_nsCounter++}_`);
4503
+ if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
4504
+ return pieces;
4505
+ }
4506
+ function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
4507
+ return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
4508
+ }
4509
+ function pick(grammar, names) {
4510
+ const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
4511
+ const keep = /* @__PURE__ */ new Set();
4512
+ const has = new Set(p.keys);
4513
+ for (const n of names) {
4514
+ if (!has.has(n)) throw new Error(`pick: rule "${n}" is not in this grammar (available: ${p.keys.join(", ")})`);
4515
+ }
4516
+ const visit = (n) => {
4517
+ if (keep.has(n) || !has.has(n)) return;
4518
+ keep.add(n);
4519
+ for (const d of p.deps.get(n) ?? []) visit(d);
4520
+ };
4521
+ for (const n of names) visit(n);
4522
+ const filt = (m) => new Map([...m].filter(([k]) => keep.has(k)));
4523
+ return {
4524
+ ...p,
4525
+ keys: [...keep],
4526
+ ruleFns: filt(p.ruleFns),
4527
+ wrappers: filt(p.wrappers),
4528
+ deps: filt(p.deps)
4529
+ };
4530
+ }
4531
+ function fusedBody(pieces) {
4532
+ const winner = /* @__PURE__ */ new Map();
4533
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4534
+ for (const [k, p] of winner) {
4535
+ for (const d of p.deps.get(k) ?? []) {
4536
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4537
+ }
4538
+ }
4539
+ const contributing = new Set(winner.values());
4540
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4541
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4542
+ const lines = [
4543
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4544
+ "const _pfFail = {}",
4545
+ "let _pfEnd",
4546
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4547
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4548
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4549
+ ...[...contributing].flatMap((p) => p.prelude),
4550
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4551
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4552
+ ];
4553
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4554
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4555
+ const env = {};
4556
+ for (const p of contributing) {
4557
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4558
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4559
+ }
4560
+ return { body, env };
4561
+ }
4562
+ function fuseRules(pieces) {
4563
+ const { body, env } = fusedBody(pieces);
4564
+ return new Function("_env", body)(env);
4565
+ }
4566
+ var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
4567
+ function itemPieces(item) {
4568
+ const carried = item[COMPOSED_PIECES];
4569
+ if (Array.isArray(carried)) return carried;
4570
+ if (item.ruleFns instanceof Map) return [item];
4571
+ return [linkable(item)];
4572
+ }
4573
+ function compose(items) {
4574
+ const pieces = items.flatMap(itemPieces);
4575
+ const map = fuseRules(pieces);
4576
+ Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
4577
+ return map;
4578
+ }
4579
+
4148
4580
  // src/combinators/guard.ts
4149
4581
  function guard(predicate) {
4150
4582
  const meta = {
@@ -4342,6 +4774,7 @@ function manyRecover(item, until) {
4342
4774
  }
4343
4775
 
4344
4776
  // src/functional/doc.ts
4777
+ var REENTRY_MAX_SPAN_FRACTION = 0.5;
4345
4778
  function isNode(x) {
4346
4779
  return typeof x === "object" && x !== null && x._tag === "node";
4347
4780
  }
@@ -4402,6 +4835,47 @@ function graftAndShift(root, path, newNode, delta) {
4402
4835
  children: newChildren
4403
4836
  };
4404
4837
  }
4838
+ function structurallyEqual(a, b) {
4839
+ if (a === b) return true;
4840
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
4841
+ const at = a._tag;
4842
+ const bt = b._tag;
4843
+ if (at !== bt) return false;
4844
+ const as = a.span;
4845
+ const bs = b.span;
4846
+ if (as || bs) {
4847
+ if (!as || !bs || as.start !== bs.start || as.end !== bs.end) return false;
4848
+ }
4849
+ if (at === "leaf" || at === "trivia") {
4850
+ return a.value === b.value;
4851
+ }
4852
+ if (a.type !== b.type) return false;
4853
+ const ac = a.children;
4854
+ const bc = b.children;
4855
+ if (ac || bc) {
4856
+ if (!ac || !bc || ac.length !== bc.length) return false;
4857
+ for (let i = 0; i < ac.length; i++) if (!structurallyEqual(ac[i], bc[i])) return false;
4858
+ }
4859
+ return true;
4860
+ }
4861
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
4862
+ if (!produced.ok) return false;
4863
+ if (boundary >= newInput.length) return true;
4864
+ for (const sentinel of [" ", "\uFFFF"]) {
4865
+ if (newInput[boundary] === sentinel) continue;
4866
+ const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
4867
+ const ctx = { trackLines: false, state, build };
4868
+ let r;
4869
+ try {
4870
+ r = ruleFn(probed, start, ctx);
4871
+ } catch {
4872
+ return false;
4873
+ }
4874
+ if (!r.ok || r.span.end !== produced.span.end) return false;
4875
+ if (!structurallyEqual(r.value, produced.value)) return false;
4876
+ }
4877
+ return true;
4878
+ }
4405
4879
  var ParseDocImpl = class _ParseDocImpl {
4406
4880
  _registry;
4407
4881
  _rootRule;
@@ -4433,20 +4907,24 @@ var ParseDocImpl = class _ParseDocImpl {
4433
4907
  }
4434
4908
  const rebuild = this._opts.rebuild ?? defaultRebuild;
4435
4909
  for (const { node: node2, path } of candidates) {
4910
+ if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
4436
4911
  const ruleFn = this._registry[node2.type];
4437
4912
  if (!ruleFn) continue;
4438
- const ctx = { trackLines: false, state: node2.state };
4913
+ if (!(node2.span.start <= from && to <= node2.span.end)) continue;
4914
+ const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
4439
4915
  const r = ruleFn(newInput, node2.span.start, ctx);
4440
4916
  if (!r.ok) continue;
4441
- if (r.span.end === node2.span.end + delta) {
4442
- if (delta === 0) {
4443
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
4444
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
4445
- }
4446
- if (this._opts.rebuild) return reparse();
4447
- const newTree = graftAndShift(this.tree, path, r.value, delta);
4448
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
4917
+ if (r.span.end !== node2.span.end + delta) continue;
4918
+ if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
4919
+ continue;
4920
+ }
4921
+ if (delta === 0) {
4922
+ const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
4923
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
4449
4924
  }
4925
+ if (this._opts.rebuild) return reparse();
4926
+ const newTree = graftAndShift(this.tree, path, r.value, delta);
4927
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
4450
4928
  }
4451
4929
  return reparse();
4452
4930
  }
@@ -4454,7 +4932,7 @@ var ParseDocImpl = class _ParseDocImpl {
4454
4932
  function parseDoc(registry, rootRule, input, opts = {}) {
4455
4933
  const ruleFn = registry[rootRule];
4456
4934
  if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
4457
- const ctx = { trackLines: false, state: opts.state };
4935
+ const ctx = { trackLines: false, state: opts.state, build: opts.build };
4458
4936
  const r = ruleFn(input, 0, ctx);
4459
4937
  if (r.ok) {
4460
4938
  return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
@@ -4462,12 +4940,45 @@ function parseDoc(registry, rootRule, input, opts = {}) {
4462
4940
  return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
4463
4941
  }
4464
4942
 
4943
+ // src/functional/run.ts
4944
+ var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
4945
+ function run(entry, input, options = {}) {
4946
+ const triviaLog = [];
4947
+ const errors = [];
4948
+ const ctx = {
4949
+ trackLines: false,
4950
+ _triviaLog: triviaLog,
4951
+ _errors: errors,
4952
+ build: options.build,
4953
+ state: options.state
4954
+ };
4955
+ const r = invoke(entry, input, 0, ctx);
4956
+ let unconsumedFrom = null;
4957
+ if (r.ok) {
4958
+ let pos = r.span?.end ?? 0;
4959
+ if (options.trivia && pos < input.length) {
4960
+ const t = invoke(options.trivia, input, pos, { trackLines: false });
4961
+ if (t.ok && t.span.end > pos) pos = t.span.end;
4962
+ }
4963
+ unconsumedFrom = pos < input.length ? pos : null;
4964
+ }
4965
+ return {
4966
+ ok: r.ok,
4967
+ value: r.ok ? r.value : void 0,
4968
+ span: r.span ?? { start: 0, end: 0 },
4969
+ expected: r.ok ? [] : r.expected ?? [],
4970
+ errors,
4971
+ triviaLog,
4972
+ unconsumedFrom
4973
+ };
4974
+ }
4975
+
4465
4976
  // src/cst/trivia-index.ts
4466
- function merge(map, key, run) {
4467
- if (run.length === 0) return;
4977
+ function merge(map, key, run2) {
4978
+ if (run2.length === 0) return;
4468
4979
  const existing = map.get(key);
4469
- if (existing) existing.push(...run);
4470
- else map.set(key, [...run]);
4980
+ if (existing) existing.push(...run2);
4981
+ else map.set(key, [...run2]);
4471
4982
  }
4472
4983
  function buildTriviaIndex(root, input = "", opts) {
4473
4984
  const before = /* @__PURE__ */ new Map();
@@ -4479,11 +4990,11 @@ function buildTriviaIndex(root, input = "", opts) {
4479
4990
  for (let i = 0; i < log.length; i += 3) {
4480
4991
  const tStart = log[i], tEnd = log[i + 1], insertIdx = log[i + 2];
4481
4992
  if (tEnd <= tStart) continue;
4482
- const run = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
4993
+ const run2 = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
4483
4994
  const next = raw[insertIdx];
4484
- if (next) merge(before, next.span.start, run);
4995
+ if (next) merge(before, next.span.start, run2);
4485
4996
  const prev = insertIdx > 0 ? raw[insertIdx - 1] : void 0;
4486
- if (prev) merge(after, prev.span.end, run);
4997
+ if (prev) merge(after, prev.span.end, run2);
4487
4998
  }
4488
4999
  }
4489
5000
  const ch = node2.children;
@@ -4500,17 +5011,17 @@ function buildTriviaIndex(root, input = "", opts) {
4500
5011
  const rootSpan = root?.span;
4501
5012
  const re = new RegExp(opts.trivia.source, opts.trivia.flags.replace(/[gy]/g, "") + "y");
4502
5013
  const scanFrom = (from) => {
4503
- const run = [];
5014
+ const run2 = [];
4504
5015
  let pos = from;
4505
5016
  re.lastIndex = pos;
4506
5017
  let m = re.exec(input);
4507
5018
  while (m && m.index === pos && m[0].length > 0) {
4508
- run.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
5019
+ run2.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
4509
5020
  pos += m[0].length;
4510
5021
  re.lastIndex = pos;
4511
5022
  m = re.exec(input);
4512
5023
  }
4513
- return run;
5024
+ return run2;
4514
5025
  };
4515
5026
  if (rootSpan && rootSpan.start > 0) {
4516
5027
  const lead = scanFrom(0);
@@ -4596,20 +5107,295 @@ function triviaEntries(log, labels, opts) {
4596
5107
  }
4597
5108
  };
4598
5109
  }
5110
+
5111
+ // src/cst/offset-model.ts
5112
+ var { isFinite } = Number;
5113
+ function floorIndex(arr, len, x) {
5114
+ let lo = 0;
5115
+ let hi = len - 1;
5116
+ let ans = -1;
5117
+ while (lo <= hi) {
5118
+ const mid = lo + hi >>> 1;
5119
+ if (arr[mid] <= x) {
5120
+ ans = mid;
5121
+ lo = mid + 1;
5122
+ } else {
5123
+ hi = mid - 1;
5124
+ }
5125
+ }
5126
+ return ans;
5127
+ }
5128
+ var OffsetIndex = class {
5129
+ /** Slot starts, sorted ascending. Parallel to `_ends`. */
5130
+ _starts;
5131
+ _ends;
5132
+ count;
5133
+ docStart;
5134
+ docEnd;
5135
+ constructor(slots, docStart = 0, docEnd) {
5136
+ const sorted = [...slots].sort((a, b) => a.start - b.start);
5137
+ const starts = [];
5138
+ const ends = [];
5139
+ let prevEnd = docStart;
5140
+ for (const s of sorted) {
5141
+ if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
5142
+ throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
5143
+ }
5144
+ if (s.start < prevEnd) {
5145
+ throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
5146
+ }
5147
+ starts.push(s.start);
5148
+ ends.push(s.end);
5149
+ prevEnd = s.end;
5150
+ }
5151
+ this._starts = starts;
5152
+ this._ends = ends;
5153
+ this.count = starts.length;
5154
+ this.docStart = docStart;
5155
+ this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
5156
+ }
5157
+ /** The slot at index `i` (0-based, source order), or undefined. */
5158
+ slot(i) {
5159
+ if (i < 0 || i >= this.count) return void 0;
5160
+ return { start: this._starts[i], end: this._ends[i] };
5161
+ }
5162
+ /**
5163
+ * Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
5164
+ * `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
5165
+ * An offset that lands inside a slot returns that slot's TRAILING gap index
5166
+ * (i.e. the gap after it) — callers querying at slot boundaries get the
5167
+ * intuitive answer via `gapBefore`/`gapAfter`.
5168
+ */
5169
+ gapIndexAt(offset) {
5170
+ const i = floorIndex(this._starts, this.count, offset);
5171
+ if (i < 0) return 0;
5172
+ if (offset < this._ends[i]) return i + 1;
5173
+ return i + 1;
5174
+ }
5175
+ /** The gap at index `i` in `[0, count]`. */
5176
+ gap(i) {
5177
+ if (i < 0 || i > this.count) return void 0;
5178
+ const start = i === 0 ? this.docStart : this._ends[i - 1];
5179
+ const end = i === this.count ? this.docEnd : this._starts[i];
5180
+ return { start, end };
5181
+ }
5182
+ /**
5183
+ * The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
5184
+ * `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
5185
+ * `docStart`). For a real slot start this is the gap preceding that slot; for an
5186
+ * adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
5187
+ */
5188
+ gapBefore(offset) {
5189
+ const e = floorIndex(this._ends, this.count, offset);
5190
+ const start = e >= 0 ? this._ends[e] : this.docStart;
5191
+ return { start, end: offset };
5192
+ }
5193
+ /**
5194
+ * The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
5195
+ * `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
5196
+ */
5197
+ gapAfter(offset) {
5198
+ return { start: offset, end: this._ceilStart(offset) };
5199
+ }
5200
+ /** Least slot start `>= offset`, else `docEnd`. */
5201
+ _ceilStart(offset) {
5202
+ let lo = 0;
5203
+ let hi = this.count - 1;
5204
+ let ans = this.docEnd;
5205
+ while (lo <= hi) {
5206
+ const mid = lo + hi >>> 1;
5207
+ if (this._starts[mid] >= offset) {
5208
+ ans = this._starts[mid];
5209
+ hi = mid - 1;
5210
+ } else {
5211
+ lo = mid + 1;
5212
+ }
5213
+ }
5214
+ return ans;
5215
+ }
5216
+ /** Is this gap empty (adjacent slots, no trivia)? */
5217
+ isEmpty(gap) {
5218
+ return gap.end <= gap.start;
5219
+ }
5220
+ };
5221
+ var NL = 10;
5222
+ var CR = 13;
5223
+ var SP = 32;
5224
+ var TAB = 9;
5225
+ function gapText(source, gap) {
5226
+ return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
5227
+ }
5228
+ function lineBreaksIn(source, gap) {
5229
+ let n = 0;
5230
+ for (let i = gap.start; i < gap.end; i++) {
5231
+ const c = source.charCodeAt(i);
5232
+ if (c === NL) n++;
5233
+ else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
5234
+ }
5235
+ return n;
5236
+ }
5237
+ function blankLinesIn(source, gap) {
5238
+ return Math.max(0, lineBreaksIn(source, gap) - 1);
5239
+ }
5240
+ function lineStartWithin(source, gap) {
5241
+ for (let i = gap.end - 1; i >= gap.start; i--) {
5242
+ const c = source.charCodeAt(i);
5243
+ if (c === NL || c === CR) return i + 1;
5244
+ }
5245
+ return gap.start;
5246
+ }
5247
+ function indentWidth(source, gap) {
5248
+ const ls = lineStartWithin(source, gap);
5249
+ if (ls === gap.start) return 0;
5250
+ return gap.end - ls;
5251
+ }
5252
+ function indentMixed(source, gap) {
5253
+ const ls = lineStartWithin(source, gap);
5254
+ let sawSpace = false;
5255
+ let sawTab = false;
5256
+ for (let i = ls; i < gap.end; i++) {
5257
+ const c = source.charCodeAt(i);
5258
+ if (c === SP) sawSpace = true;
5259
+ else if (c === TAB) sawTab = true;
5260
+ }
5261
+ return sawSpace && sawTab;
5262
+ }
5263
+ function commentsIn(source, gap, comment) {
5264
+ const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
5265
+ re.lastIndex = gap.start;
5266
+ const out = [];
5267
+ let m;
5268
+ while ((m = re.exec(source)) && m.index < gap.end) {
5269
+ if (m[0].length === 0) {
5270
+ re.lastIndex++;
5271
+ continue;
5272
+ }
5273
+ if (m.index + m[0].length > gap.end) break;
5274
+ out.push({ start: m.index, end: m.index + m[0].length });
5275
+ }
5276
+ return out;
5277
+ }
5278
+ function gapIsSignificant(source, gap, comment) {
5279
+ if (gap.end <= gap.start) return false;
5280
+ if (lineBreaksIn(source, gap) > 0) return true;
5281
+ if (comment && commentsIn(source, gap, comment).length > 0) return true;
5282
+ return false;
5283
+ }
5284
+ function collectLeafSlots(root) {
5285
+ const slots = [];
5286
+ const visit = (n) => {
5287
+ const kids = n.children;
5288
+ if (kids && kids.length) {
5289
+ for (const c of kids) visit(c);
5290
+ } else if (n.span) {
5291
+ slots.push({ start: n.span.start, end: n.span.end });
5292
+ }
5293
+ };
5294
+ if (root) visit(root);
5295
+ return slots;
5296
+ }
5297
+ function buildOffsetIndex(root, input = "") {
5298
+ return new OffsetIndex(collectLeafSlots(root), 0, input.length);
5299
+ }
5300
+
5301
+ // src/cst/relative-spans.ts
5302
+ function relativize(node2, parentBase = 0) {
5303
+ return {
5304
+ start: node2.start - parentBase,
5305
+ end: node2.end - parentBase,
5306
+ children: node2.children.map((c) => relativize(c, node2.start))
5307
+ };
5308
+ }
5309
+ function absolutize(node2, parentBase = 0) {
5310
+ const start = parentBase + node2.start;
5311
+ return {
5312
+ start,
5313
+ end: parentBase + node2.end,
5314
+ children: node2.children.map((c) => absolutize(c, start))
5315
+ };
5316
+ }
5317
+ function absoluteSpanAt(root, path) {
5318
+ let base = 0;
5319
+ let node2 = root;
5320
+ for (const idx of path) {
5321
+ base += node2.start;
5322
+ const child = node2.children[idx];
5323
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
5324
+ node2 = child;
5325
+ }
5326
+ return { start: base + node2.start, end: base + node2.end };
5327
+ }
5328
+ function shiftAbsolute(node2, at, delta) {
5329
+ const s = node2.start >= at ? node2.start + delta : node2.start;
5330
+ const e = node2.end >= at ? node2.end + delta : node2.end;
5331
+ return {
5332
+ start: s,
5333
+ end: e,
5334
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
5335
+ };
5336
+ }
5337
+ function applyEdit(root, at, delta, stats) {
5338
+ return editNode(root, at, delta, 0, 0, stats);
5339
+ }
5340
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
5341
+ const absStart = parentBase + node2.start;
5342
+ const absEnd = parentBase + node2.end;
5343
+ if (absEnd < at) return node2;
5344
+ const startShifts = absStart >= at;
5345
+ if (startShifts && parentShift === delta) return node2;
5346
+ if (stats) stats.allocated++;
5347
+ const newAbsStart = startShifts ? absStart + delta : absStart;
5348
+ const newAbsEnd = absEnd + delta;
5349
+ const newParentBase = parentBase + parentShift;
5350
+ const newStart = newAbsStart - newParentBase;
5351
+ const newEnd = newAbsEnd - newParentBase;
5352
+ const ownShift = newAbsStart - absStart;
5353
+ let changed = false;
5354
+ const newChildren = [];
5355
+ for (const c of node2.children) {
5356
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
5357
+ if (nc !== c) changed = true;
5358
+ newChildren.push(nc);
5359
+ }
5360
+ return {
5361
+ start: newStart,
5362
+ end: newEnd,
5363
+ children: changed ? newChildren : node2.children
5364
+ };
5365
+ }
5366
+
5367
+ // src/index.ts
5368
+ registerRegexAnalyzer(firstSetFromRegex);
4599
5369
  export {
5370
+ OffsetIndex,
5371
+ absoluteSpanAt,
5372
+ absolutize,
4600
5373
  annotateSpan,
5374
+ applyEdit,
4601
5375
  balanced,
5376
+ blankLinesIn,
4602
5377
  buildLineIndex,
5378
+ buildOffsetIndex,
4603
5379
  buildTriviaIndex,
4604
5380
  choice,
5381
+ collectLeafSlots,
5382
+ commentsIn,
4605
5383
  compile,
4606
5384
  completionsAt,
5385
+ compose,
4607
5386
  createVisitor,
5387
+ cstBuildHost,
4608
5388
  expect,
5389
+ gapIsSignificant,
5390
+ gapText,
4609
5391
  guard,
5392
+ indentMixed,
5393
+ indentWidth,
4610
5394
  isParseError,
4611
5395
  keywords,
4612
5396
  label,
5397
+ lineBreaksIn,
5398
+ lineStartWithin,
4613
5399
  literal,
4614
5400
  makeWord,
4615
5401
  many,
@@ -4623,14 +5409,18 @@ export {
4623
5409
  parse,
4624
5410
  parseDoc,
4625
5411
  parser,
5412
+ pick,
4626
5413
  recover,
4627
5414
  ref,
4628
5415
  regex,
5416
+ relativize,
4629
5417
  rules,
5418
+ run,
4630
5419
  scanTo,
4631
5420
  sepBy,
4632
5421
  sepByRecover,
4633
5422
  sequence,
5423
+ shiftAbsolute,
4634
5424
  skip,
4635
5425
  staticExpected,
4636
5426
  transform,