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.js CHANGED
@@ -1127,24 +1127,70 @@ function not(combinator) {
1127
1127
  };
1128
1128
  }
1129
1129
 
1130
+ // src/compiler/build-arity.ts
1131
+ var PARAM_LIST_RE = (
1132
+ // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
1133
+ /^(?:function\b[^(]*\(([^)]*)\)|\(([^)]*)\)\s*=>|([A-Za-z_$][\w$]*)\s*=>)/
1134
+ );
1135
+ var CONFIRMABLE_PARAM_RE = /^[A-Za-z_$][\w$]*\s*\??\s*(?::[^,=]+)?$/;
1136
+ function confirmedBuildArity(src) {
1137
+ const s = src.trim();
1138
+ const m = PARAM_LIST_RE.exec(s);
1139
+ if (!m) return null;
1140
+ if (m[3] !== void 0) return 1;
1141
+ const inner = (m[1] ?? m[2] ?? "").trim();
1142
+ if (inner === "") return 0;
1143
+ const parts = inner.split(",");
1144
+ for (const part of parts) {
1145
+ if (!CONFIRMABLE_PARAM_RE.test(part.trim())) return null;
1146
+ }
1147
+ if (/\barguments\b/.test(s)) return null;
1148
+ return parts.length;
1149
+ }
1150
+ function buildReadsTrivia(def) {
1151
+ const src = def.buildSrc ?? def.build.toString();
1152
+ const arity = confirmedBuildArity(src);
1153
+ if (arity === null) return true;
1154
+ return arity >= 4;
1155
+ }
1156
+ function buildReadsState(def) {
1157
+ const src = def.buildSrc ?? def.build.toString();
1158
+ const arity = confirmedBuildArity(src);
1159
+ if (arity === null) return true;
1160
+ return arity >= 5;
1161
+ }
1162
+
1130
1163
  // src/combinators/node.ts
