parseman 0.45.0 → 0.46.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 (64) hide show
  1. package/dist/analysis/choice-cost-gate.d.ts +158 -0
  2. package/dist/analysis/choice-cost-gate.d.ts.map +1 -0
  3. package/dist/analysis/choice-cost-render.d.ts +88 -0
  4. package/dist/analysis/choice-cost-render.d.ts.map +1 -0
  5. package/dist/analysis/choice-cost.d.ts +383 -0
  6. package/dist/analysis/choice-cost.d.ts.map +1 -0
  7. package/dist/analysis/corpus.d.ts +73 -0
  8. package/dist/analysis/corpus.d.ts.map +1 -0
  9. package/dist/analysis/diagnose-render.d.ts +65 -0
  10. package/dist/analysis/diagnose-render.d.ts.map +1 -0
  11. package/dist/analysis/diagnose.d.ts +15 -0
  12. package/dist/analysis/diagnose.d.ts.map +1 -1
  13. package/dist/analysis/diagnostics.cjs +11272 -0
  14. package/dist/analysis/diagnostics.cjs.map +7 -0
  15. package/dist/analysis/diagnostics.d.ts +27 -0
  16. package/dist/analysis/diagnostics.d.ts.map +1 -0
  17. package/dist/analysis/diagnostics.js +11249 -0
  18. package/dist/analysis/diagnostics.js.map +7 -0
  19. package/dist/analysis/fix-render.d.ts +43 -0
  20. package/dist/analysis/fix-render.d.ts.map +1 -0
  21. package/dist/analysis/fix.d.ts +172 -0
  22. package/dist/analysis/fix.d.ts.map +1 -0
  23. package/dist/analysis/gating.d.ts +8 -0
  24. package/dist/analysis/gating.d.ts.map +1 -1
  25. package/dist/analysis/rebuild.d.ts +61 -0
  26. package/dist/analysis/rebuild.d.ts.map +1 -0
  27. package/dist/analysis/terminal.d.ts +142 -0
  28. package/dist/analysis/terminal.d.ts.map +1 -0
  29. package/dist/cli/index.cjs +12507 -0
  30. package/dist/cli/index.cjs.map +7 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +12484 -0
  34. package/dist/cli/index.js.map +7 -0
  35. package/dist/compiler/build-arity.d.ts.map +1 -1
  36. package/dist/compiler/linker.d.ts +44 -6
  37. package/dist/compiler/linker.d.ts.map +1 -1
  38. package/dist/compiler/module-hoist.d.ts +106 -0
  39. package/dist/compiler/module-hoist.d.ts.map +1 -0
  40. package/dist/index.cjs +1297 -297
  41. package/dist/index.cjs.map +4 -4
  42. package/dist/index.d.ts +8 -2
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +1297 -297
  45. package/dist/index.js.map +4 -4
  46. package/dist/language-service/index.cjs.map +2 -2
  47. package/dist/language-service/index.js.map +2 -2
  48. package/dist/plugin/evaluator.d.ts +1 -1
  49. package/dist/plugin/evaluator.d.ts.map +1 -1
  50. package/dist/plugin/free-identifiers.d.ts +23 -0
  51. package/dist/plugin/free-identifiers.d.ts.map +1 -0
  52. package/dist/plugin/index.cjs +429 -43
  53. package/dist/plugin/index.cjs.map +4 -4
  54. package/dist/plugin/index.d.ts.map +1 -1
  55. package/dist/plugin/index.js +429 -43
  56. package/dist/plugin/index.js.map +4 -4
  57. package/dist/plugin/reducer-resolver.d.ts +17 -1
  58. package/dist/plugin/reducer-resolver.d.ts.map +1 -1
  59. package/dist/spec/index.cjs +2 -1
  60. package/dist/spec/index.cjs.map +2 -2
  61. package/dist/spec/index.js +2 -1
  62. package/dist/spec/index.js.map +2 -2
  63. package/dist/version.d.ts +1 -1
  64. package/package.json +18 -3
@@ -2,7 +2,7 @@
2
2
  import * as fs2 from "node:fs";
3
3
  import * as path2 from "node:path";
4
4
  import { createUnplugin } from "unplugin";
5
- import { parseSync as parseSync3 } from "oxc-parser";
5
+ import { parseSync as parseSync4 } from "oxc-parser";
6
6
  import { ResolverFactory } from "oxc-resolver";
7
7
  import MagicString from "magic-string";
8
8
 
@@ -1492,8 +1492,8 @@ function classifyTriviaArm(arm) {
1492
1492
  }
1493
1493
  const lc = /^(\\?.)\[\^\\n\\r\]\*$/.exec(arm);
1494
1494
  if (lc) {
1495
- const marker = lc[1];
1496
- return { kind: "untilLineBreak", code: (marker.length === 2 ? marker[1] : marker[0]).charCodeAt(0) };
1495
+ const marker2 = lc[1];
1496
+ return { kind: "untilLineBreak", code: (marker2.length === 2 ? marker2[1] : marker2[0]).charCodeAt(0) };
1497
1497
  }
1498
1498
  return null;
1499
1499
  }
