parseman 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/combinators/expect.d.ts.map +1 -1
  2. package/dist/combinators/node.d.ts +1 -1
  3. package/dist/combinators/node.d.ts.map +1 -1
  4. package/dist/combinators/parser.d.ts.map +1 -1
  5. package/dist/combinators/regex-analyze.d.ts +6 -0
  6. package/dist/combinators/regex-analyze.d.ts.map +1 -0
  7. package/dist/combinators/regex.d.ts +16 -1
  8. package/dist/combinators/regex.d.ts.map +1 -1
  9. package/dist/compiler/build-arity.d.ts.map +1 -1
  10. package/dist/compiler/codegen.d.ts +45 -0
  11. package/dist/compiler/codegen.d.ts.map +1 -1
  12. package/dist/compiler/inline-build.d.ts.map +1 -1
  13. package/dist/compiler/linker.d.ts +44 -0
  14. package/dist/compiler/linker.d.ts.map +1 -0
  15. package/dist/compiler/scannable-run.d.ts +80 -2
  16. package/dist/compiler/scannable-run.d.ts.map +1 -1
  17. package/dist/cst/offset-model.d.ts +119 -0
  18. package/dist/cst/offset-model.d.ts.map +1 -0
  19. package/dist/cst/relative-spans.d.ts +56 -0
  20. package/dist/cst/relative-spans.d.ts.map +1 -0
  21. package/dist/cst/types.d.ts +1 -1
  22. package/dist/functional/doc.d.ts +26 -6
  23. package/dist/functional/doc.d.ts.map +1 -1
  24. package/dist/functional/run.d.ts +63 -0
  25. package/dist/functional/run.d.ts.map +1 -0
  26. package/dist/index.cjs +1613 -148
  27. package/dist/index.cjs.map +4 -4
  28. package/dist/index.d.ts +11 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1613 -148
  31. package/dist/index.js.map +4 -4
  32. package/dist/plugin/evaluator.d.ts.map +1 -1
  33. package/dist/plugin/index.cjs +1520 -187
  34. package/dist/plugin/index.cjs.map +4 -4
  35. package/dist/plugin/index.d.ts.map +1 -1
  36. package/dist/plugin/index.js +1520 -187
  37. package/dist/plugin/index.js.map +4 -4
  38. package/dist/types.d.ts +12 -3
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +7 -4
  41. package/dist/combinators/lazy.d.ts +0 -14
  42. package/dist/combinators/lazy.d.ts.map +0 -1
@@ -1,6 +1,8 @@
1
1
  // src/plugin/index.ts
2
+ import * as fs from "node:fs";
2
3
  import { createUnplugin } from "unplugin";
3
4
  import { parseSync } from "oxc-parser";
5
+ import { ResolverFactory } from "oxc-resolver";
4
6
  import MagicString from "magic-string";
5
7
 
6
8
  // src/combinators/first-set.ts
@@ -286,6 +288,36 @@ function literal(value, opts = {}) {
286
288
  }
287
289
 
288
290
  // src/combinators/regex.ts
291
+ var firstSetAnalyzer = null;
292
+ function registerRegexAnalyzer(analyzer) {
293
+ firstSetAnalyzer = analyzer;
294
+ }
295
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
296
+ function regex(pattern, flags = "") {
297
+ const source = typeof pattern === "string" ? pattern : pattern.source;
298
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
299
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
300
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
301
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
302
+ return {
303
+ _tag: "regex",
304
+ _meta: meta,
305
+ _def: { tag: "regex", source, flags: resolvedFlags },
306
+ parse(input, pos, ctx) {
307
+ anchored.lastIndex = pos;
308
+ const m = anchored.exec(input);
309
+ if (m === null) {
310
+ return failAt(ctx, [`/${source}/`], pos);
311
+ }
312
+ const span = { start: pos, end: pos + m[0].length };
313
+ const leaf = { _tag: "leaf", value: m[0], span };
314
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
315
+ return { ok: true, value: m[0], span };
316
+ }
317
+ };
318
+ }
319
+
320
+ // src/combinators/regex-analyze.ts
289
321
  import regexpTree from "regexp-tree";
