parseman 0.23.0 → 0.25.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.
package/dist/index.cjs CHANGED
@@ -22,7 +22,9 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  OffsetIndex: () => OffsetIndex,
24
24
  absoluteSpanAt: () => absoluteSpanAt,
25
+ absoluteSpanCST: () => absoluteSpanCST,
25
26
  absolutize: () => absolutize,
27
+ absolutizeCST: () => absolutizeCST,
26
28
  annotateSpan: () => annotateSpan,
27
29
  applyEdit: () => applyEdit,
28
30
  balanced: () => balanced,
@@ -67,6 +69,7 @@ __export(index_exports, {
67
69
  ref: () => ref,
68
70
  regex: () => regex,
69
71
  relativize: () => relativize,
72
+ relativizeCST: () => relativizeCST,
70
73
  rules: () => rules,
71
74
  run: () => run,
72
75
  scanTo: () => scanTo,
@@ -431,24 +434,35 @@ function literal(value, opts = {}) {
431
434
  { lo: firstUpper, hi: firstUpper }
432
435
  ] } : firstSet2;
433
436
  }
437
+ const expected = [JSON.stringify(value)];
438
+ const parse2 = !caseInsensitive && value.length === 1 ? (() => {
439
+ const code = value.charCodeAt(0);
440
+ return function parse3(input, pos, ctx) {
441
+ if (input.charCodeAt(pos) === code) {
442
+ const span = { start: pos, end: pos + 1 };
443
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span });
444
+ return { ok: true, value, span };
445
+ }
446
+ return failAt(ctx, expected, pos);
447
+ };
448
+ })() : function parse3(input, pos, ctx) {
449
+ const end = pos + value.length;
450
+ if (end > input.length) {
451
+ return failAt(ctx, expected, pos);
452
+ }
453
+ const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
454
+ if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
455
+ const span = { start: pos, end };
456
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value: matchedValue, span });
457
+ return { ok: true, value: matchedValue, span };
458
+ }
459
+ return failAt(ctx, expected, pos);
460
+ };
434
461
  return {
435
462
  _tag: "literal",
436
463
  _meta: meta,
437
464
  _def: { tag: "literal", value, caseInsensitive },
438
- parse(input, pos, ctx) {
439
- const end = pos + value.length;
440
- if (end > input.length) {
441
- return failAt(ctx, [JSON.stringify(value)], pos);
442
- }
443
- const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
444
- if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
445
- const span = { start: pos, end };
446
- const leaf = { _tag: "leaf", value: matchedValue, span };
447
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
448
- return { ok: true, value: matchedValue, span };
449
- }
450
- return failAt(ctx, [JSON.stringify(value)], pos);
451
- }
465
+ parse: parse2
452
466
  };
453
467
  }
454
468
 
@@ -583,8 +597,7 @@ function regex(pattern, flags = "") {
583
597
  if (scanEnd === null) return failAt(ctx, [`/${source}/`], pos);
584
598
  const value = input.slice(pos, scanEnd);
585
599
  const span2 = { start: pos, end: scanEnd };
586
- const leaf2 = { _tag: "leaf", value, span: span2 };
587
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf2);
600
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span: span2 });
588
601
  return { ok: true, value, span: span2 };
589
602
  }
590
603
  anchored.lastIndex = pos;
@@ -593,8 +606,7 @@ function regex(pattern, flags = "") {
593
606
  return failAt(ctx, [`/${source}/`], pos);
594
607
  }
595
608
  const span = { start: pos, end: pos + m[0].length };
596
- const leaf = { _tag: "leaf", value: m[0], span };
597
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
609
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value: m[0], span });
598
610
  return { ok: true, value: m[0], span };
599
611
  }
600
612
  };
@@ -902,8 +914,7 @@ function keywords(words, opts = {}) {
902
914
  }
903
915
  const value = m[0];
904
916
  const span = { start: pos, end: pos + value.length };
905
- const leaf = { _tag: "leaf", value, span };
906
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
917
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span });
907
918
  return { ok: true, value, span };
908
919
  }
909
920
  };
@@ -1393,33 +1404,136 @@ function loopScanner(arms) {
1393
1404
  }
