parseman 0.11.0 → 0.13.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.
@@ -1,6 +1,8 @@
1
1
  // src/plugin/index.ts
2
+ import * as fs from "node:fs";
2
3
  import { createUnplugin } from "unplugin";
3
4
  import { parseSync } from "oxc-parser";
5
+ import { ResolverFactory } from "oxc-resolver";
4
6
  import MagicString from "magic-string";
5
7
 
6
8
  // src/combinators/first-set.ts
@@ -328,15 +330,15 @@ function extractFirstSet(node2) {
328
330
  case "Alternative": {
329
331
  const exprs = node2.expressions;
330
332
  if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
331
- let fs = empty();
333
+ let fs2 = empty();
332
334
  let canNL = false;
333
335
  for (const expr of exprs) {
334
336
  const r = extractFirstSet(expr);
335
- fs = union(fs, r.firstSet);
337
+ fs2 = union(fs2, r.firstSet);
336
338
  canNL = canNL || r.canMatchNewline;
337
339
  if (!canBeEmpty(expr)) break;
338
340
  }
339
- return { firstSet: fs, canMatchNewline: canNL };
341
+ return { firstSet: fs2, canMatchNewline: canNL };
340
342
  }
341
343
  case "Char": {
342
344
  const kind = node2.kind;
@@ -363,22 +365,22 @@ function extractFirstSet(node2) {
363
365
  }
364
366
  case "CharacterClass": {
365
367
  const expressions = node2.expressions;
366
- let fs = empty();
368
+ let fs2 = empty();
367
369
  let canNL = false;
368
370
  for (const expr of expressions) {
369
371
  if (expr.type === "ClassRange") {
370
372
  const from = expr.from.codePoint;
371
373
  const to = expr.to.codePoint;
372
- fs = union(fs, fromRange(from, to));
374
+ fs2 = union(fs2, fromRange(from, to));
373
375
  if (from <= 10 && 10 <= to) canNL = true;
374
376
  } else if (expr.type === "Char") {
375
377
  const r = extractFirstSet(expr);
376
- fs = union(fs, r.firstSet);
378
+ fs2 = union(fs2, r.firstSet);
377
379
  canNL = canNL || r.canMatchNewline;
378
380
  }
379
381
  }
380
382
  if (node2.negative) return { firstSet: any(), canMatchNewline: true };
381
- return { firstSet: fs, canMatchNewline: canNL };
383
+ return { firstSet: fs2, canMatchNewline: canNL };
382
384
  }
383
385
  case "Repetition": {
384
386
  return extractFirstSet(node2.expression);
@@ -687,10 +689,10 @@ function codesToFirstSet(codes) {
687
689
  ranges.push({ lo, hi });
688
690
  return { kind: "ranges", ranges };
689
691
  }
690
- function inFirstSet(code, fs) {
691
- if (fs.kind === "any") return true;
692
- if (fs.kind === "empty") return false;
693
- for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
692
+ function inFirstSet(code, fs2) {
693
+ if (fs2.kind === "any") return true;
694
+ if (fs2.kind === "empty") return false;
695
+ for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
694
696
  return false;
695
697
  }
696
698
  function areDisjoint(sets) {
@@ -778,12 +780,15 @@ function scanFastWsCommentsChunks(input, cur, wsKind, commentKind) {
778
780
  continue;
779
781
  }
780
782
  if (c === 47 && input.charCodeAt(pos + 1) === 42) {
781
- const start = pos;
782
783
  let j = pos + 2;
783
784
  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;
785
+ if (j + 1 < input.length && input.charCodeAt(j) === 42 && input.charCodeAt(j + 1) === 47) {
786
+ const start = pos;
787
+ pos = j + 2;
788
+ chunks.push({ start, end: pos, kindIndex: commentKind });
789
+ continue;
790
+ }
791
+ break;
787
792
  }
788
793
  break;
789
794
  }
@@ -1280,38 +1285,140 @@ function noTrivia(root) {
1280
1285
  return parser({ trivia: null }, root);
1281
1286
  }
1282
1287
 
1288
+ // src/combinators/expect.ts
1289
+ function staticExpected(c) {
1290
+ const def = c._def;
1291
+ switch (def.tag) {
1292
+ case "literal":
1293
+ return [JSON.stringify(def.value)];
1294
+ case "regex":
1295
+ return [`/${def.source}/`];
1296
+ case "keywords":
1297
+ return def.words.map((w) => JSON.stringify(w));
1298
+ case "label":
1299
+ return [def.label];
1300
+ case "choice":
1301
+ return def.parsers.flatMap(staticExpected);
1302
+ case "sequence":
1303
+ return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1304
+ case "node":
1305
+ case "grammar":
1306
+ case "trivia":
1307
+ case "optional":
1308
+ case "many":
1309
+ case "oneOrMore":
1310
+ case "transform":
1311
+ case "not":
1312
+ return staticExpected(def.parser);
1313
+ case "lazy":
1314
+ return staticExpected(def.thunk());
1315
+ default:
1316
+ return [];
1317
+ }
1318
+ }
1319
+ function expect(combinator, label2) {
1320
+ const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1321
+ const meta = {
1322
+ firstSet: combinator._meta.firstSet,
1323
+ canMatchNewline: combinator._meta.canMatchNewline,
1324
+ isTrivia: false
1325
+ };
1326
+ return {
1327
+ _tag: "expect",
1328
+ _meta: meta,
1329
+ _def: { tag: "expect", parser: combinator, label: label2, expected },
1330
+ parse(input, pos, ctx) {
1331
+ const result = combinator.parse(input, pos, ctx);
1332
+ if (result.ok) return result;
1333
+ const error = { _tag: "parseError", span: { start: pos, end: pos }, expected };
1334
+ ctx._errors?.push(error);
1335
+ return { ok: true, value: error, span: { start: pos, end: pos } };
1336
+ }
1337
+ };
1338
+ }
1339
+
1283
1340
  // src/compiler/scannable-run.ts
1341
+ var BACKSLASH = 92;
1284
1342
  var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1285
1343
  var META = new Set("()[]{}*+?|^$.".split(""));
1344
+ var SPACE_RANGES = [
1345
+ [9, 13],
1346
+ [32, 32],
1347
+ [160, 160],
1348
+ [5760, 5760],
1349
+ [8192, 8202],
1350
+ [8232, 8232],
1351
+ [8233, 8233],
1352
+ [8239, 8239],
1353
+ [8287, 8287],
1354
+ [12288, 12288],
1355
+ [65279, 65279]
1356
+ ];
1357
+ function shorthandRanges(ch) {
1358
+ if (ch === "d") return [[48, 57]];
1359
+ if (ch === "s") return SPACE_RANGES;
1360
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
1361
+ }
1362
+ function readUnicodeEscape(body, i) {
1363
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1364
+ const hex = body.slice(i + 2, i + 6);
1365
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1366
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
1367
+ }
1286
1368
  function parseClassRanges(body) {
1287
1369
  const ranges = [];
1288
1370
  let i = 0;
1289
- const readCp = () => {
1371
+ const readAtom = () => {
1290
1372
  const ch = body[i];
1291
1373
  if (ch === void 0) return null;
1292
1374
  if (ch === "\\") {
1375
+ const uni = readUnicodeEscape(body, i);
1376
+ if (uni) {
1377
+ i = uni.next;
1378
+ return { cp: uni.cp };
1379
+ }
1293
1380
  const e = body[i + 1];
1294
1381
  if (e === void 0) return null;
1295
1382
  i += 2;
1296
- return e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0);
1383
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1384
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
1385
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1386
+ return { cp: e.codePointAt(0) };
1297
1387
  }
1298
1388
  i += ch.length;
1299
- return ch.codePointAt(0);
1389
+ return { cp: ch.codePointAt(0) };
1300
1390
  };
1301
1391
  while (i < body.length) {
1302
- const lo = readCp();
1392
+ const lo = readAtom();
1303
1393
  if (lo === null) return null;
1394
+ if ("set" in lo) {
1395
+ ranges.push(...lo.set);
1396
+ continue;
1397
+ }
1304
1398
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1305
1399
  i += 1;
1306
- const hi = readCp();
1307
- if (hi === null) return null;
1308
- ranges.push([lo, hi]);
1400
+ const hi = readAtom();
1401
+ if (hi === null || "set" in hi) return null;
1402
+ ranges.push([lo.cp, hi.cp]);
1309
1403
  } else {
1310
- ranges.push([lo, lo]);
1404
+ ranges.push([lo.cp, lo.cp]);
1311
1405
  }
1312
1406
  }
1313
1407
  return ranges.length ? ranges : null;
1314
1408
  }
1409
+ function classToRanges(cls) {
1410
+ if (cls === "\\d") return shorthandRanges("d");
1411
+ if (cls === "\\w") return shorthandRanges("w");
1412
+ if (cls === "\\s") return shorthandRanges("s");
1413
+ const body = cls.slice(1, -1);
1414
+ if (body.startsWith("^")) return null;
1415
+ return parseClassRanges(body);
1416
+ }
1417
+ var foldEq = (cVar, cp) => {
1418
+ if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1419
+ if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1420
+ return `${cVar} === ${cp}`;
1421
+ };
1315
1422
  function literalCodePoints(frag) {
1316
1423
  const out = [];
1317
1424
  let i = 0;
@@ -1330,19 +1437,482 @@ function literalCodePoints(frag) {
1330
1437
  }
1331
1438
  return out.length ? out : null;
1332
1439
  }
1440
+ function parseStringShape(source) {
1441
+ const q = source[0];
1442
+ if (q === void 0 || q === "\\" || META.has(q)) return null;
1443
+ if (source.length < 3 || source[source.length - 1] !== q) return null;
1444
+ const inner = source.slice(1, -1);
1445
+ const CLS = String.raw`\[\^((?:\\.|[^\]])+)\]`;
1446
+ const ESC = String.raw`\\\\(\.|\[\\s\\S\]|\[\\S\\s\])`;
1447
+ let body;
1448
+ let esc;
1449
+ let m = new RegExp(`^\\((?:\\?:)?${CLS}\\|${ESC}\\)\\*$`).exec(inner);
1450
+ if (m) {
1451
+ body = m[1];
1452
+ esc = m[2];
1453
+ } else {
1454
+ m = new RegExp(`^\\((?:\\?:)?${ESC}\\|${CLS}\\)\\*$`).exec(inner);
1455
+ if (m) {
1456
+ esc = m[1];
1457
+ body = m[2];
1458
+ }
1459
+ }
1460
+ if (body === void 0 || esc === void 0) return null;
1461
+ const excluded = parseClassRanges(body);
1462
+ if (!excluded) return null;
1463
+ const qcp = q.codePointAt(0);
1464
+ const inSet = (cp) => excluded.some(([lo, hi]) => cp >= lo && cp <= hi);
1465
+ if (!inSet(qcp) || !inSet(BACKSLASH)) return null;
1466
+ return { kind: "string", quote: qcp, excluded, escLineTerm: esc !== "." };
1467
+ }
1468
+ function parseSeqParts(source) {
1469
+ const parts = [];
1470
+ let lit = [];
1471
+ const flush = () => {
1472
+ if (lit.length) {
1473
+ parts.push({ part: "lit", cps: lit, optional: false });
1474
+ lit = [];
1475
+ }
1476
+ };
1477
+ const runFrom = (ranges, negated, q) => {
1478
+ if (q === "+") {
1479
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: true });
1480
+ return 1;
1481
+ }
1482
+ if (q === "*") {
1483
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: true });
1484
+ return 1;
1485
+ }
1486
+ if (q === "?") {
1487
+ parts.push({ part: "run", ranges, negated, min: 0, unbounded: false });
1488
+ return 1;
1489
+ }
1490
+ parts.push({ part: "run", ranges, negated, min: 1, unbounded: false });
1491
+ return 0;
1492
+ };
1493
+ let i = 0;
1494
+ while (i < source.length) {
1495
+ const ch = source[i];
1496
+ if (ch === "[") {
1497
+ let k = i + 1;
1498
+ const neg = source[k] === "^";
1499
+ if (neg) k++;
1500
+ let body = "";
1501
+ while (k < source.length && source[k] !== "]") {
1502
+ if (source[k] === "\\") {
1503
+ body += source[k] + (source[k + 1] ?? "");
1504
+ k += 2;
1505
+ } else {
1506
+ body += source[k];
1507
+ k++;
1508
+ }
1509
+ }
1510
+ if (source[k] !== "]") return null;
1511
+ const ranges = parseClassRanges(body);
1512
+ if (!ranges) return null;
1513
+ flush();
1514
+ i = k + 1;
1515
+ i += runFrom(ranges, neg, source[i]);
1516
+ continue;
1517
+ }
1518
+ if (ch === "\\") {
1519
+ const e = source[i + 1];
1520
+ if (e === "d" || e === "w" || e === "s") {
1521
+ flush();
1522
+ i += 2;
1523
+ i += runFrom(shorthandRanges(e), false, source[i]);
1524
+ continue;
1525
+ }
1526
+ const uni = readUnicodeEscape(source, i);
1527
+ let cp2;
1528
+ if (uni) {
1529
+ cp2 = uni.cp;
1530
+ i = uni.next;
1531
+ } else if (e === void 0) return null;
1532
+ else if (e in CLASS_ESCAPES) {
1533
+ cp2 = CLASS_ESCAPES[e];
1534
+ i += 2;
1535
+ } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1536
+ else {
1537
+ cp2 = e.codePointAt(0);
1538
+ i += 2;
1539
+ }
1540
+ if (source[i] === "?") {
1541
+ flush();
1542
+ parts.push({ part: "lit", cps: [cp2], optional: true });
1543
+ i++;
1544
+ } else lit.push(cp2);
1545
+ continue;
1546
+ }
1547
+ if (ch === "(") {
1548
+ if (source[i + 1] !== "?" || source[i + 2] !== ":") return null;
1549
+ let k = i + 3;
1550
+ let depth = 1;
1551
+ while (k < source.length && depth > 0) {
1552
+ const c2 = source[k];
1553
+ if (c2 === "\\") {
1554
+ k += 2;
1555
+ continue;
1556
+ }
1557
+ if (c2 === "[") {
1558
+ let j = k + 1;
1559
+ if (source[j] === "^") j++;
1560
+ while (j < source.length && source[j] !== "]") {
1561
+ if (source[j] === "\\") j += 2;
1562
+ else j++;
1563
+ }
1564
+ if (source[j] !== "]") return null;
1565
+ k = j + 1;
1566
+ continue;
1567
+ }
1568
+ if (c2 === "(") depth++;
1569
+ else if (c2 === ")") depth--;
1570
+ k++;
1571
+ }
1572
+ if (depth !== 0) return null;
1573
+ const body = source.slice(i + 3, k - 1);
1574
+ const inner = parseScanShape(body);
1575
+ if (!inner || !groupInnerSafe(inner)) return null;
1576
+ const q = source[k];
1577
+ if (q === "{") return null;
1578
+ flush();
1579
+ if (q === "+") {
1580
+ if (shapeCanBeEmpty(inner)) return null;
1581
+ parts.push({ part: "group", inner, min: 1, unbounded: true });
1582
+ i = k + 1;
1583
+ } else if (q === "*") {
1584
+ if (shapeCanBeEmpty(inner)) return null;
1585
+ parts.push({ part: "group", inner, min: 0, unbounded: true });
1586
+ i = k + 1;
1587
+ } else if (q === "?") {
1588
+ parts.push({ part: "group", inner, min: 0, unbounded: false });
1589
+ i = k + 1;
1590
+ } else {
1591
+ parts.push({ part: "group", inner, min: 1, unbounded: false });
1592
+ i = k;
1593
+ }
1594
+ continue;
1595
+ }
1596
+ if (META.has(ch)) return null;
1597
+ const cp = ch.codePointAt(0);
1598
+ i += ch.length;
1599
+ if (source[i] === "?") {
1600
+ flush();
1601
+ parts.push({ part: "lit", cps: [cp], optional: true });
1602
+ i++;
1603
+ } else lit.push(cp);
1604
+ }
1605
+ flush();
1606
+ return parts.length ? parts : null;
1607
+ }
1608
+ function seqIsUnambiguous(parts) {
1609
+ let hasMandatory = false;
1610
+ for (let k = 0; k < parts.length; k++) {
1611
+ const p = parts[k];
1612
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1613
+ if (mandatory) hasMandatory = true;
1614
+ const skippableSingle = p.part === "lit" && p.optional || p.part !== "lit" && !p.unbounded && p.min === 0;
1615
+ const isUnboundedRepeat = p.part !== "lit" && p.unbounded;
1616
+ if (skippableSingle || isUnboundedRepeat) {
1617
+ const rest = seqFirstAccept(parts.slice(k + 1));
1618
+ if (!firstSetsDisjoint(partFirstAccept(p), rest)) return false;
1619
+ }
1620
+ }
1621
+ return hasMandatory;
1622
+ }
1623
+ function rangesOverlap(a, b) {
1624
+ return a.some(([lo, hi]) => b.some(([lo2, hi2]) => lo <= hi2 && lo2 <= hi));
1625
+ }
1626
+ function rangesSubset(sub, sup) {
1627
+ return sub.every(([lo, hi]) => sup.some(([lo2, hi2]) => lo >= lo2 && hi <= hi2));
1628
+ }
1629
+ function parseClassOperand(body) {
1630
+ if (body === "\\d" || body === "\\w" || body === "\\s") {
1631
+ return { ranges: shorthandRanges(body[1]), negated: false };
1632
+ }
1633
+ if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1634
+ let inner = body.slice(1, -1);
1635
+ const negated = inner.startsWith("^");
1636
+ if (negated) inner = inner.slice(1);
1637
+ const ranges = parseClassRanges(inner);
1638
+ return ranges ? { ranges, negated } : null;
1639
+ }
1640
+ const cps = literalCodePoints(body);
1641
+ if (cps && cps.length === 1) return { ranges: [[cps[0], cps[0]]], negated: false };
1642
+ return null;
1643
+ }
1644
+ function classSubset(t, u) {
1645
+ if (!t.negated && !u.negated) return rangesSubset(t.ranges, u.ranges);
1646
+ if (!t.negated && u.negated) return !rangesOverlap(t.ranges, u.ranges);
1647
+ if (t.negated && !u.negated) return false;
1648
+ return rangesSubset(u.ranges, t.ranges);
1649
+ }
1650
+ function classDisjoint(t, u) {
1651
+ if (!t.negated && !u.negated) return !rangesOverlap(t.ranges, u.ranges);
1652
+ if (!t.negated && u.negated) return rangesSubset(t.ranges, u.ranges);
1653
+ if (t.negated && !u.negated) return rangesSubset(u.ranges, t.ranges);
1654
+ return false;
1655
+ }
1656
+ function firstSetsDisjoint(a, b) {
1657
+ if (a === "any" || b === "any") return false;
1658
+ return classDisjoint(a, b);
1659
+ }
1660
+ function trailingBacktrackClass(shape) {
1661
+ if (shape.kind === "chars") return { ranges: shape.ranges, negated: false };
1662
+ if (shape.kind === "ident") return { ranges: shape.tail, negated: false };
1663
+ if (shape.kind === "litFold") return null;
1664
+ if (shape.kind === "seq") {
1665
+ const last = shape.parts[shape.parts.length - 1];
1666
+ if (last.part === "lit") return null;
1667
+ if (last.part === "run") {
1668
+ if (last.unbounded || last.min === 0) return { ranges: last.ranges, negated: last.negated };
1669
+ return null;
1670
+ }
1671
+ const innerWiggle = trailingBacktrackClass(last.inner);
1672
+ if (innerWiggle === "unsupported") return "unsupported";
1673
+ const requiredOnce = !last.unbounded && last.min === 1;
1674
+ if (requiredOnce) return innerWiggle;
1675
+ const dropExposed = shapeFirstAccept(last.inner);
1676
+ if (dropExposed === "any") return "unsupported";
1677
+ if (innerWiggle === null) return dropExposed;
1678
+ const unioned = unionCharSets([innerWiggle, dropExposed]);
1679
+ return unioned === "any" ? "unsupported" : unioned;
1680
+ }
1681
+ if (shape.kind === "alt") {
1682
+ if (!shape.disjoint) return "unsupported";
1683
+ const classes = shape.arms.map(trailingBacktrackClass);
1684
+ if (classes.some((c) => c === "unsupported")) return "unsupported";
1685
+ const concrete = classes.filter((c) => c !== null);
1686
+ if (concrete.length === 0) return null;
1687
+ const unioned = unionCharSets(concrete);
1688
+ return unioned === "any" ? "unsupported" : unioned;
1689
+ }
1690
+ return "unsupported";
1691
+ }
1692
+ function lookaheadUnambiguous(inner, operand, negative) {
1693
+ const tail = trailingBacktrackClass(inner);
1694
+ if (tail === "unsupported") return false;
1695
+ if (tail === null) return true;
1696
+ return negative ? classSubset(tail, operand) : classDisjoint(tail, operand);
1697
+ }
1698
+ function stripTrailingLookahead(source) {
1699
+ if (!source.endsWith(")")) return null;
1700
+ for (const negative of [true, false]) {
1701
+ const lead = negative ? "(?!" : "(?=";
1702
+ const idx = source.lastIndexOf(lead);
1703
+ if (idx === -1) continue;
1704
+ const body = source.slice(idx + lead.length, -1);
1705
+ const operand = body.length ? parseClassOperand(body) : null;
1706
+ if (!operand) continue;
1707
+ return { base: source.slice(0, idx), ranges: operand.ranges, classNegated: operand.negated, negative };
1708
+ }
1709
+ return null;
1710
+ }
1711
+ function unwrapOuterGroup(source) {
1712
+ while (source.startsWith("(?:")) {
1713
+ let depth = 1;
1714
+ let i = 3;
1715
+ let ok = true;
1716
+ while (i < source.length && depth > 0) {
1717
+ const ch = source[i];
1718
+ if (ch === "\\") {
1719
+ i += 2;
1720
+ continue;
1721
+ }
1722
+ if (ch === "[") {
1723
+ let j = i + 1;
1724
+ if (source[j] === "^") j++;
1725
+ while (j < source.length && source[j] !== "]") {
1726
+ if (source[j] === "\\") j += 2;
1727
+ else j++;
1728
+ }
1729
+ if (source[j] !== "]") {
1730
+ ok = false;
1731
+ break;
1732
+ }
1733
+ i = j + 1;
1734
+ continue;
1735
+ }
1736
+ if (ch === "(") depth++;
1737
+ else if (ch === ")") depth--;
1738
+ i++;
1739
+ }
1740
+ if (!ok || depth !== 0 || i !== source.length) break;
1741
+ source = source.slice(3, -1);
1742
+ }
1743
+ return source;
1744
+ }
1745
+ function splitTopLevelAlternation(source) {
1746
+ const body = unwrapOuterGroup(source);
1747
+ const parts = [];
1748
+ let depth = 0;
1749
+ let last = 0;
1750
+ let i = 0;
1751
+ while (i < body.length) {
1752
+ const ch = body[i];
1753
+ if (ch === "\\") {
1754
+ i += 2;
1755
+ continue;
1756
+ }
1757
+ if (ch === "[") {
1758
+ let j = i + 1;
1759
+ if (body[j] === "^") j++;
1760
+ while (j < body.length && body[j] !== "]") {
1761
+ if (body[j] === "\\") j += 2;
1762
+ else j++;
1763
+ }
1764
+ if (body[j] !== "]") return null;
1765
+ i = j + 1;
1766
+ continue;
1767
+ }
1768
+ if (ch === "(") {
1769
+ depth++;
1770
+ i++;
1771
+ continue;
1772
+ }
1773
+ if (ch === ")") {
1774
+ depth--;
1775
+ if (depth < 0) return null;
1776
+ i++;
1777
+ continue;
1778
+ }
1779
+ if (ch === "|" && depth === 0) {
1780
+ parts.push(body.slice(last, i));
1781
+ last = i + 1;
1782
+ i++;
1783
+ continue;
1784
+ }
1785
+ i++;
1786
+ }
1787
+ if (depth !== 0) return null;
1788
+ parts.push(body.slice(last));
1789
+ if (parts.length < 2 || parts.some((p) => p.length === 0)) return null;
1790
+ return parts;
1791
+ }
1792
+ function shapeCanBeEmpty(shape) {
1793
+ if (shape.kind === "chars") return !shape.minOne;
1794
+ if (shape.kind === "lookahead") return shapeCanBeEmpty(shape.inner);
1795
+ if (shape.kind === "alt") return shape.arms.some(shapeCanBeEmpty);
1796
+ return false;
1797
+ }
1798
+ function unionCharSets(sets) {
1799
+ if (sets.some((s) => s === "any")) return "any";
1800
+ const concrete = sets;
1801
+ if (concrete.length === 1) return concrete[0];
1802
+ if (concrete.some((s) => s.negated)) return "any";
1803
+ return { ranges: concrete.flatMap((s) => s.ranges), negated: false };
1804
+ }
1805
+ function partFirstAccept(part) {
1806
+ if (part.part === "lit") return { ranges: [[part.cps[0], part.cps[0]]], negated: false };
1807
+ if (part.part === "run") return { ranges: part.ranges, negated: part.negated };
1808
+ return shapeFirstAccept(part.inner);
1809
+ }
1810
+ function seqFirstAccept(parts) {
1811
+ const sets = [];
1812
+ for (const p of parts) {
1813
+ const mandatory = p.part === "lit" ? !p.optional : p.min === 1;
1814
+ sets.push(partFirstAccept(p));
1815
+ if (mandatory) break;
1816
+ }
1817
+ return unionCharSets(sets);
1818
+ }
1819
+ function groupInnerSafe(shape) {
1820
+ if (shape.kind === "chars" || shape.kind === "ident" || shape.kind === "seq" || shape.kind === "litFold") return true;
1821
+ if (shape.kind === "lookahead") return groupInnerSafe(shape.inner);
1822
+ if (shape.kind === "alt") return shape.disjoint;
1823
+ return false;
1824
+ }
1825
+ function shapeFirstAccept(shape) {
1826
+ if (shapeCanBeEmpty(shape)) return "any";
1827
+ switch (shape.kind) {
1828
+ case "chars":
1829
+ return { ranges: shape.ranges, negated: false };
1830
+ case "ident":
1831
+ return { ranges: shape.head, negated: false };
1832
+ case "until":
1833
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1834
+ case "delimited":
1835
+ return { ranges: [[shape.open[0], shape.open[0]]], negated: false };
1836
+ case "string":
1837
+ return { ranges: [[shape.quote, shape.quote]], negated: false };
1838
+ case "litFold": {
1839
+ const cp = shape.open[0];
1840
+ if (cp >= 65 && cp <= 90) return { ranges: [[cp, cp], [cp + 32, cp + 32]], negated: false };
1841
+ if (cp >= 97 && cp <= 122) return { ranges: [[cp, cp], [cp - 32, cp - 32]], negated: false };
1842
+ return { ranges: [[cp, cp]], negated: false };
1843
+ }
1844
+ case "seq":
1845
+ return seqFirstAccept(shape.parts);
1846
+ case "lookahead":
1847
+ return shapeFirstAccept(shape.inner);
1848
+ // zero-width — doesn't change first-char acceptance
1849
+ case "alt": {
1850
+ if (!shape.disjoint) return "any";
1851
+ const sets = shape.firsts.filter((f) => f !== null);
1852
+ return sets.length === shape.arms.length ? unionCharSets(sets) : "any";
1853
+ }
1854
+ }
1855
+ }
1856
+ function allPairsDisjoint(sets) {
1857
+ for (let i = 0; i < sets.length; i++) {
1858
+ const a = sets[i];
1859
+ if (a === "any") return false;
1860
+ for (let j = i + 1; j < sets.length; j++) {
1861
+ const b = sets[j];
1862
+ if (b === "any" || !classDisjoint(a, b)) return false;
1863
+ }
1864
+ }
1865
+ return true;
1866
+ }
1867
+ function parseAlternation(source) {
1868
+ const parts = splitTopLevelAlternation(source);
1869
+ if (!parts) return null;
1870
+ const arms = [];
1871
+ for (const p of parts) {
1872
+ const arm = parseScanShape(p);
1873
+ if (!arm) return null;
1874
+ arms.push(arm);
1875
+ }
1876
+ const firstSets = arms.map(shapeFirstAccept);
1877
+ const disjoint = allPairsDisjoint(firstSets);
1878
+ return { kind: "alt", arms, disjoint, firsts: firstSets.map((f) => f === "any" ? null : f) };
1879
+ }
1333
1880
  function parseScanShape(source) {
1881
+ const alt = parseAlternation(source);
1882
+ if (alt) return alt;
1883
+ const la = stripTrailingLookahead(source);
1884
+ if (la) {
1885
+ const inner = parseScanShapeCore(la.base);
1886
+ if (!inner) return null;
1887
+ const operand = { ranges: la.ranges, negated: la.classNegated };
1888
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
1889
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
1890
+ }
1891
+ return parseScanShapeCore(source);
1892
+ }
1893
+ function parseScanShapeCore(source) {
1334
1894
  let m = /^\[((?:\\.|[^\]])+)\]([+*])$/.exec(source);
