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.js CHANGED
@@ -343,24 +343,35 @@ function literal(value, opts = {}) {
343
343
  { lo: firstUpper, hi: firstUpper }
344
344
  ] } : firstSet2;
345
345
  }
346
+ const expected = [JSON.stringify(value)];
347
+ const parse2 = !caseInsensitive && value.length === 1 ? (() => {
348
+ const code = value.charCodeAt(0);
349
+ return function parse3(input, pos, ctx) {
350
+ if (input.charCodeAt(pos) === code) {
351
+ const span = { start: pos, end: pos + 1 };
352
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span });
353
+ return { ok: true, value, span };
354
+ }
355
+ return failAt(ctx, expected, pos);
356
+ };
357
+ })() : function parse3(input, pos, ctx) {
358
+ const end = pos + value.length;
359
+ if (end > input.length) {
360
+ return failAt(ctx, expected, pos);
361
+ }
362
+ const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
363
+ if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
364
+ const span = { start: pos, end };
365
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value: matchedValue, span });
366
+ return { ok: true, value: matchedValue, span };
367
+ }
368
+ return failAt(ctx, expected, pos);
369
+ };
346
370
  return {
347
371
  _tag: "literal",
348
372
  _meta: meta,
349
373
  _def: { tag: "literal", value, caseInsensitive },
350
- parse(input, pos, ctx) {
351
- const end = pos + value.length;
352
- if (end > input.length) {
353
- return failAt(ctx, [JSON.stringify(value)], pos);
354
- }
355
- const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
356
- if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
357
- const span = { start: pos, end };
358
- const leaf = { _tag: "leaf", value: matchedValue, span };
359
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
360
- return { ok: true, value: matchedValue, span };
361
- }
362
- return failAt(ctx, [JSON.stringify(value)], pos);
363
- }
374
+ parse: parse2
364
375
  };
365
376
  }
366
377
 
@@ -495,8 +506,7 @@ function regex(pattern, flags = "") {
495
506
  if (scanEnd === null) return failAt(ctx, [`/${source}/`], pos);
496
507
  const value = input.slice(pos, scanEnd);
497
508
  const span2 = { start: pos, end: scanEnd };
498
- const leaf2 = { _tag: "leaf", value, span: span2 };
499
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf2);
509
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span: span2 });
500
510
  return { ok: true, value, span: span2 };
501
511
  }
502
512
  anchored.lastIndex = pos;
@@ -505,8 +515,7 @@ function regex(pattern, flags = "") {
505
515
  return failAt(ctx, [`/${source}/`], pos);
506
516
  }
507
517
  const span = { start: pos, end: pos + m[0].length };
508
- const leaf = { _tag: "leaf", value: m[0], span };
509
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
518
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value: m[0], span });
510
519
  return { ok: true, value: m[0], span };
511
520
  }
512
521
  };
@@ -814,8 +823,7 @@ function keywords(words, opts = {}) {
814
823
  }
815
824
  const value = m[0];
816
825
  const span = { start: pos, end: pos + value.length };
817
- const leaf = { _tag: "leaf", value, span };
818
- if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
826
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, { _tag: "leaf", value, span });
819
827
  return { ok: true, value, span };
820
828
  }
821
829
  };
@@ -1305,33 +1313,136 @@ function loopScanner(arms) {
1305
1313
  }
1306
1314
  function regexTriviaScanner(parser2) {
1307
1315
  if (parser2._def.tag !== "regex" || parser2._def.flags) return null;
1308
- const ws = whitespaceSource(parser2._def.source);
1309
- if (ws) return ws;
1310
- return blockCommentSource(parser2._def.source) ? scanBlockComment : null;
1316
+ const source = parser2._def.source;
1317
+ return classRunSource(source) ?? altStarSource(source) ?? (blockCommentSource(source) ? scanBlockComment : null);
1311
1318
  }
