parseman 0.15.0 → 0.17.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.
@@ -126,6 +126,54 @@ function sequenceFirstSet(parsers) {
126
126
  }
127
127
  return fs2;
128
128
  }
129
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
130
+ if (seen.has(p)) return any();
131
+ seen.add(p);
132
+ const fs2 = (c) => firstSetOf(c, seen);
133
+ const d = p._def;
134
+ switch (d.tag) {
135
+ case "literal":
136
+ case "regex":
137
+ case "keywords":
138
+ return p._meta.firstSet;
139
+ // terminals: no refs, cached set is exact
140
+ case "lazy":
141
+ try {
142
+ return fs2(d.thunk());
143
+ } catch {
144
+ return any();
145
+ }
146
+ case "choice": {
147
+ let out = empty();
148
+ for (const arm of d.parsers) out = union(out, fs2(arm));
149
+ return out;
150
+ }
151
+ case "sequence": {
152
+ let out = empty();
153
+ for (const term of d.parsers) {
154
+ out = union(out, fs2(term));
155
+ if (!matchesEmpty(term)) return out;
156
+ }
157
+ return out;
158
+ }
159
+ case "oneOrMore":
160
+ case "many":
161
+ case "optional":
162
+ case "transform":
163
+ case "label":
164
+ case "trivia":
165
+ case "node":
166
+ case "grammar":
167
+ case "expect":
168
+ return fs2(d.parser);
169
+ case "sepBy":
170
+ return fs2(d.parser);
171
+ case "skip":
172
+ return fs2(d.main);
173
+ default:
174
+ return p._meta.firstSet;
175
+ }
176
+ }
129
177
  function mergeRanges(ranges) {
130
178
  if (ranges.length === 0) return [];
131
179
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -333,12 +381,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
333
381
  }
334
382
 
335
383
  // src/combinators/literal.ts