1394
1405
  function regexTriviaScanner(parser2) {
1395
1406
  if (parser2._def.tag !== "regex" || parser2._def.flags) return null;
1396
- const ws = whitespaceSource(parser2._def.source);
1397
- if (ws) return ws;
1398
- return blockCommentSource(parser2._def.source) ? scanBlockComment : null;
1407
+ const source = parser2._def.source;
1408
+ return classRunSource(source) ?? altStarSource(source) ?? (blockCommentSource(source) ? scanBlockComment : null);
1399
1409
  }
1400
- function whitespaceSource(source) {
1401
- const m = /^\[([ \\tnrf]+)\][*+]$/.exec(source);
1402
- if (!m) return null;
1403
- const chars = /* @__PURE__ */ new Set();
1404
- for (let i = 0; i < m[1].length; i++) {
1405
- const ch = m[1][i];
1406
- if (ch !== "\\") {
1407
- chars.add(ch.charCodeAt(0));
1408
- continue;
1410
+ function inRanges2(cp, ranges) {
1411
+ for (let i = 0; i < ranges.length; i++) {
1412
+ const r = ranges[i];
1413
+ if (cp >= r[0] && cp <= r[1]) return true;
1414
+ }
1415
+ return false;
1416
+ }
1417
+ function classScanner(classBody) {
1418
+ const ranges = parseClassRanges(classBody);
1419
+ if (!ranges) return null;
1420
+ return (input, cur) => {
1421
+ let pos = cur;
1422
+ while (pos < input.length && inRanges2(input.charCodeAt(pos), ranges)) pos++;
1423
+ return pos;
1424
+ };
1425
+ }
1426
+ function lineCommentScanner(commentCode) {
1427
+ return (input, cur) => {
1428
+ if (input.charCodeAt(cur) !== commentCode) return cur;
1429
+ let pos = cur + 1;
1430
+ while (pos < input.length) {
1431
+ const cc = input.charCodeAt(pos);
1432
+ if (cc === 10 || cc === 13) break;
1433
+ pos++;
1409
1434
  }
1410
- const esc = m[1][++i];
1411
- if (esc === "t") chars.add(9);
1412
- else if (esc === "n") chars.add(10);
1413
- else if (esc === "r") chars.add(13);
1414
- else if (esc === "f") chars.add(12);
1415
- else return null;
1435
+ return pos;
1436
+ };
1437
+ }
1438
+ function classRunSource(source) {
1439
+ const m = /^\[([^\]^](?:[^\]]|\\.)*)\][*+]$/.exec(source);
1440
+ return m ? classScanner(m[1]) : null;
1441
+ }
1442
+ function classifyTriviaArm(arm) {
1443
+ const cls = /^\[([^\]^](?:[^\]]|\\.)*)\][*+]?$/.exec(arm);
1444
+ if (cls) {
1445
+ const ranges = parseClassRanges(cls[1]);
1446
+ return ranges ? { kind: "class", ranges } : null;
1416
1447
  }
1448
+ const lc = /^(\\?.)\[\^\\n\\r\]\*$/.exec(arm);
1449
+ if (lc) {
1450
+ const marker = lc[1];
1451
+ return { kind: "comment", code: (marker.length === 2 ? marker[1] : marker[0]).charCodeAt(0) };
1452
+ }
1453
+ return null;
1454
+ }
1455
+ function armScanner(arm) {
1456
+ if (arm.kind === "comment") return lineCommentScanner(arm.code);
1457
+ const ranges = arm.ranges;
1417
1458
  return (input, cur) => {
1418
1459
  let pos = cur;
1419
- while (pos < input.length && chars.has(input.charCodeAt(pos))) pos++;
1460
+ while (pos < input.length && inRanges2(input.charCodeAt(pos), ranges)) pos++;
1420
1461
  return pos;
1421
1462
  };
1422
1463
  }
