parseman 0.8.0 → 0.10.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
@@ -1200,24 +1200,70 @@ function not(combinator) {
1200
1200
  };
1201
1201
  }
1202
1202
 
1203
+ // src/compiler/build-arity.ts
1204
+ var PARAM_LIST_RE = (
1205
+ // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
1206
+ /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
1207
+ );
1208
+ var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
1209
+ function confirmedBuildArity(src) {
1210
+ const s = src.trim();
1211
+ const m = PARAM_LIST_RE.exec(s);
1212
+ if (!m) return null;
1213
+ if (m[3] !== void 0) return 1;
1214
+ const inner = (m[1] ?? m[2] ?? "").trim();
1215
+ if (inner === "") return 0;
1216
+ const parts = inner.split(",");
1217
+ for (const part of parts) {
1218
+ if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
1219
+ }
1220
+ if (/\barguments\b/.test(s)) return null;
1221
+ return parts.length;
1222
+ }
1223
+ function buildReadsTrivia(def) {
1224
+ const src = def.buildSrc ?? def.build.toString();
1225
+ const arity = confirmedBuildArity(src);
1226
+ if (arity === null) return true;
1227
+ return arity >= 4;
1228
+ }
1229
+ function buildReadsState(def) {
1230
+ const src = def.buildSrc ?? def.build.toString();
1231
+ const arity = confirmedBuildArity(src);
1232
+ if (arity === null) return true;
1233
+ return arity >= 5;
1234
+ }
1235
+
1203
1236
  // src/combinators/node.ts
