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
@@ -217,6 +217,13 @@ function ref() {
217
217
  define(p) {
218
218
  if (resolved) throw new Error("ref<T>() already defined");
219
219
  resolved = p;
220
+ meta.firstSet = p._meta.firstSet;
221
+ meta.canMatchNewline = p._meta.canMatchNewline;
222
+ meta.isTrivia = p._meta.isTrivia;
223
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
224
+ else delete meta.triviaKindLabels;
225
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
226
+ else delete meta.disjoint;
220
227
  }
221
228
  };
222
229
  return slot;
@@ -324,7 +331,8 @@ function saveCstMark(ctx) {
324
331
  return {
325
332
  raw: cstRawLen(ctx),
326
333
  tlog: cstTlLen(ctx),
327
- leaves: cstLeavesLen(ctx)
334
+ leaves: cstLeavesLen(ctx),
335
+ fields: ctx._fields?.length ?? 0
328
336
  };
329
337
  }
330
338
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -348,11 +356,13 @@ function rollbackCstCapture(ctx, mark) {
348
356
  if (mark.tlog === 0) b.tl = void 0;
349
357
  else b.tl.length = mark.tlog;
350
358
  }
359
+ if (ctx._fields) ctx._fields.length = mark.fields;
351
360
  return;
352
361
  }
353
362
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
354
363
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
355
364
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
365
+ if (ctx._fields) ctx._fields.length = mark.fields;
356
366
  }
357
367
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
358
368
  const insertIdx = cstRawLen(ctx);
@@ -396,9 +406,9 @@ function asciiFoldEq(a, b) {
396
406
  }
397
407
  function literal(value, opts = {}) {
398
408
  const caseInsensitive = opts.caseInsensitive ?? false;
399
- const firstSet = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
409
+ const firstSet2 = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
400
410
  const meta = {
401
- firstSet,
411
+ firstSet: firstSet2,
402
412
  canMatchNewline: value.includes("\n"),
403
413
  isTrivia: false
404
414
  };
@@ -410,7 +420,7 @@ function literal(value, opts = {}) {
410
420
  meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
411
421
  { lo: firstLower, hi: firstLower },
412
422
  { lo: firstUpper, hi: firstUpper }
413
- ] } : firstSet;
423
+ ] } : firstSet2;
414
424
  }
415
425
  return {
416
426
  _tag: "literal",
@@ -433,68 +443,66 @@ function literal(value, opts = {}) {
433
443
  };
434
444
  }
435
445
 
436
- // src/combinators/regex.ts
437
- var firstSetAnalyzer = null;
438
- function registerRegexAnalyzer(analyzer) {
439
- firstSetAnalyzer = analyzer;
440
- }
441
- var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
442
- var SCAN_BAIL_AT = 64;
446
+ // src/regex/classes.ts
447
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
443
448
  var SPACE_RANGES = [
444
449
  [9, 13],
445
450
  [32, 32],
446
451
  [160, 160],
447
452
  [5760, 5760],
448
453
  [8192, 8202],
449
- [8232, 8233],
454
+ [8232, 8232],
455
+ [8233, 8233],
450
456
  [8239, 8239],
451
457
  [8287, 8287],
452
458
  [12288, 12288],
453
459
  [65279, 65279]
454
460
  ];
455
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
456
461
  function shorthandRanges(ch) {
457
462
  if (ch === "d") return [[48, 57]];
458
463
  if (ch === "s") return SPACE_RANGES;
459
464
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
460
465
  }