1464
+ function fusedTriviaScanner(ranges, commentCodes) {
1465
+ const c0 = commentCodes[0];
1466
+ const single = commentCodes.length === 1;
1467
+ return (input, cur) => {
1468
+ let pos = cur;
1469
+ const len = input.length;
1470
+ for (; ; ) {
1471
+ const c = input.charCodeAt(pos);
1472
+ if (pos < len && inRanges2(c, ranges)) {
1473
+ pos++;
1474
+ continue;
1475
+ }
1476
+ if (single ? c === c0 : commentCodes.includes(c)) {
1477
+ pos++;
1478
+ while (pos < len) {
1479
+ const cc = input.charCodeAt(pos);
1480
+ if (cc === 10 || cc === 13) break;
1481
+ pos++;
1482
+ }
1483
+ continue;
1484
+ }
1485
+ break;
1486
+ }
1487
+ return pos;
1488
+ };
1489
+ }
1490
+ function splitTopLevelAlts(body) {
1491
+ const arms = [];
1492
+ let inClass = false;
1493
+ let start = 0;
1494
+ for (let i = 0; i < body.length; i++) {
1495
+ const c = body[i];
1496
+ if (c === "\\") {
1497
+ i++;
1498
+ continue;
1499
+ }
1500
+ if (inClass) {
1501
+ if (c === "]") inClass = false;
1502
+ continue;
1503
+ }
1504
+ if (c === "[") inClass = true;
1505
+ else if (c === "(") return null;
1506
+ else if (c === "|") {
1507
+ arms.push(body.slice(start, i));
1508
+ start = i + 1;
1509
+ }
1510
+ }
1511
+ arms.push(body.slice(start));
1512
+ return arms;
1513
+ }
1514
+ function altStarSource(source) {
1515
+ const m = /^\(\?:(.*)\)[*+]$/.exec(source);
1516
+ if (!m) return null;
1517
+ const armSrcs = splitTopLevelAlts(m[1]);
1518
+ if (!armSrcs || armSrcs.length < 2) return null;
1519
+ const arms = [];
1520
+ for (const src of armSrcs) {
1521
+ const arm = classifyTriviaArm(src);
1522
+ if (!arm) return null;
1523
+ arms.push(arm);
1524
+ }
1525
+ const ranges = [];
1526
+ const commentCodes = [];
1527
+ for (const arm of arms) {
1528
+ if (arm.kind === "class") ranges.push(...arm.ranges);
1529
+ else commentCodes.push(arm.code);
1530
+ }
1531
+ if (commentCodes.some((code) => inRanges2(code, ranges))) {
1532
+ return loopScanner(arms.map(armScanner));
1533
+ }
1534
+ if (commentCodes.length === 0) return armScanner({ kind: "class", ranges });
1535
+ return fusedTriviaScanner(ranges, commentCodes);
1536
+ }
1423
1537
  function blockCommentSource(source) {
1424
1538
  return source === "\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/" || source === "\\/\\*[^]*?\\*\\/";
1425
1539
  }
@@ -1773,6 +1887,7 @@ function markUnusedValues(root) {
1773
1887
  }
1774
1888
 
1775
1889
  // src/combinators/parser.ts
