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
package/dist/index.cjs CHANGED
@@ -30,21 +30,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ OffsetIndex: () => OffsetIndex,
34
+ absoluteSpanAt: () => absoluteSpanAt,
35
+ absolutize: () => absolutize,
33
36
  annotateSpan: () => annotateSpan,
37
+ applyEdit: () => applyEdit,
34
38
  balanced: () => balanced,
39
+ blankLinesIn: () => blankLinesIn,
35
40
  buildLineIndex: () => buildLineIndex,
41
+ buildOffsetIndex: () => buildOffsetIndex,
36
42
  buildTriviaIndex: () => buildTriviaIndex,
37
43
  choice: () => choice,
44
+ collectLeafSlots: () => collectLeafSlots,
45
+ commentsIn: () => commentsIn,
38
46
  compile: () => compile,
39
47
  completionsAt: () => completionsAt,
48
+ compose: () => compose,
40
49
  createVisitor: () => createVisitor,
50
+ cstBuildHost: () => cstBuildHost,
41
51
  expect: () => expect,
52
+ gapIsSignificant: () => gapIsSignificant,
53
+ gapText: () => gapText,
42
54
  guard: () => guard,
55
+ indentMixed: () => indentMixed,
56
+ indentWidth: () => indentWidth,
43
57
  isParseError: () => isParseError,
44
58
  keywords: () => keywords,
45
59
  label: () => label,
60
+ lineBreaksIn: () => lineBreaksIn,
61
+ lineStartWithin: () => lineStartWithin,
46
62
  literal: () => literal,
47
- makeFunctionalDoc: () => makeFunctionalDoc,
48
63
  makeWord: () => makeWord,
49
64
  many: () => many,
50
65
  manyRecover: () => manyRecover,
