parseman 0.18.2 → 0.20.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.
Files changed (44) hide show
  1. package/README.md +7 -4
  2. package/dist/combinators/map.d.ts +5 -0
  3. package/dist/combinators/map.d.ts.map +1 -1
  4. package/dist/combinators/node.d.ts +14 -13
  5. package/dist/combinators/node.d.ts.map +1 -1
  6. package/dist/combinators/parser.d.ts.map +1 -1
  7. package/dist/combinators/ref.d.ts.map +1 -1
  8. package/dist/combinators/regex.d.ts +7 -7
  9. package/dist/combinators/regex.d.ts.map +1 -1
  10. package/dist/combinators/trivia-skip.d.ts +1 -0
  11. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  12. package/dist/compiler/build-arity.d.ts +3 -3
  13. package/dist/compiler/codegen.d.ts.map +1 -1
  14. package/dist/compiler/fields.d.ts +16 -0
  15. package/dist/compiler/fields.d.ts.map +1 -0
  16. package/dist/compiler/inline-build.d.ts.map +1 -1
  17. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  18. package/dist/compiler/linker.d.ts +17 -3
  19. package/dist/compiler/linker.d.ts.map +1 -1
  20. package/dist/compiler/scannable-run.d.ts +2 -10
  21. package/dist/compiler/scannable-run.d.ts.map +1 -1
  22. package/dist/compiler/value-usage.d.ts.map +1 -1
  23. package/dist/cst/capture-buffer.d.ts +1 -0
  24. package/dist/cst/capture-buffer.d.ts.map +1 -1
  25. package/dist/index.cjs +568 -262
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +3 -3
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +568 -252
  30. package/dist/index.js.map +4 -4
  31. package/dist/plugin/evaluator.d.ts.map +1 -1
  32. package/dist/plugin/index.cjs +568 -260
  33. package/dist/plugin/index.cjs.map +4 -4
  34. package/dist/plugin/index.js +568 -260
  35. package/dist/plugin/index.js.map +4 -4
  36. package/dist/regex/classes.d.ts +40 -0
  37. package/dist/regex/classes.d.ts.map +1 -0
  38. package/dist/regex/first-set.d.ts +35 -0
  39. package/dist/regex/first-set.d.ts.map +1 -0
  40. package/dist/types.d.ts +31 -8
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +2 -3
  43. package/dist/combinators/regex-analyze.d.ts +0 -6
  44. package/dist/combinators/regex-analyze.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -49,6 +39,7 @@ __export(index_exports, {
49
39
  createVisitor: () => createVisitor,
50
40
  cstBuildHost: () => cstBuildHost,
51
41
  expect: () => expect,
42
+ field: () => field,
52
43
  gapIsSignificant: () => gapIsSignificant,
53
44
  gapText: () => gapText,
54
45
  guard: () => guard,
@@ -349,7 +340,8 @@ function saveCstMark(ctx) {
349
340
  return {
350
341
  raw: cstRawLen(ctx),
351
342
  tlog: cstTlLen(ctx),
352
- leaves: cstLeavesLen(ctx)
343
+ leaves: cstLeavesLen(ctx),
344
+ fields: ctx._fields?.length ?? 0
353
345
  };
354
346
  }
355
347
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -373,11 +365,13 @@ function rollbackCstCapture(ctx, mark) {
373
365
  if (mark.tlog === 0) b.tl = void 0;
374
366
  else b.tl.length = mark.tlog;
375
367
  }
368
+ if (ctx._fields) ctx._fields.length = mark.fields;
376
369
  return;
377
370
  }
378
371
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
379
372
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
380
373
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
374
+ if (ctx._fields) ctx._fields.length = mark.fields;
381
375
  }
382
376
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
383
377
  const insertIdx = cstRawLen(ctx);
@@ -421,9 +415,9 @@ function asciiFoldEq(a, b) {
421
415
  }
422
416
  function literal(value, opts = {}) {
423
417
  const caseInsensitive = opts.caseInsensitive ?? false;
424
- const firstSet = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
418
+ const firstSet2 = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
425
419
  const meta = {
426
- firstSet,
420
+ firstSet: firstSet2,
427
421
  canMatchNewline: value.includes("\n"),
428
422
  isTrivia: false
429
423
  };
@@ -435,7 +429,7 @@ function literal(value, opts = {}) {
435
429
  meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
436
430
  { lo: firstLower, hi: firstLower },
437
431
  { lo: firstUpper, hi: firstUpper }
438
- ] } : firstSet;
432
+ ] } : firstSet2;
439
433
  }
440
434
  return {
441
435
  _tag: "literal",
@@ -458,68 +452,66 @@ function literal(value, opts = {}) {
458
452
  };
459
453
  }
460
454
 
461
- // src/combinators/regex.ts
462
- var firstSetAnalyzer = null;
463
- function registerRegexAnalyzer(analyzer) {
464
- firstSetAnalyzer = analyzer;
465
- }
466
- var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
467
- var SCAN_BAIL_AT = 64;
455
+ // src/regex/classes.ts
456
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
468
457
  var SPACE_RANGES = [
469
458
  [9, 13],
470
459
  [32, 32],
471
460
  [160, 160],
472
461
  [5760, 5760],
473
462
  [8192, 8202],
474
- [8232, 8233],
463
+ [8232, 8232],
464
+ [8233, 8233],
475
465
  [8239, 8239],
476
466
  [8287, 8287],
477
467
  [12288, 12288],
478
468
  [65279, 65279]
479
469
  ];
480
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
481
470
  function shorthandRanges(ch) {
482
471
  if (ch === "d") return [[48, 57]];
483
472
  if (ch === "s") return SPACE_RANGES;
484
473
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
485
474
  }