1131
- function node(type, combinator, build) {
1164
+ function collapseChild(child) {
1165
+ return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1166
+ }
1167
+ function node(type, combinator, build, opts) {
1132
1168
  const meta = {
1133
1169
  firstSet: combinator._meta.firstSet,
1134
1170
  canMatchNewline: combinator._meta.canMatchNewline,
1135
1171
  isTrivia: false
1136
1172
  };
1173
+ const collapse = opts?.collapse === true;
1174
+ const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1175
+ const capturesTrivia = buildReadsTrivia(def);
1176
+ const clonesState = buildReadsState(def);
1137
1177
  return {
1138
1178
  _tag: "node",
1139
1179
  _meta: meta,
1140
- _def: { tag: "node", type, parser: combinator, build },
1180
+ _def: def,
1141
1181
  parse(input, pos, ctx) {
1142
1182
  const saved = beginCstNodeCapture(ctx);
1183
+ if (!capturesTrivia) ctx.captureTrivia = false;
1143
1184
  const r = combinator.parse(input, pos, ctx);
1144
1185
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1145
1186
  if (!r.ok) return r;
1146
- const state = ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1147
- const built = build(children, rawChildren, r.span, triviaLog, state);
1187
+ const built = collapse && children.length === 1 ? collapseChild(children[0]) : build(
1188
+ children,
1189
+ rawChildren,
1190
+ r.span,
1191
+ triviaLog,
1192
+ clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0
1193
+ );
1148
1194
  const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1149
1195
  const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1150
1196
  if (saved.buf !== void 0 || saved.ch !== void 0) {
@@ -1294,14 +1340,140 @@ function parse(combinator, input, opts = {}) {
1294
1340
  return withErrors;
1295
1341
  }
1296
1342
 
1343
+ // src/compiler/scannable-run.ts
1344
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1345
+ var META = new Set("()[]{}*+?|^$.".split(""));
1346
+ function parseClassRanges(body) {
1347
+ const ranges = [];
1348
+ let i = 0;
1349
+ const readCp = () => {
1350
+ const ch = body[i];
1351
+ if (ch === void 0) return null;
1352
+ if (ch === "\\") {
1353
+ const e = body[i + 1];
1354
+ if (e === void 0) return null;
1355
+ i += 2;
1356
+ return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1357
+ }
1358
+ i += ch.length;
1359
+ return ch.codePointAt(0);
1360
+ };
1361
+ while (i < body.length) {
1362
+ const lo = readCp();
1363
+ if (lo === null) return null;
1364
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1365
+ i += 1;
1366
+ const hi = readCp();
1367
+ if (hi === null) return null;
1368
+ ranges.push([lo, hi]);
1369
+ } else {
1370
+ ranges.push([lo, lo]);
1371
+ }
1372
+ }
1373
+ return ranges.length ? ranges : null;
1374
+ }
1375
+ function literalCodePoints(frag) {
1376
+ const out = [];
1377
+ let i = 0;
1378
+ while (i < frag.length) {
1379
+ const ch = frag[i];
1380
+ if (ch === "\\") {
1381
+ const e = frag[i + 1];
1382
+ if (e === void 0) return null;
1383
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1384
+ i += 2;
1385
+ continue;
1386
+ }
1387
+ if (META.has(ch)) return null;
1388
+ out.push(ch.codePointAt(0));
1389
+ i += 1;
1390
+ }
1391
+ return out.length ? out : null;
1392
+ }
1393
+ function parseScanShape(source) {
1394
+ let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1395
+ if (m) {
1396
+ if (m[1].startsWith("^")) return null;
1397
+ const ranges = parseClassRanges(m[1]);
1398
+ return ranges ? { kind: "chars", ranges } : null;
1399
+ }
1400
+ m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1401
+ if (m) {
1402
+ const open = literalCodePoints(m[1]);
1403
+ const stop = parseClassRanges(m[2]);
1404
+ if (open && stop) return { kind: "until", open, stop };
1405
+ return null;
1406
+ }
1407
+ if (!source.includes("\\\\")) {
1408
+ m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1409
+ if (m && m[1] && m[2]) {
1410
+ const open = literalCodePoints(m[1]);
1411
+ const close = literalCodePoints(m[2]);
1412
+ if (open && close) return { kind: "delimited", open, close };
1413
+ }
1414
+ }
1415
+ return null;
1416
+ }
1417
+ var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1418
+ var litCond = (base, cps, firstVar) => cps.map(
1419
+ (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1420
+ ).join(" && ");
1421
+ function advanceExpr(shape) {
1422
+ if (shape.kind === "chars") {
1423
+ return {
1424
+ setup: [
1425
+ ` _e++`,
1426
+ ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1427
+ ],
1428
+ endVar: "_e"
1429
+ };
1430
+ }
1431
+ if (shape.kind === "until") {
1432
+ return {
1433
+ setup: [
1434
+ ` let j = _e + ${shape.open.length}`,
1435
+ ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1436
+ ],
1437
+ endVar: "j"
1438
+ };
1439
+ }
1440
+ const n = shape.close.length;
1441
+ return {
1442
+ setup: [
1443
+ ` let j = _e + ${shape.open.length}`,
1444
+ ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1445
+ ],
1446
+ endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1447
+ };
1448
+ }
1449
+ function guardLine(shape) {
1450
+ const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1451
+ return ` if (${cond}) {`;
1452
+ }
1453
+ function scanBranch(shape) {
1454
+ if (shape.kind === "chars") {
1455
+ return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1456
+ }
1457
+ const { setup, endVar } = advanceExpr(shape);
1458
+ return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1459
+ }
1460
+ function captureChunk(startVar, kindIndex) {
1461
+ return [
1462
+ ` if (_cap) {`,
1463
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1464
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1465
+ ` }`
1466
+ ];
1467
+ }
1468
+ function scanBranchLabeled(shape, kindIndex) {
1469
+ const { setup, endVar } = advanceExpr(shape);
1470
+ const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1471
+ if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1472
+ lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1473
+ return lines.join("\n");
1474
+ }
1475
+
1297
1476
  // src/compiler/trivia-fast-path.ts
1298
- var BLOCK_COMMENT = String.raw`\/\*(?:[^*]|\*(?!\/))*\*\/`;
1299
- var WS_CLASS_SOURCES = /* @__PURE__ */ new Set([
1300
- String.raw`[ \t\n\r\f]+`,
1301
- String.raw`[ \t\n]+`,
1302
- String.raw`[ \t]+`,
1303
- String.raw`[\t\n\f\r ]+`
1304
- ]);
1305
1477
  function unwrapTrivia2(p) {
1306
1478
  let cur = p;
1307
1479
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1317,36 +1489,30 @@ function choiceArms(p) {
1317
1489
  if (p._def.tag === "choice") return p._def.parsers;
1318
1490
  return null;
1319
1491
  }
1320
- function isWsClassSource(source) {
1321
- return WS_CLASS_SOURCES.has(source);
1322
- }
1323
- function isBlockCommentSource(source) {
1324
- return source === BLOCK_COMMENT;
1492
+ function armShape(arm) {
1493
+ const src = getCoreRegexDef(arm)?.source;
1494
+ return src ? parseScanShape(src) : null;
1325
1495
  }
1326
1496
  function analyzeTriviaFastPath(trivia2) {
1327
1497
  const core = unwrapTrivia2(trivia2);
1328
- const direct = getCoreRegexDef(core)?.source;
1329
- if (direct) {
1330
- if (isWsClassSource(direct)) return "wsOnly";
1331
- return null;
1498
+ const directSrc = getCoreRegexDef(core)?.source;
1499
+ if (directSrc) {
1500
+ const shape = parseScanShape(directSrc);
1501
+ return shape && shape.kind === "chars" ? [shape] : null;
1332
1502
  }
1333
1503
  const inner = oneOrMoreInner(core);
1334
1504
  if (!inner) return null;
1335
- const innerSrc = getCoreRegexDef(inner)?.source;
1336
- if (innerSrc && isWsClassSource(innerSrc)) return "wsOnly";
1505
+ const innerShape = armShape(inner);
1506
+ if (innerShape) return innerShape.kind === "chars" ? [innerShape] : null;
1337
1507
  const arms = choiceArms(inner);
1338
- if (!arms || arms.length !== 2) return null;
1339
- let hasWs = false;
1340
- let hasComment = false;
1508
+ if (!arms || arms.length < 2) return null;
1509
+ const shapes = [];
1341
1510
  for (const arm of arms) {
1342
- const src = getCoreRegexDef(arm)?.source;
1343
- if (!src) return null;
1344
- if (isWsClassSource(src)) hasWs = true;
1345
- else if (isBlockCommentSource(src)) hasComment = true;
1346
- else return null;
1511
+ const shape = armShape(arm);
1512
+ if (!shape) return null;
1513
+ shapes.push(shape);
1347
1514
  }
1348
- if (hasWs && hasComment) return "wsComments";
1349
- return null;
1515
+ return shapes;
1350
1516
  }
1351
1517
  var CAP_RECORD = [
1352
1518
  ` if (_cap && _e > _pos) {`,
@@ -1354,80 +1520,49 @@ var CAP_RECORD = [
1354
1520
  ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_pos, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0)`,
1355
1521
  ` }`
1356
1522
  ].join("\n");
1357
- var WS_ONLY_LOOP = [
1358
- ` while (_e < input.length) {`,
1359
- ` const c = input.charCodeAt(_e)`,
1360
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
1361
- ` break`,
1362
- ` }`
1363
- ].join("\n");
1364
- var WS_COMMENTS_LOOP = [
1365
- ` while (_e < input.length) {`,
1366
- ` const c = input.charCodeAt(_e)`,
1367
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) { _e++; continue }`,
1368
- ` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
1369
- ` let j = _e + 2`,
1370
- ` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
1371
- ` _e = j + 2 <= input.length ? j + 2 : input.length`,
1372
- ` continue`,
1373
- ` }`,
1374
- ` break`,
1375
- ` }`
1376
- ].join("\n");
1377
- var CAP_CHUNK = (wsKind, commentKind) => [
1378
- ` if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) {`,
1379
- ` const _cs = _e`,
1380
- ` _e++`,
1381
- ` while (_e < input.length) {`,
1382
- ` const c2 = input.charCodeAt(_e)`,
1383
- ` if (c2 === 32 || c2 === 9 || c2 === 10 || c2 === 13 || c2 === 12) _e++`,
1384
- ` else break`,
1385
- ` }`,
1386
- ` if (_cap) {`,
1387
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${wsKind})`,
1388
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${wsKind})`,
1389
- ` }`,
1390
- ` continue`,
1391
- ` }`,
1392
- ` if (c === 47 && input.charCodeAt(_e + 1) === 42) {`,
1393
- ` const _cs = _e`,
1394
- ` let j = _e + 2`,
1395
- ` while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++`,
1396
- ` _e = j + 2 <= input.length ? j + 2 : input.length`,
1397
- ` if (_cap) {`,
1398
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_cs, _e, ${commentKind})`,
1399
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_cs, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${commentKind})`,
1400
- ` }`,
1401
- ` continue`,
1402
- ` }`
1403
- ].join("\n");
1404
- var WS_COMMENTS_LOOP_LABELED = (wsKind, commentKind) => [
1405
- ` while (_e < input.length) {`,
1406
- ` const c = input.charCodeAt(_e)`,
1407
- CAP_CHUNK(wsKind, commentKind),
1408
- ` break`,
1409
- ` }`
1410
- ].join("\n");
1411
- function buildFastTriviaFnDecl(fnName, kind, kindIndices) {
1412
- const loop = kind === "wsComments" && kindIndices ? WS_COMMENTS_LOOP_LABELED(kindIndices.ws, kindIndices.comment) : kind === "wsComments" ? WS_COMMENTS_LOOP : WS_ONLY_LOOP;
1413
- const cap = kindIndices ? "" : CAP_RECORD;
1414
- const lines = [
1523
+ function composeFastLoop(shapes) {
1524
+ return [
1525
+ ` while (_e < input.length) {`,
1526
+ ` const c = input.charCodeAt(_e)`,
1527
+ ...shapes.map(scanBranch),
1528
+ ` break`,
1529
+ ` }`
1530
+ ].join("\n");
1531
+ }
1532
+ function buildFastTriviaFnDecl(fnName, shapes) {
1533
+ return [
1415
1534
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1416
1535
  ` let _e = _pos`,
1417
- loop
1418
- ];
1419
- if (cap) lines.push(cap);
1420
- lines.push(` return _e`, `}`);
1421
- return lines.join("\n");
1536
+ composeFastLoop(shapes),
1537
+ CAP_RECORD,
1538
+ ` return _e`,
1539
+ `}`
1540
+ ].join("\n");
1422
1541
  }
1423
- function regexArmIndex(spec, isComment) {
1542
+ function analyzeLabeledScannableRun(trivia2) {
1543
+ const spec = analyzeLabeledTrivia(trivia2);
1544
+ if (!spec) return null;
1545
+ const out = [];
1424
1546
  for (const arm of spec.arms) {
1425
1547
  const src = getCoreRegexDef(arm.parser)?.source;
1426
- if (!src) return null;
1427
- const comment = src.includes("\\*") || src.startsWith("\\/\\/");
1428
- if (comment === isComment) return arm.kindIndex;
1548
+ const shape = src ? parseScanShape(src) : null;
1549
+ if (!shape) return null;
1550
+ out.push({ shape, kindIndex: arm.kindIndex });
1429
1551
  }
1430
- return null;
1552
+ return out;
1553
+ }
1554
+ function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1555
+ return [
1556
+ `function ${fnName}(input, _pos, _ctx, _cap) {`,
1557
+ ` let _e = _pos`,
1558
+ ` while (_e < input.length) {`,
1559
+ ` const c = input.charCodeAt(_e)`,
1560
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1561
+ ` break`,
1562
+ ` }`,
1563
+ ` return _e`,
1564
+ `}`
1565
+ ].join("\n");
1431
1566
  }
1432
1567
  function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1433
1568
  const tryArms = spec.arms.map((arm, i) => {
@@ -1460,14 +1595,6 @@ function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1460
1595
  `}`
1461
1596
  ].join("\n");
1462
1597
  }
1463
- function labeledTriviaKindIndices(trivia2) {
1464
- const spec = analyzeLabeledTrivia(trivia2);
1465
- if (!spec || spec.arms.length !== 2) return null;
1466
- const ws = regexArmIndex(spec, false);
1467
- const comment = regexArmIndex(spec, true);
1468
- if (ws === null || comment === null) return null;
1469
- return { ws, comment };
1470
- }
1471
1598
  function labeledTriviaRegexArms(trivia2) {
1472
1599
  const spec = analyzeLabeledTrivia(trivia2);
1473
1600
  if (!spec) return null;
@@ -1715,14 +1842,19 @@ function ensureTriviaFn(ctx) {
1715
1842
  const fnName = `_tf${ctx.triviaFnNames.size}`;
1716
1843
  ctx.triviaFnNames.set(trivia2, fnName);
1717
1844
  ctx.triviaCaptureNames.set(trivia2, fnName);
1718
- const fastKind = analyzeTriviaFastPath(trivia2);
1719
- const kindIndices = labeledTriviaKindIndices(trivia2);
1720
- if (fastKind) {
1721
- ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastKind, kindIndices ?? void 0));
1722
- return fnName;
1723
- }
1724
1845
  const labeledSpec = analyzeLabeledTrivia(trivia2);
1725
- if (labeledSpec) {
1846
+ if (!labeledSpec) {
1847
+ const fastShapes = analyzeTriviaFastPath(trivia2);
1848
+ if (fastShapes) {
1849
+ ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastShapes));
1850
+ return fnName;
1851
+ }
1852
+ } else {
1853
+ const labeledShapes = analyzeLabeledScannableRun(trivia2);
1854
+ if (labeledShapes) {
1855
+ ctx.namedFnDecls.push(buildLabeledScannableTriviaFnDecl(fnName, labeledShapes));
1856
+ return fnName;
1857
+ }
1726
1858
  const regexSpec = labeledTriviaRegexArms(trivia2);
1727
1859
  if (regexSpec) {
1728
1860
  const reNames = [];
@@ -2160,11 +2292,11 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2160
2292
  const p = def.parsers[idx];
2161
2293
  const litVal = getCoreLiteralValue(p);
2162
2294
  const litLen = litVal.length;
2163
- const litCond = emitLiteralCondition(litVal, pos);
2295
+ const litCond2 = emitLiteralCondition(litVal, pos);
2164
2296
  const kw = first ? "if" : "else if";
2165
2297
  first = false;
2166
2298
  const litEnd = `${pos} + ${litLen}`;
2167
- stmts.push(`${ind(ctx)}${kw} (${litCond}) {`);
2299
+ stmts.push(`${ind(ctx)}${kw} (${litCond2}) {`);
2168
2300
  ctx.indent++;
2169
2301
  const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
2170
2302
  stmts.push(
@@ -2493,25 +2625,34 @@ function emitNode(def, ctx, pos) {
2493
2625
  ctx.buildSrcs.push(def.buildSrc ?? null);
2494
2626
  }
2495
2627
  const i = ind(ctx);
2628
+ const capturesTrivia = mkType !== null || buildReadsTrivia(def);
2629
+ const clonesState = buildReadsState(def);
2496
2630
  const chV = v(ctx, "_ch");
2497
2631
  const rawV = v(ctx, "_raw");
2498
- const tlV = v(ctx, "_tl");
2632
+ const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
2633
+ if (!capturesTrivia) ctx.needsEmptyTl = true;
2499
2634
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
2635
+ const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
2636
+ const innerTl = capturesTrivia ? tlV : "undefined";
2500
2637
  const stmts = [
2501
- `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []`,
2638
+ allocStmt,
2502
2639
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
2503
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${tlV}`
2640
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
2504
2641
  ];
2505
2642
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2506
2643
  stmts.push(...innerStmts);
2507
2644
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
2508
2645
  stmts.push(...emitIfFail(ctx, `!${okVar}`, failBody(ctx, '"node"', pos)));
2509
- const stV = v(ctx, "_nst");
2510
- stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2646
+ let stV = "undefined";
2647
+ if (clonesState) {
2648
+ stV = v(ctx, "_nst");
2649
+ stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
2650
+ }
2511
2651
  const ndV = v(ctx, "_nd");
2512
2652
  const ndExpr = mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `_build[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
2653
+ 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;
2513
2654
  stmts.push(
2514
- `${i}const ${ndV} = ${ndExpr}`,
2655
+ `${i}const ${ndV} = ${finalExpr}`,
2515
2656
  `${i}if (${sc}) ${sc}.push(${ndV})`,
2516
2657
  `${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} } })`
2517
2658
  );
@@ -2532,6 +2673,16 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2532
2673
  return { stmts, valueVar: vv, endVar: ev };
2533
2674
  }
2534
2675
  function emitLazy(p, def, ctx, pos) {
2676
+ const usage = ctx.lazyUsage;
2677
+ if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
2678
+ let resolved;
2679
+ try {
2680
+ resolved = def.thunk();
2681
+ } catch {
2682
+ return emitRuntimeFallback(p, ctx, pos);
2683
+ }
2684
+ return emit(resolved, ctx, pos);
2685
+ }
2535
2686
  if (!ctx.namedParsers.has(p)) {
2536
2687
  const fnName2 = `_pf${ctx.namedParsers.size}`;
2537
2688
  ctx.namedParsers.set(p, fnName2);
@@ -2796,6 +2947,80 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2796
2947
  return false;
2797
2948
  }
2798
2949
  }
2950
+ function childrenOf(def) {
2951
+ switch (def.tag) {
2952
+ case "sequence":
2953
+ case "choice":
2954
+ return def.parsers;
2955
+ case "many":
2956
+ case "optional":
2957
+ case "transform":
2958
+ case "trivia":
2959
+ case "label":
2960
+ case "grammar":
2961
+ case "not":
2962
+ case "node":
2963
+ case "withCtx":
2964
+ case "expect":
2965
+ return [def.parser];
2966
+ // emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
2967
+ // (a mandatory first match, then again inside the repeat loop) — two real
2968
+ // emit() call sites, so the usage analysis must see two edges here or it
2969
+ // undercounts a single-use `parser` ref as inline-safe when it's actually
2970
+ // referenced from two positions within this one compiled function.
2971
+ case "oneOrMore":
2972
+ return [def.parser, def.parser];
2973
+ case "sepBy":
2974
+ return [def.parser, def.parser, def.separator];
2975
+ case "skip":
2976
+ return [def.main, def.skipped];
2977
+ case "recover":
2978
+ return [def.parser, def.sentinel];
2979
+ case "scanTo":
2980
+ return [def.sentinel, ...def.skip];
2981
+ case "lazy":
2982
+ case "literal":
2983
+ case "regex":
2984
+ case "keywords":
2985
+ case "guard":
2986
+ case "unknown":
2987
+ return [];
2988
+ }
2989
+ }
2990
+ function analyzeLazyUsage(root) {
2991
+ return analyzeLazyUsageMulti([root]);
2992
+ }
2993
+ function analyzeLazyUsageMulti(roots) {
2994
+ const counts = /* @__PURE__ */ new Map();
2995
+ const recursive = /* @__PURE__ */ new Set();
2996
+ const descended = /* @__PURE__ */ new Set();
2997
+ const active = /* @__PURE__ */ new Set();
2998
+ function walk(p) {
2999
+ const def = p._def;
3000
+ if (def.tag === "lazy") {
3001
+ counts.set(p, (counts.get(p) ?? 0) + 1);
3002
+ if (active.has(p)) {
3003
+ recursive.add(p);
3004
+ return;
3005
+ }
3006
+ if (descended.has(p)) return;
3007
+ descended.add(p);
3008
+ let resolved;
3009
+ try {
3010
+ resolved = def.thunk();
3011
+ } catch {
3012
+ return;
3013
+ }
3014
+ active.add(p);
3015
+ walk(resolved);
3016
+ active.delete(p);
3017
+ return;
3018
+ }
3019
+ for (const child of childrenOf(def)) walk(child);
3020
+ }
3021
+ for (const root of roots) walk(root);
3022
+ return { counts, recursive };
3023
+ }
2799
3024
  function compile(combinator, mapFnSources) {
2800
3025
  const ctx = {
2801
3026
  vars: 0,
@@ -2812,13 +3037,16 @@ function compile(combinator, mapFnSources) {
2812
3037
  triviaCaptureNames: /* @__PURE__ */ new Map(),
2813
3038
  triviaFnNames: /* @__PURE__ */ new Map(),
2814
3039
  namedFnDecls: [],
2815
- capturing: hasNodeDef(combinator)
3040
+ capturing: hasNodeDef(combinator),
3041
+ lazyUsage: analyzeLazyUsage(combinator)
2816
3042
  };
2817
3043
  const r = emit(combinator, ctx, "_pos");
2818
3044
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
2819
3045
  ` : "";
2820
3046
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
3047
+ const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
2821
3048
  const source = [
3049
+ ...emptyTlDecls,
2822
3050
  ...ctx.regexDecls,
2823
3051
  "",
2824
3052
  ...namedPrelude,
@@ -2830,6 +3058,7 @@ function compile(combinator, mapFnSources) {
2830
3058
  `}`
2831
3059
  ].join("\n");
2832
3060
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3061
+ ...emptyTlDecls,
2833
3062
  ...ctx.regexDecls,
2834
3063
  collatorDecl,
2835
3064
  ...namedPrelude,
@@ -2883,10 +3112,12 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
2883
3112
  ].join("\n");
2884
3113
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
2885
3114
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
2886
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl;
3115
+ const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3116
+ const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
2887
3117
  if (!needsWrapper) return innerFn;
2888
3118
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
2889
3119
  const hoistedDecls = [
3120
+ emptyTlDecl,
2890
3121
  ...ctx.regexDecls.map((d) => ` ${d}`),
2891
3122
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
2892
3123
  mfDecl,
@@ -3171,6 +3402,31 @@ function replaceAtPath(rebuild, root, path, newNode) {
3171
3402
  function defaultRebuild(node2, children) {
3172
3403
  return { ...node2, children };
3173
3404
  }
3405
+ function shiftSpans(child, delta) {
3406
+ const c = child;
3407
+ const span = { start: c.span.start + delta, end: c.span.end + delta };
3408
+ if (Array.isArray(c.children)) {
3409
+ return { ...c, span, children: c.children.map((g) => shiftSpans(g, delta)) };
3410
+ }
3411
+ return { ...c, span };
3412
+ }
3413
+ function graftAndShift(root, path, newNode, delta) {
3414
+ if (path.length === 0) return newNode;
3415
+ const [idx, ...rest] = path;
3416
+ const oldChildren = root.children;
3417
+ const newChildren = oldChildren.map((child, i) => {
3418
+ if (i < idx) return child;
3419
+ if (i === idx) {
3420
+ return rest.length === 0 ? newNode : graftAndShift(child, rest, newNode, delta);
3421
+ }
3422
+ return shiftSpans(child, delta);
3423
+ });
3424
+ return {
3425
+ ...root,
3426
+ span: { start: root.span.start, end: root.span.end + delta },
3427
+ children: newChildren
3428
+ };
3429
+ }
3174
3430
  var FunctionalDocImpl = class _FunctionalDocImpl {
3175
3431
  _registry;
3176
3432
  _rootRule;
@@ -3208,7 +3464,12 @@ var FunctionalDocImpl = class _FunctionalDocImpl {
3208
3464
  const r = ruleFn(newInput, node2.span.start, ctx);
3209
3465
  if (!r.ok) continue;
3210
3466
  if (r.span.end === node2.span.end + delta) {
3211
- const newTree = replaceAtPath(rebuild, this.tree, path, r.value);
3467
+ if (delta === 0) {
3468
+ const newTree2 = replaceAtPath(rebuild, this.tree, path, r.value);
3469
+ return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree2, [], newInput);
3470
+ }
3471
+ if (this._opts.rebuild) return reparse();
3472
+ const newTree = graftAndShift(this.tree, path, r.value, delta);
3212
3473
  return new _FunctionalDocImpl(this._registry, this._rootRule, this._opts, newTree, [], newInput);
3213
3474
  }
3214
3475
  }