@@ -3391,7 +3391,8 @@ var RESOLVER_REASONS = {
3391
3391
  "unresolved-import": "its module could not be resolved or parsed",
3392
3392
  "not-found": "the name did not resolve to any binding",
3393
3393
  "computed": "it is a computed expression",
3394
- "foreign-source": "it is named inside an imported factory, whose offsets the entry-file resolver cannot index"
3394
+ "foreign-source": "it is named in a module the resolver was never told about, so its offsets index no scope tree the resolver holds",
3395
+ "ambiguous-source": "two distinct registered modules hold byte-identical text, so that text no longer names the module its offsets index"
3395
3396
  };
3396
3397
  function unconfirmableReason(def, src) {
3397
3398
  const declared = def.buildArityUnresolved;
@@ -3920,7 +3921,7 @@ function leaf(root, fn) {
3920
3921
  }
3921
3922
 
3922
3923
  // src/version.ts
3923
- var PARSEMAN_VERSION = "0.45.0";
3924
+ var PARSEMAN_VERSION = "0.46.0";
3924
3925
 
3925
3926
  // src/cst/host-mode.ts
3926
3927
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -5540,15 +5541,15 @@ function parseStringShape(source) {
5540
5541
  if (source.length < 3 || source[source.length - 1] !== q) return null;
5541
5542
  const inner = source.slice(1, -1);
5542
5543
  const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
5543
- const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5544
+ const ESC2 = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
5544
5545
  let body;
5545
5546
  let esc;
5546
- let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
5547
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC2}\\)\\*$`).exec(inner);
5547
5548
  if (m) {
5548
5549
  body = m[1];
5549
5550
  esc = m[2];
5550
5551
  } else {
5551
- m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
5552
+ m = new RegExp(`^\\((?:\\?:)?${ESC2}\\|${CLS}\\)\\*$`).exec(inner);
5552
5553
  if (m) {
5553
5554
  esc = m[1];
5554
5555
  body = m[2];
@@ -11224,7 +11225,7 @@ function pickPieces(pieces, names) {
11224
11225
  const filt = (m, pc) => new Map([...m].filter(([k]) => keep.has(k) && winner.get(k) === pc));
11225
11226
  return pieces.map((pc) => ({ ...pc, keys: pc.keys.filter((k) => keep.has(k) && winner.get(k) === pc), ruleFns: filt(pc.ruleFns, pc), wrappers: filt(pc.wrappers, pc), deps: filt(pc.deps, pc), nodeMeta: filt(pc.nodeMeta, pc) })).filter((pc) => pc.keys.length > 0);
11226
11227
  }
11227
- function fusedBody(pieces) {
11228
+ function fusedBody(pieces, hoist) {
11228
11229
  for (const p of pieces) {
11229
11230
  if (p.v === void 0) {
11230
11231
  throw new Error(
@@ -11264,8 +11265,7 @@ function fusedBody(pieces) {
11264
11265
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
11265
11266
  const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
11266
11267
  const { mode, elided } = hostModeOfPieces(pieces);
11267
- const rawBody = [
11268
- ...lines,
11268
+ const tailSrc = [
11269
11269
  "const _map = {",
11270
11270
  wrapperEntries.join(",\n"),
11271
11271
  "}",
@@ -11281,6 +11281,7 @@ function fusedBody(pieces) {
11281
11281
  "}",
11282
11282
  "return _map"
11283
11283
  ].join("\n");
11284
+ const rawBody = [...lines, tailSrc].join("\n");
11284
11285
  const ANY2 = { kind: "any" };
11285
11286
  const EMPTY3 = { kind: "empty" };
11286
11287
  const recipes = /* @__PURE__ */ new Map();
@@ -11321,7 +11322,7 @@ function fusedBody(pieces) {
11321
11322
  }
11322
11323
  if (!changed) break;
11323
11324
  }
11324
- const body = rawBody.replace(
11325
+ const resolveFS = (src) => src.replace(
11325
11326
  // Rule name + code-point var are both JS identifiers (rule names are validated
11326
11327
  // at compile time — see assertRuleName in codegen), so an identifier class
11327
11328
  // matches every well-formed placeholder.
@@ -11332,6 +11333,7 @@ function fusedBody(pieces) {
11332
11333
  return `(${firstSetCond(codevar, fs3)})`;
11333
11334
  }
11334
11335
  );
11336
+ const body = hoist === void 0 ? resolveFS(rawBody) : [...hoist.claim(lines.map(resolveFS)), resolveFS(tailSrc)].join("\n");
11335
11337
  const env = {};
11336
11338
  for (const p of contributingPieces) {
11337
11339
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -11349,8 +11351,8 @@ function hostModeOfPieces(pieces) {
11349
11351
  }
11350
11352
  return { mode, elided: elided.length > 0 };
11351
11353
  }
11352
- function emitFusedSource(pieces) {
11353
- const { body, env } = fusedBody(pieces);
11354
+ function emitFusedSource(pieces, hoist) {
11355
+ const { body, env } = fusedBody(pieces, hoist);
11354
11356
  if (Object.keys(env).length > 0) {
11355
11357
  throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
11356
11358
  }
@@ -11373,6 +11375,34 @@ function materializePiece(p, trivia2, captureTerminals = false, hostMode) {
11373
11375
  return pieces;
11374
11376
  }
11375
11377
 
11378
+ // src/functional/run.ts
11379
+ var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
11380
+ var REMOVED_RUN_RESULT_FIELDS = [
11381
+ [
11382
+ "triviaMap",
11383
+ `It was a dense root-trivia index built on every parse; root trivia is now an OPT-IN sparse capture, because most grammars paid for an index they never read. Migration: name the trivia labels your grammar defines \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia\`, whose \`.index\` carries the labels and gap lookups \`triviaMap\` exposed.`
11384
+ ],
11385
+ [
11386
+ "triviaLog",
11387
+ `It was the flat \`start, end[, kindIndex]\` log every other trivia view was derived from, and it is no longer allocated at all \u2014 not empty, absent. Migration: request the labels you need \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia.rows\`. Do NOT reuse a stride-2/3 reader: rows are a different width and a different unit (one row per SELECTED marker, not one per trivia chunk), so an old loop reads confidently wrong offsets rather than failing.`
11388
+ ],
11389
+ [
11390
+ "triviaKindLabels",
11391
+ `It was already optional, so nothing ever flagged its disappearance \u2014 and \`triviaKindMask(undefined, \u2026)\` means "capture everything", so reading it now silently WIDENS capture instead of erroring. Migration: request labels \u2014 ${SELECT_EXAMPLE} \u2014 then the set you asked for is \`RunResult.rootTrivia.select\` and the resolved table is \`RunResult.rootTrivia.index.labels\`.`
11392
+ ]
11393
+ ];
11394
+
11395
+ // src/analysis/choice-cost-render.ts
11396
+ var ESC = "\x1B";
11397
+ var ANSI = {
11398
+ dim: `${ESC}[2m`,
11399
+ bold: `${ESC}[1m`,
11400
+ red: `${ESC}[31m`,
11401
+ yellow: `${ESC}[33m`,
11402
+ cyan: `${ESC}[36m`,
11403
+ reset: `${ESC}[0m`
11404
+ };
11405
+
11376
11406
  // src/combinators/gate.ts