@@ -55,15 +70,20 @@ __export(index_exports, {
55
70
  oneOrMore: () => oneOrMore,
56
71
  optional: () => optional,
57
72
  parse: () => parse,
73
+ parseDoc: () => parseDoc,
58
74
  parser: () => parser,
75
+ pick: () => pick,
59
76
  recover: () => recover,
60
77
  ref: () => ref,
61
78
  regex: () => regex,
79
+ relativize: () => relativize,
62
80
  rules: () => rules,
81
+ run: () => run,
63
82
  scanTo: () => scanTo,
64
83
  sepBy: () => sepBy,
65
84
  sepByRecover: () => sepByRecover,
66
85
  sequence: () => sequence,
86
+ shiftAbsolute: () => shiftAbsolute,
67
87
  skip: () => skip,
68
88
  staticExpected: () => staticExpected,
69
89
  transform: () => transform,
@@ -328,6 +348,36 @@ function literal(value, opts = {}) {
328
348
  }
329
349
 
330
350
  // src/combinators/regex.ts
351
+ var firstSetAnalyzer = null;
352
+ function registerRegexAnalyzer(analyzer) {
353
+ firstSetAnalyzer = analyzer;
354
+ }
355
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
356
+ function regex(pattern, flags = "") {
357
+ const source = typeof pattern === "string" ? pattern : pattern.source;
358
+ const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
359
+ const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
360
+ const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
361
+ const meta = { firstSet, canMatchNewline, isTrivia: false };
362
+ return {
363
+ _tag: "regex",
364
+ _meta: meta,
365
+ _def: { tag: "regex", source, flags: resolvedFlags },
366
+ parse(input, pos, ctx) {
367
+ anchored.lastIndex = pos;
368
+ const m = anchored.exec(input);
369
+ if (m === null) {
370
+ return failAt(ctx, [`/${source}/`], pos);
371
+ }
372
+ const span = { start: pos, end: pos + m[0].length };
373
+ const leaf = { _tag: "leaf", value: m[0], span };
374
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
375
+ return { ok: true, value: m[0], span };
376
+ }
377
+ };
378
+ }
379
+
380
+ // src/combinators/regex-analyze.ts
331
381
  var import_regexp_tree = __toESM(require("regexp-tree"), 1);
332
382
  function firstSetFromRegex(pattern) {
333
383
  try {
@@ -432,40 +482,6 @@ function extractFirstSet(node2) {
432
482
  return { firstSet: any(), canMatchNewline: true };
433
483
  }
434
484
  }
435
- function optimizeRegex(source, flags) {
436
- try {
437
- const result = import_regexp_tree.default.optimize(`/${source}/${flags}`);
438
- const str = result.toString();
439
- const lastSlash = str.lastIndexOf("/");
440
- return str.slice(1, lastSlash);
441
- } catch {
442
- return source;
443
- }
444
- }
445
- function regex(pattern, flags = "") {
446
- const source = typeof pattern === "string" ? pattern : pattern.source;
447
- const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
448
- const optimizedSource = optimizeRegex(source, resolvedFlags);
449
- const anchored = new RegExp(optimizedSource, "y" + resolvedFlags.replace(/[gy]/g, ""));
450
- const { firstSet, canMatchNewline } = firstSetFromRegex(source);
451
- const meta = { firstSet, canMatchNewline, isTrivia: false };
452
- return {
453
- _tag: "regex",
454
- _meta: meta,
455
- _def: { tag: "regex", source, flags: resolvedFlags, optimizedSource },
456
- parse(input, pos, ctx) {
457
- anchored.lastIndex = pos;
458
- const m = anchored.exec(input);
459
- if (m === null) {
460
- return failAt(ctx, [`/${source}/`], pos);
461
- }
462
- const span = { start: pos, end: pos + m[0].length };
463
- const leaf = { _tag: "leaf", value: m[0], span };
464
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
465
- return { ok: true, value: m[0], span };
466
- }
467
- };
468
- }
469
485
 
470
486
  // src/combinators/keywords.ts
471
487
  function word(str, boundary = "_0-9A-Za-z") {
@@ -1163,7 +1179,9 @@ function rules(factory) {
1163
1179
  if (typeof key !== "string") return void 0;
1164
1180
  const record = target;
1165
1181
  if (!(key in record)) {
1166
- record[key] = ref();
1182
+ const r = ref();
1183
+ r._ruleName = key;
1184
+ record[key] = r;
1167
1185
  }
1168
1186
  return record[key];
1169
1187
  }
@@ -1228,12 +1246,14 @@ function confirmedBuildArity(src) {
1228
1246
  return parts.length;
1229
1247
  }
1230
1248
  function buildReadsTrivia(def) {
1249
+ if (!def.build) return true;
1231
1250
  const src = def.buildSrc ?? def.build.toString();
1232
1251
  const arity = confirmedBuildArity(src);
1233
1252
  if (arity === null) return true;
1234
1253
  return arity >= 4;
1235
1254
  }
1236
1255
  function buildReadsState(def) {
1256
+ if (!def.build) return true;
1237
1257
  const src = def.buildSrc ?? def.build.toString();
1238
1258
  const arity = confirmedBuildArity(src);
1239
1259
  if (arity === null) return true;
@@ -1252,8 +1272,8 @@ function node(type, combinator, build, opts) {
1252
1272
  };
1253
1273
  const collapse = opts?.collapse === true;
1254
1274
  const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1255
- const capturesTrivia = buildReadsTrivia(def);
1256
- const clonesState = buildReadsState(def);
1275
+ const capturesTrivia = build ? buildReadsTrivia(def) : true;
1276
+ const clonesState = build ? buildReadsState(def) : true;
1257
1277
  return {
1258
1278
  _tag: "node",
1259
1279
  _meta: meta,
@@ -1264,13 +1284,8 @@ function node(type, combinator, build, opts) {
1264
1284
  const r = combinator.parse(input, pos, ctx);
1265
1285
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1266
1286
  if (!r.ok) return r;
1267
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1268
- children,
1269
- rawChildren,
1270
- r.span,
1271
- triviaLog,
1272
- clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1273
- );
1287
+ const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1288
+ const built = collapse && children.length === 1 ? collapseChild(children[0]) : build ? build(children, rawChildren, r.span, triviaLog, st) : ctx.build ? ctx.build(type, children, rawChildren, r.span, triviaLog, st) : { _tag: "node", type, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children };
1274
1289
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1275
1290
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1276
1291
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1445,8 +1460,14 @@ function staticExpected(c) {
1445
1460
  case "transform":
1446
1461
  case "not":
1447
1462
  return staticExpected(def.parser);
1448
- case "lazy":
1449
- return staticExpected(def.thunk());
1463
+ case "lazy": {
1464
+ const name = c._ruleName;
1465
+ try {
1466
+ return staticExpected(def.thunk());
1467
+ } catch {
1468
+ return name ? [name] : [];
1469
+ }
1470
+ }
1450
1471
  default:
1451
1472
  return [];
1452
1473
  }
@@ -1476,8 +1497,29 @@ function expect(combinator, label2) {
1476
1497
  var BACKSLASH = 92;
1477
1498
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1478
1499
  var META = new Set("()[]{}*+?|^$.".split(""));
1500
+ var SPACE_RANGES = [
1501
+ [9, 13],
1502
+ [32, 32],
1503
+ [160, 160],
1504
+ [5760, 5760],
1505
+ [8192, 8202],
1506
+ [8232, 8232],
1507
+ [8233, 8233],
1508
+ [8239, 8239],
1509
+ [8287, 8287],
1510
+ [12288, 12288],
1511
+ [65279, 65279]
1512
+ ];
1479
1513
  function shorthandRanges(ch) {
1480
- return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1514
+ if (ch === "d") return [[48, 57]];
1515
+ if (ch === "s") return SPACE_RANGES;
1516
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1517
+ }
1518
+ function readUnicodeEscape(body, i) {
1519
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1520
+ const hex = body.slice(i + 2, i + 6);
1521
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1522
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1481
1523
  }
1482
1524
  function parseClassRanges(body) {
1483
1525
  const ranges = [];
@@ -1486,11 +1528,16 @@ function parseClassRanges(body) {
1486
1528
  const ch = body[i];
1487
1529
  if (ch === void 0) return null;
1488
1530
  if (ch === "\\") {
1531
+ const uni = readUnicodeEscape(body, i);
1532
+ if (uni) {
1533
+ i = uni.next;
1534
+ return { cp: uni.cp };
1535
+ }
1489
1536
  const e = body[i + 1];
1490
1537
  if (e === void 0) return null;
1491
1538
  i += 2;
1492
1539
  if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1493
- if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1540
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1494
1541
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1495
1542
  return { cp: e.codePointAt(0) };
1496
1543
  }
@@ -1518,10 +1565,16 @@ function parseClassRanges(body) {
1518
1565
  function classToRanges(cls) {
1519
1566
  if (cls === "\\d") return shorthandRanges("d");
1520
1567
  if (cls === "\\w") return shorthandRanges("w");
1568
+ if (cls === "\\s") return shorthandRanges("s");
1521
1569
  const body = cls.slice(1, -1);
1522
1570
  if (body.startsWith("^")) return null;
1523
1571
  return parseClassRanges(body);
1524
1572
  }
1573
+ var foldEq = (cVar, cp) => {
1574
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1575
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1576
+ return `${cVar} === ${cp}`;
1577
+ };
1525
1578
  function literalCodePoints(frag) {
1526
1579
  const out = [];
1527
1580
  let i = 0;
@@ -1568,22 +1621,612 @@ function parseStringShape(source) {
1568
1621
  if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1569
1622
  return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1570
1623
  }
1624
+ function parseSeqParts(source) {
1625
+ const parts = [];
1626
+ let lit = [];
1627
+ const flush = () => {
1628
+ if (lit.length) {
1629
+ parts.push({ part: "lit", cps: lit, optional: false });
1630
+ lit = [];
1631
+ }
1632
+ };
1633
+ const runFrom = (ranges, negated, q) => {
1634
+ if (q === "+") {
1635
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1636
+ return 1;
1637
+ }
1638
+ if (q === "*") {
1639
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1640
+ return 1;
1641
+ }
1642
+ if (q === "?") {
1643
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1644
+ return 1;
1645
+ }
1646
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1647
+ return 0;
1648
+ };
1649
+ let i = 0;
1650
+ while (i < source.length) {
1651
+ const ch = source[i];
1652
+ if (ch === "[") {
1653
+ let k = i + 1;
1654
+ const neg = source[k] === "^";
1655
+ if (neg) k++;
1656
+ let body = "";
1657
+ while (k < source.length && source[k] !== "]") {
1658
+ if (source[k] === "\\") {
1659
+ body += source[k] + (source[k + 1] ?? "");
1660
+ k += 2;
1661
+ } else {
1662
+ body += source[k];
1663
+ k++;
1664
+ }
1665
+ }
1666
+ if (source[k] !== "]") return null;
1667
+ const ranges = parseClassRanges(body);
1668
+ if (!ranges) return null;
1669
+ flush();
1670
+ i = k + 1;
1671
+ i += runFrom(ranges, neg, source[i]);
1672
+ continue;
1673
+ }
1674
+ if (ch === "\\") {
1675
+ const e = source[i + 1];
1676
+ if (e === "d" || e === "w" || e === "s") {
1677
+ flush();
1678
+ i += 2;
1679
+ i += runFrom(shorthandRanges(e), false, source[i]);
1680
+ continue;
1681
+ }
1682
+ const uni = readUnicodeEscape(source, i);
1683
+ let cp2;
1684
+ if (uni) {
1685
+ cp2 = uni.cp;
1686
+ i = uni.next;
1687
+ } else if (e === void 0) return null;
1688
+ else if (e in CLASS_ESCAPES) {
1689
+ cp2 = CLASS_ESCAPES[e];
1690
+ i += 2;
1691
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1692
+ else {
1693
+ cp2 = e.codePointAt(0);
1694
+ i += 2;
1695
+ }
1696
+ if (source[i] === "?") {
1697
+ flush();
1698
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1699
+ i++;
1700
+ } else lit.push(cp2);
1701
+ continue;
1702
+ }
1703
+ if (ch === "(") {
1704
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1705
+ let k = i + 3;
1706
+ let depth = 1;
1707
+ while (k < source.length && depth > 0) {
1708
+ const c2 = source[k];
1709
+ if (c2 === "\\") {
1710
+ k += 2;
1711
+ continue;
1712
+ }
1713
+ if (c2 === "[") {
1714
+ let j = k + 1;
1715
+ if (source[j] === "^") j++;
1716
+ while (j < source.length && source[j] !== "]") {
1717
+ if (source[j] === "\\") j += 2;
1718
+ else j++;
1719
+ }
1720
+ if (source[j] !== "]") return null;
1721
+ k = j + 1;
1722
+ continue;
1723
+ }
1724
+ if (c2 === "(") depth++;
1725
+ else if (c2 === ")") depth--;
1726
+ k++;
1727
+ }
1728
+ if (depth !== 0) return null;
1729
+ const body = source.slice(i + 3, k - 1);
1730
+ const inner = parseScanShape(body);
1731
+ if (!inner || !groupInnerSafe(inner)) return null;
1732
+ const q = source[k];
1733
+ if (q === "{") return null;
1734
+ flush();
1735
+ if (q === "+") {
1736
+ if (shapeCanBeEmpty(inner)) return null;
1737
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1738
+ i = k + 1;
1739
+ } else if (q === "*") {
1740
+ if (shapeCanBeEmpty(inner)) return null;
1741
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1742
+ i = k + 1;
1743
+ } else if (q === "?") {
1744
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1745
+ i = k + 1;
1746
+ } else {
1747
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1748
+ i = k;
1749
+ }
1750
+ continue;
1751
+ }
1752
+ if (META.has(ch)) return null;
1753
+ const cp = ch.codePointAt(0);
1754
+ i += ch.length;
1755
+ if (source[i] === "?") {
1756
+ flush();
1757
+ parts.push({ part: "lit", cps: [cp], optional: true });
1758
+ i++;
1759
+ } else lit.push(cp);
1760
+ }
1761
+ flush();
1762
+ return parts.length ? parts : null;
1763
+ }
1764
+ function seqIsUnambiguous(parts) {
1765
+ let hasMandatory = false;
1766
+ for (let k = 0; k < parts.length; k++) {
1767
+ const p = parts[k];
1768
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1769
+ if (mandatory) hasMandatory = true;
1770
+ if (p.part === "group") {
1771
+ const exposure = groupPartExposure(p);
1772
+ if (exposure === "unsupported") {
1773
+ const trailingOnce = k === parts.length - 1 && p.min === 1 && !p.unbounded;
1774
+ if (!trailingOnce) return false;
1775
+ } else if (exposure !== null) {
1776
+ if (!firstSetsDisjoint(exposure, seqFirstAccept(parts.slice(k + 1)))) return false;
1777
+ }
1778
+ }
1779
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1780
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1781
+ if (skippableSingle || isUnboundedRepeat) {
1782
+ const rest = seqFirstAccept(parts.slice(k + 1));
1783
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1784
+ }
1785
+ }
1786
+ return hasMandatory;
1787
+ }
1788
+ function rangesOverlap(a, b) {
1789
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1790
+ }
1791
+ function rangesSubset(sub, sup) {
1792
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1793
+ }
1794
+ function parseClassOperand(body) {
1795
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1796
+ return { ranges: shorthandRanges(body[1]), negated: false };
1797
+ }
1798
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1799
+ let inner = body.slice(1, -1);
1800
+ const negated = inner.startsWith("^");
1801
+ if (negated) inner = inner.slice(1);
1802
+ const ranges = parseClassRanges(inner);
1803
+ return ranges ? { ranges, negated } : null;
1804
+ }
1805
+ const cps = literalCodePoints(body);
1806
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1807
+ return null;
1808
+ }
1809
+ function classSubset(t, u) {
1810
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1811
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1812
+ if (t.negated && !u.negated) return false;
1813
+ return rangesSubset(u.ranges, t.ranges);
1814
+ }
1815
+ function classDisjoint(t, u) {
1816
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1817
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1818
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1819
+ return false;
1820
+ }
1821
+ function firstSetsDisjoint(a, b) {
1822
+ if (a === "any" || b === "any") return false;
1823
+ return classDisjoint(a, b);
1824
+ }
1825
+ function fixedClassSeq(shape) {
1826
+ if (shape.kind !== "seq") return null;
1827
+ const out = [];
1828
+ for (const p of shape.parts) {
1829
+ if (p.part === "lit") {
1830
+ if (p.optional) return null;
1831
+ for (const cp of p.cps) out.push({ ranges: [[cp, cp]], negated: false });
1832
+ } else if (p.part === "run" && !p.unbounded && p.min === 1) {
1833
+ out.push({ ranges: p.ranges, negated: p.negated });
1834
+ } else {
1835
+ return null;
1836
+ }
1837
+ }
1838
+ return out.length ? out : null;
1839
+ }
1840
+ function altFixedMutuallyExclusive(shape) {
1841
+ if (shape.kind !== "alt" || shape.disjoint) return false;
1842
+ const lists = [];
1843
+ for (const arm of shape.arms) {
1844
+ const f = fixedClassSeq(arm);
1845
+ if (!f) return false;
1846
+ lists.push(f);
1847
+ }
1848
+ for (let i = 0; i < lists.length; i++) {
1849
+ for (let j = 0; j < lists.length; j++) {
1850
+ if (i === j) continue;
1851
+ const a = lists[i];
1852
+ const b = lists[j];
1853
+ if (a.length > b.length) continue;
1854
+ let disjointAt = false;
1855
+ for (let p = 0; p < a.length; p++) {
1856
+ if (classDisjoint(a[p], b[p])) {
1857
+ disjointAt = true;
1858
+ break;
1859
+ }
1860
+ }
1861
+ if (!disjointAt) return false;
1862
+ }
1863
+ }
1864
+ return true;
1865
+ }
1866
+ function groupPartExposure(part) {
1867
+ const innerWiggle = trailingBacktrackClass(part.inner);
1868
+ if (innerWiggle === "unsupported") return "unsupported";
1869
+ const requiredOnce = !part.unbounded && part.min === 1;
1870
+ if (requiredOnce) return innerWiggle;
1871
+ const dropExposed = shapeFirstAccept(part.inner);
1872
+ if (dropExposed === "any") return "unsupported";
1873
+ if (innerWiggle === null) return dropExposed;
1874
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1875
+ return unioned === "any" ? "unsupported" : unioned;
1876
+ }
1877
+ function trailingBacktrackClass(shape) {
1878
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1879
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1880
+ if (shape.kind === "litFold") return null;
1881
+ if (shape.kind === "seq") {
1882
+ const last = shape.parts[shape.parts.length - 1];
1883
+ if (last.part === "lit") return null;
1884
+ if (last.part === "run") {
1885
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1886
+ return null;
1887
+ }
1888
+ return groupPartExposure(last);
1889
+ }
1890
+ if (shape.kind === "alt") {
1891
+ if (!shape.disjoint) return altFixedMutuallyExclusive(shape) ? null : "unsupported";
1892
+ const classes = shape.arms.map(trailingBacktrackClass);
1893
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1894
+ const concrete = classes.filter((c) => c !== null);
1895
+ if (concrete.length === 0) return null;
1896
+ const unioned = unionCharSets(concrete);
1897
+ return unioned === "any" ? "unsupported" : unioned;
1898
+ }
1899
+ return "unsupported";
1900
+ }
1901
+ function lookaheadUnambiguous(inner, operand, negative) {
1902
+ const tail = trailingBacktrackClass(inner);
1903
+ if (tail === "unsupported") return false;
1904
+ if (tail === null) return true;
1905
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1906
+ }
1907
+ function stripTrailingLookahead(source) {
1908
+ if (!source.endsWith(")")) return null;
1909
+ for (const negative of [true, false]) {
1910
+ const lead = negative ? "(?!" : "(?=";
1911
+ const idx = source.lastIndexOf(lead);
1912
+ if (idx === -1) continue;
1913
+ const body = source.slice(idx + lead.length, -1);
1914
+ const operand = body.length ? parseClassOperand(body) : null;
1915
+ if (!operand) continue;
1916
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1917
+ }
1918
+ return null;
1919
+ }
1920
+ function unwrapOuterGroup(source) {
1921
+ while (source.startsWith("(?:")) {
1922
+ let depth = 1;
1923
+ let i = 3;
1924
+ let ok = true;
1925
+ while (i < source.length && depth > 0) {
1926
+ const ch = source[i];
1927
+ if (ch === "\\") {
1928
+ i += 2;
1929
+ continue;
1930
+ }
1931
+ if (ch === "[") {
1932
+ let j = i + 1;
1933
+ if (source[j] === "^") j++;
1934
+ while (j < source.length && source[j] !== "]") {
1935
+ if (source[j] === "\\") j += 2;
1936
+ else j++;
1937
+ }
1938
+ if (source[j] !== "]") {
1939
+ ok = false;
1940
+ break;
1941
+ }
1942
+ i = j + 1;
1943
+ continue;
1944
+ }
1945
+ if (ch === "(") depth++;
1946
+ else if (ch === ")") depth--;
1947
+ i++;
1948
+ }
1949
+ if (!ok || depth !== 0 || i !== source.length) break;
1950
+ source = source.slice(3, -1);
1951
+ }
1952
+ return source;
1953
+ }
1954
+ function splitTopLevelAlternation(source) {
1955
+ const body = unwrapOuterGroup(source);
1956
+ const parts = [];
1957
+ let depth = 0;
1958
+ let last = 0;
1959
+ let i = 0;
1960
+ while (i < body.length) {
1961
+ const ch = body[i];
1962
+ if (ch === "\\") {
1963
+ i += 2;
1964
+ continue;
1965
+ }
1966
+ if (ch === "[") {
1967
+ let j = i + 1;
1968
+ if (body[j] === "^") j++;
1969
+ while (j < body.length && body[j] !== "]") {
1970
+ if (body[j] === "\\") j += 2;
1971
+ else j++;
1972
+ }
1973
+ if (body[j] !== "]") return null;
1974
+ i = j + 1;
1975
+ continue;
1976
+ }
1977
+ if (ch === "(") {
1978
+ depth++;
1979
+ i++;
1980
+ continue;
1981
+ }
1982
+ if (ch === ")") {
1983
+ depth--;
1984
+ if (depth < 0) return null;
1985
+ i++;
1986
+ continue;
1987
+ }
1988
+ if (ch === "|" && depth === 0) {
1989
+ parts.push(body.slice(last, i));
1990
+ last = i + 1;
1991
+ i++;
1992
+ continue;
1993
+ }
1994
+ i++;
1995
+ }
1996
+ if (depth !== 0) return null;
1997
+ parts.push(body.slice(last));
1998
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1999
+ return parts;
2000
+ }
2001
+ function shapeCanBeEmpty(shape) {
2002
+ if (shape.kind === "chars") return !shape.minOne;
2003
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
2004
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
2005
+ return false;
2006
+ }
2007
+ function unionCharSets(sets) {
2008
+ if (sets.some((s) => s === "any")) return "any";
2009
+ const concrete = sets;
2010
+ if (concrete.length === 1) return concrete[0];
2011
+ if (concrete.some((s) => s.negated)) return "any";
2012
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
2013
+ }
2014
+ function partFirstAccept(part) {
2015
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
2016
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
2017
+ return shapeFirstAccept(part.inner);
2018
+ }
2019
+ function seqFirstAccept(parts) {
2020
+ const sets = [];
2021
+ for (const p of parts) {
2022
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
2023
+ sets.push(partFirstAccept(p));
2024
+ if (mandatory) break;
2025
+ }
2026
+ return unionCharSets(sets);
2027
+ }
2028
+ function groupInnerSafe(shape) {
2029
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
2030
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
2031
+ if (shape.kind === "alt") return true;
2032
+ return false;
2033
+ }
2034
+ function shapeFirstAccept(shape) {
2035
+ if (shapeCanBeEmpty(shape)) return "any";
2036
+ switch (shape.kind) {
2037
+ case "chars":
2038
+ return { ranges: shape.ranges, negated: false };
2039
+ case "ident":
2040
+ return { ranges: shape.head, negated: false };
2041
+ case "until":
2042
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
2043
+ case "delimited":
2044
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
2045
+ case "string":
2046
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
2047
+ case "litFold": {
2048
+ const cp = shape.open[0];
2049
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
2050
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
2051
+ return { ranges: [[cp, cp]], negated: false };
2052
+ }
2053
+ case "seq":
2054
+ return seqFirstAccept(shape.parts);
2055
+ case "lookahead":
2056
+ return shapeFirstAccept(shape.inner);
2057
+ // zero-width — doesn't change first-char acceptance
2058
+ case "alt": {
2059
+ const sets = shape.firsts.map((f) => f === null ? "any" : f);
2060
+ return unionCharSets(sets);
2061
+ }
2062
+ }
2063
+ }
2064
+ function allPairsDisjoint(sets) {
2065
+ for (let i = 0; i < sets.length; i++) {
2066
+ const a = sets[i];
2067
+ if (a === "any") return false;
2068
+ for (let j = i + 1; j < sets.length; j++) {
2069
+ const b = sets[j];
2070
+ if (b === "any" || !classDisjoint(a, b)) return false;
2071
+ }
2072
+ }
2073
+ return true;
2074
+ }
2075
+ function parseAlternation(source) {
2076
+ const parts = splitTopLevelAlternation(source);
2077
+ if (!parts) return null;
2078
+ const arms = [];
2079
+ for (const p of parts) {
2080
+ const arm = parseScanShape(p);
2081
+ if (!arm) return null;
2082
+ arms.push(arm);
2083
+ }
2084
+ const firstSets = arms.map(shapeFirstAccept);
2085
+ const disjoint = allPairsDisjoint(firstSets);
2086
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
2087
+ }
1571
2088
  function parseScanShape(source) {
2089
+ const alt = parseAlternation(source);
2090
+ if (alt) return alt;
2091
+ const la = stripTrailingLookahead(source);
2092
+ if (la) {
2093
+ const inner = parseScanShapeCore(la.base);
2094
+ if (!inner) return null;
2095
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2096
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2097
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2098
+ }
2099
+ return parseScanShapeCore(source);
2100
+ }
2101
+ function splitDelimArms(body) {
2102
+ const arms = [];
2103
+ let depth = 0;
2104
+ let last = 0;
2105
+ let i = 0;
2106
+ while (i < body.length) {
2107
+ const ch = body[i];
2108
+ if (ch === "\\") {
2109
+ i += 2;
2110
+ continue;
2111
+ }
2112
+ if (ch === "[") {
2113
+ let j = i + 1;
2114
+ if (body[j] === "^") j++;
2115
+ while (j < body.length && body[j] !== "]") {
2116
+ if (body[j] === "\\") j += 2;
2117
+ else j++;
2118
+ }
2119
+ if (body[j] !== "]") return null;
2120
+ i = j + 1;
2121
+ continue;
2122
+ }
2123
+ if (ch === "(") {
2124
+ depth++;
2125
+ i++;
2126
+ continue;
2127
+ }
2128
+ if (ch === ")") {
2129
+ depth--;
2130
+ if (depth < 0) return null;
2131
+ i++;
2132
+ continue;
2133
+ }
2134
+ if (ch === "|" && depth === 0) {
2135
+ arms.push(body.slice(last, i));
2136
+ last = i + 1;
2137
+ }
2138
+ i++;
2139
+ }
2140
+ if (depth !== 0) return null;
2141
+ arms.push(body.slice(last));
2142
+ return arms;
2143
+ }
2144
+ function delimitedBodySound(body, close) {
2145
+ const l0 = close[0];
2146
+ if (close.length > 2) return false;
2147
+ const arms = splitDelimArms(body);
2148
+ if (!arms) return false;
2149
+ let bulkSeen = false;
2150
+ let guardSeen = false;
2151
+ for (const arm of arms) {
2152
+ const bulk = /^\[\^((?:\\.|[^\]])+)\]$/.exec(arm);
2153
+ if (bulk) {
2154
+ if (bulkSeen) return false;
2155
+ const ranges = parseClassRanges(bulk[1]);
2156
+ if (!ranges || ranges.length !== 1 || ranges[0][0] !== l0 || ranges[0][1] !== l0) return false;
2157
+ bulkSeen = true;
2158
+ continue;
2159
+ }
2160
+ if (close.length === 2 && arm.endsWith(")")) {
2161
+ const idx = arm.indexOf("(?!");
2162
+ if (idx === -1) return false;
2163
+ const prefix = literalCodePoints(arm.slice(0, idx));
2164
+ const op = parseClassOperand(arm.slice(idx + 3, -1));
2165
+ const prefixIsL0 = prefix?.length === 1 && prefix[0] === l0;
2166
+ const forbidsL1 = op && !op.negated && op.ranges.length === 1 && op.ranges[0][0] === close[1] && op.ranges[0][1] === close[1];
2167
+ if (!prefixIsL0 || !forbidsL1 || guardSeen) return false;
2168
+ guardSeen = true;
2169
+ continue;
2170
+ }
2171
+ return false;
2172
+ }
2173
+ return bulkSeen && guardSeen === (close.length === 2);
2174
+ }
2175
+ function parseDelimited(source) {
2176
+ if (source.includes("\\\\")) return null;
2177
+ let i = 0;
2178
+ while (i < source.length && source[i] !== "(") {
2179
+ i += source[i] === "\\" ? 2 : 1;
2180
+ }
2181
+ if (source[i] !== "(" || source[i + 1] !== "?" || source[i + 2] !== ":") return null;
2182
+ const openFrag = source.slice(0, i);
2183
+ let k = i + 3;
2184
+ let depth = 1;
2185
+ while (k < source.length && depth > 0) {
2186
+ const ch = source[k];
2187
+ if (ch === "\\") {
2188
+ k += 2;
2189
+ continue;
2190
+ }
2191
+ if (ch === "[") {
2192
+ let j = k + 1;
2193
+ if (source[j] === "^") j++;
2194
+ while (j < source.length && source[j] !== "]") {
2195
+ if (source[j] === "\\") j += 2;
2196
+ else j++;
2197
+ }
2198
+ if (source[j] !== "]") return null;
2199
+ k = j + 1;
2200
+ continue;
2201
+ }
2202
+ if (ch === "(") depth++;
2203
+ else if (ch === ")") depth--;
2204
+ k++;
2205
+ }
2206
+ if (depth !== 0) return null;
2207
+ const body = source.slice(i + 3, k - 1);
2208
+ if (source[k] !== "*") return null;
2209
+ const closeFrag = source.slice(k + 1);
2210
+ const open = literalCodePoints(openFrag);
2211
+ const close = literalCodePoints(closeFrag);
2212
+ if (!open || !close || !delimitedBodySound(body, close)) return null;
2213
+ return { kind: "delimited", open, close };
2214
+ }
2215
+ function parseScanShapeCore(source) {
1572
2216
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1573
- if (m) {
1574
- if (m[1].startsWith("^")) return null;
2217
+ if (m && !m[1].startsWith("^")) {
1575
2218
  const ranges = parseClassRanges(m[1]);
1576
2219
  return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1577
2220
  }
1578
- m = /^\\([dw])([+*])$/.exec(source);
2221
+ m = /^\\([dws])([+*])$/.exec(source);
1579
2222
  if (m) {
1580
2223
  return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1581
2224
  }
1582
- m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
2225
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
1583
2226
  if (m) {
1584
2227
  const head = classToRanges(m[1]);
1585
2228
  const tail = classToRanges(m[2]);
1586
- return head && tail ? { kind: "ident", head, tail } : null;
2229
+ if (head && tail) return { kind: "ident", head, tail };
1587
2230
  }
1588
2231
  const str = parseStringShape(source);
1589
2232
  if (str) return str;
@@ -1592,20 +2235,19 @@ function parseScanShape(source) {
1592
2235
  const open = literalCodePoints(m[1]);
1593
2236
  const stop = parseClassRanges(m[2]);
1594
2237
  if (open && stop) return { kind: "until", open, stop };
1595
- return null;
1596
- }
1597
- if (!source.includes("\\\\")) {
1598
- m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1599
- if (m && m[1] && m[2]) {
1600
- const open = literalCodePoints(m[1]);
1601
- const close = literalCodePoints(m[2]);
1602
- if (open && close) return { kind: "delimited", open, close };
1603
- }
1604
2238
  }
2239
+ const delim = parseDelimited(source);
2240
+ if (delim) return delim;
2241
+ const parts = parseSeqParts(source);
2242
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1605
2243
  return null;
1606
2244
  }
1607
2245
  function scanShapeFromRegex(source, flags) {
1608
- if (/[imsu]/.test(flags)) return null;
2246
+ if (/[msu]/.test(flags)) return null;
2247
+ if (/i/.test(flags)) {
2248
+ const open = literalCodePoints(source);
2249
+ return open ? { kind: "litFold", open } : null;
2250
+ }
1609
2251
  return parseScanShape(source);
1610
2252
  }
1611
2253
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
@@ -1620,6 +2262,53 @@ function stringHardStop(shape) {
1620
2262
  }
1621
2263
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1622
2264
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2265
+ if (shape.kind === "lookahead") {
2266
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
2267
+ const okV = mint("_ok");
2268
+ const endV2 = mint("_end");
2269
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
2270
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
2271
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
2272
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
2273
+ return {
2274
+ setup: [
2275
+ ...inner.setup,
2276
+ `${ind2}let ${okV} = ${inner.ok}`,
2277
+ `${ind2}let ${endV2} = ${inner.end}`,
2278
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
2279
+ ],
2280
+ ok: okV,
2281
+ end: endV2
2282
+ };
2283
+ }
2284
+ if (shape.kind === "alt") {
2285
+ const okV = mint("_ok");
2286
+ const endV2 = mint("_end");
2287
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2288
+ if (shape.disjoint) {
2289
+ const c0 = codeAt(start, firstChar);
2290
+ shape.arms.forEach((arm, k) => {
2291
+ const fs = shape.firsts[k];
2292
+ const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2293
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2294
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2295
+ lines.push(...m.setup);
2296
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2297
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2298
+ lines.push(`${ind2}}`);
2299
+ });
2300
+ } else {
2301
+ const lbl = mint("_alt");
2302
+ lines.push(`${ind2}${lbl}: {`);
2303
+ for (const arm of shape.arms) {
2304
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2305
+ lines.push(...m.setup);
2306
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
2307
+ }
2308
+ lines.push(`${ind2}}`);
2309
+ }
2310
+ return { setup: lines, ok: okV, end: endV2 };
2311
+ }
1623
2312
  if (shape.kind === "chars") {
1624
2313
  const cur = mint("_e");
1625
2314
  return {
@@ -1646,6 +2335,82 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1646
2335
  end: cur
1647
2336
  };
1648
2337
  }
2338
+ if (shape.kind === "seq") {
2339
+ const endV2 = mint("_e");
2340
+ const okV = mint("_ok");
2341
+ const runCond = (p, at) => {
2342
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2343
+ return p.negated ? `!(${c})` : `(${c})`;
2344
+ };
2345
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2346
+ let first = true;
2347
+ for (const p of shape.parts) {
2348
+ if (p.part === "lit") {
2349
+ const L = p.cps.length;
2350
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2351
+ if (p.optional) {
2352
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2353
+ } else {
2354
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2355
+ lines.push(`${ind2} ${endV2} += ${L}`);
2356
+ }
2357
+ } else if (p.part === "run" && !p.unbounded) {
2358
+ if (p.min === 1) {
2359
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2360
+ lines.push(`${ind2} ${endV2}++`);
2361
+ } else {
2362
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2363
+ }
2364
+ } else if (p.part === "run") {
2365
+ const s = mint("_s");
2366
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2367
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2368
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2369
+ } else {
2370
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2371
+ if (!p.unbounded) {
2372
+ lines.push(...inner.setup);
2373
+ if (p.min === 1) {
2374
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2375
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2376
+ } else {
2377
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2378
+ }
2379
+ } else {
2380
+ const progV = mint("_prog");
2381
+ lines.push(`${ind2} let ${progV} = false`);
2382
+ lines.push(`${ind2} while (true) {`);
2383
+ lines.push(...inner.setup);
2384
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2385
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2386
+ lines.push(`${ind2} ${progV} = true`);
2387
+ lines.push(`${ind2} }`);
2388
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2389
+ }
2390
+ }
2391
+ first = false;
2392
+ }
2393
+ lines.push(`${ind2} ${okV} = true`);
2394
+ lines.push(`${ind2}} while (false)`);
2395
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2396
+ return { setup: lines, ok: okV, end: endV2 };
2397
+ }
2398
+ if (shape.kind === "litFold") {
2399
+ const openLen2 = shape.open.length;
2400
+ const endV2 = mint("_end");
2401
+ const checks = shape.open.map((cp, k) => {
2402
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2403
+ return foldEq(cVar, cp);
2404
+ });
2405
+ return {
2406
+ setup: [
2407
+ `${ind2}let ${endV2} = ${start}`,
2408
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2409
+ ],
2410
+ ok: `${endV2} > ${start}`,
2411
+ end: endV2
2412
+ };
2413
+ }
1649
2414
  if (shape.kind === "until") {
1650
2415
  const j2 = mint("_j");
1651
2416
  const openLen2 = shape.open.length;
@@ -1927,6 +2692,7 @@ function looseMkBuildRe(type) {
1927
2692
  );
1928
2693
  }
1929
2694
  function analyzeMkInlineBuild(def) {
2695
+ if (!def.build) return null;
1930
2696
  const src = (def.buildSrc ?? def.build.toString()).trim();
1931
2697
  const strict = src.match(MK_BUILD_RE);
1932
2698
  if (strict) {
@@ -2024,6 +2790,15 @@ function v(ctx, prefix = "_v") {
2024
2790
  function ind(ctx) {
2025
2791
  return " ".repeat(ctx.indent);
2026
2792
  }
2793
+ function nsp(ctx) {
2794
+ return ctx.ns ?? "";
2795
+ }
2796
+ function mfRef(ctx) {
2797
+ return `${nsp(ctx)}_mf`;
2798
+ }
2799
+ function buildRef(ctx) {
2800
+ return `${nsp(ctx)}_build`;
2801
+ }
2027
2802
  function reindentStmts(stmts, targetLevels) {
2028
2803
  const nonEmpty = stmts.filter((s) => s.trim().length > 0);
2029
2804
  if (nonEmpty.length === 0) return stmts;
@@ -2040,8 +2815,8 @@ function failReturnArr(expectedArr, posExpr) {
2040
2815
  function hoistExpected(ctx, constArrSource) {
2041
2816
  let name = ctx.expectedMap.get(constArrSource);
2042
2817
  if (name === void 0) {
2043
- name = `_fx${ctx.expectedDecls.length}`;
2044
- ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
2818
+ name = `${nsp(ctx)}_fx${ctx.expectedDecls.length}`;
2819
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2045
2820
  ctx.expectedMap.set(constArrSource, name);
2046
2821
  }
2047
2822
  return name;
@@ -2139,13 +2914,13 @@ function emitLeafCapture(ctx, valExpr, startExpr, endExpr) {
2139
2914
  `${i}}`
2140
2915
  ];
2141
2916
  }
2142
- function ensureRegexDecl(ctx, optimizedSource, flags) {
2917
+ function ensureRegexDecl(ctx, source, flags) {
2143
2918
  const f = "y" + flags.replace(/[gy]/g, "");
2144
- const key = `${optimizedSource}/${f}`;
2919
+ const key = `${source}/${f}`;
2145
2920
  let rName = ctx.regexMap.get(key);
2146
2921
  if (rName === void 0) {
2147
- rName = `_re${ctx.regexDecls.length}`;
2148
- ctx.regexDecls.push(`const ${rName} = /${optimizedSource}/${f}`);
2922
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2923
+ ctx.regexDecls.push(`const ${rName} = /${source}/${f}`);
2149
2924
  ctx.regexMap.set(key, rName);
2150
2925
  }
2151
2926
  return rName;
@@ -2154,7 +2929,7 @@ function ensureTriviaFn(ctx) {
2154
2929
  const trivia2 = ctx.activeTrivia;
2155
2930
  const existing = ctx.triviaFnNames.get(trivia2);
2156
2931
  if (existing) return existing;
2157
- const fnName = `_tf${ctx.triviaFnNames.size}`;
2932
+ const fnName = `${nsp(ctx)}_tf${ctx.triviaFnNames.size}`;
2158
2933
  ctx.triviaFnNames.set(trivia2, fnName);
2159
2934
  ctx.triviaCaptureNames.set(trivia2, fnName);
2160
2935
  const labeledSpec = analyzeLabeledTrivia(trivia2);
@@ -2176,7 +2951,7 @@ function ensureTriviaFn(ctx) {
2176
2951
  for (const arm of regexSpec.arms) {
2177
2952
  const def = arm.parser._def;
2178
2953
  if (def.tag !== "regex") break;
2179
- reNames.push(ensureRegexDecl(ctx, def.optimizedSource, def.flags));
2954
+ reNames.push(ensureRegexDecl(ctx, def.source, def.flags));
2180
2955
  }
2181
2956
  if (reNames.length === regexSpec.arms.length) {
2182
2957
  ctx.namedFnDecls.push(buildLabeledRegexTriviaFnDecl(fnName, regexSpec, reNames));
@@ -2363,18 +3138,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
2363
3138
  return true;
2364
3139
  }
2365
3140
  }
2366
- function emitFallible(inner, ctx, pos) {
3141
+ function emitFallible(inner, ctx, pos, swallow = false) {
2367
3142
  const lbl = v(ctx, "_lbl");
2368
3143
  const okV = `${lbl}ok`;
2369
3144
  const valV = `${lbl}v`;
2370
3145
  const endV = `${lbl}e`;
2371
3146
  const savedLabel = ctx.failLabel;
2372
3147
  const savedIndent = ctx.indent;
3148
+ const savedRecord = ctx.recordFail;
2373
3149
  ctx.failLabel = lbl;
2374
3150
  ctx.indent = savedIndent + 1;
3151
+ if (swallow) ctx.recordFail = false;
2375
3152
  const r = emit(inner, ctx, pos);
2376
3153
  ctx.failLabel = savedLabel;
2377
3154
  ctx.indent = savedIndent;
3155
+ ctx.recordFail = savedRecord;
2378
3156
  const ind0 = ind(ctx);
2379
3157
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
2380
3158
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2397,6 +3175,7 @@ function emitFallible(inner, ctx, pos) {
2397
3175
  ];
2398
3176
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2399
3177
  }
3178
+ var CHARCODE_CHAIN_MAX = 16;
2400
3179
  function emitLit(def, ctx, pos) {
2401
3180
  const { value, caseInsensitive } = def;
2402
3181
  const len = value.length;
@@ -2419,7 +3198,7 @@ function emitLit(def, ctx, pos) {
2419
3198
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2420
3199
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2421
3200
  );
2422
- } else if (len <= 4) {
3201
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2423
3202
  const checks = Array.from(
2424
3203
  { length: len },
2425
3204
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2441,7 +3220,55 @@ function emitLit(def, ctx, pos) {
2441
3220
  function escapeKeywordRe(s) {
2442
3221
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2443
3222
  }
3223
+ function emitKeywordsFast(def, ctx, pos) {
3224
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
3225
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
3226
+ if (def.caseInsensitive && def.boundary) return null;
3227
+ let boundary = null;
3228
+ if (def.boundary) {
3229
+ let body = def.boundary;
3230
+ const negated = body.startsWith("^");
3231
+ if (negated) body = body.slice(1);
3232
+ const ranges = parseClassRanges(body);
3233
+ if (!ranges) return null;
3234
+ boundary = { ranges, negated };
3235
+ }
3236
+ const mint = (prefix = "_v") => v(ctx, prefix);
3237
+ const lbl = v(ctx, "_kwLbl");
3238
+ const valV = v(ctx, "_kwv");
3239
+ const endV = v(ctx, "_kwe");
3240
+ const bodyInd = ind(ctx) + " ";
3241
+ const tries = [];
3242
+ for (const w of def.words) {
3243
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
3244
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
3245
+ if (boundary) {
3246
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
3247
+ }
3248
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
3249
+ tries.push(
3250
+ ...m.setup,
3251
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
3252
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
3253
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
3254
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
3255
+ );
3256
+ }
3257
+ const stmts = [
3258
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
3259
+ `${ind(ctx)}${lbl}: {`,
3260
+ ...tries,
3261
+ `${ind(ctx)}}`,
3262
+ // Every word has length >= 1 (checked above), so a real match always
3263
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
3264
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
3265
+ ];
3266
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
3267
+ return { stmts, valueVar: valV, endVar: endV };
3268
+ }
2444
3269
  function emitKeywords(def, ctx, pos) {
3270
+ const fast = emitKeywordsFast(def, ctx, pos);
3271
+ if (fast) return fast;
2445
3272
  const alt = def.words.map(escapeKeywordRe).join("|");
2446
3273
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2447
3274
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2449,7 +3276,7 @@ function emitKeywords(def, ctx, pos) {
2449
3276
  const key = `${source}/${flags}`;
2450
3277
  let rName = ctx.regexMap.get(key);
2451
3278
  if (rName === void 0) {
2452
- rName = `_re${ctx.regexDecls.length}`;
3279
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2453
3280
  ctx.regexDecls.push(`const ${rName} = /${source}/${flags}`);
2454
3281
  ctx.regexMap.set(key, rName);
2455
3282
  }
@@ -2483,11 +3310,11 @@ function emitRegex(def, ctx, pos) {
2483
3310
  }
2484
3311
  }
2485
3312
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2486
- const key = `${def.optimizedSource}/${flags}`;
3313
+ const key = `${def.source}/${flags}`;
2487
3314
  let rName = ctx.regexMap.get(key);
2488
3315
  if (rName === void 0) {
2489
- rName = `_re${ctx.regexDecls.length}`;
2490
- ctx.regexDecls.push(`const ${rName} = /${def.optimizedSource}/${flags}`);
3316
+ rName = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
3317
+ ctx.regexDecls.push(`const ${rName} = /${def.source}/${flags}`);
2491
3318
  ctx.regexMap.set(key, rName);
2492
3319
  }
2493
3320
  const mv = v(ctx, "_m");
@@ -2572,6 +3399,31 @@ function failsAtStart(p) {
2572
3399
  return false;
2573
3400
  }
2574
3401
  }
3402
+ function armStaticExpected(ctx, p) {
3403
+ return hoistExpected(ctx, staticExpectedArr([p]));
3404
+ }
3405
+ function canMatchEmptyAtStart(p) {
3406
+ const d = p._def;
3407
+ switch (d.tag) {
3408
+ case "regex":
3409
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3410
+ case "optional":
3411
+ return true;
3412
+ case "many":
3413
+ return true;
3414
+ case "transform":
3415
+ case "label":
3416
+ return canMatchEmptyAtStart(d.parser);
3417
+ case "literal":
3418
+ return d.value.length === 0;
3419
+ default:
3420
+ return false;
3421
+ }
3422
+ }
3423
+ function needsFirstSetGuard(p) {
3424
+ const fs = p._meta.firstSet;
3425
+ return fs.kind !== "any" && !canMatchEmptyAtStart(p);
3426
+ }
2575
3427
  function emitDisjointArm(p, ctx, pos, valV, endV) {
2576
3428
  if (failsAtStart(p)) {
2577
3429
  const r = emit(p, ctx, pos);
@@ -2639,7 +3491,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
2639
3491
  const reKey = `${regexDef.source}/${cleanFlags}`;
2640
3492
  let reVar = ctx.regexMap.get(reKey);
2641
3493
  if (reVar === void 0) {
2642
- reVar = `_re${ctx.regexDecls.length}`;
3494
+ reVar = `${nsp(ctx)}_re${ctx.regexDecls.length}`;
2643
3495
  ctx.regexDecls.push(`const ${reVar} = /${regexDef.source}/${cleanFlags}`);
2644
3496
  ctx.regexMap.set(reKey, reVar);
2645
3497
  }
@@ -2713,21 +3565,26 @@ function emitFirstMatch(def, ctx, pos) {
2713
3565
  const resValV = v(ctx, "_crv");
2714
3566
  const resEndV = v(ctx, "_cre");
2715
3567
  const resOkV = v(ctx, "_crok");
3568
+ const codeV = v(ctx, "_chcode");
2716
3569
  const slots = def.parsers.map(() => v(ctx, "_cfx"));
2717
3570
  const ind0 = ind(ctx);
2718
3571
  const stmts = [
2719
3572
  `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2720
- `${ind0}let ${slots.join(", ")}`
3573
+ `${ind0}let ${slots.join(", ")}`,
3574
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2721
3575
  ];
2722
3576
  for (let i = 0; i < def.parsers.length; i++) {
2723
3577
  const p = def.parsers[i];
2724
3578
  const gate = def.gates[i];
2725
3579
  const autoNot = def.autoNot[i];
3580
+ const atStart = failsAtStart(p);
3581
+ const staticFx = armStaticExpected(ctx, p);
3582
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2726
3583
  let gateCond = null;
2727
3584
  if (gate) {
2728
3585
  const gateIdx = ctx.mapFns.length;
2729
3586
  ctx.mapFns.push(gate);
2730
- gateCond = `_mf[${gateIdx}](_ctx.state)`;
3587
+ gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
2731
3588
  }
2732
3589
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
2733
3590
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
@@ -2737,39 +3594,41 @@ function emitFirstMatch(def, ctx, pos) {
2737
3594
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2738
3595
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2739
3596
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3597
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2740
3598
  stmts.push(`${ind0}if (${skipCond}) {`);
2741
- ctx.indent++;
2742
- const ind1 = ind(ctx);
3599
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3600
+ ctx.indent += fsGuard ? 2 : 1;
2743
3601
  if (markLeaves) {
2744
3602
  stmts.push(
2745
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2746
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2747
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2748
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3603
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3604
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3605
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3606
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2749
3607
  );
2750
3608
  }
2751
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3609
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2752
3610
  stmts.push(...armStmts);
2753
- ctx.indent--;
2754
3611
  if (autoNot && autoNot.length > 0) {
2755
3612
  const anCode = v(ctx, "_anc");
2756
3613
  const rejectCond = autoNot.map(
2757
3614
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2758
3615
  ).join(" || ");
2759
- stmts.push(`${ind0} if (${okVar}) {`);
2760
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2761
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2762
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2763
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2764
- stmts.push(`${ind0} ${resOkV} = true`);
2765
- stmts.push(`${ind0} }`);
2766
- stmts.push(`${ind0} }`);
2767
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2768
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3616
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3617
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3618
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3619
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3620
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3621
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3622
+ stmts.push(`${ind(ctx)} }`);
3623
+ stmts.push(`${ind(ctx)}}`);
3624
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3625
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2769
3626
  } else {
2770
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2771
- stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
3627
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3628
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2772
3629
  }
3630
+ ctx.indent -= fsGuard ? 2 : 1;
3631
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2773
3632
  stmts.push(`${ind0}}`);
2774
3633
  }
2775
3634
  const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
@@ -2792,7 +3651,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2792
3651
  ctx.mapFnSrcs.push(def.fnSrc ?? null);
2793
3652
  const vv = v(ctx);
2794
3653
  return {
2795
- stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = _mf[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
3654
+ stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
2796
3655
  valueVar: vv,
2797
3656
  endVar: endV
2798
3657
  };
@@ -2802,7 +3661,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2802
3661
  function emitLiteralCondition(litVal, pos) {
2803
3662
  const len = litVal.length;
2804
3663
  if (len === 0) return "true";
2805
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3664
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2806
3665
  const checks = [`${pos} + ${len} <= input.length`];
2807
3666
  for (let i = 0; i < len; i++) {
2808
3667
  const code = litVal.codePointAt(i);
@@ -2851,7 +3710,7 @@ function emitMany(def, ctx, pos) {
2851
3710
  itemPos = npV;
2852
3711
  }
2853
3712
  }
2854
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3713
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2855
3714
  stmts.push(
2856
3715
  ...iterStmts,
2857
3716
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2865,7 +3724,7 @@ function emitMany(def, ctx, pos) {
2865
3724
  function emitOptional(def, ctx, pos) {
2866
3725
  const valV = v(ctx, "_opt");
2867
3726
  const endV = v(ctx, "_opte");
2868
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3727
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2869
3728
  const ind0 = ind(ctx);
2870
3729
  const stmts = [
2871
3730
  ...lblStmts,
@@ -2877,7 +3736,7 @@ function emitOptional(def, ctx, pos) {
2877
3736
  function emitSepBy(_p, def, ctx, pos) {
2878
3737
  const arrV = v(ctx, "_arr");
2879
3738
  const curV = v(ctx, "_cur");
2880
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3739
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2881
3740
  const stmts = [
2882
3741
  `${ind(ctx)}const ${arrV} = []`,
2883
3742
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2909,11 +3768,11 @@ function emitSepBy(_p, def, ctx, pos) {
2909
3768
  );
2910
3769
  sepAtPos = spV;
2911
3770
  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}; `;
2912
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3771
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2913
3772
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2914
3773
  const npV = v(ctx, "_np");
2915
3774
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2916
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3775
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2917
3776
  stmts.push(
2918
3777
  ...nextStmts,
2919
3778
  // item failed → unwind the separator too, back to the end of the last item
@@ -2926,11 +3785,11 @@ function emitSepBy(_p, def, ctx, pos) {
2926
3785
  const spV = v(ctx, "_sp");
2927
3786
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2928
3787
  sepAtPos = spV;
2929
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3788
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2930
3789
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2931
3790
  const npV = v(ctx, "_np");
2932
3791
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2933
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3792
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2934
3793
  stmts.push(
2935
3794
  ...nextStmts,
2936
3795
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2947,10 +3806,10 @@ function emitSepBy(_p, def, ctx, pos) {
2947
3806
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2948
3807
  );
2949
3808
  }
2950
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3809
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2951
3810
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2952
3811
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2953
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3812
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2954
3813
  stmts.push(
2955
3814
  ...nextStmts,
2956
3815
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -3008,7 +3867,7 @@ function emitScanTo(def, ctx, pos) {
3008
3867
  return { stmts, valueVar: valV, endVar: curV };
3009
3868
  }
3010
3869
  function emitNot(def, ctx, pos) {
3011
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3870
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
3012
3871
  const label2 = JSON.stringify(`not(${def.parser._tag})`);
3013
3872
  return {
3014
3873
  stmts: [
@@ -3020,16 +3879,17 @@ function emitNot(def, ctx, pos) {
3020
3879
  };
3021
3880
  }
3022
3881
  function emitNode(def, ctx, pos) {
3023
- const mkType = analyzeMkInlineBuild(def);
3882
+ const structural = def.build === void 0;
3883
+ const mkType = structural ? null : analyzeMkInlineBuild(def);
3024
3884
  let buildIdx = null;
3025
- if (!mkType) {
3885
+ if (!mkType && !structural) {
3026
3886
  buildIdx = ctx.buildFns.length;
3027
3887
  ctx.buildFns.push(def.build);
3028
3888
  ctx.buildSrcs.push(def.buildSrc ?? null);
3029
3889
  }
3030
3890
  const i = ind(ctx);
3031
- const capturesTrivia = mkType !== null || buildReadsTrivia(def);
3032
- const clonesState = buildReadsState(def);
3891
+ const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3892
+ const clonesState = structural || buildReadsState(def);
3033
3893
  const chV = v(ctx, "_ch");
3034
3894
  const rawV = v(ctx, "_raw");
3035
3895
  const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
@@ -3052,7 +3912,8 @@ function emitNode(def, ctx, pos) {
3052
3912
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3053
3913
  }
3054
3914
  const ndV = v(ctx, "_nd");
3055
- const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3915
+ const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
3916
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
3056
3917
  const finalExpr = def.collapse ? `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})` : ndExpr;
3057
3918
  stmts.push(
3058
3919
  `${i}const ${ndV} = ${finalExpr}`,
@@ -3077,8 +3938,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
3077
3938
  return { stmts, valueVar: vv, endVar: ev };
3078
3939
  }
3079
3940
  function emitLazy(p, def, ctx, pos) {
3941
+ const canonical = ctx.ruleNames?.get(p);
3080
3942
  const usage = ctx.lazyUsage;
3081
- if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3943
+ if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
3082
3944
  let resolved;
3083
3945
  try {
3084
3946
  resolved = def.thunk();
@@ -3088,7 +3950,7 @@ function emitLazy(p, def, ctx, pos) {
3088
3950
  return emit(resolved, ctx, pos);
3089
3951
  }
3090
3952
  if (!ctx.namedParsers.has(p)) {
3091
- const fnName2 = `_pf${ctx.namedParsers.size}`;
3953
+ const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
3092
3954
  ctx.namedParsers.set(p, fnName2);
3093
3955
  let resolved;
3094
3956
  try {
@@ -3099,11 +3961,14 @@ function emitLazy(p, def, ctx, pos) {
3099
3961
  }
3100
3962
  const savedIndent = ctx.indent;
3101
3963
  const savedFailLabel = ctx.failLabel;
3964
+ const savedRecord = ctx.recordFail;
3102
3965
  ctx.indent = 1;
3103
3966
  ctx.failLabel = "_pfail";
3967
+ ctx.recordFail = true;
3104
3968
  const r = emit(resolved, ctx, "_pos");
3105
3969
  ctx.indent = savedIndent;
3106
3970
  ctx.failLabel = savedFailLabel;
3971
+ ctx.recordFail = savedRecord;
3107
3972
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
3108
3973
  }
3109
3974
  const fnName = ctx.namedParsers.get(p);
@@ -3203,7 +4068,7 @@ function emit(p, ctx, pos) {
3203
4068
  return {
3204
4069
  stmts: [
3205
4070
  ...inner.stmts,
3206
- `${ind(ctx)}const ${mv} = _mf[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
4071
+ `${ind(ctx)}const ${mv} = ${mfRef(ctx)}[${fnIdx}](${inner.valueVar}, { start: ${pos}, end: ${inner.endVar} })`
3207
4072
  ],
3208
4073
  valueVar: mv,
3209
4074
  endVar: inner.endVar
@@ -3266,7 +4131,7 @@ function emit(p, ctx, pos) {
3266
4131
  const vv = v(ctx);
3267
4132
  return {
3268
4133
  stmts: [
3269
- ...emitIfFail(ctx, `!_mf[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
4134
+ ...emitIfFail(ctx, `!${mfRef(ctx)}[${fnIdx}](_ctx.state)`, failBody(ctx, '"gate"', pos)),
3270
4135
  `${ind(ctx)}const ${vv} = null`
3271
4136
  ],
3272
4137
  valueVar: vv,
@@ -3283,11 +4148,14 @@ function emit(p, ctx, pos) {
3283
4148
  ctx.namedParsers.set(innerParser, fnName);
3284
4149
  const savedIndent = ctx.indent;
3285
4150
  const savedFailLabel = ctx.failLabel;
4151
+ const savedRecord = ctx.recordFail;
3286
4152
  ctx.indent = 1;
3287
4153
  ctx.failLabel = "_pfail";
4154
+ ctx.recordFail = true;
3288
4155
  const innerR = emit(innerParser, ctx, "_pos");
3289
4156
  ctx.indent = savedIndent;
3290
4157
  ctx.failLabel = savedFailLabel;
4158
+ ctx.recordFail = savedRecord;
3291
4159
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
3292
4160
  }
3293
4161
  const fn = ctx.namedParsers.get(innerParser);
@@ -3296,7 +4164,7 @@ function emit(p, ctx, pos) {
3296
4164
  const ev = v(ctx, "_wce");
3297
4165
  return {
3298
4166
  stmts: [
3299
- `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
4167
+ `${ind(ctx)}const ${rv} = { ..._ctx, state: ${mfRef(ctx)}[${evIdx}]() }`,
3300
4168
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
3301
4169
  // withCtx runs on a spread child ctx — copy its recorded failure back
3302
4170
  // and propagate the inner failure verbatim (interpreter parity).
@@ -3427,6 +4295,39 @@ function analyzeLazyUsageMulti(roots) {
3427
4295
  for (const root of roots) walk2(root);
3428
4296
  return { counts, recursive };
3429
4297
  }
4298
+ function ruleDependencies(ruleMap) {
4299
+ const nameOf = /* @__PURE__ */ new Map();
4300
+ for (const [name, comb] of ruleMap) nameOf.set(comb, name);
4301
+ const deps = /* @__PURE__ */ new Map();
4302
+ for (const [name, comb] of ruleMap) {
4303
+ const found = /* @__PURE__ */ new Set();
4304
+ const seen = /* @__PURE__ */ new Set();
4305
+ const walk2 = (p, isRoot) => {
4306
+ const def = p._def;
4307
+ const boundary = def.tag === "lazy" ? p._ruleName ?? nameOf.get(p) : nameOf.get(p);
4308
+ if (!isRoot && boundary !== void 0) {
4309
+ found.add(boundary);
4310
+ return;
4311
+ }
4312
+ if (seen.has(p)) return;
4313
+ seen.add(p);
4314
+ if (def.tag === "lazy") {
4315
+ let resolved;
4316
+ try {
4317
+ resolved = def.thunk();
4318
+ } catch {
4319
+ return;
4320
+ }
4321
+ walk2(resolved, false);
4322
+ return;
4323
+ }
4324
+ for (const child of childrenOf(def)) walk2(child, false);
4325
+ };
4326
+ walk2(comb, true);
4327
+ deps.set(name, [...found]);
4328
+ }
4329
+ return deps;
4330
+ }
3430
4331
  function compile(combinator, mapFnSources) {
3431
4332
  const ctx = {
3432
4333
  vars: 0,
@@ -3435,6 +4336,7 @@ function compile(combinator, mapFnSources) {
3435
4336
  regexMap: /* @__PURE__ */ new Map(),
3436
4337
  expectedDecls: [],
3437
4338
  expectedMap: /* @__PURE__ */ new Map(),
4339
+ recordFail: true,
3438
4340
  mapFns: [],
3439
4341
  mapFnSrcs: [],
3440
4342
  buildFns: [],
@@ -3509,6 +4411,150 @@ function compile(combinator, mapFnSources) {
3509
4411
  }
3510
4412
  };
3511
4413
  }
4414
+ function compileLinkable(ruleMapArg, ns) {
4415
+ if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4416
+ const ruleMap = ruleMapArg.filter(([, val]) => {
4417
+ const d = val._def;
4418
+ if (d.tag !== "lazy") return true;
4419
+ try {
4420
+ d.thunk();
4421
+ return true;
4422
+ } catch {
4423
+ return false;
4424
+ }
4425
+ });
4426
+ const ctx = {
4427
+ vars: 0,
4428
+ indent: 1,
4429
+ regexDecls: [],
4430
+ regexMap: /* @__PURE__ */ new Map(),
4431
+ expectedDecls: [],
4432
+ expectedMap: /* @__PURE__ */ new Map(),
4433
+ recordFail: true,
4434
+ mapFns: [],
4435
+ mapFnSrcs: [],
4436
+ buildFns: [],
4437
+ buildSrcs: [],
4438
+ runtimeParsers: [],
4439
+ needsCollator: false,
4440
+ namedParsers: /* @__PURE__ */ new Map(),
4441
+ triviaCaptureNames: /* @__PURE__ */ new Map(),
4442
+ triviaFnNames: /* @__PURE__ */ new Map(),
4443
+ namedFnDecls: [],
4444
+ capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4445
+ lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4446
+ ns
4447
+ };
4448
+ const seen = /* @__PURE__ */ new Map();
4449
+ const ruleNames = /* @__PURE__ */ new Map();
4450
+ const fnNameToKey = /* @__PURE__ */ new Map();
4451
+ for (const [key, rule] of ruleMap) {
4452
+ const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4453
+ const n = seen.get(base) ?? 0;
4454
+ seen.set(base, n + 1);
4455
+ const fn = n === 0 ? base : `${base}$${n}`;
4456
+ ruleNames.set(rule, fn);
4457
+ fnNameToKey.set(fn, key);
4458
+ }
4459
+ ctx.ruleNames = ruleNames;
4460
+ {
4461
+ const scanned = /* @__PURE__ */ new Set();
4462
+ const scanExternal = (p) => {
4463
+ if (scanned.has(p)) return;
4464
+ scanned.add(p);
4465
+ const def = p._def;
4466
+ if (def.tag === "lazy") {
4467
+ let resolved;
4468
+ try {
4469
+ resolved = def.thunk();
4470
+ } catch {
4471
+ resolved = void 0;
4472
+ }
4473
+ if (resolved === void 0) {
4474
+ const name = p._ruleName;
4475
+ if (name && !ctx.namedParsers.has(p)) {
4476
+ const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4477
+ ruleNames.set(p, fn);
4478
+ ctx.namedParsers.set(p, fn);
4479
+ }
4480
+ return;
4481
+ }
4482
+ scanExternal(resolved);
4483
+ return;
4484
+ }
4485
+ for (const child of childrenOf(def)) scanExternal(child);
4486
+ };
4487
+ for (const [, rule] of ruleMap) scanExternal(rule);
4488
+ }
4489
+ const perEntry = [];
4490
+ for (const [key, rule] of ruleMap) {
4491
+ const fn = ruleNames.get(rule);
4492
+ if (!ctx.namedParsers.has(rule)) {
4493
+ ctx.namedParsers.set(rule, fn);
4494
+ let resolved = rule;
4495
+ const def = rule._def;
4496
+ if (def.tag === "lazy") {
4497
+ try {
4498
+ resolved = def.thunk();
4499
+ } catch {
4500
+ return null;
4501
+ }
4502
+ }
4503
+ const savedIndent = ctx.indent, savedFail = ctx.failLabel, savedRec = ctx.recordFail;
4504
+ ctx.indent = 1;
4505
+ ctx.failLabel = "_pfail";
4506
+ ctx.recordFail = true;
4507
+ const body = emit(resolved, ctx, "_pos");
4508
+ ctx.indent = savedIndent;
4509
+ ctx.failLabel = savedFail;
4510
+ ctx.recordFail = savedRec;
4511
+ pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
4512
+ }
4513
+ perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
4514
+ }
4515
+ if (ctx.runtimeParsers.length !== 0) return null;
4516
+ const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
4517
+ const buildSrcs = ctx.buildFns.length > 0 && ctx.buildSrcs.every((s) => s !== null) ? ctx.buildSrcs : null;
4518
+ const ruleFns = /* @__PURE__ */ new Map();
4519
+ const privateFns = [];
4520
+ for (const decl of ctx.namedFnDecls) {
4521
+ const fnName = decl.match(/^function (\S+?)\(/)?.[1];
4522
+ const key = fnName ? fnNameToKey.get(fnName) : void 0;
4523
+ if (key !== void 0) ruleFns.set(key, decl);
4524
+ else privateFns.push(decl);
4525
+ }
4526
+ const mfDecl = ctx.mapFns.length === 0 ? "" : mfSrcs ? `const ${mfRef(ctx)} = [${mfSrcs.join(", ")}]` : `const ${mfRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "mf")}]`;
4527
+ const buildDecl = ctx.buildFns.length === 0 ? "" : buildSrcs ? `const ${buildRef(ctx)} = [${buildSrcs.join(", ")}]` : `const ${buildRef(ctx)} = _env[${JSON.stringify(nsp(ctx) + "build")}]`;
4528
+ const prelude = [
4529
+ ...ctx.regexDecls,
4530
+ ...ctx.expectedDecls,
4531
+ mfDecl,
4532
+ buildDecl,
4533
+ ...privateFns
4534
+ ].filter(Boolean);
4535
+ const wrappers = /* @__PURE__ */ new Map();
4536
+ for (const { key, r } of perEntry) {
4537
+ wrappers.set(key, [
4538
+ `function(input, _pos, _ctx) {`,
4539
+ ` let pos = _pos`,
4540
+ ...r.stmts,
4541
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
4542
+ `}`
4543
+ ].join("\n"));
4544
+ }
4545
+ return {
4546
+ ns,
4547
+ keys: perEntry.map((e) => e.key),
4548
+ prelude,
4549
+ ruleFns,
4550
+ wrappers,
4551
+ deps: ruleDependencies(ruleMap),
4552
+ needsEmptyTl: !!ctx.needsEmptyTl,
4553
+ needsCollator: ctx.needsCollator,
4554
+ mfFns: mfSrcs ? [] : ctx.mapFns,
4555
+ buildFns: buildSrcs ? [] : ctx.buildFns
4556
+ };
4557
+ }
3512
4558
  function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
3513
4559
  const bodyLines = [
3514
4560
  ` let pos = _pos`,
@@ -3547,6 +4593,87 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3547
4593
  ].join("\n");
3548
4594
  }
3549
4595
 
4596
+ // src/compiler/linker.ts
4597
+ var _nsCounter = 0;
4598
+ function linkable(rulesMap, ns) {
4599
+ const pieces = compileLinkable([...Object.entries(rulesMap)], ns ?? `_lk${_nsCounter++}_`);
4600
+ if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
4601
+ return pieces;
4602
+ }
4603
+ function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
4604
+ return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
4605
+ }
4606
+ function pick(grammar, names) {
4607
+ const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
4608
+ const keep = /* @__PURE__ */ new Set();
4609
+ const has = new Set(p.keys);
4610
+ for (const n of names) {
4611
+ if (!has.has(n)) throw new Error(`pick: rule "${n}" is not in this grammar (available: ${p.keys.join(", ")})`);
4612
+ }
4613
+ const visit = (n) => {
4614
+ if (keep.has(n) || !has.has(n)) return;
4615
+ keep.add(n);
4616
+ for (const d of p.deps.get(n) ?? []) visit(d);
4617
+ };
4618
+ for (const n of names) visit(n);
4619
+ const filt = (m) => new Map([...m].filter(([k]) => keep.has(k)));
4620
+ return {
4621
+ ...p,
4622
+ keys: [...keep],
4623
+ ruleFns: filt(p.ruleFns),
4624
+ wrappers: filt(p.wrappers),
4625
+ deps: filt(p.deps)
4626
+ };
4627
+ }
4628
+ function fusedBody(pieces) {
4629
+ const winner = /* @__PURE__ */ new Map();
4630
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4631
+ for (const [k, p] of winner) {
4632
+ for (const d of p.deps.get(k) ?? []) {
4633
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4634
+ }
4635
+ }
4636
+ const contributing = new Set(winner.values());
4637
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4638
+ const needsCollator = [...contributing].some((p) => p.needsCollator);
4639
+ const lines = [
4640
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4641
+ "const _pfFail = {}",
4642
+ "let _pfEnd",
4643
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4644
+ ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4645
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4646
+ ...[...contributing].flatMap((p) => p.prelude),
4647
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4648
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4649
+ ];
4650
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4651
+ const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4652
+ const env = {};
4653
+ for (const p of contributing) {
4654
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4655
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4656
+ }
4657
+ return { body, env };
4658
+ }
4659
+ function fuseRules(pieces) {
4660
+ const { body, env } = fusedBody(pieces);
4661
+ return new Function("_env", body)(env);
4662
+ }
4663
+ var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
4664
+ function itemPieces(item) {
4665
+ const carried = item[COMPOSED_PIECES];
4666
+ if (Array.isArray(carried)) return carried;
4667
+ if (item.ruleFns instanceof Map) return [item];
4668
+ return [linkable(item)];
4669
+ }
4670
+ function compose(items) {
4671
+ const pieces = items.flatMap(itemPieces);
4672
+ const map = fuseRules(pieces);
4673
+ Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
4674
+ return map;
4675
+ }
4676
+
3550
4677
  // src/combinators/guard.ts
3551
4678
  function guard(predicate) {
3552
4679
  const meta = {
@@ -3744,6 +4871,7 @@ function manyRecover(item, until) {
3744
4871
  }
3745
4872
 
3746
4873
  // src/functional/doc.ts
4874
+ var REENTRY_MAX_SPAN_FRACTION = 0.5;
3747
4875
  function isNode(x) {
3748
4876
  return typeof x === "object" && x !== null && x._tag === "node";
3749
4877
  }
@@ -3804,7 +4932,48 @@ function graftAndShift(root, path, newNode, delta) {
3804
4932
  children: newChildren
3805
4933
  };
3806
4934
  }
3807
- var FunctionalDocImpl = class _FunctionalDocImpl {
4935
+ function structurallyEqual(a, b) {
4936
+ if (a === b) return true;
4937
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
4938
+ const at = a._tag;
4939
+ const bt = b._tag;
4940
+ if (at !== bt) return false;
4941
+ const as = a.span;
4942
+ const bs = b.span;
4943
+ if (as || bs) {
4944
+ if (!as || !bs || as.start !== bs.start || as.end !== bs.end) return false;
4945
+ }
4946
+ if (at === "leaf" || at === "trivia") {
4947
+ return a.value === b.value;
4948
+ }
4949
+ if (a.type !== b.type) return false;
4950
+ const ac = a.children;
4951
+ const bc = b.children;
4952
+ if (ac || bc) {
4953
+ if (!ac || !bc || ac.length !== bc.length) return false;
4954
+ for (let i = 0; i < ac.length; i++) if (!structurallyEqual(ac[i], bc[i])) return false;
4955
+ }
4956
+ return true;
4957
+ }
4958
+ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produced) {
4959
+ if (!produced.ok) return false;
4960
+ if (boundary >= newInput.length) return true;
4961
+ for (const sentinel of [" ", "\uFFFF"]) {
4962
+ if (newInput[boundary] === sentinel) continue;
4963
+ const probed = newInput.slice(0, boundary) + sentinel.repeat(newInput.length - boundary);
4964
+ const ctx = { trackLines: false, state, build };
4965
+ let r;
4966
+ try {
4967
+ r = ruleFn(probed, start, ctx);
4968
+ } catch {
4969
+ return false;
4970
+ }
4971
+ if (!r.ok || r.span.end !== produced.span.end) return false;
4972
+ if (!structurallyEqual(r.value, produced.value)) return false;
4973
+ }
4974
+ return true;
4975
+ }
4976
+ var ParseDocImpl = class _ParseDocImpl {
3808
4977
  _registry;
3809
4978
  _rootRule;
3810
4979
  _opts;
@@ -3821,7 +4990,7 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3821
4990
  }
3822
4991
  edit(from, to, replacement) {
3823
4992
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
3824
- const reparse = () => makeFunctionalDoc(this._registry, this._rootRule, newInput, this._opts);
4993
+ const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
3825
4994
  if (!this.tree) return reparse();
3826
4995
  const delta = replacement.length - (to - from);
3827
4996
  const found = findContaining(this.tree, from);
@@ -3835,41 +5004,78 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3835
5004
  }
3836
5005
  const rebuild = this._opts.rebuild ?? defaultRebuild;
3837
5006
  for (const { node: node2, path } of candidates) {
5007
+ if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
3838
5008
  const ruleFn = this._registry[node2.type];
3839
5009
  if (!ruleFn) continue;
3840
- const ctx = { trackLines: false, state: node2.state };
5010
+ if (!(node2.span.start <= from && to <= node2.span.end)) continue;
5011
+ const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
3841
5012
  const r = ruleFn(newInput, node2.span.start, ctx);
3842
5013
  if (!r.ok) continue;
3843
- if (r.span.end === node2.span.end + delta) {
3844
- if (delta === 0) {
3845
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
3846
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3847
- }
3848
- if (this._opts.rebuild) return reparse();
3849
- const newTree = graftAndShift(this.tree, path, r.value, delta);
3850
- return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
5014
+ if (r.span.end !== node2.span.end + delta) continue;
5015
+ if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
5016
+ continue;
5017
+ }
5018
+ if (delta === 0) {
5019
+ const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
5020
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3851
5021
  }
5022
+ if (this._opts.rebuild) return reparse();
5023
+ const newTree = graftAndShift(this.tree, path, r.value, delta);
5024
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
3852
5025
  }
3853
5026
  return reparse();
3854
5027
  }
3855
5028
  };
3856
- function makeFunctionalDoc(registry, rootRule, input, opts = {}) {
5029
+ function parseDoc(registry, rootRule, input, opts = {}) {
3857
5030
  const ruleFn = registry[rootRule];
3858
5031
  if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
3859
- const ctx = { trackLines: false, state: opts.state };
5032
+ const ctx = { trackLines: false, state: opts.state, build: opts.build };
3860
5033
  const r = ruleFn(input, 0, ctx);
3861
5034
  if (r.ok) {
3862
- return new FunctionalDocImpl(registry, rootRule, opts, r.value, [], input);
5035
+ return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
5036
+ }
5037
+ return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
5038
+ }
5039
+
5040
+ // src/functional/run.ts
5041
+ var invoke = (r, input, pos, ctx) => typeof r === "function" ? r(input, pos, ctx) : r.parse(input, pos, ctx);
5042
+ function run(entry, input, options = {}) {
5043
+ const triviaLog = [];
5044
+ const errors = [];
5045
+ const ctx = {
5046
+ trackLines: false,
5047
+ _triviaLog: triviaLog,
5048
+ _errors: errors,
5049
+ build: options.build,
5050
+ state: options.state
5051
+ };
5052
+ const r = invoke(entry, input, 0, ctx);
5053
+ let unconsumedFrom = null;
5054
+ if (r.ok) {
5055
+ let pos = r.span?.end ?? 0;
5056
+ if (options.trivia && pos < input.length) {
5057
+ const t = invoke(options.trivia, input, pos, { trackLines: false });
5058
+ if (t.ok && t.span.end > pos) pos = t.span.end;
5059
+ }
5060
+ unconsumedFrom = pos < input.length ? pos : null;
3863
5061
  }
3864
- return new FunctionalDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
5062
+ return {
5063
+ ok: r.ok,
5064
+ value: r.ok ? r.value : void 0,
5065
+ span: r.span ?? { start: 0, end: 0 },
5066
+ expected: r.ok ? [] : r.expected ?? [],
5067
+ errors,
5068
+ triviaLog,
5069
+ unconsumedFrom
5070
+ };
3865
5071
  }
3866
5072
 
3867
5073
  // src/cst/trivia-index.ts
3868
- function merge(map, key, run) {
3869
- if (run.length === 0) return;
5074
+ function merge(map, key, run2) {
5075
+ if (run2.length === 0) return;
3870
5076
  const existing = map.get(key);
3871
- if (existing) existing.push(...run);
3872
- else map.set(key, [...run]);
5077
+ if (existing) existing.push(...run2);
5078
+ else map.set(key, [...run2]);
3873
5079
  }
3874
5080
  function buildTriviaIndex(root, input = "", opts) {
3875
5081
  const before = /* @__PURE__ */ new Map();
@@ -3881,11 +5087,11 @@ function buildTriviaIndex(root, input = "", opts) {
3881
5087
  for (let i = 0; i < log.length; i += 3) {
3882
5088
  const tStart = log[i], tEnd = log[i + 1], insertIdx = log[i + 2];
3883
5089
  if (tEnd <= tStart) continue;
3884
- const run = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
5090
+ const run2 = [{ value: input.slice(tStart, tEnd), span: { start: tStart, end: tEnd } }];
3885
5091
  const next = raw[insertIdx];
3886
- if (next) merge(before, next.span.start, run);
5092
+ if (next) merge(before, next.span.start, run2);
3887
5093
  const prev = insertIdx > 0 ? raw[insertIdx - 1] : void 0;
3888
- if (prev) merge(after, prev.span.end, run);
5094
+ if (prev) merge(after, prev.span.end, run2);
3889
5095
  }
3890
5096
  }
3891
5097
  const ch = node2.children;
@@ -3902,17 +5108,17 @@ function buildTriviaIndex(root, input = "", opts) {
3902
5108
  const rootSpan = root?.span;
3903
5109
  const re = new RegExp(opts.trivia.source, opts.trivia.flags.replace(/[gy]/g, "") + "y");
3904
5110
  const scanFrom = (from) => {
3905
- const run = [];
5111
+ const run2 = [];
3906
5112
  let pos = from;
3907
5113
  re.lastIndex = pos;
3908
5114
  let m = re.exec(input);
3909
5115
  while (m && m.index === pos && m[0].length > 0) {
3910
- run.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
5116
+ run2.push({ value: m[0], span: { start: pos, end: pos + m[0].length } });
3911
5117
  pos += m[0].length;
3912
5118
  re.lastIndex = pos;
3913
5119
  m = re.exec(input);
3914
5120
  }
3915
- return run;
5121
+ return run2;
3916
5122
  };
3917
5123
  if (rootSpan && rootSpan.start > 0) {
3918
5124
  const lead = scanFrom(0);
@@ -3998,4 +5204,263 @@ function triviaEntries(log, labels, opts) {
3998
5204
  }
3999
5205
  };
4000
5206
  }
5207
+
5208
+ // src/cst/offset-model.ts
5209
+ var { isFinite } = Number;
5210
+ function floorIndex(arr, len, x) {
5211
+ let lo = 0;
5212
+ let hi = len - 1;
5213
+ let ans = -1;
5214
+ while (lo <= hi) {
5215
+ const mid = lo + hi >>> 1;
5216
+ if (arr[mid] <= x) {
5217
+ ans = mid;
5218
+ lo = mid + 1;
5219
+ } else {
5220
+ hi = mid - 1;
5221
+ }
5222
+ }
5223
+ return ans;
5224
+ }
5225
+ var OffsetIndex = class {
5226
+ /** Slot starts, sorted ascending. Parallel to `_ends`. */
5227
+ _starts;
5228
+ _ends;
5229
+ count;
5230
+ docStart;
5231
+ docEnd;
5232
+ constructor(slots, docStart = 0, docEnd) {
5233
+ const sorted = [...slots].sort((a, b) => a.start - b.start);
5234
+ const starts = [];
5235
+ const ends = [];
5236
+ let prevEnd = docStart;
5237
+ for (const s of sorted) {
5238
+ if (!isFinite(s.start) || !isFinite(s.end) || s.end < s.start) {
5239
+ throw new RangeError(`OffsetIndex: invalid slot [${s.start}, ${s.end}]`);
5240
+ }
5241
+ if (s.start < prevEnd) {
5242
+ throw new RangeError(`OffsetIndex: overlapping/out-of-order slot [${s.start}, ${s.end}] after end ${prevEnd}`);
5243
+ }
5244
+ starts.push(s.start);
5245
+ ends.push(s.end);
5246
+ prevEnd = s.end;
5247
+ }
5248
+ this._starts = starts;
5249
+ this._ends = ends;
5250
+ this.count = starts.length;
5251
+ this.docStart = docStart;
5252
+ this.docEnd = docEnd ?? (this.count ? Math.max(prevEnd, ends[ends.length - 1]) : docStart);
5253
+ }
5254
+ /** The slot at index `i` (0-based, source order), or undefined. */
5255
+ slot(i) {
5256
+ if (i < 0 || i >= this.count) return void 0;
5257
+ return { start: this._starts[i], end: this._ends[i] };
5258
+ }
5259
+ /**
5260
+ * Index of the gap that CONTAINS `offset`, in `[0, count]`. Gap `i` spans
5261
+ * `[endOf(i-1), startOf(i))` where `endOf(-1)=docStart` and `startOf(count)=docEnd`.
5262
+ * An offset that lands inside a slot returns that slot's TRAILING gap index
5263
+ * (i.e. the gap after it) — callers querying at slot boundaries get the
5264
+ * intuitive answer via `gapBefore`/`gapAfter`.
5265
+ */
5266
+ gapIndexAt(offset) {
5267
+ const i = floorIndex(this._starts, this.count, offset);
5268
+ if (i < 0) return 0;
5269
+ if (offset < this._ends[i]) return i + 1;
5270
+ return i + 1;
5271
+ }
5272
+ /** The gap at index `i` in `[0, count]`. */
5273
+ gap(i) {
5274
+ if (i < 0 || i > this.count) return void 0;
5275
+ const start = i === 0 ? this.docStart : this._ends[i - 1];
5276
+ const end = i === this.count ? this.docEnd : this._starts[i];
5277
+ return { start, end };
5278
+ }
5279
+ /**
5280
+ * The trivia gap immediately BEFORE `offset` (`[prevSlotEnd, offset)`), where
5281
+ * `prevSlotEnd` is the end of the last slot ending at or before `offset` (or
5282
+ * `docStart`). For a real slot start this is the gap preceding that slot; for an
5283
+ * adjacent slot the gap is empty and agrees with `gapAfter` on the same seam.
5284
+ */
5285
+ gapBefore(offset) {
5286
+ const e = floorIndex(this._ends, this.count, offset);
5287
+ const start = e >= 0 ? this._ends[e] : this.docStart;
5288
+ return { start, end: offset };
5289
+ }
5290
+ /**
5291
+ * The trivia gap immediately AFTER `offset` (`[offset, nextSlotStart)`), where
5292
+ * `nextSlotStart` is the least slot start at or after `offset` (or `docEnd`).
5293
+ */
5294
+ gapAfter(offset) {
5295
+ return { start: offset, end: this._ceilStart(offset) };
5296
+ }
5297
+ /** Least slot start `>= offset`, else `docEnd`. */
5298
+ _ceilStart(offset) {
5299
+ let lo = 0;
5300
+ let hi = this.count - 1;
5301
+ let ans = this.docEnd;
5302
+ while (lo <= hi) {
5303
+ const mid = lo + hi >>> 1;
5304
+ if (this._starts[mid] >= offset) {
5305
+ ans = this._starts[mid];
5306
+ hi = mid - 1;
5307
+ } else {
5308
+ lo = mid + 1;
5309
+ }
5310
+ }
5311
+ return ans;
5312
+ }
5313
+ /** Is this gap empty (adjacent slots, no trivia)? */
5314
+ isEmpty(gap) {
5315
+ return gap.end <= gap.start;
5316
+ }
5317
+ };
5318
+ var NL = 10;
5319
+ var CR = 13;
5320
+ var SP = 32;
5321
+ var TAB = 9;
5322
+ function gapText(source, gap) {
5323
+ return gap.end > gap.start ? source.slice(gap.start, gap.end) : "";
5324
+ }
5325
+ function lineBreaksIn(source, gap) {
5326
+ let n = 0;
5327
+ for (let i = gap.start; i < gap.end; i++) {
5328
+ const c = source.charCodeAt(i);
5329
+ if (c === NL) n++;
5330
+ else if (c === CR && source.charCodeAt(i + 1) !== NL) n++;
5331
+ }
5332
+ return n;
5333
+ }
5334
+ function blankLinesIn(source, gap) {
5335
+ return Math.max(0, lineBreaksIn(source, gap) - 1);
5336
+ }
5337
+ function lineStartWithin(source, gap) {
5338
+ for (let i = gap.end - 1; i >= gap.start; i--) {
5339
+ const c = source.charCodeAt(i);
5340
+ if (c === NL || c === CR) return i + 1;
5341
+ }
5342
+ return gap.start;
5343
+ }
5344
+ function indentWidth(source, gap) {
5345
+ const ls = lineStartWithin(source, gap);
5346
+ if (ls === gap.start) return 0;
5347
+ return gap.end - ls;
5348
+ }
5349
+ function indentMixed(source, gap) {
5350
+ const ls = lineStartWithin(source, gap);
5351
+ let sawSpace = false;
5352
+ let sawTab = false;
5353
+ for (let i = ls; i < gap.end; i++) {
5354
+ const c = source.charCodeAt(i);
5355
+ if (c === SP) sawSpace = true;
5356
+ else if (c === TAB) sawTab = true;
5357
+ }
5358
+ return sawSpace && sawTab;
5359
+ }
5360
+ function commentsIn(source, gap, comment) {
5361
+ const re = new RegExp(comment.source, comment.flags.replace(/[gy]/g, "") + "g");
5362
+ re.lastIndex = gap.start;
5363
+ const out = [];
5364
+ let m;
5365
+ while ((m = re.exec(source)) && m.index < gap.end) {
5366
+ if (m[0].length === 0) {
5367
+ re.lastIndex++;
5368
+ continue;
5369
+ }
5370
+ if (m.index + m[0].length > gap.end) break;
5371
+ out.push({ start: m.index, end: m.index + m[0].length });
5372
+ }
5373
+ return out;
5374
+ }
5375
+ function gapIsSignificant(source, gap, comment) {
5376
+ if (gap.end <= gap.start) return false;
5377
+ if (lineBreaksIn(source, gap) > 0) return true;
5378
+ if (comment && commentsIn(source, gap, comment).length > 0) return true;
5379
+ return false;
5380
+ }
5381
+ function collectLeafSlots(root) {
5382
+ const slots = [];
5383
+ const visit = (n) => {
5384
+ const kids = n.children;
5385
+ if (kids && kids.length) {
5386
+ for (const c of kids) visit(c);
5387
+ } else if (n.span) {
5388
+ slots.push({ start: n.span.start, end: n.span.end });
5389
+ }
5390
+ };
5391
+ if (root) visit(root);
5392
+ return slots;
5393
+ }
5394
+ function buildOffsetIndex(root, input = "") {
5395
+ return new OffsetIndex(collectLeafSlots(root), 0, input.length);
5396
+ }
5397
+
5398
+ // src/cst/relative-spans.ts
5399
+ function relativize(node2, parentBase = 0) {
5400
+ return {
5401
+ start: node2.start - parentBase,
5402
+ end: node2.end - parentBase,
5403
+ children: node2.children.map((c) => relativize(c, node2.start))
5404
+ };
5405
+ }
5406
+ function absolutize(node2, parentBase = 0) {
5407
+ const start = parentBase + node2.start;
5408
+ return {
5409
+ start,
5410
+ end: parentBase + node2.end,
5411
+ children: node2.children.map((c) => absolutize(c, start))
5412
+ };
5413
+ }
5414
+ function absoluteSpanAt(root, path) {
5415
+ let base = 0;
5416
+ let node2 = root;
5417
+ for (const idx of path) {
5418
+ base += node2.start;
5419
+ const child = node2.children[idx];
5420
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
5421
+ node2 = child;
5422
+ }
5423
+ return { start: base + node2.start, end: base + node2.end };
5424
+ }
5425
+ function shiftAbsolute(node2, at, delta) {
5426
+ const s = node2.start >= at ? node2.start + delta : node2.start;
5427
+ const e = node2.end >= at ? node2.end + delta : node2.end;
5428
+ return {
5429
+ start: s,
5430
+ end: e,
5431
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
5432
+ };
5433
+ }
5434
+ function applyEdit(root, at, delta, stats) {
5435
+ return editNode(root, at, delta, 0, 0, stats);
5436
+ }
5437
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
5438
+ const absStart = parentBase + node2.start;
5439
+ const absEnd = parentBase + node2.end;
5440
+ if (absEnd < at) return node2;
5441
+ const startShifts = absStart >= at;
5442
+ if (startShifts && parentShift === delta) return node2;
5443
+ if (stats) stats.allocated++;
5444
+ const newAbsStart = startShifts ? absStart + delta : absStart;
5445
+ const newAbsEnd = absEnd + delta;
5446
+ const newParentBase = parentBase + parentShift;
5447
+ const newStart = newAbsStart - newParentBase;
5448
+ const newEnd = newAbsEnd - newParentBase;
5449
+ const ownShift = newAbsStart - absStart;
5450
+ let changed = false;
5451
+ const newChildren = [];
5452
+ for (const c of node2.children) {
5453
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
5454
+ if (nc !== c) changed = true;
5455
+ newChildren.push(nc);
5456
+ }
5457
+ return {
5458
+ start: newStart,
5459
+ end: newEnd,
5460
+ children: changed ? newChildren : node2.children
5461
+ };
5462
+ }
5463
+
5464
+ // src/index.ts
5465
+ registerRegexAnalyzer(firstSetFromRegex);
4001
5466
  //# sourceMappingURL=index.cjs.map