1335
- if (m) {
1336
- if (m[1].startsWith("^")) return null;
1895
+ if (m && !m[1].startsWith("^")) {
1337
1896
  const ranges = parseClassRanges(m[1]);
1338
- return ranges ? { kind: "chars", ranges } : null;
1897
+ return ranges ? { kind: "chars", ranges, minOne: m[2] === "+" } : null;
1898
+ }
1899
+ m = /^\\([dws])([+*])$/.exec(source);
1900
+ if (m) {
1901
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
1339
1902
  }
1903
+ m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
1904
+ if (m) {
1905
+ const head = classToRanges(m[1]);
1906
+ const tail = classToRanges(m[2]);
1907
+ if (head && tail) return { kind: "ident", head, tail };
1908
+ }
1909
+ const str = parseStringShape(source);
1910
+ if (str) return str;
1340
1911
  m = /^(.*?)\[\^((?:\\.|[^\]])+)\]\*$/.exec(source);
1341
1912
  if (m) {
1342
1913
  const open = literalCodePoints(m[1]);
1343
1914
  const stop = parseClassRanges(m[2]);
1344
1915
  if (open && stop) return { kind: "until", open, stop };
1345
- return null;
1346
1916
  }
1347
1917
  if (!source.includes("\\\\")) {
1348
1918
  m = /^(.*?)\((?:\?:)?[\s\S]*\)\*(.*?)$/.exec(source);
@@ -1352,68 +1922,278 @@ function parseScanShape(source) {
1352
1922
  if (open && close) return { kind: "delimited", open, close };
1353
1923
  }
1354
1924
  }
