parseman 0.8.0 → 0.9.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
@@ -1294,14 +1294,140 @@ function parse(combinator, input, opts = {}) {
1294
1294
  return withErrors;
1295
1295
  }
1296
1296
 
1297
+ // src/compiler/scannable-run.ts
1298
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1299
+ var META = new Set("()[]{}*+?|^$.".split(""));
1300
+ function parseClassRanges(body) {
1301
+ const ranges = [];
1302
+ let i = 0;
1303
+ const readCp = () => {
1304
+ const ch = body[i];
1305
+ if (ch === void 0) return null;
1306
+ if (ch === "\\") {
1307
+ const e = body[i + 1];
1308
+ if (e === void 0) return null;
1309
+ i += 2;
1310
+ return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1311
+ }
1312
+ i += ch.length;
1313
+ return ch.codePointAt(0);
1314
+ };
1315
+ while (i < body.length) {
1316
+ const lo = readCp();
1317
+ if (lo === null) return null;
1318
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1319
+ i += 1;
1320
+ const hi = readCp();
1321
+ if (hi === null) return null;
1322
+ ranges.push([lo, hi]);
1323
+ } else {
1324
+ ranges.push([lo, lo]);
1325
+ }
1326
+ }
1327
+ return ranges.length ? ranges : null;
1328
+ }
1329
+ function literalCodePoints(frag) {
1330
+ const out = [];
1331
+ let i = 0;
1332
+ while (i < frag.length) {
1333
+ const ch = frag[i];
1334
+ if (ch === "\\") {
1335
+ const e = frag[i + 1];
1336
+ if (e === void 0) return null;
1337
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1338
+ i += 2;
1339
+ continue;
1340
+ }
1341
+ if (META.has(ch)) return null;
1342
+ out.push(ch.codePointAt(0));
1343
+ i += 1;
1344
+ }
1345
+ return out.length ? out : null;
1346
+ }
1347
+ function parseScanShape(source) {
1348
+ let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1349
+ if (m) {
1350
+ if (m[1].startsWith("^")) return null;
1351
+ const ranges = parseClassRanges(m[1]);
1352
+ return ranges ? { kind: "chars", ranges } : null;
1353
+ }
1354
+ m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1355
+ if (m) {
1356
+ const open = literalCodePoints(m[1]);
1357
+ const stop = parseClassRanges(m[2]);
1358
+ if (open && stop) return { kind: "until", open, stop };
1359
+ return null;
1360
+ }
1361
+ if (!source.includes("\\\\")) {
1362
+ m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
1363
+ if (m && m[1] && m[2]) {
1364
+ const open = literalCodePoints(m[1]);
1365
+ const close = literalCodePoints(m[2]);
1366
+ if (open && close) return { kind: "delimited", open, close };
1367
+ }
1368
+ }
1369
+ return null;
1370
+ }
1371
+ var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1372
+ var litCond = (base, cps, firstVar) => cps.map(
1373
+ (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1374
+ ).join(" && ");
1375
+ function advanceExpr(shape) {
1376
+ if (shape.kind === "chars") {
1377
+ return {
1378
+ setup: [
1379
+ ` _e++`,
1380
+ ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1381
+ ],
1382
+ endVar: "_e"
1383
+ };
1384
+ }
1385
+ if (shape.kind === "until") {
1386
+ return {
1387
+ setup: [
1388
+ ` let j = _e + ${shape.open.length}`,
1389
+ ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1390
+ ],
1391
+ endVar: "j"
1392
+ };
1393
+ }
1394
+ const n = shape.close.length;
1395
+ return {
1396
+ setup: [
1397
+ ` let j = _e + ${shape.open.length}`,
1398
+ ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1399
+ ],
1400
+ endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1401
+ };
1402
+ }
1403
+ function guardLine(shape) {
1404
+ const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1405
+ return ` if (${cond}) {`;
1406
+ }
1407
+ function scanBranch(shape) {
1408
+ if (shape.kind === "chars") {
1409
+ return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1410
+ }
1411
+ const { setup, endVar } = advanceExpr(shape);
1412
+ return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1413
+ }
1414
+ function captureChunk(startVar, kindIndex) {
1415
+ return [
1416
+ ` if (_cap) {`,
1417
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1418
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1419
+ ` }`
1420
+ ];
1421
+ }
1422
+ function scanBranchLabeled(shape, kindIndex) {
1423
+ const { setup, endVar } = advanceExpr(shape);
1424
+ const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1425
+ if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1426
+ lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1427
+ return lines.join("\n");
1428
+ }
1429
+
1297
1430
  // 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
1431
  function unwrapTrivia2(p) {
1306
1432
  let cur = p;
1307
1433
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1317,36 +1443,30 @@ function choiceArms(p) {
1317
1443
  if (p._def.tag === "choice") return p._def.parsers;
1318
1444
  return null;
1319
1445
  }
1320
- function isWsClassSource(source) {
1321
- return WS_CLASS_SOURCES.has(source);
1322
- }
1323
- function isBlockCommentSource(source) {
1324
- return source === BLOCK_COMMENT;
1446
+ function armShape(arm) {
1447
+ const src = getCoreRegexDef(arm)?.source;
1448
+ return src ? parseScanShape(src) : null;
1325
1449
  }
1326
1450
  function analyzeTriviaFastPath(trivia2) {
1327
1451
  const core = unwrapTrivia2(trivia2);
1328
- const direct = getCoreRegexDef(core)?.source;
1329
- if (direct) {
1330
- if (isWsClassSource(direct)) return "wsOnly";
1331
- return null;
1452
+ const directSrc = getCoreRegexDef(core)?.source;
1453
+ if (directSrc) {
1454
+ const shape = parseScanShape(directSrc);
1455
+ return shape && shape.kind === "chars" ? [shape] : null;
1332
1456
  }
1333
1457
  const inner = oneOrMoreInner(core);
1334
1458
  if (!inner) return null;
1335
- const innerSrc = getCoreRegexDef(inner)?.source;
1336
- if (innerSrc && isWsClassSource(innerSrc)) return "wsOnly";
1459
+ const innerShape = armShape(inner);
1460
+ if (innerShape) return innerShape.kind === "chars" ? [innerShape] : null;
1337
1461
  const arms = choiceArms(inner);
1338
- if (!arms || arms.length !== 2) return null;
1339
- let hasWs = false;
1340
- let hasComment = false;
1462
+ if (!arms || arms.length < 2) return null;
1463
+ const shapes = [];
1341
1464
  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;
1465
+ const shape = armShape(arm);
1466
+ if (!shape) return null;
1467
+ shapes.push(shape);
1347
1468
  }
1348
- if (hasWs && hasComment) return "wsComments";
1349
- return null;
1469
+ return shapes;
1350
1470
  }
1351
1471
  var CAP_RECORD = [
1352
1472
  ` if (_cap && _e > _pos) {`,
@@ -1354,80 +1474,49 @@ var CAP_RECORD = [
1354
1474
  ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_pos, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0)`,
1355
1475
  ` }`
1356
1476
  ].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 = [
1477
+ function composeFastLoop(shapes) {
1478
+ return [
1479
+ ` while (_e < input.length) {`,
1480
+ ` const c = input.charCodeAt(_e)`,
1481
+ ...shapes.map(scanBranch),
1482
+ ` break`,
1483
+ ` }`
1484
+ ].join("\n");
1485
+ }
1486
+ function buildFastTriviaFnDecl(fnName, shapes) {
1487
+ return [
1415
1488
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1416
1489
  ` let _e = _pos`,
1417
- loop
1418
- ];
1419
- if (cap) lines.push(cap);
1420
- lines.push(` return _e`, `}`);
1421
- return lines.join("\n");
1490
+ composeFastLoop(shapes),
1491
+ CAP_RECORD,
1492
+ ` return _e`,
1493
+ `}`
1494
+ ].join("\n");
1422
1495
  }
1423
- function regexArmIndex(spec, isComment) {
1496
+ function analyzeLabeledScannableRun(trivia2) {
1497
+ const spec = analyzeLabeledTrivia(trivia2);
1498
+ if (!spec) return null;
1499
+ const out = [];
1424
1500
  for (const arm of spec.arms) {
1425
1501
  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;
1502
+ const shape = src ? parseScanShape(src) : null;
1503
+ if (!shape) return null;
1504
+ out.push({ shape, kindIndex: arm.kindIndex });
1429
1505
  }
1430
- return null;
1506
+ return out;
1507
+ }
1508
+ function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1509
+ return [
1510
+ `function ${fnName}(input, _pos, _ctx, _cap) {`,
1511
+ ` let _e = _pos`,
1512
+ ` while (_e < input.length) {`,
1513
+ ` const c = input.charCodeAt(_e)`,
1514
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1515
+ ` break`,
1516
+ ` }`,
1517
+ ` return _e`,
1518
+ `}`
1519
+ ].join("\n");
1431
1520
  }
1432
1521
  function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1433
1522
  const tryArms = spec.arms.map((arm, i) => {
@@ -1460,14 +1549,6 @@ function buildLabeledRegexTriviaFnDecl(fnName, spec, reNames) {
1460
1549
  `}`
1461
1550
  ].join("\n");
1462
1551
  }
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
1552
  function labeledTriviaRegexArms(trivia2) {
1472
1553
  const spec = analyzeLabeledTrivia(trivia2);
1473
1554
  if (!spec) return null;
@@ -1715,14 +1796,19 @@ function ensureTriviaFn(ctx) {
1715
1796
  const fnName = `_tf${ctx.triviaFnNames.size}`;
1716
1797
  ctx.triviaFnNames.set(trivia2, fnName);
1717
1798
  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
1799
  const labeledSpec = analyzeLabeledTrivia(trivia2);
1725
- if (labeledSpec) {
1800
+ if (!labeledSpec) {
1801
+ const fastShapes = analyzeTriviaFastPath(trivia2);
1802
+ if (fastShapes) {
1803
+ ctx.namedFnDecls.push(buildFastTriviaFnDecl(fnName, fastShapes));
1804
+ return fnName;
1805
+ }
1806
+ } else {
1807
+ const labeledShapes = analyzeLabeledScannableRun(trivia2);
1808
+ if (labeledShapes) {
1809
+ ctx.namedFnDecls.push(buildLabeledScannableTriviaFnDecl(fnName, labeledShapes));
1810
+ return fnName;
1811
+ }
1726
1812
  const regexSpec = labeledTriviaRegexArms(trivia2);
1727
1813
  if (regexSpec) {
1728
1814
  const reNames = [];
@@ -2160,11 +2246,11 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2160
2246
  const p = def.parsers[idx];
2161
2247
  const litVal = getCoreLiteralValue(p);
2162
2248
  const litLen = litVal.length;
2163
- const litCond = emitLiteralCondition(litVal, pos);
2249
+ const litCond2 = emitLiteralCondition(litVal, pos);
2164
2250
  const kw = first ? "if" : "else if";
2165
2251
  first = false;
2166
2252
  const litEnd = `${pos} + ${litLen}`;
2167
- stmts.push(`${ind(ctx)}${kw} (${litCond}) {`);
2253
+ stmts.push(`${ind(ctx)}${kw} (${litCond2}) {`);
2168
2254
  ctx.indent++;
2169
2255
  const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
2170
2256
  stmts.push(
@@ -2532,6 +2618,16 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2532
2618
  return { stmts, valueVar: vv, endVar: ev };
2533
2619
  }
2534
2620
  function emitLazy(p, def, ctx, pos) {
2621
+ const usage = ctx.lazyUsage;
2622
+ if (usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
2623
+ let resolved;
2624
+ try {
2625
+ resolved = def.thunk();
2626
+ } catch {
2627
+ return emitRuntimeFallback(p, ctx, pos);
2628
+ }
2629
+ return emit(resolved, ctx, pos);
2630
+ }
2535
2631
  if (!ctx.namedParsers.has(p)) {
2536
2632
  const fnName2 = `_pf${ctx.namedParsers.size}`;
2537
2633
  ctx.namedParsers.set(p, fnName2);
@@ -2796,6 +2892,80 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
2796
2892
  return false;
2797
2893
  }
2798
2894
  }
2895
+ function childrenOf(def) {
2896
+ switch (def.tag) {
2897
+ case "sequence":
2898
+ case "choice":
2899
+ return def.parsers;
2900
+ case "many":
2901
+ case "optional":
2902
+ case "transform":
2903
+ case "trivia":
2904
+ case "label":
2905
+ case "grammar":
2906
+ case "not":
2907
+ case "node":
2908
+ case "withCtx":
2909
+ case "expect":
2910
+ return [def.parser];
2911
+ // emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
2912
+ // (a mandatory first match, then again inside the repeat loop) — two real
2913
+ // emit() call sites, so the usage analysis must see two edges here or it
2914
+ // undercounts a single-use `parser` ref as inline-safe when it's actually
2915
+ // referenced from two positions within this one compiled function.
2916
+ case "oneOrMore":
2917
+ return [def.parser, def.parser];
2918
+ case "sepBy":
2919
+ return [def.parser, def.parser, def.separator];
2920
+ case "skip":
2921
+ return [def.main, def.skipped];
2922
+ case "recover":
2923
+ return [def.parser, def.sentinel];
2924
+ case "scanTo":
2925
+ return [def.sentinel, ...def.skip];
2926
+ case "lazy":
2927
+ case "literal":
2928
+ case "regex":
2929
+ case "keywords":
2930
+ case "guard":
2931
+ case "unknown":
2932
+ return [];
2933
+ }
2934
+ }
2935
+ function analyzeLazyUsage(root) {
2936
+ return analyzeLazyUsageMulti([root]);
2937
+ }
2938
+ function analyzeLazyUsageMulti(roots) {
2939
+ const counts = /* @__PURE__ */ new Map();
2940
+ const recursive = /* @__PURE__ */ new Set();
2941
+ const descended = /* @__PURE__ */ new Set();
2942
+ const active = /* @__PURE__ */ new Set();
2943
+ function walk(p) {
2944
+ const def = p._def;
2945
+ if (def.tag === "lazy") {
2946
+ counts.set(p, (counts.get(p) ?? 0) + 1);
2947
+ if (active.has(p)) {
2948
+ recursive.add(p);
2949
+ return;
2950
+ }
2951
+ if (descended.has(p)) return;
2952
+ descended.add(p);
2953
+ let resolved;
2954
+ try {
2955
+ resolved = def.thunk();
2956
+ } catch {
2957
+ return;
2958
+ }
2959
+ active.add(p);
2960
+ walk(resolved);
2961
+ active.delete(p);
2962
+ return;
2963
+ }
2964
+ for (const child of childrenOf(def)) walk(child);
2965
+ }
2966
+ for (const root of roots) walk(root);
2967
+ return { counts, recursive };
2968
+ }
2799
2969
  function compile(combinator, mapFnSources) {
2800
2970
  const ctx = {
2801
2971
  vars: 0,
@@ -2812,7 +2982,8 @@ function compile(combinator, mapFnSources) {
2812
2982
  triviaCaptureNames: /* @__PURE__ */ new Map(),
2813
2983
  triviaFnNames: /* @__PURE__ */ new Map(),
2814
2984
  namedFnDecls: [],
2815
- capturing: hasNodeDef(combinator)
2985
+ capturing: hasNodeDef(combinator),
2986
+ lazyUsage: analyzeLazyUsage(combinator)
2816
2987
  };
2817
2988
  const r = emit(combinator, ctx, "_pos");
2818
2989
  const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })