parseman 0.10.0 → 0.12.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.
@@ -778,12 +778,15 @@ function scanFastWsCommentsChunks(input, cur, wsKind, commentKind) {
778
778
  continue;
779
779
  }
780
780
  if (c === 47 && input.charCodeAt(pos + 1) === 42) {
781
- const start = pos;
782
781
  let j = pos + 2;
783
782
  while (j + 1 < input.length && !(input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47)) j++;
784
- pos = j + 2 <= input.length ? j + 2 : input.length;
785
- chunks.push({ start, end: pos, kindIndex: commentKind });
786
- continue;
783
+ if (j + 1 < input.length && input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47) {
784
+ const start = pos;
785
+ pos = j + 2;
786
+ chunks.push({ start, end: pos, kindIndex: commentKind });
787
+ continue;
788
+ }
789
+ break;
787
790
  }
788
791
  break;
789
792
  }
@@ -1280,38 +1283,108 @@ function noTrivia(root) {
1280
1283
  return parser({ trivia: null }, root);
1281
1284
  }
1282
1285
 
1286
+ // src/combinators/expect.ts
1287
+ function staticExpected(c) {
1288
+ const def = c._def;
1289
+ switch (def.tag) {
1290
+ case "literal":
1291
+ return [JSON.stringify(def.value)];
1292
+ case "regex":
1293
+ return [`/${def.source}/`];
1294
+ case "keywords":
1295
+ return def.words.map((w) => JSON.stringify(w));
1296
+ case "label":
1297
+ return [def.label];
1298
+ case "choice":
1299
+ return def.parsers.flatMap(staticExpected);
1300
+ case "sequence":
1301
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1302
+ case "node":
1303
+ case "grammar":
1304
+ case "trivia":
1305
+ case "optional":
1306
+ case "many":
1307
+ case "oneOrMore":
1308
+ case "transform":
1309
+ case "not":
1310
+ return staticExpected(def.parser);
1311
+ case "lazy":
1312
+ return staticExpected(def.thunk());
1313
+ default:
1314
+ return [];
1315
+ }
1316
+ }
1317
+ function expect(combinator, label2) {
1318
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1319
+ const meta = {
1320
+ firstSet: combinator._meta.firstSet,
1321
+ canMatchNewline: combinator._meta.canMatchNewline,
1322
+ isTrivia: false
1323
+ };
1324
+ return {
1325
+ _tag: "expect",
1326
+ _meta: meta,
1327
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1328
+ parse(input, pos, ctx) {
1329
+ const result = combinator.parse(input, pos, ctx);
1330
+ if (result.ok) return result;
1331
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1332
+ ctx._errors?.push(error);
1333
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1334
+ }
1335
+ };
1336
+ }
1337
+
1283
1338
  // src/compiler/scannable-run.ts
1339
+ var BACKSLASH = 92;
1284
1340
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1285
1341
  var META = new Set("()[]{}*+?|^$.".split(""));
1342
+ function shorthandRanges(ch) {
1343
+ return ch === "d" ? [[48, 57]] : [[48, 57], [65, 90], [97, 122], [95, 95]];
1344
+ }
1286
1345
  function parseClassRanges(body) {
1287
1346
  const ranges = [];
1288
1347
  let i = 0;
1289
- const readCp = () => {
1348
+ const readAtom = () => {
1290
1349
  const ch = body[i];
1291
1350
  if (ch === void 0) return null;
1292
1351
  if (ch === "\\") {
1293
1352
  const e = body[i + 1];
1294
1353
  if (e === void 0) return null;
1295
1354
  i += 2;
1296
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1355
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1356
+ if (e === "d" || e === "w") return { set: shorthandRanges(e) };
1357
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1358
+ return { cp: e.codePointAt(0) };
1297
1359
  }
1298
1360
  i += ch.length;
1299
- return ch.codePointAt(0);
1361
+ return { cp: ch.codePointAt(0) };
1300
1362
  };
1301
1363
  while (i < body.length) {
1302
- const lo = readCp();
1364
+ const lo = readAtom();
1303
1365
  if (lo === null) return null;
1366
+ if ("set" in lo) {
1367
+ ranges.push(...lo.set);
1368
+ continue;
1369
+ }
1304
1370
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1305
1371
  i += 1;
1306
- const hi = readCp();
1307
- if (hi === null) return null;
1308
- ranges.push([lo, hi]);
1372
+ const hi = readAtom();
1373
+ if (hi === null || "set" in hi) return null;
1374
+ ranges.push([lo.cp, hi.cp]);
1309
1375
  } else {
1310
- ranges.push([lo, lo]);
1376
+ ranges.push([lo.cp, lo.cp]);
1311
1377
  }
1312
1378
  }
1313
1379
  return ranges.length ? ranges : null;
1314
1380
  }