1890
+ var RULE_ORDER = "__parsemanRuleOrder";
1776
1891
  function tagRule(r, key) {
1777
1892
  ;
1778
1893
  r._ruleName = key;
@@ -1817,6 +1932,11 @@ function rules(a, b) {
1817
1932
  for (const key of Object.keys(definitions)) {
1818
1933
  markUnusedValues(cache[key]);
1819
1934
  }
1935
+ Object.defineProperty(cache, RULE_ORDER, {
1936
+ value: Object.keys(definitions),
1937
+ enumerable: false,
1938
+ configurable: true
1939
+ });
1820
1940
  return cache;
1821
1941
  }
1822
1942
 
@@ -6364,17 +6484,159 @@ function manyRecover(item, until) {
6364
6484
  return many(tolerantElement(item, until, until));
6365
6485
  }
6366
6486
 
6487
+ // src/cst/relative-spans.ts
6488
+ function relativize(node2, parentBase = 0) {
6489
+ return {
6490
+ start: node2.start - parentBase,
6491
+ end: node2.end - parentBase,
6492
+ children: node2.children.map((c) => relativize(c, node2.start))
6493
+ };
6494
+ }
6495
+ function absolutize(node2, parentBase = 0) {
6496
+ const start = parentBase + node2.start;
6497
+ return {
6498
+ start,
6499
+ end: parentBase + node2.end,
6500
+ children: node2.children.map((c) => absolutize(c, start))
6501
+ };
6502
+ }
6503
+ function absoluteSpanAt(root, path) {
6504
+ let base = 0;
6505
+ let node2 = root;
6506
+ for (const idx of path) {
6507
+ base += node2.start;
6508
+ const child = node2.children[idx];
6509
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6510
+ node2 = child;
6511
+ }
6512
+ return { start: base + node2.start, end: base + node2.end };
6513
+ }
6514
+ function relativizeCST(node2, parentBase = 0) {
6515
+ const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
6516
+ if (node2.children) {
6517
+ const base = node2.span.start;
6518
+ return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
6519
+ }
6520
+ return { ...node2, span };
6521
+ }
6522
+ function absolutizeCST(node2, parentBase = 0) {
6523
+ const start = parentBase + node2.span.start;
6524
+ const span = { start, end: parentBase + node2.span.end };
6525
+ if (node2.children) {
6526
+ return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
6527
+ }
6528
+ return { ...node2, span };
6529
+ }
6530
+ function absoluteSpanCST(root, path) {
6531
+ let base = 0;
6532
+ let node2 = root;
6533
+ for (const idx of path) {
6534
+ base += node2.span.start;
6535
+ const child = node2.children?.[idx];
6536
+ if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
6537
+ node2 = child;
6538
+ }
6539
+ return { start: base + node2.span.start, end: base + node2.span.end };
6540
+ }
6541
+ function shiftAbsolute(node2, at, delta) {
6542
+ const s = node2.start >= at ? node2.start + delta : node2.start;
6543
+ const e = node2.end >= at ? node2.end + delta : node2.end;
6544
+ return {
6545
+ start: s,
6546
+ end: e,
6547
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6548
+ };
6549
+ }
6550
+ function applyEdit(root, at, delta, stats) {
6551
+ return editNode(root, at, delta, 0, 0, stats);
6552
+ }
6553
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
6554
+ const absStart = parentBase + node2.start;
6555
+ const absEnd = parentBase + node2.end;
6556
+ if (absEnd < at) return node2;
6557
+ const startShifts = absStart >= at;
6558
+ if (startShifts && parentShift === delta) return node2;
6559
+ if (stats) stats.allocated++;
6560
+ const newAbsStart = startShifts ? absStart + delta : absStart;
6561
+ const newAbsEnd = absEnd + delta;
6562
+ const newParentBase = parentBase + parentShift;
6563
+ const newStart = newAbsStart - newParentBase;
6564
+ const newEnd = newAbsEnd - newParentBase;
6565
+ const ownShift = newAbsStart - absStart;
6566
+ let changed = false;
6567
+ const newChildren = [];
6568
+ for (const c of node2.children) {
6569
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
6570
+ if (nc !== c) changed = true;
6571
+ newChildren.push(nc);
6572
+ }
6573
+ return {
6574
+ start: newStart,
6575
+ end: newEnd,
6576
+ children: changed ? newChildren : node2.children
6577
+ };
6578
+ }
6579
+
6367
6580
  // src/functional/doc.ts
6581
+ function asRuleFn(entry) {
6582
+ return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
6583
+ }
6584
+ function defOf(entry) {
6585
+ return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
6586
+ }
6587
+ function producesRepetition(def, depth = 0) {
6588
+ if (!def || depth > 24) return false;
6589
+ const d = def;
6590
+ switch (d.tag) {
6591
+ case "sepBy":
6592
+ case "many":
6593
+ case "oneOrMore":
6594
+ return true;
6595
+ // Rule entries and `ref`s wrap their body in a `lazy` thunk — resolve it. The
6596
+ // `depth` cap bounds any self-referential cycle (a repetition, if present, is
6597
+ // found shallowly before recursion goes deep).
6598
+ case "lazy": {
6599
+ const thunk = d.thunk;
6600
+ let inner;
6601
+ try {
6602
+ inner = typeof thunk === "function" ? thunk() : void 0;
6603
+ } catch {
6604
+ return false;
6605
+ }
6606
+ return producesRepetition(inner?._def, depth + 1);
6607
+ }
6608
+ // Transparent wrappers — look through to the inner parser.
6609
+ case "node":
6610
+ case "transform":
6611
+ case "token":
6612
+ case "label":
6613
+ case "field":
6614
+ case "expect":
6615
+ case "withCtx":
6616
+ case "grammar":
6617
+ case "optional":
6618
+ return producesRepetition(defOf(d.parser), depth + 1);
6619
+ case "skip":
6620
+ return producesRepetition(defOf(d.main), depth + 1);
6621
+ // A bracketed/anchored list is a sequence whose element run is a repetition.
6622
+ case "sequence":
6623
+ return d.parsers.some((p) => producesRepetition(defOf(p), depth + 1));
6624
+ default:
6625
+ return false;
6626
+ }
6627
+ }
6368
6628
  var REENTRY_MAX_SPAN_FRACTION = 0.5;
