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
@@ -34,8 +34,10 @@ __export(plugin_exports, {
34
34
  transformMacro: () => transformMacro
35
35
  });
36
36
  module.exports = __toCommonJS(plugin_exports);
37
+ var fs = __toESM(require("node:fs"), 1);
37
38
  var import_unplugin = require("unplugin");
38
39
  var import_oxc_parser = require("oxc-parser");
40
+ var import_oxc_resolver = require("oxc-resolver");
39
41
  var import_magic_string = __toESM(require("magic-string"), 1);
40
42
 
41
43
  // src/combinators/first-set.ts
@@ -321,6 +323,36 @@ function literal(value, opts = {}) {
321
323
  }
322
324
 
323
325
  // src/combinators/regex.ts
326
+ var firstSetAnalyzer = null;
327
+ function registerRegexAnalyzer(analyzer) {
328
+ firstSetAnalyzer = analyzer;
329
+ }
330
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
331
+ function regex(pattern, flags = "") {
332
+ const source = typeof pattern === "string" ? pattern : pattern.source;
333
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
334
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
335
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
336
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
337
+ return {
338
+ _tag: "regex",
339
+ _meta: meta,
340
+ _def: { tag: "regex", source, flags: resolvedFlags },
341
+ parse(input, pos, ctx) {
342
+ anchored.lastIndex = pos;
343
+ const m = anchored.exec(input);
344
+ if (m === null) {
345
+ return failAt(ctx, [`/${source}/`], pos);
346
+ }
347
+ const span = { start: pos, end: pos + m[0].length };
348
+ const leaf = { _tag: "leaf", value: m[0], span };
349
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
350
+ return { ok: true, value: m[0], span };
351
+ }
352
+ };
353
+ }
354
+
355
+ // src/combinators/regex-analyze.ts
324
356
  var import_regexp_tree = __toESM(require("regexp-tree"), 1);