1381
+ function classToRanges(cls) {
1382
+ if (cls === "\\d") return shorthandRanges("d");
1383
+ if (cls === "\\w") return shorthandRanges("w");
1384
+ const body = cls.slice(1, -1);
1385
+ if (body.startsWith("^")) return null;
1386
+ return parseClassRanges(body);
1387
+ }
1315
1388
  function literalCodePoints(frag) {
1316
1389
  const out = [];
1317
1390
  let i = 0;
@@ -1330,13 +1403,53 @@ function literalCodePoints(frag) {
1330
1403
  }
1331
1404
  return out.length ? out : null;
1332
1405
  }
1406
+ function parseStringShape(source) {
1407
+ const q = source[0];
1408
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1409
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1410
+ const inner = source.slice(1, -1);
1411
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1412
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1413
+ let body;
1414
+ let esc;
1415
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1416
+ if (m) {
1417
+ body = m[1];
1418
+ esc = m[2];
1419
+ } else {
1420
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1421
+ if (m) {
1422
+ esc = m[1];
1423
+ body = m[2];
1424
+ }
1425
+ }
1426
+ if (body === void 0 || esc === void 0) return null;
1427
+ const excluded = parseClassRanges(body);
1428
+ if (!excluded) return null;
1429
+ const qcp = q.codePointAt(0);
1430
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1431
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1432
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1433
+ }
1333
1434
  function parseScanShape(source) {
1334
1435
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1335
1436
  if (m) {
1336
1437
  if (m[1].startsWith("^")) return null;
1337
1438
  const ranges = parseClassRanges(m[1]);
1338
- return ranges ? { kind: "chars", ranges } : null;
1439
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1440
+ }
1441
+ m = /^\\([dw])([+*])$/.exec(source);
1442
+ if (m) {
1443
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1339
1444
  }
1445
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dw])(\[(?:\\.|[^\]])+\]|\\[dw])\*$/.exec(source);
1446
+ if (m) {
1447
+ const head = classToRanges(m[1]);
1448
+ const tail = classToRanges(m[2]);
1449
+ return head && tail ? { kind: "ident", head, tail } : null;
1450
+ }
1451
+ const str = parseStringShape(source);
1452
+ if (str) return str;
1340
1453
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1341
1454
  if (m) {
1342
1455
  const open = literalCodePoints(m[1]);
@@ -1354,66 +1467,147 @@ function parseScanShape(source) {
1354
1467
  }
1355
1468
  return null;
1356
1469
  }
1470
+ function scanShapeFromRegex(source, flags) {
1471
+ if (/[imsu]/.test(flags)) return null;
1472
+ return parseScanShape(source);
1473
+ }
1357
1474
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1358
1475
  var litCond = (base, cps, firstVar) => cps.map(
1359
1476
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1360
1477
  ).join(" && ");