1312
- function whitespaceSource(source) {
1313
- const m = /^\[([ \\tnrf]+)\][*+]$/.exec(source);
1314
- if (!m) return null;
1315
- const chars = /* @__PURE__ */ new Set();
1316
- for (let i = 0; i < m[1].length; i++) {
1317
- const ch = m[1][i];
1318
- if (ch !== "\\") {
1319
- chars.add(ch.charCodeAt(0));
1320
- continue;
1319
+ function inRanges2(cp, ranges) {
1320
+ for (let i = 0; i < ranges.length; i++) {
1321
+ const r = ranges[i];
1322
+ if (cp >= r[0] && cp <= r[1]) return true;
1323
+ }
1324
+ return false;
1325
+ }
1326
+ function classScanner(classBody) {
1327
+ const ranges = parseClassRanges(classBody);
1328
+ if (!ranges) return null;
1329
+ return (input, cur) => {
1330
+ let pos = cur;
1331
+ while (pos < input.length && inRanges2(input.charCodeAt(pos), ranges)) pos++;
1332
+ return pos;
1333
+ };
1334
+ }
1335
+ function lineCommentScanner(commentCode) {
1336
+ return (input, cur) => {
1337
+ if (input.charCodeAt(cur) !== commentCode) return cur;
1338
+ let pos = cur + 1;
1339
+ while (pos < input.length) {
1340
+ const cc = input.charCodeAt(pos);
1341
+ if (cc === 10 || cc === 13) break;
1342
+ pos++;
1321
1343
  }
1322
- const esc = m[1][++i];
1323
- if (esc === "t") chars.add(9);
1324
- else if (esc === "n") chars.add(10);
1325
- else if (esc === "r") chars.add(13);
1326
- else if (esc === "f") chars.add(12);
1327
- else return null;
1344
+ return pos;
1345
+ };
1346
+ }
1347
+ function classRunSource(source) {
1348
+ const m = /^\[([^\]^](?:[^\]]|\\.)*)\][*+]$/.exec(source);
1349
+ return m ? classScanner(m[1]) : null;
1350
+ }
1351
+ function classifyTriviaArm(arm) {
1352
+ const cls = /^\[([^\]^](?:[^\]]|\\.)*)\][*+]?$/.exec(arm);
1353
+ if (cls) {
1354
+ const ranges = parseClassRanges(cls[1]);
1355
+ return ranges ? { kind: "class", ranges } : null;
1328
1356
  }
1357
+ const lc = /^(\\?.)\[\^\\n\\r\]\*$/.exec(arm);
1358
+ if (lc) {
1359
+ const marker = lc[1];
1360
+ return { kind: "comment", code: (marker.length === 2 ? marker[1] : marker[0]).charCodeAt(0) };
1361
+ }
1362
+ return null;
1363
+ }
1364
+ function armScanner(arm) {
1365
+ if (arm.kind === "comment") return lineCommentScanner(arm.code);
1366
+ const ranges = arm.ranges;
1329
1367
  return (input, cur) => {
1330
1368
  let pos = cur;
1331
- while (pos < input.length && chars.has(input.charCodeAt(pos))) pos++;
1369
+ while (pos < input.length && inRanges2(input.charCodeAt(pos), ranges)) pos++;
1332
1370
  return pos;
1333
1371
  };
1334
1372
  }
1373
+ function fusedTriviaScanner(ranges, commentCodes) {
1374
+ const c0 = commentCodes[0];
1375
+ const single = commentCodes.length === 1;
1376
+ return (input, cur) => {
1377
+ let pos = cur;
1378
+ const len = input.length;
1379
+ for (; ; ) {
1380
+ const c = input.charCodeAt(pos);
1381
+ if (pos < len && inRanges2(c, ranges)) {
1382
+ pos++;
1383
+ continue;
1384
+ }
1385
+ if (single ? c === c0 : commentCodes.includes(c)) {
1386
+ pos++;
1387
+ while (pos < len) {
1388
+ const cc = input.charCodeAt(pos);
1389
+ if (cc === 10 || cc === 13) break;
1390
+ pos++;
1391
+ }
1392
+ continue;
1393
+ }
1394
+ break;
1395
+ }
1396
+ return pos;
1397
+ };
1398
+ }
1399
+ function splitTopLevelAlts(body) {
1400
+ const arms = [];
1401
+ let inClass = false;
1402
+ let start = 0;
1403
+ for (let i = 0; i < body.length; i++) {
1404
+ const c = body[i];
1405
+ if (c === "\\") {
1406
+ i++;
1407
+ continue;
1408
+ }
1409
+ if (inClass) {
1410
+ if (c === "]") inClass = false;
1411
+ continue;
1412
+ }
1413
+ if (c === "[") inClass = true;
1414
+ else if (c === "(") return null;
1415
+ else if (c === "|") {
1416
+ arms.push(body.slice(start, i));
1417
+ start = i + 1;
1418
+ }
1419
+ }
1420
+ arms.push(body.slice(start));
1421
+ return arms;
1422
+ }
1423
+ function altStarSource(source) {
1424
+ const m = /^\(\?:(.*)\)[*+]$/.exec(source);
1425
+ if (!m) return null;
1426
+ const armSrcs = splitTopLevelAlts(m[1]);
1427
+ if (!armSrcs || armSrcs.length < 2) return null;
1428
+ const arms = [];
1429
+ for (const src of armSrcs) {
1430
+ const arm = classifyTriviaArm(src);
1431
+ if (!arm) return null;
1432
+ arms.push(arm);
1433
+ }
1434
+ const ranges = [];
1435
+ const commentCodes = [];
1436
+ for (const arm of arms) {
1437
+ if (arm.kind === "class") ranges.push(...arm.ranges);
1438
+ else commentCodes.push(arm.code);
1439
+ }
1440
+ if (commentCodes.some((code) => inRanges2(code, ranges))) {
1441
+ return loopScanner(arms.map(armScanner));
1442
+ }
1443
+ if (commentCodes.length === 0) return armScanner({ kind: "class", ranges });
1444
+ return fusedTriviaScanner(ranges, commentCodes);
1445
+ }
1335
1446
  function blockCommentSource(source) {
1336
1447
  return source === "\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/" || source === "\\/\\*[^]*?\\*\\/";
1337
1448
  }