336
- var _collatorCache = null;
337
- function collator() {
338
- if (_collatorCache === null) {
339
- _collatorCache = new Intl.Collator(void 0, { sensitivity: "accent" });
384
+ function asciiFoldEq(a, b) {
385
+ if (a.length !== b.length) return false;
386
+ for (let i = 0; i < a.length; i++) {
387
+ let ca = a.charCodeAt(i);
388
+ let cb = b.charCodeAt(i);
389
+ if (ca >= 65 && ca <= 90) ca += 32;
390
+ if (cb >= 65 && cb <= 90) cb += 32;
391
+ if (ca !== cb) return false;
340
392
  }
341
- return _collatorCache;
393
+ return true;
342
394
  }
343
395
  function literal(value, opts = {}) {
344
396
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -353,8 +405,9 @@ function literal(value, opts = {}) {
353
405
  const lower = value.toLowerCase();
354
406
  const firstUpper = upper.codePointAt(0);
355
407
  const firstLower = lower.codePointAt(0);
356
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
357
- { lo: Math.min(firstLower, firstUpper), hi: Math.max(firstLower, firstUpper) }
408
+ meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
409
+ { lo: firstLower, hi: firstLower },
410
+ { lo: firstUpper, hi: firstUpper }
358
411
  ] } : firstSet;
359
412
  }
360
413
  return {
@@ -367,7 +420,7 @@ function literal(value, opts = {}) {
367
420
  return failAt(ctx, [JSON.stringify(value)], pos);
368
421
  }
369
422
  const slice = input.slice(pos, end);
370
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
423
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
371
424
  if (matched) {
372
425
  const span = { start: pos, end };
373
426
  const leaf = { _tag: "leaf", value: slice, span };
@@ -1260,6 +1313,41 @@ function markUnusedValues(root) {
1260
1313
  visit(resolveRoot(root), true);
1261
1314
  }
1262
1315
 
1316
+ // src/combinators/parser.ts
1317
+ function rules(factory) {
1318
+ const cache = {};
1319
+ const proxy = new Proxy(cache, {
1320
+ get(target, key) {
1321
+ if (typeof key !== "string") return void 0;
1322
+ const record = target;
1323
+ if (!(key in record)) {
1324
+ const r = ref();
1325
+ r._ruleName = key;
1326
+ record[key] = r;
1327
+ }
1328
+ return record[key];
1329
+ }
1330
+ });
1331
+ const definitions = factory(proxy);
1332
+ for (const key of Object.keys(definitions)) {
1333
+ const placeholder = cache[key];
1334
+ const parser2 = definitions[key];
1335
+ if (placeholder !== void 0 && typeof placeholder.define === "function") {
1336
+ ;
1337
+ placeholder.define(parser2);
1338
+ placeholder._meta.firstSet = parser2._meta.firstSet;
1339
+ placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1340
+ } else {
1341
+ ;
1342
+ cache[key] = parser2;
1343
+ }
1344
+ }
1345
+ for (const key of Object.keys(definitions)) {
1346
+ markUnusedValues(cache[key]);
1347
+ }
1348
+ return cache;
1349
+ }
1350
+
1263
1351
  // src/combinators/not.ts
1264
1352
  function not(combinator) {
1265
1353
  const meta = {
@@ -1366,6 +1454,20 @@ function transform(combinator, fn) {
1366
1454
  }
1367
1455
  };
1368
1456
  }
1457
+ function skip(main, skipped) {
1458
+ return {
1459
+ _tag: "skip",
1460
+ _meta: main._meta,
1461
+ _def: { tag: "skip", main, skipped },
1462
+ parse(input, pos, ctx) {
1463
+ const result = main.parse(input, pos, ctx);
1464
+ if (!result.ok) return result;
1465
+ const s = skipped.parse(input, result.span.end, ctx);
1466
+ if (!s.ok) return result;
1467
+ return { ...result, span: { start: result.span.start, end: s.span.end } };
1468
+ }
1469
+ };
1470
+ }
1369
1471
  function trivia(combinator) {
1370
1472
  const kindLabels = analyzeLabeledTrivia(combinator)?.labels;
1371
1473
  return {
@@ -1596,8 +1698,8 @@ function classToRanges(cls) {
1596
1698
  return parseClassRanges(body);
1597
1699
  }
1598
1700
  var foldEq = (cVar, cp) => {
1599
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1600
- if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1701
+ if (cp >= 65 && cp <= 90) return `(${cVar} | 32) === ${cp + 32}`;
1702
+ if (cp >= 97 && cp <= 122) return `(${cVar} | 32) === ${cp}`;
1601
1703
  return `${cVar} === ${cp}`;
1602
1704
  };
1603
1705
  function literalCodePoints(frag) {
@@ -1848,6 +1950,12 @@ function firstSetsDisjoint(a, b) {
1848
1950
  return classDisjoint(a, b);
1849
1951
  }
1850
1952
  function fixedClassSeq(shape) {
1953
+ if (shape.kind === "litFold") {
1954
+ return shape.open.map((cp) => {
1955
+ const other = cp >= 65 && cp <= 90 ? cp + 32 : cp >= 97 && cp <= 122 ? cp - 32 : cp;
1956
+ return other === cp ? { ranges: [[cp, cp]], negated: false } : { ranges: [[Math.min(cp, other), Math.min(cp, other)], [Math.max(cp, other), Math.max(cp, other)]], negated: false };
1957
+ });
1958
+ }
1851
1959
  if (shape.kind !== "seq") return null;
1852
1960
  const out = [];
1853
1961
  for (const p of shape.parts) {
@@ -2269,12 +2377,34 @@ function parseScanShapeCore(source) {
2269
2377
  }
2270
2378
  function scanShapeFromRegex(source, flags) {
2271
2379
  if (/[msu]/.test(flags)) return null;
2272
- if (/i/.test(flags)) {
2273
- const open = literalCodePoints(source);
2274
- return open ? { kind: "litFold", open } : null;
2275
- }
2380
+ if (/i/.test(flags)) return caseFoldShape(source);
2276
2381
  return parseScanShape(source);
2277
2382
  }
2383
+ function caseFoldShape(source) {
2384
+ const la = stripTrailingLookahead(source);
2385
+ if (la) {
2386
+ const inner = caseFoldLiteralOrAlt(la.base);
2387
+ if (!inner) return null;
2388
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2389
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2390
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2391
+ }
2392
+ return caseFoldLiteralOrAlt(source);
2393
+ }
2394
+ function caseFoldLiteralOrAlt(source) {
2395
+ const asciiOnly = (cps) => cps.every((cp) => cp <= 127);
2396
+ const open = literalCodePoints(source);
2397
+ if (open) return asciiOnly(open) ? { kind: "litFold", open } : null;
2398
+ const arms = splitTopLevelAlternation(source);
2399
+ if (!arms || arms.length < 2) return null;
2400
+ const litArms = [];
2401
+ for (const arm of arms) {
2402
+ const cp = literalCodePoints(arm);
2403
+ if (!cp || !asciiOnly(cp)) return null;
2404
+ litArms.push({ kind: "litFold", open: cp });
2405
+ }
2406
+ return { kind: "alt", arms: litArms, disjoint: false, firsts: litArms.map(() => null) };
2407
+ }
2278
2408
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2279
2409
  var litCond = (base, cps, firstVar) => cps.map(
2280
2410
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2286,6 +2416,25 @@ function stringHardStop(shape) {
2286
2416
  );
2287
2417
  }
2288
2418
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2419
+ var ALT_SWITCH_RANGE_LIMIT = 4;
2420
+ var ALT_SWITCH_MAX_CASES = 48;
2421
+ var ALT_SWITCH_MIN_CASES = 3;
2422
+ function planAltSwitch(firsts) {
2423
+ const cases = [];
2424
+ let total = 0;
2425
+ for (const fs2 of firsts) {
2426
+ if (!fs2 || fs2.negated) return { kind: "if" };
2427
+ const pts = [];
2428
+ for (const [lo, hi] of fs2.ranges) {
2429
+ if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
2430
+ for (let cp = lo; cp <= hi; cp++) pts.push(cp);
2431
+ }
2432
+ total += pts.length;
2433
+ if (total > ALT_SWITCH_MAX_CASES) return { kind: "if" };
2434
+ cases.push(pts);
2435
+ }
2436
+ return total >= ALT_SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2437
+ }
2289
2438
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2290
2439
  if (shape.kind === "lookahead") {
2291
2440
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2310,18 +2459,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2310
2459
  const okV = mint("_ok");
2311
2460
  const endV2 = mint("_end");
2312
2461
  const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2462
+ if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
2463
+ const groups = /* @__PURE__ */ new Map();
2464
+ for (const a of shape.arms) {
2465
+ const open = a.open;
2466
+ const f = open[0];
2467
+ const lower = f >= 65 && f <= 90 ? f + 32 : f;
2468
+ (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
2469
+ }
2470
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2471
+ for (const [lower, arms] of groups) {
2472
+ const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
2473
+ lines.push(`${ind2} ${labels} {`);
2474
+ for (const open of arms) {
2475
+ const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
2476
+ const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
2477
+ lines.push(`${ind2} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
2478
+ }
2479
+ lines.push(`${ind2} break`);
2480
+ lines.push(`${ind2} }`);
2481
+ }
2482
+ lines.push(`${ind2}}`);
2483
+ return { setup: lines, ok: okV, end: endV2 };
2484
+ }
2313
2485
  if (shape.disjoint) {
2314
- const c0 = codeAt(start, firstChar);
2315
- shape.arms.forEach((arm, k) => {
2316
- const fs2 = shape.firsts[k];
2317
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2318
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2319
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2320
- lines.push(...m.setup);
2321
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2322
- lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2486
+ const plan = planAltSwitch(shape.firsts);
2487
+ if (plan.kind === "switch") {
2488
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2489
+ shape.arms.forEach((arm, k) => {
2490
+ const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
2491
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2492
+ lines.push(`${ind2} ${labels} {`);
2493
+ lines.push(...m.setup);
2494
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2495
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2496
+ lines.push(`${ind2} break`);
2497
+ lines.push(`${ind2} }`);
2498
+ });
2323
2499
  lines.push(`${ind2}}`);
2324
- });
2500
+ } else {
2501
+ const c0 = codeAt(start, firstChar);
2502
+ shape.arms.forEach((arm, k) => {
2503
+ const fs2 = shape.firsts[k];
2504
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2505
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2506
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2507
+ lines.push(...m.setup);
2508
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2509
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2510
+ lines.push(`${ind2}}`);
2511
+ });
2512
+ }
2325
2513
  } else {
2326
2514
  const lbl = mint("_alt");
2327
2515
  lines.push(`${ind2}${lbl}: {`);
@@ -2809,6 +2997,16 @@ function tryInlineDestructureTransform(src, valueVars) {
2809
2997
  }
2810
2998
 
2811
2999
  // src/compiler/codegen.ts
3000
+ var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
3001
+ var _loweringSink = null;
3002
+ function beginLoweringCapture() {
3003
+ _loweringSink = /* @__PURE__ */ new Set();
3004
+ }
3005
+ function endLoweringCapture() {
3006
+ const misses = _loweringSink ? [..._loweringSink] : [];
3007
+ _loweringSink = null;
3008
+ return misses;
3009
+ }
2812
3010
  function v(ctx, prefix = "_v") {
2813
3011
  return `${prefix}${ctx.vars++}`;
2814
3012
  }
@@ -2898,16 +3096,12 @@ function namedFnPrelude() {
2898
3096
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
2899
3097
  ctx.namedFnDecls.push([
2900
3098
  `function ${fnName}(input, _pos, _ctx) {`,
2901
- ` let _pfok = false, _pfv, _pfe = _pos`,
2902
3099
  ` _pfail: {`,
2903
3100
  ...reindentStmts(bodyStmts, 2),
2904
- ` _pfv = ${valueVar}`,
2905
- ` _pfe = ${endVar}`,
2906
- ` _pfok = true`,
3101
+ ` ${NAMED_FN_END} = ${endVar}`,
3102
+ ` return ${valueVar}`,
2907
3103
  ` }`,
2908
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
2909
- ` ${NAMED_FN_END} = _pfe`,
2910
- ` return _pfv`,
3104
+ ` return ${NAMED_FN_FAIL}`,
2911
3105
  `}`
2912
3106
  ].join("\n"));
2913
3107
  }
@@ -3207,13 +3401,14 @@ function emitLit(def, ctx, pos) {
3207
3401
  const vv = v(ctx);
3208
3402
  const expectedStr = JSON.stringify(JSON.stringify(value));
3209
3403
  const stmts = [];
3210
- if (caseInsensitive) {
3211
- ctx.needsCollator = true;
3404
+ if (caseInsensitive && len > 0) {
3405
+ const match = Array.from(
3406
+ { length: len },
3407
+ (_, i) => `(${foldEq(`input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""})`, value.charCodeAt(i))})`
3408
+ ).join(" && ");
3212
3409
  stmts.push(
3213
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3214
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3215
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3216
- `${ind(ctx)}const ${vv} = ${vv}_s`
3410
+ ...emitIfFail(ctx, `${pos} + ${len} > input.length || !(${match})`, failBody(ctx, expectedStr, pos)),
3411
+ `${ind(ctx)}const ${vv} = input.slice(${pos}, ${pos} + ${len})`
3217
3412
  );
3218
3413
  } else if (len === 0) {
3219
3414
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3334,6 +3529,7 @@ function emitRegex(def, ctx, pos) {
3334
3529
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3335
3530
  }
3336
3531
  }
3532
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3337
3533
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3338
3534
  const key = `${def.source}/${flags}`;
3339
3535
  let rName = ctx.regexMap.get(key);
@@ -3606,11 +3802,16 @@ function emitFirstMatch(def, ctx, pos) {
3606
3802
  const atStart = failsAtStart(p);
3607
3803
  const staticFx = armStaticExpected(ctx, p);
3608
3804
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3609
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3805
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3806
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3807
+ const deep = firstSetOf(p);
3808
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3809
+ })();
3610
3810
  let gateCond = null;
3611
3811
  if (gate) {
3612
3812
  const gateIdx = ctx.mapFns.length;
3613
3813
  ctx.mapFns.push(gate);
3814
+ ctx.mapFnSrcs.push(null);
3614
3815
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3615
3816
  }
3616
3817
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3669,13 +3870,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3669
3870
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3670
3871
  return emitFirstMatch(def, ctx, pos);
3671
3872
  }
3873
+ function pushMapFn(ctx, fn, src) {
3874
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3875
+ const hit = ctx.mapFnSrcs.indexOf(src);
3876
+ if (hit !== -1) return hit;
3877
+ }
3878
+ const idx = ctx.mapFns.length;
3879
+ ctx.mapFns.push(fn);
3880
+ ctx.mapFnSrcs.push(src);
3881
+ return idx;
3882
+ }
3672
3883
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3673
3884
  const def = p._def;
3674
3885
  if (def.tag === "transform") {
3675
3886
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3676
- const fnIdx = ctx.mapFns.length;
3677
- ctx.mapFns.push(def.fn);
3678
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3887
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3679
3888
  const vv = v(ctx);
3680
3889
  return {
3681
3890
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3858,7 +4067,7 @@ function emitScanTo(def, ctx, pos) {
3858
4067
  `${ind(ctx)}while (${curV} < input.length) {`
3859
4068
  ];
3860
4069
  ctx.indent++;
3861
- const probeCtx = { ...ctx, capturing: false };
4070
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3862
4071
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3863
4072
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3864
4073
  if (def.skip.length > 0) {
@@ -3914,15 +4123,19 @@ function emitNode(def, ctx, pos) {
3914
4123
  ctx.buildSrcs.push(def.buildSrc ?? null);
3915
4124
  }
3916
4125
  const i = ind(ctx);
3917
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3918
- const clonesState = structural || buildReadsState(def);
4126
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4127
+ const clonesState = !structural && buildReadsState(def);
3919
4128
  const chV = v(ctx, "_ch");
3920
4129
  const rawV = v(ctx, "_raw");
3921
- const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
4130
+ const capTLv = structural ? v(ctx, "_ctl") : null;
4131
+ const capSTv = structural ? v(ctx, "_cst") : null;
4132
+ const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
3922
4133
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3923
4134
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3924
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3925
- const innerTl = capturesTrivia ? tlV : "undefined";
4135
+ if (structural) ctx.needsHostReads = true;
4136
+ const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4137
+ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4138
+ const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
3926
4139
  const stmts = [
3927
4140
  allocStmt,
3928
4141
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3933,7 +4146,10 @@ function emitNode(def, ctx, pos) {
3933
4146
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3934
4147
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3935
4148
  let stV = "undefined";
3936
- if (clonesState) {
4149
+ if (structural) {
4150
+ stV = v(ctx, "_nst");
4151
+ stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4152
+ } else if (clonesState) {
3937
4153
  stV = v(ctx, "_nst");
3938
4154
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3939
4155
  }
@@ -4042,6 +4258,32 @@ function emitExpect(def, ctx, pos) {
4042
4258
  return { stmts, valueVar: valVar, endVar };
4043
4259
  }
4044
4260
  function emit(p, ctx, pos) {
4261
+ const usage = ctx.lazyUsage;
4262
+ if (usage && !ctx.noHoist && !ctx.capAsTrivia && !ctx.ruleNames?.has(p) && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && // Only hoist subtrees big enough that de-duplicating them beats the call
4263
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4264
+ // faster than it calls and barely moves the byte count; the size explosion
4265
+ // is all in LARGE choices/sequences referenced from many positions.
4266
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4267
+ const existing = ctx.namedParsers.get(p);
4268
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4269
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4270
+ ctx.namedParsers.set(p, fnName);
4271
+ const savedIndent = ctx.indent;
4272
+ const savedFailLabel = ctx.failLabel;
4273
+ const savedRecord = ctx.recordFail;
4274
+ ctx.indent = 1;
4275
+ ctx.failLabel = "_pfail";
4276
+ ctx.recordFail = true;
4277
+ const r = emitDispatch(p, ctx, "_pos");
4278
+ ctx.indent = savedIndent;
4279
+ ctx.failLabel = savedFailLabel;
4280
+ ctx.recordFail = savedRecord;
4281
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4282
+ return emitNamedFnCall(ctx, fnName, pos);
4283
+ }
4284
+ return emitDispatch(p, ctx, pos);
4285
+ }
4286
+ function emitDispatch(p, ctx, pos) {
4045
4287
  const def = p._def;
4046
4288
  switch (def.tag) {
4047
4289
  case "literal":
@@ -4087,9 +4329,7 @@ function emit(p, ctx, pos) {
4087
4329
  };
4088
4330
  }
4089
4331
  }
4090
- const fnIdx = ctx.mapFns.length;
4091
- ctx.mapFns.push(def.fn);
4092
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4332
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4093
4333
  const mv = v(ctx, "_mapped");
4094
4334
  return {
4095
4335
  stmts: [
@@ -4154,6 +4394,7 @@ function emit(p, ctx, pos) {
4154
4394
  case "guard": {
4155
4395
  const fnIdx = ctx.mapFns.length;
4156
4396
  ctx.mapFns.push(def.predicate);
4397
+ ctx.mapFnSrcs.push(null);
4157
4398
  const vv = v(ctx);
4158
4399
  return {
4159
4400
  stmts: [
@@ -4168,6 +4409,7 @@ function emit(p, ctx, pos) {
4168
4409
  const evIdx = ctx.mapFns.length;
4169
4410
  const extra = def.extra;
4170
4411
  ctx.mapFns.push((() => extra));
4412
+ ctx.mapFnSrcs.push(null);
4171
4413
  const innerParser = def.parser;
4172
4414
  if (!ctx.namedParsers.has(innerParser)) {
4173
4415
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4287,9 +4529,58 @@ function childrenOf(def) {
4287
4529
  return [];
4288
4530
  }
4289
4531
  }
4532
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4533
+ "sequence",
4534
+ "choice",
4535
+ "many",
4536
+ "oneOrMore",
4537
+ "optional",
4538
+ "sepBy",
4539
+ "transform",
4540
+ "node",
4541
+ "not"
4542
+ ]);
4543
+ function isHoistableTag(tag) {
4544
+ return HOISTABLE_TAGS.has(tag);
4545
+ }
4546
+ var HOIST_MIN_SUBTREE = 3;
4290
4547
  function analyzeLazyUsage(root) {
4291
4548
  return analyzeLazyUsageMulti([root]);
4292
4549
  }
4550
+ function subtreeSizes(roots) {
4551
+ const sizes = /* @__PURE__ */ new Map();
4552
+ const sz = (p) => {
4553
+ const cached = sizes.get(p);
4554
+ if (cached !== void 0) return cached;
4555
+ const def = p._def;
4556
+ let s = 1;
4557
+ if (def.tag !== "lazy") {
4558
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4559
+ }
4560
+ sizes.set(p, s);
4561
+ return s;
4562
+ };
4563
+ const visitedLazy = /* @__PURE__ */ new Set();
4564
+ const visit = (p) => {
4565
+ const def = p._def;
4566
+ if (def.tag === "lazy") {
4567
+ if (visitedLazy.has(p)) return;
4568
+ visitedLazy.add(p);
4569
+ let resolved;
4570
+ try {
4571
+ resolved = def.thunk();
4572
+ } catch {
4573
+ return;
4574
+ }
4575
+ visit(resolved);
4576
+ return;
4577
+ }
4578
+ sz(p);
4579
+ for (const child of childrenOf(def)) visit(child);
4580
+ };
4581
+ for (const root of roots) visit(root);
4582
+ return sizes;
4583
+ }
4293
4584
  function analyzeLazyUsageMulti(roots) {
4294
4585
  const counts = /* @__PURE__ */ new Map();
4295
4586
  const recursive = /* @__PURE__ */ new Set();
@@ -4316,10 +4607,14 @@ function analyzeLazyUsageMulti(roots) {
4316
4607
  active.delete(p);
4317
4608
  return;
4318
4609
  }
4610
+ if (isHoistableTag(def.tag)) {
4611
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4612
+ }
4319
4613
  for (const child of childrenOf(def)) walk2(child);
4320
4614
  }
4321
- for (const root of roots) walk2(root);
4322
- return { counts, recursive };
4615
+ const rootsArr = [...roots];
4616
+ for (const root of rootsArr) walk2(root);
4617
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4323
4618
  }
4324
4619
  function ruleDependencies(ruleMap) {
4325
4620
  const nameOf = /* @__PURE__ */ new Map();
@@ -4369,7 +4664,6 @@ function compile(combinator, mapFnSources) {
4369
4664
  buildFns: [],
4370
4665
  buildSrcs: [],
4371
4666
  runtimeParsers: [],
4372
- needsCollator: false,
4373
4667
  namedParsers: /* @__PURE__ */ new Map(),
4374
4668
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4375
4669
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4378,18 +4672,18 @@ function compile(combinator, mapFnSources) {
4378
4672
  lazyUsage: analyzeLazyUsage(combinator)
4379
4673
  };
4380
4674
  const r = emit(combinator, ctx, "_pos");
4381
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4382
- ` : "";
4383
4675
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4384
4676
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4677
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4385
4678
  const source = [
4386
4679
  ...emptyTlDecls,
4680
+ ...hostReadsDecls,
4387
4681
  ...ctx.regexDecls,
4388
4682
  ...ctx.expectedDecls,
4389
4683
  "",
4390
4684
  ...namedPrelude,
4391
4685
  ctx.namedFnDecls.join("\n\n"),
4392
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4686
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4393
4687
  ` let pos = _pos`,
4394
4688
  ...r.stmts,
4395
4689
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4397,9 +4691,9 @@ function compile(combinator, mapFnSources) {
4397
4691
  ].join("\n");
4398
4692
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4399
4693
  ...emptyTlDecls,
4694
+ ...hostReadsDecls,
4400
4695
  ...ctx.regexDecls,
4401
4696
  ...ctx.expectedDecls,
4402
- collatorDecl,
4403
4697
  ...namedPrelude,
4404
4698
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4405
4699
  `let pos = _pos`,
@@ -4416,7 +4710,7 @@ function compile(combinator, mapFnSources) {
4416
4710
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4417
4711
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4418
4712
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4419
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4713
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4420
4714
  return {
4421
4715
  source,
4422
4716
  inlineExpression,
@@ -4461,7 +4755,6 @@ function compileRuleMap(ruleMap) {
4461
4755
  buildFns: [],
4462
4756
  buildSrcs: [],
4463
4757
  runtimeParsers: [],
4464
- needsCollator: false,
4465
4758
  namedParsers: /* @__PURE__ */ new Map(),
4466
4759
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4467
4760
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4480,8 +4773,6 @@ function compileRuleMap(ruleMap) {
4480
4773
  }
4481
4774
  ctx.ruleNames = ruleNames;
4482
4775
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4483
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4484
- ` : "";
4485
4776
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
4486
4777
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
4487
4778
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -4493,9 +4784,9 @@ function compileRuleMap(ruleMap) {
4493
4784
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4494
4785
  const hoistedDecls = [
4495
4786
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
4787
+ ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
4496
4788
  ...ctx.regexDecls.map((d) => ` ${d}`),
4497
4789
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4498
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4499
4790
  mfDecl,
4500
4791
  buildDecl,
4501
4792
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4548,7 +4839,6 @@ function compileLinkable(ruleMapArg, ns) {
4548
4839
  buildFns: [],
4549
4840
  buildSrcs: [],
4550
4841
  runtimeParsers: [],
4551
- needsCollator: false,
4552
4842
  namedParsers: /* @__PURE__ */ new Map(),
4553
4843
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4554
4844
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4679,12 +4969,12 @@ function compileLinkable(ruleMapArg, ns) {
4679
4969
  firstSets,
4680
4970
  deps: ruleDependencies(ruleMap),
4681
4971
  needsEmptyTl: !!ctx.needsEmptyTl,
4682
- needsCollator: ctx.needsCollator,
4972
+ needsHostReads: !!ctx.needsHostReads,
4683
4973
  mfFns: mfSrcs ? [] : ctx.mapFns,
4684
4974
  buildFns: buildSrcs ? [] : ctx.buildFns
4685
4975
  };
4686
4976
  }
4687
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4977
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4688
4978
  const bodyLines = [
4689
4979
  ` let pos = _pos`,
4690
4980
  ...r.stmts,
@@ -4698,14 +4988,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4698
4988
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4699
4989
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4700
4990
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4701
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4991
+ const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
4992
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl;
4702
4993
  if (!needsWrapper) return innerFn;
4703
4994
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4704
4995
  const hoistedDecls = [
4705
4996
  emptyTlDecl,
4997
+ hostReadsDecl,
4706
4998
  ...ctx.regexDecls.map((d) => ` ${d}`),
4707
4999
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4708
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4709
5000
  mfDecl,
4710
5001
  buildDecl,
4711
5002
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4722,64 +5013,6 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4722
5013
  ].join("\n");
4723
5014
  }
4724
5015
 
4725
- // src/compiler/linker.ts
4726
- function fusedBody(pieces) {
4727
- const winner = /* @__PURE__ */ new Map();
4728
- for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4729
- for (const [k, p] of winner) {
4730
- for (const d of p.deps.get(k) ?? []) {
4731
- if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4732
- }
4733
- }
4734
- const contributing = new Set(winner.values());
4735
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4736
- const needsCollator = [...contributing].some((p) => p.needsCollator);
4737
- const lines = [
4738
- // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4739
- "const _pfFail = {}",
4740
- "let _pfEnd",
4741
- ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4742
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4743
- // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4744
- ...[...contributing].flatMap((p) => p.prelude),
4745
- // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4746
- ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4747
- ];
4748
- const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4749
- const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4750
- const finalFS = /* @__PURE__ */ new Map();
4751
- for (const [k, p] of winner) {
4752
- const fs2 = p.firstSets?.get(k);
4753
- if (fs2) finalFS.set(k, fs2);
4754
- }
4755
- const body = rawBody.replace(
4756
- // Rule name + code-point var are both JS identifiers (rule names are validated
4757
- // at compile time — see assertRuleName in codegen), so an identifier class
4758
- // matches every well-formed placeholder.
4759
- /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4760
- (_m, name, codevar) => {
4761
- const fs2 = finalFS.get(name);
4762
- if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4763
- return `(${firstSetCond(codevar, fs2)})`;
4764
- }
4765
- );
4766
- const env = {};
4767
- for (const p of contributing) {
4768
- if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4769
- if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4770
- }
4771
- return { body, env };
4772
- }
4773
- function emitFusedSource(pieces) {
4774
- const { body, env } = fusedBody(pieces);
4775
- if (Object.keys(env).length > 0) {
4776
- throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4777
- }
4778
- return `/* @__PURE__ */ (() => {
4779
- ${body}
4780
- })()`;
4781
- }
4782
-
4783
5016
  // src/combinators/scanTo.ts
4784
5017
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4785
5018
  const meta = {
@@ -4874,6 +5107,354 @@ function escapeClassChar(ch) {
4874
5107
  return ch.replace(/[\\\]^-]/g, "\\$&");
4875
5108
  }
4876
5109
 
5110
+ // src/compiler/ir-serialize.ts
5111
+ function childrenOf2(def) {
5112
+ switch (def.tag) {
5113
+ case "sequence":
5114
+ case "choice":
5115
+ return def.parsers;
5116
+ case "many":
5117
+ case "oneOrMore":
5118
+ case "optional":
5119
+ case "transform":
5120
+ case "trivia":
5121
+ case "label":
5122
+ case "not":
5123
+ case "node":
5124
+ case "expect":
5125
+ return [def.parser];
5126
+ case "grammar":
5127
+ return def.triviaParser ? [def.parser, def.triviaParser] : [def.parser];
5128
+ case "sepBy":
5129
+ return [def.parser, def.separator];
5130
+ case "skip":
5131
+ return [def.main, def.skipped];
5132
+ case "scanTo":
5133
+ return [def.sentinel, ...def.skip];
5134
+ case "lazy":
5135
+ case "literal":
5136
+ case "regex":
5137
+ case "keywords":
5138
+ case "guard":
5139
+ case "withCtx":
5140
+ case "recover":
5141
+ case "unknown":
5142
+ return [];
5143
+ }
5144
+ }
5145
+ var ruleNameOf = (c) => c._ruleName;
5146
+ function lazyTarget(c) {
5147
+ if (c._def.tag !== "lazy") return null;
5148
+ try {
5149
+ return c._def.thunk();
5150
+ } catch {
5151
+ return null;
5152
+ }
5153
+ }
5154
+ var Unserializable = class extends Error {
5155
+ };
5156
+ function serializeRuleMap(ruleMap) {
5157
+ try {
5158
+ return new Serializer(ruleMap).run();
5159
+ } catch (e) {
5160
+ if (e instanceof Unserializable) {
5161
+ if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
5162
+ return null;
5163
+ }
5164
+ throw e;
5165
+ }
5166
+ }
5167
+ function evalRuleMapIR(ir) {
5168
+ const _tf = (child, src) => {
5169
+ let fn2;
5170
+ try {
5171
+ fn2 = (0, eval)(`(${src})`);
5172
+ } catch {
5173
+ fn2 = () => {
5174
+ throw new Error("IR transform fn not materialized");
5175
+ };
5176
+ }
5177
+ const t = transform(child, fn2);
5178
+ t._def.fnSrc = src;
5179
+ return t;
5180
+ };
5181
+ const _nd = (type, child, src, opts) => {
5182
+ const n = node(type, child, void 0, opts);
5183
+ n._def.buildSrc = src;
5184
+ return n;
5185
+ };
5186
+ const fn = new Function(
5187
+ "rules",
5188
+ "ref",
5189
+ "regex",
5190
+ "literal",
5191
+ "keywords",
5192
+ "sequence",
5193
+ "choice",
5194
+ "many",
5195
+ "oneOrMore",
5196
+ "optional",
5197
+ "sepBy",
5198
+ "not",
5199
+ "node",
5200
+ "parser",
5201
+ "scanTo",
5202
+ "transform",
5203
+ "skip",
5204
+ "trivia",
5205
+ "label",
5206
+ "expect",
5207
+ "_tf",
5208
+ "_nd",
5209
+ `return (${ir})`
5210
+ );
5211
+ const map = fn(
5212
+ rules,
5213
+ ref,
5214
+ regex,
5215
+ literal,
5216
+ keywords,
5217
+ sequence,
5218
+ choice,
5219
+ many,
5220
+ oneOrMore,
5221
+ optional,
5222
+ sepBy,
5223
+ not,
5224
+ node,
5225
+ parser,
5226
+ scanTo,
5227
+ transform,
5228
+ skip,
5229
+ trivia,
5230
+ label,
5231
+ expect,
5232
+ _tf,
5233
+ _nd
5234
+ );
5235
+ return Object.entries(map);
5236
+ }
5237
+ var Serializer = class {
5238
+ counts = /* @__PURE__ */ new Map();
5239
+ selfRef = /* @__PURE__ */ new Set();
5240
+ // combinators reached by an unnamed lazy pointing back into their own subtree
5241
+ constName = /* @__PURE__ */ new Map();
5242
+ emitted = /* @__PURE__ */ new Set();
5243
+ decls = [];
5244
+ ruleValues = /* @__PURE__ */ new Set();
5245
+ ruleMap;
5246
+ constructor(ruleMap) {
5247
+ this.ruleMap = ruleMap;
5248
+ for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
5249
+ }
5250
+ /** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
5251
+ * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
5252
+ * `g[name]` ref — only the top-level entry is unwrapped. */
5253
+ body(c) {
5254
+ return c._def.tag === "lazy" && ruleNameOf(c) !== void 0 ? lazyTarget(c) ?? c : c;
5255
+ }
5256
+ run() {
5257
+ for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
5258
+ let n = 0;
5259
+ for (const [c, count] of this.counts) {
5260
+ if (this.ruleValues.has(c)) continue;
5261
+ if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
5262
+ }
5263
+ const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
5264
+ if (this.decls.length === 0) return `rules((g) => ({
5265
+ ${body}
5266
+ }))`;
5267
+ return `rules((g) => {
5268
+ ${this.decls.map((d) => " " + d).join("\n")}
5269
+ return ({
5270
+ ${body}
5271
+ })
5272
+ })`;
5273
+ }
5274
+ /** Count identity references and flag self-referential subtrees. `active` is the
5275
+ * set of combinators on the current DFS path (to detect a lazy pointing back). */
5276
+ analyze(c, active) {
5277
+ const def = c._def;
5278
+ if (def.tag === "lazy") {
5279
+ if (ruleNameOf(c) !== void 0) return;
5280
+ const target = lazyTarget(c);
5281
+ if (!target) throw new Unserializable("unresolved unnamed ref");
5282
+ if (active.has(target)) {
5283
+ this.selfRef.add(target);
5284
+ return;
5285
+ }
5286
+ this.analyze(target, active);
5287
+ return;
5288
+ }
5289
+ if (def.tag === "guard" || def.tag === "withCtx" || def.tag === "recover" || def.tag === "unknown") {
5290
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5291
+ }
5292
+ this.counts.set(c, (this.counts.get(c) ?? 0) + 1);
5293
+ if ((this.counts.get(c) ?? 0) > 1) return;
5294
+ active.add(c);
5295
+ for (const child of childrenOf2(def)) this.analyze(child, active);
5296
+ active.delete(c);
5297
+ }
5298
+ /** Reference `c`: a const var if it has one (emitting its decl first), else inline.
5299
+ * `selfOf` is the const currently being wrapped in a ref()/define closure — an
5300
+ * unnamed lazy back into it emits the local ref var instead of recursing. */
5301
+ ref(c, selfOf) {
5302
+ const cn = this.constName.get(c);
5303
+ if (cn) {
5304
+ this.emitDecl(c);
5305
+ return cn;
5306
+ }
5307
+ return this.expr(c, selfOf);
5308
+ }
5309
+ emitDecl(c) {
5310
+ const name = this.constName.get(c);
5311
+ if (this.emitted.has(name)) return;
5312
+ this.emitted.add(name);
5313
+ this.decls.push(`const ${name} = ${this.wrap(c)}`);
5314
+ }
5315
+ selfN = 0;
5316
+ /** Emit `c`'s own constructor call, wrapping it in a `ref()`+`.define()` closure
5317
+ * if it is self-referential (an unnamed lazy in its subtree points back to it) —
5318
+ * the shape `balanced()` and other recursive library combinators build. */
5319
+ wrap(c) {
5320
+ if (!this.selfRef.has(c)) return this.expr(c, null);
5321
+ const rv = `_rr${this.selfN++}`;
5322
+ const bodyExpr = this.expr(c, { comb: c, var: rv });
5323
+ return `(() => { const ${rv} = ref(); const _b = ${bodyExpr}; ${rv}.define(_b); return _b })()`;
5324
+ }
5325
+ /** Emit the constructor-call source for `c` (never a const shortcut for `c` itself). */
5326
+ expr(c, selfOf) {
5327
+ const def = c._def;
5328
+ const kid = (x) => this.ref(x, selfOf);
5329
+ switch (def.tag) {
5330
+ case "lazy": {
5331
+ if (ruleNameOf(c) !== void 0) return `g[${JSON.stringify(ruleNameOf(c))}]`;
5332
+ const target = lazyTarget(c);
5333
+ if (selfOf && target === selfOf.comb) return selfOf.var;
5334
+ return this.ref(target, selfOf);
5335
+ }
5336
+ case "literal":
5337
+ return `literal(${JSON.stringify(def.value)}${def.caseInsensitive ? ", { caseInsensitive: true }" : ""})`;
5338
+ case "regex":
5339
+ return `regex(${JSON.stringify(def.source)}, ${JSON.stringify(def.flags)})`;
5340
+ case "keywords":
5341
+ return `keywords(${JSON.stringify(def.words)}, { caseInsensitive: ${def.caseInsensitive}${def.boundary !== void 0 ? `, boundary: ${JSON.stringify(def.boundary)}` : ""} })`;
5342
+ case "sequence":
5343
+ return `sequence(${def.parsers.map(kid).join(", ")})`;
5344
+ case "choice": {
5345
+ if (def.gates.some((g) => g !== null)) throw new Unserializable("choice with a gate() has no source");
5346
+ return `choice(${def.parsers.map(kid).join(", ")})`;
5347
+ }
5348
+ case "many":
5349
+ return `many(${kid(def.parser)})`;
5350
+ case "oneOrMore":
5351
+ return `oneOrMore(${kid(def.parser)})`;
5352
+ case "optional":
5353
+ return `optional(${kid(def.parser)})`;
5354
+ case "sepBy":
5355
+ return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
5356
+ case "not":
5357
+ return `not(${kid(def.parser)})`;
5358
+ case "trivia":
5359
+ return `trivia(${kid(def.parser)})`;
5360
+ case "label":
5361
+ return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
5362
+ case "expect":
5363
+ return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
5364
+ case "skip":
5365
+ return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
5366
+ case "scanTo":
5367
+ return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
5368
+ case "transform": {
5369
+ if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
5370
+ return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
5371
+ }
5372
+ case "node": {
5373
+ if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
5374
+ const opts = def.collapse ? `, { collapse: true }` : "";
5375
+ if (def.buildSrc !== void 0) return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts})`;
5376
+ return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
5377
+ }
5378
+ case "grammar": {
5379
+ const trivia2 = def.clearTrivia ? "null" : def.triviaParser ? kid(def.triviaParser) : "undefined";
5380
+ return `parser({ trivia: ${trivia2}${def.trackLines ? ", trackLines: true" : ""} }, ${kid(def.parser)})`;
5381
+ }
5382
+ case "guard":
5383
+ case "withCtx":
5384
+ case "recover":
5385
+ case "unknown":
5386
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5387
+ }
5388
+ }
5389
+ };
5390
+
5391
+ // src/compiler/linker.ts
5392
+ function fusedBody(pieces) {
5393
+ const winner = /* @__PURE__ */ new Map();
5394
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
5395
+ for (const [k, p] of winner) {
5396
+ for (const d of p.deps.get(k) ?? []) {
5397
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
5398
+ }
5399
+ }
5400
+ const contributing = new Set(winner.values());
5401
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5402
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5403
+ const lines = [
5404
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5405
+ "const _pfFail = {}",
5406
+ "let _pfEnd",
5407
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5408
+ ...needsHostReads ? [HOST_READS_DECL] : [],
5409
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5410
+ ...[...contributing].flatMap((p) => p.prelude),
5411
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5412
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5413
+ ];
5414
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
5415
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
5416
+ const finalFS = /* @__PURE__ */ new Map();
5417
+ for (const [k, p] of winner) {
5418
+ const fs2 = p.firstSets?.get(k);
5419
+ if (fs2) finalFS.set(k, fs2);
5420
+ }
5421
+ const body = rawBody.replace(
5422
+ // Rule name + code-point var are both JS identifiers (rule names are validated
5423
+ // at compile time — see assertRuleName in codegen), so an identifier class
5424
+ // matches every well-formed placeholder.
5425
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
5426
+ (_m, name, codevar) => {
5427
+ const fs2 = finalFS.get(name);
5428
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
5429
+ return `(${firstSetCond(codevar, fs2)})`;
5430
+ }
5431
+ );
5432
+ const env = {};
5433
+ for (const p of contributing) {
5434
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5435
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5436
+ }
5437
+ return { body, env };
5438
+ }
5439
+ function emitFusedSource(pieces) {
5440
+ const { body, env } = fusedBody(pieces);
5441
+ if (Object.keys(env).length > 0) {
5442
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
5443
+ }
5444
+ return `/* @__PURE__ */ (() => {
5445
+ ${body}
5446
+ })()`;
5447
+ }
5448
+ function isIRPiece(p) {
5449
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5450
+ }
5451
+ function materializePiece(p) {
5452
+ if (!isIRPiece(p)) return p;
5453
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5454
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5455
+ return pieces;
5456
+ }
5457
+
4877
5458
  // src/cst/offset-model.ts
4878
5459
  var { isFinite } = Number;
4879
5460
 
@@ -5385,7 +5966,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
5385
5966
  if (!code.includes("parseman")) return null;
5386
5967
  if (!code.includes("macro")) return null;
5387
5968
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
5388
- const result = transformMacro(code, id, moduleAliases);
5969
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
5389
5970
  if (result?.warnings.length) {
5390
5971
  for (const w of result.warnings) {
5391
5972
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -5447,6 +6028,14 @@ function findCarriedPiecesLiteral(root) {
5447
6028
  const obj = callee?.object?.name;
5448
6029
  const prop = callee?.property?.name;
5449
6030
  const args = node2.arguments;
6031
+ if (obj === "Object" && prop === "assign" && args && args[1]?.type === "ObjectExpression") {
6032
+ for (const p of args[1].properties ?? []) {
6033
+ if (p.computed && isComposedPiecesSymbol(p.key) && p.value) {
6034
+ found = { start: p.value.start, end: p.value.end };
6035
+ return;
6036
+ }
6037
+ }
6038
+ }
5450
6039
  if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5451
6040
  const descriptor = args[2];
5452
6041
  for (const p of descriptor?.properties ?? []) {
@@ -5467,7 +6056,21 @@ function findCarriedPiecesLiteral(root) {
5467
6056
  visit(root);
5468
6057
  return found;
5469
6058
  }
5470
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
6059
+ function extractImportBindings(body) {
6060
+ const out = /* @__PURE__ */ new Map();
6061
+ for (const stmt of body) {
6062
+ if (stmt.type !== "ImportDeclaration") continue;
6063
+ const s = stmt;
6064
+ for (const spec of s.specifiers) {
6065
+ if (spec.type === "ImportSpecifier") {
6066
+ const imported = spec.imported.name ?? spec.local.name;
6067
+ out.set(spec.local.name, { source: s.source.value, imported });
6068
+ }
6069
+ }
6070
+ }
6071
+ return out;
6072
+ }
6073
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5471
6074
  let result;
5472
6075
  try {
5473
6076
  result = (0, import_oxc_parser.parseSync)(id, code);
@@ -5506,6 +6109,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5506
6109
  const scope = /* @__PURE__ */ new Map();
5507
6110
  const replacements = [];
5508
6111
  const warnings = [];
6112
+ beginLoweringCapture();
5509
6113
  let anyUnresolved = false;
5510
6114
  let ruleMapHolderCounter = 0;
5511
6115
  const lineOf = (pos) => {
@@ -5540,14 +6144,57 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5540
6144
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5541
6145
  const localRuleMaps = /* @__PURE__ */ new Map();
5542
6146
  const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
6147
+ const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
5543
6148
  const serializePieces = (p) => {
5544
- const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5545
- return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
6149
+ const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
6150
+ ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
6151
+ ).join(", ")}])`;
6152
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, mfFns: [], buildFns: [] }`;
5546
6153
  };
5547
- const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5548
- const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
6154
+ const isSpread = (it) => "__spreadLocal" in it;
6155
+ const isIR = (it) => "ir" in it && !("ruleFns" in it);
6156
+ const serializeItem = (it) => isSpread(it) ? `...(${it.__spreadLocal}[Symbol.for('parseman.composedPieces')] ?? [])` : isIR(it) ? `{ ns: ${JSON.stringify(it.ns)}, ir: ${JSON.stringify(it.ir)} }` : serializePieces(it);
6157
+ const serializeList = (list) => `[${list.map(serializeItem).join(", ")}]`;
6158
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
5549
6159
  const localComposedPieces = /* @__PURE__ */ new Map();
5550
6160
  const importedPiecesCache = /* @__PURE__ */ new Map();
6161
+ const resolveModulePieces = (file, exportName, seen) => {
6162
+ const mod = parseModuleCached(file);
6163
+ if (!mod) return null;
6164
+ const localFor = exportLocalName(mod.body, exportName);
6165
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
6166
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
6167
+ if (!literalRange) return null;
6168
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
6169
+ const imports = extractImportBindings(mod.body);
6170
+ const stubNames = [];
6171
+ const stubVals = [];
6172
+ const spreadRe = /\.\.\.\s*\(?\s*([A-Za-z_$][\w$]*)\s*\[\s*Symbol\s*\.\s*for\s*\(\s*['"]parseman\.composedPieces['"]/g;
6173
+ const done = /* @__PURE__ */ new Set();
6174
+ for (let m; m = spreadRe.exec(literal2); ) {
6175
+ const local = m[1];
6176
+ if (done.has(local)) continue;
6177
+ done.add(local);
6178
+ const b = imports.get(local);
6179
+ let subPieces = [];
6180
+ if (b) {
6181
+ let subFile = null;
6182
+ try {
6183
+ subFile = getCompiledResolver().resolveFileSync(file, b.source).path ?? null;
6184
+ } catch {
6185
+ }
6186
+ if (subFile && !seen.has(subFile)) subPieces = resolveModulePieces(subFile, b.imported, new Set(seen).add(subFile)) ?? [];
6187
+ }
6188
+ stubNames.push(local);
6189
+ stubVals.push({ [/* @__PURE__ */ Symbol.for("parseman.composedPieces")]: subPieces });
6190
+ }
6191
+ try {
6192
+ const list = new Function(...stubNames, `return (${literal2})`)(...stubVals);
6193
+ return list.map(materializePiece);
6194
+ } catch {
6195
+ return null;
6196
+ }
6197
+ };
5551
6198
  const importedPieces = (localName) => {
5552
6199
  if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5553
6200
  let result2 = null;
@@ -5558,41 +6205,34 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5558
6205
  file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5559
6206
  } catch {
5560
6207
  }
5561
- const mod = file ? parseModuleCached(file) : null;
5562
- if (mod) {
5563
- const localFor = exportLocalName(mod.body, binding.imported);
5564
- const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5565
- const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5566
- if (literalRange) {
5567
- const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5568
- try {
5569
- result2 = new Function(`return (${literal2})`)();
5570
- } catch {
5571
- result2 = null;
5572
- }
5573
- }
5574
- }
6208
+ result2 = file ? resolveModulePieces(file, binding.imported, /* @__PURE__ */ new Set([file])) : null;
5575
6209
  }
5576
6210
  importedPiecesCache.set(localName, result2);
5577
6211
  return result2;
5578
6212
  };
6213
+ const localArg = (rm, label2) => {
6214
+ const entries = [...rm];
6215
+ const ns = nsFor(label2);
6216
+ const p = compileLinkable(entries, ns);
6217
+ if (!p) return null;
6218
+ const ir = serializeRuleMap(entries);
6219
+ return ir ? { pieces: [p], ir: { ns, ir } } : { pieces: [p] };
6220
+ };
5579
6221
  const argPieces = (arg, label2) => {
5580
6222
  if (isRulesCall(arg)) {
5581
6223
  const factory = arg.arguments[0];
5582
6224
  const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5583
- const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5584
- return p ? [p] : null;
6225
+ return rm ? localArg(rm, label2) : null;
5585
6226
  }
5586
6227
  if (arg.type === "Identifier") {
5587
6228
  const name = arg.name;
5588
6229
  const rm = localRuleMaps.get(name);
5589
- if (rm) {
5590
- const p = compileLinkable([...rm], nsFor(label2));
5591
- return p ? [p] : null;
5592
- }
6230
+ if (rm) return localArg(rm, label2);
5593
6231
  const composed = localComposedPieces.get(name);
5594
- if (composed) return composed;
5595
- return importedPieces(name);
6232
+ if (composed) return { pieces: composed };
6233
+ const pieces = importedPieces(name);
6234
+ if (!pieces) return null;
6235
+ return importBindings.has(name) ? { pieces, spreadLocal: name } : { pieces };
5596
6236
  }
5597
6237
  return null;
5598
6238
  };
@@ -5605,16 +6245,20 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5605
6245
  }
5606
6246
  const elements = arr.elements;
5607
6247
  const pieces = [];
6248
+ const carried = [];
5608
6249
  for (let i = 0; i < elements.length; i++) {
5609
- const list = argPieces(elements[i], `compose${init.start}_${i}`);
5610
- if (!list) {
6250
+ const r = argPieces(elements[i], `compose${init.start}_${i}`);
6251
+ if (!r) {
5611
6252
  warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5612
6253
  return null;
5613
6254
  }
5614
- pieces.push(...list);
6255
+ pieces.push(...r.pieces);
6256
+ if (r.spreadLocal) carried.push({ __spreadLocal: r.spreadLocal });
6257
+ else if (r.ir) carried.push(r.ir);
6258
+ else carried.push(...r.pieces);
5615
6259
  }
5616
6260
  try {
5617
- return { replacement: emitFusedSource(pieces), pieces };
6261
+ return { replacement: emitFusedSource(pieces), pieces, carried };
5618
6262
  } catch (e) {
5619
6263
  warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5620
6264
  return null;
@@ -5701,9 +6345,11 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5701
6345
  localRuleMaps.set(varName, compiledRules.ruleMap);
5702
6346
  let replacement = compiledRules.replacement;
5703
6347
  if (exportPrefix) {
5704
- const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
6348
+ const ns = nsFor(varName);
6349
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns);
5705
6350
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5706
- replacement = withCarriedPieces(replacement, [pieces]);
6351
+ const ir = serializeRuleMap([...compiledRules.ruleMap]);
6352
+ replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
5707
6353
  }
5708
6354
  }
5709
6355
  replacements.push({ start: init.start, end: init.end, replacement });
@@ -5713,7 +6359,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5713
6359
  const fused = compileComposeCall(init);
5714
6360
  if (!fused) continue;
5715
6361
  localComposedPieces.set(varName, fused.pieces);
5716
- const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
6362
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
5717
6363
  replacements.push({ start: init.start, end: init.end, replacement });
5718
6364
  continue;
5719
6365
  }
@@ -5813,6 +6459,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5813
6459
  for (const { start, end, replacement } of [...replacements].sort((a, b) => b.start - a.start)) {
5814
6460
  ms.overwrite(start, end, replacement);
5815
6461
  }
6462
+ const unlowered = endLoweringCapture();
6463
+ if (warnUnloweredRegex) {
6464
+ for (const src of unlowered) {
6465
+ warnings.push(`${id}: regex ${src} did not lower to a fast charCodeAt scan (RegExp.exec fallback)`);
6466
+ }
6467
+ }
5816
6468
  return {
5817
6469
  code: ms.toString(),
5818
6470
  map: ms.generateMap({ hires: true }),