1361
- function advanceExpr(shape) {
1478
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
1479
+ function stringHardStop(shape) {
1480
+ return shape.excluded.filter(
1481
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
1482
+ );
1483
+ }
1484
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1485
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1362
1486
  if (shape.kind === "chars") {
1487
+ const cur = mint("_e");
1363
1488
  return {
1364
1489
  setup: [
1365
- ` _e++`,
1366
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
1490
+ `${ind2}let ${cur} = ${start}`,
1491
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1367
1492
  ],
1368
- endVar: "_e"
1493
+ // `*` always matches (possibly empty); `+` needs at least one char.
1494
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
1495
+ end: cur
1496
+ };
1497
+ }
1498
+ if (shape.kind === "ident") {
1499
+ const cur = mint("_e");
1500
+ return {
1501
+ setup: [
1502
+ `${ind2}let ${cur} = ${start}`,
1503
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
1504
+ `${ind2} ${cur} = ${start} + 1`,
1505
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
1506
+ `${ind2}}`
1507
+ ],
1508
+ ok: `${cur} > ${start}`,
1509
+ end: cur
1369
1510
  };
1370
1511
  }
1371
1512
  if (shape.kind === "until") {
1513
+ const j2 = mint("_j");
1514
+ const openLen2 = shape.open.length;
1515
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1516
+ return {
1517
+ setup: [
1518
+ `${ind2}let ${j2} = ${start}`,
1519
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
1520
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
1521
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
1522
+ `${ind2}}`
1523
+ ],
1524
+ // An open-until-terminator always completes (stop char or EOF), so a matched
1525
+ // open literal is a full match; `end === start` iff the open didn't match.
1526
+ ok: `${j2} > ${start}`,
1527
+ end: j2
1528
+ };
1529
+ }
1530
+ if (shape.kind === "string") {
1531
+ const okV = mint("_ok");
1532
+ const endV2 = mint("_end");
1533
+ const i = mint("_i");
1534
+ const c2 = mint("_c");
1535
+ const hard = stringHardStop(shape);
1536
+ const ltLines = shape.escLineTerm ? [] : (() => {
1537
+ const c3 = mint("_c");
1538
+ return [
1539
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
1540
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
1541
+ ];
1542
+ })();
1372
1543
  return {
1373
1544
  setup: [
1374
- ` let j = _e + ${shape.open.length}`,
1375
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
1545
+ `${ind2}let ${okV} = false`,
1546
+ `${ind2}let ${endV2} = ${start}`,
1547
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
1548
+ `${ind2} let ${i} = ${start} + 1`,
1549
+ `${ind2} while (${i} < input.length) {`,
1550
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
1551
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
1552
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
1553
+ `${ind2} if (${i} + 1 >= input.length) break`,
1554
+ ...ltLines,
1555
+ `${ind2} ${i} += 2`,
1556
+ `${ind2} continue`,
1557
+ `${ind2} }`,
1558
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
1559
+ `${ind2} ${i}++`,
1560
+ `${ind2} }`,
1561
+ `${ind2}}`
1376
1562
  ],
1377
- endVar: "j"
1563
+ ok: okV,
1564
+ end: endV2
1378
1565
  };
1379
1566
  }
1380
- const n = shape.close.length;
1567
+ const j = mint("_j");
1568
+ const endV = mint("_end");
1569
+ const openLen = shape.open.length;
1570
+ const closeLen = shape.close.length;
1571
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
1572
+ const closeChk = litCond(j, shape.close);
1381
1573
  return {
1382
1574
  setup: [
1383
- ` let j = _e + ${shape.open.length}`,
1384
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
1575
+ `${ind2}let ${j} = ${start}`,
1576
+ `${ind2}let ${endV} = ${start}`,
1577
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
1578
+ `${ind2} ${j} = ${start} + ${openLen}`,
1579
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
1580
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
1581
+ `${ind2}}`
1385
1582
  ],
1386
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
1583
+ ok: `${endV} > ${start}`,
1584
+ end: endV
1387
1585
  };
1388
1586
  }