@@ -1685,6 +1796,7 @@ function markUnusedValues(root) {
1685
1796
  }
1686
1797
 
1687
1798
  // src/combinators/parser.ts
1799
+ var RULE_ORDER = "__parsemanRuleOrder";
1688
1800
  function tagRule(r, key) {
1689
1801
  ;
1690
1802
  r._ruleName = key;
@@ -1729,6 +1841,11 @@ function rules(a, b) {
1729
1841
  for (const key of Object.keys(definitions)) {
1730
1842
  markUnusedValues(cache[key]);
1731
1843
  }
1844
+ Object.defineProperty(cache, RULE_ORDER, {
1845
+ value: Object.keys(definitions),
1846
+ enumerable: false,
1847
+ configurable: true
1848
+ });
1732
1849
  return cache;
1733
1850
  }
1734
1851
 
@@ -6276,17 +6393,159 @@ function manyRecover(item, until) {
6276
6393
  return many(tolerantElement(item, until, until));
6277
6394
  }
6278
6395
 
6396
+ // src/cst/relative-spans.ts
6397
+ function relativize(node2, parentBase = 0) {
6398
+ return {
6399
+ start: node2.start - parentBase,
6400
+ end: node2.end - parentBase,
6401
+ children: node2.children.map((c) => relativize(c, node2.start))
6402
+ };
6403
+ }
6404
+ function absolutize(node2, parentBase = 0) {
6405
+ const start = parentBase + node2.start;
6406
+ return {
6407
+ start,
6408
+ end: parentBase + node2.end,
6409
+ children: node2.children.map((c) => absolutize(c, start))
6410
+ };
6411
+ }
6412
+ function absoluteSpanAt(root, path) {
6413
+ let base = 0;
6414
+ let node2 = root;
6415
+ for (const idx of path) {
6416
+ base += node2.start;
6417
+ const child = node2.children[idx];
6418
+ if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6419
+ node2 = child;
6420
+ }
6421
+ return { start: base + node2.start, end: base + node2.end };
6422
+ }
6423
+ function relativizeCST(node2, parentBase = 0) {
6424
+ const span = { start: node2.span.start - parentBase, end: node2.span.end - parentBase };
6425
+ if (node2.children) {
6426
+ const base = node2.span.start;
6427
+ return { ...node2, span, children: node2.children.map((c) => relativizeCST(c, base)) };
6428
+ }
6429
+ return { ...node2, span };
6430
+ }
6431
+ function absolutizeCST(node2, parentBase = 0) {
6432
+ const start = parentBase + node2.span.start;
6433
+ const span = { start, end: parentBase + node2.span.end };
6434
+ if (node2.children) {
6435
+ return { ...node2, span, children: node2.children.map((c) => absolutizeCST(c, start)) };
6436
+ }
6437
+ return { ...node2, span };
6438
+ }
6439
+ function absoluteSpanCST(root, path) {
6440
+ let base = 0;
6441
+ let node2 = root;
6442
+ for (const idx of path) {
6443
+ base += node2.span.start;
6444
+ const child = node2.children?.[idx];
6445
+ if (!child) throw new RangeError(`absoluteSpanCST: no child ${idx} on path ${path.join(".")}`);
6446
+ node2 = child;
6447
+ }
6448
+ return { start: base + node2.span.start, end: base + node2.span.end };
6449
+ }
6450
+ function shiftAbsolute(node2, at, delta) {
6451
+ const s = node2.start >= at ? node2.start + delta : node2.start;
6452
+ const e = node2.end >= at ? node2.end + delta : node2.end;
6453
+ return {
6454
+ start: s,
6455
+ end: e,
6456
+ children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6457
+ };
6458
+ }
6459
+ function applyEdit(root, at, delta, stats) {
6460
+ return editNode(root, at, delta, 0, 0, stats);
6461
+ }
6462
+ function editNode(node2, at, delta, parentBase, parentShift, stats) {
6463
+ const absStart = parentBase + node2.start;
6464
+ const absEnd = parentBase + node2.end;
6465
+ if (absEnd < at) return node2;
6466
+ const startShifts = absStart >= at;
6467
+ if (startShifts && parentShift === delta) return node2;
6468
+ if (stats) stats.allocated++;
6469
+ const newAbsStart = startShifts ? absStart + delta : absStart;
6470
+ const newAbsEnd = absEnd + delta;
6471
+ const newParentBase = parentBase + parentShift;
6472
+ const newStart = newAbsStart - newParentBase;
6473
+ const newEnd = newAbsEnd - newParentBase;
6474
+ const ownShift = newAbsStart - absStart;
6475
+ let changed = false;
6476
+ const newChildren = [];
6477
+ for (const c of node2.children) {
6478
+ const nc = editNode(c, at, delta, absStart, ownShift, stats);
6479
+ if (nc !== c) changed = true;
6480
+ newChildren.push(nc);
6481
+ }
6482
+ return {
6483
+ start: newStart,
6484
+ end: newEnd,
6485
+ children: changed ? newChildren : node2.children
6486
+ };
6487
+ }
6488
+
6279
6489
  // src/functional/doc.ts
6490
+ function asRuleFn(entry) {
6491
+ return typeof entry === "function" ? entry : (input, pos, ctx) => entry.parse(input, pos, ctx);
6492
+ }
6493
+ function defOf(entry) {
6494
+ return typeof entry === "object" && entry !== null && "_def" in entry ? entry._def : void 0;
6495
+ }
6496
+ function producesRepetition(def, depth = 0) {
6497
+ if (!def || depth > 24) return false;
6498
+ const d = def;
6499
+ switch (d.tag) {
6500
+ case "sepBy":
6501
+ case "many":
6502
+ case "oneOrMore":
6503
+ return true;
6504
+ // Rule entries and `ref`s wrap their body in a `lazy` thunk — resolve it. The
6505
+ // `depth` cap bounds any self-referential cycle (a repetition, if present, is
6506
+ // found shallowly before recursion goes deep).
6507
+ case "lazy": {
6508
+ const thunk = d.thunk;
6509
+ let inner;
6510
+ try {
6511
+ inner = typeof thunk === "function" ? thunk() : void 0;
6512
+ } catch {
6513
+ return false;
6514
+ }
6515
+ return producesRepetition(inner?._def, depth + 1);
6516
+ }
6517
+ // Transparent wrappers — look through to the inner parser.
6518
+ case "node":
6519
+ case "transform":
6520
+ case "token":
6521
+ case "label":
6522
+ case "field":
6523
+ case "expect":
6524
+ case "withCtx":
6525
+ case "grammar":
6526
+ case "optional":
6527
+ return producesRepetition(defOf(d.parser), depth + 1);
6528
+ case "skip":
6529
+ return producesRepetition(defOf(d.main), depth + 1);
6530
+ // A bracketed/anchored list is a sequence whose element run is a repetition.
6531
+ case "sequence":
6532
+ return d.parsers.some((p) => producesRepetition(defOf(p), depth + 1));
6533
+ default:
6534
+ return false;
6535
+ }
6536
+ }
6280
6537
  var REENTRY_MAX_SPAN_FRACTION = 0.5;
6281
6538
  function isNode(x) {
6282
6539
  return typeof x === "object" && x !== null && x._tag === "node";
6283
6540
  }
6284
- function findContaining(node2, pos, path = []) {
6541
+ function findContaining(node2, nodeAbsStart, pos, path = []) {
6285
6542
  for (let i = 0; i < node2.children.length; i++) {
6286
6543
  const child = node2.children[i];
6287
6544
  if (!isNode(child)) continue;
6288
- if (child.span.start <= pos && pos < child.span.end) {
6289
- const inner = findContaining(child, pos, [...path, i]);
6545
+ const childAbsStart = nodeAbsStart + child.span.start;
6546
+ const childAbsEnd = nodeAbsStart + child.span.end;
6547
+ if (childAbsStart <= pos && pos < childAbsEnd) {
6548
+ const inner = findContaining(child, childAbsStart, pos, [...path, i]);
6290
6549
  return inner ?? { node: child, path: [...path, i] };
6291
6550
  }
6292
6551
  }
@@ -6313,24 +6572,20 @@ function replaceAtPath(rebuild, root, path, newNode) {
6313
6572
  function defaultRebuild(node2, children) {
6314
6573
  return { ...node2, children };
6315
6574
  }
6316
- function shiftSpans(child, delta) {
6575
+ function shiftRelStart(child, delta) {
6317
6576
  const c = child;
6318
- const span = { start: c.span.start + delta, end: c.span.end + delta };
6319
- if (Array.isArray(c.children)) {
6320
- return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
6321
- }
6322
- return { ...c, span };
6577
+ return { ...c, span: { start: c.span.start + delta, end: c.span.end + delta } };
6323
6578
  }
6324
- function graftAndShift(root, path, newNode, delta) {
6579
+ function graftRelative(root, path, newNode, delta) {
6325
6580
  if (path.length === 0) return newNode;
6326
6581
  const [idx, ...rest] = path;
6327
6582
  const oldChildren = root.children;
6328
6583
  const newChildren = oldChildren.map((child, i) => {
6329
6584
  if (i < idx) return child;
6330
6585
  if (i === idx) {
6331
- return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
6586
+ return rest.length === 0 ? newNode : graftRelative(child, rest, newNode, delta);
6332
6587
  }
6333
- return shiftSpans(child, delta);
6588
+ return shiftRelStart(child, delta);
6334
6589
  });
6335
6590
  return {
6336
6591
  ...root,
@@ -6338,6 +6593,96 @@ function graftAndShift(root, path, newNode, delta) {
6338
6593
  children: newChildren
6339
6594
  };
6340
6595
  }
6596
+ function parseMiddle(input, reStart, reEnd, cStart, ruleFn, separator, build) {
6597
+ const ctx = { trackLines: false, state: null, build };
6598
+ const out = [];
6599
+ let pos = reStart;
6600
+ let guard2 = 0;
6601
+ while (pos < reEnd) {
6602
+ if (++guard2 > reEnd - reStart + 2) return null;
6603
+ if (separator && out.length > 0 && out[out.length - 1]._tag === "node") {
6604
+ if (pos + separator.length > reEnd || !input.startsWith(separator, pos)) return null;
6605
+ out.push({ _tag: "leaf", value: separator, span: { start: pos - cStart, end: pos + separator.length - cStart } });
6606
+ pos += separator.length;
6607
+ continue;
6608
+ }
6609
+ let r;
6610
+ try {
6611
+ r = ruleFn(input, pos, ctx);
6612
+ } catch {
6613
+ return null;
6614
+ }
6615
+ if (!r.ok || r.span.end <= pos || r.span.end > reEnd) return null;
6616
+ out.push(relativizeCST(r.value, cStart));
6617
+ pos = r.span.end;
6618
+ }
6619
+ return pos === reEnd ? out : null;
6620
+ }
6621
+ function tryListSplice(root, path, C, cStart, newInput, from, to, delta, registry, build) {
6622
+ const kids = C.children;
6623
+ if (kids.length === 0) return null;
6624
+ let h = 0;
6625
+ while (h < kids.length && cStart + kids[h].span.end <= from) h++;
6626
+ while (h > 0 && kids[h - 1]._tag === "node") h--;
6627
+ let t = kids.length;
6628
+ while (t > h && cStart + kids[t - 1].span.start >= to) t--;
6629
+ while (t < kids.length && kids[t]._tag !== "node") t++;
6630
+ if (t >= kids.length) return null;
6631
+ for (let i = t; i < kids.length; i++) {
6632
+ if (kids[i]._tag === "node" && kids[i].state != null) return null;
6633
+ }
6634
+ let elemType;
6635
+ let elemCount = 0;
6636
+ for (const k of kids) {
6637
+ if (k._tag !== "node") continue;
6638
+ elemCount++;
6639
+ if (elemType === void 0) elemType = k.type;
6640
+ else if (k.type !== elemType) return null;
6641
+ }
6642
+ if (elemType === void 0 || elemCount < 2) return null;
6643
+ const ruleFn = registry[elemType];
6644
+ if (!ruleFn) return null;
6645
+ let separator = null;
6646
+ for (let i = 1; i < kids.length - 1; i++) {
6647
+ if (kids[i - 1]._tag === "node" && kids[i + 1]._tag === "node") {
6648
+ if (kids[i]._tag !== "leaf") return null;
6649
+ const sep = kids[i].value ?? null;
6650
+ if (separator === null) separator = sep;
6651
+ else if (sep !== separator) return null;
6652
+ }
6653
+ }
6654
+ if (separator === null) return null;
6655
+ const first = kids[0];
6656
+ const last = kids[kids.length - 1];
6657
+ if (first._tag === "leaf" && first.value !== separator && from < cStart + first.span.end) return null;
6658
+ if (last._tag === "leaf" && last.value !== separator && to > cStart + last.span.start) return null;
6659
+ const reStart = h > 0 ? cStart + kids[h - 1].span.end : cStart;
6660
+ const reEnd = cStart + kids[t].span.start + delta;
6661
+ if (reEnd < reStart) return null;
6662
+ const middle = parseMiddle(newInput, reStart, reEnd, cStart, ruleFn, separator, build);
6663
+ if (!middle) return null;
6664
+ if (middle.length > 0) {
6665
+ const last2 = middle[middle.length - 1];
6666
+ if (!(last2._tag === "leaf" && last2.value === separator)) return null;
6667
+ }
6668
+ if (reEnd < newInput.length) {
6669
+ for (const sentinel of [" ", "\uFFFF"]) {
6670
+ if (newInput[reEnd] === sentinel) continue;
6671
+ const probed = newInput.slice(0, reEnd) + sentinel.repeat(newInput.length - reEnd);
6672
+ const probe = parseMiddle(probed, reStart, reEnd, cStart, ruleFn, separator, build);
6673
+ if (!probe || probe.length !== middle.length) return null;
6674
+ for (let i = 0; i < middle.length; i++) if (!structurallyEqual(probe[i], middle[i])) return null;
6675
+ }
6676
+ }
6677
+ const head = kids.slice(0, h);
6678
+ const tail = kids.slice(t).map((k) => shiftRelStart(k, delta));
6679
+ const newC = {
6680
+ ...C,
6681
+ span: { start: C.span.start, end: C.span.end + delta },
6682
+ children: [...head, ...middle, ...tail]
6683
+ };
6684
+ return graftRelative(root, path, newC, delta);
6685
+ }
6341
6686
  function structurallyEqual(a, b) {
6342
6687
  if (a === b) return true;
6343
6688
  if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
@@ -6381,27 +6726,68 @@ function boundaryIsSafe(ruleFn, newInput, start, boundary, state, build, produce
6381
6726
  }
6382
6727
  var ParseDocImpl = class _ParseDocImpl {
6383
6728
  _registry;
6729
+ /** Registry entries normalized to callable parse functions (memoized once). */
6730
+ _fns;
6731
+ /** Rule names the grammar proves are genuine repetitions — the ONLY splice-safe types. */
6732
+ _reps;
6384
6733
  _rootRule;
6385
6734
  _opts;
6386
- tree;
6735
+ /**
6736
+ * The tree, held in whichever coordinate system it arrived in. A fresh parse
6737
+ * arrives ABSOLUTE (`_abs` set); an incremental graft arrives PARENT-RELATIVE
6738
+ * (`_rel` set). The public `tree` is always the RELATIVE form (so untouched
6739
+ * subtrees stay shared by identity across edits and reads are O(1)); it's
6740
+ * materialized from `_abs` once, on demand, and memoized. `undefined` = not yet
6741
+ * computed; `null` = failed parse. A fresh parse that's never edited nor
6742
+ * tree-read pays no conversion — important for the full-reparse fallback path.
6743
+ */
6744
+ _abs;
6745
+ _rel;
6387
6746
  errors;
6388
6747
  input;
6389
- constructor(registry, rootRule, opts, tree, errors, input) {
6748
+ constructor(registry, rootRule, opts, trees, errors, input) {
6390
6749
  this._registry = registry;
6750
+ this._fns = {};
6751
+ this._reps = /* @__PURE__ */ new Set();
6752
+ for (const [name, entry] of Object.entries(registry)) {
6753
+ this._fns[name] = asRuleFn(entry);
6754
+ if (producesRepetition(defOf(entry))) this._reps.add(name);
6755
+ }
6391
6756
  this._rootRule = rootRule;
6392
6757
  this._opts = opts;
6393
- this.tree = tree;
6758
+ this._abs = trees.abs;
6759
+ this._rel = trees.rel;
6394
6760
  this.errors = errors;
6395
6761
  this.input = input;
6396
6762
  }
6763
+ /**
6764
+ * The parse tree with PARENT-RELATIVE spans — a node's `span` is relative to
6765
+ * its parent's start (root base 0). This is the shareable representation:
6766
+ * untouched subtrees keep the same identity across `.edit()`s, and reading the
6767
+ * tree after a length-changing edit is O(1) (no offset rewrite). For absolute
6768
+ * positions use the O(depth) cursor `spanAt(path)`, or `absolutizeCST(tree)` to
6769
+ * materialize the whole absolute tree. (A fresh non-incremental `node().parse()`
6770
+ * result is unchanged — still absolute.)
6771
+ */
6772
+ get tree() {
6773
+ if (this._rel === void 0) {
6774
+ this._rel = this._abs ? relativizeCST(this._abs, 0) : null;
6775
+ }
6776
+ return this._rel;
6777
+ }
6778
+ spanAt(path) {
6779
+ const rel = this.tree;
6780
+ if (!rel) throw new Error("spanAt on a failed parse (tree is null)");
6781
+ return absoluteSpanCST(rel, path);
6782
+ }
6397
6783
  edit(from, to, replacement) {
6398
6784
  const newInput = this.input.slice(0, from) + replacement + this.input.slice(to);
6399
6785
  const reparse = () => parseDoc(this._registry, this._rootRule, newInput, this._opts);
6400
- if (!this.tree) return reparse();
6786
+ const root = this.tree;
6787
+ if (!root) return reparse();
6401
6788
  const delta = replacement.length - (to - from);
6402
- const found = findContaining(this.tree, from);
6403
- if (!found) return reparse();
6404
- const ancestors = ancestorsAt(this.tree, found.path);
6789
+ const found = findContaining(root, 0, from) ?? { node: root, path: [] };
6790
+ const ancestors = ancestorsAt(root, found.path);
6405
6791
  const candidates = [found];
6406
6792
  const pathCopy = [...found.path];
6407
6793
  for (let i = ancestors.length - 2; i >= 0; i--) {
@@ -6410,37 +6796,50 @@ var ParseDocImpl = class _ParseDocImpl {
6410
6796
  }
6411
6797
  const rebuild = this._opts.rebuild ?? defaultRebuild;
6412
6798
  for (const { node: node2, path } of candidates) {
6413
- if (node2.span.end - node2.span.start > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6414
- const ruleFn = this._registry[node2.type];
6799
+ const { start: absStart, end: absEnd } = absoluteSpanCST(root, path);
6800
+ if (absEnd - absStart > this.input.length * REENTRY_MAX_SPAN_FRACTION) break;
6801
+ const ruleFn = this._fns[node2.type];
6415
6802
  if (!ruleFn) continue;
6416
- if (!(node2.span.start <= from && to <= node2.span.end)) continue;
6803
+ if (!(absStart <= from && to <= absEnd)) continue;
6417
6804
  const ctx = { trackLines: false, state: node2.state, build: this._opts.build };
6418
- const r = ruleFn(newInput, node2.span.start, ctx);
6805
+ const r = ruleFn(newInput, absStart, ctx);
6419
6806
  if (!r.ok) continue;
6420
- if (r.span.end !== node2.span.end + delta) continue;
6421
- if (!boundaryIsSafe(ruleFn, newInput, node2.span.start, node2.span.end + delta, node2.state, this._opts.build, r)) {
6807
+ if (r.span.end !== absEnd + delta) continue;
6808
+ if (!boundaryIsSafe(ruleFn, newInput, absStart, absEnd + delta, node2.state, this._opts.build, r)) {
6422
6809
  continue;
6423
6810
  }
6811
+ const parentBase = absStart - node2.span.start;
6812
+ const newRel = relativizeCST(r.value, parentBase);
6424
6813
  if (delta === 0) {
6425
- const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
6426
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
6814
+ const newTree2 = replaceAtPath(rebuild, root, path, newRel);
6815
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree2 }, [], newInput);
6427
6816
  }
6428
6817
  if (this._opts.rebuild) return reparse();
6429
- const newTree = graftAndShift(this.tree, path, r.value, delta);
6430
- return new _ParseDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
6818
+ const newTree = graftRelative(root, path, newRel, delta);
6819
+ return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: newTree }, [], newInput);
6820
+ }
6821
+ if (this._opts.structuralReuse && delta !== 0 && !this._opts.rebuild) {
6822
+ const spliceCandidates = [...candidates, { node: root, path: [] }];
6823
+ for (const { node: node2, path } of spliceCandidates) {
6824
+ if (!this._reps.has(node2.type)) continue;
6825
+ const { start: cStart, end: cEnd } = absoluteSpanCST(root, path);
6826
+ if (!(cStart <= from && to <= cEnd)) continue;
6827
+ const spliced = tryListSplice(root, path, node2, cStart, newInput, from, to, delta, this._fns, this._opts.build);
6828
+ if (spliced) return new _ParseDocImpl(this._registry, this._rootRule, this._opts, { rel: spliced }, [], newInput);
6829
+ }
6431
6830
  }
6432
6831
  return reparse();
6433
6832
  }
6434
6833
  };
6435
6834
  function parseDoc(registry, rootRule, input, opts = {}) {
6436
- const ruleFn = registry[rootRule];
6437
- if (!ruleFn) throw new Error(`No rule '${rootRule}' in registry`);
6835
+ const entry = registry[rootRule];
6836
+ if (!entry) throw new Error(`No rule '${rootRule}' in registry`);
6438
6837
  const ctx = { trackLines: false, state: opts.state, build: opts.build };
6439
- const r = ruleFn(input, 0, ctx);
6838
+ const r = asRuleFn(entry)(input, 0, ctx);
6440
6839
  if (r.ok) {
6441
- return new ParseDocImpl(registry, rootRule, opts, r.value, [], input);
6840
+ return new ParseDocImpl(registry, rootRule, opts, { abs: r.value }, [], input);
6442
6841
  }
6443
- return new ParseDocImpl(registry, rootRule, opts, null, [{ ok: false, expected: r.expected, span: r.span }], input);
6842
+ return new ParseDocImpl(registry, rootRule, opts, { abs: null }, [{ ok: false, expected: r.expected, span: r.span }], input);
6444
6843
  }
6445
6844
 
6446
6845
  // src/functional/run.ts
@@ -6809,78 +7208,14 @@ function buildOffsetIndex(root, input = "") {
6809
7208
  return new OffsetIndex(collectLeafSlots(root), 0, input.length);
6810
7209
  }
6811
7210
 
6812
- // src/cst/relative-spans.ts
6813
- function relativize(node2, parentBase = 0) {
6814
- return {
6815
- start: node2.start - parentBase,
6816
- end: node2.end - parentBase,
6817
- children: node2.children.map((c) => relativize(c, node2.start))
6818
- };
6819
- }
6820
- function absolutize(node2, parentBase = 0) {
6821
- const start = parentBase + node2.start;
6822
- return {
6823
- start,
6824
- end: parentBase + node2.end,
6825
- children: node2.children.map((c) => absolutize(c, start))
6826
- };
6827
- }
6828
- function absoluteSpanAt(root, path) {
6829
- let base = 0;
6830
- let node2 = root;
6831
- for (const idx of path) {
6832
- base += node2.start;
6833
- const child = node2.children[idx];
6834
- if (!child) throw new RangeError(`absoluteSpanAt: no child ${idx} on path ${path.join(".")}`);
6835
- node2 = child;
6836
- }
6837
- return { start: base + node2.start, end: base + node2.end };
6838
- }
6839
- function shiftAbsolute(node2, at, delta) {
6840
- const s = node2.start >= at ? node2.start + delta : node2.start;
6841
- const e = node2.end >= at ? node2.end + delta : node2.end;
6842
- return {
6843
- start: s,
6844
- end: e,
6845
- children: node2.children.map((c) => shiftAbsolute(c, at, delta))
6846
- };
6847
- }
6848
- function applyEdit(root, at, delta, stats) {
6849
- return editNode(root, at, delta, 0, 0, stats);
6850
- }
6851
- function editNode(node2, at, delta, parentBase, parentShift, stats) {
6852
- const absStart = parentBase + node2.start;
6853
- const absEnd = parentBase + node2.end;
6854
- if (absEnd < at) return node2;
6855
- const startShifts = absStart >= at;
6856
- if (startShifts && parentShift === delta) return node2;
6857
- if (stats) stats.allocated++;
6858
- const newAbsStart = startShifts ? absStart + delta : absStart;
6859
- const newAbsEnd = absEnd + delta;
6860
- const newParentBase = parentBase + parentShift;
6861
- const newStart = newAbsStart - newParentBase;
6862
- const newEnd = newAbsEnd - newParentBase;
6863
- const ownShift = newAbsStart - absStart;
6864
- let changed = false;
6865
- const newChildren = [];
6866
- for (const c of node2.children) {
6867
- const nc = editNode(c, at, delta, absStart, ownShift, stats);
6868
- if (nc !== c) changed = true;
6869
- newChildren.push(nc);
6870
- }
6871
- return {
6872
- start: newStart,
6873
- end: newEnd,
6874
- children: changed ? newChildren : node2.children
6875
- };
6876
- }
6877
-
6878
7211
  // src/index.ts
6879
7212
  registerRegexAnalyzer(firstSetFromRegex);
6880
7213
  export {
6881
7214
  OffsetIndex,
6882
7215
  absoluteSpanAt,
7216
+ absoluteSpanCST,
6883
7217
  absolutize,
7218
+ absolutizeCST,
6884
7219
  annotateSpan,
6885
7220
  applyEdit,
6886
7221
  balanced,
@@ -6925,6 +7260,7 @@ export {
6925
7260
  ref,
6926
7261
  regex,
6927
7262
  relativize,
7263
+ relativizeCST,
6928
7264
  rules,
6929
7265
  run,
6930
7266
  scanTo,