325
357
  function firstSetFromRegex(pattern) {
326
358
  try {
@@ -363,15 +395,15 @@ function extractFirstSet(node2) {
363
395
  case "Alternative": {
364
396
  const exprs = node2.expressions;
365
397
  if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
366
- let fs = empty();
398
+ let fs2 = empty();
367
399
  let canNL = false;
368
400
  for (const expr of exprs) {
369
401
  const r = extractFirstSet(expr);
370
- fs = union(fs, r.firstSet);
402
+ fs2 = union(fs2, r.firstSet);
371
403
  canNL = canNL || r.canMatchNewline;
372
404
  if (!canBeEmpty(expr)) break;
373
405
  }
374
- return { firstSet: fs, canMatchNewline: canNL };
406
+ return { firstSet: fs2, canMatchNewline: canNL };
375
407
  }
376
408
  case "Char": {
377
409
  const kind = node2.kind;
@@ -398,22 +430,22 @@ function extractFirstSet(node2) {
398
430
  }
399
431
  case "CharacterClass": {
400
432
  const expressions = node2.expressions;
401
- let fs = empty();
433
+ let fs2 = empty();
402
434
  let canNL = false;
403
435
  for (const expr of expressions) {
404
436
  if (expr.type === "ClassRange") {
405
437
  const from = expr.from.codePoint;
406
438
  const to = expr.to.codePoint;
407
- fs = union(fs, fromRange(from, to));
439
+ fs2 = union(fs2, fromRange(from, to));
408
440
  if (from <= 10 && 10 <= to) canNL = true;
409
441
  } else if (expr.type === "Char") {
410
442
  const r = extractFirstSet(expr);
411
- fs = union(fs, r.firstSet);
443
+ fs2 = union(fs2, r.firstSet);
412
444
  canNL = canNL || r.canMatchNewline;
413
445
  }
414
446
  }
415
447
  if (node2.negative) return { firstSet: any(), canMatchNewline: true };
416
- return { firstSet: fs, canMatchNewline: canNL };
448
+ return { firstSet: fs2, canMatchNewline: canNL };
417
449
  }
418
450
  case "Repetition": {
419
451
  return extractFirstSet(node2.expression);
@@ -425,40 +457,6 @@ function extractFirstSet(node2) {
425
457
  return { firstSet: any(), canMatchNewline: true };
426
458
  }
427
459
  }
428
- function optimizeRegex(source, flags) {
429
- try {
430
- const result = import_regexp_tree.default.optimize(`/${source}/${flags}`);
431
- const str = result.toString();
432
- const lastSlash = str.lastIndexOf("/");
433
- return str.slice(1, lastSlash);
434
- } catch {
435
- return source;
436
- }
437
- }
438
- function regex(pattern, flags = "") {
439
- const source = typeof pattern === "string" ? pattern : pattern.source;
440
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
441
- const optimizedSource = optimizeRegex(source, resolvedFlags);
442
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
443
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
444
- const meta = { firstSet, canMatchNewline, isTrivia: false };
445
- return {
446
- _tag: "regex",
447
- _meta: meta,
448
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
449
- parse(input, pos, ctx) {
450
- anchored.lastIndex = pos;
451
- const m = anchored.exec(input);
452
- if (m === null) {
453
- return failAt(ctx, [`/${source}/`], pos);
454
- }
455
- const span = { start: pos, end: pos + m[0].length };
456
- const leaf = { _tag: "leaf", value: m[0], span };
457
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
458
- return { ok: true, value: m[0], span };
459
- }
460
- };
461
- }
462
460
 
463
461
  // src/combinators/keywords.ts
464
462
  function word(str, boundary = "_0-9A-Za-z") {
@@ -722,10 +720,10 @@ function codesToFirstSet(codes) {
722
720
  ranges.push({ lo, hi });
723
721
  return { kind: "ranges", ranges };
724
722
  }
725
- function inFirstSet(code, fs) {
726
- if (fs.kind === "any") return true;
727
- if (fs.kind === "empty") return false;
728
- for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
723
+ function inFirstSet(code, fs2) {
724
+ if (fs2.kind === "any") return true;
725
+ if (fs2.kind === "empty") return false;
726
+ for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
729
727
  return false;
730
728
  }
731
729
  function areDisjoint(sets) {
@@ -1158,12 +1156,14 @@ function confirmedBuildArity(src) {
1158
1156
  return parts.length;
1159
1157
  }
1160
1158
  function buildReadsTrivia(def) {
1159
+ if (!def.build) return true;
1161
1160
  const src = def.buildSrc ?? def.build.toString();
1162
1161
  const arity = confirmedBuildArity(src);
1163
1162
  if (arity === null) return true;
1164
1163
  return arity >= 4;
1165
1164
  }
1166
1165
  function buildReadsState(def) {
1166
+ if (!def.build) return true;
1167
1167
  const src = def.buildSrc ?? def.build.toString();
1168
1168
  const arity = confirmedBuildArity(src);
1169
1169
  if (arity === null) return true;
@@ -1182,8 +1182,8 @@ function node(type, combinator, build, opts) {
1182
1182
  };
1183
1183
  const collapse = opts?.collapse === true;
1184
1184
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1185
- const capturesTrivia = buildReadsTrivia(def);
1186
- const clonesState = buildReadsState(def);
1185
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1186
+ const clonesState = build ? buildReadsState(def) : true;
1187
1187
  return {
1188
1188
  _tag: "node",
1189
1189
  _meta: meta,
@@ -1194,13 +1194,8 @@ function node(type, combinator, build, opts) {
1194
1194
  const r = combinator.parse(input, pos, ctx);
1195
1195
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1196
1196
  if (!r.ok) return r;
1197
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1198
- children,
1199
- rawChildren,
1200
- r.span,
1201
- triviaLog,
1202
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1203
- );
1197
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1198
+ 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 };
1204
1199
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1205
1200
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1206
1201
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1343,8 +1338,14 @@ function staticExpected(c) {
1343
1338
  case "transform":
1344
1339
  case "not":
1345
1340
  return staticExpected(def.parser);
1346
- case "lazy":
1347
- return staticExpected(def.thunk());
1341
+ case "lazy": {
1342
+ const name = c._ruleName;
1343
+ try {
1344
+ return staticExpected(def.thunk());
1345
+ } catch {
1346
+ return name ? [name] : [];
1347
+ }
1348
+ }
1348
1349
  default:
1349
1350
  return [];
1350
1351
  }
@@ -1374,8 +1375,29 @@ function expect(combinator, label2) {
1374
1375
  var BACKSLASH = 92;
1375
1376
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1376
1377
  var META = new Set("()[]{}*+?|^$.".split(""));
1378
+ var SPACE_RANGES = [
1379
+ [9, 13],
1380
+ [32, 32],
1381
+ [160, 160],
1382
+ [5760, 5760],
1383
+ [8192, 8202],
1384
+ [8232, 8232],
1385
+ [8233, 8233],
1386
+ [8239, 8239],
1387
+ [8287, 8287],
1388
+ [12288, 12288],
1389
+ [65279, 65279]
1390
+ ];
1377
1391
  function shorthandRanges(ch) {
1378
- return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1392
+ if (ch === "d") return [[48, 57]];
1393
+ if (ch === "s") return SPACE_RANGES;
1394
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1395
+ }
1396
+ function readUnicodeEscape(body, i) {
1397
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1398
+ const hex = body.slice(i + 2, i + 6);
1399
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1400
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1379
1401
  }
1380
1402
  function parseClassRanges(body) {
1381
1403
  const ranges = [];
@@ -1384,11 +1406,16 @@ function parseClassRanges(body) {
1384
1406
  const ch = body[i];
1385
1407
  if (ch === void 0) return null;
1386
1408
  if (ch === "\\") {
1409
+ const uni = readUnicodeEscape(body, i);
1410
+ if (uni) {
1411
+ i = uni.next;
1412
+ return { cp: uni.cp };
1413
+ }
1387
1414
  const e = body[i + 1];
1388
1415
  if (e === void 0) return null;
1389
1416
  i += 2;
1390
1417
  if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1391
- if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1418
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1392
1419
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1393
1420
  return { cp: e.codePointAt(0) };
1394
1421
  }
@@ -1416,10 +1443,16 @@ function parseClassRanges(body) {
1416
1443
  function classToRanges(cls) {
1417
1444
  if (cls === "\\d") return shorthandRanges("d");
1418
1445
  if (cls === "\\w") return shorthandRanges("w");
1446
+ if (cls === "\\s") return shorthandRanges("s");
1419
1447
  const body = cls.slice(1, -1);
1420
1448
  if (body.startsWith("^")) return null;
1421
1449
  return parseClassRanges(body);
1422
1450
  }
1451
+ var foldEq = (cVar, cp) => {
1452
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1453
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1454
+ return `${cVar} === ${cp}`;
1455
+ };
1423
1456
  function literalCodePoints(frag) {
1424
1457
  const out = [];
1425
1458
  let i = 0;
@@ -1466,22 +1499,612 @@ function parseStringShape(source) {
1466
1499
  if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1467
1500
  return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1468
1501
  }
1502
+ function parseSeqParts(source) {
1503
+ const parts = [];
1504
+ let lit = [];
1505
+ const flush = () => {
1506
+ if (lit.length) {
1507
+ parts.push({ part: "lit", cps: lit, optional: false });
1508
+ lit = [];
1509
+ }
1510
+ };
1511
+ const runFrom = (ranges, negated, q) => {
1512
+ if (q === "+") {
1513
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1514
+ return 1;
1515
+ }
1516
+ if (q === "*") {
1517
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1518
+ return 1;
1519
+ }
1520
+ if (q === "?") {
1521
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1522
+ return 1;
1523
+ }
1524
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1525
+ return 0;
1526
+ };
1527
+ let i = 0;
1528
+ while (i < source.length) {
1529
+ const ch = source[i];
1530
+ if (ch === "[") {
1531
+ let k = i + 1;
1532
+ const neg = source[k] === "^";
1533
+ if (neg) k++;
1534
+ let body = "";
1535
+ while (k < source.length && source[k] !== "]") {
1536
+ if (source[k] === "\\") {
1537
+ body += source[k] + (source[k + 1] ?? "");
1538
+ k += 2;
1539
+ } else {
1540
+ body += source[k];
1541
+ k++;
1542
+ }
1543
+ }
1544
+ if (source[k] !== "]") return null;
1545
+ const ranges = parseClassRanges(body);
1546
+ if (!ranges) return null;
1547
+ flush();
1548
+ i = k + 1;
1549
+ i += runFrom(ranges, neg, source[i]);
1550
+ continue;
1551
+ }
1552
+ if (ch === "\\") {
1553
+ const e = source[i + 1];
1554
+ if (e === "d" || e === "w" || e === "s") {
1555
+ flush();
1556
+ i += 2;
1557
+ i += runFrom(shorthandRanges(e), false, source[i]);
1558
+ continue;
1559
+ }
1560
+ const uni = readUnicodeEscape(source, i);
1561
+ let cp2;
1562
+ if (uni) {
1563
+ cp2 = uni.cp;
1564
+ i = uni.next;
1565
+ } else if (e === void 0) return null;
1566
+ else if (e in CLASS_ESCAPES) {
1567
+ cp2 = CLASS_ESCAPES[e];
1568
+ i += 2;
1569
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1570
+ else {
1571
+ cp2 = e.codePointAt(0);
1572
+ i += 2;
1573
+ }
1574
+ if (source[i] === "?") {
1575
+ flush();
1576
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1577
+ i++;
1578
+ } else lit.push(cp2);
1579
+ continue;
1580
+ }
1581
+ if (ch === "(") {
1582
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1583
+ let k = i + 3;
1584
+ let depth = 1;
1585
+ while (k < source.length && depth > 0) {
1586
+ const c2 = source[k];
1587
+ if (c2 === "\\") {
1588
+ k += 2;
1589
+ continue;
1590
+ }
1591
+ if (c2 === "[") {
1592
+ let j = k + 1;
1593
+ if (source[j] === "^") j++;
1594
+ while (j < source.length && source[j] !== "]") {
1595
+ if (source[j] === "\\") j += 2;
1596
+ else j++;
1597
+ }
1598
+ if (source[j] !== "]") return null;
1599
+ k = j + 1;
1600
+ continue;
1601
+ }
1602
+ if (c2 === "(") depth++;
1603
+ else if (c2 === ")") depth--;
1604
+ k++;
1605
+ }
1606
+ if (depth !== 0) return null;
1607
+ const body = source.slice(i + 3, k - 1);
1608
+ const inner = parseScanShape(body);
1609
+ if (!inner || !groupInnerSafe(inner)) return null;
1610
+ const q = source[k];
1611
+ if (q === "{") return null;
1612
+ flush();
1613
+ if (q === "+") {
1614
+ if (shapeCanBeEmpty(inner)) return null;
1615
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1616
+ i = k + 1;
1617
+ } else if (q === "*") {
1618
+ if (shapeCanBeEmpty(inner)) return null;
1619
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1620
+ i = k + 1;
1621
+ } else if (q === "?") {
1622
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1623
+ i = k + 1;
1624
+ } else {
1625
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1626
+ i = k;
1627
+ }
1628
+ continue;
1629
+ }
1630
+ if (META.has(ch)) return null;
1631
+ const cp = ch.codePointAt(0);
1632
+ i += ch.length;
1633
+ if (source[i] === "?") {
1634
+ flush();
1635
+ parts.push({ part: "lit", cps: [cp], optional: true });
1636
+ i++;
1637
+ } else lit.push(cp);
1638
+ }
1639
+ flush();
1640
+ return parts.length ? parts : null;
1641
+ }
1642
+ function seqIsUnambiguous(parts) {
1643
+ let hasMandatory = false;
1644
+ for (let k = 0; k < parts.length; k++) {
1645
+ const p = parts[k];
1646
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1647
+ if (mandatory) hasMandatory = true;
1648
+ if (p.part === "group") {
1649
+ const exposure = groupPartExposure(p);
1650
+ if (exposure === "unsupported") {
1651
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1652
+ if (!trailingOnce) return false;
1653
+ } else if (exposure !== null) {
1654
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1655
+ }
1656
+ }
1657
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1658
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1659
+ if (skippableSingle || isUnboundedRepeat) {
1660
+ const rest = seqFirstAccept(parts.slice(k + 1));
1661
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1662
+ }
1663
+ }
1664
+ return hasMandatory;
1665
+ }
1666
+ function rangesOverlap(a, b) {
1667
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1668
+ }
1669
+ function rangesSubset(sub, sup) {
1670
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1671
+ }
1672
+ function parseClassOperand(body) {
1673
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1674
+ return { ranges: shorthandRanges(body[1]), negated: false };
1675
+ }
1676
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1677
+ let inner = body.slice(1, -1);
1678
+ const negated = inner.startsWith("^");
1679
+ if (negated) inner = inner.slice(1);
1680
+ const ranges = parseClassRanges(inner);
1681
+ return ranges ? { ranges, negated } : null;
1682
+ }
1683
+ const cps = literalCodePoints(body);
1684
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1685
+ return null;
1686
+ }
1687
+ function classSubset(t, u) {
1688
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1689
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1690
+ if (t.negated && !u.negated) return false;
1691
+ return rangesSubset(u.ranges, t.ranges);
1692
+ }
1693
+ function classDisjoint(t, u) {
1694
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1695
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1696
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1697
+ return false;
1698
+ }
1699
+ function firstSetsDisjoint(a, b) {
1700
+ if (a === "any" || b === "any") return false;
1701
+ return classDisjoint(a, b);
1702
+ }
1703
+ function fixedClassSeq(shape) {
1704
+ if (shape.kind !== "seq") return null;
1705
+ const out = [];
1706
+ for (const p of shape.parts) {
1707
+ if (p.part === "lit") {
1708
+ if (p.optional) return null;
1709
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1710
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1711
+ out.push({ ranges: p.ranges, negated: p.negated });
1712
+ } else {
1713
+ return null;
1714
+ }
1715
+ }
1716
+ return out.length ? out : null;
1717
+ }
1718
+ function altFixedMutuallyExclusive(shape) {
1719
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1720
+ const lists = [];
1721
+ for (const arm of shape.arms) {
1722
+ const f = fixedClassSeq(arm);
1723
+ if (!f) return false;
1724
+ lists.push(f);
1725
+ }
1726
+ for (let i = 0; i < lists.length; i++) {
1727
+ for (let j = 0; j < lists.length; j++) {
1728
+ if (i === j) continue;
1729
+ const a = lists[i];
1730
+ const b = lists[j];
1731
+ if (a.length > b.length) continue;
1732
+ let disjointAt = false;
1733
+ for (let p = 0; p < a.length; p++) {
1734
+ if (classDisjoint(a[p], b[p])) {
1735
+ disjointAt = true;
1736
+ break;
1737
+ }
1738
+ }
1739
+ if (!disjointAt) return false;
1740
+ }
1741
+ }
1742
+ return true;
1743
+ }
1744
+ function groupPartExposure(part) {
1745
+ const innerWiggle = trailingBacktrackClass(part.inner);
1746
+ if (innerWiggle === "unsupported") return "unsupported";
1747
+ const requiredOnce = !part.unbounded && part.min === 1;
1748
+ if (requiredOnce) return innerWiggle;
1749
+ const dropExposed = shapeFirstAccept(part.inner);
1750
+ if (dropExposed === "any") return "unsupported";
1751
+ if (innerWiggle === null) return dropExposed;
1752
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1753
+ return unioned === "any" ? "unsupported" : unioned;
1754
+ }
1755
+ function trailingBacktrackClass(shape) {
1756
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1757
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1758
+ if (shape.kind === "litFold") return null;
1759
+ if (shape.kind === "seq") {
1760
+ const last = shape.parts[shape.parts.length - 1];
1761
+ if (last.part === "lit") return null;
1762
+ if (last.part === "run") {
1763
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1764
+ return null;
1765
+ }
1766
+ return groupPartExposure(last);
1767
+ }
1768
+ if (shape.kind === "alt") {
1769
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1770
+ const classes = shape.arms.map(trailingBacktrackClass);
1771
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1772
+ const concrete = classes.filter((c) => c !== null);
1773
+ if (concrete.length === 0) return null;
1774
+ const unioned = unionCharSets(concrete);
1775
+ return unioned === "any" ? "unsupported" : unioned;
1776
+ }
1777
+ return "unsupported";
1778
+ }
1779
+ function lookaheadUnambiguous(inner, operand, negative) {
1780
+ const tail = trailingBacktrackClass(inner);
1781
+ if (tail === "unsupported") return false;
1782
+ if (tail === null) return true;
1783
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1784
+ }
1785
+ function stripTrailingLookahead(source) {
1786
+ if (!source.endsWith(")")) return null;
1787
+ for (const negative of [true, false]) {
1788
+ const lead = negative ? "(?!" : "(?=";
1789
+ const idx = source.lastIndexOf(lead);
1790
+ if (idx === -1) continue;
1791
+ const body = source.slice(idx + lead.length, -1);
1792
+ const operand = body.length ? parseClassOperand(body) : null;
1793
+ if (!operand) continue;
1794
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1795
+ }
1796
+ return null;
1797
+ }
1798
+ function unwrapOuterGroup(source) {
1799
+ while (source.startsWith("(?:")) {
1800
+ let depth = 1;
1801
+ let i = 3;
1802
+ let ok = true;
1803
+ while (i < source.length && depth > 0) {
1804
+ const ch = source[i];
1805
+ if (ch === "\\") {
1806
+ i += 2;
1807
+ continue;
1808
+ }
1809
+ if (ch === "[") {
1810
+ let j = i + 1;
1811
+ if (source[j] === "^") j++;
1812
+ while (j < source.length && source[j] !== "]") {
1813
+ if (source[j] === "\\") j += 2;
1814
+ else j++;
1815
+ }
1816
+ if (source[j] !== "]") {
1817
+ ok = false;
1818
+ break;
1819
+ }
1820
+ i = j + 1;
1821
+ continue;
1822
+ }
1823
+ if (ch === "(") depth++;
1824
+ else if (ch === ")") depth--;
1825
+ i++;
1826
+ }
1827
+ if (!ok || depth !== 0 || i !== source.length) break;
1828
+ source = source.slice(3, -1);
1829
+ }
1830
+ return source;
1831
+ }
1832
+ function splitTopLevelAlternation(source) {
1833
+ const body = unwrapOuterGroup(source);
1834
+ const parts = [];
1835
+ let depth = 0;
1836
+ let last = 0;
1837
+ let i = 0;
1838
+ while (i < body.length) {
1839
+ const ch = body[i];
1840
+ if (ch === "\\") {
1841
+ i += 2;
1842
+ continue;
1843
+ }
1844
+ if (ch === "[") {
1845
+ let j = i + 1;
1846
+ if (body[j] === "^") j++;
1847
+ while (j < body.length && body[j] !== "]") {
1848
+ if (body[j] === "\\") j += 2;
1849
+ else j++;
1850
+ }
1851
+ if (body[j] !== "]") return null;
1852
+ i = j + 1;
1853
+ continue;
1854
+ }
1855
+ if (ch === "(") {
1856
+ depth++;
1857
+ i++;
1858
+ continue;
1859
+ }
1860
+ if (ch === ")") {
1861
+ depth--;
1862
+ if (depth < 0) return null;
1863
+ i++;
1864
+ continue;
1865
+ }
1866
+ if (ch === "|" && depth === 0) {
1867
+ parts.push(body.slice(last, i));
1868
+ last = i + 1;
1869
+ i++;
1870
+ continue;
1871
+ }
1872
+ i++;
1873
+ }
1874
+ if (depth !== 0) return null;
1875
+ parts.push(body.slice(last));
1876
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1877
+ return parts;
1878
+ }
1879
+ function shapeCanBeEmpty(shape) {
1880
+ if (shape.kind === "chars") return !shape.minOne;
1881
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
1882
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
1883
+ return false;
1884
+ }
1885
+ function unionCharSets(sets) {
1886
+ if (sets.some((s) => s === "any")) return "any";
1887
+ const concrete = sets;
1888
+ if (concrete.length === 1) return concrete[0];
1889
+ if (concrete.some((s) => s.negated)) return "any";
1890
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
1891
+ }
1892
+ function partFirstAccept(part) {
1893
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
1894
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
1895
+ return shapeFirstAccept(part.inner);
1896
+ }
1897
+ function seqFirstAccept(parts) {
1898
+ const sets = [];
1899
+ for (const p of parts) {
1900
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1901
+ sets.push(partFirstAccept(p));
1902
+ if (mandatory) break;
1903
+ }
1904
+ return unionCharSets(sets);
1905
+ }
1906
+ function groupInnerSafe(shape) {
1907
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1908
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1909
+ if (shape.kind === "alt") return true;
1910
+ return false;
1911
+ }
1912
+ function shapeFirstAccept(shape) {
1913
+ if (shapeCanBeEmpty(shape)) return "any";
1914
+ switch (shape.kind) {
1915
+ case "chars":
1916
+ return { ranges: shape.ranges, negated: false };
1917
+ case "ident":
1918
+ return { ranges: shape.head, negated: false };
1919
+ case "until":
1920
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1921
+ case "delimited":
1922
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1923
+ case "string":
1924
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
1925
+ case "litFold": {
1926
+ const cp = shape.open[0];
1927
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
1928
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
1929
+ return { ranges: [[cp, cp]], negated: false };
1930
+ }
1931
+ case "seq":
1932
+ return seqFirstAccept(shape.parts);
1933
+ case "lookahead":
1934
+ return shapeFirstAccept(shape.inner);
1935
+ // zero-width — doesn't change first-char acceptance
1936
+ case "alt": {
1937
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
1938
+ return unionCharSets(sets);
1939
+ }
1940
+ }
1941
+ }
1942
+ function allPairsDisjoint(sets) {
1943
+ for (let i = 0; i < sets.length; i++) {
1944
+ const a = sets[i];
1945
+ if (a === "any") return false;
1946
+ for (let j = i + 1; j < sets.length; j++) {
1947
+ const b = sets[j];
1948
+ if (b === "any" || !classDisjoint(a, b)) return false;
1949
+ }
1950
+ }
1951
+ return true;
1952
+ }
1953
+ function parseAlternation(source) {
1954
+ const parts = splitTopLevelAlternation(source);
1955
+ if (!parts) return null;
1956
+ const arms = [];
1957
+ for (const p of parts) {
1958
+ const arm = parseScanShape(p);
1959
+ if (!arm) return null;
1960
+ arms.push(arm);
1961
+ }
1962
+ const firstSets = arms.map(shapeFirstAccept);
1963
+ const disjoint = allPairsDisjoint(firstSets);
1964
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
1965
+ }
1469
1966
  function parseScanShape(source) {
1967
+ const alt = parseAlternation(source);
1968
+ if (alt) return alt;
1969
+ const la = stripTrailingLookahead(source);
1970
+ if (la) {
1971
+ const inner = parseScanShapeCore(la.base);
1972
+ if (!inner) return null;
1973
+ const operand = { ranges: la.ranges, negated: la.classNegated };
1974
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
1975
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
1976
+ }
1977
+ return parseScanShapeCore(source);
1978
+ }
1979
+ function splitDelimArms(body) {
1980
+ const arms = [];
1981
+ let depth = 0;
1982
+ let last = 0;
1983
+ let i = 0;
1984
+ while (i < body.length) {
1985
+ const ch = body[i];
1986
+ if (ch === "\\") {
1987
+ i += 2;
1988
+ continue;
1989
+ }
1990
+ if (ch === "[") {
1991
+ let j = i + 1;
1992
+ if (body[j] === "^") j++;
1993
+ while (j < body.length && body[j] !== "]") {
1994
+ if (body[j] === "\\") j += 2;
1995
+ else j++;
1996
+ }
1997
+ if (body[j] !== "]") return null;
1998
+ i = j + 1;
1999
+ continue;
2000
+ }
2001
+ if (ch === "(") {
2002
+ depth++;
2003
+ i++;
2004
+ continue;
2005
+ }
2006
+ if (ch === ")") {
2007
+ depth--;
2008
+ if (depth < 0) return null;
2009
+ i++;
2010
+ continue;
2011
+ }
2012
+ if (ch === "|" && depth === 0) {
2013
+ arms.push(body.slice(last, i));
2014
+ last = i + 1;
2015
+ }
2016
+ i++;
2017
+ }
2018
+ if (depth !== 0) return null;
2019
+ arms.push(body.slice(last));
2020
+ return arms;
2021
+ }
2022
+ function delimitedBodySound(body, close) {
2023
+ const l0 = close[0];
2024
+ if (close.length > 2) return false;
2025
+ const arms = splitDelimArms(body);
2026
+ if (!arms) return false;
2027
+ let bulkSeen = false;
2028
+ let guardSeen = false;
2029
+ for (const arm of arms) {
2030
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
2031
+ if (bulk) {
2032
+ if (bulkSeen) return false;
2033
+ const ranges = parseClassRanges(bulk[1]);
2034
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2035
+ bulkSeen = true;
2036
+ continue;
2037
+ }
2038
+ if (close.length === 2 && arm.endsWith(")")) {
2039
+ const idx = arm.indexOf("(?!");
2040
+ if (idx === -1) return false;
2041
+ const prefix = literalCodePoints(arm.slice(0, idx));
2042
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2043
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2044
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2045
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2046
+ guardSeen = true;
2047
+ continue;
2048
+ }
2049
+ return false;
2050
+ }
2051
+ return bulkSeen && guardSeen === (close.length === 2);
2052
+ }
2053
+ function parseDelimited(source) {
2054
+ if (source.includes("\\\\")) return null;
2055
+ let i = 0;
2056
+ while (i < source.length && source[i] !== "(") {
2057
+ i += source[i] === "\\" ? 2 : 1;
2058
+ }
2059
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2060
+ const openFrag = source.slice(0, i);
2061
+ let k = i + 3;
2062
+ let depth = 1;
2063
+ while (k < source.length && depth > 0) {
2064
+ const ch = source[k];
2065
+ if (ch === "\\") {
2066
+ k += 2;
2067
+ continue;
2068
+ }
2069
+ if (ch === "[") {
2070
+ let j = k + 1;
2071
+ if (source[j] === "^") j++;
2072
+ while (j < source.length && source[j] !== "]") {
2073
+ if (source[j] === "\\") j += 2;
2074
+ else j++;
2075
+ }
2076
+ if (source[j] !== "]") return null;
2077
+ k = j + 1;
2078
+ continue;
2079
+ }
2080
+ if (ch === "(") depth++;
2081
+ else if (ch === ")") depth--;
2082
+ k++;
2083
+ }
2084
+ if (depth !== 0) return null;
2085
+ const body = source.slice(i + 3, k - 1);
2086
+ if (source[k] !== "*") return null;
2087
+ const closeFrag = source.slice(k + 1);
2088
+ const open = literalCodePoints(openFrag);
2089
+ const close = literalCodePoints(closeFrag);
2090
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2091
+ return { kind: "delimited", open, close };
2092
+ }
2093
+ function parseScanShapeCore(source) {
1470
2094
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1471
- if (m) {
1472
- if (m[1].startsWith("^")) return null;
2095
+ if (m && !m[1].startsWith("^")) {
1473
2096
  const ranges = parseClassRanges(m[1]);
1474
2097
  return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1475
2098
  }
1476
- m = /^\\([dw])([+*])$/.exec(source);
2099
+ m = /^\\([dws])([+*])$/.exec(source);
1477
2100
  if (m) {
1478
2101
  return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1479
2102
  }
1480
- m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
2103
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
1481
2104
  if (m) {
1482
2105
  const head = classToRanges(m[1]);
1483
2106
  const tail = classToRanges(m[2]);
1484
- return head && tail ? { kind: "ident", head, tail } : null;
2107
+ if (head && tail) return { kind: "ident", head, tail };
1485
2108
  }
1486
2109
  const str = parseStringShape(source);
1487
2110
  if (str) return str;
@@ -1490,20 +2113,19 @@ function parseScanShape(source) {
1490
2113
  const open = literalCodePoints(m[1]);
1491
2114
  const stop = parseClassRanges(m[2]);
1492
2115
  if (open && stop) return { kind: "until", open, stop };
1493
- return null;
1494
- }
1495
- if (!source.includes("\\\\")) {
1496
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1497
- if (m && m[1] && m[2]) {
1498
- const open = literalCodePoints(m[1]);
1499
- const close = literalCodePoints(m[2]);
1500
- if (open && close) return { kind: "delimited", open, close };
1501
- }
1502
2116
  }
2117
+ const delim = parseDelimited(source);
2118
+ if (delim) return delim;
2119
+ const parts = parseSeqParts(source);
2120
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1503
2121
  return null;
1504
2122
  }
1505
2123
  function scanShapeFromRegex(source, flags) {
1506
- if (/[imsu]/.test(flags)) return null;
2124
+ if (/[msu]/.test(flags)) return null;
2125
+ if (/i/.test(flags)) {
2126
+ const open = literalCodePoints(source);
2127
+ return open ? { kind: "litFold", open } : null;
2128
+ }
1507
2129
  return parseScanShape(source);
1508
2130
  }
1509
2131
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
@@ -1518,6 +2140,53 @@ function stringHardStop(shape) {
1518
2140
  }
1519
2141
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1520
2142
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2143
+ if (shape.kind === "lookahead") {
2144
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
2145
+ const okV = mint("_ok");
2146
+ const endV2 = mint("_end");
2147
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
2148
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
2149
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
2150
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
2151
+ return {
2152
+ setup: [
2153
+ ...inner.setup,
2154
+ `${ind2}let ${okV} = ${inner.ok}`,
2155
+ `${ind2}let ${endV2} = ${inner.end}`,
2156
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
2157
+ ],
2158
+ ok: okV,
2159
+ end: endV2
2160
+ };
2161
+ }
2162
+ if (shape.kind === "alt") {
2163
+ const okV = mint("_ok");
2164
+ const endV2 = mint("_end");
2165
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2166
+ if (shape.disjoint) {
2167
+ const c0 = codeAt(start, firstChar);
2168
+ shape.arms.forEach((arm, k) => {
2169
+ const fs2 = shape.firsts[k];
2170
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2171
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2172
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2173
+ lines.push(...m.setup);
2174
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2175
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2176
+ lines.push(`${ind2}}`);
2177
+ });
2178
+ } else {
2179
+ const lbl = mint("_alt");
2180
+ lines.push(`${ind2}${lbl}: {`);
2181
+ for (const arm of shape.arms) {
2182
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2183
+ lines.push(...m.setup);
2184
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
2185
+ }
2186
+ lines.push(`${ind2}}`);
2187
+ }
2188
+ return { setup: lines, ok: okV, end: endV2 };
2189
+ }
1521
2190
  if (shape.kind === "chars") {
1522
2191
  const cur = mint("_e");
1523
2192
  return {
@@ -1544,6 +2213,82 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1544
2213
  end: cur
1545
2214
  };
1546
2215
  }
2216
+ if (shape.kind === "seq") {
2217
+ const endV2 = mint("_e");
2218
+ const okV = mint("_ok");
2219
+ const runCond = (p, at) => {
2220
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2221
+ return p.negated ? `!(${c})` : `(${c})`;
2222
+ };
2223
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2224
+ let first = true;
2225
+ for (const p of shape.parts) {
2226
+ if (p.part === "lit") {
2227
+ const L = p.cps.length;
2228
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2229
+ if (p.optional) {
2230
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2231
+ } else {
2232
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2233
+ lines.push(`${ind2} ${endV2} += ${L}`);
2234
+ }
2235
+ } else if (p.part === "run" && !p.unbounded) {
2236
+ if (p.min === 1) {
2237
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2238
+ lines.push(`${ind2} ${endV2}++`);
2239
+ } else {
2240
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2241
+ }
2242
+ } else if (p.part === "run") {
2243
+ const s = mint("_s");
2244
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2245
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2246
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2247
+ } else {
2248
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2249
+ if (!p.unbounded) {
2250
+ lines.push(...inner.setup);
2251
+ if (p.min === 1) {
2252
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2253
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2254
+ } else {
2255
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2256
+ }
2257
+ } else {
2258
+ const progV = mint("_prog");
2259
+ lines.push(`${ind2} let ${progV} = false`);
2260
+ lines.push(`${ind2} while (true) {`);
2261
+ lines.push(...inner.setup);
2262
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2263
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2264
+ lines.push(`${ind2} ${progV} = true`);
2265
+ lines.push(`${ind2} }`);
2266
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2267
+ }
2268
+ }
2269
+ first = false;
2270
+ }
2271
+ lines.push(`${ind2} ${okV} = true`);
2272
+ lines.push(`${ind2}} while (false)`);
2273
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2274
+ return { setup: lines, ok: okV, end: endV2 };
2275
+ }
2276
+ if (shape.kind === "litFold") {
2277
+ const openLen2 = shape.open.length;
2278
+ const endV2 = mint("_end");
2279
+ const checks = shape.open.map((cp, k) => {
2280
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2281
+ return foldEq(cVar, cp);
2282
+ });
2283
+ return {
2284
+ setup: [
2285
+ `${ind2}let ${endV2} = ${start}`,
2286
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2287
+ ],
2288
+ ok: `${endV2} > ${start}`,
2289
+ end: endV2
2290
+ };
2291
+ }
1547
2292
  if (shape.kind === "until") {
1548
2293
  const j2 = mint("_j");
1549
2294
  const openLen2 = shape.open.length;
@@ -1825,6 +2570,7 @@ function looseMkBuildRe(type) {
1825
2570
  );
1826
2571
  }
1827
2572
  function analyzeMkInlineBuild(def) {
2573
+ if (!def.build) return null;
1828
2574
  const src = (def.buildSrc ?? def.build.toString()).trim();
1829
2575
  const strict = src.match(MK_BUILD_RE);
1830
2576
  if (strict) {
@@ -1922,6 +2668,15 @@ function v(ctx, prefix = "_v") {
1922
2668
  function ind(ctx) {
1923
2669
  return " ".repeat(ctx.indent);
1924
2670
  }
2671
+ function nsp(ctx) {
2672
+ return ctx.ns ?? "";
2673
+ }
2674
+ function mfRef(ctx) {
2675
+ return `${nsp(ctx)}_mf`;
2676
+ }
2677
+ function buildRef(ctx) {
2678
+ return `${nsp(ctx)}_build`;
2679
+ }
1925
2680
  function reindentStmts(stmts, targetLevels) {
1926
2681
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
1927
2682
  if (nonEmpty.length === 0) return stmts;
@@ -1938,8 +2693,8 @@ function failReturnArr(expectedArr, posExpr) {
1938
2693
  function hoistExpected(ctx, constArrSource) {
1939
2694
  let name = ctx.expectedMap.get(constArrSource);
1940
2695
  if (name === void 0) {
1941
- name = `_fx${ctx.expectedDecls.length}`;
1942
- ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
2696
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2697
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
1943
2698
  ctx.expectedMap.set(constArrSource, name);
1944
2699
  }
1945
2700
  return name;
@@ -2037,13 +2792,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2037
2792
  `${i}}`
2038
2793
  ];
2039
2794
  }
2040
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2795
+ function ensureRegexDecl(ctx, source, flags) {
2041
2796
  const f = "y" + flags.replace(/[gy]/g, "");
2042
- const key = `${optimizedSource}/${f}`;
2797
+ const key = `${source}/${f}`;
2043
2798
  let rName = ctx.regexMap.get(key);
2044
2799
  if (rName === void 0) {
2045
- rName = `_re${ctx.regexDecls.length}`;
2046
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2800
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2801
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2047
2802
  ctx.regexMap.set(key, rName);
2048
2803
  }
2049
2804
  return rName;
@@ -2052,7 +2807,7 @@ function ensureTriviaFn(ctx) {
2052
2807
  const trivia2 = ctx.activeTrivia;
2053
2808
  const existing = ctx.triviaFnNames.get(trivia2);
2054
2809
  if (existing) return existing;
2055
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2810
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2056
2811
  ctx.triviaFnNames.set(trivia2, fnName);
2057
2812
  ctx.triviaCaptureNames.set(trivia2, fnName);
2058
2813
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2074,7 +2829,7 @@ function ensureTriviaFn(ctx) {
2074
2829
  for (const arm of regexSpec.arms) {
2075
2830
  const def = arm.parser._def;
2076
2831
  if (def.tag !== "regex") break;
2077
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2832
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2078
2833
  }
2079
2834
  if (reNames.length === regexSpec.arms.length) {
2080
2835
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -2120,10 +2875,10 @@ function ensureTriviaFn(ctx) {
2120
2875
  function ensureTriviaCaptureFn(ctx) {
2121
2876
  return ensureTriviaFn(ctx);
2122
2877
  }
2123
- function firstSetCond(codeVar, fs) {
2124
- if (fs.kind === "any") return "true";
2125
- if (fs.kind === "empty") return "false";
2126
- return fs.ranges.map(
2878
+ function firstSetCond(codeVar, fs2) {
2879
+ if (fs2.kind === "any") return "true";
2880
+ if (fs2.kind === "empty") return "false";
2881
+ return fs2.ranges.map(
2127
2882
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
2128
2883
  ).join(" || ");
2129
2884
  }
@@ -2135,10 +2890,10 @@ function planDisjointDispatch(parsers) {
2135
2890
  const cases = [];
2136
2891
  let total = 0;
2137
2892
  for (const p of parsers) {
2138
- const fs = p._meta.firstSet;
2139
- if (fs.kind !== "ranges") return { kind: "if" };
2893
+ const fs2 = p._meta.firstSet;
2894
+ if (fs2.kind !== "ranges") return { kind: "if" };
2140
2895
  const pts = [];
2141
- for (const r of fs.ranges) {
2896
+ for (const r of fs2.ranges) {
2142
2897
  if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2143
2898
  for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2144
2899
  }
@@ -2261,18 +3016,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
2261
3016
  return true;
2262
3017
  }
2263
3018
  }
2264
- function emitFallible(inner, ctx, pos) {
3019
+ function emitFallible(inner, ctx, pos, swallow = false) {
2265
3020
  const lbl = v(ctx, "_lbl");
2266
3021
  const okV = `${lbl}ok`;
2267
3022
  const valV = `${lbl}v`;
2268
3023
  const endV = `${lbl}e`;
2269
3024
  const savedLabel = ctx.failLabel;
2270
3025
  const savedIndent = ctx.indent;
3026
+ const savedRecord = ctx.recordFail;
2271
3027
  ctx.failLabel = lbl;
2272
3028
  ctx.indent = savedIndent + 1;
3029
+ if (swallow) ctx.recordFail = false;
2273
3030
  const r = emit(inner, ctx, pos);
2274
3031
  ctx.failLabel = savedLabel;
2275
3032
  ctx.indent = savedIndent;
3033
+ ctx.recordFail = savedRecord;
2276
3034
  const ind0 = ind(ctx);
2277
3035
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
2278
3036
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2295,6 +3053,7 @@ function emitFallible(inner, ctx, pos) {
2295
3053
  ];
2296
3054
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2297
3055
  }
3056
+ var CHARCODE_CHAIN_MAX = 16;
2298
3057
  function emitLit(def, ctx, pos) {
2299
3058
  const { value, caseInsensitive } = def;
2300
3059
  const len = value.length;
@@ -2317,7 +3076,7 @@ function emitLit(def, ctx, pos) {
2317
3076
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2318
3077
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2319
3078
  );
2320
- } else if (len <= 4) {
3079
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2321
3080
  const checks = Array.from(
2322
3081
  { length: len },
2323
3082
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2339,7 +3098,55 @@ function emitLit(def, ctx, pos) {
2339
3098
  function escapeKeywordRe(s) {
2340
3099
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2341
3100
  }
3101
+ function emitKeywordsFast(def, ctx, pos) {
3102
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
3103
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
3104
+ if (def.caseInsensitive && def.boundary) return null;
3105
+ let boundary = null;
3106
+ if (def.boundary) {
3107
+ let body = def.boundary;
3108
+ const negated = body.startsWith("^");
3109
+ if (negated) body = body.slice(1);
3110
+ const ranges = parseClassRanges(body);
3111
+ if (!ranges) return null;
3112
+ boundary = { ranges, negated };
3113
+ }
3114
+ const mint = (prefix = "_v") => v(ctx, prefix);
3115
+ const lbl = v(ctx, "_kwLbl");
3116
+ const valV = v(ctx, "_kwv");
3117
+ const endV = v(ctx, "_kwe");
3118
+ const bodyInd = ind(ctx) + " ";
3119
+ const tries = [];
3120
+ for (const w of def.words) {
3121
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
3122
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
3123
+ if (boundary) {
3124
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
3125
+ }
3126
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
3127
+ tries.push(
3128
+ ...m.setup,
3129
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
3130
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
3131
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
3132
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
3133
+ );
3134
+ }
3135
+ const stmts = [
3136
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
3137
+ `${ind(ctx)}${lbl}: {`,
3138
+ ...tries,
3139
+ `${ind(ctx)}}`,
3140
+ // Every word has length >= 1 (checked above), so a real match always
3141
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
3142
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
3143
+ ];
3144
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
3145
+ return { stmts, valueVar: valV, endVar: endV };
3146
+ }
2342
3147
  function emitKeywords(def, ctx, pos) {
3148
+ const fast = emitKeywordsFast(def, ctx, pos);
3149
+ if (fast) return fast;
2343
3150
  const alt = def.words.map(escapeKeywordRe).join("|");
2344
3151
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2345
3152
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2347,7 +3154,7 @@ function emitKeywords(def, ctx, pos) {
2347
3154
  const key = `${source}/${flags}`;
2348
3155
  let rName = ctx.regexMap.get(key);
2349
3156
  if (rName === void 0) {
2350
- rName = `_re${ctx.regexDecls.length}`;
3157
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2351
3158
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
2352
3159
  ctx.regexMap.set(key, rName);
2353
3160
  }
@@ -2381,11 +3188,11 @@ function emitRegex(def, ctx, pos) {
2381
3188
  }
2382
3189
  }
2383
3190
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2384
- const key = `${def.optimizedSource}/${flags}`;
3191
+ const key = `${def.source}/${flags}`;
2385
3192
  let rName = ctx.regexMap.get(key);
2386
3193
  if (rName === void 0) {
2387
- rName = `_re${ctx.regexDecls.length}`;
2388
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3194
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3195
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
2389
3196
  ctx.regexMap.set(key, rName);
2390
3197
  }
2391
3198
  const mv = v(ctx, "_m");
@@ -2470,6 +3277,31 @@ function failsAtStart(p) {
2470
3277
  return false;
2471
3278
  }
2472
3279
  }
3280
+ function armStaticExpected(ctx, p) {
3281
+ return hoistExpected(ctx, staticExpectedArr([p]));
3282
+ }
3283
+ function canMatchEmptyAtStart(p) {
3284
+ const d = p._def;
3285
+ switch (d.tag) {
3286
+ case "regex":
3287
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3288
+ case "optional":
3289
+ return true;
3290
+ case "many":
3291
+ return true;
3292
+ case "transform":
3293
+ case "label":
3294
+ return canMatchEmptyAtStart(d.parser);
3295
+ case "literal":
3296
+ return d.value.length === 0;
3297
+ default:
3298
+ return false;
3299
+ }
3300
+ }
3301
+ function needsFirstSetGuard(p) {
3302
+ const fs2 = p._meta.firstSet;
3303
+ return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
3304
+ }
2473
3305
  function emitDisjointArm(p, ctx, pos, valV, endV) {
2474
3306
  if (failsAtStart(p)) {
2475
3307
  const r = emit(p, ctx, pos);
@@ -2537,7 +3369,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
2537
3369
  const reKey = `${regexDef.source}/${cleanFlags}`;
2538
3370
  let reVar = ctx.regexMap.get(reKey);
2539
3371
  if (reVar === void 0) {
2540
- reVar = `_re${ctx.regexDecls.length}`;
3372
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2541
3373
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
2542
3374
  ctx.regexMap.set(reKey, reVar);
2543
3375
  }
@@ -2611,21 +3443,26 @@ function emitFirstMatch(def, ctx, pos) {
2611
3443
  const resValV = v(ctx, "_crv");
2612
3444
  const resEndV = v(ctx, "_cre");
2613
3445
  const resOkV = v(ctx, "_crok");
3446
+ const codeV = v(ctx, "_chcode");
2614
3447
  const slots = def.parsers.map(() => v(ctx, "_cfx"));
2615
3448
  const ind0 = ind(ctx);
2616
3449
  const stmts = [
2617
3450
  `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2618
- `${ind0}let ${slots.join(", ")}`
3451
+ `${ind0}let ${slots.join(", ")}`,
3452
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2619
3453
  ];
2620
3454
  for (let i = 0; i < def.parsers.length; i++) {
2621
3455
  const p = def.parsers[i];
2622
3456
  const gate = def.gates[i];
2623
3457
  const autoNot = def.autoNot[i];
3458
+ const atStart = failsAtStart(p);
3459
+ const staticFx = armStaticExpected(ctx, p);
3460
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2624
3461
  let gateCond = null;
2625
3462
  if (gate) {
2626
3463
  const gateIdx = ctx.mapFns.length;
2627
3464
  ctx.mapFns.push(gate);
2628
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3465
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
2629
3466
  }
2630
3467
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2631
3468
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -2635,39 +3472,41 @@ function emitFirstMatch(def, ctx, pos) {
2635
3472
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2636
3473
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2637
3474
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3475
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2638
3476
  stmts.push(`${ind0}if (${skipCond}) {`);
2639
- ctx.indent++;
2640
- const ind1 = ind(ctx);
3477
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3478
+ ctx.indent += fsGuard ? 2 : 1;
2641
3479
  if (markLeaves) {
2642
3480
  stmts.push(
2643
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2644
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2645
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2646
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3481
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3482
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3483
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3484
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2647
3485
  );
2648
3486
  }
2649
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3487
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2650
3488
  stmts.push(...armStmts);
2651
- ctx.indent--;
2652
3489
  if (autoNot && autoNot.length > 0) {
2653
3490
  const anCode = v(ctx, "_anc");
2654
3491
  const rejectCond = autoNot.map(
2655
3492
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2656
3493
  ).join(" || ");
2657
- stmts.push(`${ind0} if (${okVar}) {`);
2658
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2659
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2660
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2661
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2662
- stmts.push(`${ind0} ${resOkV} = true`);
2663
- stmts.push(`${ind0} }`);
2664
- stmts.push(`${ind0} }`);
2665
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2666
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3494
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3495
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3496
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3497
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3498
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3499
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3500
+ stmts.push(`${ind(ctx)} }`);
3501
+ stmts.push(`${ind(ctx)}}`);
3502
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3503
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2667
3504
  } else {
2668
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2669
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
3505
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3506
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2670
3507
  }
3508
+ ctx.indent -= fsGuard ? 2 : 1;
3509
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2671
3510
  stmts.push(`${ind0}}`);
2672
3511
  }
2673
3512
  const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
@@ -2690,7 +3529,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2690
3529
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
2691
3530
  const vv = v(ctx);
2692
3531
  return {
2693
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3532
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
2694
3533
  valueVar: vv,
2695
3534
  endVar: endV
2696
3535
  };
@@ -2700,7 +3539,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2700
3539
  function emitLiteralCondition(litVal, pos) {
2701
3540
  const len = litVal.length;
2702
3541
  if (len === 0) return "true";
2703
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3542
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2704
3543
  const checks = [`${pos} + ${len} <= input.length`];
2705
3544
  for (let i = 0; i < len; i++) {
2706
3545
  const code = litVal.codePointAt(i);
@@ -2749,7 +3588,7 @@ function emitMany(def, ctx, pos) {
2749
3588
  itemPos = npV;
2750
3589
  }
2751
3590
  }
2752
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3591
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2753
3592
  stmts.push(
2754
3593
  ...iterStmts,
2755
3594
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2763,7 +3602,7 @@ function emitMany(def, ctx, pos) {
2763
3602
  function emitOptional(def, ctx, pos) {
2764
3603
  const valV = v(ctx, "_opt");
2765
3604
  const endV = v(ctx, "_opte");
2766
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3605
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2767
3606
  const ind0 = ind(ctx);
2768
3607
  const stmts = [
2769
3608
  ...lblStmts,
@@ -2775,7 +3614,7 @@ function emitOptional(def, ctx, pos) {
2775
3614
  function emitSepBy(_p, def, ctx, pos) {
2776
3615
  const arrV = v(ctx, "_arr");
2777
3616
  const curV = v(ctx, "_cur");
2778
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3617
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2779
3618
  const stmts = [
2780
3619
  `${ind(ctx)}const ${arrV} = []`,
2781
3620
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2807,11 +3646,11 @@ function emitSepBy(_p, def, ctx, pos) {
2807
3646
  );
2808
3647
  sepAtPos = spV;
2809
3648
  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}; `;
2810
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3649
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2811
3650
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2812
3651
  const npV = v(ctx, "_np");
2813
3652
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2814
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3653
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2815
3654
  stmts.push(
2816
3655
  ...nextStmts,
2817
3656
  // item failed → unwind the separator too, back to the end of the last item
@@ -2824,11 +3663,11 @@ function emitSepBy(_p, def, ctx, pos) {
2824
3663
  const spV = v(ctx, "_sp");
2825
3664
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2826
3665
  sepAtPos = spV;
2827
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3666
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2828
3667
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2829
3668
  const npV = v(ctx, "_np");
2830
3669
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2831
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3670
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2832
3671
  stmts.push(
2833
3672
  ...nextStmts,
2834
3673
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2845,10 +3684,10 @@ function emitSepBy(_p, def, ctx, pos) {
2845
3684
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2846
3685
  );
2847
3686
  }
2848
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3687
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2849
3688
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2850
3689
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2851
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3690
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2852
3691
  stmts.push(
2853
3692
  ...nextStmts,
2854
3693
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -2906,7 +3745,7 @@ function emitScanTo(def, ctx, pos) {
2906
3745
  return { stmts, valueVar: valV, endVar: curV };
2907
3746
  }
2908
3747
  function emitNot(def, ctx, pos) {
2909
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3748
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
2910
3749
  const label2 = JSON.stringify(`not(${def.parser._tag})`);
2911
3750
  return {
2912
3751
  stmts: [
@@ -2918,16 +3757,17 @@ function emitNot(def, ctx, pos) {
2918
3757
  };
2919
3758
  }
2920
3759
  function emitNode(def, ctx, pos) {
2921
- const mkType = analyzeMkInlineBuild(def);
3760
+ const structural = def.build === void 0;
3761
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
2922
3762
  let buildIdx = null;
2923
- if (!mkType) {
3763
+ if (!mkType && !structural) {
2924
3764
  buildIdx = ctx.buildFns.length;
2925
3765
  ctx.buildFns.push(def.build);
2926
3766
  ctx.buildSrcs.push(def.buildSrc ?? null);
2927
3767
  }
2928
3768
  const i = ind(ctx);
2929
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
2930
- const clonesState = buildReadsState(def);
3769
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3770
+ const clonesState = structural || buildReadsState(def);
2931
3771
  const chV = v(ctx, "_ch");
2932
3772
  const rawV = v(ctx, "_raw");
2933
3773
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -2950,7 +3790,8 @@ function emitNode(def, ctx, pos) {
2950
3790
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2951
3791
  }
2952
3792
  const ndV = v(ctx, "_nd");
2953
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3793
+ 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})`;
3794
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
2954
3795
  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;
2955
3796
  stmts.push(
2956
3797
  `${i}const ${ndV} = ${finalExpr}`,
@@ -2975,8 +3816,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2975
3816
  return { stmts, valueVar: vv, endVar: ev };
2976
3817
  }
2977
3818
  function emitLazy(p, def, ctx, pos) {
3819
+ const canonical = ctx.ruleNames?.get(p);
2978
3820
  const usage = ctx.lazyUsage;
2979
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3821
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
2980
3822
  let resolved;
2981
3823
  try {
2982
3824
  resolved = def.thunk();
@@ -2986,7 +3828,7 @@ function emitLazy(p, def, ctx, pos) {
2986
3828
  return emit(resolved, ctx, pos);
2987
3829
  }
2988
3830
  if (!ctx.namedParsers.has(p)) {
2989
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3831
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
2990
3832
  ctx.namedParsers.set(p, fnName2);
2991
3833
  let resolved;
2992
3834
  try {
@@ -2997,11 +3839,14 @@ function emitLazy(p, def, ctx, pos) {
2997
3839
  }
2998
3840
  const savedIndent = ctx.indent;
2999
3841
  const savedFailLabel = ctx.failLabel;
3842
+ const savedRecord = ctx.recordFail;
3000
3843
  ctx.indent = 1;
3001
3844
  ctx.failLabel = "_pfail";
3845
+ ctx.recordFail = true;
3002
3846
  const r = emit(resolved, ctx, "_pos");
3003
3847
  ctx.indent = savedIndent;
3004
3848
  ctx.failLabel = savedFailLabel;
3849
+ ctx.recordFail = savedRecord;
3005
3850
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
3006
3851
  }
3007
3852
  const fnName = ctx.namedParsers.get(p);
@@ -3101,7 +3946,7 @@ function emit(p, ctx, pos) {
3101
3946
  return {
3102
3947
  stmts: [
3103
3948
  ...inner.stmts,
3104
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3949
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3105
3950
  ],
3106
3951
  valueVar: mv,
3107
3952
  endVar: inner.endVar
@@ -3164,7 +4009,7 @@ function emit(p, ctx, pos) {
3164
4009
  const vv = v(ctx);
3165
4010
  return {
3166
4011
  stmts: [
3167
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
4012
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3168
4013
  `${ind(ctx)}const ${vv} = null`
3169
4014
  ],
3170
4015
  valueVar: vv,
@@ -3181,11 +4026,14 @@ function emit(p, ctx, pos) {
3181
4026
  ctx.namedParsers.set(innerParser, fnName);
3182
4027
  const savedIndent = ctx.indent;
3183
4028
  const savedFailLabel = ctx.failLabel;
4029
+ const savedRecord = ctx.recordFail;
3184
4030
  ctx.indent = 1;
3185
4031
  ctx.failLabel = "_pfail";
4032
+ ctx.recordFail = true;
3186
4033
  const innerR = emit(innerParser, ctx, "_pos");
3187
4034
  ctx.indent = savedIndent;
3188
4035
  ctx.failLabel = savedFailLabel;
4036
+ ctx.recordFail = savedRecord;
3189
4037
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
3190
4038
  }
3191
4039
  const fn = ctx.namedParsers.get(innerParser);
@@ -3194,7 +4042,7 @@ function emit(p, ctx, pos) {
3194
4042
  const ev = v(ctx, "_wce");
3195
4043
  return {
3196
4044
  stmts: [
3197
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4045
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3198
4046
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3199
4047
  // withCtx runs on a spread child ctx — copy its recorded failure back
3200
4048
  // and propagate the inner failure verbatim (interpreter parity).
@@ -3325,6 +4173,39 @@ function analyzeLazyUsageMulti(roots) {
3325
4173
  for (const root of roots) walk2(root);
3326
4174
  return { counts, recursive };
3327
4175
  }
4176
+ function ruleDependencies(ruleMap) {
4177
+ const nameOf = /* @__PURE__ */ new Map();
4178
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4179
+ const deps = /* @__PURE__ */ new Map();
4180
+ for (const [name, comb] of ruleMap) {
4181
+ const found = /* @__PURE__ */ new Set();
4182
+ const seen = /* @__PURE__ */ new Set();
4183
+ const walk2 = (p, isRoot) => {
4184
+ const def = p._def;
4185
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4186
+ if (!isRoot && boundary !== void 0) {
4187
+ found.add(boundary);
4188
+ return;
4189
+ }
4190
+ if (seen.has(p)) return;
4191
+ seen.add(p);
4192
+ if (def.tag === "lazy") {
4193
+ let resolved;
4194
+ try {
4195
+ resolved = def.thunk();
4196
+ } catch {
4197
+ return;
4198
+ }
4199
+ walk2(resolved, false);
4200
+ return;
4201
+ }
4202
+ for (const child of childrenOf(def)) walk2(child, false);
4203
+ };
4204
+ walk2(comb, true);
4205
+ deps.set(name, [...found]);
4206
+ }
4207
+ return deps;
4208
+ }
3328
4209
  function compile(combinator, mapFnSources) {
3329
4210
  const ctx = {
3330
4211
  vars: 0,
@@ -3333,6 +4214,7 @@ function compile(combinator, mapFnSources) {
3333
4214
  regexMap: /* @__PURE__ */ new Map(),
3334
4215
  expectedDecls: [],
3335
4216
  expectedMap: /* @__PURE__ */ new Map(),
4217
+ recordFail: true,
3336
4218
  mapFns: [],
3337
4219
  mapFnSrcs: [],
3338
4220
  buildFns: [],
@@ -3415,6 +4297,7 @@ function compileRuleMap(ruleMap) {
3415
4297
  regexMap: /* @__PURE__ */ new Map(),
3416
4298
  expectedDecls: [],
3417
4299
  expectedMap: /* @__PURE__ */ new Map(),
4300
+ recordFail: true,
3418
4301
  mapFns: [],
3419
4302
  mapFnSrcs: [],
3420
4303
  buildFns: [],
@@ -3428,6 +4311,15 @@ function compileRuleMap(ruleMap) {
3428
4311
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
3429
4312
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule))
3430
4313
  };
4314
+ const seenNames = /* @__PURE__ */ new Map();
4315
+ const ruleNames = /* @__PURE__ */ new Map();
4316
+ for (const [key, rule] of ruleMap) {
4317
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4318
+ const n = seenNames.get(base) ?? 0;
4319
+ seenNames.set(base, n + 1);
4320
+ ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4321
+ }
4322
+ ctx.ruleNames = ruleNames;
3431
4323
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
3432
4324
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
3433
4325
  ` : "";
@@ -3437,8 +4329,8 @@ function compileRuleMap(ruleMap) {
3437
4329
  const mfCovered = ctx.mapFns.length === 0 || derivedSrcs !== void 0;
3438
4330
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
3439
4331
  if (!canInline) return null;
3440
- const mfDecl = derivedSrcs?.length ? ` const _mf = [${derivedSrcs.join(", ")}]` : "";
3441
- const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4332
+ const mfDecl = derivedSrcs?.length ? ` const ${mfRef(ctx)} = [${derivedSrcs.join(", ")}]` : "";
4333
+ const buildDecl = buildSources?.length ? ` const ${buildRef(ctx)} = [${buildSources.join(", ")}]` : "";
3442
4334
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3443
4335
  const hoistedDecls = [
3444
4336
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
@@ -3471,6 +4363,150 @@ function compileRuleMap(ruleMap) {
3471
4363
  ].join("\n");
3472
4364
  return { keys: perEntry.map((e) => e.key), replacement };
3473
4365
  }
4366
+ function compileLinkable(ruleMapArg, ns) {
4367
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4368
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4369
+ const d = val._def;
4370
+ if (d.tag !== "lazy") return true;
4371
+ try {
4372
+ d.thunk();
4373
+ return true;
4374
+ } catch {
4375
+ return false;
4376
+ }
4377
+ });
4378
+ const ctx = {
4379
+ vars: 0,
4380
+ indent: 1,
4381
+ regexDecls: [],
4382
+ regexMap: /* @__PURE__ */ new Map(),
4383
+ expectedDecls: [],
4384
+ expectedMap: /* @__PURE__ */ new Map(),
4385
+ recordFail: true,
4386
+ mapFns: [],
4387
+ mapFnSrcs: [],
4388
+ buildFns: [],
4389
+ buildSrcs: [],
4390
+ runtimeParsers: [],
4391
+ needsCollator: false,
4392
+ namedParsers: /* @__PURE__ */ new Map(),
4393
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4394
+ triviaFnNames: /* @__PURE__ */ new Map(),
4395
+ namedFnDecls: [],
4396
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4397
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4398
+ ns
4399
+ };
4400
+ const seen = /* @__PURE__ */ new Map();
4401
+ const ruleNames = /* @__PURE__ */ new Map();
4402
+ const fnNameToKey = /* @__PURE__ */ new Map();
4403
+ for (const [key, rule] of ruleMap) {
4404
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4405
+ const n = seen.get(base) ?? 0;
4406
+ seen.set(base, n + 1);
4407
+ const fn = n === 0 ? base : `${base}$${n}`;
4408
+ ruleNames.set(rule, fn);
4409
+ fnNameToKey.set(fn, key);
4410
+ }
4411
+ ctx.ruleNames = ruleNames;
4412
+ {
4413
+ const scanned = /* @__PURE__ */ new Set();
4414
+ const scanExternal = (p) => {
4415
+ if (scanned.has(p)) return;
4416
+ scanned.add(p);
4417
+ const def = p._def;
4418
+ if (def.tag === "lazy") {
4419
+ let resolved;
4420
+ try {
4421
+ resolved = def.thunk();
4422
+ } catch {
4423
+ resolved = void 0;
4424
+ }
4425
+ if (resolved === void 0) {
4426
+ const name = p._ruleName;
4427
+ if (name && !ctx.namedParsers.has(p)) {
4428
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4429
+ ruleNames.set(p, fn);
4430
+ ctx.namedParsers.set(p, fn);
4431
+ }
4432
+ return;
4433
+ }
4434
+ scanExternal(resolved);
4435
+ return;
4436
+ }
4437
+ for (const child of childrenOf(def)) scanExternal(child);
4438
+ };
4439
+ for (const [, rule] of ruleMap) scanExternal(rule);
4440
+ }
4441
+ const perEntry = [];
4442
+ for (const [key, rule] of ruleMap) {
4443
+ const fn = ruleNames.get(rule);
4444
+ if (!ctx.namedParsers.has(rule)) {
4445
+ ctx.namedParsers.set(rule, fn);
4446
+ let resolved = rule;
4447
+ const def = rule._def;
4448
+ if (def.tag === "lazy") {
4449
+ try {
4450
+ resolved = def.thunk();
4451
+ } catch {
4452
+ return null;
4453
+ }
4454
+ }
4455
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4456
+ ctx.indent = 1;
4457
+ ctx.failLabel = "_pfail";
4458
+ ctx.recordFail = true;
4459
+ const body = emit(resolved, ctx, "_pos");
4460
+ ctx.indent = savedIndent;
4461
+ ctx.failLabel = savedFail;
4462
+ ctx.recordFail = savedRec;
4463
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4464
+ }
4465
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4466
+ }
4467
+ if (ctx.runtimeParsers.length !== 0) return null;
4468
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4469
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4470
+ const ruleFns = /* @__PURE__ */ new Map();
4471
+ const privateFns = [];
4472
+ for (const decl of ctx.namedFnDecls) {
4473
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4474
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4475
+ if (key !== void 0) ruleFns.set(key, decl);
4476
+ else privateFns.push(decl);
4477
+ }
4478
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4479
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4480
+ const prelude = [
4481
+ ...ctx.regexDecls,
4482
+ ...ctx.expectedDecls,
4483
+ mfDecl,
4484
+ buildDecl,
4485
+ ...privateFns
4486
+ ].filter(Boolean);
4487
+ const wrappers = /* @__PURE__ */ new Map();
4488
+ for (const { key, r } of perEntry) {
4489
+ wrappers.set(key, [
4490
+ `function(input, _pos, _ctx) {`,
4491
+ ` let pos = _pos`,
4492
+ ...r.stmts,
4493
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4494
+ `}`
4495
+ ].join("\n"));
4496
+ }
4497
+ return {
4498
+ ns,
4499
+ keys: perEntry.map((e) => e.key),
4500
+ prelude,
4501
+ ruleFns,
4502
+ wrappers,
4503
+ deps: ruleDependencies(ruleMap),
4504
+ needsEmptyTl: !!ctx.needsEmptyTl,
4505
+ needsCollator: ctx.needsCollator,
4506
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4507
+ buildFns: buildSrcs ? [] : ctx.buildFns
4508
+ };
4509
+ }
3474
4510
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
3475
4511
  const bodyLines = [
3476
4512
  ` let pos = _pos`,
@@ -3509,6 +4545,48 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3509
4545
  ].join("\n");
3510
4546
  }
3511
4547
 
4548
+ // src/compiler/linker.ts
4549
+ function fusedBody(pieces) {
4550
+ const winner = /* @__PURE__ */ new Map();
4551
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4552
+ for (const [k, p] of winner) {
4553
+ for (const d of p.deps.get(k) ?? []) {
4554
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4555
+ }
4556
+ }
4557
+ const contributing = new Set(winner.values());
4558
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4559
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4560
+ const lines = [
4561
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4562
+ "const _pfFail = {}",
4563
+ "let _pfEnd",
4564
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4565
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4566
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4567
+ ...[...contributing].flatMap((p) => p.prelude),
4568
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4569
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4570
+ ];
4571
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4572
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4573
+ const env = {};
4574
+ for (const p of contributing) {
4575
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4576
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4577
+ }
4578
+ return { body, env };
4579
+ }
4580
+ function emitFusedSource(pieces) {
4581
+ const { body, env } = fusedBody(pieces);
4582
+ if (Object.keys(env).length > 0) {
4583
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4584
+ }
4585
+ return `/* @__PURE__ */ (() => {
4586
+ ${body}
4587
+ })()`;
4588
+ }
4589
+
3512
4590
  // src/combinators/scanTo.ts
3513
4591
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
3514
4592
  const meta = {
@@ -3589,11 +4667,11 @@ function balanced(open, close, options = {}) {
3589
4667
  return combi;
3590
4668
  }
3591
4669
  function firstSetClassChars(c) {
3592
- const fs = c._meta.firstSet;
3593
- if (fs.kind === "empty") return [];
3594
- if (fs.kind !== "ranges") return null;
4670
+ const fs2 = c._meta.firstSet;
4671
+ if (fs2.kind === "empty") return [];
4672
+ if (fs2.kind !== "ranges") return null;
3595
4673
  const out = [];
3596
- for (const { lo, hi } of fs.ranges) {
4674
+ for (const { lo, hi } of fs2.ranges) {
3597
4675
  if (hi - lo > 8) return null;
3598
4676
  for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3599
4677
  }
@@ -3603,6 +4681,12 @@ function escapeClassChar(ch) {
3603
4681
  return ch.replace(/[\\\]^-]/g, "\\$&");
3604
4682
  }
3605
4683
 
4684
+ // src/cst/offset-model.ts
4685
+ var { isFinite } = Number;
4686
+
4687
+ // src/index.ts
4688
+ registerRegexAnalyzer(firstSetFromRegex);
4689
+
3606
4690
  // src/plugin/evaluator.ts
3607
4691
  function isWordFactory(v2) {
3608
4692
  return !!v2 && typeof v2 === "object" && v2.tag === "wordFactory";
@@ -3702,16 +4786,18 @@ function exprToCombi(node2, scope, code, mfs) {
3702
4786
  }
3703
4787
  if (callee.name === "node" && code !== void 0) {
3704
4788
  const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
3705
- if (!typeArg || !parserArg || !buildArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement" || buildArg.type === "SpreadElement") return null;
4789
+ if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
3706
4790
  const typeVal = anyValue(typeArg, scope, code);
3707
4791
  if (typeof typeVal !== "string") return null;
3708
4792
  const inner = anyValue(parserArg, scope, code, mfs);
3709
4793
  if (!isCombinator(inner)) return null;
3710
- const buildSrc = code.slice(buildArg.start, buildArg.end);
4794
+ const be = buildArg;
4795
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
4796
+ const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
3711
4797
  const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
3712
4798
  try {
3713
- const combi = node(typeVal, inner, () => null, collapse ? { collapse: true } : void 0);
3714
- if (combi._def.tag === "node") combi._def.buildSrc = buildSrc;
4799
+ const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
4800
+ if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
3715
4801
  return combi;
3716
4802
  } catch {
3717
4803
  return null;
@@ -3897,6 +4983,49 @@ function evaluateCombinatorArray(node2, scope, code) {
3897
4983
  if (!val.every(isCombinator)) return null;
3898
4984
  return val;
3899
4985
  }
4986
+ function propKey(prop) {
4987
+ if (prop.type !== "Property" || prop.computed || !prop.key) return null;
4988
+ if (prop.key.type === "Identifier") return prop.key.name ?? null;
4989
+ if (prop.key.type === "Literal") return String(prop.key.value);
4990
+ return null;
4991
+ }
4992
+ function collectRuleKeys(retObj) {
4993
+ const out = [];
4994
+ for (const prop of retObj.properties) {
4995
+ const key = propKey(prop);
4996
+ if (!key) return null;
4997
+ out.push(key);
4998
+ }
4999
+ return out;
5000
+ }
5001
+ function flattenRuleEntries(retObj, scope, code) {
5002
+ const out = [];
5003
+ for (const prop of retObj.properties) {
5004
+ const key = propKey(prop);
5005
+ if (!key) return null;
5006
+ out.push({ key, value: prop.value, scope, code });
5007
+ }
5008
+ return out;
5009
+ }
5010
+ function evalBodyStatements(statements, scope, code) {
5011
+ const bodyMfs = [];
5012
+ for (const stmt of statements) {
5013
+ for (const d of stmt.declarations) {
5014
+ const decl = d;
5015
+ if (!decl.init) return false;
5016
+ const id = decl.id;
5017
+ if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return false;
5018
+ const name = id.name;
5019
+ const before = bodyMfs.length;
5020
+ const val = anyValue(decl.init, scope, code, bodyMfs);
5021
+ if (val === null) return false;
5022
+ const thisDeclMfSrcs = bodyMfs.slice(before);
5023
+ if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
5024
+ else scope.set(name, val);
5025
+ }
5026
+ }
5027
+ return true;
5028
+ }
3900
5029
  function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3901
5030
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
3902
5031
  const factory = factoryNode;
@@ -3929,40 +5058,41 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3929
5058
  if (!returnExpr) return null;
3930
5059
  const retObj = returnExpr.type === "ParenthesizedExpression" ? returnExpr.expression : returnExpr;
3931
5060
  if (retObj.type !== "ObjectExpression") return null;
5061
+ const keys = collectRuleKeys(retObj);
5062
+ if (!keys) return null;
3932
5063
  const ruleRefs = /* @__PURE__ */ new Map();
3933
- for (const prop of retObj.properties) {
3934
- if (prop.type !== "Property") return null;
3935
- if (prop.computed) return null;
3936
- const p = prop;
3937
- const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : null;
3938
- if (!key) return null;
3939
- ruleRefs.set(key, ref());
5064
+ const tagRef = (r, name) => {
5065
+ r._ruleName = name;
5066
+ };
5067
+ for (const key of keys) {
5068
+ if (!ruleRefs.has(key)) {
5069
+ const r = ref();
5070
+ tagRef(r, key);
5071
+ ruleRefs.set(key, r);
5072
+ }
3940
5073
  }
3941
- const localScope = new Map(scope);
3942
- localScope.set(proxyName, Object.fromEntries(ruleRefs));
3943
- const bodyMfs = [];
3944
- for (const stmt of statements) {
3945
- for (const d of stmt.declarations) {
3946
- const decl = d;
3947
- if (!decl.init) return null;
3948
- const id = decl.id;
3949
- if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return null;
3950
- const name = id.name;
3951
- const before = bodyMfs.length;
3952
- const val = anyValue(decl.init, localScope, code, bodyMfs);
3953
- if (val === null) return null;
3954
- const thisDeclMfSrcs = bodyMfs.slice(before);
3955
- if (isCombinator(val)) {
3956
- localScope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
3957
- } else {
3958
- localScope.set(name, val);
5074
+ const externalRefs = /* @__PURE__ */ new Map();
5075
+ const gProxy = new Proxy(Object.fromEntries(ruleRefs), {
5076
+ get(target, prop) {
5077
+ if (typeof prop !== "string" || prop in target) return target[prop];
5078
+ let ext = externalRefs.get(prop);
5079
+ if (!ext) {
5080
+ ext = ref();
5081
+ tagRef(ext, prop);
5082
+ externalRefs.set(prop, ext);
3959
5083
  }
5084
+ return ext;
3960
5085
  }
3961
- }
3962
- for (const prop of retObj.properties) {
3963
- const p = prop;
3964
- const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
3965
- const val = anyValue(p.value, localScope, code, mapFnSources);
5086
+ });
5087
+ const localScope = new Map(scope);
5088
+ localScope.set(proxyName, gProxy);
5089
+ if (!evalBodyStatements(statements, localScope, code)) return null;
5090
+ const entries = flattenRuleEntries(retObj, localScope, code);
5091
+ if (!entries) return null;
5092
+ const finalByKey = /* @__PURE__ */ new Map();
5093
+ for (const e of entries) finalByKey.set(e.key, e);
5094
+ for (const [key, e] of finalByKey) {
5095
+ const val = anyValue(e.value, e.scope, e.code, mapFnSources);
3966
5096
  if (!isCombinator(val)) return null;
3967
5097
  ruleRefs.get(key).define(val);
3968
5098
  }
@@ -4019,7 +5149,36 @@ function referencesAny(node2, names, scope) {
4019
5149
  }
4020
5150
 
4021
5151
  // src/plugin/index.ts
5152
+ var import_node_crypto = require("node:crypto");
4022
5153
  var PARSEMAN_MODULE = "parseman";
5154
+ var _compiledResolver = null;
5155
+ function getCompiledResolver() {
5156
+ return _compiledResolver ??= new import_oxc_resolver.ResolverFactory({
5157
+ extensions: [".js", ".mjs", ".cjs"],
5158
+ conditionNames: ["import", "require", "default"],
5159
+ mainFields: ["module", "main"]
5160
+ });
5161
+ }
5162
+ var _moduleParseCache = /* @__PURE__ */ new Map();
5163
+ function parseModuleCached(filePath) {
5164
+ let mtimeMs;
5165
+ try {
5166
+ mtimeMs = fs.statSync(filePath).mtimeMs;
5167
+ } catch {
5168
+ return null;
5169
+ }
5170
+ const hit = _moduleParseCache.get(filePath);
5171
+ if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
5172
+ let parsed = null;
5173
+ try {
5174
+ const src = fs.readFileSync(filePath, "utf8");
5175
+ const r = (0, import_oxc_parser.parseSync)(filePath, src);
5176
+ if (r.errors.length === 0) parsed = { body: r.program.body, src };
5177
+ } catch {
5178
+ }
5179
+ _moduleParseCache.set(filePath, { mtimeMs, parsed });
5180
+ return parsed;
5181
+ }
4023
5182
  var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
4024
5183
  name: "parseman",
4025
5184
  // Run BEFORE the bundler's TS/JS transform — otherwise esbuild (Vite) strips
@@ -4044,6 +5203,77 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
4044
5203
  return { code: result.code, map: result.map };
4045
5204
  }
4046
5205
  }));
5206
+ function exportLocalName(body, exported) {
5207
+ for (const st of body) {
5208
+ if (st.type !== "ExportNamedDeclaration") continue;
5209
+ const decl = st.declaration;
5210
+ if (decl?.type === "VariableDeclaration") {
5211
+ for (const d of decl.declarations ?? []) {
5212
+ const idn = d.id;
5213
+ if (idn?.type === "Identifier" && idn.name === exported) return exported;
5214
+ }
5215
+ }
5216
+ for (const sp of st.specifiers ?? []) {
5217
+ const exp = sp.exported?.name;
5218
+ const loc = sp.local?.name;
5219
+ if (exp === exported && loc) return loc;
5220
+ }
5221
+ }
5222
+ return null;
5223
+ }
5224
+ function topLevelInit(body, name) {
5225
+ for (const st of body) {
5226
+ const vd = st.type === "VariableDeclaration" ? st : st.type === "ExportNamedDeclaration" && st.declaration?.type === "VariableDeclaration" ? st.declaration : null;
5227
+ if (!vd) continue;
5228
+ for (const d of vd.declarations ?? []) {
5229
+ const idn = d.id;
5230
+ if (idn?.type === "Identifier" && idn.name === name && d.init) return d.init;
5231
+ }
5232
+ }
5233
+ return null;
5234
+ }
5235
+ function isComposedPiecesSymbol(n) {
5236
+ if (!n || n.type !== "CallExpression") return false;
5237
+ const callee = n.callee;
5238
+ const obj = callee?.object?.name;
5239
+ const prop = callee?.property?.name;
5240
+ const arg0 = n.arguments?.[0];
5241
+ return obj === "Symbol" && prop === "for" && arg0?.value === "parseman.composedPieces";
5242
+ }
5243
+ function findCarriedPiecesLiteral(root) {
5244
+ let found = null;
5245
+ const visit = (n) => {
5246
+ if (found || !n || typeof n !== "object") return;
5247
+ if (Array.isArray(n)) {
5248
+ for (const c of n) visit(c);
5249
+ return;
5250
+ }
5251
+ const node2 = n;
5252
+ if (node2.type === "CallExpression") {
5253
+ const callee = node2.callee;
5254
+ const obj = callee?.object?.name;
5255
+ const prop = callee?.property?.name;
5256
+ const args = node2.arguments;
5257
+ if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5258
+ const descriptor = args[2];
5259
+ for (const p of descriptor?.properties ?? []) {
5260
+ const key = p.key;
5261
+ if ((key?.name === "value" || key?.value === "value") && p.value) {
5262
+ found = { start: p.value.start, end: p.value.end };
5263
+ return;
5264
+ }
5265
+ }
5266
+ }
5267
+ }
5268
+ for (const k in node2) {
5269
+ if (k === "type" || k === "start" || k === "end") continue;
5270
+ const v2 = node2[k];
5271
+ if (v2 && typeof v2 === "object") visit(v2);
5272
+ }
5273
+ };
5274
+ visit(root);
5275
+ return found;
5276
+ }
4047
5277
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
4048
5278
  let result;
4049
5279
  try {
@@ -4055,15 +5285,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4055
5285
  const body = result.program.body;
4056
5286
  const macroImports = [];
4057
5287
  const allNames = /* @__PURE__ */ new Set();
5288
+ const importBindings = /* @__PURE__ */ new Map();
4058
5289
  for (const stmt of body) {
4059
5290
  if (stmt.type !== "ImportDeclaration") continue;
4060
5291
  const s = stmt;
4061
- if (!moduleAliases.has(s.source.value)) continue;
4062
- const isMacro = s.attributes.some((a) => {
5292
+ const isMacro = moduleAliases.has(s.source.value) && s.attributes.some((a) => {
4063
5293
  const key = a.key.type === "Identifier" ? a.key.name : String(a.key.value);
4064
5294
  return key === "type" && a.value.value === "macro";
4065
5295
  });
4066
- if (!isMacro) continue;
5296
+ if (!isMacro) {
5297
+ for (const spec of s.specifiers) {
5298
+ if (spec.type === "ImportSpecifier") {
5299
+ const imported = spec.imported.name ?? spec.local.name;
5300
+ importBindings.set(spec.local.name, { source: s.source.value, imported });
5301
+ }
5302
+ }
5303
+ continue;
5304
+ }
4067
5305
  const names = /* @__PURE__ */ new Set();
4068
5306
  for (const spec of s.specifiers) {
4069
5307
  if (spec.type === "ImportSpecifier") names.add(spec.local.name);
@@ -4106,6 +5344,89 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4106
5344
  return { replacement: compiled.replacement, ruleMap };
4107
5345
  };
4108
5346
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
5347
+ const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5348
+ const localRuleMaps = /* @__PURE__ */ new Map();
5349
+ const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5350
+ const serializePieces = (p) => {
5351
+ const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5352
+ 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: [] }`;
5353
+ };
5354
+ const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5355
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
5356
+ const localComposedPieces = /* @__PURE__ */ new Map();
5357
+ const importedPiecesCache = /* @__PURE__ */ new Map();
5358
+ const importedPieces = (localName) => {
5359
+ if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5360
+ let result2 = null;
5361
+ const binding = importBindings.get(localName);
5362
+ if (binding) {
5363
+ let file = null;
5364
+ try {
5365
+ file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5366
+ } catch {
5367
+ }
5368
+ const mod = file ? parseModuleCached(file) : null;
5369
+ if (mod) {
5370
+ const localFor = exportLocalName(mod.body, binding.imported);
5371
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5372
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5373
+ if (literalRange) {
5374
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5375
+ try {
5376
+ result2 = new Function(`return (${literal2})`)();
5377
+ } catch {
5378
+ result2 = null;
5379
+ }
5380
+ }
5381
+ }
5382
+ }
5383
+ importedPiecesCache.set(localName, result2);
5384
+ return result2;
5385
+ };
5386
+ const argPieces = (arg, label2) => {
5387
+ if (isRulesCall(arg)) {
5388
+ const factory = arg.arguments[0];
5389
+ const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5390
+ const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5391
+ return p ? [p] : null;
5392
+ }
5393
+ if (arg.type === "Identifier") {
5394
+ const name = arg.name;
5395
+ const rm = localRuleMaps.get(name);
5396
+ if (rm) {
5397
+ const p = compileLinkable([...rm], nsFor(label2));
5398
+ return p ? [p] : null;
5399
+ }
5400
+ const composed = localComposedPieces.get(name);
5401
+ if (composed) return composed;
5402
+ return importedPieces(name);
5403
+ }
5404
+ return null;
5405
+ };
5406
+ const compileComposeCall = (init) => {
5407
+ const args = init.arguments;
5408
+ const arr = args[0];
5409
+ if (!arr || arr.type !== "ArrayExpression") {
5410
+ warn(init.start, "compose(): expected a static array of grammars/artifacts");
5411
+ return null;
5412
+ }
5413
+ const elements = arr.elements;
5414
+ const pieces = [];
5415
+ for (let i = 0; i < elements.length; i++) {
5416
+ const list = argPieces(elements[i], `compose${init.start}_${i}`);
5417
+ if (!list) {
5418
+ warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5419
+ return null;
5420
+ }
5421
+ pieces.push(...list);
5422
+ }
5423
+ try {
5424
+ return { replacement: emitFusedSource(pieces), pieces };
5425
+ } catch (e) {
5426
+ warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5427
+ return null;
5428
+ }
5429
+ };
4109
5430
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
4110
5431
  const refNames = /* @__PURE__ */ new Set();
4111
5432
  for (const stmt of body) {
@@ -4184,11 +5505,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
4184
5505
  if (isRulesCall(init)) {
4185
5506
  const compiledRules = compileRulesFactory(init, varName);
4186
5507
  if (!compiledRules) continue;
4187
- replacements.push({
4188
- start: init.start,
4189
- end: init.end,
4190
- replacement: compiledRules.replacement
4191
- });
5508
+ localRuleMaps.set(varName, compiledRules.ruleMap);
5509
+ let replacement = compiledRules.replacement;
5510
+ if (exportPrefix) {
5511
+ const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
5512
+ if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5513
+ replacement = withCarriedPieces(replacement, [pieces]);
5514
+ }
5515
+ }
5516
+ replacements.push({ start: init.start, end: init.end, replacement });
5517
+ continue;
5518
+ }
5519
+ if (isComposeCall(init)) {
5520
+ const fused = compileComposeCall(init);
5521
+ if (!fused) continue;
5522
+ localComposedPieces.set(varName, fused.pieces);
5523
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
5524
+ replacements.push({ start: init.start, end: init.end, replacement });
4192
5525
  continue;
4193
5526
  }
4194
5527
  const mapFnSources = [];