1389
- function guardLine(shape) {
1390
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1391
- return ` if (${cond}) {`;
1392
- }
1393
- function scanBranch(shape) {
1394
- if (shape.kind === "chars") {
1395
- return ` if (${classCond("c", shape.ranges)}) { _e++; continue }`;
1396
- }
1397
- const { setup, endVar } = advanceExpr(shape);
1398
- return [guardLine(shape), ...setup, ` _e = ${endVar}`, ` continue`, ` }`].join("\n");
1587
+ function scanBranch(shape, mint) {
1588
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1589
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1399
1590
  }
1400
- function captureChunk(startVar, kindIndex) {
1591
+ function scanBranchLabeled(shape, kindIndex, mint) {
1592
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1401
1593
  return [
1594
+ ...m.setup,
1595
+ ` if (${m.end} > _e) {`,
1402
1596
  ` if (_cap) {`,
1403
- ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(${startVar}, _e, ${kindIndex})`,
1404
- ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(${startVar}, _e, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1405
- ` }`
1406
- ];
1407
- }
1408
- function scanBranchLabeled(shape, kindIndex) {
1409
- const { setup, endVar } = advanceExpr(shape);
1410
- const lines = [guardLine(shape), ` const _cs = _e`, ...setup];
1411
- if (endVar !== "_e") lines.push(` _e = ${endVar}`);
1412
- lines.push(...captureChunk("_cs", kindIndex), ` continue`, ` }`);
1413
- return lines.join("\n");
1597
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
1598
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
1599
+ ` }`,
1600
+ ` _e = ${m.end}`,
1601
+ ` continue`,
1602
+ ` }`
1603
+ ].join("\n");
1414
1604
  }
1415
1605
 
1416
1606
  // src/compiler/trivia-fast-path.ts
1607
+ function makeMint() {
1608
+ let n = 0;
1609
+ return (prefix = "_v") => `${prefix}${n++}`;
1610
+ }
1417
1611
  function unwrapTrivia2(p) {
1418
1612
  let cur = p;
1419
1613
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1430,14 +1624,14 @@ function choiceArms(p) {
1430
1624
  return null;
1431
1625
  }
1432
1626
  function armShape(arm) {
1433
- const src = getCoreRegexDef(arm)?.source;
1434
- return src ? parseScanShape(src) : null;
1627
+ const d = getCoreRegexDef(arm);
1628
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1435
1629
  }
1436
1630
  function analyzeTriviaFastPath(trivia2) {
1437
1631
  const core = unwrapTrivia2(trivia2);
1438
- const directSrc = getCoreRegexDef(core)?.source;
1439
- if (directSrc) {
1440
- const shape = parseScanShape(directSrc);
1632
+ const directDef = getCoreRegexDef(core);
1633
+ if (directDef) {
1634
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1441
1635
  return shape && shape.kind === "chars" ? [shape] : null;
1442
1636
  }
1443
1637
  const inner = oneOrMoreInner(core);
@@ -1461,10 +1655,11 @@ var CAP_RECORD = [
1461
1655
  ` }`
1462
1656
  ].join("\n");
1463
1657
  function composeFastLoop(shapes) {
1658
+ const mint = makeMint();
1464
1659
  return [
1465
1660
  ` while (_e < input.length) {`,
1466
1661
  ` const c = input.charCodeAt(_e)`,
1467
- ...shapes.map(scanBranch),
1662
+ ...shapes.map((s) => scanBranch(s, mint)),
1468
1663
  ` break`,
1469
1664
  ` }`
1470
1665
  ].join("\n");
@@ -1484,20 +1679,21 @@ function analyzeLabeledScannableRun(trivia2) {
1484
1679
  if (!spec) return null;
1485
1680
  const out = [];
1486
1681
  for (const arm of spec.arms) {
1487
- const src = getCoreRegexDef(arm.parser)?.source;
1488
- const shape = src ? parseScanShape(src) : null;
1682
+ const d = getCoreRegexDef(arm.parser);
1683
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1489
1684
  if (!shape) return null;
1490
1685
  out.push({ shape, kindIndex: arm.kindIndex });
1491
1686
  }
1492
1687
  return out;
1493
1688
  }
1494
1689
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
1690
+ const mint = makeMint();
1495
1691
  return [
1496
1692
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1497
1693
  ` let _e = _pos`,
1498
1694
  ` while (_e < input.length) {`,
1499
1695
  ` const c = input.charCodeAt(_e)`,
1500
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
1696
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1501
1697
  ` break`,
1502
1698
  ` }`,
1503
1699
  ` return _e`,
@@ -1574,6 +1770,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1574
1770
  ].join("\n");
1575
1771
  }
1576
1772
 
1773
+ // src/compiler/scannable-terminal.ts
1774
+ function emitScannableTerminal(shape, c) {
1775
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
1776
+ const stmts = [...m.setup];
1777
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
1778
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
1779
+ return { stmts, endVar: m.end };
1780
+ }
1781
+
1577
1782
  // src/compiler/inline-build.ts
1578
1783
  var MK_BUILD_RE = /^\(\s*(?:\w+\s*,\s*){3}\w+\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\1\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*\)\s*$/;
1579
1784
  function escapeRegExp(s) {
@@ -1695,14 +1900,45 @@ function failReturn(expected, posExpr) {
1695
1900
  function failReturnArr(expectedArr, posExpr) {
1696
1901
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1697
1902
  }
1903
+ function hoistExpected(ctx, constArrSource) {
1904
+ let name = ctx.expectedMap.get(constArrSource);
1905
+ if (name === void 0) {
1906
+ name = `_fx${ctx.expectedDecls.length}`;
1907
+ ctx.expectedDecls.push(`const ${name} = Object.freeze(${constArrSource})`);
1908
+ ctx.expectedMap.set(constArrSource, name);
1909
+ }
1910
+ return name;
1911
+ }
1698
1912
  function failBody(ctx, expected, posExpr) {
1699
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
1913
+ if (ctx.failLabel) {
1914
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1915
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
1916
+ }
1700
1917
  return failReturn(expected, posExpr);
1701
1918
  }
1702
- function failArrBody(ctx, expectedArr, posExpr) {
1703
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
1919
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
1920
+ if (ctx.failLabel) {
1921
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1922
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
1923
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
1924
+ }
1925
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1704
1926
  return failReturnArr(expectedArr, posExpr);
1705
1927
  }
1928
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
1929
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
1930
+ }
1931
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
1932
+ if (srcCtx !== "_ctx") {
1933
+ if (ctx.failLabel) {
1934
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
1935
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
1936
+ }
1937
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
1938
+ }
1939
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
1940
+ return resultFromRecorded();
1941
+ }
1706
1942
  function emitIfFail(ctx, cond, body) {
1707
1943
  return [
1708
1944
  `${ind(ctx)}if (${cond}) {`,
@@ -1738,13 +1974,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1738
1974
  `}`
1739
1975
  ].join("\n"));
1740
1976
  }
1741
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
1977
+ function emitNamedFnCall(ctx, fnName, pos) {
1742
1978
  const vv = v(ctx, "_pfv");
1743
1979
  const ev = v(ctx, "_pfe");
1744
1980
  return {
1745
1981
  stmts: [
1746
1982
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1747
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
1983
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
1984
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
1985
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1748
1986
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1749
1987
  ],
1750
1988
  valueVar: vv,
@@ -1854,6 +2092,27 @@ function firstSetCond(codeVar, fs) {
1854
2092
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1855
2093
  ).join(" || ");
1856
2094
  }
2095
+ var SWITCH_RANGE_LIMIT = 4;
2096
+ var SWITCH_MAX_CASES = 48;
2097
+ var SWITCH_MIN_CASES = 3;
2098
+ var _forceDisjointIf = false;
2099
+ function planDisjointDispatch(parsers) {
2100
+ const cases = [];
2101
+ let total = 0;
2102
+ for (const p of parsers) {
2103
+ const fs = p._meta.firstSet;
2104
+ if (fs.kind !== "ranges") return { kind: "if" };
2105
+ const pts = [];
2106
+ for (const r of fs.ranges) {
2107
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2108
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2109
+ }
2110
+ total += pts.length;
2111
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2112
+ cases.push(pts);
2113
+ }
2114
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2115
+ }
1857
2116
  function captureRestoreBody(mL, mR, mTl, mLg) {
1858
2117
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1859
2118
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -2070,6 +2329,22 @@ function emitKeywords(def, ctx, pos) {
2070
2329
  return { stmts, valueVar: vv, endVar };
2071
2330
  }
2072
2331
  function emitRegex(def, ctx, pos) {
2332
+ const expectedStr = JSON.stringify(`/${def.source}/`);
2333
+ const shape = scanShapeFromRegex(def.source, def.flags);
2334
+ if (shape) {
2335
+ const vv2 = v(ctx);
2336
+ const scanned = emitScannableTerminal(shape, {
2337
+ ind: ind(ctx),
2338
+ pos,
2339
+ valueVar: vv2,
2340
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
2341
+ fresh: (prefix) => v(ctx, prefix)
2342
+ });
2343
+ if (scanned) {
2344
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
2345
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
2346
+ }
2347
+ }
2073
2348
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2074
2349
  const key = `${def.optimizedSource}/${flags}`;
2075
2350
  let rName = ctx.regexMap.get(key);
@@ -2080,7 +2355,6 @@ function emitRegex(def, ctx, pos) {
2080
2355
  }
2081
2356
  const mv = v(ctx, "_m");
2082
2357
  const vv = v(ctx);
2083
- const expectedStr = JSON.stringify(`/${def.source}/`);
2084
2358
  const stmts = [
2085
2359
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2086
2360
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2139,15 +2413,43 @@ function emitSeq(def, ctx, pos) {
2139
2413
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2140
2414
  return { stmts, valueVar: arrV, endVar };
2141
2415
  }
2416
+ function staticExpectedArr(parsers) {
2417
+ return JSON.stringify(parsers.flatMap((p) => {
2418
+ const e = staticExpected(p);
2419
+ return e.length > 0 ? e : [p._tag];
2420
+ }));
2421
+ }
2422
+ function failsAtStart(p) {
2423
+ const d = p._def;
2424
+ switch (d.tag) {
2425
+ case "literal":
2426
+ case "regex":
2427
+ case "keywords":
2428
+ case "guard":
2429
+ case "not":
2430
+ return true;
2431
+ case "transform":
2432
+ case "label":
2433
+ return failsAtStart(d.parser);
2434
+ default:
2435
+ return false;
2436
+ }
2437
+ }
2438
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
2439
+ if (failsAtStart(p)) {
2440
+ const r = emit(p, ctx, pos);
2441
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
2442
+ }
2443
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
2444
+ return [
2445
+ ...stmts,
2446
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
2447
+ `${ind(ctx)}${valV} = ${valVar}`,
2448
+ `${ind(ctx)}${endV} = ${endVar}`
2449
+ ];
2450
+ }
2142
2451
  function emitChoice(def, ctx, pos) {
2143
- const allExpected = JSON.stringify(
2144
- def.parsers.map((p) => {
2145
- const d = p._def;
2146
- if (d.tag === "literal") return JSON.stringify(d.value);
2147
- if (d.tag === "regex") return `/${d.source}/`;
2148
- return p._tag;
2149
- })
2150
- );
2452
+ const allExpected = staticExpectedArr(def.parsers);
2151
2453
  if (def.disjoint) {
2152
2454
  const codeV = v(ctx, "_code");
2153
2455
  const valV = v(ctx, "_chv");
@@ -2156,6 +2458,27 @@ function emitChoice(def, ctx, pos) {
2156
2458
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2157
2459
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2158
2460
  ];
2461
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
2462
+ if (plan.kind === "switch") {
2463
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
2464
+ ctx.indent++;
2465
+ for (let i = 0; i < def.parsers.length; i++) {
2466
+ const p = def.parsers[i];
2467
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
2468
+ stmts.push(`${ind(ctx)}{`);
2469
+ ctx.indent++;
2470
+ stmts.push(
2471
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
2472
+ `${ind(ctx)}break`
2473
+ );
2474
+ ctx.indent--;
2475
+ stmts.push(`${ind(ctx)}}`);
2476
+ }
2477
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
2478
+ ctx.indent--;
2479
+ stmts.push(`${ind(ctx)}}`);
2480
+ return { stmts, valueVar: valV, endVar: endV };
2481
+ }
2159
2482
  let first = true;
2160
2483
  for (const p of def.parsers) {
2161
2484
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2163,10 +2486,7 @@ function emitChoice(def, ctx, pos) {
2163
2486
  first = false;
2164
2487
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2165
2488
  ctx.indent++;
2166
- const r = emit(p, ctx, pos);
2167
- stmts.push(...r.stmts);
2168
- stmts.push(`${ind(ctx)}${valV} = ${r.valueVar}`);
2169
- stmts.push(`${ind(ctx)}${endV} = ${r.endVar}`);
2489
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2170
2490
  ctx.indent--;
2171
2491
  stmts.push(`${ind(ctx)}}`);
2172
2492
  }
@@ -2175,7 +2495,7 @@ function emitChoice(def, ctx, pos) {
2175
2495
  }
2176
2496
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2177
2497
  }
2178
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2498
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2179
2499
  const superParser = def.parsers[superIndex];
2180
2500
  const regexDef = getCoreRegexDef(superParser);
2181
2501
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2190,10 +2510,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2190
2510
  const wordV = v(ctx, "_gw");
2191
2511
  const endV = v(ctx, "_ge");
2192
2512
  const valV = v(ctx, "_gcv");
2513
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2193
2514
  const stmts = [
2194
2515
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2195
2516
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2196
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
2517
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2197
2518
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2198
2519
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2199
2520
  `${ind(ctx)}let ${valV}`
@@ -2251,13 +2572,15 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2251
2572
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2252
2573
  return { stmts, valueVar: valV, endVar: endV };
2253
2574
  }
2254
- function emitFirstMatch(def, allExpected, ctx, pos) {
2575
+ function emitFirstMatch(def, ctx, pos) {
2255
2576
  const resValV = v(ctx, "_crv");
2256
2577
  const resEndV = v(ctx, "_cre");
2257
2578
  const resOkV = v(ctx, "_crok");
2579
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2258
2580
  const ind0 = ind(ctx);
2259
2581
  const stmts = [
2260
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
2582
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
2583
+ `${ind0}let ${slots.join(", ")}`
2261
2584
  ];
2262
2585
  for (let i = 0; i < def.parsers.length; i++) {
2263
2586
  const p = def.parsers[i];
@@ -2304,22 +2627,24 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2304
2627
  stmts.push(`${ind0} ${resOkV} = true`);
2305
2628
  stmts.push(`${ind0} }`);
2306
2629
  stmts.push(`${ind0} }`);
2630
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx }`);
2307
2631
  if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