1925
+ const parts = parseSeqParts(source);
1926
+ if (parts && seqIsUnambiguous(parts)) return { kind: "seq", parts };
1355
1927
  return null;
1356
1928
  }
1929
+ function scanShapeFromRegex(source, flags) {
1930
+ if (/[msu]/.test(flags)) return null;
1931
+ if (/i/.test(flags)) {
1932
+ const open = literalCodePoints(source);
1933
+ return open ? { kind: "litFold", open } : null;
1934
+ }
1935
+ return parseScanShape(source);
1936
+ }
1357
1937
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
1358
1938
  var litCond = (base, cps, firstVar) => cps.map(
1359
1939
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
1360
1940
  ).join(" && ");
1361
- function advanceExpr(shape) {
1941
+ var LINE_TERMINATORS = [10, 13, 8232, 8233];
1942
+ function stringHardStop(shape) {
1943
+ return shape.excluded.filter(
1944
+ ([lo, hi]) => !(lo === hi && (lo === shape.quote || lo === BACKSLASH))
1945
+ );
1946
+ }
1947
+ var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
1948
+ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
1949
+ if (shape.kind === "lookahead") {
1950
+ const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
1951
+ const okV = mint("_ok");
1952
+ const endV2 = mint("_end");
1953
+ const rawIn = classCond(`input.charCodeAt(${inner.end})`, shape.ranges);
1954
+ const charInClass = shape.classNegated ? `!(${rawIn})` : `(${rawIn})`;
1955
+ const hasMatchingChar = `${inner.end} < input.length && ${charInClass}`;
1956
+ const guardFails = shape.negative ? hasMatchingChar : `!(${hasMatchingChar})`;
1957
+ return {
1958
+ setup: [
1959
+ ...inner.setup,
1960
+ `${ind2}let ${okV} = ${inner.ok}`,
1961
+ `${ind2}let ${endV2} = ${inner.end}`,
1962
+ `${ind2}if (${okV} && (${guardFails})) { ${okV} = false; ${endV2} = ${start} }`
1963
+ ],
1964
+ ok: okV,
1965
+ end: endV2
1966
+ };
1967
+ }
1968
+ if (shape.kind === "alt") {
1969
+ const okV = mint("_ok");
1970
+ const endV2 = mint("_end");
1971
+ const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
1972
+ if (shape.disjoint) {
1973
+ const c0 = codeAt(start, firstChar);
1974
+ shape.arms.forEach((arm, k) => {
1975
+ const fs2 = shape.firsts[k];
1976
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
1977
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
1978
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
1979
+ lines.push(...m.setup);
1980
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
1981
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
1982
+ lines.push(`${ind2}}`);
1983
+ });
1984
+ } else {
1985
+ const lbl = mint("_alt");
1986
+ lines.push(`${ind2}${lbl}: {`);
1987
+ for (const arm of shape.arms) {
1988
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
1989
+ lines.push(...m.setup);
1990
+ lines.push(`${ind2} if (${m.ok}) { ${okV} = true; ${endV2} = ${m.end}; break ${lbl} }`);
1991
+ }
1992
+ lines.push(`${ind2}}`);
1993
+ }
1994
+ return { setup: lines, ok: okV, end: endV2 };
1995
+ }
1362
1996
  if (shape.kind === "chars") {
1997
+ const cur = mint("_e");
1363
1998
  return {
1364
1999
  setup: [
1365
- ` _e++`,
1366
- ` while (_e < input.length) { const c2 = input.charCodeAt(_e); if (${classCond("c2", shape.ranges)}) _e++; else break }`
2000
+ `${ind2}let ${cur} = ${start}`,
2001
+ `${ind2}while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.ranges)})) ${cur}++`
1367
2002
  ],
1368
- endVar: "_e"
2003
+ // `*` always matches (possibly empty); `+` needs at least one char.
2004
+ ok: shape.minOne ? `${cur} > ${start}` : "true",
2005
+ end: cur
2006
+ };
2007
+ }
2008
+ if (shape.kind === "ident") {
2009
+ const cur = mint("_e");
2010
+ return {
2011
+ setup: [
2012
+ `${ind2}let ${cur} = ${start}`,
2013
+ `${ind2}if (${start} < input.length && (${classCond(codeAt(start, firstChar), shape.head)})) {`,
2014
+ `${ind2} ${cur} = ${start} + 1`,
2015
+ `${ind2} while (${cur} < input.length && (${classCond(`input.charCodeAt(${cur})`, shape.tail)})) ${cur}++`,
2016
+ `${ind2}}`
2017
+ ],
2018
+ ok: `${cur} > ${start}`,
2019
+ end: cur
2020
+ };
2021
+ }
2022
+ if (shape.kind === "seq") {
2023
+ const endV2 = mint("_e");
2024
+ const okV = mint("_ok");
2025
+ const runCond = (p, at) => {
2026
+ const c = classCond(`input.charCodeAt(${at})`, p.ranges);
2027
+ return p.negated ? `!(${c})` : `(${c})`;
2028
+ };
2029
+ const lines = [`${ind2}let ${endV2} = ${start}`, `${ind2}let ${okV} = false`, `${ind2}do {`];
2030
+ let first = true;
2031
+ for (const p of shape.parts) {
2032
+ if (p.part === "lit") {
2033
+ const L = p.cps.length;
2034
+ const cond = litCond(endV2, p.cps, first ? firstChar : void 0);
2035
+ if (p.optional) {
2036
+ lines.push(`${ind2} if (${endV2} + ${L} <= input.length && (${cond})) ${endV2} += ${L}`);
2037
+ } else {
2038
+ lines.push(`${ind2} if (!(${endV2} + ${L} <= input.length && (${cond}))) break`);
2039
+ lines.push(`${ind2} ${endV2} += ${L}`);
2040
+ }
2041
+ } else if (p.part === "run" && !p.unbounded) {
2042
+ if (p.min === 1) {
2043
+ lines.push(`${ind2} if (!(${endV2} < input.length && ${runCond(p, endV2)})) break`);
2044
+ lines.push(`${ind2} ${endV2}++`);
2045
+ } else {
2046
+ lines.push(`${ind2} if (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2047
+ }
2048
+ } else if (p.part === "run") {
2049
+ const s = mint("_s");
2050
+ lines.push(`${ind2} const ${s} = ${endV2}`);
2051
+ lines.push(`${ind2} while (${endV2} < input.length && ${runCond(p, endV2)}) ${endV2}++`);
2052
+ if (p.min === 1) lines.push(`${ind2} if (${endV2} === ${s}) break`);
2053
+ } else {
2054
+ const inner = emitShapeMatch(p.inner, endV2, mint, `${ind2} `, first ? firstChar : void 0);
2055
+ if (!p.unbounded) {
2056
+ lines.push(...inner.setup);
2057
+ if (p.min === 1) {
2058
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2059
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2060
+ } else {
2061
+ lines.push(`${ind2} if (${inner.ok}) ${endV2} = ${inner.end}`);
2062
+ }
2063
+ } else {
2064
+ const progV = mint("_prog");
2065
+ lines.push(`${ind2} let ${progV} = false`);
2066
+ lines.push(`${ind2} while (true) {`);
2067
+ lines.push(...inner.setup);
2068
+ lines.push(`${ind2} if (!(${inner.ok})) break`);
2069
+ lines.push(`${ind2} ${endV2} = ${inner.end}`);
2070
+ lines.push(`${ind2} ${progV} = true`);
2071
+ lines.push(`${ind2} }`);
2072
+ if (p.min === 1) lines.push(`${ind2} if (!${progV}) break`);
2073
+ }
2074
+ }
2075
+ first = false;
2076
+ }
2077
+ lines.push(`${ind2} ${okV} = true`);
2078
+ lines.push(`${ind2}} while (false)`);
2079
+ lines.push(`${ind2}if (!${okV}) ${endV2} = ${start}`);
2080
+ return { setup: lines, ok: okV, end: endV2 };
2081
+ }
2082
+ if (shape.kind === "litFold") {
2083
+ const openLen2 = shape.open.length;
2084
+ const endV2 = mint("_end");
2085
+ const checks = shape.open.map((cp, k) => {
2086
+ const cVar = k === 0 && firstChar ? firstChar : `input.charCodeAt(${start} + ${k})`;
2087
+ return foldEq(cVar, cp);
2088
+ });
2089
+ return {
2090
+ setup: [
2091
+ `${ind2}let ${endV2} = ${start}`,
2092
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${checks.join(" && ")})) ${endV2} = ${start} + ${openLen2}`
2093
+ ],
2094
+ ok: `${endV2} > ${start}`,
2095
+ end: endV2
1369
2096
  };
1370
2097
  }
1371
2098
  if (shape.kind === "until") {
2099
+ const j2 = mint("_j");
2100
+ const openLen2 = shape.open.length;
2101
+ const openChk2 = openLen2 === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
2102
+ return {
2103
+ setup: [
2104
+ `${ind2}let ${j2} = ${start}`,
2105
+ `${ind2}if (${start} + ${openLen2} <= input.length && (${openChk2})) {`,
2106
+ `${ind2} ${j2} = ${start} + ${openLen2}`,
2107
+ `${ind2} while (${j2} < input.length && !(${classCond(`input.charCodeAt(${j2})`, shape.stop)})) ${j2}++`,
2108
+ `${ind2}}`
2109
+ ],
2110
+ // An open-until-terminator always completes (stop char or EOF), so a matched
2111
+ // open literal is a full match; `end === start` iff the open didn't match.
2112
+ ok: `${j2} > ${start}`,
2113
+ end: j2
2114
+ };
2115
+ }
2116
+ if (shape.kind === "string") {
2117
+ const okV = mint("_ok");
2118
+ const endV2 = mint("_end");
2119
+ const i = mint("_i");
2120
+ const c2 = mint("_c");
2121
+ const hard = stringHardStop(shape);
2122
+ const ltLines = shape.escLineTerm ? [] : (() => {
2123
+ const c3 = mint("_c");
2124
+ return [
2125
+ `${ind2} const ${c3} = input.charCodeAt(${i} + 1)`,
2126
+ `${ind2} if (${LINE_TERMINATORS.map((t) => `${c3} === ${t}`).join(" || ")}) break`
2127
+ ];
2128
+ })();
1372
2129
  return {
1373
2130
  setup: [
1374
- ` let j = _e + ${shape.open.length}`,
1375
- ` while (j < input.length && !(${classCond("input.charCodeAt(j)", shape.stop)})) j++`
2131
+ `${ind2}let ${okV} = false`,
2132
+ `${ind2}let ${endV2} = ${start}`,
2133
+ `${ind2}if (${start} < input.length && (${codeAt(start, firstChar)}) === ${shape.quote}) {`,
2134
+ `${ind2} let ${i} = ${start} + 1`,
2135
+ `${ind2} while (${i} < input.length) {`,
2136
+ `${ind2} const ${c2} = input.charCodeAt(${i})`,
2137
+ `${ind2} if (${c2} === ${shape.quote}) { ${okV} = true; ${endV2} = ${i} + 1; break }`,
2138
+ `${ind2} if (${c2} === ${BACKSLASH}) {`,
2139
+ `${ind2} if (${i} + 1 >= input.length) break`,
2140
+ ...ltLines,
2141
+ `${ind2} ${i} += 2`,
2142
+ `${ind2} continue`,
2143
+ `${ind2} }`,
2144
+ ...hard.length ? [`${ind2} if (${classCond(c2, hard)}) break`] : [],
2145
+ `${ind2} ${i}++`,
2146
+ `${ind2} }`,
2147
+ `${ind2}}`
1376
2148
  ],
1377
- endVar: "j"
2149
+ ok: okV,
2150
+ end: endV2
1378
2151
  };
1379
2152
  }
1380
- const n = shape.close.length;
2153
+ const j = mint("_j");
2154
+ const endV = mint("_end");
2155
+ const openLen = shape.open.length;
2156
+ const closeLen = shape.close.length;
2157
+ const openChk = openLen === 1 ? `${codeAt(start, firstChar)} === ${shape.open[0]}` : litCond(start, shape.open, firstChar);
2158
+ const closeChk = litCond(j, shape.close);
1381
2159
  return {
1382
2160
  setup: [
1383
- ` let j = _e + ${shape.open.length}`,
1384
- ` while (j + ${n - 1} < input.length && !(${litCond("j", shape.close)})) j++`
2161
+ `${ind2}let ${j} = ${start}`,
2162
+ `${ind2}let ${endV} = ${start}`,
2163
+ `${ind2}if (${start} + ${openLen} <= input.length && (${openChk})) {`,
2164
+ `${ind2} ${j} = ${start} + ${openLen}`,
2165
+ `${ind2} while (${j} + ${closeLen - 1} < input.length && !(${closeChk})) ${j}++`,
2166
+ `${ind2} if (${j} + ${closeLen} <= input.length && (${closeChk})) ${endV} = ${j} + ${closeLen}`,
2167
+ `${ind2}}`
1385
2168
  ],
1386
- endVar: n === 1 ? "(j + 1 <= input.length ? j + 1 : input.length)" : `(j + ${n} <= input.length ? j + ${n} : input.length)`
2169
+ ok: `${endV} > ${start}`,
2170
+ end: endV
1387
2171
  };
1388
2172
  }
1389
- function guardLine(shape) {
1390
- const cond = shape.kind === "chars" ? classCond("c", shape.ranges) : litCond("_e", shape.open, "c");
1391
- return ` if (${cond}) {`;
2173
+ function scanBranch(shape, mint) {
2174
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
2175
+ return [...m.setup, ` if (${m.end} > _e) { _e = ${m.end}; continue }`].join("\n");
1392
2176
  }
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");
1399
- }
1400
- function captureChunk(startVar, kindIndex) {
2177
+ function scanBranchLabeled(shape, kindIndex, mint) {
2178
+ const m = emitShapeMatch(shape, "_e", mint, " ", "c");
1401
2179
  return [
2180
+ ...m.setup,
2181
+ ` if (${m.end} > _e) {`,
1402
2182
  ` 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");
2183
+ ` if (_ctx._triviaLog !== undefined) _ctx._triviaLog.push(_e, ${m.end}, ${kindIndex})`,
2184
+ ` if (_ctx._cstTriviaLog !== undefined) _ctx._cstTriviaLog.push(_e, ${m.end}, _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0, ${kindIndex})`,
2185
+ ` }`,
2186
+ ` _e = ${m.end}`,
2187
+ ` continue`,
2188
+ ` }`
2189
+ ].join("\n");
1414
2190
  }
1415
2191
 
1416
2192
  // src/compiler/trivia-fast-path.ts
2193
+ function makeMint() {
2194
+ let n = 0;
2195
+ return (prefix = "_v") => `${prefix}${n++}`;
2196
+ }
1417
2197
  function unwrapTrivia2(p) {
1418
2198
  let cur = p;
1419
2199
  while (cur._def.tag === "trivia") cur = cur._def.parser;
@@ -1430,14 +2210,14 @@ function choiceArms(p) {
1430
2210
  return null;
1431
2211
  }
1432
2212
  function armShape(arm) {
1433
- const src = getCoreRegexDef(arm)?.source;
1434
- return src ? parseScanShape(src) : null;
2213
+ const d = getCoreRegexDef(arm);
2214
+ return d ? scanShapeFromRegex(d.source, d.flags) : null;
1435
2215
  }
1436
2216
  function analyzeTriviaFastPath(trivia2) {
1437
2217
  const core = unwrapTrivia2(trivia2);
1438
- const directSrc = getCoreRegexDef(core)?.source;
1439
- if (directSrc) {
1440
- const shape = parseScanShape(directSrc);
2218
+ const directDef = getCoreRegexDef(core);
2219
+ if (directDef) {
2220
+ const shape = scanShapeFromRegex(directDef.source, directDef.flags);
1441
2221
  return shape && shape.kind === "chars" ? [shape] : null;
1442
2222
  }
1443
2223
  const inner = oneOrMoreInner(core);
@@ -1461,10 +2241,11 @@ var CAP_RECORD = [
1461
2241
  ` }`
1462
2242
  ].join("\n");
1463
2243
  function composeFastLoop(shapes) {
2244
+ const mint = makeMint();
1464
2245
  return [
1465
2246
  ` while (_e < input.length) {`,
1466
2247
  ` const c = input.charCodeAt(_e)`,
1467
- ...shapes.map(scanBranch),
2248
+ ...shapes.map((s) => scanBranch(s, mint)),
1468
2249
  ` break`,
1469
2250
  ` }`
1470
2251
  ].join("\n");
@@ -1484,20 +2265,21 @@ function analyzeLabeledScannableRun(trivia2) {
1484
2265
  if (!spec) return null;
1485
2266
  const out = [];
1486
2267
  for (const arm of spec.arms) {
1487
- const src = getCoreRegexDef(arm.parser)?.source;
1488
- const shape = src ? parseScanShape(src) : null;
2268
+ const d = getCoreRegexDef(arm.parser);
2269
+ const shape = d ? scanShapeFromRegex(d.source, d.flags) : null;
1489
2270
  if (!shape) return null;
1490
2271
  out.push({ shape, kindIndex: arm.kindIndex });
1491
2272
  }
1492
2273
  return out;
1493
2274
  }
1494
2275
  function buildLabeledScannableTriviaFnDecl(fnName, arms) {
2276
+ const mint = makeMint();
1495
2277
  return [
1496
2278
  `function ${fnName}(input, _pos, _ctx, _cap) {`,
1497
2279
  ` let _e = _pos`,
1498
2280
  ` while (_e < input.length) {`,
1499
2281
  ` const c = input.charCodeAt(_e)`,
1500
- ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex)),
2282
+ ...arms.map((a) => scanBranchLabeled(a.shape, a.kindIndex, mint)),
1501
2283
  ` break`,
1502
2284
  ` }`,
1503
2285
  ` return _e`,
@@ -1574,6 +2356,15 @@ function buildLabeledRuntimeTriviaFnDecl(fnName, spec, rpStartIndex) {
1574
2356
  ].join("\n");
1575
2357
  }
1576
2358
 
2359
+ // src/compiler/scannable-terminal.ts
2360
+ function emitScannableTerminal(shape, c) {
2361
+ const m = emitShapeMatch(shape, c.pos, c.fresh, c.ind);
2362
+ const stmts = [...m.setup];
2363
+ if (m.ok !== "true") stmts.push(...c.failIf(`!(${m.ok})`));
2364
+ stmts.push(`${c.ind}const ${c.valueVar} = input.slice(${c.pos}, ${m.end})`);
2365
+ return { stmts, endVar: m.end };
2366
+ }
2367
+
1577
2368
  // src/compiler/inline-build.ts
1578
2369
  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
2370
  function escapeRegExp(s) {
@@ -1695,14 +2486,45 @@ function failReturn(expected, posExpr) {
1695
2486
  function failReturnArr(expectedArr, posExpr) {
1696
2487
  return `return { ok: false, expected: ${expectedArr}, span: { start: ${posExpr}, end: ${posExpr} } }`;
1697
2488
  }
2489
+ function hoistExpected(ctx, constArrSource) {
2490
+ let name = ctx.expectedMap.get(constArrSource);
2491
+ if (name === void 0) {
2492
+ name = `_fx${ctx.expectedDecls.length}`;
2493
+ ctx.expectedDecls.push(`const ${name} = ${constArrSource}`);
2494
+ ctx.expectedMap.set(constArrSource, name);
2495
+ }
2496
+ return name;
2497
+ }
1698
2498
  function failBody(ctx, expected, posExpr) {
1699
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2499
+ if (ctx.failLabel) {
2500
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2501
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
2502
+ }
1700
2503
  return failReturn(expected, posExpr);
1701
2504
  }
1702
- function failArrBody(ctx, expectedArr, posExpr) {
1703
- if (ctx.failLabel) return `break ${ctx.failLabel}`;
2505
+ function failArrBody(ctx, expectedArr, posExpr, constant = true) {
2506
+ if (ctx.failLabel) {
2507
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2508
+ const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
2509
+ return `{ _ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
2510
+ }
2511
+ if (!constant) return `return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
1704
2512
  return failReturnArr(expectedArr, posExpr);
1705
2513
  }
2514
+ function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
2515
+ return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
2516
+ }
2517
+ function propagateFailBody(ctx, srcCtx = "_ctx") {
2518
+ if (srcCtx !== "_ctx") {
2519
+ if (ctx.failLabel) {
2520
+ if (!ctx.recordFail) return `break ${ctx.failLabel}`;
2521
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; break ${ctx.failLabel} }`;
2522
+ }
2523
+ return `{ _ctx._fe = ${srcCtx}._fe; _ctx._fx = ${srcCtx}._fx; ${resultFromRecorded()} }`;
2524
+ }
2525
+ if (ctx.failLabel) return `break ${ctx.failLabel}`;
2526
+ return resultFromRecorded();
2527
+ }
1706
2528
  function emitIfFail(ctx, cond, body) {
1707
2529
  return [
1708
2530
  `${ind(ctx)}if (${cond}) {`,
@@ -1738,13 +2560,15 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
1738
2560
  `}`
1739
2561
  ].join("\n"));
