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.cjs CHANGED
@@ -30,19 +30,35 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ OffsetIndex: () => OffsetIndex,
34
+ absoluteSpanAt: () => absoluteSpanAt,
35
+ absolutize: () => absolutize,
33
36
  annotateSpan: () => annotateSpan,
37
+ applyEdit: () => applyEdit,
34
38
  balanced: () => balanced,
39
+ blankLinesIn: () => blankLinesIn,
35
40
  buildLineIndex: () => buildLineIndex,
41
+ buildOffsetIndex: () => buildOffsetIndex,
36
42
  buildTriviaIndex: () => buildTriviaIndex,
37
43
  choice: () => choice,
44
+ collectLeafSlots: () => collectLeafSlots,
45
+ commentsIn: () => commentsIn,
38
46
  compile: () => compile,
39
47
  completionsAt: () => completionsAt,
48
+ compose: () => compose,
40
49
  createVisitor: () => createVisitor,
50
+ cstBuildHost: () => cstBuildHost,
41
51
  expect: () => expect,
52
+ gapIsSignificant: () => gapIsSignificant,
53
+ gapText: () => gapText,
42
54
  guard: () => guard,
55
+ indentMixed: () => indentMixed,
56
+ indentWidth: () => indentWidth,
43
57
  isParseError: () => isParseError,
44
58
  keywords: () => keywords,
45
59
  label: () => label,
60
+ lineBreaksIn: () => lineBreaksIn,
61
+ lineStartWithin: () => lineStartWithin,
46
62
  literal: () => literal,
47
63
  makeWord: () => makeWord,
48
64
  many: () => many,