6369
6629
  function isNode(x) {
6370
6630
  return typeof x === "object" && x !== null && x._tag === "node";
6371
6631
  }
6372
- function findContaining(node2, pos, path = []) {
6632
+ function findContaining(node2, nodeAbsStart, pos, path = []) {
6373
6633
  for (let i = 0; i < node2.children.length; i++) {
6374
6634
  const child = node2.children[i];
6375
6635
  if (!isNode(child)) continue;
6376
- if (child.span.start <= pos && pos < child.span.end) {
6377
- const inner = findContaining(child, pos, [...path, i]);
6636
+ const childAbsStart = nodeAbsStart + child.span.start;
6637
+ const childAbsEnd = nodeAbsStart + child.span.end;
6638
+ if (childAbsStart <= pos && pos < childAbsEnd) {
6639
+ const inner = findContaining(child, childAbsStart, pos, [...path, i]);
6378
6640
  return inner ?? { node: child, path: [...path, i] };
6379
6641
  }
6380
6642
  }
@@ -6401,24 +6663,20 @@ function replaceAtPath(rebuild, root, path, newNode) {
6401
6663
  function defaultRebuild(node2, children) {
6402
6664
  return { ...node2, children };
6403
6665
  }
6404
- function shiftSpans(child, delta) {
6666
+ function shiftRelStart(child, delta) {
6405
6667
  const c = child;
6406
- const span = { start: c.span.start + delta, end: c.span.end + delta };
6407
- if (Array.isArray(c.children)) {
6408
- return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
6409
- }
6410
- return { ...c, span };
6668
+ return { ...c, span: { start: c.span.start + delta, end: c.span.end + delta } };
6411
6669
  }
6412
- function graftAndShift(root, path, newNode, delta) {
6670
+ function graftRelative(root, path, newNode, delta) {
6413
6671
  if (path.length === 0) return newNode;
6414
6672
  const [idx, ...rest] = path;
6415
6673
  const oldChildren = root.children;
6416
6674
  const newChildren = oldChildren.map((child, i) => {
6417
6675
  if (i < idx) return child;
6418
6676
  if (i === idx) {
6419
- return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
6677
+ return rest.length === 0 ? newNode : graftRelative(child, rest, newNode, delta);
6420
6678
  }
6421
- return shiftSpans(child, delta);
6679
+ return shiftRelStart(child, delta);
6422
6680
  });
6423
6681
  return {
6424
6682
  ...root,
@@ -6426,6 +6684,96 @@ function graftAndShift(root, path, newNode, delta) {
6426
6684
  children: newChildren
6427
6685
  };
6428
6686
  }
6687
+ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build) {
6688
+ const ctx = { trackLines: false, state: null, build };
6689
+ const out = [];
6690
+ let pos = reStart;
6691
+ let guard2 = 0;
6692
+ while (pos < reEnd) {
6693
+ if (++guard2 > reEnd - reStart + 2) return null;
6694
+ if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
6695
+ if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
6696
+ out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
6697
+ pos += separator.length;
6698
+ continue;
6699
+ }
6700
+ let r;
6701
+ try {
6702
+ r = ruleFn(input, pos, ctx);
6703
+ } catch {
6704
+ return null;
6705
+ }
6706
+ if (!r.ok || r.span.end <= pos || r.span.end > reEnd) return null;
6707
+ out.push(relativizeCST(r.value, cStart));
6708
+ pos = r.span.end;
6709
+ }
6710
+ return pos === reEnd ? out : null;
6711
+ }
6712
+ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build) {
6713
+ const kids = C.children;
6714
+ if (kids.length === 0) return null;
6715
+ let h = 0;
6716
+ while (h < kids.length && cStart + kids[h].span.end <= from) h++;
6717
+ while (h > 0 && kids[h - 1]._tag === "node") h--;
6718
+ let t = kids.length;
6719
+ while (t > h && cStart + kids[t - 1].span.start >= to) t--;
6720
+ while (t < kids.length && kids[t]._tag !== "node") t++;
6721
+ if (t >= kids.length) return null;
6722
+ for (let i = t; i < kids.length; i++) {
6723
+ if (kids[i]._tag === "node" && kids[i].state != null) return null;
6724
+ }
6725
+ let elemType;
6726
+ let elemCount = 0;
6727
+ for (const k of kids) {
6728
+ if (k._tag !== "node") continue;
6729
+ elemCount++;
6730
+ if (elemType === void 0) elemType = k.type;
6731
+ else if (k.type !== elemType) return null;
6732
+ }
6733
+ if (elemType === void 0 || elemCount < 2) return null;
6734
+ const ruleFn = registry[elemType];
6735
+ if (!ruleFn) return null;
6736
+ let separator = null;
6737
+ for (let i = 1; i < kids.length - 1; i++) {
6738
+ if (kids[i - 1]._tag === "node" && kids[i + 1]._tag === "node") {
6739
+ if (kids[i]._tag !== "leaf") return null;
6740
+ const sep = kids[i].value ?? null;
6741
+ if (separator === null) separator = sep;
6742
+ else if (sep !== separator) return null;
6743
+ }
6744
+ }
6745
+ if (separator === null) return null;
6746
+ const first = kids[0];
6747
+ const last = kids[kids.length - 1];
6748
+ if (first._tag === "leaf" && first.value !== separator && from < cStart + first.span.end) return null;
6749
+ if (last._tag === "leaf" && last.value !== separator && to > cStart + last.span.start) return null;
6750
+ const reStart = h > 0 ? cStart + kids[h - 1].span.end : cStart;
6751
+ const reEnd = cStart + kids[t].span.start + delta;
6752
+ if (reEnd < reStart) return null;
6753
+ const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build);
6754
+ if (!middle) return null;
6755
+ if (middle.length > 0) {
6756
+ const last2 = middle[middle.length - 1];
6757
+ if (!(last2._tag === "leaf" && last2.value === separator)) return null;
6758
+ }
6759
+ if (reEnd < newInput.length) {
6760
+ for (const sentinel of [" ", "\uFFFF"]) {
6761
+ if (newInput[reEnd] === sentinel) continue;
6762
+ const probed = newInput.slice(0, reEnd) + sentinel.repeat(newInput.length - reEnd);
6763
+ const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build);
6764
+ if (!probe || probe.length !== middle.length) return null;
6765
+ for (let i = 0; i < middle.length; i++) if (!structurallyEqual(probe[i], middle[i])) return null;
6766
+ }
6767
+ }
6768
+ const head = kids.slice(0, h);
6769
+ const tail = kids.slice(t).map((k) => shiftRelStart(k, delta));
6770
+ const newC = {
6771
+ ...C,
6772
+ span: { start: C.span.start, end: C.span.end + delta },
6773
+ children: [...head, ...middle, ...tail]
6774
+ };
6775
+ return graftRelative(root, path, newC, delta);
6776
+ }
6429
6777
  function structurallyEqual(a, b) {
6430
6778
  if (a === b) return true;
6431
6779
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
@@ -6469,27 +6817,68 @@ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produce
6469
6817
  }
6470
6818
  var ParseDocImpl = class _ParseDocImpl {
6471
6819
  _registry;
6820
+ /** Registry entries normalized to callable parse functions (memoized once). */
6821
+ _fns;
6822
+ /** Rule names the grammar proves are genuine repetitions — the ONLY splice-safe types. */
6823
+ _reps;
6472
6824
  _rootRule;
6473
6825
  _opts;
6474
- tree;
6826
+ /**
6827
+ * The tree, held in whichever coordinate system it arrived in. A fresh parse
6828
+ * arrives ABSOLUTE (`_abs` set); an incremental graft arrives PARENT-RELATIVE
6829
+ * (`_rel` set). The public `tree` is always the RELATIVE form (so untouched
6830
+ * subtrees stay shared by identity across edits and reads are O(1)); it's
6831
+ * materialized from `_abs` once, on demand, and memoized. `undefined` = not yet
6832
+ * computed; `null` = failed parse. A fresh parse that's never edited nor
6833
+ * tree-read pays no conversion — important for the full-reparse fallback path.
6834
+ */
6835
+ _abs;
6836
+ _rel;
6475
6837
  errors;
6476
6838
  input;
6477
- constructor(registry, rootRule, opts, tree, errors, input) {
6839
+ constructor(registry, rootRule, opts, trees, errors, input) {
6478
6840
  this._registry = registry;
6841
+ this._fns = {};
6842
+ this._reps = /* @__PURE__ */ new Set();
6843
+ for (const [name, entry] of Object.entries(registry)) {
6844
+ this._fns[name] = asRuleFn(entry);
6845
+ if (producesRepetition(defOf(entry))) this._reps.add(name);
6846
+ }
6479
6847
  this._rootRule = rootRule;
6480
6848
  this._opts = opts;
6481
- this.tree = tree;
6849
+ this._abs = trees.abs;
6850
+ this._rel = trees.rel;
6482
6851
  this.errors = errors;
6483
6852
  this.input = input;
6484
6853
  }
6854
+ /**
6855
+ * The parse tree with PARENT-RELATIVE spans — a node's `span` is relative to
6856
+ * its parent's start (root base 0). This is the shareable representation:
6857
+ * untouched subtrees keep the same identity across `.edit()`s, and reading the
6858
+ * tree after a length-changing edit is O(1) (no offset rewrite). For absolute
6859
+ * positions use the O(depth) cursor `spanAt(path)`, or `absolutizeCST(tree)` to
6860
+ * materialize the whole absolute tree. (A fresh non-incremental `node().parse()`
6861
+ * result is unchanged — still absolute.)
6862
+ */
6863
+ get tree() {
6864
+ if (this._rel === void 0) {
6865
+ this._rel = this._abs ? relativizeCST(this._abs, 0) : null;
6866
+ }
6867
+ return this._rel;
6868
+ }
6869
+ spanAt(path) {
6870
+ const rel = this.tree;
6871
+ if (!rel) throw new Error("spanAt on a failed parse (tree is null)");
6872
+ return absoluteSpanCST(rel, path);
6873
+ }
6485
6874
  edit(from, to, replacement) {
6486
6875
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
6487
6876
  const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
6488
- if (!this.tree) return reparse();
6877
+ const root = this.tree;
6878
+ if (!root) return reparse();
6489
6879
  const delta = replacement.length - (to - from);
6490
- const found = findContaining(this.tree, from);
6491
- if (!found) return reparse();
6492
- const ancestors = ancestorsAt(this.tree, found.path);
6880
+ const found = findContaining(root, 0, from) ?? { node: root, path: [] };
6881
+ const ancestors = ancestorsAt(root, found.path);
6493
6882
  const candidates = [found];
6494
6883
  const pathCopy = [...found.path];
6495
6884
  for (let i = ancestors.length - 2; i >= 0; i--) {
@@ -6498,37 +6887,50 @@ var ParseDocImpl = class _ParseDocImpl {
6498
6887
  }
6499
6888
  const rebuild = this._opts.rebuild ?? defaultRebuild;
6500
6889
  for (const { node: node2, path } of candidates) {
6501
- if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6502
- const ruleFn = this._registry[node2.type];
6890
+ const { start: absStart, end: absEnd } = absoluteSpanCST(root, path);
6891
+ if (absEnd - absStart > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6892
+ const ruleFn = this._fns[node2.type];
6503
6893
  if (!ruleFn) continue;
6504
- if (!(node2.span.start <= from && to <= node2.span.end)) continue;
6894
+ if (!(absStart <= from && to <= absEnd)) continue;
6505
6895
  const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
6506
- const r = ruleFn(newInput, node2.span.start, ctx);
6896
+ const r = ruleFn(newInput, absStart, ctx);
6507
6897
  if (!r.ok) continue;
6508
- if (r.span.end !== node2.span.end + delta) continue;
6509
- if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
6898
+ if (r.span.end !== absEnd + delta) continue;
6899
+ if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r)) {
6510
6900
  continue;
6511
6901
  }
6902
+ const parentBase = absStart - node2.span.start;
6903
+ const newRel = relativizeCST(r.value, parentBase);
6512
6904
  if (delta === 0) {
6513
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
6514
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
6905
+ const newTree2 = replaceAtPath(rebuild, root, path, newRel);
6906
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree2 }, [], newInput);
6515
6907
  }
6516
6908
  if (this._opts.rebuild) return reparse();
6517
- const newTree = graftAndShift(this.tree, path, r.value, delta);
6518
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
6909
+ const newTree = graftRelative(root, path, newRel, delta);
6910
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree }, [], newInput);
6911
+ }
6912
+ if (this._opts.structuralReuse && delta !== 0 && !this._opts.rebuild) {
6913
+ const spliceCandidates = [...candidates, { node: root, path: [] }];
6914
+ for (const { node: node2, path } of spliceCandidates) {
6915
+ if (!this._reps.has(node2.type)) continue;
6916
+ const { start: cStart, end: cEnd } = absoluteSpanCST(root, path);
6917
+ if (!(cStart <= from && to <= cEnd)) continue;
6918
+ const spliced = tryListSplice(root, path, node2, cStart, newInput, from, to, delta, this._fns, this._opts.build);
6919
+ if (spliced) return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: spliced }, [], newInput);
6920
+ }
6519
6921
  }