461
- function inRanges(cp, ranges) {
462
- for (let i = 0; i < ranges.length; i++) {
463
- const [lo, hi] = ranges[i];
464
- if (cp >= lo && cp <= hi) return true;
465
- }
466
- return false;
466
+ function readUnicodeEscape(body, i) {
467
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
468
+ const hex = body.slice(i + 2, i + 6);
469
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
470
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
467
471
  }
468
- function readClassRanges(body) {
469
- if (body.startsWith("^")) return null;
472
+ function parseClassRanges(body) {
470
473
  const ranges = [];
471
474
  let i = 0;
472
475
  const readAtom = () => {
473
476
  const ch = body[i];
474
477
  if (ch === void 0) return null;
475
478
  if (ch === "\\") {
476
- const esc = body[i + 1];
477
- if (esc === void 0) return null;
479
+ const uni = readUnicodeEscape(body, i);
480
+ if (uni) {
481
+ i = uni.next;
482
+ return { cp: uni.cp };
483
+ }
484
+ const e = body[i + 1];
485
+ if (e === void 0) return null;
478
486
  i += 2;
479
- if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
480
- if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
481
- if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
482
- return { cp: esc.charCodeAt(0) };
487
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
488
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
489
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
490
+ return { cp: e.codePointAt(0) };
483
491
  }
484
- i++;
485
- return { cp: ch.charCodeAt(0) };
492
+ i += ch.length;
493
+ return { cp: ch.codePointAt(0) };
486
494
  };
487
495
  while (i < body.length) {
488
496
  const lo = readAtom();
489
- if (!lo) return null;
490
- if ("ranges" in lo) {
491
- ranges.push(...lo.ranges);
497
+ if (lo === null) return null;
498
+ if ("set" in lo) {
499
+ ranges.push(...lo.set);
492
500
  continue;
493
501
  }
494
502
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
495
- i++;
503
+ i += 1;
496
504
  const hi = readAtom();
497
- if (!hi || "ranges" in hi) return null;
505
+ if (hi === null || "set" in hi) return null;
498
506
  ranges.push([lo.cp, hi.cp]);
499
507
  } else {
500
508
  ranges.push([lo.cp, lo.cp]);
@@ -502,6 +510,25 @@ function readClassRanges(body) {
502
510
  }
503
511
  return ranges.length ? ranges : null;
504
512
  }
513
+
514
+ // src/combinators/regex.ts
515
+ var firstSetAnalyzer = null;
516
+ function registerRegexAnalyzer(analyzer) {
517
+ firstSetAnalyzer = analyzer;
518
+ }
519
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
520
+ var SCAN_BAIL_AT = 64;
521
+ function inRanges(cp, ranges) {
522
+ for (let i = 0; i < ranges.length; i++) {
523
+ const [lo, hi] = ranges[i];
524
+ if (cp >= lo && cp <= hi) return true;
525
+ }
526
+ return false;
527
+ }
528
+ function readClassRanges(body) {
529
+ if (body.startsWith("^")) return null;
530
+ return parseClassRanges(body);
531
+ }
505
532
  function shortScanner(source, flags) {
506
533
  if (/[imsuvy]/.test(flags)) return null;
507
534
  let ranges = null;
@@ -535,8 +562,8 @@ function regex(pattern, flags = "") {
535
562
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
536
563
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
537
564
  const scan = shortScanner(source, resolvedFlags);
538
- const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
539
- const meta = { firstSet, canMatchNewline, isTrivia: false };
565
+ const { firstSet: firstSet2, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
566
+ const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
540
567
  return {
541
568
  _tag: "regex",
542
569
  _meta: meta,
@@ -564,111 +591,266 @@ function regex(pattern, flags = "") {
564
591
  };
565
592
  }
566
593
 
567
- // src/combinators/regex-analyze.ts
568
- var import_regexp_tree = __toESM(require("regexp-tree"), 1);
569
- function firstSetFromRegex(pattern) {
570
- try {
571
- const ast = import_regexp_tree.default.parse(`/${pattern}/`);
572
- return extractFirstSet(ast.body);
573
- } catch {
574
- return { firstSet: any(), canMatchNewline: true };
575
- }
594
+ // src/regex/first-set.ts
595
+ var ANY = { t: "any" };
596
+ var EMPTY2 = { t: "empty" };
597
+ function charNode(set, nl) {
598
+ return { t: "char", set, nl };
576
599
  }
577
- function canBeEmpty(node2) {
578
- if (!node2) return true;
579
- switch (node2.type) {
580
- case "Repetition": {
581
- const q = node2.quantifier;
582
- return q.kind === "*" || q.kind === "?" || q.kind === "Range" && (q.from ?? 1) === 0;
600
+ function rangesToSet(ranges) {
601
+ let fs2 = empty();
602
+ for (const [lo, hi] of ranges) fs2 = union(fs2, fromRange(lo, hi));
603
+ return fs2;
604
+ }
605
+ function rangesHaveNewline(ranges) {
606
+ return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
607
+ }
608
+ var BAIL = /* @__PURE__ */ Symbol("regex-bail");
609
+ function parseRegex(src) {
610
+ let i = 0;
611
+ const parseAlt = () => {
612
+ const arms = [parseSeq()];
613
+ while (src[i] === "|") {
614
+ i++;
615
+ arms.push(parseSeq());
583
616
  }
584
- case "Alternative": {
585
- const exprs = node2.expressions;
586
- return !exprs || exprs.length === 0 || exprs.every((e) => canBeEmpty(e));
617
+ return arms.length === 1 ? arms[0] : { t: "alt", arms };
618
+ };
619
+ const parseSeq = () => {
620
+ const parts = [];
621
+ while (i < src.length) {
622
+ const ch = src[i];
623
+ if (ch === "|" || ch === ")") break;
624
+ parts.push(parseQuantified());
625
+ }
626
+ if (parts.length === 0) return EMPTY2;
627
+ return parts.length === 1 ? parts[0] : { t: "seq", parts };
628
+ };
629
+ const parseQuantified = () => {
630
+ const atom = parseAtom();
631
+ const ch = src[i];
632
+ let min = null;
633
+ if (ch === "*") {
634
+ min = 0;
635
+ i++;
636
+ } else if (ch === "+") {
637
+ min = 1;
638
+ i++;
639
+ } else if (ch === "?") {
640
+ min = 0;
641
+ i++;
642
+ } else if (ch === "{") {
643
+ min = parseBraceQuantifier();
587
644
  }
588
- case "Group":
589
- return canBeEmpty(node2.expression);
590
- case "Disjunction":
591
- return canBeEmpty(node2.left) || canBeEmpty(node2.right);
592
- default:
593
- return false;
594
- }
595
- }
596
- function extractFirstSet(node2) {
597
- if (!node2) return { firstSet: empty(), canMatchNewline: false };
598
- switch (node2.type) {
599
- case "Disjunction": {
600
- const left = extractFirstSet(node2.left);
601
- const right = extractFirstSet(node2.right);
602
- return {
603
- firstSet: union(left.firstSet, right.firstSet),
604
- canMatchNewline: left.canMatchNewline || right.canMatchNewline
605
- };
645
+ if (min === null) return atom;
646
+ if (src[i] === "?") i++;
647
+ return { t: "rep", node: atom, min };
648
+ };
649
+ const parseBraceQuantifier = () => {
650
+ const close = src.indexOf("}", i);
651
+ if (close === -1) return null;
652
+ const m = /^(\d+)(?:,(\d*))?$/.exec(src.slice(i + 1, close));
653
+ if (!m) return null;
654
+ i = close + 1;
655
+ return Number.parseInt(m[1], 10);
656
+ };
657
+ const parseAtom = () => {
658
+ const ch = src[i];
659
+ switch (ch) {
660
+ case "(":
661
+ return parseGroup();
662
+ case "[":
663
+ return parseClass();
664
+ case ".":
665
+ i++;
666
+ return charNode(any(), false);
667
+ // `.` matches any char except line terminators
668
+ case "\\":
669
+ return parseEscape();
670
+ case "^":
671
+ case "$":
672
+ i++;
673
+ return EMPTY2;
674
+ // anchors are zero-width
675
+ case "{":
676
+ i++;
677
+ return charNode(fromRange(123, 123), false);
678
+ // a `{` with nothing to quantify is a literal brace
679
+ default:
680
+ i += ch.length;
681
+ return charNode(fromRange(ch.codePointAt(0), ch.codePointAt(0)), ch === "\n");
606
682
  }
607
- case "Alternative": {
608
- const exprs = node2.expressions;
609
- if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
610
- let fs2 = empty();
611
- let canNL = false;
612
- for (const expr of exprs) {
613
- const r = extractFirstSet(expr);
614
- fs2 = union(fs2, r.firstSet);
615
- canNL = canNL || r.canMatchNewline;
616
- if (!canBeEmpty(expr)) break;
617
- }
618
- return { firstSet: fs2, canMatchNewline: canNL };
683
+ };
684
+ const parseGroup = () => {
685
+ i++;
686
+ let lookaround = false;
687
+ if (src[i] === "?") {
688
+ const c1 = src[i + 1];
689
+ if (c1 === ":") i += 2;
690
+ else if (c1 === "=" || c1 === "!") {
691
+ i += 2;
692
+ lookaround = true;
693
+ } else if (c1 === "<") {
694
+ const c2 = src[i + 2];
695
+ if (c2 === "=" || c2 === "!") {
696
+ i += 3;
697
+ lookaround = true;
698
+ } else {
699
+ const gt = src.indexOf(">", i);
700
+ if (gt === -1) throw BAIL;
701
+ i = gt + 1;
702
+ }
703
+ } else throw BAIL;
619
704
  }
620
- case "Char": {
621
- const kind = node2.kind;
622
- if (kind === "simple") {
623
- const code = node2.codePoint ?? node2.value.codePointAt(0) ?? 0;
624
- return { firstSet: fromRange(code, code), canMatchNewline: code === 10 };
625
- }
626
- if (kind === "meta") {
627
- const val = node2.value;
628
- if (val === ".") return { firstSet: any(), canMatchNewline: false };
629
- if (val === "d") return { firstSet: fromRange(48, 57), canMatchNewline: false };
630
- if (val === "D") return { firstSet: any(), canMatchNewline: true };
631
- if (val === "w") return {
632
- firstSet: union(
633
- union(fromRange(48, 57), fromRange(65, 90)),
634
- union(fromRange(97, 122), fromRange(95, 95))
635
- ),
636
- canMatchNewline: false
637
- };
638
- if (val === "s") return { firstSet: fromRange(9, 13), canMatchNewline: true };
639
- if (val === "n") return { firstSet: fromRange(10, 10), canMatchNewline: true };
705
+ const inner = parseAlt();
706
+ if (src[i] !== ")") throw BAIL;
707
+ i++;
708
+ return lookaround ? EMPTY2 : inner;
709
+ };
710
+ const parseClass = () => {
711
+ let k = i + 1;
712
+ const negated = src[k] === "^";
713
+ if (negated) k++;
714
+ let body = "";
715
+ while (k < src.length && src[k] !== "]") {
716
+ if (src[k] === "\\") {
717
+ body += src[k] + (src[k + 1] ?? "");
718
+ k += 2;
719
+ } else {
720
+ body += src[k];
721
+ k++;
640
722
  }
641
- return { firstSet: any(), canMatchNewline: true };
642
723
  }
643
- case "CharacterClass": {
644
- const expressions = node2.expressions;
724
+ if (src[k] !== "]") throw BAIL;
725
+ i = k + 1;
726
+ if (negated) return charNode(any(), true);
727
+ const ranges = parseClassRanges(body);
728
+ if (!ranges) return charNode(any(), true);
729
+ return charNode(rangesToSet(ranges), rangesHaveNewline(ranges));
730
+ };
731
+ const parseEscape = () => {
732
+ const e = src[i + 1];
733
+ if (e === void 0) throw BAIL;
734
+ if (e === "u" && /^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) {
735
+ const cp = Number.parseInt(src.slice(i + 2, i + 6), 16);
736
+ i += 6;
737
+ return charNode(fromRange(cp, cp), cp === 10);
738
+ }
739
+ if (e === "x" && /^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) {
740
+ const cp = Number.parseInt(src.slice(i + 2, i + 4), 16);
741
+ i += 4;
742
+ return charNode(fromRange(cp, cp), cp === 10);
743
+ }
744
+ i += 2;
745
+ switch (e) {
746
+ case "d":
747
+ return charNode(rangesToSet(shorthandRanges("d")), false);
748
+ case "w":
749
+ return charNode(rangesToSet(shorthandRanges("w")), false);
750
+ case "s":
751
+ return charNode(rangesToSet(shorthandRanges("s")), true);
752
+ case "D":
753
+ case "W":
754
+ case "S":
755
+ return charNode(any(), true);
756
+ // negated shorthand → widen
757
+ case "b":
758
+ case "B":
759
+ return EMPTY2;
760
+ // word boundary: zero-width
761
+ case "n":
762
+ return charNode(fromRange(10, 10), true);
763
+ case "r":
764
+ return charNode(fromRange(13, 13), false);
765
+ case "t":
766
+ return charNode(fromRange(9, 9), false);
767
+ case "f":
768
+ return charNode(fromRange(12, 12), false);
769
+ case "v":
770
+ return charNode(fromRange(11, 11), false);
771
+ case "0":
772
+ return charNode(fromRange(0, 0), false);
773
+ default:
774
+ if (e >= "1" && e <= "9") return ANY;
775
+ return charNode(fromRange(e.codePointAt(0), e.codePointAt(0)), e === "\n");
776
+ }
777
+ };
778
+ const node2 = parseAlt();
779
+ if (i < src.length) throw BAIL;
780
+ return node2;
781
+ }
782
+ function nullable(n) {
783
+ switch (n.t) {
784
+ case "char":
785
+ return false;
786
+ case "any":
787
+ return true;
788
+ case "empty":
789
+ return true;
790
+ case "seq":
791
+ return n.parts.every(nullable);
792
+ case "alt":
793
+ return n.arms.some(nullable);
794
+ case "rep":
795
+ return n.min === 0 || nullable(n.node);
796
+ }
797
+ }
798
+ function firstSet(n) {
799
+ switch (n.t) {
800
+ case "char":
801
+ return n.set;
802
+ case "any":
803
+ return any();
804
+ case "empty":
805
+ return empty();
806
+ case "rep":
807
+ return firstSet(n.node);
808
+ case "alt": {
645
809
  let fs2 = empty();
646
- let canNL = false;
647
- for (const expr of expressions) {
648
- if (expr.type === "ClassRange") {
649
- const from = expr.from.codePoint;
650
- const to = expr.to.codePoint;
651
- fs2 = union(fs2, fromRange(from, to));
652
- if (from <= 10 && 10 <= to) canNL = true;
653
- } else if (expr.type === "Char") {
654
- const r = extractFirstSet(expr);
655
- fs2 = union(fs2, r.firstSet);
656
- canNL = canNL || r.canMatchNewline;
657
- }
658
- }
659
- if (node2.negative) return { firstSet: any(), canMatchNewline: true };
660
- return { firstSet: fs2, canMatchNewline: canNL };
810
+ for (const arm of n.arms) fs2 = union(fs2, firstSet(arm));
811
+ return fs2;
661
812
  }
662
- case "Repetition": {
663
- return extractFirstSet(node2.expression);
813
+ case "seq": {
814
+ let fs2 = empty();
815
+ for (const part of n.parts) {
816
+ fs2 = union(fs2, firstSet(part));
817
+ if (!nullable(part)) break;
818
+ }
819
+ return fs2;
664
820
  }
665
- case "Group": {
666
- return extractFirstSet(node2.expression);
821
+ }
822
+ }
823
+ function firstCanBeNewline(n) {
824
+ switch (n.t) {
825
+ case "char":
826
+ return n.nl;
827
+ case "any":
828
+ return true;
829
+ case "empty":
830
+ return false;
831
+ case "rep":
832
+ return firstCanBeNewline(n.node);
833
+ case "alt":
834
+ return n.arms.some(firstCanBeNewline);
835
+ case "seq": {
836
+ for (const part of n.parts) {
837
+ if (firstCanBeNewline(part)) return true;
838
+ if (!nullable(part)) break;
839
+ }
840
+ return false;
667
841
  }
668
- default:
669
- return { firstSet: any(), canMatchNewline: true };
670
842
  }
671
843
  }
844
+ function firstSetFromRegex(source) {
845
+ let ast;
846
+ try {
847
+ ast = parseRegex(source);
848
+ } catch {
849
+ return { firstSet: any(), canMatchNewline: true };
850
+ }
851
+ if (nullable(ast)) return { firstSet: any(), canMatchNewline: true };
852
+ return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
853
+ }
672
854
 
673
855
  // src/combinators/keywords.ts
674
856
  function word(str, boundary = "_0-9A-Za-z") {
@@ -683,19 +865,19 @@ function keywords(words, opts = {}) {
683
865
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
684
866
  const flags = opts.caseInsensitive ? "iuy" : "uy";
685
867
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
686
- let firstSet = empty();
868
+ let firstSet2 = empty();
687
869
  for (const w of sorted) {
688
870
  if (w.length === 0) continue;
689
871
  const cp = w.codePointAt(0);
690
- firstSet = union(firstSet, fromChar(cp));
872
+ firstSet2 = union(firstSet2, fromChar(cp));
691
873
  if (opts.caseInsensitive) {
692
874
  const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
693
875
  const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
694
- if (u !== void 0) firstSet = union(firstSet, fromChar(u));
695
- if (l !== void 0) firstSet = union(firstSet, fromChar(l));
876
+ if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
877
+ if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
696
878
  }
697
879
  }
698
- const meta = { firstSet, canMatchNewline: false, isTrivia: false };
880
+ const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
699
881
  return {
700
882
  _tag: "keywords",
701
883
  _meta: meta,
@@ -1089,10 +1271,10 @@ function needsDeferredTriviaCommit(ctx) {
1089
1271
  }
1090
1272
  function saveTriviaMark(ctx) {
1091
1273
  const m = saveCstMark(ctx);
1092
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1274
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1093
1275
  }
1094
1276
  function rollbackTrivia(ctx, mark) {
1095
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1277
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1096
1278
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1097
1279
  }
1098
1280
  function scanWithLabels(input, cur, ctx) {
@@ -1476,6 +1658,9 @@ function markUnusedValues(root) {
1476
1658
  case "transform":
1477
1659
  visit(def.parser, true);
1478
1660
  return;
1661
+ case "field":
1662
+ visit(def.parser, true);
1663
+ return;
1479
1664
  case "skip":
1480
1665
  visit(def.main, consumed);
1481
1666
  visit(def.skipped, false);
@@ -1527,6 +1712,11 @@ function markUnusedValues(root) {
1527
1712
  }
1528
1713
 
1529
1714
  // src/combinators/parser.ts
1715
+ function tagRule(r, key) {
1716
+ ;
1717
+ r._ruleName = key;
1718
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1719
+ }
1530
1720
  function rules(factory) {
1531
1721
  const cache = {};
1532
1722
  const proxy = new Proxy(cache, {
@@ -1535,7 +1725,7 @@ function rules(factory) {
1535
1725
  const record = target;
1536
1726
  if (!(key in record)) {
1537
1727
  const r = ref();
1538
- r._ruleName = key;
1728
+ tagRule(r, key);
1539
1729
  record[key] = r;
1540
1730
  }
1541
1731
  return record[key];
@@ -1546,12 +1736,12 @@ function rules(factory) {
1546
1736
  const placeholder = cache[key];
1547
1737
  const parser2 = definitions[key];
1548
1738
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1549
- ;
1739
+ tagRule(parser2, key);
1550
1740
  placeholder.define(parser2);
1551
1741
  placeholder._meta.firstSet = parser2._meta.firstSet;
1552
1742
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1553
1743
  } else {
1554
- ;
1744
+ tagRule(parser2, key);
1555
1745
  cache[key] = parser2;
1556
1746
  }
1557
1747
  }
@@ -1608,44 +1798,130 @@ function buildReadsTrivia(def) {
1608
1798
  const src = def.buildSrc ?? def.build.toString();
1609
1799
  const arity = confirmedBuildArity(src);
1610
1800
  if (arity === null) return true;
1611
- return arity >= 4;
1801
+ return arity >= 5;
1612
1802
  }
1613
1803
  function buildReadsState(def) {
1614
1804
  if (!def.build) return true;
1615
1805
  const src = def.buildSrc ?? def.build.toString();
1616
1806
  const arity = confirmedBuildArity(src);
1617
1807
  if (arity === null) return true;
1618
- return arity >= 5;
1808
+ return arity >= 6;
1809
+ }
1810
+
1811
+ // src/compiler/fields.ts
1812
+ function buildReadsFields(def) {
1813
+ if (!def.build) return true;
1814
+ const src = def.buildSrc ?? def.build.toString();
1815
+ const arity = confirmedBuildArity(src);
1816
+ if (arity === null) return true;
1817
+ return arity >= 2;
1818
+ }
1819
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1820
+ if (seen.has(p)) return false;
1821
+ seen.add(p);
1822
+ const d = p._def;
1823
+ switch (d.tag) {
1824
+ case "field":
1825
+ return true;
1826
+ case "node":
1827
+ return false;
1828
+ case "lazy": {
1829
+ try {
1830
+ return parserHasOwnFields(d.thunk(), seen);
1831
+ } catch {
1832
+ return false;
1833
+ }
1834
+ }
1835
+ case "sequence":
1836
+ case "choice":
1837
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1838
+ case "sepBy":
1839
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1840
+ case "skip":
1841
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1842
+ case "grammar":
1843
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1844
+ case "scanTo":
1845
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1846
+ case "recover":
1847
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1848
+ case "many":
1849
+ case "oneOrMore":
1850
+ case "optional":
1851
+ case "transform":
1852
+ case "trivia":
1853
+ case "token":
1854
+ case "label":
1855
+ case "expect":
1856
+ case "withCtx":
1857
+ case "not":
1858
+ return parserHasOwnFields(d.parser, seen);
1859
+ default:
1860
+ return false;
1861
+ }
1862
+ }
1863
+ function buildFieldMap(captures) {
1864
+ if (!captures?.length) return void 0;
1865
+ const out = {};
1866
+ for (const cap of captures) {
1867
+ const entry = { value: cap.value, span: cap.span };
1868
+ const current = out[cap.name];
1869
+ if (current === void 0) out[cap.name] = entry;
1870
+ else if (Array.isArray(current)) current.push(entry);
1871
+ else out[cap.name] = [current, entry];
1872
+ }
1873
+ return out;
1619
1874
  }
1620
1875
 
1621
1876
  // src/combinators/node.ts
1622
- function collapseChild(child) {
1877
+ function unwrapChild(child) {
1623
1878
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1624
1879
  }
1625
- function node(type, combinator, build, opts) {
1880
+ function isCstChild(value) {
1881
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1882
+ }
1883
+ function missingInferredType() {
1884
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1885
+ }
1886
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1887
+ const hasExplicitType = typeof typeOrCombinator === "string";
1888
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1889
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1890
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1891
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1892
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1626
1893
  const meta = {
1627
1894
  firstSet: combinator._meta.firstSet,
1628
1895
  canMatchNewline: combinator._meta.canMatchNewline,
1629
1896
  isTrivia: false
1630
1897
  };
1898
+ const unwrap = opts?.unwrap === true;
1631
1899
  const collapse = opts?.collapse === true;
1632
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1900
+ if (unwrap && collapse) {
1901
+ throw new Error("node() options cannot set both unwrap and collapse");
1902
+ }
1903
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1633
1904
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1634
1905
  const clonesState = build ? buildReadsState(def) : true;
1906
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1635
1907
  return {
1636
1908
  _tag: "node",
1637
1909
  _meta: meta,
1638
1910
  _def: def,
1639
1911
  parse(input, pos, ctx) {
1640
1912
  const saved = beginCstNodeCapture(ctx);
1913
+ const savedFields = ctx._fields;
1914
+ ctx._fields = capturesFields ? [] : void 0;
1641
1915
  if (!capturesTrivia) ctx.captureTrivia = false;
1642
1916
  const r = combinator.parse(input, pos, ctx);
1917
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1918
+ ctx._fields = savedFields;
1643
1919
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1644
1920
  if (!r.ok) return r;
1645
1921
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1646
- 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 };
1647
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1648
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1922
+ const nodeType = def.type ?? missingInferredType();
1923
+ 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 };
1924
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1649
1925
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1650
1926
  pushCstChild(ctx, built, rawEntry);
1651
1927
  }
@@ -1706,6 +1982,19 @@ function label(name, combinator) {
1706
1982
  }
1707
1983
  };
1708
1984
  }
1985
+ function field(name, combinator) {
1986
+ return {
1987
+ _tag: combinator._tag,
1988
+ _meta: combinator._meta,
1989
+ _def: { tag: "field", name, parser: combinator },
1990
+ parse(input, pos, ctx) {
1991
+ const result = combinator.parse(input, pos, ctx);
1992
+ if (!result.ok) return result;
1993
+ ctx._fields?.push({ name, value: result.value, span: result.span });
1994
+ return result;
1995
+ }
1996
+ };
1997
+ }
1709
1998
 
1710
1999
  // src/compiler/line-index.ts
1711
2000
  function buildLineIndex(input) {
@@ -1892,77 +2181,11 @@ function expect(combinator, label2) {
1892
2181
 
1893
2182
  // src/compiler/scannable-run.ts
1894
2183
  var BACKSLASH = 92;
1895
- var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1896
2184
  var META = new Set("()[]{}*+?|^$.".split(""));
1897
- var SPACE_RANGES2 = [
1898
- [9, 13],
1899
- [32, 32],
1900
- [160, 160],
1901
- [5760, 5760],
1902
- [8192, 8202],
1903
- [8232, 8232],
1904
- [8233, 8233],
1905
- [8239, 8239],
1906
- [8287, 8287],
1907
- [12288, 12288],
1908
- [65279, 65279]
1909
- ];
1910
- function shorthandRanges2(ch) {
1911
- if (ch === "d") return [[48, 57]];
1912
- if (ch === "s") return SPACE_RANGES2;
1913
- return [[48, 57], [65, 90], [97, 122], [95, 95]];
1914
- }
1915
- function readUnicodeEscape(body, i) {
1916
- if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1917
- const hex = body.slice(i + 2, i + 6);
1918
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1919
- return { cp: Number.parseInt(hex, 16), next: i + 6 };
1920
- }
1921
- function parseClassRanges(body) {
1922
- const ranges = [];
1923
- let i = 0;
1924
- const readAtom = () => {
1925
- const ch = body[i];
1926
- if (ch === void 0) return null;
1927
- if (ch === "\\") {
1928
- const uni = readUnicodeEscape(body, i);
1929
- if (uni) {
1930
- i = uni.next;
1931
- return { cp: uni.cp };
1932
- }
1933
- const e = body[i + 1];
1934
- if (e === void 0) return null;
1935
- i += 2;
1936
- if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
1937
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1938
- if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1939
- return { cp: e.codePointAt(0) };
1940
- }
1941
- i += ch.length;
1942
- return { cp: ch.codePointAt(0) };
1943
- };
1944
- while (i < body.length) {
1945
- const lo = readAtom();
1946
- if (lo === null) return null;
1947
- if ("set" in lo) {
1948
- ranges.push(...lo.set);
1949
- continue;
1950
- }
1951
- if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1952
- i += 1;
1953
- const hi = readAtom();
1954
- if (hi === null || "set" in hi) return null;
1955
- ranges.push([lo.cp, hi.cp]);
1956
- } else {
1957
- ranges.push([lo.cp, lo.cp]);
1958
- }
1959
- }
1960
- return ranges.length ? ranges : null;
1961
- }
1962
2185
  function classToRanges(cls) {
1963
- if (cls === "\\d") return shorthandRanges2("d");
1964
- if (cls === "\\w") return shorthandRanges2("w");
1965
- if (cls === "\\s") return shorthandRanges2("s");
2186
+ if (cls === "\\d") return shorthandRanges("d");
2187
+ if (cls === "\\w") return shorthandRanges("w");
2188
+ if (cls === "\\s") return shorthandRanges("s");
1966
2189
  const body = cls.slice(1, -1);
1967
2190
  if (body.startsWith("^")) return null;
1968
2191
  return parseClassRanges(body);
@@ -1980,7 +2203,7 @@ function literalCodePoints(frag) {
1980
2203
  if (ch === "\\") {
1981
2204
  const e = frag[i + 1];
1982
2205
  if (e === void 0) return null;
1983
- out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
2206
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1984
2207
  i += 2;
1985
2208
  continue;
1986
2209
  }
@@ -2073,7 +2296,7 @@ function parseSeqParts(source) {
2073
2296
  if (e === "d" || e === "w" || e === "s") {
2074
2297
  flush();
2075
2298
  i += 2;
2076
- i += runFrom(shorthandRanges2(e), false, source[i]);
2299
+ i += runFrom(shorthandRanges(e), false, source[i]);
2077
2300
  continue;
2078
2301
  }
2079
2302
  const uni = readUnicodeEscape(source, i);
@@ -2082,8 +2305,8 @@ function parseSeqParts(source) {
2082
2305
  cp2 = uni.cp;
2083
2306
  i = uni.next;
2084
2307
  } else if (e === void 0) return null;
2085
- else if (e in CLASS_ESCAPES2) {
2086
- cp2 = CLASS_ESCAPES2[e];
2308
+ else if (e in CLASS_ESCAPES) {
2309
+ cp2 = CLASS_ESCAPES[e];
2087
2310
  i += 2;
2088
2311
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
2089
2312
  else {
@@ -2190,7 +2413,7 @@ function rangesSubset(sub, sup) {
2190
2413
  }
2191
2414
  function parseClassOperand(body) {
2192
2415
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2193
- return { ranges: shorthandRanges2(body[1]), negated: false };
2416
+ return { ranges: shorthandRanges(body[1]), negated: false };
2194
2417
  }
2195
2418
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2196
2419
  let inner = body.slice(1, -1);
@@ -2623,7 +2846,7 @@ function parseScanShapeCore(source) {
2623
2846
  }
2624
2847
  m = /^\\([dws])([+*])$/.exec(source);
2625
2848
  if (m) {
2626
- return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2849
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2627
2850
  }
2628
2851
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2629
2852
  if (m) {
@@ -3165,21 +3388,22 @@ function emitScannableTerminal(shape, c) {
3165
3388
  }
3166
3389
 
3167
3390
  // src/compiler/inline-build.ts
3168
- 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*$/;
3391
+ 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*$/;
3169
3392
  function escapeRegExp(s) {
3170
3393
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3171
3394
  }
3172
3395
  function looseMkBuildRe(type) {
3173
3396
  return new RegExp(
3174
- 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*$`
3397
+ 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*$`
3175
3398
  );
3176
3399
  }
3177
3400
  function analyzeMkInlineBuild(def) {
3178
3401
  if (!def.build) return null;
3402
+ if (def.type === void 0) return null;
3179
3403
  const src = (def.buildSrc ?? def.build.toString()).trim();
3180
3404
  const strict = src.match(MK_BUILD_RE);
3181
3405
  if (strict) {
3182
- const mkType = strict[2];
3406
+ const mkType = strict[6];
3183
3407
  return mkType === def.type ? def.type : null;
3184
3408
  }
3185
3409
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3514,8 +3738,9 @@ function planDisjointDispatch(parsers) {
3514
3738
  }
3515
3739
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3516
3740
  }
3517
- function captureRestoreBody(mL, mR, mTl, mLg) {
3518
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3741
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3742
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3743
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3519
3744
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3520
3745
  }
3521
3746
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3563,6 +3788,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3563
3788
  // Delegating wrappers: defer to the wrapped parser.
3564
3789
  case "transform":
3565
3790
  case "label":
3791
+ case "field":
3566
3792
  case "expect":
3567
3793
  case "withCtx":
3568
3794
  case "grammar":
@@ -3609,6 +3835,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3609
3835
  case "optional":
3610
3836
  case "transform":
3611
3837
  case "label":
3838
+ case "field":
3612
3839
  case "expect":
3613
3840
  case "withCtx":
3614
3841
  case "grammar":
@@ -3646,22 +3873,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3646
3873
  ctx.recordFail = savedRecord;
3647
3874
  const ind0 = ind(ctx);
3648
3875
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3876
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3649
3877
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3650
3878
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3651
3879
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3880
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3652
3881
  const stmts = [
3653
3882
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3654
3883
  ...mL ? [
3655
3884
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3656
3885
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3657
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3886
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3887
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3658
3888
  ] : [],
3659
3889
  `${ind0}${lbl}: {`,
3660
3890
  ...r.stmts,
3661
3891
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3662
3892
  `${ind0}}`,
3663
3893
  ...mL ? [
3664
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3894
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3665
3895
  ] : []
3666
3896
  ];
3667
3897
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3888,6 +4118,7 @@ function failsAtStart(p) {
3888
4118
  return true;
3889
4119
  case "transform":
3890
4120
  case "label":
4121
+ case "field":
3891
4122
  return failsAtStart(d.parser);
3892
4123
  default:
3893
4124
  return false;
@@ -3907,6 +4138,7 @@ function canMatchEmptyAtStart(p) {
3907
4138
  return true;
3908
4139
  case "transform":
3909
4140
  case "label":
4141
+ case "field":
3910
4142
  return canMatchEmptyAtStart(d.parser);
3911
4143
  case "literal":
3912
4144
  return d.value.length === 0;
@@ -4089,11 +4321,13 @@ function emitFirstMatch(def, ctx, pos) {
4089
4321
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
4090
4322
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
4091
4323
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4324
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
4092
4325
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
4093
4326
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
4094
4327
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
4095
4328
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
4096
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4329
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4330
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
4097
4331
  const failSlot = atStart ? staticFx : "_ctx._fx";
4098
4332
  stmts.push(`${ind0}if (${skipCond}) {`);
4099
4333
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -4103,7 +4337,8 @@ function emitFirstMatch(def, ctx, pos) {
4103
4337
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
4104
4338
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
4105
4339
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
4106
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4340
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4341
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
4107
4342
  );
4108
4343
  }
4109
4344
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4496,6 +4731,9 @@ function emitToken(def, ctx, pos) {
4496
4731
  };
4497
4732
  }
4498
4733
  function emitNode(def, ctx, pos) {
4734
+ if (def.type === void 0) {
4735
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4736
+ }
4499
4737
  const structural = def.build === void 0;
4500
4738
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4501
4739
  let buildIdx = null;
@@ -4507,25 +4745,36 @@ function emitNode(def, ctx, pos) {
4507
4745
  const i = ind(ctx);
4508
4746
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4509
4747
  const clonesState = !structural && buildReadsState(def);
4748
+ const hasFields = parserHasOwnFields(def.parser);
4749
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4510
4750
  const chV = v(ctx, "_ch");
4511
4751
  const rawV = v(ctx, "_raw");
4512
4752
  const capTLv = structural ? v(ctx, "_ctl") : null;
4513
4753
  const capSTv = structural ? v(ctx, "_cst") : null;
4754
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4514
4755
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4515
4756
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4516
4757
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4517
4758
  if (structural) ctx.needsHostReads = true;
4518
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4759
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4760
+ 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)` : ""}
4519
4761
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4520
4762
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4763
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4764
+ const sf = hasFields ? v(ctx, "_sf") : null;
4765
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4766
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4521
4767
  const stmts = [
4522
4768
  allocStmt,
4523
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4524
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4769
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4770
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4525
4771
  ];
4526
4772
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4527
4773
  stmts.push(...innerStmts);
4528
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4774
+ if (sf && fArr) {
4775
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4776
+ }
4777
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4529
4778
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4530
4779
  let stV = "undefined";
4531
4780
  if (structural) {
@@ -4535,14 +4784,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4535
4784
  stV = v(ctx, "_nst");
4536
4785
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4537
4786
  }
4787
+ if (sf && fArr) {
4788
+ const fe = v(ctx, "_fe");
4789
+ const cur = v(ctx, "_fc");
4790
+ stmts.push(
4791
+ `${i}let ${fObj} = undefined`,
4792
+ `${i}if (${fArr} && ${fArr}.length) {`,
4793
+ `${i} ${fObj} = {}`,
4794
+ `${i} for (const ${fe} of ${fArr}) {`,
4795
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4796
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4797
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4798
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4799
+ `${i} }`,
4800
+ `${i}}`
4801
+ );
4802
+ }
4538
4803
  const ndV = v(ctx, "_nd");
4539
- 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})`;
4540
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4541
- 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;
4804
+ 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})`;
4805
+ 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;
4806
+ 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;
4807
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4808
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4809
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4542
4810
  stmts.push(
4543
4811
  `${i}const ${ndV} = ${finalExpr}`,
4544
4812
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4545
- `${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} } })`
4813
+ `${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} } })`
4546
4814
  );
4547
4815
  return { stmts, valueVar: ndV, endVar };
4548
4816
  }
@@ -4757,6 +5025,17 @@ function emitDispatch(p, ctx, pos) {
4757
5025
  endVar: inner.endVar
4758
5026
  };
4759
5027
  }
5028
+ case "field": {
5029
+ const inner = emit(def.parser, ctx, pos);
5030
+ return {
5031
+ stmts: [
5032
+ ...inner.stmts,
5033
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
5034
+ ],
5035
+ valueVar: inner.valueVar,
5036
+ endVar: inner.endVar
5037
+ };
5038
+ }
4760
5039
  case "grammar": {
4761
5040
  const savedTrivia = ctx.activeTrivia;
4762
5041
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4858,6 +5137,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4858
5137
  case "trivia":
4859
5138
  case "token":
4860
5139
  case "label":
5140
+ case "field":
4861
5141
  case "optional":
4862
5142
  case "many":
4863
5143
  case "oneOrMore":
@@ -4894,6 +5174,7 @@ function childrenOf(def) {
4894
5174
  case "trivia":
4895
5175
  case "token":
4896
5176
  case "label":
5177
+ case "field":
4897
5178
  case "grammar":
4898
5179
  case "not":
4899
5180
  case "node":
@@ -5135,6 +5416,11 @@ function assertRuleName(name) {
5135
5416
  );
5136
5417
  }
5137
5418
  }
5419
+ function publicRuleWrapperSource(rule, fnSource) {
5420
+ const labels = rule._meta.triviaKindLabels;
5421
+ if (labels === void 0) return fnSource;
5422
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5423
+ }
5138
5424
  function compileRuleMap(ruleMap) {
5139
5425
  for (const [, rule] of ruleMap) markUnusedValues(rule);
5140
5426
  const ctx = {
@@ -5167,7 +5453,7 @@ function compileRuleMap(ruleMap) {
5167
5453
  ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
5168
5454
  }
5169
5455
  ctx.ruleNames = ruleNames;
5170
- const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
5456
+ const perEntry = ruleMap.map(([key, rule]) => ({ key, rule, r: emit(rule, ctx, "_pos") }));
5171
5457
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
5172
5458
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
5173
5459
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -5197,7 +5483,10 @@ function compileRuleMap(ruleMap) {
5197
5483
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5198
5484
  `}`
5199
5485
  ].join("\n");
5200
- const objBody = perEntry.map(({ key, r }) => ` ${JSON.stringify(key)}: ${entryFnText(r).split("\n").join("\n ")}`).join(",\n");
5486
+ const objBody = perEntry.map(({ key, rule, r }) => {
5487
+ const src = publicRuleWrapperSource(rule, entryFnText(r));
5488
+ return ` ${JSON.stringify(key)}: ${src.split("\n").join("\n ")}`;
5489
+ }).join(",\n");
5201
5490
  const replacement = [
5202
5491
  `/* @__PURE__ */ (() => {`,
5203
5492
  ...hoistedDecls,
@@ -5310,7 +5599,7 @@ function compileLinkable(ruleMapArg, ns) {
5310
5599
  ctx.recordFail = savedRec;
5311
5600
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5312
5601
  }
5313
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5602
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5314
5603
  }
5315
5604
  if (ctx.runtimeParsers.length !== 0) return null;
5316
5605
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5333,14 +5622,14 @@ function compileLinkable(ruleMapArg, ns) {
5333
5622
  ...privateFns
5334
5623
  ].filter(Boolean);
5335
5624
  const wrappers = /* @__PURE__ */ new Map();
5336
- for (const { key, r } of perEntry) {
5337
- wrappers.set(key, [
5625
+ for (const { key, rule, r } of perEntry) {
5626
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5338
5627
  `function(input, _pos, _ctx) {`,
5339
5628
  ` let pos = _pos`,
5340
5629
  ...r.stmts,
5341
5630
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5342
5631
  `}`
5343
- ].join("\n"));
5632
+ ].join("\n")));
5344
5633
  }
5345
5634
  const firstSets = /* @__PURE__ */ new Map();
5346
5635
  for (const [key, rule] of ruleMap) {
@@ -5515,6 +5804,7 @@ function childrenOf2(def) {
5515
5804
  case "trivia":
5516
5805
  case "token":
5517
5806
  case "label":
5807
+ case "field":
5518
5808
  case "not":
5519
5809
  case "node":
5520
5810
  case "expect":
@@ -5600,6 +5890,7 @@ function evalRuleMapIR(ir) {
5600
5890
  "skip",
5601
5891
  "trivia",
5602
5892
  "label",
5893
+ "field",
5603
5894
  "expect",
5604
5895
  "_tf",
5605
5896
  "_nd",
@@ -5626,6 +5917,7 @@ function evalRuleMapIR(ir) {
5626
5917
  skip,
5627
5918
  trivia,
5628
5919
  label,
5920
+ field,
5629
5921
  expect,
5630
5922
  _tf,
5631
5923
  _nd
@@ -5759,6 +6051,8 @@ ${body}
5759
6051
  return `token(${kid(def.parser)})`;
5760
6052
  case "label":
5761
6053
  return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
6054
+ case "field":
6055
+ return `field(${JSON.stringify(def.name)}, ${kid(def.parser)})`;
5762
6056
  case "expect":
5763
6057
  return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
5764
6058
  case "skip":
@@ -5771,7 +6065,11 @@ ${body}
5771
6065
  }
5772
6066
  case "node": {
5773
6067
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
5774
- const opts = def.collapse ? `, { collapse: true }` : "";
6068
+ const opts = def.unwrap || def.collapse ? `, { ${def.unwrap ? "unwrap: true" : "collapse: true"} }` : "";
6069
+ if (def.type === void 0) {
6070
+ if (def.buildSrc !== void 0) throw new Unserializable("inferred node build without inferred type");
6071
+ return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
6072
+ }
5775
6073
  if (def.buildSrc !== void 0) return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts})`;
5776
6074
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
5777
6075
  }
@@ -5798,8 +6096,9 @@ function fusedBody(pieces) {
5798
6096
  }
5799
6097
  }
5800
6098
  const contributing = new Set(winner.values());
5801
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5802
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
6099
+ const contributingPieces = [...contributing];
6100
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
6101
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5803
6102
  const lines = [
5804
6103
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5805
6104
  "const _pfFail = {}",
@@ -5807,7 +6106,7 @@ function fusedBody(pieces) {
5807
6106
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5808
6107
  ...needsHostReads ? [HOST_READS_DECL] : [],
5809
6108
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5810
- ...[...contributing].flatMap((p) => p.prelude),
6109
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5811
6110
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5812
6111
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5813
6112
  ];
@@ -5830,7 +6129,7 @@ function fusedBody(pieces) {
5830
6129
  }
5831
6130
  );
5832
6131
  const env = {};
5833
- for (const p of contributing) {
6132
+ for (const p of contributingPieces) {
5834
6133
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5835
6134
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5836
6135
  }
@@ -5878,6 +6177,7 @@ var SUPPORTED = {
5878
6177
  sepBy: (...a) => sepBy(a[0], a[1]),
5879
6178
  trivia: (...a) => trivia(a[0]),
5880
6179
  label: (...a) => label(a[0], a[1]),
6180
+ field: (...a) => field(a[0], a[1]),
5881
6181
  noTrivia: (...a) => noTrivia(a[0]),
5882
6182
  token: (...a) => token(a[0]),
5883
6183
  expect: (...a) => expect(a[0], a[1])
@@ -5898,19 +6198,20 @@ function scopeGet(scope, name, mfs) {
5898
6198
  if (isCombinator(entry)) return entry;
5899
6199
  return null;
5900
6200
  }
5901
- function staticCollapseOption(expr) {
5902
- if (expr.type !== "ObjectExpression") return false;
6201
+ function staticNodeOptions(expr) {
6202
+ if (expr.type !== "ObjectExpression") return void 0;
6203
+ const opts = {};
5903
6204
  for (const prop of expr.properties) {
5904
6205
  const p = prop;
5905
6206
  if (p.computed) continue;
5906
6207
  const key = p.key;
5907
6208
  const name = key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
5908
- if (name === "collapse") {
6209
+ if (name === "unwrap" || name === "collapse") {
5909
6210
  const val = p.value;
5910
- return (val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true;
6211
+ if ((val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true) opts[name] = true;
5911
6212
  }
5912
6213
  }
5913
- return false;
6214
+ return opts.unwrap || opts.collapse ? opts : void 0;
5914
6215
  }
5915
6216
  function exprToCombi(node2, scope, code, mfs) {
5916
6217
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -5960,18 +6261,22 @@ function exprToCombi(node2, scope, code, mfs) {
5960
6261
  }
5961
6262
  }
5962
6263
  if (callee.name === "node" && code !== void 0) {
5963
- const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
5964
- if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
5965
- const typeVal = anyValue(typeArg, scope, code);
5966
- if (typeof typeVal !== "string") return null;
6264
+ const [firstArg, secondArg, thirdArg, fourthArg] = node2.arguments;
6265
+ if (!firstArg || firstArg.type === "SpreadElement") return null;
6266
+ const firstVal = anyValue(firstArg, scope, code, mfs);
6267
+ const explicitType = typeof firstVal === "string" ? firstVal : void 0;
6268
+ const parserArg = explicitType !== void 0 ? secondArg : firstArg;
6269
+ const buildArg = explicitType !== void 0 ? thirdArg : secondArg;
6270
+ const optsArg = explicitType !== void 0 ? fourthArg : thirdArg;
6271
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
5967
6272
  const inner = anyValue(parserArg, scope, code, mfs);
5968
6273
  if (!isCombinator(inner)) return null;
5969
6274
  const be = buildArg;
5970
6275
  const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
5971
6276
  const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
5972
- const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
6277
+ const opts = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticNodeOptions(optsArg) : void 0;
5973
6278
  try {
5974
- const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
6279
+ const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
5975
6280
  if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
5976
6281
  return combi;
5977
6282
  } catch {
@@ -6237,7 +6542,9 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
6237
6542
  if (!keys) return null;
6238
6543
  const ruleRefs = /* @__PURE__ */ new Map();
6239
6544
  const tagRef = (r, name) => {
6545
+ ;
6240
6546
  r._ruleName = name;
6547
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
6241
6548
  };
6242
6549
  for (const key of keys) {
6243
6550
  if (!ruleRefs.has(key)) {
@@ -6269,6 +6576,7 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
6269
6576
  for (const [key, e] of finalByKey) {
6270
6577
  const val = anyValue(e.value, e.scope, e.code, mapFnSources);
6271
6578
  if (!isCombinator(val)) return null;
6579
+ tagRef(val, key);
6272
6580
  ruleRefs.get(key).define(val);
6273
6581
  }
6274
6582
  return ruleRefs;