486
- function inRanges(cp, ranges) {
487
- for (let i = 0; i < ranges.length; i++) {
488
- const [lo, hi] = ranges[i];
489
- if (cp >= lo && cp <= hi) return true;
490
- }
491
- return false;
475
+ function readUnicodeEscape(body, i) {
476
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
477
+ const hex = body.slice(i + 2, i + 6);
478
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
479
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
492
480
  }
493
- function readClassRanges(body) {
494
- if (body.startsWith("^")) return null;
481
+ function parseClassRanges(body) {
495
482
  const ranges = [];
496
483
  let i = 0;
497
484
  const readAtom = () => {
498
485
  const ch = body[i];
499
486
  if (ch === void 0) return null;
500
487
  if (ch === "\\") {
501
- const esc = body[i + 1];
502
- if (esc === void 0) return null;
488
+ const uni = readUnicodeEscape(body, i);
489
+ if (uni) {
490
+ i = uni.next;
491
+ return { cp: uni.cp };
492
+ }
493
+ const e = body[i + 1];
494
+ if (e === void 0) return null;
503
495
  i += 2;
504
- if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
505
- if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
506
- if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
507
- return { cp: esc.charCodeAt(0) };
496
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
497
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
498
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
499
+ return { cp: e.codePointAt(0) };
508
500
  }
509
- i++;
510
- return { cp: ch.charCodeAt(0) };
501
+ i += ch.length;
502
+ return { cp: ch.codePointAt(0) };
511
503
  };
512
504
  while (i < body.length) {
513
505
  const lo = readAtom();
514
- if (!lo) return null;
515
- if ("ranges" in lo) {
516
- ranges.push(...lo.ranges);
506
+ if (lo === null) return null;
507
+ if ("set" in lo) {
508
+ ranges.push(...lo.set);
517
509
  continue;
518
510
  }
519
511
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
520
- i++;
512
+ i += 1;
521
513
  const hi = readAtom();
522
- if (!hi || "ranges" in hi) return null;
514
+ if (hi === null || "set" in hi) return null;
523
515
  ranges.push([lo.cp, hi.cp]);
524
516
  } else {
525
517
  ranges.push([lo.cp, lo.cp]);
@@ -527,6 +519,25 @@ function readClassRanges(body) {
527
519
  }
528
520
  return ranges.length ? ranges : null;
529
521
  }
522
+
523
+ // src/combinators/regex.ts
524
+ var firstSetAnalyzer = null;
525
+ function registerRegexAnalyzer(analyzer) {
526
+ firstSetAnalyzer = analyzer;
527
+ }
528
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
529
+ var SCAN_BAIL_AT = 64;
530
+ function inRanges(cp, ranges) {
531
+ for (let i = 0; i < ranges.length; i++) {
532
+ const [lo, hi] = ranges[i];
533
+ if (cp >= lo && cp <= hi) return true;
534
+ }
535
+ return false;
536
+ }
537
+ function readClassRanges(body) {
538
+ if (body.startsWith("^")) return null;
539
+ return parseClassRanges(body);
540
+ }
530
541
  function shortScanner(source, flags) {
531
542
  if (/[imsuvy]/.test(flags)) return null;
532
543
  let ranges = null;
@@ -560,8 +571,8 @@ function regex(pattern, flags = "") {
560
571
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
561
572
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
562
573
  const scan = shortScanner(source, resolvedFlags);
563
- const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
564
- const meta = { firstSet, canMatchNewline, isTrivia: false };
574
+ const { firstSet: firstSet2, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
575
+ const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
565
576
  return {
566
577
  _tag: "regex",
567
578
  _meta: meta,
@@ -589,111 +600,266 @@ function regex(pattern, flags = "") {
589
600
  };
590
601
  }
591
602
 
592
- // src/combinators/regex-analyze.ts
593
- var import_regexp_tree = __toESM(require("regexp-tree"), 1);
594
- function firstSetFromRegex(pattern) {
595
- try {
596
- const ast = import_regexp_tree.default.parse(`/${pattern}/`);
597
- return extractFirstSet(ast.body);
598
- } catch {
599
- return { firstSet: any(), canMatchNewline: true };
600
- }
603
+ // src/regex/first-set.ts
604
+ var ANY = { t: "any" };
605
+ var EMPTY2 = { t: "empty" };
606
+ function charNode(set, nl) {
607
+ return { t: "char", set, nl };
601
608
  }
602
- function canBeEmpty(node2) {
603
- if (!node2) return true;
604
- switch (node2.type) {
605
- case "Repetition": {
606
- const q = node2.quantifier;
607
- return q.kind === "*" || q.kind === "?" || q.kind === "Range" && (q.from ?? 1) === 0;
609
+ function rangesToSet(ranges) {
610
+ let fs = empty();
611
+ for (const [lo, hi] of ranges) fs = union(fs, fromRange(lo, hi));
612
+ return fs;
613
+ }
614
+ function rangesHaveNewline(ranges) {
615
+ return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
616
+ }
617
+ var BAIL = /* @__PURE__ */ Symbol("regex-bail");
618
+ function parseRegex(src) {
619
+ let i = 0;
620
+ const parseAlt = () => {
621
+ const arms = [parseSeq()];
622
+ while (src[i] === "|") {
623
+ i++;
624
+ arms.push(parseSeq());
608
625
  }
609
- case "Alternative": {
610
- const exprs = node2.expressions;
611
- return !exprs || exprs.length === 0 || exprs.every((e) => canBeEmpty(e));
626
+ return arms.length === 1 ? arms[0] : { t: "alt", arms };
627
+ };
628
+ const parseSeq = () => {
629
+ const parts = [];
630
+ while (i < src.length) {
631
+ const ch = src[i];
632
+ if (ch === "|" || ch === ")") break;
633
+ parts.push(parseQuantified());
634
+ }
635
+ if (parts.length === 0) return EMPTY2;
636
+ return parts.length === 1 ? parts[0] : { t: "seq", parts };
637
+ };
638
+ const parseQuantified = () => {
639
+ const atom = parseAtom();
640
+ const ch = src[i];
641
+ let min = null;
642
+ if (ch === "*") {
643
+ min = 0;
644
+ i++;
645
+ } else if (ch === "+") {
646
+ min = 1;
647
+ i++;
648
+ } else if (ch === "?") {
649
+ min = 0;
650
+ i++;
651
+ } else if (ch === "{") {
652
+ min = parseBraceQuantifier();
612
653
  }
613
- case "Group":
614
- return canBeEmpty(node2.expression);
615
- case "Disjunction":
616
- return canBeEmpty(node2.left) || canBeEmpty(node2.right);
617
- default:
618
- return false;
619
- }
620
- }
621
- function extractFirstSet(node2) {
622
- if (!node2) return { firstSet: empty(), canMatchNewline: false };
623
- switch (node2.type) {
624
- case "Disjunction": {
625
- const left = extractFirstSet(node2.left);
626
- const right = extractFirstSet(node2.right);
627
- return {
628
- firstSet: union(left.firstSet, right.firstSet),
629
- canMatchNewline: left.canMatchNewline || right.canMatchNewline
630
- };
654
+ if (min === null) return atom;
655
+ if (src[i] === "?") i++;
656
+ return { t: "rep", node: atom, min };
657
+ };
658
+ const parseBraceQuantifier = () => {
659
+ const close = src.indexOf("}", i);
660
+ if (close === -1) return null;
661
+ const m = /^(\d+)(?:,(\d*))?$/.exec(src.slice(i + 1, close));
662
+ if (!m) return null;
663
+ i = close + 1;
664
+ return Number.parseInt(m[1], 10);
665
+ };
666
+ const parseAtom = () => {
667
+ const ch = src[i];
668
+ switch (ch) {
669
+ case "(":
670
+ return parseGroup();
671
+ case "[":
672
+ return parseClass();
673
+ case ".":
674
+ i++;
675
+ return charNode(any(), false);
676
+ // `.` matches any char except line terminators
677
+ case "\\":
678
+ return parseEscape();
679
+ case "^":
680
+ case "$":
681
+ i++;
682
+ return EMPTY2;
683
+ // anchors are zero-width
684
+ case "{":
685
+ i++;
686
+ return charNode(fromRange(123, 123), false);
687
+ // a `{` with nothing to quantify is a literal brace
688
+ default:
689
+ i += ch.length;
690
+ return charNode(fromRange(ch.codePointAt(0), ch.codePointAt(0)), ch === "\n");
631
691
  }
632
- case "Alternative": {
633
- const exprs = node2.expressions;
634
- if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
635
- let fs = empty();
636
- let canNL = false;
637
- for (const expr of exprs) {
638
- const r = extractFirstSet(expr);
639
- fs = union(fs, r.firstSet);
640
- canNL = canNL || r.canMatchNewline;
641
- if (!canBeEmpty(expr)) break;
642
- }
643
- return { firstSet: fs, canMatchNewline: canNL };
692
+ };
693
+ const parseGroup = () => {
694
+ i++;
695
+ let lookaround = false;
696
+ if (src[i] === "?") {
697
+ const c1 = src[i + 1];
698
+ if (c1 === ":") i += 2;
699
+ else if (c1 === "=" || c1 === "!") {
700
+ i += 2;
701
+ lookaround = true;
702
+ } else if (c1 === "<") {
703
+ const c2 = src[i + 2];
704
+ if (c2 === "=" || c2 === "!") {
705
+ i += 3;
706
+ lookaround = true;
707
+ } else {
708
+ const gt = src.indexOf(">", i);
709
+ if (gt === -1) throw BAIL;
710
+ i = gt + 1;
711
+ }
712
+ } else throw BAIL;
644
713
  }
645
- case "Char": {
646
- const kind = node2.kind;
647
- if (kind === "simple") {
648
- const code = node2.codePoint ?? node2.value.codePointAt(0) ?? 0;
649
- return { firstSet: fromRange(code, code), canMatchNewline: code === 10 };
650
- }
651
- if (kind === "meta") {
652
- const val = node2.value;
653
- if (val === ".") return { firstSet: any(), canMatchNewline: false };
654
- if (val === "d") return { firstSet: fromRange(48, 57), canMatchNewline: false };
655
- if (val === "D") return { firstSet: any(), canMatchNewline: true };
656
- if (val === "w") return {
657
- firstSet: union(
658
- union(fromRange(48, 57), fromRange(65, 90)),
659
- union(fromRange(97, 122), fromRange(95, 95))
660
- ),
661
- canMatchNewline: false
662
- };
663
- if (val === "s") return { firstSet: fromRange(9, 13), canMatchNewline: true };
664
- if (val === "n") return { firstSet: fromRange(10, 10), canMatchNewline: true };
714
+ const inner = parseAlt();
715
+ if (src[i] !== ")") throw BAIL;
716
+ i++;
717
+ return lookaround ? EMPTY2 : inner;
718
+ };
719
+ const parseClass = () => {
720
+ let k = i + 1;
721
+ const negated = src[k] === "^";
722
+ if (negated) k++;
723
+ let body = "";
724
+ while (k < src.length && src[k] !== "]") {
725
+ if (src[k] === "\\") {
726
+ body += src[k] + (src[k + 1] ?? "");
727
+ k += 2;
728
+ } else {
729
+ body += src[k];
730
+ k++;
665
731
  }
666
- return { firstSet: any(), canMatchNewline: true };
667
732
  }
668
- case "CharacterClass": {
669
- const expressions = node2.expressions;
733
+ if (src[k] !== "]") throw BAIL;
734
+ i = k + 1;
735
+ if (negated) return charNode(any(), true);
736
+ const ranges = parseClassRanges(body);
737
+ if (!ranges) return charNode(any(), true);
738
+ return charNode(rangesToSet(ranges), rangesHaveNewline(ranges));
739
+ };
740
+ const parseEscape = () => {
741
+ const e = src[i + 1];
742
+ if (e === void 0) throw BAIL;
743
+ if (e === "u" && /^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) {
744
+ const cp = Number.parseInt(src.slice(i + 2, i + 6), 16);
745
+ i += 6;
746
+ return charNode(fromRange(cp, cp), cp === 10);
747
+ }
748
+ if (e === "x" && /^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) {
749
+ const cp = Number.parseInt(src.slice(i + 2, i + 4), 16);
750
+ i += 4;
751
+ return charNode(fromRange(cp, cp), cp === 10);
752
+ }
753
+ i += 2;
754
+ switch (e) {
755
+ case "d":
756
+ return charNode(rangesToSet(shorthandRanges("d")), false);
757
+ case "w":
758
+ return charNode(rangesToSet(shorthandRanges("w")), false);
759
+ case "s":
760
+ return charNode(rangesToSet(shorthandRanges("s")), true);
761
+ case "D":
762
+ case "W":
763
+ case "S":
764
+ return charNode(any(), true);
765
+ // negated shorthand → widen
766
+ case "b":
767
+ case "B":
768
+ return EMPTY2;
769
+ // word boundary: zero-width
770
+ case "n":
771
+ return charNode(fromRange(10, 10), true);
772
+ case "r":
773
+ return charNode(fromRange(13, 13), false);
774
+ case "t":
775
+ return charNode(fromRange(9, 9), false);
776
+ case "f":
777
+ return charNode(fromRange(12, 12), false);
778
+ case "v":
779
+ return charNode(fromRange(11, 11), false);
780
+ case "0":
781
+ return charNode(fromRange(0, 0), false);
782
+ default:
783
+ if (e >= "1" && e <= "9") return ANY;
784
+ return charNode(fromRange(e.codePointAt(0), e.codePointAt(0)), e === "\n");
785
+ }
786
+ };
787
+ const node2 = parseAlt();
788
+ if (i < src.length) throw BAIL;
789
+ return node2;
790
+ }
791
+ function nullable(n) {
792
+ switch (n.t) {
793
+ case "char":
794
+ return false;
795
+ case "any":
796
+ return true;
797
+ case "empty":
798
+ return true;
799
+ case "seq":
800
+ return n.parts.every(nullable);
801
+ case "alt":
802
+ return n.arms.some(nullable);
803
+ case "rep":
804
+ return n.min === 0 || nullable(n.node);
805
+ }
806
+ }
807
+ function firstSet(n) {
808
+ switch (n.t) {
809
+ case "char":
810
+ return n.set;
811
+ case "any":
812
+ return any();
813
+ case "empty":
814
+ return empty();
815
+ case "rep":
816
+ return firstSet(n.node);
817
+ case "alt": {
670
818
  let fs = empty();
671
- let canNL = false;
672
- for (const expr of expressions) {
673
- if (expr.type === "ClassRange") {
674
- const from = expr.from.codePoint;
675
- const to = expr.to.codePoint;
676
- fs = union(fs, fromRange(from, to));
677
- if (from <= 10 && 10 <= to) canNL = true;
678
- } else if (expr.type === "Char") {
679
- const r = extractFirstSet(expr);
680
- fs = union(fs, r.firstSet);
681
- canNL = canNL || r.canMatchNewline;
682
- }
683
- }
684
- if (node2.negative) return { firstSet: any(), canMatchNewline: true };
685
- return { firstSet: fs, canMatchNewline: canNL };
819
+ for (const arm of n.arms) fs = union(fs, firstSet(arm));
820
+ return fs;
686
821
  }
687
- case "Repetition": {
688
- return extractFirstSet(node2.expression);
822
+ case "seq": {
823
+ let fs = empty();
824
+ for (const part of n.parts) {
825
+ fs = union(fs, firstSet(part));
826
+ if (!nullable(part)) break;
827
+ }
828
+ return fs;
689
829
  }
690
- case "Group": {
691
- return extractFirstSet(node2.expression);
830
+ }
831
+ }
832
+ function firstCanBeNewline(n) {
833
+ switch (n.t) {
834
+ case "char":
835
+ return n.nl;
836
+ case "any":
837
+ return true;
838
+ case "empty":
839
+ return false;
840
+ case "rep":
841
+ return firstCanBeNewline(n.node);
842
+ case "alt":
843
+ return n.arms.some(firstCanBeNewline);
844
+ case "seq": {
845
+ for (const part of n.parts) {
846
+ if (firstCanBeNewline(part)) return true;
847
+ if (!nullable(part)) break;
848
+ }
849
+ return false;
692
850
  }
693
- default:
694
- return { firstSet: any(), canMatchNewline: true };
695
851
  }
696
852
  }
853
+ function firstSetFromRegex(source) {
854
+ let ast;
855
+ try {
856
+ ast = parseRegex(source);
857
+ } catch {
858
+ return { firstSet: any(), canMatchNewline: true };
859
+ }
860
+ if (nullable(ast)) return { firstSet: any(), canMatchNewline: true };
861
+ return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
862
+ }
697
863
 
698
864
  // src/combinators/keywords.ts
699
865
  function word(str, boundary = "_0-9A-Za-z") {
@@ -711,19 +877,19 @@ function keywords(words, opts = {}) {
711
877
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
712
878
  const flags = opts.caseInsensitive ? "iuy" : "uy";
713
879
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
714
- let firstSet = empty();
880
+ let firstSet2 = empty();
715
881
  for (const w of sorted) {
716
882
  if (w.length === 0) continue;
717
883
  const cp = w.codePointAt(0);
718
- firstSet = union(firstSet, fromChar(cp));
884
+ firstSet2 = union(firstSet2, fromChar(cp));
719
885
  if (opts.caseInsensitive) {
720
886
  const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
721
887
  const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
722
- if (u !== void 0) firstSet = union(firstSet, fromChar(u));
723
- if (l !== void 0) firstSet = union(firstSet, fromChar(l));
888
+ if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
889
+ if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
724
890
  }
725
891
  }
726
- const meta = { firstSet, canMatchNewline: false, isTrivia: false };
892
+ const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
727
893
  return {
728
894
  _tag: "keywords",
729
895
  _meta: meta,
@@ -1117,10 +1283,10 @@ function needsDeferredTriviaCommit(ctx) {
1117
1283
  }
1118
1284
  function saveTriviaMark(ctx) {
1119
1285
  const m = saveCstMark(ctx);
1120
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1286
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1121
1287
  }
1122
1288
  function rollbackTrivia(ctx, mark) {
1123
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1289
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1124
1290
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1125
1291
  }
1126
1292
  function scanWithLabels(input, cur, ctx) {
@@ -1494,6 +1660,13 @@ function ref() {
1494
1660
  define(p) {
1495
1661
  if (resolved) throw new Error("ref<T>() already defined");
1496
1662
  resolved = p;
1663
+ meta.firstSet = p._meta.firstSet;
1664
+ meta.canMatchNewline = p._meta.canMatchNewline;
1665
+ meta.isTrivia = p._meta.isTrivia;
1666
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
1667
+ else delete meta.triviaKindLabels;
1668
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
1669
+ else delete meta.disjoint;
1497
1670
  }
1498
1671
  };
1499
1672
  return slot;
@@ -1534,6 +1707,9 @@ function markUnusedValues(root) {
1534
1707
  case "transform":
1535
1708
  visit(def.parser, true);
1536
1709
  return;
1710
+ case "field":
1711
+ visit(def.parser, true);
1712
+ return;
1537
1713
  case "skip":
1538
1714
  visit(def.main, consumed);
1539
1715
  visit(def.skipped, false);
@@ -1585,6 +1761,11 @@ function markUnusedValues(root) {
1585
1761
  }
1586
1762
 
1587
1763
  // src/combinators/parser.ts
1764
+ function tagRule(r, key) {
1765
+ ;
1766
+ r._ruleName = key;
1767
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1768
+ }
1588
1769
  function rules(factory) {
1589
1770
  const cache = {};
1590
1771
  const proxy = new Proxy(cache, {
@@ -1593,7 +1774,7 @@ function rules(factory) {
1593
1774
  const record = target;
1594
1775
  if (!(key in record)) {
1595
1776
  const r = ref();
1596
- r._ruleName = key;
1777
+ tagRule(r, key);
1597
1778
  record[key] = r;
1598
1779
  }
1599
1780
  return record[key];
@@ -1604,12 +1785,12 @@ function rules(factory) {
1604
1785
  const placeholder = cache[key];
1605
1786
  const parser2 = definitions[key];
1606
1787
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1607
- ;
1788
+ tagRule(parser2, key);
1608
1789
  placeholder.define(parser2);
1609
1790
  placeholder._meta.firstSet = parser2._meta.firstSet;
1610
1791
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1611
1792
  } else {
1612
- ;
1793
+ tagRule(parser2, key);
1613
1794
  cache[key] = parser2;
1614
1795
  }
1615
1796
  }
@@ -1666,44 +1847,130 @@ function buildReadsTrivia(def) {
1666
1847
  const src = def.buildSrc ?? def.build.toString();
1667
1848
  const arity = confirmedBuildArity(src);
1668
1849
  if (arity === null) return true;
1669
- return arity >= 4;
1850
+ return arity >= 5;
1670
1851
  }
1671
1852
  function buildReadsState(def) {
1672
1853
  if (!def.build) return true;
1673
1854
  const src = def.buildSrc ?? def.build.toString();
1674
1855
  const arity = confirmedBuildArity(src);
1675
1856
  if (arity === null) return true;
1676
- return arity >= 5;
1857
+ return arity >= 6;
1858
+ }
1859
+
1860
+ // src/compiler/fields.ts
1861
+ function buildReadsFields(def) {
1862
+ if (!def.build) return true;
1863
+ const src = def.buildSrc ?? def.build.toString();
1864
+ const arity = confirmedBuildArity(src);
1865
+ if (arity === null) return true;
1866
+ return arity >= 2;
1867
+ }
1868
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1869
+ if (seen.has(p)) return false;
1870
+ seen.add(p);
1871
+ const d = p._def;
1872
+ switch (d.tag) {
1873
+ case "field":
1874
+ return true;
1875
+ case "node":
1876
+ return false;
1877
+ case "lazy": {
1878
+ try {
1879
+ return parserHasOwnFields(d.thunk(), seen);
1880
+ } catch {
1881
+ return false;
1882
+ }
1883
+ }
1884
+ case "sequence":
1885
+ case "choice":
1886
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1887
+ case "sepBy":
1888
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1889
+ case "skip":
1890
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1891
+ case "grammar":
1892
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1893
+ case "scanTo":
1894
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1895
+ case "recover":
1896
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1897
+ case "many":
1898
+ case "oneOrMore":
1899
+ case "optional":
1900
+ case "transform":
1901
+ case "trivia":
1902
+ case "token":
1903
+ case "label":
1904
+ case "expect":
1905
+ case "withCtx":
1906
+ case "not":
1907
+ return parserHasOwnFields(d.parser, seen);
1908
+ default:
1909
+ return false;
1910
+ }
1911
+ }
1912
+ function buildFieldMap(captures) {
1913
+ if (!captures?.length) return void 0;
1914
+ const out = {};
1915
+ for (const cap of captures) {
1916
+ const entry = { value: cap.value, span: cap.span };
1917
+ const current = out[cap.name];
1918
+ if (current === void 0) out[cap.name] = entry;
1919
+ else if (Array.isArray(current)) current.push(entry);
1920
+ else out[cap.name] = [current, entry];
1921
+ }
1922
+ return out;
1677
1923
  }
1678
1924
 
1679
1925
  // src/combinators/node.ts
1680
- function collapseChild(child) {
1926
+ function unwrapChild(child) {
1681
1927
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1682
1928
  }
1683
- function node(type, combinator, build, opts) {
1929
+ function isCstChild(value) {
1930
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1931
+ }
1932
+ function missingInferredType() {
1933
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1934
+ }
1935
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1936
+ const hasExplicitType = typeof typeOrCombinator === "string";
1937
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1938
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1939
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1940
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1941
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1684
1942
  const meta = {
1685
1943
  firstSet: combinator._meta.firstSet,
1686
1944
  canMatchNewline: combinator._meta.canMatchNewline,
1687
1945
  isTrivia: false
1688
1946
  };
1947
+ const unwrap = opts?.unwrap === true;
1689
1948
  const collapse = opts?.collapse === true;
1690
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1949
+ if (unwrap && collapse) {
1950
+ throw new Error("node() options cannot set both unwrap and collapse");
1951
+ }
1952
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1691
1953
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1692
1954
  const clonesState = build ? buildReadsState(def) : true;
1955
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1693
1956
  return {
1694
1957
  _tag: "node",
1695
1958
  _meta: meta,
1696
1959
  _def: def,
1697
1960
  parse(input, pos, ctx) {
1698
1961
  const saved = beginCstNodeCapture(ctx);
1962
+ const savedFields = ctx._fields;
1963
+ ctx._fields = capturesFields ? [] : void 0;
1699
1964
  if (!capturesTrivia) ctx.captureTrivia = false;
1700
1965
  const r = combinator.parse(input, pos, ctx);
1966
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1967
+ ctx._fields = savedFields;
1701
1968
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1702
1969
  if (!r.ok) return r;
1703
1970
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1704
- const built = collapse && children.length === 1 ? collapseChild(children[0]) : build ? build(children, rawChildren, r.span, triviaLog, st) : ctx.build ? ctx.build(type, children, rawChildren, r.span, triviaLog, st) : { _tag: "node", type, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children };
1705
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1706
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1971
+ const nodeType = def.type ?? missingInferredType();
1972
+ const built = unwrap && children.length === 1 ? unwrapChild(children[0]) : collapse && children.length === 1 ? children[0] : !build && ctx.build?._parsemanCstCollapse && children.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children[0], children, rawChildren) ? children[0] : build ? build(children, fields, r.span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children, fields, r.span, rawChildren, triviaLog, st) : { _tag: "node", type: nodeType, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children };
1973
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1707
1974
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1708
1975
  pushCstChild(ctx, built, rawEntry);
1709
1976
  }
@@ -1764,6 +2031,19 @@ function label(name, combinator) {
1764
2031
  }
1765
2032
  };
1766
2033
  }
2034
+ function field(name, combinator) {
2035
+ return {
2036
+ _tag: combinator._tag,
2037
+ _meta: combinator._meta,
2038
+ _def: { tag: "field", name, parser: combinator },
2039
+ parse(input, pos, ctx) {
2040
+ const result = combinator.parse(input, pos, ctx);
2041
+ if (!result.ok) return result;
2042
+ ctx._fields?.push({ name, value: result.value, span: result.span });
2043
+ return result;
2044
+ }
2045
+ };
2046
+ }
1767
2047
 
1768
2048
  // src/compiler/line-index.ts
1769
2049
  function buildLineIndex(input) {
@@ -1973,77 +2253,11 @@ function expect(combinator, label2) {
1973
2253
 
1974
2254
  // src/compiler/scannable-run.ts
1975
2255
  var BACKSLASH = 92;
1976
- var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1977
2256
  var META = new Set("()[]{}*+?|^$.".split(""));
1978
- var SPACE_RANGES2 = [
1979
- [9, 13],
1980
- [32, 32],
1981
- [160, 160],
1982
- [5760, 5760],
1983
- [8192, 8202],
1984
- [8232, 8232],
1985
- [8233, 8233],
1986
- [8239, 8239],
1987
- [8287, 8287],
1988
- [12288, 12288],
1989
- [65279, 65279]
1990
- ];
1991
- function shorthandRanges2(ch) {
1992
- if (ch === "d") return [[48, 57]];
1993
- if (ch === "s") return SPACE_RANGES2;
1994
- return [[48, 57], [65, 90], [97, 122], [95, 95]];
1995
- }
1996
- function readUnicodeEscape(body, i) {
1997
- if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1998
- const hex = body.slice(i + 2, i + 6);
1999
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
2000
- return { cp: Number.parseInt(hex, 16), next: i + 6 };
2001
- }
2002
- function parseClassRanges(body) {
2003
- const ranges = [];
2004
- let i = 0;
2005
- const readAtom = () => {
2006
- const ch = body[i];
2007
- if (ch === void 0) return null;
2008
- if (ch === "\\") {
2009
- const uni = readUnicodeEscape(body, i);
2010
- if (uni) {
2011
- i = uni.next;
2012
- return { cp: uni.cp };
2013
- }
2014
- const e = body[i + 1];
2015
- if (e === void 0) return null;
2016
- i += 2;
2017
- if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
2018
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
2019
- if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
2020
- return { cp: e.codePointAt(0) };
2021
- }
2022
- i += ch.length;
2023
- return { cp: ch.codePointAt(0) };
2024
- };
2025
- while (i < body.length) {
2026
- const lo = readAtom();
2027
- if (lo === null) return null;
2028
- if ("set" in lo) {
2029
- ranges.push(...lo.set);
2030
- continue;
2031
- }
2032
- if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
2033
- i += 1;
2034
- const hi = readAtom();
2035
- if (hi === null || "set" in hi) return null;
2036
- ranges.push([lo.cp, hi.cp]);
2037
- } else {
2038
- ranges.push([lo.cp, lo.cp]);
2039
- }
2040
- }
2041
- return ranges.length ? ranges : null;
2042
- }
2043
2257
  function classToRanges(cls) {
2044
- if (cls === "\\d") return shorthandRanges2("d");
2045
- if (cls === "\\w") return shorthandRanges2("w");
2046
- if (cls === "\\s") return shorthandRanges2("s");
2258
+ if (cls === "\\d") return shorthandRanges("d");
2259
+ if (cls === "\\w") return shorthandRanges("w");
2260
+ if (cls === "\\s") return shorthandRanges("s");
2047
2261
  const body = cls.slice(1, -1);
2048
2262
  if (body.startsWith("^")) return null;
2049
2263
  return parseClassRanges(body);
@@ -2061,7 +2275,7 @@ function literalCodePoints(frag) {
2061
2275
  if (ch === "\\") {
2062
2276
  const e = frag[i + 1];
2063
2277
  if (e === void 0) return null;
2064
- out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
2278
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
2065
2279
  i += 2;
2066
2280
  continue;
2067
2281
  }
@@ -2154,7 +2368,7 @@ function parseSeqParts(source) {
2154
2368
  if (e === "d" || e === "w" || e === "s") {
2155
2369
  flush();
2156
2370
  i += 2;
2157
- i += runFrom(shorthandRanges2(e), false, source[i]);
2371
+ i += runFrom(shorthandRanges(e), false, source[i]);
2158
2372
  continue;
2159
2373
  }
2160
2374
  const uni = readUnicodeEscape(source, i);
@@ -2163,8 +2377,8 @@ function parseSeqParts(source) {
2163
2377
  cp2 = uni.cp;
2164
2378
  i = uni.next;
2165
2379
  } else if (e === void 0) return null;
2166
- else if (e in CLASS_ESCAPES2) {
2167
- cp2 = CLASS_ESCAPES2[e];
2380
+ else if (e in CLASS_ESCAPES) {
2381
+ cp2 = CLASS_ESCAPES[e];
2168
2382
  i += 2;
2169
2383
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
2170
2384
  else {
@@ -2271,7 +2485,7 @@ function rangesSubset(sub, sup) {
2271
2485
  }
2272
2486
  function parseClassOperand(body) {
2273
2487
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2274
- return { ranges: shorthandRanges2(body[1]), negated: false };
2488
+ return { ranges: shorthandRanges(body[1]), negated: false };
2275
2489
  }
2276
2490
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2277
2491
  let inner = body.slice(1, -1);
@@ -2704,7 +2918,7 @@ function parseScanShapeCore(source) {
2704
2918
  }
2705
2919
  m = /^\\([dws])([+*])$/.exec(source);
2706
2920
  if (m) {
2707
- return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2921
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2708
2922
  }
2709
2923
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2710
2924
  if (m) {
@@ -3246,21 +3460,22 @@ function emitScannableTerminal(shape, c) {
3246
3460
  }
3247
3461
 
3248
3462
  // src/compiler/inline-build.ts
3249
- 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*$/;
3463
+ var MK_BUILD_RE = /^\(\s*(\w+)\s*,\s*\w+\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)\s*=>\s*mk\s*\(\s*(['"])([^'"]+)\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$/;
3250
3464
  function escapeRegExp(s) {
3251
3465
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3252
3466
  }
3253
3467
  function looseMkBuildRe(type) {
3254
3468
  return new RegExp(
3255
- String.raw`^\(\s*(?:\w+\s*,\s*){3}\w+\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\1\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*,\s*\w+\s*\)\s*$`
3469
+ String.raw`^\(\s*(\w+)\s*,\s*\w+\s*,\s*(\w+)\s*,\s*(\w+)\s*,\s*(\w+)\s*\)\s*=>\s*.+\(\s*(['"])${escapeRegExp(type)}\5\s*,\s*\1\s*,\s*\3\s*,\s*\2\s*,\s*\4\s*\)\s*$`
3256
3470
  );
3257
3471
  }
3258
3472
  function analyzeMkInlineBuild(def) {
3259
3473
  if (!def.build) return null;
3474
+ if (def.type === void 0) return null;
3260
3475
  const src = (def.buildSrc ?? def.build.toString()).trim();
3261
3476
  const strict = src.match(MK_BUILD_RE);
3262
3477
  if (strict) {
3263
- const mkType = strict[2];
3478
+ const mkType = strict[6];
3264
3479
  return mkType === def.type ? def.type : null;
3265
3480
  }
3266
3481
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3587,8 +3802,9 @@ function planDisjointDispatch(parsers) {
3587
3802
  }
3588
3803
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3589
3804
  }
3590
- function captureRestoreBody(mL, mR, mTl, mLg) {
3591
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3805
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3806
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3807
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3592
3808
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3593
3809
  }
3594
3810
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3636,6 +3852,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3636
3852
  // Delegating wrappers: defer to the wrapped parser.
3637
3853
  case "transform":
3638
3854
  case "label":
3855
+ case "field":
3639
3856
  case "expect":
3640
3857
  case "withCtx":
3641
3858
  case "grammar":
@@ -3682,6 +3899,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3682
3899
  case "optional":
3683
3900
  case "transform":
3684
3901
  case "label":
3902
+ case "field":
3685
3903
  case "expect":
3686
3904
  case "withCtx":
3687
3905
  case "grammar":
@@ -3719,22 +3937,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3719
3937
  ctx.recordFail = savedRecord;
3720
3938
  const ind0 = ind(ctx);
3721
3939
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3940
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3722
3941
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3723
3942
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3724
3943
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3944
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3725
3945
  const stmts = [
3726
3946
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3727
3947
  ...mL ? [
3728
3948
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3729
3949
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3730
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3950
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3951
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3731
3952
  ] : [],
3732
3953
  `${ind0}${lbl}: {`,
3733
3954
  ...r.stmts,
3734
3955
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3735
3956
  `${ind0}}`,
3736
3957
  ...mL ? [
3737
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3958
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3738
3959
  ] : []
3739
3960
  ];
3740
3961
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3961,6 +4182,7 @@ function failsAtStart(p) {
3961
4182
  return true;
3962
4183
  case "transform":
3963
4184
  case "label":
4185
+ case "field":
3964
4186
  return failsAtStart(d.parser);
3965
4187
  default:
3966
4188
  return false;
@@ -3980,6 +4202,7 @@ function canMatchEmptyAtStart(p) {
3980
4202
  return true;
3981
4203
  case "transform":
3982
4204
  case "label":
4205
+ case "field":
3983
4206
  return canMatchEmptyAtStart(d.parser);
3984
4207
  case "literal":
3985
4208
  return d.value.length === 0;
@@ -4162,11 +4385,13 @@ function emitFirstMatch(def, ctx, pos) {
4162
4385
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
4163
4386
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
4164
4387
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4388
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
4165
4389
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
4166
4390
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
4167
4391
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
4168
4392
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
4169
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4393
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4394
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
4170
4395
  const failSlot = atStart ? staticFx : "_ctx._fx";
4171
4396
  stmts.push(`${ind0}if (${skipCond}) {`);
4172
4397
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -4176,7 +4401,8 @@ function emitFirstMatch(def, ctx, pos) {
4176
4401
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
4177
4402
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
4178
4403
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
4179
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4404
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4405
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
4180
4406
  );
4181
4407
  }
4182
4408
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4569,6 +4795,9 @@ function emitToken(def, ctx, pos) {
4569
4795
  };
4570
4796
  }
4571
4797
  function emitNode(def, ctx, pos) {
4798
+ if (def.type === void 0) {
4799
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4800
+ }
4572
4801
  const structural = def.build === void 0;
4573
4802
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4574
4803
  let buildIdx = null;
@@ -4580,25 +4809,36 @@ function emitNode(def, ctx, pos) {
4580
4809
  const i = ind(ctx);
4581
4810
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4582
4811
  const clonesState = !structural && buildReadsState(def);
4812
+ const hasFields = parserHasOwnFields(def.parser);
4813
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4583
4814
  const chV = v(ctx, "_ch");
4584
4815
  const rawV = v(ctx, "_raw");
4585
4816
  const capTLv = structural ? v(ctx, "_ctl") : null;
4586
4817
  const capSTv = structural ? v(ctx, "_cst") : null;
4818
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4587
4819
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4588
4820
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4589
4821
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4590
4822
  if (structural) ctx.needsHostReads = true;
4591
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4823
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4824
+ const allocStmt = structural ? `${i}const ${capTLv} = ${hostTriviaGate}, ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6))${capFv ? `, ${capFv} = _ctx.build !== undefined && _hostReads(_ctx.build, 2)` : ""}
4592
4825
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4593
4826
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4827
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4828
+ const sf = hasFields ? v(ctx, "_sf") : null;
4829
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4830
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4594
4831
  const stmts = [
4595
4832
  allocStmt,
4596
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4597
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4833
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4834
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4598
4835
  ];
4599
4836
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4600
4837
  stmts.push(...innerStmts);
4601
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4838
+ if (sf && fArr) {
4839
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4840
+ }
4841
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4602
4842
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4603
4843
  let stV = "undefined";
4604
4844
  if (structural) {
@@ -4608,14 +4848,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4608
4848
  stV = v(ctx, "_nst");
4609
4849
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4610
4850
  }
4851
+ if (sf && fArr) {
4852
+ const fe = v(ctx, "_fe");
4853
+ const cur = v(ctx, "_fc");
4854
+ stmts.push(
4855
+ `${i}let ${fObj} = undefined`,
4856
+ `${i}if (${fArr} && ${fArr}.length) {`,
4857
+ `${i} ${fObj} = {}`,
4858
+ `${i} for (const ${fe} of ${fArr}) {`,
4859
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4860
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4861
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4862
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4863
+ `${i} }`,
4864
+ `${i}}`
4865
+ );
4866
+ }
4611
4867
  const ndV = v(ctx, "_nd");
4612
- const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV})`;
4613
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4614
- const finalExpr = def.collapse ? `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})` : ndExpr;
4868
+ const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: { start: ${pos}, end: ${endVar} }, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, pos, endVar, tlV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV})`;
4869
+ const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${fObj}, { start: ${pos}, end: ${endVar} }, ${rawV}, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4870
+ const hostCollapseExpr = ctx.ns || structural ? `_ctx.build !== undefined && _ctx.build._parsemanCstCollapse !== undefined && ${chV}.length === 1 && ${rawV}.length === 1 && _ctx.build._parsemanCstCollapse(${JSON.stringify(def.type)}, ${chV}[0], ${chV}, ${rawV}) ? ${chV}[0] : (${ndExpr})` : ndExpr;
4871
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4872
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4873
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4615
4874
  stmts.push(
4616
4875
  `${i}const ${ndV} = ${finalExpr}`,
4617
4876
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4618
- `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && ${ndV}._tag === 'node') ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
4877
+ `${i}if (${sr}) ${sr}.push((typeof ${ndV} === 'object' && ${ndV} !== null && (${ndV}._tag === 'node' || ${ndV}._tag === 'leaf' || ${ndV}._tag === 'error')) ? ${ndV} : { _tag: 'leaf', value: typeof ${ndV} === 'string' ? ${ndV} : '', span: { start: ${pos}, end: ${endVar} } })`
4619
4878
  );
4620
4879
  return { stmts, valueVar: ndV, endVar };
4621
4880
  }
@@ -4830,6 +5089,17 @@ function emitDispatch(p, ctx, pos) {
4830
5089
  endVar: inner.endVar
4831
5090
  };
4832
5091
  }
5092
+ case "field": {
5093
+ const inner = emit(def.parser, ctx, pos);
5094
+ return {
5095
+ stmts: [
5096
+ ...inner.stmts,
5097
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
5098
+ ],
5099
+ valueVar: inner.valueVar,
5100
+ endVar: inner.endVar
5101
+ };
5102
+ }
4833
5103
  case "grammar": {
4834
5104
  const savedTrivia = ctx.activeTrivia;
4835
5105
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4931,6 +5201,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4931
5201
  case "trivia":
4932
5202
  case "token":
4933
5203
  case "label":
5204
+ case "field":
4934
5205
  case "optional":
4935
5206
  case "many":
4936
5207
  case "oneOrMore":
@@ -4967,6 +5238,7 @@ function childrenOf(def) {
4967
5238
  case "trivia":
4968
5239
  case "token":
4969
5240
  case "label":
5241
+ case "field":
4970
5242
  case "grammar":
4971
5243
  case "not":
4972
5244
  case "node":
@@ -5208,6 +5480,11 @@ function assertRuleName(name) {
5208
5480
  );
5209
5481
  }
5210
5482
  }
5483
+ function publicRuleWrapperSource(rule, fnSource) {
5484
+ const labels = rule._meta.triviaKindLabels;
5485
+ if (labels === void 0) return fnSource;
5486
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5487
+ }
5211
5488
  function compileLinkable(ruleMapArg, ns) {
5212
5489
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
5213
5490
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
@@ -5310,7 +5587,7 @@ function compileLinkable(ruleMapArg, ns) {
5310
5587
  ctx.recordFail = savedRec;
5311
5588
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5312
5589
  }
5313
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5590
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5314
5591
  }
5315
5592
  if (ctx.runtimeParsers.length !== 0) return null;
5316
5593
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5333,14 +5610,14 @@ function compileLinkable(ruleMapArg, ns) {
5333
5610
  ...privateFns
5334
5611
  ].filter(Boolean);
5335
5612
  const wrappers = /* @__PURE__ */ new Map();
5336
- for (const { key, r } of perEntry) {
5337
- wrappers.set(key, [
5613
+ for (const { key, rule, r } of perEntry) {
5614
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5338
5615
  `function(input, _pos, _ctx) {`,
5339
5616
  ` let pos = _pos`,
5340
5617
  ...r.stmts,
5341
5618
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5342
5619
  `}`
5343
- ].join("\n"));
5620
+ ].join("\n")));
5344
5621
  }
5345
5622
  const firstSets = /* @__PURE__ */ new Map();
5346
5623
  for (const [key, rule] of ruleMap) {
@@ -5543,6 +5820,7 @@ function evalRuleMapIR(ir) {
5543
5820
  "skip",
5544
5821
  "trivia",
5545
5822
  "label",
5823
+ "field",
5546
5824
  "expect",
5547
5825
  "_tf",
5548
5826
  "_nd",
@@ -5569,6 +5847,7 @@ function evalRuleMapIR(ir) {
5569
5847
  skip,
5570
5848
  trivia,
5571
5849
  label,
5850
+ field,
5572
5851
  expect,
5573
5852
  _tf,
5574
5853
  _nd
@@ -5583,9 +5862,26 @@ function linkable(rulesMap, ns) {
5583
5862
  if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
5584
5863
  return pieces;
5585
5864
  }
5586
- function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
5865
+ function normalizeCstCollapse(collapse) {
5866
+ if (collapse === true) return () => true;
5867
+ if (Array.isArray(collapse)) {
5868
+ const types = new Set(collapse);
5869
+ return (type) => types.has(type);
5870
+ }
5871
+ return typeof collapse === "function" ? collapse : void 0;
5872
+ }
5873
+ function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
5587
5874
  return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
5588
5875
  }
5876
+ function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
5877
+ if (typeof typeOrOptions === "string") {
5878
+ return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
5879
+ }
5880
+ const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
5881
+ const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
5882
+ if (collapse) host._parsemanCstCollapse = collapse;
5883
+ return host;
5884
+ }
5589
5885
  function pick(grammar, names) {
5590
5886
  const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
5591
5887
  const keep = /* @__PURE__ */ new Set();
@@ -5617,8 +5913,9 @@ function fusedBody(pieces) {
5617
5913
  }
5618
5914
  }
5619
5915
  const contributing = new Set(winner.values());
5620
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5621
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5916
+ const contributingPieces = [...contributing];
5917
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
5918
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5622
5919
  const lines = [
5623
5920
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5624
5921
  "const _pfFail = {}",
@@ -5626,7 +5923,7 @@ function fusedBody(pieces) {
5626
5923
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5627
5924
  ...needsHostReads ? [HOST_READS_DECL] : [],
5628
5925
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5629
- ...[...contributing].flatMap((p) => p.prelude),
5926
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5630
5927
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5631
5928
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5632
5929
  ];
@@ -5649,7 +5946,7 @@ function fusedBody(pieces) {
5649
5946
  }
5650
5947
  );
5651
5948
  const env = {};
5652
- for (const p of contributing) {
5949
+ for (const p of contributingPieces) {
5653
5950
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5654
5951
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5655
5952
  }
@@ -5669,14 +5966,23 @@ function materializePiece(p) {
5669
5966
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5670
5967
  return pieces;
5671
5968
  }
5672
- function itemPieces(item) {
5969
+ function nextComposeNs(used) {
5970
+ let ns;
5971
+ do {
5972
+ ns = `_lk${_nsCounter++}_`;
5973
+ } while (used.has(ns));
5974
+ used.add(ns);
5975
+ return ns;
5976
+ }
5977
+ function itemPieces(item, used) {
5673
5978
  const carried = item[COMPOSED_PIECES];
5674
- if (Array.isArray(carried)) return carried.map(materializePiece);
5675
- if (item.ruleFns instanceof Map) return [item];
5676
- return [linkable(item)];
5979
+ const pieces = Array.isArray(carried) ? carried.map(materializePiece) : item.ruleFns instanceof Map ? [item] : [linkable(item, nextComposeNs(used))];
5980
+ for (const p of pieces) used.add(p.ns);
5981
+ return pieces;
5677
5982
  }
5678
5983
  function compose(items) {
5679
- const pieces = items.flatMap(itemPieces);
5984
+ const used = /* @__PURE__ */ new Set();
5985
+ const pieces = items.flatMap((item) => itemPieces(item, used));
5680
5986
  const map = fuseRules(pieces);
5681
5987
  Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
5682
5988
  return map;