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.js CHANGED
@@ -252,7 +252,8 @@ function saveCstMark(ctx) {
252
252
  return {
253
253
  raw: cstRawLen(ctx),
254
254
  tlog: cstTlLen(ctx),
255
- leaves: cstLeavesLen(ctx)
255
+ leaves: cstLeavesLen(ctx),
256
+ fields: ctx._fields?.length ?? 0
256
257
  };
257
258
  }
258
259
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -276,11 +277,13 @@ function rollbackCstCapture(ctx, mark) {
276
277
  if (mark.tlog === 0) b.tl = void 0;
277
278
  else b.tl.length = mark.tlog;
278
279
  }
280
+ if (ctx._fields) ctx._fields.length = mark.fields;
279
281
  return;
280
282
  }
281
283
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
282
284
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
283
285
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
286
+ if (ctx._fields) ctx._fields.length = mark.fields;
284
287
  }
285
288
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
286
289
  const insertIdx = cstRawLen(ctx);
@@ -324,9 +327,9 @@ function asciiFoldEq(a, b) {
324
327
  }
325
328
  function literal(value, opts = {}) {
326
329
  const caseInsensitive = opts.caseInsensitive ?? false;
327
- const firstSet = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
330
+ const firstSet2 = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
328
331
  const meta = {
329
- firstSet,
332
+ firstSet: firstSet2,
330
333
  canMatchNewline: value.includes("\n"),
331
334
  isTrivia: false
332
335
  };
@@ -338,7 +341,7 @@ function literal(value, opts = {}) {
338
341
  meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
339
342
  { lo: firstLower, hi: firstLower },
340
343
  { lo: firstUpper, hi: firstUpper }
341
- ] } : firstSet;
344
+ ] } : firstSet2;
342
345
  }
343
346
  return {
344
347
  _tag: "literal",
@@ -361,68 +364,66 @@ function literal(value, opts = {}) {
361
364
  };
362
365
  }
363
366
 
364
- // src/combinators/regex.ts
365
- var firstSetAnalyzer = null;
366
- function registerRegexAnalyzer(analyzer) {
367
- firstSetAnalyzer = analyzer;
368
- }
369
- var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
370
- var SCAN_BAIL_AT = 64;
367
+ // src/regex/classes.ts
368
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
371
369
  var SPACE_RANGES = [
372
370
  [9, 13],
373
371
  [32, 32],
374
372
  [160, 160],
375
373
  [5760, 5760],
376
374
  [8192, 8202],
377
- [8232, 8233],
375
+ [8232, 8232],
376
+ [8233, 8233],
378
377
  [8239, 8239],
379
378
  [8287, 8287],
380
379
  [12288, 12288],
381
380
  [65279, 65279]
382
381
  ];
383
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
384
382
  function shorthandRanges(ch) {
385
383
  if (ch === "d") return [[48, 57]];
386
384
  if (ch === "s") return SPACE_RANGES;
387
385
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
388
386
  }