290
322
  function firstSetFromRegex(pattern) {
291
323
  try {
@@ -328,15 +360,15 @@ function extractFirstSet(node2) {
328
360
  case "Alternative": {
329
361
  const exprs = node2.expressions;
330
362
  if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
331
- let fs = empty();
363
+ let fs2 = empty();
332
364
  let canNL = false;
333
365
  for (const expr of exprs) {
334
366
  const r = extractFirstSet(expr);
335
- fs = union(fs, r.firstSet);
367
+ fs2 = union(fs2, r.firstSet);
336
368
  canNL = canNL || r.canMatchNewline;
337
369
  if (!canBeEmpty(expr)) break;
338
370
  }
339
- return { firstSet: fs, canMatchNewline: canNL };
371
+ return { firstSet: fs2, canMatchNewline: canNL };
340
372
  }
341
373
  case "Char": {
342
374
  const kind = node2.kind;
@@ -363,22 +395,22 @@ function extractFirstSet(node2) {
363
395
  }
364
396
  case "CharacterClass": {
365
397
  const expressions = node2.expressions;
366
- let fs = empty();
398
+ let fs2 = empty();
367
399
  let canNL = false;
368
400
  for (const expr of expressions) {
369
401
  if (expr.type === "ClassRange") {
370
402
  const from = expr.from.codePoint;
371
403
  const to = expr.to.codePoint;
372
- fs = union(fs, fromRange(from, to));
404
+ fs2 = union(fs2, fromRange(from, to));
373
405
  if (from <= 10 && 10 <= to) canNL = true;
374
406
  } else if (expr.type === "Char") {
375
407
  const r = extractFirstSet(expr);
376
- fs = union(fs, r.firstSet);
408
+ fs2 = union(fs2, r.firstSet);
377
409
  canNL = canNL || r.canMatchNewline;
378
410
  }
379
411
  }
380
412
  if (node2.negative) return { firstSet: any(), canMatchNewline: true };
381
- return { firstSet: fs, canMatchNewline: canNL };
413
+ return { firstSet: fs2, canMatchNewline: canNL };
382
414
  }
383
415
  case "Repetition": {
384
416
  return extractFirstSet(node2.expression);
@@ -390,40 +422,6 @@ function extractFirstSet(node2) {
390
422
  return { firstSet: any(), canMatchNewline: true };
391
423
  }
392
424
  }
393
- function optimizeRegex(source, flags) {
394
- try {
395
- const result = regexpTree.optimize(`/${source}/${flags}`);
396
- const str = result.toString();
397
- const lastSlash = str.lastIndexOf("/");
398
- return str.slice(1, lastSlash);
399
- } catch {
400
- return source;
401
- }
402
- }
403
- function regex(pattern, flags = "") {
404
- const source = typeof pattern === "string" ? pattern : pattern.source;
405
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
406
- const optimizedSource = optimizeRegex(source, resolvedFlags);
407
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
408
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
409
- const meta = { firstSet, canMatchNewline, isTrivia: false };
410
- return {
411
- _tag: "regex",
412
- _meta: meta,
413
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
414
- parse(input, pos, ctx) {
415
- anchored.lastIndex = pos;
416
- const m = anchored.exec(input);
417
- if (m === null) {
418
- return failAt(ctx, [`/${source}/`], pos);
419
- }
420
- const span = { start: pos, end: pos + m[0].length };
421
- const leaf = { _tag: "leaf", value: m[0], span };
422
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
423
- return { ok: true, value: m[0], span };
424
- }
425
- };
426
- }
427
425
 
428
426
  // src/combinators/keywords.ts
429
427
  function word(str, boundary = "_0-9A-Za-z") {
@@ -687,10 +685,10 @@ function codesToFirstSet(codes) {
687
685
  ranges.push({ lo, hi });
688
686
  return { kind: "ranges", ranges };
689
687
  }
690
- function inFirstSet(code, fs) {
691
- if (fs.kind === "any") return true;
692
- if (fs.kind === "empty") return false;
693
- for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
688
+ function inFirstSet(code, fs2) {
689
+ if (fs2.kind === "any") return true;
690
+ if (fs2.kind === "empty") return false;
691
+ for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
694
692
  return false;
695
693
  }
696
694
  function areDisjoint(sets) {
@@ -1123,12 +1121,14 @@ function confirmedBuildArity(src) {
1123
1121
  return parts.length;
1124
1122
  }
1125
1123
  function buildReadsTrivia(def) {
1124
+ if (!def.build) return true;
1126
1125
  const src = def.buildSrc ?? def.build.toString();
1127
1126
  const arity = confirmedBuildArity(src);
1128
1127
  if (arity === null) return true;
1129
1128
  return arity >= 4;
1130
1129
  }
1131
1130
  function buildReadsState(def) {
1131
+ if (!def.build) return true;
1132
1132
  const src = def.buildSrc ?? def.build.toString();
1133
1133
  const arity = confirmedBuildArity(src);
1134
1134
  if (arity === null) return true;
@@ -1147,8 +1147,8 @@ function node(type, combinator, build, opts) {
1147
1147
  };
1148
1148
  const collapse = opts?.collapse === true;
1149
1149
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1150
- const capturesTrivia = buildReadsTrivia(def);
1151
- const clonesState = buildReadsState(def);
1150
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1151
+ const clonesState = build ? buildReadsState(def) : true;
1152
1152
  return {
1153
1153
  _tag: "node",
1154
1154
  _meta: meta,
@@ -1159,13 +1159,8 @@ function node(type, combinator, build, opts) {
1159
1159
  const r = combinator.parse(input, pos, ctx);
1160
1160
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1161
1161
  if (!r.ok) return r;
1162
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1163
- children,
1164
- rawChildren,
1165
- r.span,
1166
- triviaLog,
1167
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1168
- );
1162
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1163
+ const built = collapse && children.length === 1 ? collapseChild(children[0]) : build ? build(children, rawChildren, r.span, triviaLog, st) : ctx.build ? ctx.build(type, children, rawChildren, r.span, triviaLog, st) : { _tag: "node", type, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children };
1169
1164
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1170
1165
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1171
1166
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1308,8 +1303,14 @@ function staticExpected(c) {
1308
1303
  case "transform":
1309
1304
  case "not":
1310
1305
  return staticExpected(def.parser);
1311
- case "lazy":
1312
- return staticExpected(def.thunk());
1306
+ case "lazy": {
1307
+ const name = c._ruleName;
1308
+ try {
1309
+ return staticExpected(def.thunk());
1310
+ } catch {
1311
+ return name ? [name] : [];
1312
+ }
1313
+ }
1313
1314
  default:
1314
1315
  return [];
1315
1316
  }
@@ -1339,8 +1340,29 @@ function expect(combinator, label2) {
1339
1340
  var BACKSLASH = 92;
1340
1341
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1341
1342
  var META = new Set("()[]{}*+?|^$.".split(""));
1343
+ var SPACE_RANGES = [
1344
+ [9, 13],
1345
+ [32, 32],
1346
+ [160, 160],
1347
+ [5760, 5760],
1348
+ [8192, 8202],
1349
+ [8232, 8232],
1350
+ [8233, 8233],
1351
+ [8239, 8239],
1352
+ [8287, 8287],
1353
+ [12288, 12288],
1354
+ [65279, 65279]
1355
+ ];
1342
1356
  function shorthandRanges(ch) {
1343
- return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1357
+ if (ch === "d") return [[48, 57]];
1358
+ if (ch === "s") return SPACE_RANGES;
1359
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1360
+ }
1361
+ function readUnicodeEscape(body, i) {
1362
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1363
+ const hex = body.slice(i + 2, i + 6);
1364
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1365
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1344
1366
  }
1345
1367
  function parseClassRanges(body) {
1346
1368
  const ranges = [];
@@ -1349,11 +1371,16 @@ function parseClassRanges(body) {
1349
1371
  const ch = body[i];
1350
1372
  if (ch === void 0) return null;
1351
1373
  if (ch === "\\") {
1374
+ const uni = readUnicodeEscape(body, i);
1375
+ if (uni) {
1376
+ i = uni.next;
1377
+ return { cp: uni.cp };
1378
+ }
1352
1379
  const e = body[i + 1];
1353
1380
  if (e === void 0) return null;
1354
1381
  i += 2;
1355
1382
  if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1356
- if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1383
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1357
1384
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1358
1385
  return { cp: e.codePointAt(0) };
1359
1386
  }
@@ -1381,10 +1408,16 @@ function parseClassRanges(body) {
1381
1408
  function classToRanges(cls) {
1382
1409
  if (cls === "\\d") return shorthandRanges("d");
1383
1410
  if (cls === "\\w") return shorthandRanges("w");
1411
+ if (cls === "\\s") return shorthandRanges("s");
1384
1412
  const body = cls.slice(1, -1);
1385
1413
  if (body.startsWith("^")) return null;
1386
1414
  return parseClassRanges(body);
1387
1415
  }
1416
+ var foldEq = (cVar, cp) => {
1417
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1418
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1419
+ return `${cVar} === ${cp}`;
1420
+ };
1388
1421
  function literalCodePoints(frag) {
1389
1422
  const out = [];
1390
1423
  let i = 0;
@@ -1431,22 +1464,612 @@ function parseStringShape(source) {
1431
1464
  if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1432
1465
  return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1433
1466
  }
1467
+ function parseSeqParts(source) {
1468
+ const parts = [];
1469
+ let lit = [];
1470
+ const flush = () => {
1471
+ if (lit.length) {
1472
+ parts.push({ part: "lit", cps: lit, optional: false });
1473
+ lit = [];
1474
+ }
1475
+ };
1476
+ const runFrom = (ranges, negated, q) => {
1477
+ if (q === "+") {
1478
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1479
+ return 1;
1480
+ }
1481
+ if (q === "*") {
1482
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1483
+ return 1;
1484
+ }
1485
+ if (q === "?") {
1486
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1487
+ return 1;
1488
+ }
1489
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1490
+ return 0;
1491
+ };
1492
+ let i = 0;
1493
+ while (i < source.length) {
1494
+ const ch = source[i];
1495
+ if (ch === "[") {
1496
+ let k = i + 1;
1497
+ const neg = source[k] === "^";
1498
+ if (neg) k++;
1499
+ let body = "";
1500
+ while (k < source.length && source[k] !== "]") {
1501
+ if (source[k] === "\\") {
1502
+ body += source[k] + (source[k + 1] ?? "");
1503
+ k += 2;
1504
+ } else {
1505
+ body += source[k];
1506
+ k++;
1507
+ }
1508
+ }
1509
+ if (source[k] !== "]") return null;
1510
+ const ranges = parseClassRanges(body);
1511
+ if (!ranges) return null;
1512
+ flush();
1513
+ i = k + 1;
1514
+ i += runFrom(ranges, neg, source[i]);
1515
+ continue;
1516
+ }
1517
+ if (ch === "\\") {
1518
+ const e = source[i + 1];
1519
+ if (e === "d" || e === "w" || e === "s") {
1520
+ flush();
1521
+ i += 2;
1522
+ i += runFrom(shorthandRanges(e), false, source[i]);
1523
+ continue;
1524
+ }
1525
+ const uni = readUnicodeEscape(source, i);
1526
+ let cp2;
1527
+ if (uni) {
1528
+ cp2 = uni.cp;
1529
+ i = uni.next;
1530
+ } else if (e === void 0) return null;
1531
+ else if (e in CLASS_ESCAPES) {
1532
+ cp2 = CLASS_ESCAPES[e];
1533
+ i += 2;
1534
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1535
+ else {
1536
+ cp2 = e.codePointAt(0);
1537
+ i += 2;
1538
+ }
1539
+ if (source[i] === "?") {
1540
+ flush();
1541
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1542
+ i++;
1543
+ } else lit.push(cp2);
1544
+ continue;
1545
+ }
1546
+ if (ch === "(") {
1547
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1548
+ let k = i + 3;
1549
+ let depth = 1;
1550
+ while (k < source.length && depth > 0) {
1551
+ const c2 = source[k];
1552
+ if (c2 === "\\") {
1553
+ k += 2;
1554
+ continue;
1555
+ }
1556
+ if (c2 === "[") {
1557
+ let j = k + 1;
1558
+ if (source[j] === "^") j++;
1559
+ while (j < source.length && source[j] !== "]") {
1560
+ if (source[j] === "\\") j += 2;
1561
+ else j++;
1562
+ }
1563
+ if (source[j] !== "]") return null;
1564
+ k = j + 1;
1565
+ continue;
1566
+ }
1567
+ if (c2 === "(") depth++;
1568
+ else if (c2 === ")") depth--;
1569
+ k++;
1570
+ }
1571
+ if (depth !== 0) return null;
1572
+ const body = source.slice(i + 3, k - 1);
1573
+ const inner = parseScanShape(body);
1574
+ if (!inner || !groupInnerSafe(inner)) return null;
1575
+ const q = source[k];
1576
+ if (q === "{") return null;
1577
+ flush();
1578
+ if (q === "+") {
1579
+ if (shapeCanBeEmpty(inner)) return null;
1580
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1581
+ i = k + 1;
1582
+ } else if (q === "*") {
1583
+ if (shapeCanBeEmpty(inner)) return null;
1584
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1585
+ i = k + 1;
1586
+ } else if (q === "?") {
1587
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1588
+ i = k + 1;
1589
+ } else {
1590
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1591
+ i = k;
1592
+ }
1593
+ continue;
1594
+ }
1595
+ if (META.has(ch)) return null;
1596
+ const cp = ch.codePointAt(0);
1597
+ i += ch.length;
1598
+ if (source[i] === "?") {
1599
+ flush();
1600
+ parts.push({ part: "lit", cps: [cp], optional: true });
1601
+ i++;
1602
+ } else lit.push(cp);
1603
+ }
1604
+ flush();
1605
+ return parts.length ? parts : null;
1606
+ }
1607
+ function seqIsUnambiguous(parts) {
1608
+ let hasMandatory = false;
1609
+ for (let k = 0; k < parts.length; k++) {
1610
+ const p = parts[k];
1611
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1612
+ if (mandatory) hasMandatory = true;
1613
+ if (p.part === "group") {
1614
+ const exposure = groupPartExposure(p);
1615
+ if (exposure === "unsupported") {
1616
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1617
+ if (!trailingOnce) return false;
1618
+ } else if (exposure !== null) {
1619
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1620
+ }
1621
+ }
1622
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1623
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1624
+ if (skippableSingle || isUnboundedRepeat) {
1625
+ const rest = seqFirstAccept(parts.slice(k + 1));
1626
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1627
+ }
1628
+ }
1629
+ return hasMandatory;
1630
+ }
1631
+ function rangesOverlap(a, b) {
1632
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1633
+ }
1634
+ function rangesSubset(sub, sup) {
1635
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1636
+ }
1637
+ function parseClassOperand(body) {
1638
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1639
+ return { ranges: shorthandRanges(body[1]), negated: false };
1640
+ }
1641
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1642
+ let inner = body.slice(1, -1);
1643
+ const negated = inner.startsWith("^");
1644
+ if (negated) inner = inner.slice(1);
1645
+ const ranges = parseClassRanges(inner);
1646
+ return ranges ? { ranges, negated } : null;
1647
+ }
1648
+ const cps = literalCodePoints(body);
1649
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1650
+ return null;
1651
+ }
1652
+ function classSubset(t, u) {
1653
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1654
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1655
+ if (t.negated && !u.negated) return false;
1656
+ return rangesSubset(u.ranges, t.ranges);
1657
+ }
1658
+ function classDisjoint(t, u) {
1659
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1660
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1661
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1662
+ return false;
1663
+ }
1664
+ function firstSetsDisjoint(a, b) {
1665
+ if (a === "any" || b === "any") return false;
1666
+ return classDisjoint(a, b);
1667
+ }
1668
+ function fixedClassSeq(shape) {
1669
+ if (shape.kind !== "seq") return null;
1670
+ const out = [];
1671
+ for (const p of shape.parts) {
1672
+ if (p.part === "lit") {
1673
+ if (p.optional) return null;
1674
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1675
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1676
+ out.push({ ranges: p.ranges, negated: p.negated });
1677
+ } else {
1678
+ return null;
1679
+ }
1680
+ }
1681
+ return out.length ? out : null;
1682
+ }
1683
+ function altFixedMutuallyExclusive(shape) {
1684
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1685
+ const lists = [];
1686
+ for (const arm of shape.arms) {
1687
+ const f = fixedClassSeq(arm);
1688
+ if (!f) return false;
1689
+ lists.push(f);
1690
+ }
1691
+ for (let i = 0; i < lists.length; i++) {
1692
+ for (let j = 0; j < lists.length; j++) {
1693
+ if (i === j) continue;
1694
+ const a = lists[i];
1695
+ const b = lists[j];
1696
+ if (a.length > b.length) continue;
1697
+ let disjointAt = false;
1698
+ for (let p = 0; p < a.length; p++) {
1699
+ if (classDisjoint(a[p], b[p])) {
1700
+ disjointAt = true;
1701
+ break;
1702
+ }
1703
+ }
1704
+ if (!disjointAt) return false;
1705
+ }
1706
+ }
1707
+ return true;
1708
+ }
1709
+ function groupPartExposure(part) {
1710
+ const innerWiggle = trailingBacktrackClass(part.inner);
1711
+ if (innerWiggle === "unsupported") return "unsupported";
1712
+ const requiredOnce = !part.unbounded && part.min === 1;
1713
+ if (requiredOnce) return innerWiggle;
1714
+ const dropExposed = shapeFirstAccept(part.inner);
1715
+ if (dropExposed === "any") return "unsupported";
1716
+ if (innerWiggle === null) return dropExposed;
1717
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1718
+ return unioned === "any" ? "unsupported" : unioned;
1719
+ }
1720
+ function trailingBacktrackClass(shape) {
1721
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1722
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1723
+ if (shape.kind === "litFold") return null;
1724
+ if (shape.kind === "seq") {
1725
+ const last = shape.parts[shape.parts.length - 1];
1726
+ if (last.part === "lit") return null;
1727
+ if (last.part === "run") {
1728
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1729
+ return null;
1730
+ }
1731
+ return groupPartExposure(last);
1732
+ }
1733
+ if (shape.kind === "alt") {
1734
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1735
+ const classes = shape.arms.map(trailingBacktrackClass);
1736
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1737
+ const concrete = classes.filter((c) => c !== null);
1738
+ if (concrete.length === 0) return null;
1739
+ const unioned = unionCharSets(concrete);
1740
+ return unioned === "any" ? "unsupported" : unioned;
1741
+ }
1742
+ return "unsupported";
1743
+ }
1744
+ function lookaheadUnambiguous(inner, operand, negative) {
1745
+ const tail = trailingBacktrackClass(inner);
1746
+ if (tail === "unsupported") return false;
1747
+ if (tail === null) return true;
1748
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1749
+ }
1750
+ function stripTrailingLookahead(source) {
1751
+ if (!source.endsWith(")")) return null;
1752
+ for (const negative of [true, false]) {
1753
+ const lead = negative ? "(?!" : "(?=";
1754
+ const idx = source.lastIndexOf(lead);
1755
+ if (idx === -1) continue;
1756
+ const body = source.slice(idx + lead.length, -1);
1757
+ const operand = body.length ? parseClassOperand(body) : null;
1758
+ if (!operand) continue;
1759
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1760
+ }
1761
+ return null;
1762
+ }
1763
+ function unwrapOuterGroup(source) {
1764
+ while (source.startsWith("(?:")) {
1765
+ let depth = 1;
1766
+ let i = 3;
1767
+ let ok = true;
1768
+ while (i < source.length && depth > 0) {
1769
+ const ch = source[i];
1770
+ if (ch === "\\") {
1771
+ i += 2;
1772
+ continue;
1773
+ }
1774
+ if (ch === "[") {
1775
+ let j = i + 1;
1776
+ if (source[j] === "^") j++;
1777
+ while (j < source.length && source[j] !== "]") {
1778
+ if (source[j] === "\\") j += 2;
1779
+ else j++;
1780
+ }
1781
+ if (source[j] !== "]") {
1782
+ ok = false;
1783
+ break;
1784
+ }
1785
+ i = j + 1;
1786
+ continue;
1787
+ }
1788
+ if (ch === "(") depth++;
1789
+ else if (ch === ")") depth--;
1790
+ i++;
1791
+ }
1792
+ if (!ok || depth !== 0 || i !== source.length) break;
1793
+ source = source.slice(3, -1);
1794
+ }
1795
+ return source;
1796
+ }
1797
+ function splitTopLevelAlternation(source) {
1798
+ const body = unwrapOuterGroup(source);
1799
+ const parts = [];
1800
+ let depth = 0;
1801
+ let last = 0;
1802
+ let i = 0;
1803
+ while (i < body.length) {
1804
+ const ch = body[i];
1805
+ if (ch === "\\") {
1806
+ i += 2;
1807
+ continue;
1808
+ }
1809
+ if (ch === "[") {
1810
+ let j = i + 1;
1811
+ if (body[j] === "^") j++;
1812
+ while (j < body.length && body[j] !== "]") {
1813
+ if (body[j] === "\\") j += 2;
1814
+ else j++;
1815
+ }
1816
+ if (body[j] !== "]") return null;
1817
+ i = j + 1;
1818
+ continue;
1819
+ }
1820
+ if (ch === "(") {
1821
+ depth++;
1822
+ i++;
1823
+ continue;
1824
+ }
1825
+ if (ch === ")") {
1826
+ depth--;
1827
+ if (depth < 0) return null;
1828
+ i++;
1829
+ continue;
1830
+ }
1831
+ if (ch === "|" && depth === 0) {
1832
+ parts.push(body.slice(last, i));
1833
+ last = i + 1;
1834
+ i++;
1835
+ continue;
1836
+ }
1837
+ i++;
1838
+ }
1839
+ if (depth !== 0) return null;
1840
+ parts.push(body.slice(last));
1841
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1842
+ return parts;
1843
+ }
1844
+ function shapeCanBeEmpty(shape) {
1845
+ if (shape.kind === "chars") return !shape.minOne;
1846
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
1847
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
1848
+ return false;
1849
+ }
1850
+ function unionCharSets(sets) {
1851
+ if (sets.some((s) => s === "any")) return "any";
1852
+ const concrete = sets;
1853
+ if (concrete.length === 1) return concrete[0];
1854
+ if (concrete.some((s) => s.negated)) return "any";
1855
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
1856
+ }
1857
+ function partFirstAccept(part) {
1858
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
1859
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
1860
+ return shapeFirstAccept(part.inner);
1861
+ }
1862
+ function seqFirstAccept(parts) {
1863
+ const sets = [];
1864
+ for (const p of parts) {
1865
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1866
+ sets.push(partFirstAccept(p));
1867
+ if (mandatory) break;
1868
+ }
1869
+ return unionCharSets(sets);
1870
+ }
1871
+ function groupInnerSafe(shape) {
1872
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1873
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1874
+ if (shape.kind === "alt") return true;
1875
+ return false;
1876
+ }
1877
+ function shapeFirstAccept(shape) {
1878
+ if (shapeCanBeEmpty(shape)) return "any";
1879
+ switch (shape.kind) {
1880
+ case "chars":
1881
+ return { ranges: shape.ranges, negated: false };
1882
+ case "ident":
1883
+ return { ranges: shape.head, negated: false };
1884
+ case "until":
1885
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1886
+ case "delimited":
1887
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1888
+ case "string":
1889
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
1890
+ case "litFold": {
1891
+ const cp = shape.open[0];
1892
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
1893
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
1894
+ return { ranges: [[cp, cp]], negated: false };
1895
+ }
1896
+ case "seq":
1897
+ return seqFirstAccept(shape.parts);
1898
+ case "lookahead":
1899
+ return shapeFirstAccept(shape.inner);
1900
+ // zero-width — doesn't change first-char acceptance
1901
+ case "alt": {
1902
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
1903
+ return unionCharSets(sets);
1904
+ }
1905
+ }
1906
+ }
1907
+ function allPairsDisjoint(sets) {
1908
+ for (let i = 0; i < sets.length; i++) {
1909
+ const a = sets[i];
1910
+ if (a === "any") return false;
1911
+ for (let j = i + 1; j < sets.length; j++) {
1912
+ const b = sets[j];
1913
+ if (b === "any" || !classDisjoint(a, b)) return false;
1914
+ }
1915
+ }
1916
+ return true;
1917
+ }
1918
+ function parseAlternation(source) {
1919
+ const parts = splitTopLevelAlternation(source);
1920
+ if (!parts) return null;
1921
+ const arms = [];
1922
+ for (const p of parts) {
1923
+ const arm = parseScanShape(p);
1924
+ if (!arm) return null;
1925
+ arms.push(arm);
1926
+ }
1927
+ const firstSets = arms.map(shapeFirstAccept);
1928
+ const disjoint = allPairsDisjoint(firstSets);
1929
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
1930
+ }
1434
1931
  function parseScanShape(source) {
1932
+ const alt = parseAlternation(source);
1933
+ if (alt) return alt;
1934
+ const la = stripTrailingLookahead(source);
1935
+ if (la) {
1936
+ const inner = parseScanShapeCore(la.base);
1937
+ if (!inner) return null;
1938
+ const operand = { ranges: la.ranges, negated: la.classNegated };
1939
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
1940
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
1941
+ }
1942
+ return parseScanShapeCore(source);
1943
+ }
1944
+ function splitDelimArms(body) {
1945
+ const arms = [];
1946
+ let depth = 0;
1947
+ let last = 0;
1948
+ let i = 0;
1949
+ while (i < body.length) {
1950
+ const ch = body[i];
1951
+ if (ch === "\\") {
1952
+ i += 2;
1953
+ continue;
1954
+ }
1955
+ if (ch === "[") {
1956
+ let j = i + 1;
1957
+ if (body[j] === "^") j++;
1958
+ while (j < body.length && body[j] !== "]") {
1959
+ if (body[j] === "\\") j += 2;
1960
+ else j++;
1961
+ }
1962
+ if (body[j] !== "]") return null;
1963
+ i = j + 1;
1964
+ continue;
1965
+ }
1966
+ if (ch === "(") {
1967
+ depth++;
1968
+ i++;
1969
+ continue;
1970
+ }
1971
+ if (ch === ")") {
1972
+ depth--;
1973
+ if (depth < 0) return null;
1974
+ i++;
1975
+ continue;
1976
+ }
1977
+ if (ch === "|" && depth === 0) {
1978
+ arms.push(body.slice(last, i));
1979
+ last = i + 1;
1980
+ }
1981
+ i++;
1982
+ }
1983
+ if (depth !== 0) return null;
1984
+ arms.push(body.slice(last));
1985
+ return arms;
1986
+ }
1987
+ function delimitedBodySound(body, close) {
1988
+ const l0 = close[0];
1989
+ if (close.length > 2) return false;
1990
+ const arms = splitDelimArms(body);
1991
+ if (!arms) return false;
1992
+ let bulkSeen = false;
1993
+ let guardSeen = false;
1994
+ for (const arm of arms) {
1995
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
1996
+ if (bulk) {
1997
+ if (bulkSeen) return false;
1998
+ const ranges = parseClassRanges(bulk[1]);
1999
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2000
+ bulkSeen = true;
2001
+ continue;
2002
+ }
2003
+ if (close.length === 2 && arm.endsWith(")")) {
2004
+ const idx = arm.indexOf("(?!");
2005
+ if (idx === -1) return false;
2006
+ const prefix = literalCodePoints(arm.slice(0, idx));
2007
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2008
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2009
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2010
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2011
+ guardSeen = true;
2012
+ continue;
2013
+ }
2014
+ return false;
2015
+ }
2016
+ return bulkSeen && guardSeen === (close.length === 2);
2017
+ }
2018
+ function parseDelimited(source) {
2019
+ if (source.includes("\\\\")) return null;
2020
+ let i = 0;
2021
+ while (i < source.length && source[i] !== "(") {
2022
+ i += source[i] === "\\" ? 2 : 1;
2023
+ }
2024
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2025
+ const openFrag = source.slice(0, i);
2026
+ let k = i + 3;
2027
+ let depth = 1;
2028
+ while (k < source.length && depth > 0) {
2029
+ const ch = source[k];
2030
+ if (ch === "\\") {
2031
+ k += 2;
2032
+ continue;
2033
+ }
2034
+ if (ch === "[") {
2035
+ let j = k + 1;
2036
+ if (source[j] === "^") j++;
2037
+ while (j < source.length && source[j] !== "]") {
2038
+ if (source[j] === "\\") j += 2;
2039
+ else j++;
2040
+ }
2041
+ if (source[j] !== "]") return null;
2042
+ k = j + 1;
2043
+ continue;
2044
+ }
2045
+ if (ch === "(") depth++;
2046
+ else if (ch === ")") depth--;
2047
+ k++;
2048
+ }
2049
+ if (depth !== 0) return null;
2050
+ const body = source.slice(i + 3, k - 1);
2051
+ if (source[k] !== "*") return null;
2052
+ const closeFrag = source.slice(k + 1);
2053
+ const open = literalCodePoints(openFrag);
2054
+ const close = literalCodePoints(closeFrag);
2055
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2056
+ return { kind: "delimited", open, close };
2057
+ }
2058
+ function parseScanShapeCore(source) {
1435
2059
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1436
- if (m) {
1437
- if (m[1].startsWith("^")) return null;
2060
+ if (m && !m[1].startsWith("^")) {
1438
2061
  const ranges = parseClassRanges(m[1]);
1439
2062
  return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1440
2063
  }
1441
- m = /^\\([dw])([+*])$/.exec(source);
2064
+ m = /^\\([dws])([+*])$/.exec(source);
1442
2065
  if (m) {
1443
2066
  return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1444
2067
  }
1445
- m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
2068
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
1446
2069
  if (m) {
1447
2070
  const head = classToRanges(m[1]);
1448
2071
  const tail = classToRanges(m[2]);
1449
- return head && tail ? { kind: "ident", head, tail } : null;
2072
+ if (head && tail) return { kind: "ident", head, tail };
1450
2073
  }
1451
2074
  const str = parseStringShape(source);
1452
2075
  if (str) return str;
@@ -1455,20 +2078,19 @@ function parseScanShape(source) {
1455
2078
  const open = literalCodePoints(m[1]);
1456
2079
  const stop = parseClassRanges(m[2]);
1457
2080
  if (open && stop) return { kind: "until", open, stop };
1458
- return null;
1459
- }
1460
- if (!source.includes("\\\\")) {
1461
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1462
- if (m && m[1] && m[2]) {
1463
- const open = literalCodePoints(m[1]);
1464
- const close = literalCodePoints(m[2]);
1465
- if (open && close) return { kind: "delimited", open, close };
1466
- }
1467
2081
  }
2082
+ const delim = parseDelimited(source);
2083
+ if (delim) return delim;
2084
+ const parts = parseSeqParts(source);
2085
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1468
2086
  return null;
1469
2087
  }
1470
2088
  function scanShapeFromRegex(source, flags) {
1471
- if (/[imsu]/.test(flags)) return null;
2089
+ if (/[msu]/.test(flags)) return null;
2090
+ if (/i/.test(flags)) {
2091
+ const open = literalCodePoints(source);
2092
+ return open ? { kind: "litFold", open } : null;
2093
+ }
1472
2094
  return parseScanShape(source);
1473
2095
  }
1474
2096
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
@@ -1483,6 +2105,53 @@ function stringHardStop(shape) {
1483
2105
  }
1484
2106
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1485
2107
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2108
+ if (shape.kind === "lookahead") {
2109
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
2110
+ const okV = mint("_ok");
2111
+ const endV2 = mint("_end");
2112
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
2113
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
2114
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
2115
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
2116
+ return {
2117
+ setup: [
2118
+ ...inner.setup,
2119
+ `${ind2}let ${okV} = ${inner.ok}`,
2120
+ `${ind2}let ${endV2} = ${inner.end}`,
2121
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
2122
+ ],
2123
+ ok: okV,
2124
+ end: endV2
2125
+ };
2126
+ }
2127
+ if (shape.kind === "alt") {
2128
+ const okV = mint("_ok");
2129
+ const endV2 = mint("_end");
2130
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2131
+ if (shape.disjoint) {
2132
+ const c0 = codeAt(start, firstChar);
2133
+ shape.arms.forEach((arm, k) => {
2134
+ const fs2 = shape.firsts[k];
2135
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2136
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2137
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2138
+ lines.push(...m.setup);
2139
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2140
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2141
+ lines.push(`${ind2}}`);
2142
+ });
2143
+ } else {
2144
+ const lbl = mint("_alt");
2145
+ lines.push(`${ind2}${lbl}: {`);
2146
+ for (const arm of shape.arms) {
2147
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2148
+ lines.push(...m.setup);
2149
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
2150
+ }
2151
+ lines.push(`${ind2}}`);
2152
+ }
2153
+ return { setup: lines, ok: okV, end: endV2 };
2154
+ }
1486
2155
  if (shape.kind === "chars") {
1487
2156
  const cur = mint("_e");
1488
2157
  return {
@@ -1509,6 +2178,82 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1509
2178
  end: cur
1510
2179
  };
1511
2180
  }
2181
+ if (shape.kind === "seq") {
2182
+ const endV2 = mint("_e");
2183
+ const okV = mint("_ok");
2184
+ const runCond = (p, at) => {
2185
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2186
+ return p.negated ? `!(${c})` : `(${c})`;
2187
+ };
2188
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2189
+ let first = true;
2190
+ for (const p of shape.parts) {
2191
+ if (p.part === "lit") {
2192
+ const L = p.cps.length;
2193
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2194
+ if (p.optional) {
2195
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2196
+ } else {
2197
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2198
+ lines.push(`${ind2} ${endV2} += ${L}`);
2199
+ }
2200
+ } else if (p.part === "run" && !p.unbounded) {
2201
+ if (p.min === 1) {
2202
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2203
+ lines.push(`${ind2} ${endV2}++`);
2204
+ } else {
2205
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2206
+ }
2207
+ } else if (p.part === "run") {
2208
+ const s = mint("_s");
2209
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2210
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2211
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2212
+ } else {
2213
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2214
+ if (!p.unbounded) {
2215
+ lines.push(...inner.setup);
2216
+ if (p.min === 1) {
2217
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2218
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2219
+ } else {
2220
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2221
+ }
2222
+ } else {
2223
+ const progV = mint("_prog");
2224
+ lines.push(`${ind2} let ${progV} = false`);
2225
+ lines.push(`${ind2} while (true) {`);
2226
+ lines.push(...inner.setup);
2227
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2228
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2229
+ lines.push(`${ind2} ${progV} = true`);
2230
+ lines.push(`${ind2} }`);
2231
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2232
+ }
2233
+ }
2234
+ first = false;
2235
+ }
2236
+ lines.push(`${ind2} ${okV} = true`);
2237
+ lines.push(`${ind2}} while (false)`);
2238
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2239
+ return { setup: lines, ok: okV, end: endV2 };
2240
+ }
2241
+ if (shape.kind === "litFold") {
2242
+ const openLen2 = shape.open.length;
2243
+ const endV2 = mint("_end");
2244
+ const checks = shape.open.map((cp, k) => {
2245
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2246
+ return foldEq(cVar, cp);
2247
+ });
2248
+ return {
2249
+ setup: [
2250
+ `${ind2}let ${endV2} = ${start}`,
2251
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2252
+ ],
2253
+ ok: `${endV2} > ${start}`,
2254
+ end: endV2
2255
+ };
2256
+ }
1512
2257
  if (shape.kind === "until") {
1513
2258
  const j2 = mint("_j");
1514
2259
  const openLen2 = shape.open.length;
@@ -1790,6 +2535,7 @@ function looseMkBuildRe(type) {
1790
2535
  );
1791
2536
  }
1792
2537
  function analyzeMkInlineBuild(def) {
2538
+ if (!def.build) return null;
1793
2539
  const src = (def.buildSrc ?? def.build.toString()).trim();
1794
2540
  const strict = src.match(MK_BUILD_RE);
1795
2541
  if (strict) {
@@ -1887,6 +2633,15 @@ function v(ctx, prefix = "_v") {
1887
2633
  function ind(ctx) {
1888
2634
  return " ".repeat(ctx.indent);
1889
2635
  }
2636
+ function nsp(ctx) {
2637
+ return ctx.ns ?? "";
2638
+ }
2639
+ function mfRef(ctx) {
2640
+ return `${nsp(ctx)}_mf`;
2641
+ }
2642
+ function buildRef(ctx) {
2643
+ return `${nsp(ctx)}_build`;
2644
+ }
1890
2645
  function reindentStmts(stmts, targetLevels) {
1891
2646
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
1892
2647
  if (nonEmpty.length === 0) return stmts;
@@ -1903,8 +2658,8 @@ function failReturnArr(expectedArr, posExpr) {
1903
2658
  function hoistExpected(ctx, constArrSource) {
1904
2659
  let name = ctx.expectedMap.get(constArrSource);
1905
2660
  if (name === void 0) {
1906
- name = `_fx${ctx.expectedDecls.length}`;
1907
- ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
2661
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2662
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
1908
2663
  ctx.expectedMap.set(constArrSource, name);
1909
2664
  }
1910
2665
  return name;
@@ -2002,13 +2757,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2002
2757
  `${i}}`
2003
2758
  ];
2004
2759
  }
2005
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2760
+ function ensureRegexDecl(ctx, source, flags) {
2006
2761
  const f = "y" + flags.replace(/[gy]/g, "");
2007
- const key = `${optimizedSource}/${f}`;
2762
+ const key = `${source}/${f}`;
2008
2763
  let rName = ctx.regexMap.get(key);
2009
2764
  if (rName === void 0) {
2010
- rName = `_re${ctx.regexDecls.length}`;
2011
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2765
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2766
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2012
2767
  ctx.regexMap.set(key, rName);
2013
2768
  }
2014
2769
  return rName;
@@ -2017,7 +2772,7 @@ function ensureTriviaFn(ctx) {
2017
2772
  const trivia2 = ctx.activeTrivia;
2018
2773
  const existing = ctx.triviaFnNames.get(trivia2);
2019
2774
  if (existing) return existing;
2020
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2775
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2021
2776
  ctx.triviaFnNames.set(trivia2, fnName);
2022
2777
  ctx.triviaCaptureNames.set(trivia2, fnName);
2023
2778
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2039,7 +2794,7 @@ function ensureTriviaFn(ctx) {
2039
2794
  for (const arm of regexSpec.arms) {
2040
2795
  const def = arm.parser._def;
2041
2796
  if (def.tag !== "regex") break;
2042
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2797
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2043
2798
  }
2044
2799
  if (reNames.length === regexSpec.arms.length) {
2045
2800
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -2085,10 +2840,10 @@ function ensureTriviaFn(ctx) {
2085
2840
  function ensureTriviaCaptureFn(ctx) {
2086
2841
  return ensureTriviaFn(ctx);
2087
2842
  }
2088
- function firstSetCond(codeVar, fs) {
2089
- if (fs.kind === "any") return "true";
2090
- if (fs.kind === "empty") return "false";
2091
- return fs.ranges.map(
2843
+ function firstSetCond(codeVar, fs2) {
2844
+ if (fs2.kind === "any") return "true";
2845
+ if (fs2.kind === "empty") return "false";
2846
+ return fs2.ranges.map(
2092
2847
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
2093
2848
  ).join(" || ");
2094
2849
  }
@@ -2100,10 +2855,10 @@ function planDisjointDispatch(parsers) {
2100
2855
  const cases = [];
2101
2856
  let total = 0;
2102
2857
  for (const p of parsers) {
2103
- const fs = p._meta.firstSet;
2104
- if (fs.kind !== "ranges") return { kind: "if" };
2858
+ const fs2 = p._meta.firstSet;
2859
+ if (fs2.kind !== "ranges") return { kind: "if" };
2105
2860
  const pts = [];
2106
- for (const r of fs.ranges) {
2861
+ for (const r of fs2.ranges) {
2107
2862
  if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2108
2863
  for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2109
2864
  }
@@ -2226,18 +2981,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
2226
2981
  return true;
2227
2982
  }
2228
2983
  }
2229
- function emitFallible(inner, ctx, pos) {
2984
+ function emitFallible(inner, ctx, pos, swallow = false) {
2230
2985
  const lbl = v(ctx, "_lbl");
2231
2986
  const okV = `${lbl}ok`;
2232
2987
  const valV = `${lbl}v`;
2233
2988
  const endV = `${lbl}e`;
2234
2989
  const savedLabel = ctx.failLabel;
2235
2990
  const savedIndent = ctx.indent;
2991
+ const savedRecord = ctx.recordFail;
2236
2992
  ctx.failLabel = lbl;
2237
2993
  ctx.indent = savedIndent + 1;
2994
+ if (swallow) ctx.recordFail = false;
2238
2995
  const r = emit(inner, ctx, pos);
2239
2996
  ctx.failLabel = savedLabel;
2240
2997
  ctx.indent = savedIndent;
2998
+ ctx.recordFail = savedRecord;
2241
2999
  const ind0 = ind(ctx);
2242
3000
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
2243
3001
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2260,6 +3018,7 @@ function emitFallible(inner, ctx, pos) {
2260
3018
  ];
2261
3019
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2262
3020
  }
3021
+ var CHARCODE_CHAIN_MAX = 16;
2263
3022
  function emitLit(def, ctx, pos) {
2264
3023
  const { value, caseInsensitive } = def;
2265
3024
  const len = value.length;
@@ -2282,7 +3041,7 @@ function emitLit(def, ctx, pos) {
2282
3041
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2283
3042
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2284
3043
  );
2285
- } else if (len <= 4) {
3044
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2286
3045
  const checks = Array.from(
2287
3046
  { length: len },
2288
3047
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2304,7 +3063,55 @@ function emitLit(def, ctx, pos) {
2304
3063
  function escapeKeywordRe(s) {
2305
3064
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2306
3065
  }
3066
+ function emitKeywordsFast(def, ctx, pos) {
3067
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
3068
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
3069
+ if (def.caseInsensitive && def.boundary) return null;
3070
+ let boundary = null;
3071
+ if (def.boundary) {
3072
+ let body = def.boundary;
3073
+ const negated = body.startsWith("^");
3074
+ if (negated) body = body.slice(1);
3075
+ const ranges = parseClassRanges(body);
3076
+ if (!ranges) return null;
3077
+ boundary = { ranges, negated };
3078
+ }
3079
+ const mint = (prefix = "_v") => v(ctx, prefix);
3080
+ const lbl = v(ctx, "_kwLbl");
3081
+ const valV = v(ctx, "_kwv");
3082
+ const endV = v(ctx, "_kwe");
3083
+ const bodyInd = ind(ctx) + " ";
3084
+ const tries = [];
3085
+ for (const w of def.words) {
3086
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
3087
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
3088
+ if (boundary) {
3089
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
3090
+ }
3091
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
3092
+ tries.push(
3093
+ ...m.setup,
3094
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
3095
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
3096
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
3097
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
3098
+ );
3099
+ }
3100
+ const stmts = [
3101
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
3102
+ `${ind(ctx)}${lbl}: {`,
3103
+ ...tries,
3104
+ `${ind(ctx)}}`,
3105
+ // Every word has length >= 1 (checked above), so a real match always
3106
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
3107
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
3108
+ ];
3109
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
3110
+ return { stmts, valueVar: valV, endVar: endV };
3111
+ }
2307
3112
  function emitKeywords(def, ctx, pos) {
3113
+ const fast = emitKeywordsFast(def, ctx, pos);
3114
+ if (fast) return fast;
2308
3115
  const alt = def.words.map(escapeKeywordRe).join("|");
2309
3116
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2310
3117
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2312,7 +3119,7 @@ function emitKeywords(def, ctx, pos) {
2312
3119
  const key = `${source}/${flags}`;
2313
3120
  let rName = ctx.regexMap.get(key);
2314
3121
  if (rName === void 0) {
2315
- rName = `_re${ctx.regexDecls.length}`;
3122
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2316
3123
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
2317
3124
  ctx.regexMap.set(key, rName);
2318
3125
  }
@@ -2346,11 +3153,11 @@ function emitRegex(def, ctx, pos) {
2346
3153
  }
2347
3154
  }
2348
3155
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2349
- const key = `${def.optimizedSource}/${flags}`;
3156
+ const key = `${def.source}/${flags}`;
2350
3157
  let rName = ctx.regexMap.get(key);
2351
3158
  if (rName === void 0) {
2352
- rName = `_re${ctx.regexDecls.length}`;
2353
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3159
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3160
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
2354
3161
  ctx.regexMap.set(key, rName);
2355
3162
  }
2356
3163
  const mv = v(ctx, "_m");
@@ -2435,6 +3242,31 @@ function failsAtStart(p) {
2435
3242
  return false;
2436
3243
  }
2437
3244
  }
3245
+ function armStaticExpected(ctx, p) {
3246
+ return hoistExpected(ctx, staticExpectedArr([p]));
3247
+ }
3248
+ function canMatchEmptyAtStart(p) {
3249
+ const d = p._def;
3250
+ switch (d.tag) {
3251
+ case "regex":
3252
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3253
+ case "optional":
3254
+ return true;
3255
+ case "many":
3256
+ return true;
3257
+ case "transform":
3258
+ case "label":
3259
+ return canMatchEmptyAtStart(d.parser);
3260
+ case "literal":
3261
+ return d.value.length === 0;
3262
+ default:
3263
+ return false;
3264
+ }
3265
+ }
3266
+ function needsFirstSetGuard(p) {
3267
+ const fs2 = p._meta.firstSet;
3268
+ return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
3269
+ }
2438
3270
  function emitDisjointArm(p, ctx, pos, valV, endV) {
2439
3271
  if (failsAtStart(p)) {
2440
3272
  const r = emit(p, ctx, pos);
@@ -2502,7 +3334,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
2502
3334
  const reKey = `${regexDef.source}/${cleanFlags}`;
2503
3335
  let reVar = ctx.regexMap.get(reKey);
2504
3336
  if (reVar === void 0) {
2505
- reVar = `_re${ctx.regexDecls.length}`;
3337
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2506
3338
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
2507
3339
  ctx.regexMap.set(reKey, reVar);
2508
3340
  }
@@ -2576,21 +3408,26 @@ function emitFirstMatch(def, ctx, pos) {
2576
3408
  const resValV = v(ctx, "_crv");
2577
3409
  const resEndV = v(ctx, "_cre");
2578
3410
  const resOkV = v(ctx, "_crok");
3411
+ const codeV = v(ctx, "_chcode");
2579
3412
  const slots = def.parsers.map(() => v(ctx, "_cfx"));
2580
3413
  const ind0 = ind(ctx);
2581
3414
  const stmts = [
2582
3415
  `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2583
- `${ind0}let ${slots.join(", ")}`
3416
+ `${ind0}let ${slots.join(", ")}`,
3417
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2584
3418
  ];
2585
3419
  for (let i = 0; i < def.parsers.length; i++) {
2586
3420
  const p = def.parsers[i];
2587
3421
  const gate = def.gates[i];
2588
3422
  const autoNot = def.autoNot[i];
3423
+ const atStart = failsAtStart(p);
3424
+ const staticFx = armStaticExpected(ctx, p);
3425
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2589
3426
  let gateCond = null;
2590
3427
  if (gate) {
2591
3428
  const gateIdx = ctx.mapFns.length;
2592
3429
  ctx.mapFns.push(gate);
2593
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3430
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
2594
3431
  }
2595
3432
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2596
3433
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -2600,39 +3437,41 @@ function emitFirstMatch(def, ctx, pos) {
2600
3437
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2601
3438
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2602
3439
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3440
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2603
3441
  stmts.push(`${ind0}if (${skipCond}) {`);
2604
- ctx.indent++;
2605
- const ind1 = ind(ctx);
3442
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3443
+ ctx.indent += fsGuard ? 2 : 1;
2606
3444
  if (markLeaves) {
2607
3445
  stmts.push(
2608
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2609
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2610
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2611
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3446
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3447
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3448
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3449
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2612
3450
  );
2613
3451
  }
2614
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3452
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2615
3453
  stmts.push(...armStmts);
2616
- ctx.indent--;
2617
3454
  if (autoNot && autoNot.length > 0) {
2618
3455
  const anCode = v(ctx, "_anc");
2619
3456
  const rejectCond = autoNot.map(
2620
3457
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2621
3458
  ).join(" || ");
2622
- stmts.push(`${ind0} if (${okVar}) {`);
2623
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2624
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2625
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2626
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2627
- stmts.push(`${ind0} ${resOkV} = true`);
2628
- stmts.push(`${ind0} }`);
2629
- stmts.push(`${ind0} }`);
2630
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2631
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3459
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3460
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3461
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3462
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3463
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3464
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3465
+ stmts.push(`${ind(ctx)} }`);
3466
+ stmts.push(`${ind(ctx)}}`);
3467
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3468
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2632
3469
  } else {
2633
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2634
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
3470
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3471
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2635
3472
  }
3473
+ ctx.indent -= fsGuard ? 2 : 1;
3474
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2636
3475
  stmts.push(`${ind0}}`);
2637
3476
  }
2638
3477
  const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
@@ -2655,7 +3494,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2655
3494
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
2656
3495
  const vv = v(ctx);
2657
3496
  return {
2658
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3497
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
2659
3498
  valueVar: vv,
2660
3499
  endVar: endV
2661
3500
  };
@@ -2665,7 +3504,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2665
3504
  function emitLiteralCondition(litVal, pos) {
2666
3505
  const len = litVal.length;
2667
3506
  if (len === 0) return "true";
2668
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3507
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2669
3508
  const checks = [`${pos} + ${len} <= input.length`];
2670
3509
  for (let i = 0; i < len; i++) {
2671
3510
  const code = litVal.codePointAt(i);
@@ -2714,7 +3553,7 @@ function emitMany(def, ctx, pos) {
2714
3553
  itemPos = npV;
2715
3554
  }
2716
3555
  }
2717
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3556
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2718
3557
  stmts.push(
2719
3558
  ...iterStmts,
2720
3559
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2728,7 +3567,7 @@ function emitMany(def, ctx, pos) {
2728
3567
  function emitOptional(def, ctx, pos) {
2729
3568
  const valV = v(ctx, "_opt");
2730
3569
  const endV = v(ctx, "_opte");
2731
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3570
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2732
3571
  const ind0 = ind(ctx);
2733
3572
  const stmts = [
2734
3573
  ...lblStmts,
@@ -2740,7 +3579,7 @@ function emitOptional(def, ctx, pos) {
2740
3579
  function emitSepBy(_p, def, ctx, pos) {
2741
3580
  const arrV = v(ctx, "_arr");
2742
3581
  const curV = v(ctx, "_cur");
2743
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3582
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2744
3583
  const stmts = [
2745
3584
  `${ind(ctx)}const ${arrV} = []`,
2746
3585
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2772,11 +3611,11 @@ function emitSepBy(_p, def, ctx, pos) {
2772
3611
  );
2773
3612
  sepAtPos = spV;
2774
3613
  const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
2775
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3614
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2776
3615
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2777
3616
  const npV = v(ctx, "_np");
2778
3617
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2779
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3618
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2780
3619
  stmts.push(
2781
3620
  ...nextStmts,
2782
3621
  // item failed → unwind the separator too, back to the end of the last item
@@ -2789,11 +3628,11 @@ function emitSepBy(_p, def, ctx, pos) {
2789
3628
  const spV = v(ctx, "_sp");
2790
3629
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2791
3630
  sepAtPos = spV;
2792
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3631
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2793
3632
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2794
3633
  const npV = v(ctx, "_np");
2795
3634
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2796
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3635
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2797
3636
  stmts.push(
2798
3637
  ...nextStmts,
2799
3638
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2810,10 +3649,10 @@ function emitSepBy(_p, def, ctx, pos) {
2810
3649
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2811
3650
  );
2812
3651
  }
2813
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3652
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2814
3653
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2815
3654
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2816
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3655
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2817
3656
  stmts.push(
2818
3657
  ...nextStmts,
2819
3658
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -2871,7 +3710,7 @@ function emitScanTo(def, ctx, pos) {
2871
3710
  return { stmts, valueVar: valV, endVar: curV };
2872
3711
  }
2873
3712
  function emitNot(def, ctx, pos) {
2874
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3713
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
2875
3714
  const label2 = JSON.stringify(`not(${def.parser._tag})`);
2876
3715
  return {
2877
3716
  stmts: [
@@ -2883,16 +3722,17 @@ function emitNot(def, ctx, pos) {
2883
3722
  };
2884
3723
  }
2885
3724
  function emitNode(def, ctx, pos) {
2886
- const mkType = analyzeMkInlineBuild(def);
3725
+ const structural = def.build === void 0;
3726
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
2887
3727
  let buildIdx = null;
2888
- if (!mkType) {
3728
+ if (!mkType && !structural) {
2889
3729
  buildIdx = ctx.buildFns.length;
2890
3730
  ctx.buildFns.push(def.build);
2891
3731
  ctx.buildSrcs.push(def.buildSrc ?? null);
2892
3732
  }
2893
3733
  const i = ind(ctx);
2894
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
2895
- const clonesState = buildReadsState(def);
3734
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3735
+ const clonesState = structural || buildReadsState(def);
2896
3736
  const chV = v(ctx, "_ch");
2897
3737
  const rawV = v(ctx, "_raw");
2898
3738
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -2915,7 +3755,8 @@ function emitNode(def, ctx, pos) {
2915
3755
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2916
3756
  }
2917
3757
  const ndV = v(ctx, "_nd");
2918
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3758
+ const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3759
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
2919
3760
  const finalExpr = def.collapse ? `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})` : ndExpr;
2920
3761
  stmts.push(
2921
3762
  `${i}const ${ndV} = ${finalExpr}`,
@@ -2940,8 +3781,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2940
3781
  return { stmts, valueVar: vv, endVar: ev };
2941
3782
  }
2942
3783
  function emitLazy(p, def, ctx, pos) {
3784
+ const canonical = ctx.ruleNames?.get(p);
2943
3785
  const usage = ctx.lazyUsage;
2944
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3786
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
2945
3787
  let resolved;
2946
3788
  try {
2947
3789
  resolved = def.thunk();
@@ -2951,7 +3793,7 @@ function emitLazy(p, def, ctx, pos) {
2951
3793
  return emit(resolved, ctx, pos);
2952
3794
  }
2953
3795
  if (!ctx.namedParsers.has(p)) {
2954
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3796
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
2955
3797
  ctx.namedParsers.set(p, fnName2);
2956
3798
  let resolved;
2957
3799
  try {
@@ -2962,11 +3804,14 @@ function emitLazy(p, def, ctx, pos) {
2962
3804
  }
2963
3805
  const savedIndent = ctx.indent;
2964
3806
  const savedFailLabel = ctx.failLabel;
3807
+ const savedRecord = ctx.recordFail;
2965
3808
  ctx.indent = 1;
2966
3809
  ctx.failLabel = "_pfail";
3810
+ ctx.recordFail = true;
2967
3811
  const r = emit(resolved, ctx, "_pos");
2968
3812
  ctx.indent = savedIndent;
2969
3813
  ctx.failLabel = savedFailLabel;
3814
+ ctx.recordFail = savedRecord;
2970
3815
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2971
3816
  }
2972
3817
  const fnName = ctx.namedParsers.get(p);
@@ -3066,7 +3911,7 @@ function emit(p, ctx, pos) {
3066
3911
  return {
3067
3912
  stmts: [
3068
3913
  ...inner.stmts,
3069
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3914
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3070
3915
  ],
3071
3916
  valueVar: mv,
3072
3917
  endVar: inner.endVar
@@ -3129,7 +3974,7 @@ function emit(p, ctx, pos) {
3129
3974
  const vv = v(ctx);
3130
3975
  return {
3131
3976
  stmts: [
3132
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3977
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3133
3978
  `${ind(ctx)}const ${vv} = null`
3134
3979
  ],
3135
3980
  valueVar: vv,
@@ -3146,11 +3991,14 @@ function emit(p, ctx, pos) {
3146
3991
  ctx.namedParsers.set(innerParser, fnName);
3147
3992
  const savedIndent = ctx.indent;
3148
3993
  const savedFailLabel = ctx.failLabel;
3994
+ const savedRecord = ctx.recordFail;
3149
3995
  ctx.indent = 1;
3150
3996
  ctx.failLabel = "_pfail";
3997
+ ctx.recordFail = true;
3151
3998
  const innerR = emit(innerParser, ctx, "_pos");
3152
3999
  ctx.indent = savedIndent;
3153
4000
  ctx.failLabel = savedFailLabel;
4001
+ ctx.recordFail = savedRecord;
3154
4002
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
3155
4003
  }
3156
4004
  const fn = ctx.namedParsers.get(innerParser);
@@ -3159,7 +4007,7 @@ function emit(p, ctx, pos) {
3159
4007
  const ev = v(ctx, "_wce");
3160
4008
  return {
3161
4009
  stmts: [
3162
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4010
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3163
4011
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3164
4012
  // withCtx runs on a spread child ctx — copy its recorded failure back
3165
4013
  // and propagate the inner failure verbatim (interpreter parity).
@@ -3290,6 +4138,39 @@ function analyzeLazyUsageMulti(roots) {
3290
4138
  for (const root of roots) walk2(root);
3291
4139
  return { counts, recursive };
3292
4140
  }
4141
+ function ruleDependencies(ruleMap) {
4142
+ const nameOf = /* @__PURE__ */ new Map();
4143
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4144
+ const deps = /* @__PURE__ */ new Map();
4145
+ for (const [name, comb] of ruleMap) {
4146
+ const found = /* @__PURE__ */ new Set();
4147
+ const seen = /* @__PURE__ */ new Set();
4148
+ const walk2 = (p, isRoot) => {
4149
+ const def = p._def;
4150
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4151
+ if (!isRoot && boundary !== void 0) {
4152
+ found.add(boundary);
4153
+ return;
4154
+ }
4155
+ if (seen.has(p)) return;
4156
+ seen.add(p);
4157
+ if (def.tag === "lazy") {
4158
+ let resolved;
4159
+ try {
4160
+ resolved = def.thunk();
4161
+ } catch {
4162
+ return;
4163
+ }
4164
+ walk2(resolved, false);
4165
+ return;
4166
+ }
4167
+ for (const child of childrenOf(def)) walk2(child, false);
4168
+ };
4169
+ walk2(comb, true);
4170
+ deps.set(name, [...found]);
4171
+ }
4172
+ return deps;
4173
+ }
3293
4174
  function compile(combinator, mapFnSources) {
3294
4175
  const ctx = {
3295
4176
  vars: 0,
@@ -3298,6 +4179,7 @@ function compile(combinator, mapFnSources) {
3298
4179
  regexMap: /* @__PURE__ */ new Map(),
3299
4180
  expectedDecls: [],
3300
4181
  expectedMap: /* @__PURE__ */ new Map(),
4182
+ recordFail: true,
3301
4183
  mapFns: [],
3302
4184
  mapFnSrcs: [],
3303
4185
  buildFns: [],
@@ -3380,6 +4262,7 @@ function compileRuleMap(ruleMap) {
3380
4262
  regexMap: /* @__PURE__ */ new Map(),
3381
4263
  expectedDecls: [],
3382
4264
  expectedMap: /* @__PURE__ */ new Map(),
4265
+ recordFail: true,
3383
4266
  mapFns: [],
3384
4267
  mapFnSrcs: [],
3385
4268
  buildFns: [],
@@ -3393,6 +4276,15 @@ function compileRuleMap(ruleMap) {
3393
4276
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
3394
4277
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule))
3395
4278
  };
4279
+ const seenNames = /* @__PURE__ */ new Map();
4280
+ const ruleNames = /* @__PURE__ */ new Map();
4281
+ for (const [key, rule] of ruleMap) {
4282
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4283
+ const n = seenNames.get(base) ?? 0;
4284
+ seenNames.set(base, n + 1);
4285
+ ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4286
+ }
4287
+ ctx.ruleNames = ruleNames;
3396
4288
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
3397
4289
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
3398
4290
  ` : "";
@@ -3402,8 +4294,8 @@ function compileRuleMap(ruleMap) {
3402
4294
  const mfCovered = ctx.mapFns.length === 0 || derivedSrcs !== void 0;
3403
4295
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
3404
4296
  if (!canInline) return null;
3405
- const mfDecl = derivedSrcs?.length ? ` const _mf = [${derivedSrcs.join(", ")}]` : "";
3406
- const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4297
+ const mfDecl = derivedSrcs?.length ? ` const ${mfRef(ctx)} = [${derivedSrcs.join(", ")}]` : "";
4298
+ const buildDecl = buildSources?.length ? ` const ${buildRef(ctx)} = [${buildSources.join(", ")}]` : "";
3407
4299
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3408
4300
  const hoistedDecls = [
3409
4301
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
@@ -3436,6 +4328,150 @@ function compileRuleMap(ruleMap) {
3436
4328
  ].join("\n");
3437
4329
  return { keys: perEntry.map((e) => e.key), replacement };
3438
4330
  }
4331
+ function compileLinkable(ruleMapArg, ns) {
4332
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4333
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4334
+ const d = val._def;
4335
+ if (d.tag !== "lazy") return true;
4336
+ try {
4337
+ d.thunk();
4338
+ return true;
4339
+ } catch {
4340
+ return false;
4341
+ }
4342
+ });
4343
+ const ctx = {
4344
+ vars: 0,
4345
+ indent: 1,
4346
+ regexDecls: [],
4347
+ regexMap: /* @__PURE__ */ new Map(),
4348
+ expectedDecls: [],
4349
+ expectedMap: /* @__PURE__ */ new Map(),
4350
+ recordFail: true,
4351
+ mapFns: [],
4352
+ mapFnSrcs: [],
4353
+ buildFns: [],
4354
+ buildSrcs: [],
4355
+ runtimeParsers: [],
4356
+ needsCollator: false,
4357
+ namedParsers: /* @__PURE__ */ new Map(),
4358
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4359
+ triviaFnNames: /* @__PURE__ */ new Map(),
4360
+ namedFnDecls: [],
4361
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4362
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4363
+ ns
4364
+ };
4365
+ const seen = /* @__PURE__ */ new Map();
4366
+ const ruleNames = /* @__PURE__ */ new Map();
4367
+ const fnNameToKey = /* @__PURE__ */ new Map();
4368
+ for (const [key, rule] of ruleMap) {
4369
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4370
+ const n = seen.get(base) ?? 0;
4371
+ seen.set(base, n + 1);
4372
+ const fn = n === 0 ? base : `${base}$${n}`;
4373
+ ruleNames.set(rule, fn);
4374
+ fnNameToKey.set(fn, key);
4375
+ }
4376
+ ctx.ruleNames = ruleNames;
4377
+ {
4378
+ const scanned = /* @__PURE__ */ new Set();
4379
+ const scanExternal = (p) => {
4380
+ if (scanned.has(p)) return;
4381
+ scanned.add(p);
4382
+ const def = p._def;
4383
+ if (def.tag === "lazy") {
4384
+ let resolved;
4385
+ try {
4386
+ resolved = def.thunk();
4387
+ } catch {
4388
+ resolved = void 0;
4389
+ }
4390
+ if (resolved === void 0) {
4391
+ const name = p._ruleName;
4392
+ if (name && !ctx.namedParsers.has(p)) {
4393
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4394
+ ruleNames.set(p, fn);
4395
+ ctx.namedParsers.set(p, fn);
4396
+ }
4397
+ return;
4398
+ }
4399
+ scanExternal(resolved);
4400
+ return;
4401
+ }
4402
+ for (const child of childrenOf(def)) scanExternal(child);
4403
+ };
4404
+ for (const [, rule] of ruleMap) scanExternal(rule);
4405
+ }
4406
+ const perEntry = [];
4407
+ for (const [key, rule] of ruleMap) {
4408
+ const fn = ruleNames.get(rule);
4409
+ if (!ctx.namedParsers.has(rule)) {
4410
+ ctx.namedParsers.set(rule, fn);
4411
+ let resolved = rule;
4412
+ const def = rule._def;
4413
+ if (def.tag === "lazy") {
4414
+ try {
4415
+ resolved = def.thunk();
4416
+ } catch {
4417
+ return null;
4418
+ }
4419
+ }
4420
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4421
+ ctx.indent = 1;
4422
+ ctx.failLabel = "_pfail";
4423
+ ctx.recordFail = true;
4424
+ const body = emit(resolved, ctx, "_pos");
4425
+ ctx.indent = savedIndent;
4426
+ ctx.failLabel = savedFail;
4427
+ ctx.recordFail = savedRec;
4428
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4429
+ }
4430
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4431
+ }
4432
+ if (ctx.runtimeParsers.length !== 0) return null;
4433
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4434
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4435
+ const ruleFns = /* @__PURE__ */ new Map();
4436
+ const privateFns = [];
4437
+ for (const decl of ctx.namedFnDecls) {
4438
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4439
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4440
+ if (key !== void 0) ruleFns.set(key, decl);
4441
+ else privateFns.push(decl);
4442
+ }
4443
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4444
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4445
+ const prelude = [
4446
+ ...ctx.regexDecls,
4447
+ ...ctx.expectedDecls,
4448
+ mfDecl,
4449
+ buildDecl,
4450
+ ...privateFns
4451
+ ].filter(Boolean);
4452
+ const wrappers = /* @__PURE__ */ new Map();
4453
+ for (const { key, r } of perEntry) {
4454
+ wrappers.set(key, [
4455
+ `function(input, _pos, _ctx) {`,
4456
+ ` let pos = _pos`,
4457
+ ...r.stmts,
4458
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4459
+ `}`
4460
+ ].join("\n"));
4461
+ }
4462
+ return {
4463
+ ns,
4464
+ keys: perEntry.map((e) => e.key),
4465
+ prelude,
4466
+ ruleFns,
4467
+ wrappers,
4468
+ deps: ruleDependencies(ruleMap),
4469
+ needsEmptyTl: !!ctx.needsEmptyTl,
4470
+ needsCollator: ctx.needsCollator,
4471
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4472
+ buildFns: buildSrcs ? [] : ctx.buildFns
4473
+ };
4474
+ }
3439
4475
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
3440
4476
  const bodyLines = [
3441
4477
  ` let pos = _pos`,
@@ -3474,6 +4510,48 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3474
4510
  ].join("\n");
3475
4511
  }
3476
4512
 
4513
+ // src/compiler/linker.ts
4514
+ function fusedBody(pieces) {
4515
+ const winner = /* @__PURE__ */ new Map();
4516
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4517
+ for (const [k, p] of winner) {
4518
+ for (const d of p.deps.get(k) ?? []) {
4519
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4520
+ }
4521
+ }
4522
+ const contributing = new Set(winner.values());
4523
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4524
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4525
+ const lines = [
4526
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4527
+ "const _pfFail = {}",
4528
+ "let _pfEnd",
4529
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4530
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4531
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4532
+ ...[...contributing].flatMap((p) => p.prelude),
4533
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4534
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4535
+ ];
4536
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4537
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4538
+ const env = {};
4539
+ for (const p of contributing) {
4540
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4541
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4542
+ }
4543
+ return { body, env };
4544
+ }
4545
+ function emitFusedSource(pieces) {
4546
+ const { body, env } = fusedBody(pieces);
4547
+ if (Object.keys(env).length > 0) {
4548
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4549
+ }
4550
+ return `/* @__PURE__ */ (() => {
4551
+ ${body}
4552
+ })()`;
4553
+ }
4554
+
3477
4555
  // src/combinators/scanTo.ts
3478
4556
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
3479
4557
  const meta = {
@@ -3554,11 +4632,11 @@ function balanced(open, close, options = {}) {
3554
4632
  return combi;
3555
4633
  }
3556
4634
  function firstSetClassChars(c) {
3557
- const fs = c._meta.firstSet;
3558
- if (fs.kind === "empty") return [];
3559
- if (fs.kind !== "ranges") return null;
4635
+ const fs2 = c._meta.firstSet;
4636
+ if (fs2.kind === "empty") return [];
4637
+ if (fs2.kind !== "ranges") return null;
3560
4638
  const out = [];
3561
- for (const { lo, hi } of fs.ranges) {
4639
+ for (const { lo, hi } of fs2.ranges) {
3562
4640
  if (hi - lo > 8) return null;
3563
4641
  for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3564
4642
  }
@@ -3568,6 +4646,12 @@ function escapeClassChar(ch) {
3568
4646
  return ch.replace(/[\\\]^-]/g, "\\$&");
3569
4647
  }
3570
4648
 
4649
+ // src/cst/offset-model.ts
4650
+ var { isFinite } = Number;
4651
+
4652
+ // src/index.ts
4653
+ registerRegexAnalyzer(firstSetFromRegex);
4654
+
3571
4655
  // src/plugin/evaluator.ts
3572
4656
  function isWordFactory(v2) {
3573
4657
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
@@ -3667,16 +4751,18 @@ function exprToCombi(node2, scope, code, mfs) {
3667
4751
  }
3668
4752
  if (callee.name === "node" && code !== void 0) {
3669
4753
  const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
3670
- if (!typeArg || !parserArg || !buildArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || buildArg.type === "SpreadElement") return null;
4754
+ if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
3671
4755
  const typeVal = anyValue(typeArg, scope, code);
3672
4756
  if (typeof typeVal !== "string") return null;
3673
4757
  const inner = anyValue(parserArg, scope, code, mfs);
3674
4758
  if (!isCombinator(inner)) return null;
3675
- const buildSrc = code.slice(buildArg.start, buildArg.end);
4759
+ const be = buildArg;
4760
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
4761
+ const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
3676
4762
  const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
3677
4763
  try {
3678
- const combi = node(typeVal, inner, () => null, collapse ? { collapse: true } : void 0);
3679
- if (combi._def.tag === "node") combi._def.buildSrc = buildSrc;
4764
+ const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
4765
+ if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
3680
4766
  return combi;
3681
4767
  } catch {
3682
4768
  return null;
@@ -3862,6 +4948,49 @@ function evaluateCombinatorArray(node2, scope, code) {
3862
4948
  if (!val.every(isCombinator)) return null;
3863
4949
  return val;
3864
4950
  }
4951
+ function propKey(prop) {
4952
+ if (prop.type !== "Property" || prop.computed || !prop.key) return null;
4953
+ if (prop.key.type === "Identifier") return prop.key.name ?? null;
4954
+ if (prop.key.type === "Literal") return String(prop.key.value);
4955
+ return null;
4956
+ }
4957
+ function collectRuleKeys(retObj) {
4958
+ const out = [];
4959
+ for (const prop of retObj.properties) {
4960
+ const key = propKey(prop);
4961
+ if (!key) return null;
4962
+ out.push(key);
4963
+ }
4964
+ return out;
4965
+ }
4966
+ function flattenRuleEntries(retObj, scope, code) {
4967
+ const out = [];
4968
+ for (const prop of retObj.properties) {
4969
+ const key = propKey(prop);
4970
+ if (!key) return null;
4971
+ out.push({ key, value: prop.value, scope, code });
4972
+ }
4973
+ return out;
4974
+ }
4975
+ function evalBodyStatements(statements, scope, code) {
4976
+ const bodyMfs = [];
4977
+ for (const stmt of statements) {
4978
+ for (const d of stmt.declarations) {
4979
+ const decl = d;
4980
+ if (!decl.init) return false;
4981
+ const id = decl.id;
4982
+ if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return false;
4983
+ const name = id.name;
4984
+ const before = bodyMfs.length;
4985
+ const val = anyValue(decl.init, scope, code, bodyMfs);
4986
+ if (val === null) return false;
4987
+ const thisDeclMfSrcs = bodyMfs.slice(before);
4988
+ if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
4989
+ else scope.set(name, val);
4990
+ }
4991
+ }
4992
+ return true;
4993
+ }
3865
4994
  function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3866
4995
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
3867
4996
  const factory = factoryNode;
@@ -3894,40 +5023,41 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3894
5023
  if (!returnExpr) return null;
3895
5024
  const retObj = returnExpr.type === "ParenthesizedExpression" ? returnExpr.expression : returnExpr;
3896
5025
  if (retObj.type !== "ObjectExpression") return null;
5026
+ const keys = collectRuleKeys(retObj);
5027
+ if (!keys) return null;
3897
5028
  const ruleRefs = /* @__PURE__ */ new Map();
3898
- for (const prop of retObj.properties) {
3899
- if (prop.type !== "Property") return null;
3900
- if (prop.computed) return null;
3901
- const p = prop;
3902
- const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : null;
3903
- if (!key) return null;
3904
- ruleRefs.set(key, ref());
5029
+ const tagRef = (r, name) => {
5030
+ r._ruleName = name;
5031
+ };
5032
+ for (const key of keys) {
5033
+ if (!ruleRefs.has(key)) {
5034
+ const r = ref();
5035
+ tagRef(r, key);
5036
+ ruleRefs.set(key, r);
5037
+ }
3905
5038
  }
3906
- const localScope = new Map(scope);
3907
- localScope.set(proxyName, Object.fromEntries(ruleRefs));
3908
- const bodyMfs = [];
3909
- for (const stmt of statements) {
3910
- for (const d of stmt.declarations) {
3911
- const decl = d;
3912
- if (!decl.init) return null;
3913
- const id = decl.id;
3914
- if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return null;
3915
- const name = id.name;
3916
- const before = bodyMfs.length;
3917
- const val = anyValue(decl.init, localScope, code, bodyMfs);
3918
- if (val === null) return null;
3919
- const thisDeclMfSrcs = bodyMfs.slice(before);
3920
- if (isCombinator(val)) {
3921
- localScope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
3922
- } else {
3923
- localScope.set(name, val);
5039
+ const externalRefs = /* @__PURE__ */ new Map();
5040
+ const gProxy = new Proxy(Object.fromEntries(ruleRefs), {
5041
+ get(target, prop) {
5042
+ if (typeof prop !== "string" || prop in target) return target[prop];
5043
+ let ext = externalRefs.get(prop);
5044
+ if (!ext) {
5045
+ ext = ref();
5046
+ tagRef(ext, prop);
5047
+ externalRefs.set(prop, ext);
3924
5048
  }
5049
+ return ext;
3925
5050
  }
3926
- }
3927
- for (const prop of retObj.properties) {
3928
- const p = prop;
3929
- const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
3930
- const val = anyValue(p.value, localScope, code, mapFnSources);
5051
+ });
5052
+ const localScope = new Map(scope);
5053
+ localScope.set(proxyName, gProxy);
5054
+ if (!evalBodyStatements(statements, localScope, code)) return null;
5055
+ const entries = flattenRuleEntries(retObj, localScope, code);
5056
+ if (!entries) return null;
5057
+ const finalByKey = /* @__PURE__ */ new Map();
5058
+ for (const e of entries) finalByKey.set(e.key, e);
5059
+ for (const [key, e] of finalByKey) {
5060
+ const val = anyValue(e.value, e.scope, e.code, mapFnSources);
3931
5061
  if (!isCombinator(val)) return null;
3932
5062
  ruleRefs.get(key).define(val);
3933
5063
  }
@@ -3984,7 +5114,36 @@ function referencesAny(node2, names, scope) {
3984
5114
  }
3985
5115
 
3986
5116
  // src/plugin/index.ts
5117
+ import { createHash } from "node:crypto";
3987
5118
  var PARSEMAN_MODULE = "parseman";
5119
+ var _compiledResolver = null;
5120
+ function getCompiledResolver() {
5121
+ return _compiledResolver ??= new ResolverFactory({
5122
+ extensions: [".js", ".mjs", ".cjs"],
5123
+ conditionNames: ["import", "require", "default"],
5124
+ mainFields: ["module", "main"]
5125
+ });
5126
+ }
5127
+ var _moduleParseCache = /* @__PURE__ */ new Map();
5128
+ function parseModuleCached(filePath) {
5129
+ let mtimeMs;
5130
+ try {
5131
+ mtimeMs = fs.statSync(filePath).mtimeMs;
5132
+ } catch {
5133
+ return null;
5134
+ }
5135
+ const hit = _moduleParseCache.get(filePath);
5136
+ if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
5137
+ let parsed = null;
5138
+ try {
5139
+ const src = fs.readFileSync(filePath, "utf8");
5140
+ const r = parseSync(filePath, src);
5141
+ if (r.errors.length === 0) parsed = { body: r.program.body, src };
5142
+ } catch {
5143
+ }
5144
+ _moduleParseCache.set(filePath, { mtimeMs, parsed });
5145
+ return parsed;
5146
+ }
3988
5147
  var plugin_default = createUnplugin((opts = {}) => ({
3989
5148
  name: "parseman",
3990
5149
  // Run BEFORE the bundler's TS/JS transform — otherwise esbuild (Vite) strips
@@ -4009,6 +5168,77 @@ var plugin_default = createUnplugin((opts = {}) => ({
4009
5168
  return { code: result.code, map: result.map };
4010
5169
  }
4011
5170
  }));
5171
+ function exportLocalName(body, exported) {
5172
+ for (const st of body) {
5173
+ if (st.type !== "ExportNamedDeclaration") continue;
5174
+ const decl = st.declaration;
5175
+ if (decl?.type === "VariableDeclaration") {
5176
+ for (const d of decl.declarations ?? []) {
5177
+ const idn = d.id;
5178
+ if (idn?.type === "Identifier" && idn.name === exported) return exported;
5179
+ }
5180
+ }
5181
+ for (const sp of st.specifiers ?? []) {
5182
+ const exp = sp.exported?.name;
5183
+ const loc = sp.local?.name;
5184
+ if (exp === exported && loc) return loc;
5185
+ }
5186
+ }
5187
+ return null;
5188
+ }
5189
+ function topLevelInit(body, name) {
5190
+ for (const st of body) {
5191
+ const vd = st.type === "VariableDeclaration" ? st : st.type === "ExportNamedDeclaration" && st.declaration?.type === "VariableDeclaration" ? st.declaration : null;
5192
+ if (!vd) continue;
5193
+ for (const d of vd.declarations ?? []) {
5194
+ const idn = d.id;
5195
+ if (idn?.type === "Identifier" && idn.name === name && d.init) return d.init;
5196
+ }
5197
+ }
5198
+ return null;
5199
+ }
5200
+ function isComposedPiecesSymbol(n) {
5201
+ if (!n || n.type !== "CallExpression") return false;
5202
+ const callee = n.callee;
5203
+ const obj = callee?.object?.name;
5204
+ const prop = callee?.property?.name;
5205
+ const arg0 = n.arguments?.[0];
5206
+ return obj === "Symbol" && prop === "for" && arg0?.value === "parseman.composedPieces";
5207
+ }
5208
+ function findCarriedPiecesLiteral(root) {
5209
+ let found = null;
5210
+ const visit = (n) => {
5211
+ if (found || !n || typeof n !== "object") return;
5212
+ if (Array.isArray(n)) {
5213
+ for (const c of n) visit(c);
5214
+ return;
5215
+ }
5216
+ const node2 = n;
5217
+ if (node2.type === "CallExpression") {
5218
+ const callee = node2.callee;
5219
+ const obj = callee?.object?.name;
5220
+ const prop = callee?.property?.name;
5221
+ const args = node2.arguments;
5222
+ if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5223
+ const descriptor = args[2];
5224
+ for (const p of descriptor?.properties ?? []) {
5225
+ const key = p.key;
5226
+ if ((key?.name === "value" || key?.value === "value") && p.value) {
5227
+ found = { start: p.value.start, end: p.value.end };
5228
+ return;
5229
+ }
5230
+ }
5231
+ }
5232
+ }
5233
+ for (const k in node2) {
5234
+ if (k === "type" || k === "start" || k === "end") continue;
5235
+ const v2 = node2[k];
5236
+ if (v2 && typeof v2 === "object") visit(v2);
5237
+ }
5238
+ };
5239
+ visit(root);
5240
+ return found;
5241
+ }
4012
5242
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
4013
5243
  let result;
4014
5244
  try {
@@ -4020,15 +5250,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4020
5250
  const body = result.program.body;
4021
5251
  const macroImports = [];
4022
5252
  const allNames = /* @__PURE__ */ new Set();
5253
+ const importBindings = /* @__PURE__ */ new Map();
4023
5254
  for (const stmt of body) {
4024
5255
  if (stmt.type !== "ImportDeclaration") continue;
4025
5256
  const s = stmt;
4026
- if (!moduleAliases.has(s.source.value)) continue;
4027
- const isMacro = s.attributes.some((a) => {
5257
+ const isMacro = moduleAliases.has(s.source.value) && s.attributes.some((a) => {
4028
5258
  const key = a.key.type === "Identifier" ? a.key.name : String(a.key.value);
4029
5259
  return key === "type" && a.value.value === "macro";
4030
5260
  });
4031
- if (!isMacro) continue;
5261
+ if (!isMacro) {
5262
+ for (const spec of s.specifiers) {
5263
+ if (spec.type === "ImportSpecifier") {
5264
+ const imported = spec.imported.name ?? spec.local.name;
5265
+ importBindings.set(spec.local.name, { source: s.source.value, imported });
5266
+ }
5267
+ }
5268
+ continue;
5269
+ }
4032
5270
  const names = /* @__PURE__ */ new Set();
4033
5271
  for (const spec of s.specifiers) {
4034
5272
  if (spec.type === "ImportSpecifier") names.add(spec.local.name);
@@ -4071,6 +5309,89 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4071
5309
  return { replacement: compiled.replacement, ruleMap };
4072
5310
  };
4073
5311
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
5312
+ const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5313
+ const localRuleMaps = /* @__PURE__ */ new Map();
5314
+ const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5315
+ const serializePieces = (p) => {
5316
+ const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5317
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
5318
+ };
5319
+ const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5320
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
5321
+ const localComposedPieces = /* @__PURE__ */ new Map();
5322
+ const importedPiecesCache = /* @__PURE__ */ new Map();
5323
+ const importedPieces = (localName) => {
5324
+ if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5325
+ let result2 = null;
5326
+ const binding = importBindings.get(localName);
5327
+ if (binding) {
5328
+ let file = null;
5329
+ try {
5330
+ file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5331
+ } catch {
5332
+ }
5333
+ const mod = file ? parseModuleCached(file) : null;
5334
+ if (mod) {
5335
+ const localFor = exportLocalName(mod.body, binding.imported);
5336
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5337
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5338
+ if (literalRange) {
5339
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5340
+ try {
5341
+ result2 = new Function(`return (${literal2})`)();
5342
+ } catch {
5343
+ result2 = null;
5344
+ }
5345
+ }
5346
+ }
5347
+ }
5348
+ importedPiecesCache.set(localName, result2);
5349
+ return result2;
5350
+ };
5351
+ const argPieces = (arg, label2) => {
5352
+ if (isRulesCall(arg)) {
5353
+ const factory = arg.arguments[0];
5354
+ const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5355
+ const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5356
+ return p ? [p] : null;
5357
+ }
5358
+ if (arg.type === "Identifier") {
5359
+ const name = arg.name;
5360
+ const rm = localRuleMaps.get(name);
5361
+ if (rm) {
5362
+ const p = compileLinkable([...rm], nsFor(label2));
5363
+ return p ? [p] : null;
5364
+ }
5365
+ const composed = localComposedPieces.get(name);
5366
+ if (composed) return composed;
5367
+ return importedPieces(name);
5368
+ }
5369
+ return null;
5370
+ };
5371
+ const compileComposeCall = (init) => {
5372
+ const args = init.arguments;
5373
+ const arr = args[0];
5374
+ if (!arr || arr.type !== "ArrayExpression") {
5375
+ warn(init.start, "compose(): expected a static array of grammars/artifacts");
5376
+ return null;
5377
+ }
5378
+ const elements = arr.elements;
5379
+ const pieces = [];
5380
+ for (let i = 0; i < elements.length; i++) {
5381
+ const list = argPieces(elements[i], `compose${init.start}_${i}`);
5382
+ if (!list) {
5383
+ warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5384
+ return null;
5385
+ }
5386
+ pieces.push(...list);
5387
+ }
5388
+ try {
5389
+ return { replacement: emitFusedSource(pieces), pieces };
5390
+ } catch (e) {
5391
+ warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5392
+ return null;
5393
+ }
5394
+ };
4074
5395
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
4075
5396
  const refNames = /* @__PURE__ */ new Set();
4076
5397
  for (const stmt of body) {
@@ -4149,11 +5470,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4149
5470
  if (isRulesCall(init)) {
4150
5471
  const compiledRules = compileRulesFactory(init, varName);
4151
5472
  if (!compiledRules) continue;
4152
- replacements.push({
4153
- start: init.start,
4154
- end: init.end,
4155
- replacement: compiledRules.replacement
4156
- });
5473
+ localRuleMaps.set(varName, compiledRules.ruleMap);
5474
+ let replacement = compiledRules.replacement;
5475
+ if (exportPrefix) {
5476
+ const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
5477
+ if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5478
+ replacement = withCarriedPieces(replacement, [pieces]);
5479
+ }
5480
+ }
5481
+ replacements.push({ start: init.start, end: init.end, replacement });
5482
+ continue;
5483
+ }
5484
+ if (isComposeCall(init)) {
5485
+ const fused = compileComposeCall(init);
5486
+ if (!fused) continue;
5487
+ localComposedPieces.set(varName, fused.pieces);
5488
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
5489
+ replacements.push({ start: init.start, end: init.end, replacement });
4157
5490
  continue;
4158
5491
  }
4159
5492
  const mapFnSources = [];