1204
- function node(type, combinator, build) {
1237
+ function collapseChild(child) {
1238
+ return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1239
+ }
1240
+ function node(type, combinator, build, opts) {
1205
1241
  const meta = {
1206
1242
  firstSet: combinator._meta.firstSet,
1207
1243
  canMatchNewline: combinator._meta.canMatchNewline,
1208
1244
  isTrivia: false
1209
1245
  };
1246
+ const collapse = opts?.collapse === true;
1247
+ const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1248
+ const capturesTrivia = buildReadsTrivia(def);
1249
+ const clonesState = buildReadsState(def);
1210
1250
  return {
1211
1251
  _tag: "node",
1212
1252
  _meta: meta,
1213
- _def: { tag: "node", type, parser: combinator, build },
1253
+ _def: def,
1214
1254
  parse(input, pos, ctx) {
1215
1255
  const saved = beginCstNodeCapture(ctx);
1256
+ if (!capturesTrivia) ctx.captureTrivia = false;
1216
1257
  const r = combinator.parse(input, pos, ctx);
1217
1258
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1218
1259
  if (!r.ok) return r;
1219
- const state = ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1220
- const built = build(children, rawChildren, r.span, triviaLog, state);
1260
+ const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1261
+ children,
1262
+ rawChildren,
1263
+ r.span,
1264
+ triviaLog,
1265
+ clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1266
+ );
1221
1267
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1222
1268
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1223
1269
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1367,14 +1413,140 @@ function parse(combinator, input, opts = {}) {
1367
1413
  return withErrors;
1368
1414
  }
1369
1415
 
1416
+ // src/compiler/scannable-run.ts
1417
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1418
+ var META = new Set("()[]{}*+?|^$.".split(""));
1419
+ function parseClassRanges(body) {
1420
+ const ranges = [];
1421
+ let i = 0;
1422
+ const readCp = () => {
1423
+ const ch = body[i];
1424
+ if (ch === void 0) return null;
1425
+ if (ch === "\\") {
1426
+ const e = body[i + 1];
1427
+ if (e === void 0) return null;
1428
+ i += 2;
1429
+ return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1430
+ }
1431
+ i += ch.length;
1432
+ return ch.codePointAt(0);
1433
+ };
1434
+ while (i < body.length) {
1435
+ const lo = readCp();
1436
+ if (lo === null) return null;
1437
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1438
+ i += 1;
1439
+ const hi = readCp();
1440
+ if (hi === null) return null;
1441
+ ranges.push([lo, hi]);
1442
+ } else {
1443
+ ranges.push([lo, lo]);
1444
+ }
1445
+ }
1446
+ return ranges.length ? ranges : null;
1447
+ }
1448
+ function literalCodePoints(frag) {
1449
+ const out = [];
1450
+ let i = 0;
1451
+ while (i < frag.length) {
1452
+ const ch = frag[i];
1453
+ if (ch === "\\") {
1454
+ const e = frag[i + 1];
1455
+ if (e === void 0) return null;
1456
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1457
+ i += 2;
1458
+ continue;
1459
+ }
1460
+ if (META.has(ch)) return null;
1461
+ out.push(ch.codePointAt(0));
1462
+ i += 1;
1463
+ }
1464
+ return out.length ? out : null;
1465
+ }
1466
+ function parseScanShape(source) {
1467
+ let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1468
+ if (m) {
1469
+ if (m[1].startsWith("^")) return null;
1470
+ const ranges = parseClassRanges(m[1]);
1471
+ return ranges ? { kind: "chars", ranges } : null;
1472
+ }
1473
+ m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1474
+ if (m) {
1475
+ const open = literalCodePoints(m[1]);
1476
+ const stop = parseClassRanges(m[2]);
1477
+ if (open && stop) return { kind: "until", open, stop };
1478
+ return null;
1479
+ }
1480
+ if (!source.includes("\\\\")) {
1481
+ m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1482
+ if (m && m[1] && m[2]) {
1483
+ const open = literalCodePoints(m[1]);
1484
+ const close = literalCodePoints(m[2]);
1485
+ if (open && close) return { kind: "delimited", open, close };
1486
+ }
1487
+ }
1488
+ return null;
1489
+ }
1490
+ var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1491
+ var litCond = (base, cps, firstVar) => cps.map(
1492
+ (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1493
+ ).join(" && ");
1494
+ function advanceExpr(shape) {
1495
+ if (shape.kind === "chars") {
1496
+ return {
1497
+ setup: [
1498
+ ` _e++`,
1499
+ ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1500
+ ],
1501
+ endVar: "_e"
1502
+ };
1503
+ }
1504
+ if (shape.kind === "until") {
1505
+ return {
1506
+ setup: [
1507
+ ` let j = _e + ${shape.open.length}`,
1508
+ ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1509
+ ],
1510
+ endVar: "j"
1511
+ };
1512
+ }
1513
+ const n = shape.close.length;
1514
+ return {
1515
+ setup: [
1516
+ ` let j = _e + ${shape.open.length}`,
1517
+ ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1518
+ ],
1519
+ endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1520
+ };
1521
+ }
1522
+ function guardLine(shape) {
1523
+ const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1524
+ return ` if (${cond}) {`;
1525
+ }
1526
+ function scanBranch(shape) {
1527
+ if (shape.kind === "chars") {
1528
+ return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1529
+ }
1530
+ const { setup, endVar } = advanceExpr(shape);
1531
+ return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1532
+ }
1533
+ function captureChunk(startVar, kindIndex) {
1534
+ return [
1535
+ ` if (_cap) {`,
1536
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1537
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1538
+ ` }`
1539
+ ];
1540
+ }
1541
+ function scanBranchLabeled(shape, kindIndex) {
1542
+ const { setup, endVar } = advanceExpr(shape);
1543
+ const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1544
+ if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1545
+ lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1546
+ return lines.join("\n");
1547
+ }
1548
+
1370
1549
  // src/compiler/trivia-fast-path.ts
1371
- var BLOCK_COMMENT = String.raw`\/\*(?:[^*]|\*(?!\/))*\*\/`;
1372
- var WS_CLASS_SOURCES = /* @__PURE__ */ new Set([
1373
- String.raw`[ \t\n\r\f]+`,
1374
- String.raw`[ \t\n]+`,
1375
- String.raw`[ \t]+`,
1376
- String.raw`[\t\n\f\r ]+`
1377
- ]);
1378
1550
  function unwrapTrivia2(p) {
1379
1551
  let cur = p;
1380
1552
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1390,36 +1562,30 @@ function choiceArms(p) {
1390
1562
  if (p._def.tag === "choice") return p._def.parsers;
1391
1563
  return null;
1392
1564
  }
1393
- function isWsClassSource(source) {
1394
- return WS_CLASS_SOURCES.has(source);
1395
- }
1396
- function isBlockCommentSource(source) {
1397
- return source === BLOCK_COMMENT;
1565
+ function armShape(arm) {
1566
+ const src = getCoreRegexDef(arm)?.source;
1567
+ return src ? parseScanShape(src) : null;
1398
1568
  }
1399
1569
  function analyzeTriviaFastPath(trivia2) {
1400
1570
  const core = unwrapTrivia2(trivia2);
1401
- const direct = getCoreRegexDef(core)?.source;
1402
- if (direct) {
1403
- if (isWsClassSource(direct)) return "wsOnly";
1404
- return null;
1571
+ const directSrc = getCoreRegexDef(core)?.source;
1572
+ if (directSrc) {
1573
+ const shape = parseScanShape(directSrc);
1574
+ return shape && shape.kind === "chars" ? [shape] : null;
1405
1575
  }
1406
1576
  const inner = oneOrMoreInner(core);
1407
1577
  if (!inner) return null;
1408
- const innerSrc = getCoreRegexDef(inner)?.source;
1409
- if (innerSrc && isWsClassSource(innerSrc)) return "wsOnly";
1578
+ const innerShape = armShape(inner);
1579
+ if (innerShape) return innerShape.kind === "chars" ? [innerShape] : null;
1410
1580
  const arms = choiceArms(inner);
1411
- if (!arms || arms.length !== 2) return null;
1412
- let hasWs = false;
1413
- let hasComment = false;
1581
+ if (!arms || arms.length < 2) return null;
1582
+ const shapes = [];
1414
1583
  for (const arm of arms) {
1415
- const src = getCoreRegexDef(arm)?.source;
1416
- if (!src) return null;
1417
- if (isWsClassSource(src)) hasWs = true;
1418
- else if (isBlockCommentSource(src)) hasComment = true;
1419
- else return null;
1584
+ const shape = armShape(arm);
1585
+ if (!shape) return null;
1586
+ shapes.push(shape);
1420
1587
  }
1421
- if (hasWs && hasComment) return "wsComments";
1422
- return null;
1588
+ return shapes;
1423
1589
  }
1424
1590
  var CAP_RECORD = [
1425
1591
  ` if (_cap && _e > _pos) {`,
@@ -1427,80 +1593,49 @@ var CAP_RECORD = [
1427
1593
  ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_pos, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0)`,
1428
1594
  ` }`
1429
1595
  ].join("\n");
1430
- var WS_ONLY_LOOP = [
1431
- ` while (_e < input.length) {`,
1432
- ` const c = input.charCodeAt(_e)`,
1433
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
1434
- ` break`,
1435
- ` }`
1436
- ].join("\n");
1437
- var WS_COMMENTS_LOOP = [
1438
- ` while (_e < input.length) {`,
1439
- ` const c = input.charCodeAt(_e)`,
1440
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
1441
- ` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
1442
- ` let j = _e + 2`,
1443
- ` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
1444
- ` _e = j + 2 <= input.length ? j + 2 : input.length`,
1445
- ` continue`,
1446
- ` }`,
1447
- ` break`,
1448
- ` }`
1449
- ].join("\n");
1450
- var CAP_CHUNK = (wsKind, commentKind) => [
1451
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) {`,
1452
- ` const _cs = _e`,
1453
- ` _e++`,
1454
- ` while (_e < input.length) {`,
1455
- ` const c2 = input.charCodeAt(_e)`,
1456
- ` if (c2 === 32 || c2 === 9 || c2 === 10 || c2 === 13 || c2 === 12) _e++`,
1457
- ` else break`,
1458
- ` }`,
1459
- ` if (_cap) {`,
1460
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${wsKind})`,
1461
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${wsKind})`,
1462
- ` }`,
1463
- ` continue`,
1464
- ` }`,
1465
- ` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
1466
- ` const _cs = _e`,
1467
- ` let j = _e + 2`,
1468
- ` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
1469
- ` _e = j + 2 <= input.length ? j + 2 : input.length`,
1470
- ` if (_cap) {`,
1471
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${commentKind})`,
1472
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${commentKind})`,
1473
- ` }`,
1474
- ` continue`,
1475
- ` }`
1476
- ].join("\n");
1477
- var WS_COMMENTS_LOOP_LABELED = (wsKind, commentKind) => [
1478
- ` while (_e < input.length) {`,
1479
- ` const c = input.charCodeAt(_e)`,
1480
- CAP_CHUNK(wsKind, commentKind),
1481
- ` break`,
1482
- ` }`
1483
- ].join("\n");
1484
- function buildFastTriviaFnDecl(fnName, kind, kindIndices) {
1485
- const loop = kind === "wsComments" && kindIndices ? WS_COMMENTS_LOOP_LABELED(kindIndices.ws, kindIndices.comment) : kind === "wsComments" ? WS_COMMENTS_LOOP : WS_ONLY_LOOP;
1486
- const cap = kindIndices ? "" : CAP_RECORD;
1487
- const lines = [
1596
+ function composeFastLoop(shapes) {
1597
+ return [
1598
+ ` while (_e < input.length) {`,
1599
+ ` const c = input.charCodeAt(_e)`,
1600
+ ...shapes.map(scanBranch),
1601
+ ` break`,
1602
+ ` }`
1603
+ ].join("\n");
1604
+ }
1605
+ function buildFastTriviaFnDecl(fnName, shapes) {
1606
+ return [
1488
1607
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1489
1608
  ` let _e = _pos`,
1490
- loop
1491
- ];
1492
- if (cap) lines.push(cap);
1493
- lines.push(` return _e`, `}`);
1494
- return lines.join("\n");
1609
+ composeFastLoop(shapes),
1610
+ CAP_RECORD,
1611
+ ` return _e`,
1612
+ `}`
1613
+ ].join("\n");
1495
1614
  }
1496
- function regexArmIndex(spec, isComment) {
1615
+ function analyzeLabeledScannableRun(trivia2) {
1616
+ const spec = analyzeLabeledTrivia(trivia2);
1617
+ if (!spec) return null;
1618
+ const out = [];
1497
1619
  for (const arm of spec.arms) {
1498
1620
  const src = getCoreRegexDef(arm.parser)?.source;
1499
- if (!src) return null;
1500
- const comment = src.includes("\\*") || src.startsWith("\\/\\/");
1501
- if (comment === isComment) return arm.kindIndex;
1621
+ const shape = src ? parseScanShape(src) : null;
1622
+ if (!shape) return null;
1623
+ out.push({ shape, kindIndex: arm.kindIndex });
1502
1624
  }
1503
- return null;
1625
+ return out;
1626
+ }
1627
+ function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1628
+ return [
1629
+ `function ${fnName}(input, _pos, _ctx, _cap) {`,
1630
+ ` let _e = _pos`,
1631
+ ` while (_e < input.length) {`,
1632
+ ` const c = input.charCodeAt(_e)`,
1633
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1634
+ ` break`,
1635
+ ` }`,
1636
+ ` return _e`,
1637
+ `}`
1638
+ ].join("\n");
1504
1639
  }
1505
1640
  function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1506
1641
  const tryArms = spec.arms.map((arm, i) => {
@@ -1533,14 +1668,6 @@ function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1533
1668
  `}`
1534
1669
  ].join("\n");
1535
1670
  }
1536
- function labeledTriviaKindIndices(trivia2) {
1537
- const spec = analyzeLabeledTrivia(trivia2);
1538
- if (!spec || spec.arms.length !== 2) return null;
1539
- const ws = regexArmIndex(spec, false);
1540
- const comment = regexArmIndex(spec, true);
1541
- if (ws === null || comment === null) return null;
1542
- return { ws, comment };
1543
- }
1544
1671
  function labeledTriviaRegexArms(trivia2) {
1545
1672
  const spec = analyzeLabeledTrivia(trivia2);
1546
1673
  if (!spec) return null;
@@ -1788,14 +1915,19 @@ function ensureTriviaFn(ctx) {
1788
1915
  const fnName = `_tf${ctx.triviaFnNames.size}`;
1789
1916
  ctx.triviaFnNames.set(trivia2, fnName);
1790
1917
  ctx.triviaCaptureNames.set(trivia2, fnName);
1791
- const fastKind = analyzeTriviaFastPath(trivia2);
1792
- const kindIndices = labeledTriviaKindIndices(trivia2);
1793
- if (fastKind) {
1794
- ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastKind, kindIndices ?? void 0));
1795
- return fnName;
1796
- }
1797
1918
  const labeledSpec = analyzeLabeledTrivia(trivia2);
1798
- if (labeledSpec) {
1919
+ if (!labeledSpec) {
1920
+ const fastShapes = analyzeTriviaFastPath(trivia2);
1921
+ if (fastShapes) {
1922
+ ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastShapes));
1923
+ return fnName;
1924
+ }
1925
+ } else {
1926
+ const labeledShapes = analyzeLabeledScannableRun(trivia2);
1927
+ if (labeledShapes) {
1928
+ ctx.namedFnDecls.push(buildLabeledScannableTriviaFnDecl(fnName, labeledShapes));
1929
+ return fnName;
1930
+ }
1799
1931
  const regexSpec = labeledTriviaRegexArms(trivia2);
1800
1932
  if (regexSpec) {
1801
1933
  const reNames = [];
@@ -2233,11 +2365,11 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2233
2365
  const p = def.parsers[idx];
2234
2366
  const litVal = getCoreLiteralValue(p);
2235
2367
  const litLen = litVal.length;
2236
- const litCond = emitLiteralCondition(litVal, pos);
2368
+ const litCond2 = emitLiteralCondition(litVal, pos);
2237
2369
  const kw = first ? "if" : "else if";
2238
2370
  first = false;
2239
2371
  const litEnd = `${pos} + ${litLen}`;
2240
- stmts.push(`${ind(ctx)}${kw} (${litCond}) {`);
2372
+ stmts.push(`${ind(ctx)}${kw} (${litCond2}) {`);
2241
2373
  ctx.indent++;
2242
2374
  const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
2243
2375
  stmts.push(
@@ -2566,25 +2698,34 @@ function emitNode(def, ctx, pos) {
2566
2698
  ctx.buildSrcs.push(def.buildSrc ?? null);
2567
2699
  }
2568
2700
  const i = ind(ctx);
2701
+ const capturesTrivia = mkType !== null || buildReadsTrivia(def);
2702
+ const clonesState = buildReadsState(def);
2569
2703
  const chV = v(ctx, "_ch");
2570
2704
  const rawV = v(ctx, "_raw");
2571
- const tlV = v(ctx, "_tl");
2705
+ const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
2706
+ if (!capturesTrivia) ctx.needsEmptyTl = true;
2572
2707
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
2708
+ const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
2709
+ const innerTl = capturesTrivia ? tlV : "undefined";
2573
2710
  const stmts = [
2574
- `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []`,
2711
+ allocStmt,
2575
2712
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
2576
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${tlV}`
2713
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
2577
2714
  ];
2578
2715
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2579
2716
  stmts.push(...innerStmts);
2580
2717
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
2581
2718
  stmts.push(...emitIfFail(ctx, `!${okVar}`, failBody(ctx, '"node"', pos)));
2582
- const stV = v(ctx, "_nst");
2583
- stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2719
+ let stV = "undefined";
2720
+ if (clonesState) {
2721
+ stV = v(ctx, "_nst");
2722
+ stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2723
+ }
2584
2724
  const ndV = v(ctx, "_nd");
2585
2725
  const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
2726
+ 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;
2586
2727
  stmts.push(
2587
- `${i}const ${ndV} = ${ndExpr}`,
2728
+ `${i}const ${ndV} = ${finalExpr}`,
2588
2729
  `${i}if (${sc}) ${sc}.push(${ndV})`,
2589
2730
  `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && ${ndV}._tag === 'node') ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
2590
2731
  );
@@ -2605,6 +2746,16 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2605
2746
  return { stmts, valueVar: vv, endVar: ev };
2606
2747
  }
2607
2748
  function emitLazy(p, def, ctx, pos) {
2749
+ const usage = ctx.lazyUsage;
2750
+ if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
2751
+ let resolved;
2752
+ try {
2753
+ resolved = def.thunk();
2754
+ } catch {
2755
+ return emitRuntimeFallback(p, ctx, pos);
2756
+ }
2757
+ return emit(resolved, ctx, pos);
2758
+ }
2608
2759
  if (!ctx.namedParsers.has(p)) {
2609
2760
  const fnName2 = `_pf${ctx.namedParsers.size}`;
2610
2761
  ctx.namedParsers.set(p, fnName2);
@@ -2869,6 +3020,80 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2869
3020
  return false;
2870
3021
  }
2871
3022
  }
3023
+ function childrenOf(def) {
3024
+ switch (def.tag) {
3025
+ case "sequence":
3026
+ case "choice":
3027
+ return def.parsers;
3028
+ case "many":
3029
+ case "optional":
3030
+ case "transform":
3031
+ case "trivia":
3032
+ case "label":
3033
+ case "grammar":
3034
+ case "not":
3035
+ case "node":
3036
+ case "withCtx":
3037
+ case "expect":
3038
+ return [def.parser];
3039
+ // emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
3040
+ // (a mandatory first match, then again inside the repeat loop) — two real
3041
+ // emit() call sites, so the usage analysis must see two edges here or it
3042
+ // undercounts a single-use `parser` ref as inline-safe when it's actually
3043
+ // referenced from two positions within this one compiled function.
3044
+ case "oneOrMore":
3045
+ return [def.parser, def.parser];
3046
+ case "sepBy":
3047
+ return [def.parser, def.parser, def.separator];
3048
+ case "skip":
3049
+ return [def.main, def.skipped];
3050
+ case "recover":
3051
+ return [def.parser, def.sentinel];
3052
+ case "scanTo":
3053
+ return [def.sentinel, ...def.skip];
3054
+ case "lazy":
3055
+ case "literal":
3056
+ case "regex":
3057
+ case "keywords":
3058
+ case "guard":
3059
+ case "unknown":
3060
+ return [];
3061
+ }
3062
+ }
3063
+ function analyzeLazyUsage(root) {
3064
+ return analyzeLazyUsageMulti([root]);
3065
+ }
3066
+ function analyzeLazyUsageMulti(roots) {
3067
+ const counts = /* @__PURE__ */ new Map();
3068
+ const recursive = /* @__PURE__ */ new Set();
3069
+ const descended = /* @__PURE__ */ new Set();
3070
+ const active = /* @__PURE__ */ new Set();
3071
+ function walk(p) {
3072
+ const def = p._def;
3073
+ if (def.tag === "lazy") {
3074
+ counts.set(p, (counts.get(p) ?? 0) + 1);
3075
+ if (active.has(p)) {
3076
+ recursive.add(p);
3077
+ return;
3078
+ }
3079
+ if (descended.has(p)) return;
3080
+ descended.add(p);
3081
+ let resolved;
3082
+ try {
3083
+ resolved = def.thunk();
3084
+ } catch {
3085
+ return;
3086
+ }
3087
+ active.add(p);
3088
+ walk(resolved);
3089
+ active.delete(p);
3090
+ return;
3091
+ }
3092
+ for (const child of childrenOf(def)) walk(child);
3093
+ }
3094
+ for (const root of roots) walk(root);
3095
+ return { counts, recursive };
3096
+ }
2872
3097
  function compile(combinator, mapFnSources) {
2873
3098
  const ctx = {
2874
3099
  vars: 0,
@@ -2885,13 +3110,16 @@ function compile(combinator, mapFnSources) {
2885
3110
  triviaCaptureNames: /* @__PURE__ */ new Map(),
2886
3111
  triviaFnNames: /* @__PURE__ */ new Map(),
2887
3112
  namedFnDecls: [],
2888
- capturing: hasNodeDef(combinator)
3113
+ capturing: hasNodeDef(combinator),
3114
+ lazyUsage: analyzeLazyUsage(combinator)
2889
3115
  };
2890
3116
  const r = emit(combinator, ctx, "_pos");
2891
3117
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
2892
3118
  ` : "";
2893
3119
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
3120
+ const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
2894
3121
  const source = [
3122
+ ...emptyTlDecls,
2895
3123
  ...ctx.regexDecls,
2896
3124
  "",
2897
3125
  ...namedPrelude,
@@ -2903,6 +3131,7 @@ function compile(combinator, mapFnSources) {
2903
3131
  `}`
2904
3132
  ].join("\n");
2905
3133
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3134
+ ...emptyTlDecls,
2906
3135
  ...ctx.regexDecls,
2907
3136
  collatorDecl,
2908
3137
  ...namedPrelude,
@@ -2956,10 +3185,12 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
2956
3185
  ].join("\n");
2957
3186
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
2958
3187
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
2959
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl;
3188
+ const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3189
+ const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
2960
3190
  if (!needsWrapper) return innerFn;
2961
3191
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
2962
3192
  const hoistedDecls = [
3193
+ emptyTlDecl,
2963
3194
  ...ctx.regexDecls.map((d) => ` ${d}`),
2964
3195
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
2965
3196
  mfDecl,
@@ -3244,6 +3475,31 @@ function replaceAtPath(rebuild, root, path, newNode) {
3244
3475
  function defaultRebuild(node2, children) {
3245
3476
  return { ...node2, children };
3246
3477
  }
3478
+ function shiftSpans(child, delta) {
3479
+ const c = child;
3480
+ const span = { start: c.span.start + delta, end: c.span.end + delta };
3481
+ if (Array.isArray(c.children)) {
3482
+ return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
3483
+ }
3484
+ return { ...c, span };
3485
+ }
3486
+ function graftAndShift(root, path, newNode, delta) {
3487
+ if (path.length === 0) return newNode;
3488
+ const [idx, ...rest] = path;
3489
+ const oldChildren = root.children;
3490
+ const newChildren = oldChildren.map((child, i) => {
3491
+ if (i < idx) return child;
3492
+ if (i === idx) {
3493
+ return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
3494
+ }
3495
+ return shiftSpans(child, delta);
3496
+ });
3497
+ return {
3498
+ ...root,
3499
+ span: { start: root.span.start, end: root.span.end + delta },
3500
+ children: newChildren
3501
+ };
3502
+ }
3247
3503
  var FunctionalDocImpl = class _FunctionalDocImpl {
3248
3504
  _registry;
3249
3505
  _rootRule;
@@ -3281,7 +3537,12 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3281
3537
  const r = ruleFn(newInput, node2.span.start, ctx);
3282
3538
  if (!r.ok) continue;
3283
3539
  if (r.span.end === node2.span.end + delta) {
3284
- const newTree = replaceAtPath(rebuild, this.tree, path, r.value);
3540
+ if (delta === 0) {
3541
+ const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
3542
+ return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3543
+ }
3544
+ if (this._opts.rebuild) return reparse();
3545
+ const newTree = graftAndShift(this.tree, path, r.value, delta);
3285
3546
  return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
3286
3547
  }
3287
3548
  }