@@ -56,14 +72,18 @@ __export(index_exports, {
56
72
  parse: () => parse,
57
73
  parseDoc: () => parseDoc,
58
74
  parser: () => parser,
75
+ pick: () => pick,
59
76
  recover: () => recover,
60
77
  ref: () => ref,
61
78
  regex: () => regex,
79
+ relativize: () => relativize,
62
80
  rules: () => rules,
81
+ run: () => run,
63
82
  scanTo: () => scanTo,
64
83
  sepBy: () => sepBy,
65
84
  sepByRecover: () => sepByRecover,
66
85
  sequence: () => sequence,
86
+ shiftAbsolute: () => shiftAbsolute,
67
87
  skip: () => skip,
68
88
  staticExpected: () => staticExpected,
69
89
  transform: () => transform,
@@ -328,6 +348,36 @@ function literal(value, opts = {}) {
328
348
  }
329
349
 
330
350
  // src/combinators/regex.ts
351
+ var firstSetAnalyzer = null;
352
+ function registerRegexAnalyzer(analyzer) {
353
+ firstSetAnalyzer = analyzer;
354
+ }
355
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
356
+ function regex(pattern, flags = "") {
357
+ const source = typeof pattern === "string" ? pattern : pattern.source;
358
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
359
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
360
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
361
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
362
+ return {
363
+ _tag: "regex",
364
+ _meta: meta,
365
+ _def: { tag: "regex", source, flags: resolvedFlags },
366
+ parse(input, pos, ctx) {
367
+ anchored.lastIndex = pos;
368
+ const m = anchored.exec(input);
369
+ if (m === null) {
370
+ return failAt(ctx, [`/${source}/`], pos);
371
+ }
372
+ const span = { start: pos, end: pos + m[0].length };
373
+ const leaf = { _tag: "leaf", value: m[0], span };
374
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
375
+ return { ok: true, value: m[0], span };
376
+ }
377
+ };
378
+ }
379
+
380
+ // src/combinators/regex-analyze.ts
331
381
  var import_regexp_tree = __toESM(require("regexp-tree"), 1);
332
382
  function firstSetFromRegex(pattern) {
333
383
  try {
@@ -432,40 +482,6 @@ function extractFirstSet(node2) {
432
482
  return { firstSet: any(), canMatchNewline: true };
433
483
  }
434
484
  }
435
- function optimizeRegex(source, flags) {
436
- try {
437
- const result = import_regexp_tree.default.optimize(`/${source}/${flags}`);
438
- const str = result.toString();
439
- const lastSlash = str.lastIndexOf("/");
440
- return str.slice(1, lastSlash);
441
- } catch {
442
- return source;
443
- }
444
- }
445
- function regex(pattern, flags = "") {
446
- const source = typeof pattern === "string" ? pattern : pattern.source;
447
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
448
- const optimizedSource = optimizeRegex(source, resolvedFlags);
449
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
450
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
451
- const meta = { firstSet, canMatchNewline, isTrivia: false };
452
- return {
453
- _tag: "regex",
454
- _meta: meta,
455
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
456
- parse(input, pos, ctx) {
457
- anchored.lastIndex = pos;
458
- const m = anchored.exec(input);
459
- if (m === null) {
460
- return failAt(ctx, [`/${source}/`], pos);
461
- }
462
- const span = { start: pos, end: pos + m[0].length };
463
- const leaf = { _tag: "leaf", value: m[0], span };
464
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
465
- return { ok: true, value: m[0], span };
466
- }
467
- };
468
- }
469
485
 
470
486
  // src/combinators/keywords.ts
471
487
  function word(str, boundary = "_0-9A-Za-z") {
@@ -1163,7 +1179,9 @@ function rules(factory) {
1163
1179
  if (typeof key !== "string") return void 0;
1164
1180
  const record = target;
1165
1181
  if (!(key in record)) {
1166
- record[key] = ref();
1182
+ const r = ref();
1183
+ r._ruleName = key;
1184
+ record[key] = r;
1167
1185
  }
1168
1186
  return record[key];
1169
1187
  }
@@ -1228,12 +1246,14 @@ function confirmedBuildArity(src) {
1228
1246
  return parts.length;
1229
1247
  }
1230
1248
  function buildReadsTrivia(def) {
1249
+ if (!def.build) return true;
1231
1250
  const src = def.buildSrc ?? def.build.toString();
1232
1251
  const arity = confirmedBuildArity(src);
1233
1252
  if (arity === null) return true;
1234
1253
  return arity >= 4;
1235
1254
  }
1236
1255
  function buildReadsState(def) {
1256
+ if (!def.build) return true;
1237
1257
  const src = def.buildSrc ?? def.build.toString();
1238
1258
  const arity = confirmedBuildArity(src);
1239
1259
  if (arity === null) return true;
@@ -1252,8 +1272,8 @@ function node(type, combinator, build, opts) {
1252
1272
  };
1253
1273
  const collapse = opts?.collapse === true;
1254
1274
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1255
- const capturesTrivia = buildReadsTrivia(def);
1256
- const clonesState = buildReadsState(def);
1275
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1276
+ const clonesState = build ? buildReadsState(def) : true;
1257
1277
  return {
1258
1278
  _tag: "node",
1259
1279
  _meta: meta,
@@ -1264,13 +1284,8 @@ function node(type, combinator, build, opts) {
1264
1284
  const r = combinator.parse(input, pos, ctx);
1265
1285
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1266
1286
  if (!r.ok) return r;
1267
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1268
- children,
1269
- rawChildren,
1270
- r.span,
1271
- triviaLog,
1272
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1273
- );
1287
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1288
+ 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 };
1274
1289
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1275
1290
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1276
1291
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1445,8 +1460,14 @@ function staticExpected(c) {
1445
1460
  case "transform":
1446
1461
  case "not":
1447
1462
  return staticExpected(def.parser);
1448
- case "lazy":
1449
- return staticExpected(def.thunk());
1463
+ case "lazy": {
1464
+ const name = c._ruleName;
1465
+ try {
1466
+ return staticExpected(def.thunk());
1467
+ } catch {
1468
+ return name ? [name] : [];
1469
+ }
1470
+ }
1450
1471
  default:
1451
1472
  return [];
1452
1473
  }
@@ -1746,6 +1767,15 @@ function seqIsUnambiguous(parts) {
1746
1767
  const p = parts[k];
1747
1768
  const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1748
1769
  if (mandatory) hasMandatory = true;
1770
+ if (p.part === "group") {
1771
+ const exposure = groupPartExposure(p);
1772
+ if (exposure === "unsupported") {
1773
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1774
+ if (!trailingOnce) return false;
1775
+ } else if (exposure !== null) {
1776
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1777
+ }
1778
+ }
1749
1779
  const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1750
1780
  const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1751
1781
  if (skippableSingle || isUnboundedRepeat) {
@@ -1792,6 +1822,58 @@ function firstSetsDisjoint(a, b) {
1792
1822
  if (a === "any" || b === "any") return false;
1793
1823
  return classDisjoint(a, b);
1794
1824
  }
1825
+ function fixedClassSeq(shape) {
1826
+ if (shape.kind !== "seq") return null;
1827
+ const out = [];
1828
+ for (const p of shape.parts) {
1829
+ if (p.part === "lit") {
1830
+ if (p.optional) return null;
1831
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1832
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1833
+ out.push({ ranges: p.ranges, negated: p.negated });
1834
+ } else {
1835
+ return null;
1836
+ }
1837
+ }
1838
+ return out.length ? out : null;
1839
+ }
1840
+ function altFixedMutuallyExclusive(shape) {
1841
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1842
+ const lists = [];
1843
+ for (const arm of shape.arms) {
1844
+ const f = fixedClassSeq(arm);
1845
+ if (!f) return false;
1846
+ lists.push(f);
1847
+ }
1848
+ for (let i = 0; i < lists.length; i++) {
1849
+ for (let j = 0; j < lists.length; j++) {
1850
+ if (i === j) continue;
1851
+ const a = lists[i];
1852
+ const b = lists[j];
1853
+ if (a.length > b.length) continue;
1854
+ let disjointAt = false;
1855
+ for (let p = 0; p < a.length; p++) {
1856
+ if (classDisjoint(a[p], b[p])) {
1857
+ disjointAt = true;
1858
+ break;
1859
+ }
1860
+ }
1861
+ if (!disjointAt) return false;
1862
+ }
1863
+ }
1864
+ return true;
1865
+ }
1866
+ function groupPartExposure(part) {
1867
+ const innerWiggle = trailingBacktrackClass(part.inner);
1868
+ if (innerWiggle === "unsupported") return "unsupported";
1869
+ const requiredOnce = !part.unbounded && part.min === 1;
1870
+ if (requiredOnce) return innerWiggle;
1871
+ const dropExposed = shapeFirstAccept(part.inner);
1872
+ if (dropExposed === "any") return "unsupported";
1873
+ if (innerWiggle === null) return dropExposed;
1874
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1875
+ return unioned === "any" ? "unsupported" : unioned;
1876
+ }
1795
1877
  function trailingBacktrackClass(shape) {
1796
1878
  if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1797
1879
  if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
@@ -1803,18 +1885,10 @@ function trailingBacktrackClass(shape) {
1803
1885
  if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1804
1886
  return null;
1805
1887
  }
1806
- const innerWiggle = trailingBacktrackClass(last.inner);
1807
- if (innerWiggle === "unsupported") return "unsupported";
1808
- const requiredOnce = !last.unbounded && last.min === 1;
1809
- if (requiredOnce) return innerWiggle;
1810
- const dropExposed = shapeFirstAccept(last.inner);
1811
- if (dropExposed === "any") return "unsupported";
1812
- if (innerWiggle === null) return dropExposed;
1813
- const unioned = unionCharSets([innerWiggle, dropExposed]);
1814
- return unioned === "any" ? "unsupported" : unioned;
1888
+ return groupPartExposure(last);
1815
1889
  }
1816
1890
  if (shape.kind === "alt") {
1817
- if (!shape.disjoint) return "unsupported";
1891
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1818
1892
  const classes = shape.arms.map(trailingBacktrackClass);
1819
1893
  if (classes.some((c) => c === "unsupported")) return "unsupported";
1820
1894
  const concrete = classes.filter((c) => c !== null);
@@ -1954,7 +2028,7 @@ function seqFirstAccept(parts) {
1954
2028
  function groupInnerSafe(shape) {
1955
2029
  if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1956
2030
  if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1957
- if (shape.kind === "alt") return shape.disjoint;
2031
+ if (shape.kind === "alt") return true;
1958
2032
  return false;
1959
2033
  }
1960
2034
  function shapeFirstAccept(shape) {
@@ -1982,9 +2056,8 @@ function shapeFirstAccept(shape) {
1982
2056
  return shapeFirstAccept(shape.inner);
1983
2057
  // zero-width — doesn't change first-char acceptance
1984
2058
  case "alt": {
1985
- if (!shape.disjoint) return "any";
1986
- const sets = shape.firsts.filter((f) => f !== null);
1987
- return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
2059
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
2060
+ return unionCharSets(sets);
1988
2061
  }
1989
2062
  }
1990
2063
  }
@@ -2025,6 +2098,120 @@ function parseScanShape(source) {
2025
2098
  }
2026
2099
  return parseScanShapeCore(source);
2027
2100
  }
2101
+ function splitDelimArms(body) {
2102
+ const arms = [];
2103
+ let depth = 0;
2104
+ let last = 0;
2105
+ let i = 0;
2106
+ while (i < body.length) {
2107
+ const ch = body[i];
2108
+ if (ch === "\\") {
2109
+ i += 2;
2110
+ continue;
2111
+ }
2112
+ if (ch === "[") {
2113
+ let j = i + 1;
2114
+ if (body[j] === "^") j++;
2115
+ while (j < body.length && body[j] !== "]") {
2116
+ if (body[j] === "\\") j += 2;
2117
+ else j++;
2118
+ }
2119
+ if (body[j] !== "]") return null;
2120
+ i = j + 1;
2121
+ continue;
2122
+ }
2123
+ if (ch === "(") {
2124
+ depth++;
2125
+ i++;
2126
+ continue;
2127
+ }
2128
+ if (ch === ")") {
2129
+ depth--;
2130
+ if (depth < 0) return null;
2131
+ i++;
2132
+ continue;
2133
+ }
2134
+ if (ch === "|" && depth === 0) {
2135
+ arms.push(body.slice(last, i));
2136
+ last = i + 1;
2137
+ }
2138
+ i++;
2139
+ }
2140
+ if (depth !== 0) return null;
2141
+ arms.push(body.slice(last));
2142
+ return arms;
2143
+ }
2144
+ function delimitedBodySound(body, close) {
2145
+ const l0 = close[0];
2146
+ if (close.length > 2) return false;
2147
+ const arms = splitDelimArms(body);
2148
+ if (!arms) return false;
2149
+ let bulkSeen = false;
2150
+ let guardSeen = false;
2151
+ for (const arm of arms) {
2152
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
2153
+ if (bulk) {
2154
+ if (bulkSeen) return false;
2155
+ const ranges = parseClassRanges(bulk[1]);
2156
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2157
+ bulkSeen = true;
2158
+ continue;
2159
+ }
2160
+ if (close.length === 2 && arm.endsWith(")")) {
2161
+ const idx = arm.indexOf("(?!");
2162
+ if (idx === -1) return false;
2163
+ const prefix = literalCodePoints(arm.slice(0, idx));
2164
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2165
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2166
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2167
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2168
+ guardSeen = true;
2169
+ continue;
2170
+ }
2171
+ return false;
2172
+ }
2173
+ return bulkSeen && guardSeen === (close.length === 2);
2174
+ }
2175
+ function parseDelimited(source) {
2176
+ if (source.includes("\\\\")) return null;
2177
+ let i = 0;
2178
+ while (i < source.length && source[i] !== "(") {
2179
+ i += source[i] === "\\" ? 2 : 1;
2180
+ }
2181
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2182
+ const openFrag = source.slice(0, i);
2183
+ let k = i + 3;
2184
+ let depth = 1;
2185
+ while (k < source.length && depth > 0) {
2186
+ const ch = source[k];
2187
+ if (ch === "\\") {
2188
+ k += 2;
2189
+ continue;
2190
+ }
2191
+ if (ch === "[") {
2192
+ let j = k + 1;
2193
+ if (source[j] === "^") j++;
2194
+ while (j < source.length && source[j] !== "]") {
2195
+ if (source[j] === "\\") j += 2;
2196
+ else j++;
2197
+ }
2198
+ if (source[j] !== "]") return null;
2199
+ k = j + 1;
2200
+ continue;
2201
+ }
2202
+ if (ch === "(") depth++;
2203
+ else if (ch === ")") depth--;
2204
+ k++;
2205
+ }
2206
+ if (depth !== 0) return null;
2207
+ const body = source.slice(i + 3, k - 1);
2208
+ if (source[k] !== "*") return null;
2209
+ const closeFrag = source.slice(k + 1);
2210
+ const open = literalCodePoints(openFrag);
2211
+ const close = literalCodePoints(closeFrag);
2212
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2213
+ return { kind: "delimited", open, close };
2214
+ }
2028
2215
  function parseScanShapeCore(source) {
2029
2216
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
2030
2217
  if (m && !m[1].startsWith("^")) {
@@ -2049,14 +2236,8 @@ function parseScanShapeCore(source) {
2049
2236
  const stop = parseClassRanges(m[2]);
2050
2237
  if (open && stop) return { kind: "until", open, stop };
2051
2238
  }
2052
- if (!source.includes("\\\\")) {
2053
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
2054
- if (m && m[1] && m[2]) {
2055
- const open = literalCodePoints(m[1]);
2056
- const close = literalCodePoints(m[2]);
2057
- if (open && close) return { kind: "delimited", open, close };
2058
- }
2059
- }
2239
+ const delim = parseDelimited(source);
2240
+ if (delim) return delim;
2060
2241
  const parts = parseSeqParts(source);
2061
2242
  if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
2062
2243
  return null;
@@ -2511,6 +2692,7 @@ function looseMkBuildRe(type) {
2511
2692
  );
2512
2693
  }
2513
2694
  function analyzeMkInlineBuild(def) {
2695
+ if (!def.build) return null;
2514
2696
  const src = (def.buildSrc ?? def.build.toString()).trim();
2515
2697
  const strict = src.match(MK_BUILD_RE);
2516
2698
  if (strict) {
@@ -2608,6 +2790,15 @@ function v(ctx, prefix = "_v") {
2608
2790
  function ind(ctx) {
2609
2791
  return " ".repeat(ctx.indent);
2610
2792
  }
2793
+ function nsp(ctx) {
2794
+ return ctx.ns ?? "";
2795
+ }
2796
+ function mfRef(ctx) {
2797
+ return `${nsp(ctx)}_mf`;
2798
+ }
2799
+ function buildRef(ctx) {
2800
+ return `${nsp(ctx)}_build`;
2801
+ }
2611
2802
  function reindentStmts(stmts, targetLevels) {
2612
2803
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
2613
2804
  if (nonEmpty.length === 0) return stmts;
@@ -2624,7 +2815,7 @@ function failReturnArr(expectedArr, posExpr) {
2624
2815
  function hoistExpected(ctx, constArrSource) {
2625
2816
  let name = ctx.expectedMap.get(constArrSource);
2626
2817
  if (name === void 0) {
2627
- name = `_fx${ctx.expectedDecls.length}`;
2818
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2628
2819
  ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2629
2820
  ctx.expectedMap.set(constArrSource, name);
2630
2821
  }
@@ -2723,13 +2914,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2723
2914
  `${i}}`
2724
2915
  ];
2725
2916
  }
2726
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2917
+ function ensureRegexDecl(ctx, source, flags) {
2727
2918
  const f = "y" + flags.replace(/[gy]/g, "");
2728
- const key = `${optimizedSource}/${f}`;
2919
+ const key = `${source}/${f}`;
2729
2920
  let rName = ctx.regexMap.get(key);
2730
2921
  if (rName === void 0) {
2731
- rName = `_re${ctx.regexDecls.length}`;
2732
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2922
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2923
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2733
2924
  ctx.regexMap.set(key, rName);
2734
2925
  }
2735
2926
  return rName;
@@ -2738,7 +2929,7 @@ function ensureTriviaFn(ctx) {
2738
2929
  const trivia2 = ctx.activeTrivia;
2739
2930
  const existing = ctx.triviaFnNames.get(trivia2);
2740
2931
  if (existing) return existing;
2741
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2932
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2742
2933
  ctx.triviaFnNames.set(trivia2, fnName);
2743
2934
  ctx.triviaCaptureNames.set(trivia2, fnName);
2744
2935
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2760,7 +2951,7 @@ function ensureTriviaFn(ctx) {
2760
2951
  for (const arm of regexSpec.arms) {
2761
2952
  const def = arm.parser._def;
2762
2953
  if (def.tag !== "regex") break;
2763
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2954
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2764
2955
  }
2765
2956
  if (reNames.length === regexSpec.arms.length) {
2766
2957
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -3085,7 +3276,7 @@ function emitKeywords(def, ctx, pos) {
3085
3276
  const key = `${source}/${flags}`;
3086
3277
  let rName = ctx.regexMap.get(key);
3087
3278
  if (rName === void 0) {
3088
- rName = `_re${ctx.regexDecls.length}`;
3279
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3089
3280
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
3090
3281
  ctx.regexMap.set(key, rName);
3091
3282
  }
@@ -3119,11 +3310,11 @@ function emitRegex(def, ctx, pos) {
3119
3310
  }
3120
3311
  }
3121
3312
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3122
- const key = `${def.optimizedSource}/${flags}`;
3313
+ const key = `${def.source}/${flags}`;
3123
3314
  let rName = ctx.regexMap.get(key);
3124
3315
  if (rName === void 0) {
3125
- rName = `_re${ctx.regexDecls.length}`;
3126
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3316
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3317
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
3127
3318
  ctx.regexMap.set(key, rName);
3128
3319
  }
3129
3320
  const mv = v(ctx, "_m");
@@ -3300,7 +3491,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3300
3491
  const reKey = `${regexDef.source}/${cleanFlags}`;
3301
3492
  let reVar = ctx.regexMap.get(reKey);
3302
3493
  if (reVar === void 0) {
3303
- reVar = `_re${ctx.regexDecls.length}`;
3494
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3304
3495
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
3305
3496
  ctx.regexMap.set(reKey, reVar);
3306
3497
  }
@@ -3393,7 +3584,7 @@ function emitFirstMatch(def, ctx, pos) {
3393
3584
  if (gate) {
3394
3585
  const gateIdx = ctx.mapFns.length;
3395
3586
  ctx.mapFns.push(gate);
3396
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3587
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3397
3588
  }
3398
3589
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3399
3590
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -3460,7 +3651,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3460
3651
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
3461
3652
  const vv = v(ctx);
3462
3653
  return {
3463
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3654
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3464
3655
  valueVar: vv,
3465
3656
  endVar: endV
3466
3657
  };
@@ -3688,16 +3879,17 @@ function emitNot(def, ctx, pos) {
3688
3879
  };
3689
3880
  }
3690
3881
  function emitNode(def, ctx, pos) {
3691
- const mkType = analyzeMkInlineBuild(def);
3882
+ const structural = def.build === void 0;
3883
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
3692
3884
  let buildIdx = null;
3693
- if (!mkType) {
3885
+ if (!mkType && !structural) {
3694
3886
  buildIdx = ctx.buildFns.length;
3695
3887
  ctx.buildFns.push(def.build);
3696
3888
  ctx.buildSrcs.push(def.buildSrc ?? null);
3697
3889
  }
3698
3890
  const i = ind(ctx);
3699
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
3700
- const clonesState = buildReadsState(def);
3891
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3892
+ const clonesState = structural || buildReadsState(def);
3701
3893
  const chV = v(ctx, "_ch");
3702
3894
  const rawV = v(ctx, "_raw");
3703
3895
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -3720,7 +3912,8 @@ function emitNode(def, ctx, pos) {
3720
3912
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3721
3913
  }
3722
3914
  const ndV = v(ctx, "_nd");
3723
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3915
+ 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})`;
3916
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
3724
3917
  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;
3725
3918
  stmts.push(
3726
3919
  `${i}const ${ndV} = ${finalExpr}`,
@@ -3745,8 +3938,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
3745
3938
  return { stmts, valueVar: vv, endVar: ev };
3746
3939
  }
3747
3940
  function emitLazy(p, def, ctx, pos) {
3941
+ const canonical = ctx.ruleNames?.get(p);
3748
3942
  const usage = ctx.lazyUsage;
3749
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3943
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3750
3944
  let resolved;
3751
3945
  try {
3752
3946
  resolved = def.thunk();
@@ -3756,7 +3950,7 @@ function emitLazy(p, def, ctx, pos) {
3756
3950
  return emit(resolved, ctx, pos);
3757
3951
  }
3758
3952
  if (!ctx.namedParsers.has(p)) {
3759
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3953
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
3760
3954
  ctx.namedParsers.set(p, fnName2);
3761
3955
  let resolved;
3762
3956
  try {
@@ -3874,7 +4068,7 @@ function emit(p, ctx, pos) {
3874
4068
  return {
3875
4069
  stmts: [
3876
4070
  ...inner.stmts,
3877
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
4071
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3878
4072
  ],
3879
4073
  valueVar: mv,
3880
4074
  endVar: inner.endVar
@@ -3937,7 +4131,7 @@ function emit(p, ctx, pos) {
3937
4131
  const vv = v(ctx);
3938
4132
  return {
3939
4133
  stmts: [
3940
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
4134
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3941
4135
  `${ind(ctx)}const ${vv} = null`
3942
4136
  ],
3943
4137
  valueVar: vv,
@@ -3970,7 +4164,7 @@ function emit(p, ctx, pos) {
3970
4164
  const ev = v(ctx, "_wce");
3971
4165
  return {
3972
4166
  stmts: [
3973
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4167
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3974
4168
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3975
4169
  // withCtx runs on a spread child ctx — copy its recorded failure back
3976
4170
  // and propagate the inner failure verbatim (interpreter parity).
@@ -4101,6 +4295,39 @@ function analyzeLazyUsageMulti(roots) {
4101
4295
  for (const root of roots) walk2(root);
4102
4296
  return { counts, recursive };
4103
4297
  }
4298
+ function ruleDependencies(ruleMap) {
4299
+ const nameOf = /* @__PURE__ */ new Map();
4300
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4301
+ const deps = /* @__PURE__ */ new Map();
4302
+ for (const [name, comb] of ruleMap) {
4303
+ const found = /* @__PURE__ */ new Set();
4304
+ const seen = /* @__PURE__ */ new Set();
4305
+ const walk2 = (p, isRoot) => {
4306
+ const def = p._def;
4307
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4308
+ if (!isRoot && boundary !== void 0) {
4309
+ found.add(boundary);
4310
+ return;
4311
+ }
4312
+ if (seen.has(p)) return;
4313
+ seen.add(p);
4314
+ if (def.tag === "lazy") {
4315
+ let resolved;
4316
+ try {
4317
+ resolved = def.thunk();
4318
+ } catch {
4319
+ return;
4320
+ }
4321
+ walk2(resolved, false);
4322
+ return;
4323
+ }
4324
+ for (const child of childrenOf(def)) walk2(child, false);
4325
+ };
4326
+ walk2(comb, true);
4327
+ deps.set(name, [...found]);
4328
+ }
4329
+ return deps;
4330
+ }
4104
4331
  function compile(combinator, mapFnSources) {
4105
4332
  const ctx = {
4106
4333
  vars: 0,
@@ -4184,6 +4411,150 @@ function compile(combinator, mapFnSources) {
4184
4411
  }
4185
4412
  };
4186
4413
  }
4414
+ function compileLinkable(ruleMapArg, ns) {
4415
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4416
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4417
+ const d = val._def;
4418
+ if (d.tag !== "lazy") return true;
4419
+ try {
4420
+ d.thunk();
4421
+ return true;
4422
+ } catch {
4423
+ return false;
4424
+ }
4425
+ });
4426
+ const ctx = {
4427
+ vars: 0,
4428
+ indent: 1,
4429
+ regexDecls: [],
4430
+ regexMap: /* @__PURE__ */ new Map(),
4431
+ expectedDecls: [],
4432
+ expectedMap: /* @__PURE__ */ new Map(),
4433
+ recordFail: true,
4434
+ mapFns: [],
4435
+ mapFnSrcs: [],
4436
+ buildFns: [],
4437
+ buildSrcs: [],
4438
+ runtimeParsers: [],
4439
+ needsCollator: false,
4440
+ namedParsers: /* @__PURE__ */ new Map(),
4441
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4442
+ triviaFnNames: /* @__PURE__ */ new Map(),
4443
+ namedFnDecls: [],
4444
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4445
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4446
+ ns
4447
+ };
4448
+ const seen = /* @__PURE__ */ new Map();
4449
+ const ruleNames = /* @__PURE__ */ new Map();
4450
+ const fnNameToKey = /* @__PURE__ */ new Map();
4451
+ for (const [key, rule] of ruleMap) {
4452
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4453
+ const n = seen.get(base) ?? 0;
4454
+ seen.set(base, n + 1);
4455
+ const fn = n === 0 ? base : `${base}$${n}`;
4456
+ ruleNames.set(rule, fn);
4457
+ fnNameToKey.set(fn, key);
4458
+ }
4459
+ ctx.ruleNames = ruleNames;
4460
+ {
4461
+ const scanned = /* @__PURE__ */ new Set();
4462
+ const scanExternal = (p) => {
4463
+ if (scanned.has(p)) return;
4464
+ scanned.add(p);
4465
+ const def = p._def;
4466
+ if (def.tag === "lazy") {
4467
+ let resolved;
4468
+ try {
4469
+ resolved = def.thunk();
4470
+ } catch {
4471
+ resolved = void 0;
4472
+ }
4473
+ if (resolved === void 0) {
4474
+ const name = p._ruleName;
4475
+ if (name && !ctx.namedParsers.has(p)) {
4476
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4477
+ ruleNames.set(p, fn);
4478
+ ctx.namedParsers.set(p, fn);
4479
+ }
4480
+ return;
4481
+ }
4482
+ scanExternal(resolved);
4483
+ return;
4484
+ }
4485
+ for (const child of childrenOf(def)) scanExternal(child);
4486
+ };
4487
+ for (const [, rule] of ruleMap) scanExternal(rule);
4488
+ }
4489
+ const perEntry = [];
4490
+ for (const [key, rule] of ruleMap) {
4491
+ const fn = ruleNames.get(rule);
4492
+ if (!ctx.namedParsers.has(rule)) {
4493
+ ctx.namedParsers.set(rule, fn);
4494
+ let resolved = rule;
4495
+ const def = rule._def;
4496
+ if (def.tag === "lazy") {
4497
+ try {
4498
+ resolved = def.thunk();
4499
+ } catch {
4500
+ return null;
4501
+ }
4502
+ }
4503
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4504
+ ctx.indent = 1;
4505
+ ctx.failLabel = "_pfail";
4506
+ ctx.recordFail = true;
4507
+ const body = emit(resolved, ctx, "_pos");
4508
+ ctx.indent = savedIndent;
4509
+ ctx.failLabel = savedFail;
4510
+ ctx.recordFail = savedRec;
4511
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4512
+ }
4513
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4514
+ }
4515
+ if (ctx.runtimeParsers.length !== 0) return null;
4516
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4517
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4518
+ const ruleFns = /* @__PURE__ */ new Map();
4519
+ const privateFns = [];
4520
+ for (const decl of ctx.namedFnDecls) {
4521
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4522
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4523
+ if (key !== void 0) ruleFns.set(key, decl);
4524
+ else privateFns.push(decl);
4525
+ }
4526
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4527
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4528
+ const prelude = [
4529
+ ...ctx.regexDecls,
4530
+ ...ctx.expectedDecls,
4531
+ mfDecl,
4532
+ buildDecl,
4533
+ ...privateFns
4534
+ ].filter(Boolean);
4535
+ const wrappers = /* @__PURE__ */ new Map();
4536
+ for (const { key, r } of perEntry) {
4537
+ wrappers.set(key, [
4538
+ `function(input, _pos, _ctx) {`,
4539
+ ` let pos = _pos`,
4540
+ ...r.stmts,
4541
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4542
+ `}`
4543
+ ].join("\n"));
4544
+ }
4545
+ return {
4546
+ ns,
4547
+ keys: perEntry.map((e) => e.key),
4548
+ prelude,
4549
+ ruleFns,
4550
+ wrappers,
4551
+ deps: ruleDependencies(ruleMap),
4552
+ needsEmptyTl: !!ctx.needsEmptyTl,
4553
+ needsCollator: ctx.needsCollator,
4554
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4555
+ buildFns: buildSrcs ? [] : ctx.buildFns
4556
+ };
4557
+ }
4187
4558
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4188
4559
  const bodyLines = [
4189
4560
  ` let pos = _pos`,
@@ -4222,6 +4593,87 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4222
4593
  ].join("\n");
4223
4594
  }
4224
4595
 
4596
+ // src/compiler/linker.ts
4597
+ var _nsCounter = 0;
4598
+ function linkable(rulesMap, ns) {
4599
+ const pieces = compileLinkable([...Object.entries(rulesMap)], ns ?? `_lk${_nsCounter++}_`);
4600
+ if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
4601
+ return pieces;
4602
+ }
4603
+ function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
4604
+ return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
4605
+ }
4606
+ function pick(grammar, names) {
4607
+ const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
4608
+ const keep = /* @__PURE__ */ new Set();
4609
+ const has = new Set(p.keys);
4610
+ for (const n of names) {
4611
+ if (!has.has(n)) throw new Error(`pick: rule "${n}" is not in this grammar (available: ${p.keys.join(", ")})`);
4612
+ }
4613
+ const visit = (n) => {
4614
+ if (keep.has(n) || !has.has(n)) return;
4615
+ keep.add(n);
4616
+ for (const d of p.deps.get(n) ?? []) visit(d);
4617
+ };
4618
+ for (const n of names) visit(n);
4619
+ const filt = (m) => new Map([...m].filter(([k]) => keep.has(k)));
4620
+ return {
4621
+ ...p,
4622
+ keys: [...keep],
4623
+ ruleFns: filt(p.ruleFns),
4624
+ wrappers: filt(p.wrappers),
4625
+ deps: filt(p.deps)
4626
+ };
4627
+ }
4628
+ function fusedBody(pieces) {
4629
+ const winner = /* @__PURE__ */ new Map();
4630
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4631
+ for (const [k, p] of winner) {
4632
+ for (const d of p.deps.get(k) ?? []) {
4633
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4634
+ }
4635
+ }
4636
+ const contributing = new Set(winner.values());
4637
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4638
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4639
+ const lines = [
4640
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4641
+ "const _pfFail = {}",
4642
+ "let _pfEnd",
4643
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4644
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4645
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4646
+ ...[...contributing].flatMap((p) => p.prelude),
4647
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4648
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4649
+ ];
4650
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4651
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4652
+ const env = {};
4653
+ for (const p of contributing) {
4654
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4655
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4656
+ }
4657
+ return { body, env };
4658
+ }
4659
+ function fuseRules(pieces) {
4660
+ const { body, env } = fusedBody(pieces);
4661
+ return new Function("_env", body)(env);
4662
+ }
4663
+ var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
4664
+ function itemPieces(item) {
4665
+ const carried = item[COMPOSED_PIECES];
4666
+ if (Array.isArray(carried)) return carried;
4667
+ if (item.ruleFns instanceof Map) return [item];
4668
+ return [linkable(item)];
4669
+ }
4670
+ function compose(items) {
4671
+ const pieces = items.flatMap(itemPieces);
4672
+ const map = fuseRules(pieces);
4673
+ Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
4674
+ return map;
4675
+ }
4676
+
4225
4677
  // src/combinators/guard.ts
4226
4678
  function guard(predicate) {
4227
4679
  const meta = {
@@ -4419,6 +4871,7 @@ function manyRecover(item, until) {
4419
4871
  }
4420
4872
 
4421
4873
  // src/functional/doc.ts
4874
+ var REENTRY_MAX_SPAN_FRACTION = 0.5;
4422
4875
  function isNode(x) {
4423
4876
  return typeof x === "object" && x !== null && x._tag === "node";
4424
4877
  }
@@ -4479,6 +4932,47 @@ function graftAndShift(root, path, newNode, delta) {
4479
4932
  children: newChildren
4480
4933
  };
4481
4934
  }
4935
+ function structurallyEqual(a, b) {
4936
+ if (a === b) return true;
4937
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
4938
+ const at = a._tag;
4939
+ const bt = b._tag;
4940
+ if (at !== bt) return false;
4941
+ const as = a.span;
4942
+ const bs = b.span;
4943
+ if (as || bs) {
4944
+ if (!as || !bs || as.start !== bs.start || as.end !== bs.end) return false;
4945
+ }
4946
+ if (at === "leaf" || at === "trivia") {
4947
+ return a.value === b.value;
4948
+ }
4949
+ if (a.type !== b.type) return false;
4950
+ const ac = a.children;
4951
+ const bc = b.children;
4952
+ if (ac || bc) {
4953
+ if (!ac || !bc || ac.length !== bc.length) return false;
4954
+ for (let i = 0; i < ac.length; i++) if (!structurallyEqual(ac[i], bc[i])) return false;
4955
+ }
4956
+ return true;
4957
+ }
4958
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
4959
+ if (!produced.ok) return false;
4960
+ if (boundary >= newInput.length) return true;
4961
+ for (const sentinel of [" ", "\uFFFF"]) {
4962
+ if (newInput[boundary] === sentinel) continue;
4963
+ const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
4964
+ const ctx = { trackLines: false, state, build };
4965
+ let r;
4966
+ try {
4967
+ r = ruleFn(probed, start, ctx);
4968
+ } catch {
4969
+ return false;
4970
+ }
4971
+ if (!r.ok || r.span.end !== produced.span.end) return false;
4972
+ if (!structurallyEqual(r.value, produced.value)) return false;
4973
+ }
4974
+ return true;
4975
+ }
4482
4976
  var ParseDocImpl = class _ParseDocImpl {
4483
4977
  _registry;
4484
4978
  _rootRule;
@@ -4510,20 +5004,24 @@ var ParseDocImpl = class _ParseDocImpl {
4510
5004
  }
4511
5005
  const rebuild = this._opts.rebuild ?? defaultRebuild;
4512
5006
  for (const { node: node2, path } of candidates) {
5007
+ if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
4513
5008
  const ruleFn = this._registry[node2.type];
4514
5009
  if (!ruleFn) continue;
4515
- const ctx = { trackLines: false, state: node2.state };
5010
+ if (!(node2.span.start <= from && to <= node2.span.end)) continue;
5011
+ const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
4516
5012
  const r = ruleFn(newInput, node2.span.start, ctx);
4517
5013
  if (!r.ok) continue;
4518
- if (r.span.end === node2.span.end + delta) {
4519
- if (delta === 0) {
4520
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
4521
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
4522
- }
4523
- if (this._opts.rebuild) return reparse();
4524
- const newTree = graftAndShift(this.tree, path, r.value, delta);
4525
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
5014
+ if (r.span.end !== node2.span.end + delta) continue;
5015
+ if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
5016
+ continue;
4526
5017
  }
5018
+ if (delta === 0) {
5019
+ const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
5020
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
5021
+ }
5022
+ if (this._opts.rebuild) return reparse();
5023
+ const newTree = graftAndShift(this.tree, path, r.value, delta);
5024
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
4527
5025
  }
4528
5026
  return reparse();
4529
5027
  }
@@ -4531,7 +5029,7 @@ var ParseDocImpl = class _ParseDocImpl {
4531
5029
  function parseDoc(registry, rootRule, input, opts = {}) {
4532
5030
  const ruleFn = registry[rootRule];
4533
5031
  if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
4534
- const ctx = { trackLines: false, state: opts.state };
5032
+ const ctx = { trackLines: false, state: opts.state, build: opts.build };
4535
5033
  const r = ruleFn(input, 0, ctx);
4536
5034
  if (r.ok) {
4537
5035
  return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
@@ -4539,12 +5037,45 @@ function parseDoc(registry, rootRule, input, opts = {}) {
4539
5037
  return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
4540
5038
  }
4541
5039
 
5040
+ // src/functional/run.ts
5041
+ var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
5042
+ function run(entry, input, options = {}) {
5043
+ const triviaLog = [];
5044
+ const errors = [];
5045
+ const ctx = {
5046
+ trackLines: false,
5047
+ _triviaLog: triviaLog,
5048
+ _errors: errors,
5049
+ build: options.build,
5050
+ state: options.state
5051
+ };
5052
+ const r = invoke(entry, input, 0, ctx);
5053
+ let unconsumedFrom = null;
5054
+ if (r.ok) {
5055
+ let pos = r.span?.end ?? 0;
5056
+ if (options.trivia && pos < input.length) {
5057
+ const t = invoke(options.trivia, input, pos, { trackLines: false });
5058
+ if (t.ok && t.span.end > pos) pos = t.span.end;
5059
+ }
5060
+ unconsumedFrom = pos < input.length ? pos : null;
5061
+ }
5062
+ return {
5063
+ ok: r.ok,
5064
+ value: r.ok ? r.value : void 0,
5065
+ span: r.span ?? { start: 0, end: 0 },
5066
+ expected: r.ok ? [] : r.expected ?? [],
5067
+ errors,
5068
+ triviaLog,
5069
+ unconsumedFrom
5070
+ };
5071
+ }
5072
+
4542
5073
  // src/cst/trivia-index.ts
4543
- function merge(map, key, run) {
4544
- if (run.length === 0) return;
5074
+ function merge(map, key, run2) {
5075
+ if (run2.length === 0) return;
4545
5076
  const existing = map.get(key);
4546
- if (existing) existing.push(...run);
4547
- else map.set(key, [...run]);
5077
+ if (existing) existing.push(...run2);
5078
+ else map.set(key, [...run2]);
4548
5079
  }
4549
5080
  function buildTriviaIndex(root, input = "", opts) {
4550
5081
  const before = /* @__PURE__ */ new Map();
@@ -4556,11 +5087,11 @@ function buildTriviaIndex(root, input = "", opts) {
4556
5087
  for (let i = 0; i < log.length; i += 3) {
4557
5088
  const tStart = log[i], tEnd = log[i + 1], insertIdx = log[i + 2];
4558
5089
  if (tEnd <= tStart) continue;
4559
- const run = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
5090
+ const run2 = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
4560
5091
  const next = raw[insertIdx];
4561
- if (next) merge(before, next.span.start, run);
5092
+ if (next) merge(before, next.span.start, run2);
4562
5093
  const prev = insertIdx > 0 ? raw[insertIdx - 1] : void 0;
4563
- if (prev) merge(after, prev.span.end, run);
5094
+ if (prev) merge(after, prev.span.end, run2);
4564
5095
  }
4565
5096
  }
4566
5097
  const ch = node2.children;
@@ -4577,17 +5108,17 @@ function buildTriviaIndex(root, input = "", opts) {
4577
5108
  const rootSpan = root?.span;
4578
5109
  const re = new RegExp(opts.trivia.source, opts.trivia.flags.replace(/[gy]/g, "") + "y");
4579
5110
  const scanFrom = (from) => {
4580
- const run = [];
5111
+ const run2 = [];
4581
5112
  let pos = from;
4582
5113
  re.lastIndex = pos;
4583
5114
  let m = re.exec(input);
4584
5115
  while (m && m.index === pos && m[0].length > 0) {
4585
- run.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
5116
+ run2.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
4586
5117
  pos += m[0].length;
4587
5118
  re.lastIndex = pos;
4588
5119
  m = re.exec(input);
4589
5120
  }
4590
- return run;
5121
+ return run2;
4591
5122
  };
4592
5123
  if (rootSpan && rootSpan.start > 0) {
4593
5124
  const lead = scanFrom(0);
@@ -4673,4 +5204,263 @@ function triviaEntries(log, labels, opts) {
4673
5204
  }
4674
5205
  };
4675
5206
  }
5207
+
5208
+ // src/cst/offset-model.ts
5209
+ var { isFinite } = Number;
5210
+ function floorIndex(arr, len, x) {
5211
+ let lo = 0;
5212
+ let hi = len - 1;
5213
+ let ans = -1;
5214
+ while (lo <= hi) {
5215
+ const mid = lo + hi >>> 1;
5216
+ if (arr[mid] <= x) {
5217
+ ans = mid;
5218
+ lo = mid + 1;
5219
+ } else {
5220
+ hi = mid - 1;
5221
+ }
5222
+ }
5223
+ return ans;
5224
+ }
5225
+ var OffsetIndex = class {
5226
+ /** Slot starts, sorted ascending. Parallel to `_ends`. */
5227
+ _starts;
5228
+ _ends;
5229
+ count;
5230
+ docStart;
5231
+ docEnd;
5232
+ constructor(slots, docStart = 0, docEnd) {
5233
+ const sorted = [...slots].sort((a, b) => a.start - b.start);
5234
+ const starts = [];
5235
+ const ends = [];
5236
+ let prevEnd = docStart;
5237
+ for (const s of sorted) {
5238
+ if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
5239
+ throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
5240
+ }
5241
+ if (s.start < prevEnd) {
5242
+ throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
5243
+ }
5244
+ starts.push(s.start);
5245
+ ends.push(s.end);
5246
+ prevEnd = s.end;
5247
+ }
5248
+ this._starts = starts;
5249
+ this._ends = ends;
5250
+ this.count = starts.length;
5251
+ this.docStart = docStart;
5252
+ this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
5253
+ }
5254
+ /** The slot at index `i` (0-based, source order), or undefined. */
5255
+ slot(i) {
5256
+ if (i < 0 || i >= this.count) return void 0;
5257
+ return { start: this._starts[i], end: this._ends[i] };
5258
+ }
5259
+ /**
5260
+ * Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
5261
+ * `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
5262
+ * An offset that lands inside a slot returns that slot's TRAILING gap index
5263
+ * (i.e. the gap after it) — callers querying at slot boundaries get the
5264
+ * intuitive answer via `gapBefore`/`gapAfter`.
5265
+ */
5266
+ gapIndexAt(offset) {
5267
+ const i = floorIndex(this._starts, this.count, offset);
5268
+ if (i < 0) return 0;
5269
+ if (offset < this._ends[i]) return i + 1;
5270
+ return i + 1;
5271
+ }
5272
+ /** The gap at index `i` in `[0, count]`. */
5273
+ gap(i) {
5274
+ if (i < 0 || i > this.count) return void 0;
5275
+ const start = i === 0 ? this.docStart : this._ends[i - 1];
5276
+ const end = i === this.count ? this.docEnd : this._starts[i];
5277
+ return { start, end };
5278
+ }
5279
+ /**
5280
+ * The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
5281
+ * `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
5282
+ * `docStart`). For a real slot start this is the gap preceding that slot; for an
5283
+ * adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
5284
+ */
5285
+ gapBefore(offset) {
5286
+ const e = floorIndex(this._ends, this.count, offset);
5287
+ const start = e >= 0 ? this._ends[e] : this.docStart;
5288
+ return { start, end: offset };
5289
+ }
5290
+ /**
5291
+ * The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
5292
+ * `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
5293
+ */
5294
+ gapAfter(offset) {
5295
+ return { start: offset, end: this._ceilStart(offset) };
5296
+ }
5297
+ /** Least slot start `>= offset`, else `docEnd`. */
5298
+ _ceilStart(offset) {
5299
+ let lo = 0;
5300
+ let hi = this.count - 1;
5301
+ let ans = this.docEnd;
5302
+ while (lo <= hi) {
5303
+ const mid = lo + hi >>> 1;
5304
+ if (this._starts[mid] >= offset) {
5305
+ ans = this._starts[mid];
5306
+ hi = mid - 1;
5307
+ } else {
5308
+ lo = mid + 1;
5309
+ }
5310
+ }
5311
+ return ans;
5312
+ }
5313
+ /** Is this gap empty (adjacent slots, no trivia)? */
5314
+ isEmpty(gap) {
5315
+ return gap.end <= gap.start;
5316
+ }
5317
+ };
5318
+ var NL = 10;
5319
+ var CR = 13;
5320
+ var SP = 32;
5321
+ var TAB = 9;
5322
+ function gapText(source, gap) {
5323
+ return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
5324
+ }
5325
+ function lineBreaksIn(source, gap) {
5326
+ let n = 0;
5327
+ for (let i = gap.start; i < gap.end; i++) {
5328
+ const c = source.charCodeAt(i);
5329
+ if (c === NL) n++;
5330
+ else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
5331
+ }
5332
+ return n;
5333
+ }
5334
+ function blankLinesIn(source, gap) {
5335
+ return Math.max(0, lineBreaksIn(source, gap) - 1);
5336
+ }
5337
+ function lineStartWithin(source, gap) {
5338
+ for (let i = gap.end - 1; i >= gap.start; i--) {
5339
+ const c = source.charCodeAt(i);
5340
+ if (c === NL || c === CR) return i + 1;
5341
+ }
5342
+ return gap.start;
5343
+ }
5344
+ function indentWidth(source, gap) {
5345
+ const ls = lineStartWithin(source, gap);
5346
+ if (ls === gap.start) return 0;
5347
+ return gap.end - ls;
5348
+ }
5349
+ function indentMixed(source, gap) {
5350
+ const ls = lineStartWithin(source, gap);
5351
+ let sawSpace = false;
5352
+ let sawTab = false;
5353
+ for (let i = ls; i < gap.end; i++) {
5354
+ const c = source.charCodeAt(i);
5355
+ if (c === SP) sawSpace = true;
5356
+ else if (c === TAB) sawTab = true;
5357
+ }
5358
+ return sawSpace && sawTab;
5359
+ }
5360
+ function commentsIn(source, gap, comment) {
5361
+ const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
5362
+ re.lastIndex = gap.start;
5363
+ const out = [];
5364
+ let m;
5365
+ while ((m = re.exec(source)) && m.index < gap.end) {
5366
+ if (m[0].length === 0) {
5367
+ re.lastIndex++;
5368
+ continue;
5369
+ }
5370
+ if (m.index + m[0].length > gap.end) break;
5371
+ out.push({ start: m.index, end: m.index + m[0].length });
5372
+ }
5373
+ return out;
5374
+ }
5375
+ function gapIsSignificant(source, gap, comment) {
5376
+ if (gap.end <= gap.start) return false;
5377
+ if (lineBreaksIn(source, gap) > 0) return true;
5378
+ if (comment && commentsIn(source, gap, comment).length > 0) return true;
5379
+ return false;
5380
+ }
5381
+ function collectLeafSlots(root) {
5382
+ const slots = [];
5383
+ const visit = (n) => {
5384
+ const kids = n.children;
5385
+ if (kids && kids.length) {
5386
+ for (const c of kids) visit(c);
5387
+ } else if (n.span) {
5388
+ slots.push({ start: n.span.start, end: n.span.end });
5389
+ }
5390
+ };
5391
+ if (root) visit(root);
5392
+ return slots;
5393
+ }
5394
+ function buildOffsetIndex(root, input = "") {
5395
+ return new OffsetIndex(collectLeafSlots(root), 0, input.length);
5396
+ }
5397
+
5398
+ // src/cst/relative-spans.ts
5399
+ function relativize(node2, parentBase = 0) {
5400
+ return {
5401
+ start: node2.start - parentBase,
5402
+ end: node2.end - parentBase,
5403
+ children: node2.children.map((c) => relativize(c, node2.start))
5404
+ };
5405
+ }
5406
+ function absolutize(node2, parentBase = 0) {
5407
+ const start = parentBase + node2.start;
5408
+ return {
5409
+ start,
5410
+ end: parentBase + node2.end,
5411
+ children: node2.children.map((c) => absolutize(c, start))
5412
+ };
5413
+ }
5414
+ function absoluteSpanAt(root, path) {
5415
+ let base = 0;
5416
+ let node2 = root;
5417
+ for (const idx of path) {
5418
+ base += node2.start;
5419
+ const child = node2.children[idx];
5420
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
5421
+ node2 = child;
5422
+ }
5423
+ return { start: base + node2.start, end: base + node2.end };
5424
+ }
5425
+ function shiftAbsolute(node2, at, delta) {
5426
+ const s = node2.start >= at ? node2.start + delta : node2.start;
5427
+ const e = node2.end >= at ? node2.end + delta : node2.end;
5428
+ return {
5429
+ start: s,
5430
+ end: e,
5431
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
5432
+ };
5433
+ }
5434
+ function applyEdit(root, at, delta, stats) {
5435
+ return editNode(root, at, delta, 0, 0, stats);
5436
+ }
5437
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
5438
+ const absStart = parentBase + node2.start;
5439
+ const absEnd = parentBase + node2.end;
5440
+ if (absEnd < at) return node2;
5441
+ const startShifts = absStart >= at;
5442
+ if (startShifts && parentShift === delta) return node2;
5443
+ if (stats) stats.allocated++;
5444
+ const newAbsStart = startShifts ? absStart + delta : absStart;
5445
+ const newAbsEnd = absEnd + delta;
5446
+ const newParentBase = parentBase + parentShift;
5447
+ const newStart = newAbsStart - newParentBase;
5448
+ const newEnd = newAbsEnd - newParentBase;
5449
+ const ownShift = newAbsStart - absStart;
5450
+ let changed = false;
5451
+ const newChildren = [];
5452
+ for (const c of node2.children) {
5453
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
5454
+ if (nc !== c) changed = true;
5455
+ newChildren.push(nc);
5456
+ }
5457
+ return {
5458
+ start: newStart,
5459
+ end: newEnd,
5460
+ children: changed ? newChildren : node2.children
5461
+ };
5462
+ }
5463
+
5464
+ // src/index.ts
5465
+ registerRegexAnalyzer(firstSetFromRegex);
4676
5466
  //# sourceMappingURL=index.cjs.map