389
- function inRanges(cp, ranges) {
390
- for (let i = 0; i < ranges.length; i++) {
391
- const [lo, hi] = ranges[i];
392
- if (cp >= lo && cp <= hi) return true;
393
- }
394
- return false;
387
+ function readUnicodeEscape(body, i) {
388
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
389
+ const hex = body.slice(i + 2, i + 6);
390
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
391
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
395
392
  }
396
- function readClassRanges(body) {
397
- if (body.startsWith("^")) return null;
393
+ function parseClassRanges(body) {
398
394
  const ranges = [];
399
395
  let i = 0;
400
396
  const readAtom = () => {
401
397
  const ch = body[i];
402
398
  if (ch === void 0) return null;
403
399
  if (ch === "\\") {
404
- const esc = body[i + 1];
405
- if (esc === void 0) return null;
400
+ const uni = readUnicodeEscape(body, i);
401
+ if (uni) {
402
+ i = uni.next;
403
+ return { cp: uni.cp };
404
+ }
405
+ const e = body[i + 1];
406
+ if (e === void 0) return null;
406
407
  i += 2;
407
- if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
408
- if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
409
- if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
410
- return { cp: esc.charCodeAt(0) };
408
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
409
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
410
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
411
+ return { cp: e.codePointAt(0) };
411
412
  }
412
- i++;
413
- return { cp: ch.charCodeAt(0) };
413
+ i += ch.length;
414
+ return { cp: ch.codePointAt(0) };
414
415
  };
415
416
  while (i < body.length) {
416
417
  const lo = readAtom();
417
- if (!lo) return null;
418
- if ("ranges" in lo) {
419
- ranges.push(...lo.ranges);
418
+ if (lo === null) return null;
419
+ if ("set" in lo) {
420
+ ranges.push(...lo.set);
420
421
  continue;
421
422
  }
422
423
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
423
- i++;
424
+ i += 1;
424
425
  const hi = readAtom();
425
- if (!hi || "ranges" in hi) return null;
426
+ if (hi === null || "set" in hi) return null;
426
427
  ranges.push([lo.cp, hi.cp]);
427
428
  } else {
428
429
  ranges.push([lo.cp, lo.cp]);
@@ -430,6 +431,25 @@ function readClassRanges(body) {
430
431
  }
431
432
  return ranges.length ? ranges : null;
432
433
  }
434
+
435
+ // src/combinators/regex.ts
436
+ var firstSetAnalyzer = null;
437
+ function registerRegexAnalyzer(analyzer) {
438
+ firstSetAnalyzer = analyzer;
439
+ }
440
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
441
+ var SCAN_BAIL_AT = 64;
442
+ function inRanges(cp, ranges) {
443
+ for (let i = 0; i < ranges.length; i++) {
444
+ const [lo, hi] = ranges[i];
445
+ if (cp >= lo && cp <= hi) return true;
446
+ }
447
+ return false;
448
+ }
449
+ function readClassRanges(body) {
450
+ if (body.startsWith("^")) return null;
451
+ return parseClassRanges(body);
452
+ }
433
453
  function shortScanner(source, flags) {
434
454
  if (/[imsuvy]/.test(flags)) return null;
435
455
  let ranges = null;
@@ -463,8 +483,8 @@ function regex(pattern, flags = "") {
463
483
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
464
484
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
465
485
  const scan = shortScanner(source, resolvedFlags);
466
- const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
467
- const meta = { firstSet, canMatchNewline, isTrivia: false };
486
+ const { firstSet: firstSet2, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
487
+ const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
468
488
  return {
469
489
  _tag: "regex",
470
490
  _meta: meta,
@@ -492,111 +512,266 @@ function regex(pattern, flags = "") {
492
512
  };
493
513
  }
494
514
 
495
- // src/combinators/regex-analyze.ts
496
- import regexpTree from "regexp-tree";
497
- function firstSetFromRegex(pattern) {
498
- try {
499
- const ast = regexpTree.parse(`/${pattern}/`);
500
- return extractFirstSet(ast.body);
501
- } catch {
502
- return { firstSet: any(), canMatchNewline: true };
503
- }
515
+ // src/regex/first-set.ts
516
+ var ANY = { t: "any" };
517
+ var EMPTY2 = { t: "empty" };
518
+ function charNode(set, nl) {
519
+ return { t: "char", set, nl };
504
520
  }
505
- function canBeEmpty(node2) {
506
- if (!node2) return true;
507
- switch (node2.type) {
508
- case "Repetition": {
509
- const q = node2.quantifier;
510
- return q.kind === "*" || q.kind === "?" || q.kind === "Range" && (q.from ?? 1) === 0;
521
+ function rangesToSet(ranges) {
522
+ let fs = empty();
523
+ for (const [lo, hi] of ranges) fs = union(fs, fromRange(lo, hi));
524
+ return fs;
525
+ }
526
+ function rangesHaveNewline(ranges) {
527
+ return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
528
+ }
529
+ var BAIL = /* @__PURE__ */ Symbol("regex-bail");
530
+ function parseRegex(src) {
531
+ let i = 0;
532
+ const parseAlt = () => {
533
+ const arms = [parseSeq()];
534
+ while (src[i] === "|") {
535
+ i++;
536
+ arms.push(parseSeq());
511
537
  }
512
- case "Alternative": {
513
- const exprs = node2.expressions;
514
- return !exprs || exprs.length === 0 || exprs.every((e) => canBeEmpty(e));
538
+ return arms.length === 1 ? arms[0] : { t: "alt", arms };
539
+ };
540
+ const parseSeq = () => {
541
+ const parts = [];
542
+ while (i < src.length) {
543
+ const ch = src[i];
544
+ if (ch === "|" || ch === ")") break;
545
+ parts.push(parseQuantified());
546
+ }
547
+ if (parts.length === 0) return EMPTY2;
548
+ return parts.length === 1 ? parts[0] : { t: "seq", parts };
549
+ };
550
+ const parseQuantified = () => {
551
+ const atom = parseAtom();
552
+ const ch = src[i];
553
+ let min = null;
554
+ if (ch === "*") {
555
+ min = 0;
556
+ i++;
557
+ } else if (ch === "+") {
558
+ min = 1;
559
+ i++;
560
+ } else if (ch === "?") {
561
+ min = 0;
562
+ i++;
563
+ } else if (ch === "{") {
564
+ min = parseBraceQuantifier();
515
565
  }
516
- case "Group":
517
- return canBeEmpty(node2.expression);
518
- case "Disjunction":
519
- return canBeEmpty(node2.left) || canBeEmpty(node2.right);
520
- default:
521
- return false;
522
- }
523
- }
524
- function extractFirstSet(node2) {
525
- if (!node2) return { firstSet: empty(), canMatchNewline: false };
526
- switch (node2.type) {
527
- case "Disjunction": {
528
- const left = extractFirstSet(node2.left);
529
- const right = extractFirstSet(node2.right);
530
- return {
531
- firstSet: union(left.firstSet, right.firstSet),
532
- canMatchNewline: left.canMatchNewline || right.canMatchNewline
533
- };
566
+ if (min === null) return atom;
567
+ if (src[i] === "?") i++;
568
+ return { t: "rep", node: atom, min };
569
+ };
570
+ const parseBraceQuantifier = () => {
571
+ const close = src.indexOf("}", i);
572
+ if (close === -1) return null;
573
+ const m = /^(\d+)(?:,(\d*))?$/.exec(src.slice(i + 1, close));
574
+ if (!m) return null;
575
+ i = close + 1;
576
+ return Number.parseInt(m[1], 10);
577
+ };
578
+ const parseAtom = () => {
579
+ const ch = src[i];
580
+ switch (ch) {
581
+ case "(":
582
+ return parseGroup();
583
+ case "[":
584
+ return parseClass();
585
+ case ".":
586
+ i++;
587
+ return charNode(any(), false);
588
+ // `.` matches any char except line terminators
589
+ case "\\":
590
+ return parseEscape();
591
+ case "^":
592
+ case "$":
593
+ i++;
594
+ return EMPTY2;
595
+ // anchors are zero-width
596
+ case "{":
597
+ i++;
598
+ return charNode(fromRange(123, 123), false);
599
+ // a `{` with nothing to quantify is a literal brace
600
+ default:
601
+ i += ch.length;
602
+ return charNode(fromRange(ch.codePointAt(0), ch.codePointAt(0)), ch === "\n");
534
603
  }
535
- case "Alternative": {
536
- const exprs = node2.expressions;
537
- if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
538
- let fs = empty();
539
- let canNL = false;
540
- for (const expr of exprs) {
541
- const r = extractFirstSet(expr);
542
- fs = union(fs, r.firstSet);
543
- canNL = canNL || r.canMatchNewline;
544
- if (!canBeEmpty(expr)) break;
545
- }
546
- return { firstSet: fs, canMatchNewline: canNL };
604
+ };
605
+ const parseGroup = () => {
606
+ i++;
607
+ let lookaround = false;
608
+ if (src[i] === "?") {
609
+ const c1 = src[i + 1];
610
+ if (c1 === ":") i += 2;
611
+ else if (c1 === "=" || c1 === "!") {
612
+ i += 2;
613
+ lookaround = true;
614
+ } else if (c1 === "<") {
615
+ const c2 = src[i + 2];
616
+ if (c2 === "=" || c2 === "!") {
617
+ i += 3;
618
+ lookaround = true;
619
+ } else {
620
+ const gt = src.indexOf(">", i);
621
+ if (gt === -1) throw BAIL;
622
+ i = gt + 1;
623
+ }
624
+ } else throw BAIL;
547
625
  }
548
- case "Char": {
549
- const kind = node2.kind;
550
- if (kind === "simple") {
551
- const code = node2.codePoint ?? node2.value.codePointAt(0) ?? 0;
552
- return { firstSet: fromRange(code, code), canMatchNewline: code === 10 };
553
- }
554
- if (kind === "meta") {
555
- const val = node2.value;
556
- if (val === ".") return { firstSet: any(), canMatchNewline: false };
557
- if (val === "d") return { firstSet: fromRange(48, 57), canMatchNewline: false };
558
- if (val === "D") return { firstSet: any(), canMatchNewline: true };
559
- if (val === "w") return {
560
- firstSet: union(
561
- union(fromRange(48, 57), fromRange(65, 90)),
562
- union(fromRange(97, 122), fromRange(95, 95))
563
- ),
564
- canMatchNewline: false
565
- };
566
- if (val === "s") return { firstSet: fromRange(9, 13), canMatchNewline: true };
567
- if (val === "n") return { firstSet: fromRange(10, 10), canMatchNewline: true };
626
+ const inner = parseAlt();
627
+ if (src[i] !== ")") throw BAIL;
628
+ i++;
629
+ return lookaround ? EMPTY2 : inner;
630
+ };
631
+ const parseClass = () => {
632
+ let k = i + 1;
633
+ const negated = src[k] === "^";
634
+ if (negated) k++;
635
+ let body = "";
636
+ while (k < src.length && src[k] !== "]") {
637
+ if (src[k] === "\\") {
638
+ body += src[k] + (src[k + 1] ?? "");
639
+ k += 2;
640
+ } else {
641
+ body += src[k];
642
+ k++;
568
643
  }
569
- return { firstSet: any(), canMatchNewline: true };
570
644
  }
571
- case "CharacterClass": {
572
- const expressions = node2.expressions;
645
+ if (src[k] !== "]") throw BAIL;
646
+ i = k + 1;
647
+ if (negated) return charNode(any(), true);
648
+ const ranges = parseClassRanges(body);
649
+ if (!ranges) return charNode(any(), true);
650
+ return charNode(rangesToSet(ranges), rangesHaveNewline(ranges));
651
+ };
652
+ const parseEscape = () => {
653
+ const e = src[i + 1];
654
+ if (e === void 0) throw BAIL;
655
+ if (e === "u" && /^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) {
656
+ const cp = Number.parseInt(src.slice(i + 2, i + 6), 16);
657
+ i += 6;
658
+ return charNode(fromRange(cp, cp), cp === 10);
659
+ }
660
+ if (e === "x" && /^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) {
661
+ const cp = Number.parseInt(src.slice(i + 2, i + 4), 16);
662
+ i += 4;
663
+ return charNode(fromRange(cp, cp), cp === 10);
664
+ }
665
+ i += 2;
666
+ switch (e) {
667
+ case "d":
668
+ return charNode(rangesToSet(shorthandRanges("d")), false);
669
+ case "w":
670
+ return charNode(rangesToSet(shorthandRanges("w")), false);
671
+ case "s":
672
+ return charNode(rangesToSet(shorthandRanges("s")), true);
673
+ case "D":
674
+ case "W":
675
+ case "S":
676
+ return charNode(any(), true);
677
+ // negated shorthand → widen
678
+ case "b":
679
+ case "B":
680
+ return EMPTY2;
681
+ // word boundary: zero-width
682
+ case "n":
683
+ return charNode(fromRange(10, 10), true);
684
+ case "r":
685
+ return charNode(fromRange(13, 13), false);
686
+ case "t":
687
+ return charNode(fromRange(9, 9), false);
688
+ case "f":
689
+ return charNode(fromRange(12, 12), false);
690
+ case "v":
691
+ return charNode(fromRange(11, 11), false);
692
+ case "0":
693
+ return charNode(fromRange(0, 0), false);
694
+ default:
695
+ if (e >= "1" && e <= "9") return ANY;
696
+ return charNode(fromRange(e.codePointAt(0), e.codePointAt(0)), e === "\n");
697
+ }
698
+ };
699
+ const node2 = parseAlt();
700
+ if (i < src.length) throw BAIL;
701
+ return node2;
702
+ }
703
+ function nullable(n) {
704
+ switch (n.t) {
705
+ case "char":
706
+ return false;
707
+ case "any":
708
+ return true;
709
+ case "empty":
710
+ return true;
711
+ case "seq":
712
+ return n.parts.every(nullable);
713
+ case "alt":
714
+ return n.arms.some(nullable);
715
+ case "rep":
716
+ return n.min === 0 || nullable(n.node);
717
+ }
718
+ }
719
+ function firstSet(n) {
720
+ switch (n.t) {
721
+ case "char":
722
+ return n.set;
723
+ case "any":
724
+ return any();
725
+ case "empty":
726
+ return empty();
727
+ case "rep":
728
+ return firstSet(n.node);
729
+ case "alt": {
573
730
  let fs = empty();
574
- let canNL = false;
575
- for (const expr of expressions) {
576
- if (expr.type === "ClassRange") {
577
- const from = expr.from.codePoint;
578
- const to = expr.to.codePoint;
579
- fs = union(fs, fromRange(from, to));
580
- if (from <= 10 && 10 <= to) canNL = true;
581
- } else if (expr.type === "Char") {
582
- const r = extractFirstSet(expr);
583
- fs = union(fs, r.firstSet);
584
- canNL = canNL || r.canMatchNewline;
585
- }
586
- }
587
- if (node2.negative) return { firstSet: any(), canMatchNewline: true };
588
- return { firstSet: fs, canMatchNewline: canNL };
731
+ for (const arm of n.arms) fs = union(fs, firstSet(arm));
732
+ return fs;
589
733
  }
590
- case "Repetition": {
591
- return extractFirstSet(node2.expression);
734
+ case "seq": {
735
+ let fs = empty();
736
+ for (const part of n.parts) {
737
+ fs = union(fs, firstSet(part));
738
+ if (!nullable(part)) break;
739
+ }
740
+ return fs;
592
741
  }
593
- case "Group": {
594
- return extractFirstSet(node2.expression);
742
+ }
743
+ }
744
+ function firstCanBeNewline(n) {
745
+ switch (n.t) {
746
+ case "char":
747
+ return n.nl;
748
+ case "any":
749
+ return true;
750
+ case "empty":
751
+ return false;
752
+ case "rep":
753
+ return firstCanBeNewline(n.node);
754
+ case "alt":
755
+ return n.arms.some(firstCanBeNewline);
756
+ case "seq": {
757
+ for (const part of n.parts) {
758
+ if (firstCanBeNewline(part)) return true;
759
+ if (!nullable(part)) break;
760
+ }
761
+ return false;
595
762
  }
596
- default:
597
- return { firstSet: any(), canMatchNewline: true };
598
763
  }
599
764
  }
765
+ function firstSetFromRegex(source) {
766
+ let ast;
767
+ try {
768
+ ast = parseRegex(source);
769
+ } catch {
770
+ return { firstSet: any(), canMatchNewline: true };
771
+ }
772
+ if (nullable(ast)) return { firstSet: any(), canMatchNewline: true };
773
+ return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
774
+ }
600
775
 
601
776
  // src/combinators/keywords.ts
602
777
  function word(str, boundary = "_0-9A-Za-z") {
@@ -614,19 +789,19 @@ function keywords(words, opts = {}) {
614
789
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
615
790
  const flags = opts.caseInsensitive ? "iuy" : "uy";
616
791
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
617
- let firstSet = empty();
792
+ let firstSet2 = empty();
618
793
  for (const w of sorted) {
619
794
  if (w.length === 0) continue;
620
795
  const cp = w.codePointAt(0);
621
- firstSet = union(firstSet, fromChar(cp));
796
+ firstSet2 = union(firstSet2, fromChar(cp));
622
797
  if (opts.caseInsensitive) {
623
798
  const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
624
799
  const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
625
- if (u !== void 0) firstSet = union(firstSet, fromChar(u));
626
- if (l !== void 0) firstSet = union(firstSet, fromChar(l));
800
+ if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
801
+ if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
627
802
  }
628
803
  }
629
- const meta = { firstSet, canMatchNewline: false, isTrivia: false };
804
+ const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
630
805
  return {
631
806
  _tag: "keywords",
632
807
  _meta: meta,
@@ -1020,10 +1195,10 @@ function needsDeferredTriviaCommit(ctx) {
1020
1195
  }
1021
1196
  function saveTriviaMark(ctx) {
1022
1197
  const m = saveCstMark(ctx);
1023
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1198
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1024
1199
  }
1025
1200
  function rollbackTrivia(ctx, mark) {
1026
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1201
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1027
1202
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1028
1203
  }
1029
1204
  function scanWithLabels(input, cur, ctx) {
@@ -1397,6 +1572,13 @@ function ref() {
1397
1572
  define(p) {
1398
1573
  if (resolved) throw new Error("ref<T>() already defined");
1399
1574
  resolved = p;
1575
+ meta.firstSet = p._meta.firstSet;
1576
+ meta.canMatchNewline = p._meta.canMatchNewline;
1577
+ meta.isTrivia = p._meta.isTrivia;
1578
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
1579
+ else delete meta.triviaKindLabels;
1580
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
1581
+ else delete meta.disjoint;
1400
1582
  }
1401
1583
  };
1402
1584
  return slot;
@@ -1437,6 +1619,9 @@ function markUnusedValues(root) {
1437
1619
  case "transform":
1438
1620
  visit(def.parser, true);
1439
1621
  return;
1622
+ case "field":
1623
+ visit(def.parser, true);
1624
+ return;
1440
1625
  case "skip":
1441
1626
  visit(def.main, consumed);
1442
1627
  visit(def.skipped, false);
@@ -1488,6 +1673,11 @@ function markUnusedValues(root) {
1488
1673
  }
1489
1674
 
1490
1675
  // src/combinators/parser.ts
1676
+ function tagRule(r, key) {
1677
+ ;
1678
+ r._ruleName = key;
1679
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1680
+ }
1491
1681
  function rules(factory) {
1492
1682
  const cache = {};
1493
1683
  const proxy = new Proxy(cache, {
@@ -1496,7 +1686,7 @@ function rules(factory) {
1496
1686
  const record = target;
1497
1687
  if (!(key in record)) {
1498
1688
  const r = ref();
1499
- r._ruleName = key;
1689
+ tagRule(r, key);
1500
1690
  record[key] = r;
1501
1691
  }
1502
1692
  return record[key];
@@ -1507,12 +1697,12 @@ function rules(factory) {
1507
1697
  const placeholder = cache[key];
1508
1698
  const parser2 = definitions[key];
1509
1699
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1510
- ;
1700
+ tagRule(parser2, key);
1511
1701
  placeholder.define(parser2);
1512
1702
  placeholder._meta.firstSet = parser2._meta.firstSet;
1513
1703
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1514
1704
  } else {
1515
- ;
1705
+ tagRule(parser2, key);
1516
1706
  cache[key] = parser2;
1517
1707
  }
1518
1708
  }
@@ -1569,44 +1759,130 @@ function buildReadsTrivia(def) {
1569
1759
  const src = def.buildSrc ?? def.build.toString();
1570
1760
  const arity = confirmedBuildArity(src);
1571
1761
  if (arity === null) return true;
1572
- return arity >= 4;
1762
+ return arity >= 5;
1573
1763
  }
1574
1764
  function buildReadsState(def) {
1575
1765
  if (!def.build) return true;
1576
1766
  const src = def.buildSrc ?? def.build.toString();
1577
1767
  const arity = confirmedBuildArity(src);
1578
1768
  if (arity === null) return true;
1579
- return arity >= 5;
1769
+ return arity >= 6;
1770
+ }
1771
+
1772
+ // src/compiler/fields.ts
1773
+ function buildReadsFields(def) {
1774
+ if (!def.build) return true;
1775
+ const src = def.buildSrc ?? def.build.toString();
1776
+ const arity = confirmedBuildArity(src);
1777
+ if (arity === null) return true;
1778
+ return arity >= 2;
1779
+ }
1780
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1781
+ if (seen.has(p)) return false;
1782
+ seen.add(p);
1783
+ const d = p._def;
1784
+ switch (d.tag) {
1785
+ case "field":
1786
+ return true;
1787
+ case "node":
1788
+ return false;
1789
+ case "lazy": {
1790
+ try {
1791
+ return parserHasOwnFields(d.thunk(), seen);
1792
+ } catch {
1793
+ return false;
1794
+ }
1795
+ }
1796
+ case "sequence":
1797
+ case "choice":
1798
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1799
+ case "sepBy":
1800
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1801
+ case "skip":
1802
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1803
+ case "grammar":
1804
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1805
+ case "scanTo":
1806
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1807
+ case "recover":
1808
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1809
+ case "many":
1810
+ case "oneOrMore":
1811
+ case "optional":
1812
+ case "transform":
1813
+ case "trivia":
1814
+ case "token":
1815
+ case "label":
1816
+ case "expect":
1817
+ case "withCtx":
1818
+ case "not":
1819
+ return parserHasOwnFields(d.parser, seen);
1820
+ default:
1821
+ return false;
1822
+ }
1823
+ }
1824
+ function buildFieldMap(captures) {
1825
+ if (!captures?.length) return void 0;
1826
+ const out = {};
1827
+ for (const cap of captures) {
1828
+ const entry = { value: cap.value, span: cap.span };
1829
+ const current = out[cap.name];
1830
+ if (current === void 0) out[cap.name] = entry;
1831
+ else if (Array.isArray(current)) current.push(entry);
1832
+ else out[cap.name] = [current, entry];
1833
+ }
1834
+ return out;
1580
1835
  }
1581
1836
 
1582
1837
  // src/combinators/node.ts
1583
- function collapseChild(child) {
1838
+ function unwrapChild(child) {
1584
1839
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1585
1840
  }
1586
- function node(type, combinator, build, opts) {
1841
+ function isCstChild(value) {
1842
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1843
+ }
1844
+ function missingInferredType() {
1845
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1846
+ }
1847
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1848
+ const hasExplicitType = typeof typeOrCombinator === "string";
1849
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1850
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1851
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1852
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1853
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1587
1854
  const meta = {
1588
1855
  firstSet: combinator._meta.firstSet,
1589
1856
  canMatchNewline: combinator._meta.canMatchNewline,
1590
1857
  isTrivia: false
1591
1858
  };
1859
+ const unwrap = opts?.unwrap === true;
1592
1860
  const collapse = opts?.collapse === true;
1593
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1861
+ if (unwrap && collapse) {
1862
+ throw new Error("node() options cannot set both unwrap and collapse");
1863
+ }
1864
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1594
1865
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1595
1866
  const clonesState = build ? buildReadsState(def) : true;
1867
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1596
1868
  return {
1597
1869
  _tag: "node",
1598
1870
  _meta: meta,
1599
1871
  _def: def,
1600
1872
  parse(input, pos, ctx) {
1601
1873
  const saved = beginCstNodeCapture(ctx);
1874
+ const savedFields = ctx._fields;
1875
+ ctx._fields = capturesFields ? [] : void 0;
1602
1876
  if (!capturesTrivia) ctx.captureTrivia = false;
1603
1877
  const r = combinator.parse(input, pos, ctx);
1878
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1879
+ ctx._fields = savedFields;
1604
1880
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1605
1881
  if (!r.ok) return r;
1606
1882
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1607
- 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 };
1608
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1609
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1883
+ const nodeType = def.type ?? missingInferredType();
1884
+ 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 };
1885
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1610
1886
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1611
1887
  pushCstChild(ctx, built, rawEntry);
1612
1888
  }
@@ -1667,6 +1943,19 @@ function label(name, combinator) {
1667
1943
  }
1668
1944
  };
1669
1945
  }
1946
+ function field(name, combinator) {
1947
+ return {
1948
+ _tag: combinator._tag,
1949
+ _meta: combinator._meta,
1950
+ _def: { tag: "field", name, parser: combinator },
1951
+ parse(input, pos, ctx) {
1952
+ const result = combinator.parse(input, pos, ctx);
1953
+ if (!result.ok) return result;
1954
+ ctx._fields?.push({ name, value: result.value, span: result.span });
1955
+ return result;
1956
+ }
1957
+ };
1958
+ }
1670
1959
 
1671
1960
  // src/compiler/line-index.ts
1672
1961
  function buildLineIndex(input) {
@@ -1876,77 +2165,11 @@ function expect(combinator, label2) {
1876
2165
 
1877
2166
  // src/compiler/scannable-run.ts
1878
2167
  var BACKSLASH = 92;
1879
- var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1880
2168
  var META = new Set("()[]{}*+?|^$.".split(""));
1881
- var SPACE_RANGES2 = [
1882
- [9, 13],
1883
- [32, 32],
1884
- [160, 160],
1885
- [5760, 5760],
1886
- [8192, 8202],
1887
- [8232, 8232],
1888
- [8233, 8233],
1889
- [8239, 8239],
1890
- [8287, 8287],
1891
- [12288, 12288],
1892
- [65279, 65279]
1893
- ];
1894
- function shorthandRanges2(ch) {
1895
- if (ch === "d") return [[48, 57]];
1896
- if (ch === "s") return SPACE_RANGES2;
1897
- return [[48, 57], [65, 90], [97, 122], [95, 95]];
1898
- }
1899
- function readUnicodeEscape(body, i) {
1900
- if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1901
- const hex = body.slice(i + 2, i + 6);
1902
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1903
- return { cp: Number.parseInt(hex, 16), next: i + 6 };
1904
- }
1905
- function parseClassRanges(body) {
1906
- const ranges = [];
1907
- let i = 0;
1908
- const readAtom = () => {
1909
- const ch = body[i];
1910
- if (ch === void 0) return null;
1911
- if (ch === "\\") {
1912
- const uni = readUnicodeEscape(body, i);
1913
- if (uni) {
1914
- i = uni.next;
1915
- return { cp: uni.cp };
1916
- }
1917
- const e = body[i + 1];
1918
- if (e === void 0) return null;
1919
- i += 2;
1920
- if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
1921
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1922
- if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1923
- return { cp: e.codePointAt(0) };
1924
- }
1925
- i += ch.length;
1926
- return { cp: ch.codePointAt(0) };
1927
- };
1928
- while (i < body.length) {
1929
- const lo = readAtom();
1930
- if (lo === null) return null;
1931
- if ("set" in lo) {
1932
- ranges.push(...lo.set);
1933
- continue;
1934
- }
1935
- if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1936
- i += 1;
1937
- const hi = readAtom();
1938
- if (hi === null || "set" in hi) return null;
1939
- ranges.push([lo.cp, hi.cp]);
1940
- } else {
1941
- ranges.push([lo.cp, lo.cp]);
1942
- }
1943
- }
1944
- return ranges.length ? ranges : null;
1945
- }
1946
2169
  function classToRanges(cls) {
1947
- if (cls === "\\d") return shorthandRanges2("d");
1948
- if (cls === "\\w") return shorthandRanges2("w");
1949
- if (cls === "\\s") return shorthandRanges2("s");
2170
+ if (cls === "\\d") return shorthandRanges("d");
2171
+ if (cls === "\\w") return shorthandRanges("w");
2172
+ if (cls === "\\s") return shorthandRanges("s");
1950
2173
  const body = cls.slice(1, -1);
1951
2174
  if (body.startsWith("^")) return null;
1952
2175
  return parseClassRanges(body);
@@ -1964,7 +2187,7 @@ function literalCodePoints(frag) {
1964
2187
  if (ch === "\\") {
1965
2188
  const e = frag[i + 1];
1966
2189
  if (e === void 0) return null;
1967
- out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
2190
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1968
2191
  i += 2;
1969
2192
  continue;
1970
2193
  }
@@ -2057,7 +2280,7 @@ function parseSeqParts(source) {
2057
2280
  if (e === "d" || e === "w" || e === "s") {
2058
2281
  flush();
2059
2282
  i += 2;
2060
- i += runFrom(shorthandRanges2(e), false, source[i]);
2283
+ i += runFrom(shorthandRanges(e), false, source[i]);
2061
2284
  continue;
2062
2285
  }
2063
2286
  const uni = readUnicodeEscape(source, i);
@@ -2066,8 +2289,8 @@ function parseSeqParts(source) {
2066
2289
  cp2 = uni.cp;
2067
2290
  i = uni.next;
2068
2291
  } else if (e === void 0) return null;
2069
- else if (e in CLASS_ESCAPES2) {
2070
- cp2 = CLASS_ESCAPES2[e];
2292
+ else if (e in CLASS_ESCAPES) {
2293
+ cp2 = CLASS_ESCAPES[e];
2071
2294
  i += 2;
2072
2295
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
2073
2296
  else {
@@ -2174,7 +2397,7 @@ function rangesSubset(sub, sup) {
2174
2397
  }
2175
2398
  function parseClassOperand(body) {
2176
2399
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2177
- return { ranges: shorthandRanges2(body[1]), negated: false };
2400
+ return { ranges: shorthandRanges(body[1]), negated: false };
2178
2401
  }
2179
2402
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2180
2403
  let inner = body.slice(1, -1);
@@ -2607,7 +2830,7 @@ function parseScanShapeCore(source) {
2607
2830
  }
2608
2831
  m = /^\\([dws])([+*])$/.exec(source);
2609
2832
  if (m) {
2610
- return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2833
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2611
2834
  }
2612
2835
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2613
2836
  if (m) {
@@ -3149,21 +3372,22 @@ function emitScannableTerminal(shape, c) {
3149
3372
  }
3150
3373
 
3151
3374
  // src/compiler/inline-build.ts
3152
- 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*$/;
3375
+ 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*$/;
3153
3376
  function escapeRegExp(s) {
3154
3377
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3155
3378
  }
3156
3379
  function looseMkBuildRe(type) {
3157
3380
  return new RegExp(
3158
- 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*$`
3381
+ 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*$`
3159
3382
  );
3160
3383
  }
3161
3384
  function analyzeMkInlineBuild(def) {
3162
3385
  if (!def.build) return null;
3386
+ if (def.type === void 0) return null;
3163
3387
  const src = (def.buildSrc ?? def.build.toString()).trim();
3164
3388
  const strict = src.match(MK_BUILD_RE);
3165
3389
  if (strict) {
3166
- const mkType = strict[2];
3390
+ const mkType = strict[6];
3167
3391
  return mkType === def.type ? def.type : null;
3168
3392
  }
3169
3393
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3490,8 +3714,9 @@ function planDisjointDispatch(parsers) {
3490
3714
  }
3491
3715
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3492
3716
  }
3493
- function captureRestoreBody(mL, mR, mTl, mLg) {
3494
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3717
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3718
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3719
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3495
3720
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3496
3721
  }
3497
3722
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3539,6 +3764,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3539
3764
  // Delegating wrappers: defer to the wrapped parser.
3540
3765
  case "transform":
3541
3766
  case "label":
3767
+ case "field":
3542
3768
  case "expect":
3543
3769
  case "withCtx":
3544
3770
  case "grammar":
@@ -3585,6 +3811,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3585
3811
  case "optional":
3586
3812
  case "transform":
3587
3813
  case "label":
3814
+ case "field":
3588
3815
  case "expect":
3589
3816
  case "withCtx":
3590
3817
  case "grammar":
@@ -3622,22 +3849,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3622
3849
  ctx.recordFail = savedRecord;
3623
3850
  const ind0 = ind(ctx);
3624
3851
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3852
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3625
3853
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3626
3854
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3627
3855
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3856
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3628
3857
  const stmts = [
3629
3858
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3630
3859
  ...mL ? [
3631
3860
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3632
3861
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3633
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3862
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3863
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3634
3864
  ] : [],
3635
3865
  `${ind0}${lbl}: {`,
3636
3866
  ...r.stmts,
3637
3867
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3638
3868
  `${ind0}}`,
3639
3869
  ...mL ? [
3640
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3870
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3641
3871
  ] : []
3642
3872
  ];
3643
3873
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3864,6 +4094,7 @@ function failsAtStart(p) {
3864
4094
  return true;
3865
4095
  case "transform":
3866
4096
  case "label":
4097
+ case "field":
3867
4098
  return failsAtStart(d.parser);
3868
4099
  default:
3869
4100
  return false;
@@ -3883,6 +4114,7 @@ function canMatchEmptyAtStart(p) {
3883
4114
  return true;
3884
4115
  case "transform":
3885
4116
  case "label":
4117
+ case "field":
3886
4118
  return canMatchEmptyAtStart(d.parser);
3887
4119
  case "literal":
3888
4120
  return d.value.length === 0;
@@ -4065,11 +4297,13 @@ function emitFirstMatch(def, ctx, pos) {
4065
4297
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
4066
4298
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
4067
4299
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4300
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
4068
4301
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
4069
4302
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
4070
4303
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
4071
4304
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
4072
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4305
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4306
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
4073
4307
  const failSlot = atStart ? staticFx : "_ctx._fx";
4074
4308
  stmts.push(`${ind0}if (${skipCond}) {`);
4075
4309
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -4079,7 +4313,8 @@ function emitFirstMatch(def, ctx, pos) {
4079
4313
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
4080
4314
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
4081
4315
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
4082
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4316
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4317
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
4083
4318
  );
4084
4319
  }
4085
4320
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4472,6 +4707,9 @@ function emitToken(def, ctx, pos) {
4472
4707
  };
4473
4708
  }
4474
4709
  function emitNode(def, ctx, pos) {
4710
+ if (def.type === void 0) {
4711
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4712
+ }
4475
4713
  const structural = def.build === void 0;
4476
4714
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4477
4715
  let buildIdx = null;
@@ -4483,25 +4721,36 @@ function emitNode(def, ctx, pos) {
4483
4721
  const i = ind(ctx);
4484
4722
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4485
4723
  const clonesState = !structural && buildReadsState(def);
4724
+ const hasFields = parserHasOwnFields(def.parser);
4725
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4486
4726
  const chV = v(ctx, "_ch");
4487
4727
  const rawV = v(ctx, "_raw");
4488
4728
  const capTLv = structural ? v(ctx, "_ctl") : null;
4489
4729
  const capSTv = structural ? v(ctx, "_cst") : null;
4730
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4490
4731
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4491
4732
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4492
4733
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4493
4734
  if (structural) ctx.needsHostReads = true;
4494
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4735
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4736
+ 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)` : ""}
4495
4737
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4496
4738
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4739
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4740
+ const sf = hasFields ? v(ctx, "_sf") : null;
4741
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4742
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4497
4743
  const stmts = [
4498
4744
  allocStmt,
4499
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4500
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4745
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4746
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4501
4747
  ];
4502
4748
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4503
4749
  stmts.push(...innerStmts);
4504
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4750
+ if (sf && fArr) {
4751
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4752
+ }
4753
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4505
4754
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4506
4755
  let stV = "undefined";
4507
4756
  if (structural) {
@@ -4511,14 +4760,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4511
4760
  stV = v(ctx, "_nst");
4512
4761
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4513
4762
  }
4763
+ if (sf && fArr) {
4764
+ const fe = v(ctx, "_fe");
4765
+ const cur = v(ctx, "_fc");
4766
+ stmts.push(
4767
+ `${i}let ${fObj} = undefined`,
4768
+ `${i}if (${fArr} && ${fArr}.length) {`,
4769
+ `${i} ${fObj} = {}`,
4770
+ `${i} for (const ${fe} of ${fArr}) {`,
4771
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4772
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4773
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4774
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4775
+ `${i} }`,
4776
+ `${i}}`
4777
+ );
4778
+ }
4514
4779
  const ndV = v(ctx, "_nd");
4515
- 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})`;
4516
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4517
- 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;
4780
+ 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})`;
4781
+ 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;
4782
+ 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;
4783
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4784
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4785
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4518
4786
  stmts.push(
4519
4787
  `${i}const ${ndV} = ${finalExpr}`,
4520
4788
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4521
- `${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} } })`
4789
+ `${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} } })`
4522
4790
  );
4523
4791
  return { stmts, valueVar: ndV, endVar };
4524
4792
  }
@@ -4733,6 +5001,17 @@ function emitDispatch(p, ctx, pos) {
4733
5001
  endVar: inner.endVar
4734
5002
  };
4735
5003
  }
5004
+ case "field": {
5005
+ const inner = emit(def.parser, ctx, pos);
5006
+ return {
5007
+ stmts: [
5008
+ ...inner.stmts,
5009
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
5010
+ ],
5011
+ valueVar: inner.valueVar,
5012
+ endVar: inner.endVar
5013
+ };
5014
+ }
4736
5015
  case "grammar": {
4737
5016
  const savedTrivia = ctx.activeTrivia;
4738
5017
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4834,6 +5113,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4834
5113
  case "trivia":
4835
5114
  case "token":
4836
5115
  case "label":
5116
+ case "field":
4837
5117
  case "optional":
4838
5118
  case "many":
4839
5119
  case "oneOrMore":
@@ -4870,6 +5150,7 @@ function childrenOf(def) {
4870
5150
  case "trivia":
4871
5151
  case "token":
4872
5152
  case "label":
5153
+ case "field":
4873
5154
  case "grammar":
4874
5155
  case "not":
4875
5156
  case "node":
@@ -5111,6 +5392,11 @@ function assertRuleName(name) {
5111
5392
  );
5112
5393
  }
5113
5394
  }
5395
+ function publicRuleWrapperSource(rule, fnSource) {
5396
+ const labels = rule._meta.triviaKindLabels;
5397
+ if (labels === void 0) return fnSource;
5398
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5399
+ }
5114
5400
  function compileLinkable(ruleMapArg, ns) {
5115
5401
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
5116
5402
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
@@ -5213,7 +5499,7 @@ function compileLinkable(ruleMapArg, ns) {
5213
5499
  ctx.recordFail = savedRec;
5214
5500
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5215
5501
  }
5216
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5502
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5217
5503
  }
5218
5504
  if (ctx.runtimeParsers.length !== 0) return null;
5219
5505
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5236,14 +5522,14 @@ function compileLinkable(ruleMapArg, ns) {
5236
5522
  ...privateFns
5237
5523
  ].filter(Boolean);
5238
5524
  const wrappers = /* @__PURE__ */ new Map();
5239
- for (const { key, r } of perEntry) {
5240
- wrappers.set(key, [
5525
+ for (const { key, rule, r } of perEntry) {
5526
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5241
5527
  `function(input, _pos, _ctx) {`,
5242
5528
  ` let pos = _pos`,
5243
5529
  ...r.stmts,
5244
5530
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5245
5531
  `}`
5246
- ].join("\n"));
5532
+ ].join("\n")));
5247
5533
  }
5248
5534
  const firstSets = /* @__PURE__ */ new Map();
5249
5535
  for (const [key, rule] of ruleMap) {
@@ -5446,6 +5732,7 @@ function evalRuleMapIR(ir) {
5446
5732
  "skip",
5447
5733
  "trivia",
5448
5734
  "label",
5735
+ "field",
5449
5736
  "expect",
5450
5737
  "_tf",
5451
5738
  "_nd",
@@ -5472,6 +5759,7 @@ function evalRuleMapIR(ir) {
5472
5759
  skip,
5473
5760
  trivia,
5474
5761
  label,
5762
+ field,
5475
5763
  expect,
5476
5764
  _tf,
5477
5765
  _nd
@@ -5486,9 +5774,26 @@ function linkable(rulesMap, ns) {
5486
5774
  if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
5487
5775
  return pieces;
5488
5776
  }
5489
- function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
5777
+ function normalizeCstCollapse(collapse) {
5778
+ if (collapse === true) return () => true;
5779
+ if (Array.isArray(collapse)) {
5780
+ const types = new Set(collapse);
5781
+ return (type) => types.has(type);
5782
+ }
5783
+ return typeof collapse === "function" ? collapse : void 0;
5784
+ }
5785
+ function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
5490
5786
  return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
5491
5787
  }
5788
+ function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
5789
+ if (typeof typeOrOptions === "string") {
5790
+ return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
5791
+ }
5792
+ const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
5793
+ const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
5794
+ if (collapse) host._parsemanCstCollapse = collapse;
5795
+ return host;
5796
+ }
5492
5797
  function pick(grammar, names) {
5493
5798
  const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
5494
5799
  const keep = /* @__PURE__ */ new Set();
@@ -5520,8 +5825,9 @@ function fusedBody(pieces) {
5520
5825
  }
5521
5826
  }
5522
5827
  const contributing = new Set(winner.values());
5523
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5524
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5828
+ const contributingPieces = [...contributing];
5829
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
5830
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5525
5831
  const lines = [
5526
5832
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5527
5833
  "const _pfFail = {}",
@@ -5529,7 +5835,7 @@ function fusedBody(pieces) {
5529
5835
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5530
5836
  ...needsHostReads ? [HOST_READS_DECL] : [],
5531
5837
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5532
- ...[...contributing].flatMap((p) => p.prelude),
5838
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5533
5839
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5534
5840
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5535
5841
  ];
@@ -5552,7 +5858,7 @@ function fusedBody(pieces) {
5552
5858
  }
5553
5859
  );
5554
5860
  const env = {};
5555
- for (const p of contributing) {
5861
+ for (const p of contributingPieces) {
5556
5862
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5557
5863
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5558
5864
  }
@@ -5572,14 +5878,23 @@ function materializePiece(p) {
5572
5878
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5573
5879
  return pieces;
5574
5880
  }
5575
- function itemPieces(item) {
5881
+ function nextComposeNs(used) {
5882
+ let ns;
5883
+ do {
5884
+ ns = `_lk${_nsCounter++}_`;
5885
+ } while (used.has(ns));
5886
+ used.add(ns);
5887
+ return ns;
5888
+ }
5889
+ function itemPieces(item, used) {
5576
5890
  const carried = item[COMPOSED_PIECES];
5577
- if (Array.isArray(carried)) return carried.map(materializePiece);
5578
- if (item.ruleFns instanceof Map) return [item];
5579
- return [linkable(item)];
5891
+ const pieces = Array.isArray(carried) ? carried.map(materializePiece) : item.ruleFns instanceof Map ? [item] : [linkable(item, nextComposeNs(used))];
5892
+ for (const p of pieces) used.add(p.ns);
5893
+ return pieces;
5580
5894
  }
5581
5895
  function compose(items) {
5582
- const pieces = items.flatMap(itemPieces);
5896
+ const used = /* @__PURE__ */ new Set();
5897
+ const pieces = items.flatMap((item) => itemPieces(item, used));
5583
5898
  const map = fuseRules(pieces);
5584
5899
  Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
5585
5900
  return map;
@@ -6305,6 +6620,7 @@ export {
6305
6620
  createVisitor,
6306
6621
  cstBuildHost,
6307
6622
  expect,
6623
+ field,
6308
6624
  gapIsSignificant,
6309
6625
  gapText,
6310
6626
  guard,