6520
6922
  return reparse();
6521
6923
  }
6522
6924
  };
6523
6925
  function parseDoc(registry, rootRule, input, opts = {}) {
6524
- const ruleFn = registry[rootRule];
6525
- if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
6926
+ const entry = registry[rootRule];
6927
+ if (!entry) throw new Error(`No rule '${rootRule}' in registry`);
6526
6928
  const ctx = { trackLines: false, state: opts.state, build: opts.build };
6527
- const r = ruleFn(input, 0, ctx);
6929
+ const r = asRuleFn(entry)(input, 0, ctx);
6528
6930
  if (r.ok) {
6529
- return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
6931
+ return new ParseDocImpl(registry, rootRule, opts, { abs: r.value }, [], input);
6530
6932
  }
6531
- return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
6933
+ return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [{ ok: false, expected: r.expected, span: r.span }], input);
6532
6934
  }
6533
6935
 
6534
6936
  // src/functional/run.ts
@@ -6897,72 +7299,6 @@ function buildOffsetIndex(root, input = "") {
6897
7299
  return new OffsetIndex(collectLeafSlots(root), 0, input.length);
6898
7300
  }
6899
7301
 
6900
- // src/cst/relative-spans.ts
6901
- function relativize(node2, parentBase = 0) {
6902
- return {
6903
- start: node2.start - parentBase,
6904
- end: node2.end - parentBase,
6905
- children: node2.children.map((c) => relativize(c, node2.start))
6906
- };
6907
- }
6908
- function absolutize(node2, parentBase = 0) {
6909
- const start = parentBase + node2.start;
6910
- return {
6911
- start,
6912
- end: parentBase + node2.end,
6913
- children: node2.children.map((c) => absolutize(c, start))
6914
- };
6915
- }
6916
- function absoluteSpanAt(root, path) {
6917
- let base = 0;
6918
- let node2 = root;
6919
- for (const idx of path) {
6920
- base += node2.start;
6921
- const child = node2.children[idx];
6922
- if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6923
- node2 = child;
6924
- }
6925
- return { start: base + node2.start, end: base + node2.end };
6926
- }
6927
- function shiftAbsolute(node2, at, delta) {
6928
- const s = node2.start >= at ? node2.start + delta : node2.start;
6929
- const e = node2.end >= at ? node2.end + delta : node2.end;
6930
- return {
6931
- start: s,
6932
- end: e,
6933
- children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6934
- };
6935
- }
6936
- function applyEdit(root, at, delta, stats) {
6937
- return editNode(root, at, delta, 0, 0, stats);
6938
- }
6939
- function editNode(node2, at, delta, parentBase, parentShift, stats) {
6940
- const absStart = parentBase + node2.start;
6941
- const absEnd = parentBase + node2.end;
6942
- if (absEnd < at) return node2;
6943
- const startShifts = absStart >= at;
6944
- if (startShifts && parentShift === delta) return node2;
6945
- if (stats) stats.allocated++;
6946
- const newAbsStart = startShifts ? absStart + delta : absStart;
6947
- const newAbsEnd = absEnd + delta;
6948
- const newParentBase = parentBase + parentShift;
6949
- const newStart = newAbsStart - newParentBase;
6950
- const newEnd = newAbsEnd - newParentBase;
6951
- const ownShift = newAbsStart - absStart;
6952
- let changed = false;
6953
- const newChildren = [];
6954
- for (const c of node2.children) {
6955
- const nc = editNode(c, at, delta, absStart, ownShift, stats);
6956
- if (nc !== c) changed = true;
6957
- newChildren.push(nc);
6958
- }
6959
- return {
6960
- start: newStart,
6961
- end: newEnd,
6962
- children: changed ? newChildren : node2.children
6963
- };
6964
- }
6965
-
6966
7302
  // src/index.ts
6967
7303
  registerRegexAnalyzer(firstSetFromRegex);
6968
7304
  //# sourceMappingURL=index.cjs.map