11377
11407
  function gate(predicate) {
11378
11408
  const meta = {
@@ -11392,23 +11422,6 @@ function gate(predicate) {
11392
11422
  };
11393
11423
  }
11394
11424
 
11395
- // src/functional/run.ts
11396
- var SELECT_EXAMPLE = "run(entry, input, { rootTrivia: { select: ['blockComment', 'lineComment'] } })";
11397
- var REMOVED_RUN_RESULT_FIELDS = [
11398
- [
11399
- "triviaMap",
11400
- `It was a dense root-trivia index built on every parse; root trivia is now an OPT-IN sparse capture, because most grammars paid for an index they never read. Migration: name the trivia labels your grammar defines \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia\`, whose \`.index\` carries the labels and gap lookups \`triviaMap\` exposed.`
11401
- ],
11402
- [
11403
- "triviaLog",
11404
- `It was the flat \`start, end[, kindIndex]\` log every other trivia view was derived from, and it is no longer allocated at all \u2014 not empty, absent. Migration: request the labels you need \u2014 ${SELECT_EXAMPLE} \u2014 and read \`RunResult.rootTrivia.rows\`. Do NOT reuse a stride-2/3 reader: rows are a different width and a different unit (one row per SELECTED marker, not one per trivia chunk), so an old loop reads confidently wrong offsets rather than failing.`
11405
- ],
11406
- [
11407
- "triviaKindLabels",
11408
- `It was already optional, so nothing ever flagged its disappearance \u2014 and \`triviaKindMask(undefined, \u2026)\` means "capture everything", so reading it now silently WIDENS capture instead of erroring. Migration: request labels \u2014 ${SELECT_EXAMPLE} \u2014 then the set you asked for is \`RunResult.rootTrivia.select\` and the resolved table is \`RunResult.rootTrivia.index.labels\`.`
11409
- ]
11410
- ];
11411
-
11412
11425
  // src/index.ts
11413
11426
  registerRegexAnalyzer(firstSetFromRegex);
11414
11427
 
@@ -11602,8 +11615,10 @@ function stripTsFromSource(node2, code) {
11602
11615
  return out + code.slice(cur, end);
11603
11616
  }
11604
11617
  var _reducers = null;
11605
- function setReducerResolver(r) {
11618
+ var _entrySource = null;
11619
+ function setReducerResolver(r, entrySource = null) {
11606
11620
  _reducers = r;
11621
+ _entrySource = entrySource;
11607
11622
  }
11608
11623
  function isWordFactory(v2) {
11609
11624
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
@@ -11975,6 +11990,9 @@ function exprToCombi(node2, scope, code, mfs) {
11975
11990
  const resolved = _reducers?.resolve(buildSrc, be.start, code);
11976
11991
  if (resolved) {
11977
11992
  if (resolved.src !== null) combi._def.buildSigSrc = resolved.src;
11993
+ if (_entrySource !== null && code !== _entrySource && resolved.src !== null && be.type === "Identifier") {
11994
+ combi._def.buildSrc = resolved.src;
11995
+ }
11978
11996
  if (combi._def.buildArity === void 0 && resolved.arity !== null) combi._def.buildArity = resolved.arity;
11979
11997
  if (resolved.reason !== void 0) combi._def.buildArityUnresolved = resolved.reason;
11980
11998
  }
@@ -12790,6 +12808,8 @@ function createReducerResolver(entryFile, body, src) {
12790
12808
  const scopes = buildScopeTree(body, src.length);
12791
12809
  markReassignments(body, scopes);
12792
12810
  const entry = { file: entryFile, src, body, scopes };
12811
+ const registered = /* @__PURE__ */ new Map();
12812
+ const ambiguous = /* @__PURE__ */ new Set();
12793
12813
  const fromBinding = (mod, binding, memberName, hops, seen) => {
12794
12814
  if (hops > MAX_HOPS) return { arity: null, src: null, reason: "not-found" };
12795
12815
  if (binding.reassigned) return { arity: null, src: null, reason: "reassigned" };
@@ -12834,19 +12854,342 @@ function createReducerResolver(entryFile, body, src) {
12834
12854
  return { arity, src: mod.src.slice(init.start, init.end), ...reason ? { reason } : {} };
12835
12855
  };
12836
12856
  return {
12857
+ register(file) {
12858
+ const mod = analyseModuleFile(file);
12859
+ if (!mod) return false;
12860
+ if (mod.src === src) return true;
12861
+ const prior = registered.get(mod.src);
12862
+ if (prior !== void 0 && prior.file !== mod.file) {
12863
+ ambiguous.add(mod.src);
12864
+ registered.delete(mod.src);
12865
+ return false;
12866
+ }
12867
+ if (!ambiguous.has(mod.src)) registered.set(mod.src, mod);
12868
+ return !ambiguous.has(mod.src);
12869
+ },
12837
12870
  resolve(exprSrc, offset, offsetSrc) {
12838
12871
  const text = exprSrc.trim();
12839
12872
  const ident = IDENT_RE.test(text) ? text : null;
12840
12873
  const member = ident ? null : MEMBER_RE.exec(text);
12841
12874
  if (!ident && !member) return null;
12842
- if (offsetSrc !== src) return { arity: null, src: null, reason: "foreign-source" };
12875
+ const mod = offsetSrc === src ? entry : registered.get(offsetSrc);
12876
+ if (!mod) {
12877
+ return { arity: null, src: null, reason: ambiguous.has(offsetSrc) ? "ambiguous-source" : "foreign-source" };
12878
+ }
12843
12879
  const name = ident ?? member[1];
12844
12880
  const memberName = ident ? void 0 : member[2];
12845
- const hit = lookup(scopeAt(scopes, offset), name);
12881
+ const hit = lookup(scopeAt(mod.scopes, offset), name);
12846
12882
  if (!hit) return { arity: null, src: null, reason: "not-found" };
12847
- return fromBinding(entry, hit.binding, memberName, 0, /* @__PURE__ */ new Set());
12883
+ return fromBinding(mod, hit.binding, memberName, 0, /* @__PURE__ */ new Set());
12884
+ }
12885
+ };
12886
+ }
12887
+
12888
+ // src/plugin/free-identifiers.ts
12889
+ import { parseSync as parseSync3 } from "oxc-parser";
12890
+ var isNode = (v2) => typeof v2 === "object" && v2 !== null && typeof v2.type === "string";
12891
+ var TYPE_KEYS = /* @__PURE__ */ new Set([
12892
+ "typeAnnotation",
12893
+ "returnType",
12894
+ "typeParameters",
12895
+ "typeArguments",
12896
+ "superTypeArguments",
12897
+ "typeName",
12898
+ "implements",
12899
+ "extends",
12900
+ "accessibility"
12901
+ ]);
12902
+ var TS_VALUE_WRAPPERS = /* @__PURE__ */ new Set([
12903
+ "TSAsExpression",
12904
+ "TSSatisfiesExpression",
12905
+ "TSNonNullExpression",
12906
+ "TSInstantiationExpression",
12907
+ "TSTypeAssertion"
12908
+ ]);
12909
+ var HOST_GLOBALS = /* @__PURE__ */ new Set([
12910
+ ...Object.getOwnPropertyNames(globalThis),
12911
+ "undefined",
12912
+ "NaN",
12913
+ "Infinity",
12914
+ "globalThis",
12915
+ "arguments",
12916
+ "window",
12917
+ "document",
12918
+ "self",
12919
+ "navigator",
12920
+ "location",
12921
+ "top",
12922
+ "parent",
12923
+ "require",
12924
+ "module",
12925
+ "exports",
12926
+ "__dirname",
12927
+ "__filename"
12928
+ ]);
12929
+ function bindPattern(pattern, out) {
12930
+ if (!isNode(pattern)) return;
12931
+ switch (pattern.type) {
12932
+ case "Identifier":
12933
+ case "BindingIdentifier":
12934
+ out.add(String(pattern.name));
12935
+ return;
12936
+ case "ObjectPattern":
12937
+ for (const prop of pattern.properties ?? []) {
12938
+ if (!isNode(prop)) continue;
12939
+ if (prop.type === "RestElement" || prop.type === "BindingRestElement") bindPattern(prop.argument, out);
12940
+ else bindPattern(prop.value, out);
12941
+ }
12942
+ return;
12943
+ case "ArrayPattern":
12944
+ for (const el of pattern.elements ?? []) bindPattern(el, out);
12945
+ return;
12946
+ case "AssignmentPattern":
12947
+ bindPattern(pattern.left, out);
12948
+ return;
12949
+ case "RestElement":
12950
+ case "BindingRestElement":
12951
+ bindPattern(pattern.argument, out);
12952
+ return;
12953
+ case "TSParameterProperty":
12954
+ bindPattern(pattern.parameter, out);
12955
+ return;
12956
+ default:
12957
+ return;
12958
+ }
12959
+ }
12960
+ function collectBindings(node2, out) {
12961
+ if (Array.isArray(node2)) {
12962
+ for (const child of node2) collectBindings(child, out);
12963
+ return;
12964
+ }
12965
+ if (!isNode(node2)) return;
12966
+ switch (node2.type) {
12967
+ case "ImportSpecifier":
12968
+ case "ImportDefaultSpecifier":
12969
+ case "ImportNamespaceSpecifier":
12970
+ if (isNode(node2.local)) out.add(String(node2.local.name));
12971
+ break;
12972
+ case "VariableDeclarator":
12973
+ bindPattern(node2.id, out);
12974
+ break;
12975
+ case "FunctionDeclaration":
12976
+ case "FunctionExpression":
12977
+ case "TSDeclareFunction":
12978
+ case "ArrowFunctionExpression":
12979
+ if (isNode(node2.id)) out.add(String(node2.id.name));
12980
+ for (const p of node2.params?.items ?? node2.params ?? []) bindPattern(p, out);
12981
+ if (isNode(node2.params) && node2.params.rest) bindPattern(node2.params.rest, out);
12982
+ break;
12983
+ case "ClassDeclaration":
12984
+ case "ClassExpression":
12985
+ if (isNode(node2.id)) out.add(String(node2.id.name));
12986
+ break;
12987
+ case "CatchClause":
12988
+ if (node2.param) {
12989
+ bindPattern(isNode(node2.param) && node2.param.pattern ? node2.param.pattern : node2.param, out);
12990
+ }
12991
+ break;
12992
+ default:
12993
+ break;
12994
+ }
12995
+ for (const key of Object.keys(node2)) {
12996
+ if (key === "type" || key === "start" || key === "end") continue;
12997
+ collectBindings(node2[key], out);
12998
+ }
12999
+ }
13000
+ var FUNCTION_TYPES2 = /* @__PURE__ */ new Set(["ArrowFunctionExpression", "FunctionExpression", "FunctionDeclaration"]);
13001
+ function describeTopLevel(stmt) {
13002
+ const declared = /* @__PURE__ */ new Set();
13003
+ let target = stmt;
13004
+ let prefix = "";
13005
+ let exported = false;
13006
+ if (stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration") {
13007
+ exported = true;
13008
+ prefix = "export ";
13009
+ if (!isNode(stmt.declaration)) return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}{ \u2026 }` };
13010
+ target = stmt.declaration;
13011
+ }
13012
+ if (target.type === "VariableDeclaration") {
13013
+ const decls = (target.declarations ?? []).filter(isNode);
13014
+ for (const d of decls) bindPattern(d.id, declared);
13015
+ const declaresOnlyFunctions = decls.length > 0 && decls.every((d) => isNode(d.init) && FUNCTION_TYPES2.has(d.init.type));
13016
+ const name = [...declared][0];
13017
+ return {
13018
+ declared,
13019
+ exported,
13020
+ declaresOnlyFunctions,
13021
+ label: name ? `${prefix}${String(target.kind)} ${name}` : null
13022
+ };
13023
+ }
13024
+ if (target.type === "FunctionDeclaration" && isNode(target.id)) {
13025
+ declared.add(String(target.id.name));
13026
+ return { declared, exported, declaresOnlyFunctions: true, label: `${prefix}function ${String(target.id.name)}` };
13027
+ }
13028
+ if (target.type === "ClassDeclaration" && isNode(target.id)) {
13029
+ declared.add(String(target.id.name));
13030
+ return { declared, exported, declaresOnlyFunctions: false, label: `${prefix}class ${String(target.id.name)}` };
13031
+ }
13032
+ return { declared, exported, declaresOnlyFunctions: false, label: null };
13033
+ }
13034
+ function findFreeIdentifiers(code, filename) {
13035
+ let parsed;
13036
+ try {
13037
+ parsed = parseSync3(filename, code);
13038
+ } catch {
13039
+ return [];
13040
+ }
13041
+ if (parsed.errors.length > 0) return [];
13042
+ const bound = /* @__PURE__ */ new Set();
13043
+ collectBindings(parsed.program.body, bound);
13044
+ const lineStarts = [0];
13045
+ for (let i = 0; i < code.length; i++) if (code.charCodeAt(i) === 10) lineStarts.push(i + 1);
13046
+ const positionOf = (offset) => {
13047
+ let lo = 0;
13048
+ let hi = lineStarts.length - 1;
13049
+ while (lo < hi) {
13050
+ const mid = lo + hi + 1 >> 1;
13051
+ if (lineStarts[mid] <= offset) lo = mid;
13052
+ else hi = mid - 1;
13053
+ }
13054
+ return { line: lo + 1, column: offset - lineStarts[lo] + 1 };
13055
+ };
13056
+ const statements = parsed.program.body.filter(isNode);
13057
+ const tops = statements.map(describeTopLevel);
13058
+ const candidates = [];
13059
+ const seen = /* @__PURE__ */ new Set();
13060
+ const readIn = /* @__PURE__ */ new Map();
13061
+ const visit = (node2, stmt, insideFunction) => {
13062
+ if (Array.isArray(node2)) {
13063
+ for (const child of node2) visit(child, stmt, insideFunction);
13064
+ return;
13065
+ }
13066
+ if (!isNode(node2)) return;
13067
+ const type = node2.type;
13068
+ if (type === "ImportDeclaration" || type === "ExportAllDeclaration") return;
13069
+ if (type === "MetaProperty" || type === "PrivateIdentifier") return;
13070
+ if (type === "ExportNamedDeclaration" && node2.source) return;
13071
+ if (type.startsWith("TS")) {
13072
+ if (TS_VALUE_WRAPPERS.has(type)) visit(node2.expression, stmt, insideFunction);
13073
+ return;
13074
+ }
13075
+ if (type === "Identifier" || type === "IdentifierReference") {
13076
+ const name = String(node2.name);
13077
+ let readers = readIn.get(name);
13078
+ if (!readers) readIn.set(name, readers = /* @__PURE__ */ new Set());
13079
+ readers.add(stmt);
13080
+ if (bound.has(name) || HOST_GLOBALS.has(name)) return;
13081
+ const offset = typeof node2.start === "number" ? node2.start : 0;
13082
+ const { line, column } = positionOf(offset);
13083
+ const key = `${name}@${line}:${column}`;
13084
+ if (seen.has(key)) return;
13085
+ seen.add(key);
13086
+ candidates.push({ name, line, column, enclosing: tops[stmt].label, stmt, insideFunction });
13087
+ return;
13088
+ }
13089
+ const nowInsideFunction = insideFunction || FUNCTION_TYPES2.has(type);
13090
+ for (const key of Object.keys(node2)) {
13091
+ if (key === "type" || key === "start" || key === "end") continue;
13092
+ if (TYPE_KEYS.has(key)) continue;
13093
+ if (key === "key" && node2.computed !== true) continue;
13094
+ if (key === "property" && node2.computed !== true) continue;
13095
+ if (key === "label") continue;
13096
+ if (key === "exported") continue;
13097
+ if (key === "imported") continue;
13098
+ visit(node2[key], stmt, nowInsideFunction);
13099
+ }
13100
+ };
13101
+ for (let i = 0; i < statements.length; i++) visit(statements[i], i, false);
13102
+ return candidates.filter((c) => {
13103
+ const top = tops[c.stmt];
13104
+ if (!c.insideFunction || top.exported || !top.declaresOnlyFunctions || top.declared.size === 0) return true;
13105
+ for (const name of top.declared) {
13106
+ const readers = readIn.get(name);
13107
+ if (readers && [...readers].some((i) => i !== c.stmt)) return true;
13108
+ }
13109
+ return false;
13110
+ }).map(({ name, line, column, enclosing }) => ({ name, line, column, enclosing }));
13111
+ }
13112
+
13113
+ // src/compiler/module-hoist.ts
13114
+ var marker = (id) => `/*@pmh:${id}@*/`;
13115
+ var MARKER_RE = /\/\*@pmh:(\d+)@\*\/\n?/g;
13116
+ var HOIST_MARKER_PROBE = /\/\*@pmh:\d+@\*\//;
13117
+ var DECL_RE = /^(?:const|let|var|function\*?)\s+([A-Za-z_$][\w$]*)/;
13118
+ var ALWAYS_LOCAL = /* @__PURE__ */ new Set(["_map", "_k", "_env"]);
13119
+ function declaredNames(text) {
13120
+ const out = [];
13121
+ for (const line of text.split("\n")) {
13122
+ const m = DECL_RE.exec(line);
13123
+ if (m) out.push(m[1]);
13124
+ }
13125
+ return out;
13126
+ }
13127
+ function createModuleHoist() {
13128
+ const texts = [];
13129
+ const claim = (decls) => decls.map((text) => {
13130
+ const id = texts.length;
13131
+ texts.push(text);
13132
+ return marker(id);
13133
+ });
13134
+ const finalize = () => {
13135
+ const nameTexts = /* @__PURE__ */ new Map();
13136
+ const textCount = /* @__PURE__ */ new Map();
13137
+ const textNames = /* @__PURE__ */ new Map();
13138
+ const order = [];
13139
+ for (const text of texts) {
13140
+ const seen = textCount.get(text);
13141
+ textCount.set(text, (seen ?? 0) + 1);
13142
+ if (seen === void 0) {
13143
+ order.push(text);
13144
+ const names = declaredNames(text);
13145
+ textNames.set(text, names);
13146
+ for (const n of names) {
13147
+ const s = nameTexts.get(n);
13148
+ if (s) s.add(text);
13149
+ else nameTexts.set(n, /* @__PURE__ */ new Set([text]));
13150
+ }
13151
+ }
13152
+ }
13153
+ const hoisted = new Set(
13154
+ order.filter(
13155
+ (text) => (textCount.get(text) ?? 0) >= 2 && textNames.get(text).length > 0 && textNames.get(text).every((n) => !ALWAYS_LOCAL.has(n) && nameTexts.get(n).size === 1)
13156
+ )
13157
+ );
13158
+ for (; ; ) {
13159
+ const localNames = [];
13160
+ for (const [n, ts] of nameTexts) {
13161
+ if (ts.size !== 1 || !hoisted.has([...ts][0])) localNames.push(n);
13162
+ }
13163
+ let changed = false;
13164
+ for (const text of [...hoisted]) {
13165
+ if (localNames.some((n) => new RegExp(`\\b${escapeName(n)}\\b`).test(text))) {
13166
+ hoisted.delete(text);
13167
+ changed = true;
13168
+ }
13169
+ }
13170
+ if (!changed) break;
12848
13171
  }
13172
+ const preludeTexts = order.filter((t) => hoisted.has(t));
13173
+ const prelude = preludeTexts.length === 0 ? "" : `${preludeTexts.join("\n")}
13174
+ `;
13175
+ const removed = texts.reduce((n, t) => hoisted.has(t) ? n + t.length + 1 : n, 0);
13176
+ return {
13177
+ prelude,
13178
+ savedBytes: removed - prelude.length,
13179
+ hoistedNames: preludeTexts.flatMap((t) => textNames.get(t)),
13180
+ resolve: (src) => src.replace(MARKER_RE, (m, idStr) => {
13181
+ const text = texts[Number(idStr)];
13182
+ if (text === void 0) throw new Error(`parseman: unknown hoist marker ${m}`);
13183
+ if (hoisted.has(text)) return "";
13184
+ return m.endsWith("\n") ? `${text}
13185
+ ` : text;
13186
+ })
13187
+ };
12849
13188
  };
13189
+ return { claim, finalize };
13190
+ }
13191
+ function escapeName(n) {
13192
+ return n.replace(/\$/g, "\\$");
12850
13193
  }
12851
13194
 
12852
13195
  // src/plugin/index.ts
@@ -12942,7 +13285,7 @@ function parseModuleCached(filePath) {
12942
13285
  let parsed = null;
12943
13286
  try {
12944
13287
  const src = fs2.readFileSync(filePath, "utf8");
12945
- const r = parseSync3(filePath, src);
13288
+ const r = parseSync4(filePath, src);
12946
13289
  if (r.errors.length === 0) parsed = { body: r.program.body, src };
12947
13290
  } catch {
12948
13291
  }
@@ -13083,7 +13426,7 @@ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recov
13083
13426
  const source = fs2.readFileSync(file, "utf8");
13084
13427
  const transformed = transformMacro(source, file, moduleAliases, warnUnloweredRegex, recovery);
13085
13428
  if (!transformed || transformed.warnings.length > 0) return null;
13086
- const parsed = parseSync3(file, transformed.code);
13429
+ const parsed = parseSync4(file, transformed.code);
13087
13430
  parsedModule = parsed.errors.length === 0 ? { body: parsed.program.body, src: transformed.code } : null;
13088
13431
  return parsedModule;
13089
13432
  } catch {
@@ -13107,7 +13450,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13107
13450
  function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recovery, grammarCoverage) {
13108
13451
  let result;
13109
13452
  try {
13110
- result = parseSync3(id, code);
13453
+ result = parseSync4(id, code);
13111
13454
  } catch {
13112
13455
  return null;
13113
13456
  }
@@ -13164,7 +13507,8 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13164
13507
  }
13165
13508
  }
13166
13509
  }
13167
- setReducerResolver(createReducerResolver(id, body, code));
13510
+ const reducerResolver = createReducerResolver(id, body, code);
13511
+ setReducerResolver(reducerResolver, code);
13168
13512
  const topLevelFunction = (moduleBody, name) => {
13169
13513
  for (const st of moduleBody) {
13170
13514
  const decl = st.type === "ExportNamedDeclaration" ? st.declaration : st;
@@ -13259,15 +13603,18 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13259
13603
  scope: factoryScope,
13260
13604
  imported: true
13261
13605
  });
13606
+ reducerResolver.register(file);
13262
13607
  }
13263
13608
  const scope = /* @__PURE__ */ new Map();
13264
13609
  const replacements = [];
13265
13610
  const warnings = [];
13611
+ const moduleHoist = grammarCoverage ? void 0 : createModuleHoist();
13266
13612
  beginLoweringCapture();
13267
13613
  beginInlineCapCapture();
13268
13614
  beginDegradationCapture();
13269
13615
  let anyUnresolved = false;
13270
13616
  let keepMacroImport = false;
13617
+ const exportedFactories = [];
13271
13618
  let runtimeComposeFallback = false;
13272
13619
  let ruleMapHolderCounter = 0;
13273
13620
  const lineOf = (pos) => {
@@ -13626,7 +13973,7 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13626
13973
  const pieces = materializeCarried(carried, composing, false, cHostMode);
13627
13974
  try {
13628
13975
  return {
13629
- replacement: emitFusedSource(pieces),
13976
+ replacement: emitFusedSource(pieces, moduleHoist),
13630
13977
  carried,
13631
13978
  ...composing ? { trivia: composing } : {},
13632
13979
  ...importedFactories.length ? { importedFactories } : {}
@@ -13694,7 +14041,7 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13694
14041
  warn(init.start, "composeLeaf(): every pre-final grammar must explicitly prove recognition-only");
13695
14042
  return null;
13696
14043
  }
13697
- const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece]));
14044
+ const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece], moduleHoist));
13698
14045
  return {
13699
14046
  replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement, `${id} composeLeaf()`)),
13700
14047
  ...importedFactories.length ? { importedFactories } : {}
@@ -13879,7 +14226,10 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13879
14226
  scope.set(varName, staticValue);
13880
14227
  continue;
13881
14228
  }
13882
- if (factoryDecls.has(varName)) continue;
14229
+ if (factoryDecls.has(varName)) {
14230
+ if (exportPrefix) exportedFactories.push({ name: varName, pos: init.start });
14231
+ continue;
14232
+ }
13883
14233
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
13884
14234
  continue;
13885
14235
  }
@@ -13950,6 +14300,14 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13950
14300
  for (const imp of macroImports) {
13951
14301
  imp.fullyResolved = !anyUnresolved && !keepMacroImport;
13952
14302
  }
14303
+ if (exportedFactories.length > 0 && macroImports.some((imp) => imp.fullyResolved)) {
14304
+ const which = exportedFactories.map((f) => ` - ${id}:${lineOf(f.pos)} \u2014 export const ${f.name}`).join("\n");
14305
+ throw new Error(
14306
+ `${id} \u2014 a rules() factory cannot be exported; lowering leaves its body verbatim, so the export would ship a binding that throws ReferenceError on first call:
14307
+ ${which}
14308
+ Drop the \`export\`. The factory is macro-only; \`rules(${exportedFactories[0].name})\` is lowered in place and needs no runtime value.`
14309
+ );
14310
+ }
13953
14311
  const ms = new MagicString(code);
13954
14312
  const replacementRanges = runtimeComposeFallback ? [] : replacements.map(({ start, end }) => ({ start, end }));
13955
14313
  const isInsideReplacement = (start, end) => replacementRanges.some((r) => start >= r.start && end <= r.end);
@@ -13998,8 +14356,21 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
13998
14356
  }
13999
14357
  }
14000
14358
  const applied = runtimeComposeFallback ? [] : replacements;
14359
+ const hoisted = applied.length > 0 ? moduleHoist?.finalize() : void 0;
14001
14360
  for (const { start, end, replacement } of applied.slice().sort((a, b) => b.start - a.start)) {
14002
- ms.overwrite(start, end, replacement);
14361
+ ms.overwrite(start, end, hoisted ? hoisted.resolve(replacement) : replacement);
14362
+ }
14363
+ if (hoisted !== void 0 && hoisted.prelude !== "") {
14364
+ const claiming = applied.filter((r) => HOIST_MARKER_PROBE.test(r.replacement));
14365
+ if (claiming.length === 0) {
14366
+ throw new Error(`${id} \u2014 internal: a module-hoist prelude was produced but no applied replacement carries a hoist marker`);
14367
+ }
14368
+ const firstStart = Math.min(...claiming.map((r) => r.start));
14369
+ const anchor = body.find((s) => s.start <= firstStart && firstStart < s.end);
14370
+ if (anchor === void 0) {
14371
+ throw new Error(`${id} \u2014 internal: no top-level statement encloses macro replacement at ${firstStart}`);
14372
+ }
14373
+ ms.appendLeft(anchor.start, hoisted.prelude);
14003
14374
  }
14004
14375
  if (applied.length > 0) {
14005
14376
  ms.prepend(
@@ -14009,6 +14380,21 @@ function transformMacroImpl(code, id, moduleAliases, warnUnloweredRegex, recover
14009
14380
  `
14010
14381
  );
14011
14382
  }
14383
+ if (applied.length > 0) {
14384
+ let sourceFree = null;
14385
+ const freeInSource = () => sourceFree ??= new Set(findFreeIdentifiers(code, id).map((f) => f.name));
14386
+ const free = findFreeIdentifiers(ms.toString(), id).filter((f) => !freeInSource().has(f.name));
14387
+ if (free.length > 0) {
14388
+ const macroProvided = free.filter((f) => allNames.has(f.name));
14389
+ const where = (f) => ` - ${id}:${f.line}:${f.column} \u2014 \`${f.name}\`` + (f.enclosing ? `, inside \`${f.enclosing}\`` : "");
14390
+ const cause = macroProvided.length > 0 ? `
14391
+ ${macroProvided.length === free.length ? "All" : `${macroProvided.length}`} of these came from the \`with { type: 'macro' }\` import, which was removed because every macro declaration lowered. Something still names them verbatim \u2014 a declaration parseman left as text rather than compiling. Make that declaration macro-buildable, or stop exporting it so lowering can drop it.` : "";
14392
+ throw new Error(
14393
+ `${id} \u2014 parseman will not emit this module: ${free.length} identifier(s) are read but bound by nothing, so it would throw ReferenceError at runtime (positions are in the EMITTED module):
14394
+ ${free.map(where).join("\n")}${cause}`
14395
+ );
14396
+ }
14397
+ }
14012
14398
  for (const line of formatInlineCapSites(endInlineCapCapture(), resolveInlineMax())) warnings.push(`${id}: ${line}`);
14013
14399
  const unlowered = endLoweringCapture();
14014
14400
  if (warnUnloweredRegex) {