2308
2632
  } else {
2309
2633
  stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2310
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
2634
+ stmts.push(`${ind0} else { ${slots[i]} = _ctx._fx${rollback ? `; ${rollback}` : ""} }`);
2311
2635
  }
2312
2636
  stmts.push(`${ind0}}`);
2313
2637
  }
2314
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
2638
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
2639
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2315
2640
  return { stmts, valueVar: resValV, endVar: resEndV };
2316
2641
  }
2317
2642
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2318
2643
  if (strategy.tag === "greedyClassify")
2319
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
2644
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2320
2645
  if (strategy.tag === "literalsLongestFirst")
2321
2646
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2322
- return emitFirstMatch(def, allExpected, ctx, pos);
2647
+ return emitFirstMatch(def, ctx, pos);
2323
2648
  }
2324
2649
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2325
2650
  const def = p._def;
@@ -2547,10 +2872,11 @@ function emitScanTo(def, ctx, pos) {
2547
2872
  }
2548
2873
  function emitNot(def, ctx, pos) {
2549
2874
  const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
2875
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2550
2876
  return {
2551
2877
  stmts: [
2552
2878
  ...stmts,
2553
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
2879
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2554
2880
  ],
2555
2881
  valueVar: "null",
2556
2882
  endVar: pos
@@ -2582,7 +2908,7 @@ function emitNode(def, ctx, pos) {
2582
2908
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2583
2909
  stmts.push(...innerStmts);
2584
2910
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
2585
- stmts.push(...emitIfFail(ctx, `!${okVar}`, failBody(ctx, '"node"', pos)));
2911
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2586
2912
  let stV = "undefined";
2587
2913
  if (clonesState) {
2588
2914
  stV = v(ctx, "_nst");
@@ -2604,9 +2930,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2604
2930
  const rv = v(ctx, "_rt");
2605
2931
  const vv = v(ctx, "_rtv");
2606
2932
  const ev = v(ctx, "_rte");
2933
+ const failStmt = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${rv}.span.start; _ctx._fx = ${rv}.expected; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : `return { ok: false, expected: ${rv}.expected, span: ${rv}.span }`;
2607
2934
  const stmts = [
2608
2935
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2609
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
2936
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2610
2937
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2611
2938
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2612
2939
  ];
@@ -2643,7 +2970,7 @@ function emitLazy(p, def, ctx, pos) {
2643
2970
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2644
2971
  }
2645
2972
  const fnName = ctx.namedParsers.get(p);
2646
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
2973
+ return emitNamedFnCall(ctx, fnName, pos);
2647
2974
  }
2648
2975
  function emitRecover(def, ctx, pos) {
2649
2976
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2661,7 +2988,7 @@ function emitRecover(def, ctx, pos) {
2661
2988
  `${ind0} if (${sentOk}) break`,
2662
2989
  `${ind0} ${scanV}++`,
2663
2990
  `${ind0} }`,
2664
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
2991
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2665
2992
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2666
2993
  `${ind0} ${valVar} = ${errV}`,
2667
2994
  `${ind0} ${endVar} = ${scanV}`,
@@ -2834,7 +3161,9 @@ function emit(p, ctx, pos) {
2834
3161
  stmts: [
2835
3162
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2836
3163
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2837
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3164
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3165
+ // and propagate the inner failure verbatim (interpreter parity).
3166
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2838
3167
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2839
3168
  ],
2840
3169
  valueVar: vv,
@@ -2935,7 +3264,7 @@ function analyzeLazyUsageMulti(roots) {
2935
3264
  const recursive = /* @__PURE__ */ new Set();
2936
3265
  const descended = /* @__PURE__ */ new Set();
2937
3266
  const active = /* @__PURE__ */ new Set();
2938
- function walk(p) {
3267
+ function walk2(p) {
2939
3268
  const def = p._def;
2940
3269
  if (def.tag === "lazy") {
2941
3270
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -2952,13 +3281,13 @@ function analyzeLazyUsageMulti(roots) {
2952
3281
  return;
2953
3282
  }
2954
3283
  active.add(p);
2955
- walk(resolved);
3284
+ walk2(resolved);
2956
3285
  active.delete(p);
2957
3286
  return;
2958
3287
  }
2959
- for (const child of childrenOf(def)) walk(child);
3288
+ for (const child of childrenOf(def)) walk2(child);
2960
3289
  }
2961
- for (const root of roots) walk(root);
3290
+ for (const root of roots) walk2(root);
2962
3291
  return { counts, recursive };
2963
3292
  }
2964
3293
  function compile(combinator, mapFnSources) {
@@ -2967,6 +3296,8 @@ function compile(combinator, mapFnSources) {
2967
3296
  indent: 1,
2968
3297
  regexDecls: [],
2969
3298
  regexMap: /* @__PURE__ */ new Map(),
3299
+ expectedDecls: [],
3300
+ expectedMap: /* @__PURE__ */ new Map(),
2970
3301
  mapFns: [],
2971
3302
  mapFnSrcs: [],
2972
3303
  buildFns: [],
@@ -2988,6 +3319,7 @@ function compile(combinator, mapFnSources) {
2988
3319
  const source = [
2989
3320
  ...emptyTlDecls,
2990
3321
  ...ctx.regexDecls,
3322
+ ...ctx.expectedDecls,
2991
3323
  "",
2992
3324
  ...namedPrelude,
2993
3325
  ctx.namedFnDecls.join("\n\n"),
@@ -3000,6 +3332,7 @@ function compile(combinator, mapFnSources) {
3000
3332
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3001
3333
  ...emptyTlDecls,
3002
3334
  ...ctx.regexDecls,
3335
+ ...ctx.expectedDecls,
3003
3336
  collatorDecl,
3004
3337
  ...namedPrelude,
3005
3338
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3045,6 +3378,8 @@ function compileRuleMap(ruleMap) {
3045
3378
  indent: 1,
3046
3379
  regexDecls: [],
3047
3380
  regexMap: /* @__PURE__ */ new Map(),
3381
+ expectedDecls: [],
3382
+ expectedMap: /* @__PURE__ */ new Map(),
3048
3383
  mapFns: [],
3049
3384
  mapFnSrcs: [],
3050
3385
  buildFns: [],
@@ -3073,6 +3408,7 @@ function compileRuleMap(ruleMap) {
3073
3408
  const hoistedDecls = [
3074
3409
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
3075
3410
  ...ctx.regexDecls.map((d) => ` ${d}`),
3411
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3076
3412
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3077
3413
  mfDecl,
3078
3414
  buildDecl,
@@ -3114,12 +3450,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3114
3450
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3115
3451
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3116
3452
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3117
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3453
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3118
3454
  if (!needsWrapper) return innerFn;
3119
3455
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3120
3456
  const hoistedDecls = [
3121
3457
  emptyTlDecl,
3122
3458
  ...ctx.regexDecls.map((d) => ` ${d}`),
3459
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3123
3460
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3124
3461
  mfDecl,
3125
3462
  buildDecl,
@@ -3137,58 +3474,6 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3137
3474
  ].join("\n");
3138
3475
  }
3139
3476
 
3140
- // src/combinators/expect.ts
3141
- function staticExpected(c) {
3142
- const def = c._def;
3143
- switch (def.tag) {
3144
- case "literal":
3145
- return [JSON.stringify(def.value)];
3146
- case "regex":
3147
- return [`/${def.source}/`];
3148
- case "keywords":
3149
- return def.words.map((w) => JSON.stringify(w));
3150
- case "label":
3151
- return [def.label];
3152
- case "choice":
3153
- return def.parsers.flatMap(staticExpected);
3154
- case "sequence":
3155
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
3156
- case "node":
3157
- case "grammar":
3158
- case "trivia":
3159
- case "optional":
3160
- case "many":
3161
- case "oneOrMore":
3162
- case "transform":
3163
- case "not":
3164
- return staticExpected(def.parser);
3165
- case "lazy":
3166
- return staticExpected(def.thunk());
3167
- default:
3168
- return [];
3169
- }
3170
- }
3171
- function expect(combinator, label2) {
3172
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
3173
- const meta = {
3174
- firstSet: combinator._meta.firstSet,
3175
- canMatchNewline: combinator._meta.canMatchNewline,
3176
- isTrivia: false
3177
- };
3178
- return {
3179
- _tag: "expect",
3180
- _meta: meta,
3181
- _def: { tag: "expect", parser: combinator, label: label2, expected },
3182
- parse(input, pos, ctx) {
3183
- const result = combinator.parse(input, pos, ctx);
3184
- if (result.ok) return result;
3185
- const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
3186
- ctx._errors?.push(error);
3187
- return { ok: true, value: error, span: { start: pos, end: pos } };
3188
- }
3189
- };
3190
- }
3191
-
3192
3477
  // src/combinators/scanTo.ts
3193
3478
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
3194
3479
  const meta = {
@@ -3570,6 +3855,13 @@ function evaluateWordFactory(node2, scope, code) {
3570
3855
  function evaluateExpr(node2, scope, code, mapFnSources) {
3571
3856
  return exprToCombi(node2, scope, code, mapFnSources);
3572
3857
  }
3858
+ function evaluateCombinatorArray(node2, scope, code) {
3859
+ if (node2.type !== "ArrayExpression") return null;
3860
+ const val = anyValue(node2, scope, code, []);
3861
+ if (!Array.isArray(val) || val.length === 0) return null;
3862
+ if (!val.every(isCombinator)) return null;
3863
+ return val;
3864
+ }
3573
3865
  function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3574
3866
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
3575
3867
  const factory = factoryNode;
@@ -3873,6 +4165,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3873
4165
  scope.set(varName, wordFactory);
3874
4166
  continue;
3875
4167
  }
4168
+ const combiArray = evaluateCombinatorArray(init, scope, code);
4169
+ if (combiArray) {
4170
+ ;
4171
+ scope.set(varName, combiArray);
4172
+ continue;
4173
+ }
3876
4174
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
3877
4175
  continue;
3878
4176
  }