1740
2562
  }
1741
- function emitNamedFnCall(ctx, fnName, pos, failExpected) {
2563
+ function emitNamedFnCall(ctx, fnName, pos) {
1742
2564
  const vv = v(ctx, "_pfv");
1743
2565
  const ev = v(ctx, "_pfe");
1744
2566
  return {
1745
2567
  stmts: [
1746
2568
  `${ind(ctx)}const ${vv} = ${fnName}(input, ${pos}, _ctx)`,
1747
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, failExpected, pos)),
2569
+ // ref/lazy returns the inner failure verbatim — propagate the recorded
2570
+ // deepest failure (shares _ctx with the named fn), not a "parser" label.
2571
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx)),
1748
2572
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
1749
2573
  ],
1750
2574
  valueVar: vv,
@@ -1847,13 +2671,34 @@ function ensureTriviaFn(ctx) {
1847
2671
  function ensureTriviaCaptureFn(ctx) {
1848
2672
  return ensureTriviaFn(ctx);
1849
2673
  }
1850
- function firstSetCond(codeVar, fs) {
1851
- if (fs.kind === "any") return "true";
1852
- if (fs.kind === "empty") return "false";
1853
- return fs.ranges.map(
2674
+ function firstSetCond(codeVar, fs2) {
2675
+ if (fs2.kind === "any") return "true";
2676
+ if (fs2.kind === "empty") return "false";
2677
+ return fs2.ranges.map(
1854
2678
  (r) => r.lo === r.hi ? `${codeVar} === ${r.lo}` : `(${codeVar} >= ${r.lo} && ${codeVar} <= ${r.hi})`
1855
2679
  ).join(" || ");
1856
2680
  }
2681
+ var SWITCH_RANGE_LIMIT = 4;
2682
+ var SWITCH_MAX_CASES = 48;
2683
+ var SWITCH_MIN_CASES = 3;
2684
+ var _forceDisjointIf = false;
2685
+ function planDisjointDispatch(parsers) {
2686
+ const cases = [];
2687
+ let total = 0;
2688
+ for (const p of parsers) {
2689
+ const fs2 = p._meta.firstSet;
2690
+ if (fs2.kind !== "ranges") return { kind: "if" };
2691
+ const pts = [];
2692
+ for (const r of fs2.ranges) {
2693
+ if (r.hi - r.lo + 1 > SWITCH_RANGE_LIMIT) return { kind: "if" };
2694
+ for (let cp = r.lo; cp <= r.hi; cp++) pts.push(cp);
2695
+ }
2696
+ total += pts.length;
2697
+ if (total > SWITCH_MAX_CASES) return { kind: "if" };
2698
+ cases.push(pts);
2699
+ }
2700
+ return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2701
+ }
1857
2702
  function captureRestoreBody(mL, mR, mTl, mLg) {
1858
2703
  const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
1859
2704
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
@@ -1967,18 +2812,21 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
1967
2812
  return true;
1968
2813
  }
1969
2814
  }
1970
- function emitFallible(inner, ctx, pos) {
2815
+ function emitFallible(inner, ctx, pos, swallow = false) {
1971
2816
  const lbl = v(ctx, "_lbl");
1972
2817
  const okV = `${lbl}ok`;
1973
2818
  const valV = `${lbl}v`;
1974
2819
  const endV = `${lbl}e`;
1975
2820
  const savedLabel = ctx.failLabel;
1976
2821
  const savedIndent = ctx.indent;
2822
+ const savedRecord = ctx.recordFail;
1977
2823
  ctx.failLabel = lbl;
1978
2824
  ctx.indent = savedIndent + 1;
2825
+ if (swallow) ctx.recordFail = false;
1979
2826
  const r = emit(inner, ctx, pos);
1980
2827
  ctx.failLabel = savedLabel;
1981
2828
  ctx.indent = savedIndent;
2829
+ ctx.recordFail = savedRecord;
1982
2830
  const ind0 = ind(ctx);
1983
2831
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
1984
2832
  const mL = needsRollback ? v(ctx, "_fcl") : null;
@@ -2001,6 +2849,7 @@ function emitFallible(inner, ctx, pos) {
2001
2849
  ];
2002
2850
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
2003
2851
  }
2852
+ var CHARCODE_CHAIN_MAX = 16;
2004
2853
  function emitLit(def, ctx, pos) {
2005
2854
  const { value, caseInsensitive } = def;
2006
2855
  const len = value.length;
@@ -2023,7 +2872,7 @@ function emitLit(def, ctx, pos) {
2023
2872
  ...emitIfFail(ctx, `${pos} >= input.length || input.charCodeAt(${pos}) !== ${code}`, failBody(ctx, expectedStr, pos)),
2024
2873
  `${ind(ctx)}const ${vv} = ${JSON.stringify(value)}`
2025
2874
  );
2026
- } else if (len <= 4) {
2875
+ } else if (len <= CHARCODE_CHAIN_MAX) {
2027
2876
  const checks = Array.from(
2028
2877
  { length: len },
2029
2878
  (_, i) => `input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""}) !== ${value.codePointAt(i)}`
@@ -2045,7 +2894,55 @@ function emitLit(def, ctx, pos) {
2045
2894
  function escapeKeywordRe(s) {
2046
2895
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2047
2896
  }
2897
+ function emitKeywordsFast(def, ctx, pos) {
2898
+ if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
2899
+ if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
2900
+ if (def.caseInsensitive && def.boundary) return null;
2901
+ let boundary = null;
2902
+ if (def.boundary) {
2903
+ let body = def.boundary;
2904
+ const negated = body.startsWith("^");
2905
+ if (negated) body = body.slice(1);
2906
+ const ranges = parseClassRanges(body);
2907
+ if (!ranges) return null;
2908
+ boundary = { ranges, negated };
2909
+ }
2910
+ const mint = (prefix = "_v") => v(ctx, prefix);
2911
+ const lbl = v(ctx, "_kwLbl");
2912
+ const valV = v(ctx, "_kwv");
2913
+ const endV = v(ctx, "_kwe");
2914
+ const bodyInd = ind(ctx) + " ";
2915
+ const tries = [];
2916
+ for (const w of def.words) {
2917
+ const cps = Array.from(w, (ch) => ch.codePointAt(0));
2918
+ let shape = def.caseInsensitive ? { kind: "litFold", open: cps } : { kind: "seq", parts: [{ part: "lit", cps, optional: false }] };
2919
+ if (boundary) {
2920
+ shape = { kind: "lookahead", inner: shape, ranges: boundary.ranges, classNegated: boundary.negated, negative: true };
2921
+ }
2922
+ const m = emitShapeMatch(shape, pos, mint, bodyInd);
2923
+ tries.push(
2924
+ ...m.setup,
2925
+ // Slice from input rather than reusing the literal word: `caseInsensitive`
2926
+ // must return the text as it actually appeared (e.g. "ABC" for keyword
2927
+ // "abc"), matching what the original `RegExp.exec()[0]` returned.
2928
+ `${bodyInd}if (${m.ok}) { ${valV} = input.slice(${pos}, ${m.end}); ${endV} = ${m.end}; break ${lbl} }`
2929
+ );
2930
+ }
2931
+ const stmts = [
2932
+ `${ind(ctx)}let ${valV} = '', ${endV} = ${pos}`,
2933
+ `${ind(ctx)}${lbl}: {`,
2934
+ ...tries,
2935
+ `${ind(ctx)}}`,
2936
+ // Every word has length >= 1 (checked above), so a real match always
2937
+ // advances past `pos` — `endV === pos` only happens when no candidate matched.
2938
+ ...emitIfFail(ctx, `${endV} === ${pos}`, failBody(ctx, '"keyword"', pos))
2939
+ ];
2940
+ stmts.push(...emitLeafCapture(ctx, valV, pos, endV));
2941
+ return { stmts, valueVar: valV, endVar: endV };
2942
+ }
2048
2943
  function emitKeywords(def, ctx, pos) {
2944
+ const fast = emitKeywordsFast(def, ctx, pos);
2945
+ if (fast) return fast;
2049
2946
  const alt = def.words.map(escapeKeywordRe).join("|");
2050
2947
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
2051
2948
  const flags = def.caseInsensitive ? "iuy" : "uy";
@@ -2070,6 +2967,22 @@ function emitKeywords(def, ctx, pos) {
2070
2967
  return { stmts, valueVar: vv, endVar };
2071
2968
  }
2072
2969
  function emitRegex(def, ctx, pos) {
2970
+ const expectedStr = JSON.stringify(`/${def.source}/`);
2971
+ const shape = scanShapeFromRegex(def.source, def.flags);
2972
+ if (shape) {
2973
+ const vv2 = v(ctx);
2974
+ const scanned = emitScannableTerminal(shape, {
2975
+ ind: ind(ctx),
2976
+ pos,
2977
+ valueVar: vv2,
2978
+ failIf: (cond) => emitIfFail(ctx, cond, failBody(ctx, expectedStr, pos)),
2979
+ fresh: (prefix) => v(ctx, prefix)
2980
+ });
2981
+ if (scanned) {
2982
+ const stmts2 = [...scanned.stmts, ...emitLeafCapture(ctx, vv2, pos, scanned.endVar)];
2983
+ return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
2984
+ }
2985
+ }
2073
2986
  const flags = "y" + def.flags.replace(/[gy]/g, "");
2074
2987
  const key = `${def.optimizedSource}/${flags}`;
2075
2988
  let rName = ctx.regexMap.get(key);
@@ -2080,7 +2993,6 @@ function emitRegex(def, ctx, pos) {
2080
2993
  }
2081
2994
  const mv = v(ctx, "_m");
2082
2995
  const vv = v(ctx);
2083
- const expectedStr = JSON.stringify(`/${def.source}/`);
2084
2996
  const stmts = [
2085
2997
  `${ind(ctx)}${rName}.lastIndex = ${pos}`,
2086
2998
  `${ind(ctx)}const ${mv} = ${rName}.exec(input)`,
@@ -2139,15 +3051,68 @@ function emitSeq(def, ctx, pos) {
2139
3051
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
2140
3052
  return { stmts, valueVar: arrV, endVar };
2141
3053
  }
3054
+ function staticExpectedArr(parsers) {
3055
+ return JSON.stringify(parsers.flatMap((p) => {
3056
+ const e = staticExpected(p);
3057
+ return e.length > 0 ? e : [p._tag];
3058
+ }));
3059
+ }
3060
+ function failsAtStart(p) {
3061
+ const d = p._def;
3062
+ switch (d.tag) {
3063
+ case "literal":
3064
+ case "regex":
3065
+ case "keywords":
3066
+ case "guard":
3067
+ case "not":
3068
+ return true;
3069
+ case "transform":
3070
+ case "label":
3071
+ return failsAtStart(d.parser);
3072
+ default:
3073
+ return false;
3074
+ }
3075
+ }
3076
+ function armStaticExpected(ctx, p) {
3077
+ return hoistExpected(ctx, staticExpectedArr([p]));
3078
+ }
3079
+ function canMatchEmptyAtStart(p) {
3080
+ const d = p._def;
3081
+ switch (d.tag) {
3082
+ case "regex":
3083
+ return /(?:[*?]|\{0,|\{\d*,)/.test(d.source);
3084
+ case "optional":
3085
+ return true;
3086
+ case "many":
3087
+ return true;
3088
+ case "transform":
3089
+ case "label":
3090
+ return canMatchEmptyAtStart(d.parser);
3091
+ case "literal":
3092
+ return d.value.length === 0;
3093
+ default:
3094
+ return false;
3095
+ }
3096
+ }
3097
+ function needsFirstSetGuard(p) {
3098
+ const fs2 = p._meta.firstSet;
3099
+ return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
3100
+ }
3101
+ function emitDisjointArm(p, ctx, pos, valV, endV) {
3102
+ if (failsAtStart(p)) {
3103
+ const r = emit(p, ctx, pos);
3104
+ return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
3105
+ }
3106
+ const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3107
+ return [
3108
+ ...stmts,
3109
+ ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
3110
+ `${ind(ctx)}${valV} = ${valVar}`,
3111
+ `${ind(ctx)}${endV} = ${endVar}`
3112
+ ];
3113
+ }
2142
3114
  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
- );
3115
+ const allExpected = staticExpectedArr(def.parsers);
2151
3116
  if (def.disjoint) {
2152
3117
  const codeV = v(ctx, "_code");
2153
3118
  const valV = v(ctx, "_chv");
@@ -2156,6 +3121,27 @@ function emitChoice(def, ctx, pos) {
2156
3121
  `${ind(ctx)}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
2157
3122
  `${ind(ctx)}let ${valV}, ${endV} = ${pos}`
2158
3123
  ];
3124
+ const plan = _forceDisjointIf ? { kind: "if" } : planDisjointDispatch(def.parsers);
3125
+ if (plan.kind === "switch") {
3126
+ stmts.push(`${ind(ctx)}switch (${codeV}) {`);
3127
+ ctx.indent++;
3128
+ for (let i = 0; i < def.parsers.length; i++) {
3129
+ const p = def.parsers[i];
3130
+ for (const cp of plan.cases[i]) stmts.push(`${ind(ctx)}case ${cp}:`);
3131
+ stmts.push(`${ind(ctx)}{`);
3132
+ ctx.indent++;
3133
+ stmts.push(
3134
+ ...emitDisjointArm(p, ctx, pos, valV, endV),
3135
+ `${ind(ctx)}break`
3136
+ );
3137
+ ctx.indent--;
3138
+ stmts.push(`${ind(ctx)}}`);
3139
+ }
3140
+ stmts.push(`${ind(ctx)}default: ${failArrBody(ctx, allExpected, pos)}`);
3141
+ ctx.indent--;
3142
+ stmts.push(`${ind(ctx)}}`);
3143
+ return { stmts, valueVar: valV, endVar: endV };
3144
+ }
2159
3145
  let first = true;
2160
3146
  for (const p of def.parsers) {
2161
3147
  const cond = firstSetCond(codeV, p._meta.firstSet);
@@ -2163,10 +3149,7 @@ function emitChoice(def, ctx, pos) {
2163
3149
  first = false;
2164
3150
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
2165
3151
  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}`);
3152
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
2170
3153
  ctx.indent--;
2171
3154
  stmts.push(`${ind(ctx)}}`);
2172
3155
  }
@@ -2175,7 +3158,7 @@ function emitChoice(def, ctx, pos) {
2175
3158
  }
2176
3159
  return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
2177
3160
  }
2178
- function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
3161
+ function emitGreedyClassify(def, superIndex, ctx, pos) {
2179
3162
  const superParser = def.parsers[superIndex];
2180
3163
  const regexDef = getCoreRegexDef(superParser);
2181
3164
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -2190,10 +3173,11 @@ function emitGreedyClassify(def, superIndex, allExpected, ctx, pos) {
2190
3173
  const wordV = v(ctx, "_gw");
2191
3174
  const endV = v(ctx, "_ge");
2192
3175
  const valV = v(ctx, "_gcv");
3176
+ const regexExpected = JSON.stringify(staticExpected(superParser));
2193
3177
  const stmts = [
2194
3178
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
2195
3179
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
2196
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, allExpected, pos)),
3180
+ ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
2197
3181
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
2198
3182
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
2199
3183
  `${ind(ctx)}let ${valV}`
@@ -2251,18 +3235,25 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
2251
3235
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
2252
3236
  return { stmts, valueVar: valV, endVar: endV };
2253
3237
  }
2254
- function emitFirstMatch(def, allExpected, ctx, pos) {
3238
+ function emitFirstMatch(def, ctx, pos) {
2255
3239
  const resValV = v(ctx, "_crv");
2256
3240
  const resEndV = v(ctx, "_cre");
2257
3241
  const resOkV = v(ctx, "_crok");
3242
+ const codeV = v(ctx, "_chcode");
3243
+ const slots = def.parsers.map(() => v(ctx, "_cfx"));
2258
3244
  const ind0 = ind(ctx);
2259
3245
  const stmts = [
2260
- `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`
3246
+ `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
3247
+ `${ind0}let ${slots.join(", ")}`,
3248
+ `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
2261
3249
  ];
2262
3250
  for (let i = 0; i < def.parsers.length; i++) {
2263
3251
  const p = def.parsers[i];
2264
3252
  const gate = def.gates[i];
2265
3253
  const autoNot = def.autoNot[i];
3254
+ const atStart = failsAtStart(p);
3255
+ const staticFx = armStaticExpected(ctx, p);
3256
+ const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
2266
3257
  let gateCond = null;
2267
3258
  if (gate) {
2268
3259
  const gateIdx = ctx.mapFns.length;
@@ -2277,49 +3268,53 @@ function emitFirstMatch(def, allExpected, ctx, pos) {
2277
3268
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
2278
3269
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
2279
3270
  const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
3271
+ const failSlot = atStart ? staticFx : "_ctx._fx";
2280
3272
  stmts.push(`${ind0}if (${skipCond}) {`);
2281
- ctx.indent++;
2282
- const ind1 = ind(ctx);
3273
+ if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
3274
+ ctx.indent += fsGuard ? 2 : 1;
2283
3275
  if (markLeaves) {
2284
3276
  stmts.push(
2285
- `${ind1}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
2286
- `${ind1}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
2287
- `${ind1}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
2288
- `${ind1}const ${markLog} = _ctx._triviaLog?.length ?? 0`
3277
+ `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3278
+ `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3279
+ `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3280
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
2289
3281
  );
2290
3282
  }
2291
- const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
3283
+ const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
2292
3284
  stmts.push(...armStmts);
2293
- ctx.indent--;
2294
3285
  if (autoNot && autoNot.length > 0) {
2295
3286
  const anCode = v(ctx, "_anc");
2296
3287
  const rejectCond = autoNot.map(
2297
3288
  (check) => check.kind === "firstSet" ? firstSetCond(anCode, check.set) : `input.startsWith(${JSON.stringify(check.value)}, ${endVar})`
2298
3289
  ).join(" || ");
2299
- stmts.push(`${ind0} if (${okVar}) {`);
2300
- stmts.push(`${ind0} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
2301
- stmts.push(`${ind0} if (!(${rejectCond})) {`);
2302
- stmts.push(`${ind0} ${resValV} = ${valVar}`);
2303
- stmts.push(`${ind0} ${resEndV} = ${endVar}`);
2304
- stmts.push(`${ind0} ${resOkV} = true`);
2305
- stmts.push(`${ind0} }`);
2306
- stmts.push(`${ind0} }`);
2307
- if (rollback) stmts.push(`${ind0} if (!${resOkV}) { ${rollback} }`);
3290
+ stmts.push(`${ind(ctx)}if (${okVar}) {`);
3291
+ stmts.push(`${ind(ctx)} const ${anCode} = ${endVar} < input.length ? input.charCodeAt(${endVar}) : -1`);
3292
+ stmts.push(`${ind(ctx)} if (!(${rejectCond})) {`);
3293
+ stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
3294
+ stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
3295
+ stmts.push(`${ind(ctx)} ${resOkV} = true`);
3296
+ stmts.push(`${ind(ctx)} }`);
3297
+ stmts.push(`${ind(ctx)}}`);
3298
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
3299
+ if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
2308
3300
  } else {
2309
- stmts.push(`${ind0} if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
2310
- if (rollback) stmts.push(`${ind0} else { ${rollback} }`);
3301
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
3302
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
2311
3303
  }
3304
+ ctx.indent -= fsGuard ? 2 : 1;
3305
+ if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
2312
3306
  stmts.push(`${ind0}}`);
2313
3307
  }
2314
- stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, allExpected, pos)));
3308
+ const concatExpr = `[${slots.map((s) => `...(${s} || [])`).join(", ")}]`;
3309
+ stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
2315
3310
  return { stmts, valueVar: resValV, endVar: resEndV };
2316
3311
  }
2317
3312
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
2318
3313
  if (strategy.tag === "greedyClassify")
2319
- return emitGreedyClassify(def, strategy.superIndex, allExpected, ctx, pos);
3314
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
2320
3315
  if (strategy.tag === "literalsLongestFirst")
2321
3316
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
2322
- return emitFirstMatch(def, allExpected, ctx, pos);
3317
+ return emitFirstMatch(def, ctx, pos);
2323
3318
  }
2324
3319
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2325
3320
  const def = p._def;
@@ -2340,7 +3335,7 @@ function emitTransformChain(p, baseValue, endV, startPos, ctx) {
2340
3335
  function emitLiteralCondition(litVal, pos) {
2341
3336
  const len = litVal.length;
2342
3337
  if (len === 0) return "true";
2343
- if (len > 4) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
3338
+ if (len > CHARCODE_CHAIN_MAX) return `input.startsWith(${JSON.stringify(litVal)}, ${pos})`;
2344
3339
  const checks = [`${pos} + ${len} <= input.length`];
2345
3340
  for (let i = 0; i < len; i++) {
2346
3341
  const code = litVal.codePointAt(i);
@@ -2389,7 +3384,7 @@ function emitMany(def, ctx, pos) {
2389
3384
  itemPos = npV;
2390
3385
  }
2391
3386
  }
2392
- const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos);
3387
+ const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
2393
3388
  stmts.push(
2394
3389
  ...iterStmts,
2395
3390
  `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
@@ -2403,7 +3398,7 @@ function emitMany(def, ctx, pos) {
2403
3398
  function emitOptional(def, ctx, pos) {
2404
3399
  const valV = v(ctx, "_opt");
2405
3400
  const endV = v(ctx, "_opte");
2406
- const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
3401
+ const { stmts: lblStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos, true);
2407
3402
  const ind0 = ind(ctx);
2408
3403
  const stmts = [
2409
3404
  ...lblStmts,
@@ -2415,7 +3410,7 @@ function emitOptional(def, ctx, pos) {
2415
3410
  function emitSepBy(_p, def, ctx, pos) {
2416
3411
  const arrV = v(ctx, "_arr");
2417
3412
  const curV = v(ctx, "_cur");
2418
- const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos);
3413
+ const { stmts: firstStmts, okVar: firstOk, valVar: firstVal, endVar: firstEnd } = emitFallible(def.parser, ctx, pos, true);
2419
3414
  const stmts = [
2420
3415
  `${ind(ctx)}const ${arrV} = []`,
2421
3416
  `${ind(ctx)}let ${curV} = ${pos}`,
@@ -2447,11 +3442,11 @@ function emitSepBy(_p, def, ctx, pos) {
2447
3442
  );
2448
3443
  sepAtPos = spV;
2449
3444
  const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
2450
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3445
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2451
3446
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
2452
3447
  const npV = v(ctx, "_np");
2453
3448
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
2454
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3449
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2455
3450
  stmts.push(
2456
3451
  ...nextStmts,
2457
3452
  // item failed → unwind the separator too, back to the end of the last item
@@ -2464,11 +3459,11 @@ function emitSepBy(_p, def, ctx, pos) {
2464
3459
  const spV = v(ctx, "_sp");
2465
3460
  stmts.push(`${ind(ctx)}const ${spV} = ${trivFn}(input, ${curV}, _ctx)`);
2466
3461
  sepAtPos = spV;
2467
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3462
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2468
3463
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2469
3464
  const npV = v(ctx, "_np");
2470
3465
  stmts.push(`${ind(ctx)}const ${npV} = ${trivFn}(input, ${sepEnd}, _ctx)`);
2471
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV);
3466
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
2472
3467
  stmts.push(
2473
3468
  ...nextStmts,
2474
3469
  `${ind(ctx)}if (!${nextOk}) break`,
@@ -2485,10 +3480,10 @@ function emitSepBy(_p, def, ctx, pos) {
2485
3480
  `${ind(ctx)}const ${markRw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`
2486
3481
  );
2487
3482
  }
2488
- const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos);
3483
+ const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
2489
3484
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
2490
3485
  const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
2491
- const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd);
3486
+ const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
2492
3487
  stmts.push(
2493
3488
  ...nextStmts,
2494
3489
  `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`,
@@ -2546,11 +3541,12 @@ function emitScanTo(def, ctx, pos) {
2546
3541
  return { stmts, valueVar: valV, endVar: curV };
2547
3542
  }
2548
3543
  function emitNot(def, ctx, pos) {
2549
- const { stmts, okVar } = emitFallible(def.parser, ctx, pos);
3544
+ const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
3545
+ const label2 = JSON.stringify(`not(${def.parser._tag})`);
2550
3546
  return {
2551
3547
  stmts: [
2552
3548
  ...stmts,
2553
- ...emitIfFail(ctx, okVar, failBody(ctx, '"not"', pos))
3549
+ ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
2554
3550
  ],
2555
3551
  valueVar: "null",
2556
3552
  endVar: pos
@@ -2582,7 +3578,7 @@ function emitNode(def, ctx, pos) {
2582
3578
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
2583
3579
  stmts.push(...innerStmts);
2584
3580
  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)));
3581
+ stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
2586
3582
  let stV = "undefined";
2587
3583
  if (clonesState) {
2588
3584
  stV = v(ctx, "_nst");
@@ -2604,9 +3600,10 @@ function emitRuntimeFallback(parser2, ctx, pos) {
2604
3600
  const rv = v(ctx, "_rt");
2605
3601
  const vv = v(ctx, "_rtv");
2606
3602
  const ev = v(ctx, "_rte");
3603
+ 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
3604
  const stmts = [
2608
3605
  `${ind(ctx)}const ${rv} = _rp[${idx}].parse(input, ${pos}, _ctx)`,
2609
- ...emitIfFail(ctx, `!${rv}.ok`, failBody(ctx, '"runtime"', pos)),
3606
+ ...emitIfFail(ctx, `!${rv}.ok`, failStmt),
2610
3607
  `${ind(ctx)}const ${vv} = ${rv}.value`,
2611
3608
  `${ind(ctx)}const ${ev} = ${rv}.span.end`
2612
3609
  ];
@@ -2635,15 +3632,18 @@ function emitLazy(p, def, ctx, pos) {
2635
3632
  }
2636
3633
  const savedIndent = ctx.indent;
2637
3634
  const savedFailLabel = ctx.failLabel;
3635
+ const savedRecord = ctx.recordFail;
2638
3636
  ctx.indent = 1;
2639
3637
  ctx.failLabel = "_pfail";
3638
+ ctx.recordFail = true;
2640
3639
  const r = emit(resolved, ctx, "_pos");
2641
3640
  ctx.indent = savedIndent;
2642
3641
  ctx.failLabel = savedFailLabel;
3642
+ ctx.recordFail = savedRecord;
2643
3643
  pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
2644
3644
  }
2645
3645
  const fnName = ctx.namedParsers.get(p);
2646
- return emitNamedFnCall(ctx, fnName, pos, '"parser"');
3646
+ return emitNamedFnCall(ctx, fnName, pos);
2647
3647
  }
2648
3648
  function emitRecover(def, ctx, pos) {
2649
3649
  const { stmts: innerStmts, okVar, valVar, endVar } = emitFallible(def.parser, ctx, pos);
@@ -2661,7 +3661,7 @@ function emitRecover(def, ctx, pos) {
2661
3661
  `${ind0} if (${sentOk}) break`,
2662
3662
  `${ind0} ${scanV}++`,
2663
3663
  `${ind0} }`,
2664
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: [] }`,
3664
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
2665
3665
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
2666
3666
  `${ind0} ${valVar} = ${errV}`,
2667
3667
  `${ind0} ${endVar} = ${scanV}`,
@@ -2819,11 +3819,14 @@ function emit(p, ctx, pos) {
2819
3819
  ctx.namedParsers.set(innerParser, fnName);
2820
3820
  const savedIndent = ctx.indent;
2821
3821
  const savedFailLabel = ctx.failLabel;
3822
+ const savedRecord = ctx.recordFail;
2822
3823
  ctx.indent = 1;
2823
3824
  ctx.failLabel = "_pfail";
3825
+ ctx.recordFail = true;
2824
3826
  const innerR = emit(innerParser, ctx, "_pos");
2825
3827
  ctx.indent = savedIndent;
2826
3828
  ctx.failLabel = savedFailLabel;
3829
+ ctx.recordFail = savedRecord;
2827
3830
  pushNamedFnDecl(ctx, fnName, innerR.stmts, innerR.valueVar, innerR.endVar);
2828
3831
  }
2829
3832
  const fn = ctx.namedParsers.get(innerParser);
@@ -2834,7 +3837,9 @@ function emit(p, ctx, pos) {
2834
3837
  stmts: [
2835
3838
  `${ind(ctx)}const ${rv} = { ..._ctx, state: _mf[${evIdx}]() }`,
2836
3839
  `${ind(ctx)}const ${vv} = ${fn}(input, ${pos}, ${rv})`,
2837
- ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, failBody(ctx, '"withCtx"', pos)),
3840
+ // withCtx runs on a spread child ctx — copy its recorded failure back
3841
+ // and propagate the inner failure verbatim (interpreter parity).
3842
+ ...emitIfFail(ctx, `${vv} === ${NAMED_FN_FAIL}`, propagateFailBody(ctx, rv)),
2838
3843
  `${ind(ctx)}const ${ev} = ${NAMED_FN_END}`
2839
3844
  ],
2840
3845
  valueVar: vv,
@@ -2935,7 +3940,7 @@ function analyzeLazyUsageMulti(roots) {
2935
3940
  const recursive = /* @__PURE__ */ new Set();
2936
3941
  const descended = /* @__PURE__ */ new Set();
2937
3942
  const active = /* @__PURE__ */ new Set();
2938
- function walk(p) {
3943
+ function walk2(p) {
2939
3944
  const def = p._def;
2940
3945
  if (def.tag === "lazy") {
2941
3946
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -2952,13 +3957,13 @@ function analyzeLazyUsageMulti(roots) {
2952
3957
  return;
2953
3958
  }
2954
3959
  active.add(p);
2955
- walk(resolved);
3960
+ walk2(resolved);
2956
3961
  active.delete(p);
2957
3962
  return;
2958
3963
  }
2959
- for (const child of childrenOf(def)) walk(child);
3964
+ for (const child of childrenOf(def)) walk2(child);
2960
3965
  }
2961
- for (const root of roots) walk(root);
3966
+ for (const root of roots) walk2(root);
2962
3967
  return { counts, recursive };
2963
3968
  }
2964
3969
  function compile(combinator, mapFnSources) {
@@ -2967,6 +3972,9 @@ function compile(combinator, mapFnSources) {
2967
3972
  indent: 1,
2968
3973
  regexDecls: [],
2969
3974
  regexMap: /* @__PURE__ */ new Map(),
3975
+ expectedDecls: [],
3976
+ expectedMap: /* @__PURE__ */ new Map(),
3977
+ recordFail: true,
2970
3978
  mapFns: [],
2971
3979
  mapFnSrcs: [],
2972
3980
  buildFns: [],
@@ -2988,6 +3996,7 @@ function compile(combinator, mapFnSources) {
2988
3996
  const source = [
2989
3997
  ...emptyTlDecls,
2990
3998
  ...ctx.regexDecls,
3999
+ ...ctx.expectedDecls,
2991
4000
  "",
2992
4001
  ...namedPrelude,
2993
4002
  ctx.namedFnDecls.join("\n\n"),
@@ -3000,6 +4009,7 @@ function compile(combinator, mapFnSources) {
3000
4009
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
3001
4010
  ...emptyTlDecls,
3002
4011
  ...ctx.regexDecls,
4012
+ ...ctx.expectedDecls,
3003
4013
  collatorDecl,
3004
4014
  ...namedPrelude,
3005
4015
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
@@ -3045,6 +4055,9 @@ function compileRuleMap(ruleMap) {
3045
4055
  indent: 1,
3046
4056
  regexDecls: [],
3047
4057
  regexMap: /* @__PURE__ */ new Map(),
4058
+ expectedDecls: [],
4059
+ expectedMap: /* @__PURE__ */ new Map(),
4060
+ recordFail: true,
3048
4061
  mapFns: [],
3049
4062
  mapFnSrcs: [],
3050
4063
  buildFns: [],
@@ -3073,6 +4086,7 @@ function compileRuleMap(ruleMap) {
3073
4086
  const hoistedDecls = [
3074
4087
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
3075
4088
  ...ctx.regexDecls.map((d) => ` ${d}`),
4089
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3076
4090
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3077
4091
  mfDecl,
3078
4092
  buildDecl,
@@ -3114,12 +4128,13 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3114
4128
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
3115
4129
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
3116
4130
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
3117
- const needsWrapper = ctx.regexDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4131
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
3118
4132
  if (!needsWrapper) return innerFn;
3119
4133
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
3120
4134
  const hoistedDecls = [
3121
4135
  emptyTlDecl,
3122
4136
  ...ctx.regexDecls.map((d) => ` ${d}`),
4137
+ ...ctx.expectedDecls.map((d) => ` ${d}`),
3123
4138
  collatorDecl ? ` ${collatorDecl.trim()}` : "",
3124
4139
  mfDecl,
3125
4140
  buildDecl,
@@ -3137,58 +4152,6 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
3137
4152
  ].join("\n");
3138
4153
  }
3139
4154
 
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
4155
  // src/combinators/scanTo.ts
3193
4156
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
3194
4157
  const meta = {
@@ -3269,11 +4232,11 @@ function balanced(open, close, options = {}) {
3269
4232
  return combi;
3270
4233
  }
3271
4234
  function firstSetClassChars(c) {
3272
- const fs = c._meta.firstSet;
3273
- if (fs.kind === "empty") return [];
3274
- if (fs.kind !== "ranges") return null;
4235
+ const fs2 = c._meta.firstSet;
4236
+ if (fs2.kind === "empty") return [];
4237
+ if (fs2.kind !== "ranges") return null;
3275
4238
  const out = [];
3276
- for (const { lo, hi } of fs.ranges) {
4239
+ for (const { lo, hi } of fs2.ranges) {
3277
4240
  if (hi - lo > 8) return null;
3278
4241
  for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
3279
4242
  }
@@ -3570,7 +4533,124 @@ function evaluateWordFactory(node2, scope, code) {
3570
4533
  function evaluateExpr(node2, scope, code, mapFnSources) {
3571
4534
  return exprToCombi(node2, scope, code, mapFnSources);
3572
4535
  }
3573
- function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
4536
+ function evaluateCombinatorArray(node2, scope, code) {
4537
+ if (node2.type !== "ArrayExpression") return null;
4538
+ const val = anyValue(node2, scope, code, []);
4539
+ if (!Array.isArray(val) || val.length === 0) return null;
4540
+ if (!val.every(isCombinator)) return null;
4541
+ return val;
4542
+ }
4543
+ function asFragmentFactory(expr) {
4544
+ if (expr.type !== "ArrowFunctionExpression" && expr.type !== "FunctionExpression" && expr.type !== "FunctionDeclaration") return null;
4545
+ const fn = expr;
4546
+ return factoryBodyAndReturn(fn) ? fn : null;
4547
+ }
4548
+ var unwrapParen = (e) => e && e.type === "ParenthesizedExpression" ? e.expression : e;
4549
+ function factoryBodyAndReturn(fn) {
4550
+ const body = fn.body;
4551
+ if (!body) return null;
4552
+ const b = unwrapParen(body);
4553
+ if (b.type === "ObjectExpression") return { statements: [], retObj: b };
4554
+ if (b.type === "BlockStatement") {
4555
+ const statements = [];
4556
+ for (const stmt of b.body) {
4557
+ if (stmt.type === "ReturnStatement") {
4558
+ const a = unwrapParen(stmt.argument);
4559
+ if (!a || a.type !== "ObjectExpression") return null;
4560
+ return { statements, retObj: a };
4561
+ }
4562
+ if (stmt.type === "VariableDeclaration") statements.push(stmt);
4563
+ else return null;
4564
+ }
4565
+ }
4566
+ return null;
4567
+ }
4568
+ function spreadFactoryCall(spread, resolve) {
4569
+ const call = spread.argument;
4570
+ if (!call || call.type !== "CallExpression") return null;
4571
+ if (!call.callee || call.callee.type !== "Identifier" || !call.callee.name) return null;
4572
+ const resolved = resolve(call.callee.name);
4573
+ return resolved ? { fn: resolved.fn, args: call.arguments ?? [], code: resolved.code } : null;
4574
+ }
4575
+ function factoryCallScope(fn, args, outer) {
4576
+ const s = new Map(outer);
4577
+ const params = fn.params ?? [];
4578
+ for (let i = 0; i < params.length; i++) {
4579
+ const pn = params[i];
4580
+ if (!pn || pn.type !== "Identifier" || !pn.name) continue;
4581
+ const arg = args[i];
4582
+ if (arg && arg.type === "Identifier" && arg.name && outer.has(arg.name)) {
4583
+ s.set(pn.name, outer.get(arg.name));
4584
+ }
4585
+ }
4586
+ return s;
4587
+ }
4588
+ function propKey(prop) {
4589
+ if (prop.type !== "Property" || prop.computed || !prop.key) return null;
4590
+ if (prop.key.type === "Identifier") return prop.key.name ?? null;
4591
+ if (prop.key.type === "Literal") return String(prop.key.value);
4592
+ return null;
4593
+ }
4594
+ function collectRuleKeys(retObj, resolve, seen) {
4595
+ const out = [];
4596
+ for (const prop of retObj.properties) {
4597
+ if (prop.type === "SpreadElement") {
4598
+ const sf = spreadFactoryCall(prop, resolve);
4599
+ if (!sf || seen.has(sf.fn)) return null;
4600
+ const br = factoryBodyAndReturn(sf.fn);
4601
+ if (!br) return null;
4602
+ const nested = collectRuleKeys(br.retObj, resolve, new Set(seen).add(sf.fn));
4603
+ if (!nested) return null;
4604
+ out.push(...nested);
4605
+ continue;
4606
+ }
4607
+ const key = propKey(prop);
4608
+ if (!key) return null;
4609
+ out.push(key);
4610
+ }
4611
+ return out;
4612
+ }
4613
+ function flattenRuleEntries(retObj, scope, code, resolve, seen) {
4614
+ const out = [];
4615
+ for (const prop of retObj.properties) {
4616
+ if (prop.type === "SpreadElement") {
4617
+ const sf = spreadFactoryCall(prop, resolve);
4618
+ if (!sf || seen.has(sf.fn)) return null;
4619
+ const br = factoryBodyAndReturn(sf.fn);
4620
+ if (!br) return null;
4621
+ const fragScope = factoryCallScope(sf.fn, sf.args, scope);
4622
+ if (!evalBodyStatements(br.statements, fragScope, sf.code)) return null;
4623
+ const nested = flattenRuleEntries(br.retObj, fragScope, sf.code, resolve, new Set(seen).add(sf.fn));
4624
+ if (!nested) return null;
4625
+ out.push(...nested);
4626
+ continue;
4627
+ }
4628
+ const key = propKey(prop);
4629
+ if (!key) return null;
4630
+ out.push({ key, value: prop.value, scope, code });
4631
+ }
4632
+ return out;
4633
+ }
4634
+ function evalBodyStatements(statements, scope, code) {
4635
+ const bodyMfs = [];
4636
+ for (const stmt of statements) {
4637
+ for (const d of stmt.declarations) {
4638
+ const decl = d;
4639
+ if (!decl.init) return false;
4640
+ const id = decl.id;
4641
+ if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return false;
4642
+ const name = id.name;
4643
+ const before = bodyMfs.length;
4644
+ const val = anyValue(decl.init, scope, code, bodyMfs);
4645
+ if (val === null) return false;
4646
+ const thisDeclMfSrcs = bodyMfs.slice(before);
4647
+ if (isCombinator(val)) scope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
4648
+ else scope.set(name, val);
4649
+ }
4650
+ }
4651
+ return true;
4652
+ }
4653
+ function evaluateParserFactory(factoryNode, scope, code, mapFnSources, resolveFactory = () => null) {
3574
4654
  if (factoryNode.type !== "ArrowFunctionExpression" && factoryNode.type !== "FunctionDeclaration" && factoryNode.type !== "FunctionExpression") return null;
3575
4655
  const factory = factoryNode;
3576
4656
  const params = factory.params;
@@ -3602,40 +4682,21 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
3602
4682
  if (!returnExpr) return null;
3603
4683
  const retObj = returnExpr.type === "ParenthesizedExpression" ? returnExpr.expression : returnExpr;
3604
4684
  if (retObj.type !== "ObjectExpression") return null;
4685
+ const keys = collectRuleKeys(retObj, resolveFactory, /* @__PURE__ */ new Set());
4686
+ if (!keys) return null;
3605
4687
  const ruleRefs = /* @__PURE__ */ new Map();
3606
- for (const prop of retObj.properties) {
3607
- if (prop.type !== "Property") return null;
3608
- if (prop.computed) return null;
3609
- const p = prop;
3610
- const key = p.key.type === "Identifier" ? p.key.name : p.key.type === "Literal" ? String(p.key.value) : null;
3611
- if (!key) return null;
3612
- ruleRefs.set(key, ref());
4688
+ for (const key of keys) {
4689
+ if (!ruleRefs.has(key)) ruleRefs.set(key, ref());
3613
4690
  }
3614
4691
  const localScope = new Map(scope);
3615
4692
  localScope.set(proxyName, Object.fromEntries(ruleRefs));
3616
- const bodyMfs = [];
3617
- for (const stmt of statements) {
3618
- for (const d of stmt.declarations) {
3619
- const decl = d;
3620
- if (!decl.init) return null;
3621
- const id = decl.id;
3622
- if (id.type !== "Identifier" && id.type !== "BindingIdentifier") return null;
3623
- const name = id.name;
3624
- const before = bodyMfs.length;
3625
- const val = anyValue(decl.init, localScope, code, bodyMfs);
3626
- if (val === null) return null;
3627
- const thisDeclMfSrcs = bodyMfs.slice(before);
3628
- if (isCombinator(val)) {
3629
- localScope.set(name, { combi: val, mfSrcs: thisDeclMfSrcs });
3630
- } else {
3631
- localScope.set(name, val);
3632
- }
3633
- }
3634
- }
3635
- for (const prop of retObj.properties) {
3636
- const p = prop;
3637
- const key = p.key.type === "Identifier" ? p.key.name : String(p.key.value);
3638
- const val = anyValue(p.value, localScope, code, mapFnSources);
4693
+ if (!evalBodyStatements(statements, localScope, code)) return null;
4694
+ const entries = flattenRuleEntries(retObj, localScope, code, resolveFactory, /* @__PURE__ */ new Set());
4695
+ if (!entries) return null;
4696
+ const finalByKey = /* @__PURE__ */ new Map();
4697
+ for (const e of entries) finalByKey.set(e.key, e);
4698
+ for (const [key, e] of finalByKey) {
4699
+ const val = anyValue(e.value, e.scope, e.code, mapFnSources);
3639
4700
  if (!isCombinator(val)) return null;
3640
4701
  ruleRefs.get(key).define(val);
3641
4702
  }
@@ -3693,6 +4754,40 @@ function referencesAny(node2, names, scope) {
3693
4754
 
3694
4755
  // src/plugin/index.ts
3695
4756
  var PARSEMAN_MODULE = "parseman";
4757
+ var _fragmentResolver = null;
4758
+ function getFragmentResolver() {
4759
+ return _fragmentResolver ??= new ResolverFactory({
4760
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
4761
+ // TS/NodeNext style: a `./x.js` import may point at `./x.ts` source.
4762
+ extensionAlias: {
4763
+ ".js": [".ts", ".tsx", ".js", ".jsx"],
4764
+ ".mjs": [".mts", ".mjs"],
4765
+ ".cjs": [".cts", ".cjs"]
4766
+ },
4767
+ conditionNames: ["source", "development", "import", "require", "default"],
4768
+ mainFields: ["source", "module", "main"]
4769
+ });
4770
+ }
4771
+ var _moduleParseCache = /* @__PURE__ */ new Map();
4772
+ function parseModuleCached(filePath) {
4773
+ let mtimeMs;
4774
+ try {
4775
+ mtimeMs = fs.statSync(filePath).mtimeMs;
4776
+ } catch {
4777
+ return null;
4778
+ }
4779
+ const hit = _moduleParseCache.get(filePath);
4780
+ if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
4781
+ let parsed = null;
4782
+ try {
4783
+ const src = fs.readFileSync(filePath, "utf8");
4784
+ const r = parseSync(filePath, src);
4785
+ if (r.errors.length === 0) parsed = { body: r.program.body, src };
4786
+ } catch {
4787
+ }
4788
+ _moduleParseCache.set(filePath, { mtimeMs, parsed });
4789
+ return parsed;
4790
+ }
3696
4791
  var plugin_default = createUnplugin((opts = {}) => ({
3697
4792
  name: "parseman",
3698
4793
  // Run BEFORE the bundler's TS/JS transform — otherwise esbuild (Vite) strips
@@ -3728,15 +4823,23 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3728
4823
  const body = result.program.body;
3729
4824
  const macroImports = [];
3730
4825
  const allNames = /* @__PURE__ */ new Set();
4826
+ const importBindings = /* @__PURE__ */ new Map();
3731
4827
  for (const stmt of body) {
3732
4828
  if (stmt.type !== "ImportDeclaration") continue;
3733
4829
  const s = stmt;
3734
- if (!moduleAliases.has(s.source.value)) continue;
3735
- const isMacro = s.attributes.some((a) => {
4830
+ const isMacro = moduleAliases.has(s.source.value) && s.attributes.some((a) => {
3736
4831
  const key = a.key.type === "Identifier" ? a.key.name : String(a.key.value);
3737
4832
  return key === "type" && a.value.value === "macro";
3738
4833
  });
3739
- if (!isMacro) continue;
4834
+ if (!isMacro) {
4835
+ for (const spec of s.specifiers) {
4836
+ if (spec.type === "ImportSpecifier") {
4837
+ const imported = spec.imported.name ?? spec.local.name;
4838
+ importBindings.set(spec.local.name, { source: s.source.value, imported });
4839
+ }
4840
+ }
4841
+ continue;
4842
+ }
3740
4843
  const names = /* @__PURE__ */ new Set();
3741
4844
  for (const spec of s.specifiers) {
3742
4845
  if (spec.type === "ImportSpecifier") names.add(spec.local.name);
@@ -3746,6 +4849,63 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3746
4849
  }
3747
4850
  if (macroImports.length === 0) return null;
3748
4851
  const scope = /* @__PURE__ */ new Map();
4852
+ const factories = /* @__PURE__ */ new Map();
4853
+ const resolver = getFragmentResolver();
4854
+ const importedFactoryCache = /* @__PURE__ */ new Map();
4855
+ const OUTPUT_DIR = /([\\/])(dist|lib|build|out|output|compiled|cjs|esm|es)([\\/])/;
4856
+ const resolveSourcePath = (spec) => {
4857
+ let resolved = null;
4858
+ try {
4859
+ resolved = resolver.resolveFileSync(id, spec).path ?? null;
4860
+ } catch {
4861
+ }
4862
+ if (!resolved) return null;
4863
+ if (OUTPUT_DIR.test(resolved)) {
4864
+ const srcCandidate = resolved.replace(OUTPUT_DIR, "$1src$3").replace(/\.(c|m)?js$/, ".ts");
4865
+ try {
4866
+ if (fs.statSync(srcCandidate).isFile()) return srcCandidate;
4867
+ } catch {
4868
+ }
4869
+ }
4870
+ return resolved;
4871
+ };
4872
+ const resolveImportedFactory = (name) => {
4873
+ if (importedFactoryCache.has(name)) return importedFactoryCache.get(name);
4874
+ let result2 = null;
4875
+ const binding = importBindings.get(name);
4876
+ if (binding) {
4877
+ const filePath = resolveSourcePath(binding.source);
4878
+ const mod = filePath ? parseModuleCached(filePath) : null;
4879
+ if (mod) {
4880
+ for (const st of mod.body) {
4881
+ if (st.type !== "ExportNamedDeclaration" || !st.declaration) continue;
4882
+ const decl = st.declaration;
4883
+ if (decl.type === "VariableDeclaration") {
4884
+ for (const d of decl.declarations ?? []) {
4885
+ if (d.id?.type === "Identifier" && d.id.name === binding.imported && d.init) {
4886
+ const fac = asFragmentFactory(d.init);
4887
+ if (fac) {
4888
+ result2 = { fn: fac, code: mod.src };
4889
+ break;
4890
+ }
4891
+ }
4892
+ }
4893
+ } else if (decl.type === "FunctionDeclaration" && decl.id?.type === "Identifier" && decl.id.name === binding.imported) {
4894
+ const fac = asFragmentFactory(decl);
4895
+ if (fac) result2 = { fn: fac, code: mod.src };
4896
+ }
4897
+ if (result2) break;
4898
+ }
4899
+ }
4900
+ }
4901
+ importedFactoryCache.set(name, result2);
4902
+ return result2;
4903
+ };
4904
+ const resolveFactory = (n) => {
4905
+ const local = factories.get(n);
4906
+ if (local) return { fn: local, code };
4907
+ return resolveImportedFactory(n);
4908
+ };
3749
4909
  const replacements = [];
3750
4910
  const warnings = [];
3751
4911
  let anyUnresolved = false;
@@ -3766,7 +4926,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3766
4926
  warn(init.start, `${label2}: rules() needs a factory argument`);
3767
4927
  return null;
3768
4928
  }
3769
- const ruleMap = evaluateParserFactory(factoryArg, scope, code, []);
4929
+ const ruleMap = evaluateParserFactory(factoryArg, scope, code, [], resolveFactory);
3770
4930
  if (!ruleMap) {
3771
4931
  warn(init.start, `${label2}: rules(...) factory isn't statically evaluable`);
3772
4932
  return null;
@@ -3864,6 +5024,11 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3864
5024
  });
3865
5025
  continue;
3866
5026
  }
5027
+ const frag = asFragmentFactory(init);
5028
+ if (frag) {
5029
+ factories.set(varName, frag);
5030
+ continue;
5031
+ }
3867
5032
  const mapFnSources = [];
3868
5033
  const parser2 = evaluateExpr(init, scope, code, mapFnSources);
3869
5034
  if (parser2 === null) {
@@ -3873,6 +5038,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
3873
5038
  scope.set(varName, wordFactory);
3874
5039
  continue;
3875
5040
  }
5041
+ const combiArray = evaluateCombinatorArray(init, scope, code);
5042
+ if (combiArray) {
5043
+ ;
5044
+ scope.set(varName, combiArray);
5045
+ continue;
5046
+ }
3876
5047
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
3877
5048
  continue;
3878
5049
  }