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
@@ -182,6 +182,13 @@ function ref() {
182
182
  define(p) {
183
183
  if (resolved) throw new Error("ref<T>() already defined");
184
184
  resolved = p;
185
+ meta.firstSet = p._meta.firstSet;
186
+ meta.canMatchNewline = p._meta.canMatchNewline;
187
+ meta.isTrivia = p._meta.isTrivia;
188
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
189
+ else delete meta.triviaKindLabels;
190
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
191
+ else delete meta.disjoint;
185
192
  }
186
193
  };
187
194
  return slot;
@@ -289,7 +296,8 @@ function saveCstMark(ctx) {
289
296
  return {
290
297
  raw: cstRawLen(ctx),
291
298
  tlog: cstTlLen(ctx),
292
- leaves: cstLeavesLen(ctx)
299
+ leaves: cstLeavesLen(ctx),
300
+ fields: ctx._fields?.length ?? 0
293
301
  };
294
302
  }
295
303
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -313,11 +321,13 @@ function rollbackCstCapture(ctx, mark) {
313
321
  if (mark.tlog === 0) b.tl = void 0;
314
322
  else b.tl.length = mark.tlog;
315
323
  }
324
+ if (ctx._fields) ctx._fields.length = mark.fields;
316
325
  return;
317
326
  }
318
327
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
319
328
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
320
329
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
330
+ if (ctx._fields) ctx._fields.length = mark.fields;
321
331
  }
322
332
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
323
333
  const insertIdx = cstRawLen(ctx);
@@ -361,9 +371,9 @@ function asciiFoldEq(a, b) {
361
371
  }
362
372
  function literal(value, opts = {}) {
363
373
  const caseInsensitive = opts.caseInsensitive ?? false;
364
- const firstSet = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
374
+ const firstSet2 = value.length > 0 ? fromChar(value.codePointAt(0)) : empty();
365
375
  const meta = {
366
- firstSet,
376
+ firstSet: firstSet2,
367
377
  canMatchNewline: value.includes("\n"),
368
378
  isTrivia: false
369
379
  };
@@ -375,7 +385,7 @@ function literal(value, opts = {}) {
375
385
  meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
376
386
  { lo: firstLower, hi: firstLower },
377
387
  { lo: firstUpper, hi: firstUpper }
378
- ] } : firstSet;
388
+ ] } : firstSet2;
379
389
  }
380
390
  return {
381
391
  _tag: "literal",
@@ -398,68 +408,66 @@ function literal(value, opts = {}) {
398
408
  };
399
409
  }
400
410
 
401
- // src/combinators/regex.ts
402
- var firstSetAnalyzer = null;
403
- function registerRegexAnalyzer(analyzer) {
404
- firstSetAnalyzer = analyzer;
405
- }
406
- var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
407
- var SCAN_BAIL_AT = 64;
411
+ // src/regex/classes.ts
412
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
408
413
  var SPACE_RANGES = [
409
414
  [9, 13],
410
415
  [32, 32],
411
416
  [160, 160],
412
417
  [5760, 5760],
413
418
  [8192, 8202],
414
- [8232, 8233],
419
+ [8232, 8232],
420
+ [8233, 8233],
415
421
  [8239, 8239],
416
422
  [8287, 8287],
417
423
  [12288, 12288],
418
424
  [65279, 65279]
419
425
  ];
420
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
421
426
  function shorthandRanges(ch) {
422
427
  if (ch === "d") return [[48, 57]];
423
428
  if (ch === "s") return SPACE_RANGES;
424
429
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
425
430
  }
426
- function inRanges(cp, ranges) {
427
- for (let i = 0; i < ranges.length; i++) {
428
- const [lo, hi] = ranges[i];
429
- if (cp >= lo && cp <= hi) return true;
430
- }
431
- return false;
431
+ function readUnicodeEscape(body, i) {
432
+ if (body[i] !== "\\" || body[i + 1] !== "u") return null;
433
+ const hex = body.slice(i + 2, i + 6);
434
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
435
+ return { cp: Number.parseInt(hex, 16), next: i + 6 };
432
436
  }
433
- function readClassRanges(body) {
434
- if (body.startsWith("^")) return null;
437
+ function parseClassRanges(body) {
435
438
  const ranges = [];
436
439
  let i = 0;
437
440
  const readAtom = () => {
438
441
  const ch = body[i];
439
442
  if (ch === void 0) return null;
440
443
  if (ch === "\\") {
441
- const esc = body[i + 1];
442
- if (esc === void 0) return null;
444
+ const uni = readUnicodeEscape(body, i);
445
+ if (uni) {
446
+ i = uni.next;
447
+ return { cp: uni.cp };
448
+ }
449
+ const e = body[i + 1];
450
+ if (e === void 0) return null;
443
451
  i += 2;
444
- if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
445
- if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
446
- if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
447
- return { cp: esc.charCodeAt(0) };
452
+ if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
453
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
454
+ if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
455
+ return { cp: e.codePointAt(0) };
448
456
  }
449
- i++;
450
- return { cp: ch.charCodeAt(0) };
457
+ i += ch.length;
458
+ return { cp: ch.codePointAt(0) };
451
459
  };
452
460
  while (i < body.length) {
453
461
  const lo = readAtom();
454
- if (!lo) return null;
455
- if ("ranges" in lo) {
456
- ranges.push(...lo.ranges);
462
+ if (lo === null) return null;
463
+ if ("set" in lo) {
464
+ ranges.push(...lo.set);
457
465
  continue;
458
466
  }
459
467
  if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
460
- i++;
468
+ i += 1;
461
469
  const hi = readAtom();
462
- if (!hi || "ranges" in hi) return null;
470
+ if (hi === null || "set" in hi) return null;
463
471
  ranges.push([lo.cp, hi.cp]);
464
472
  } else {
465
473
  ranges.push([lo.cp, lo.cp]);
@@ -467,6 +475,25 @@ function readClassRanges(body) {
467
475
  }
468
476
  return ranges.length ? ranges : null;
469
477
  }
478
+
479
+ // src/combinators/regex.ts
480
+ var firstSetAnalyzer = null;
481
+ function registerRegexAnalyzer(analyzer) {
482
+ firstSetAnalyzer = analyzer;
483
+ }
484
+ var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
485
+ var SCAN_BAIL_AT = 64;
486
+ function inRanges(cp, ranges) {
487
+ for (let i = 0; i < ranges.length; i++) {
488
+ const [lo, hi] = ranges[i];
489
+ if (cp >= lo && cp <= hi) return true;
490
+ }
491
+ return false;
492
+ }
493
+ function readClassRanges(body) {
494
+ if (body.startsWith("^")) return null;
495
+ return parseClassRanges(body);
496
+ }
470
497
  function shortScanner(source, flags) {
471
498
  if (/[imsuvy]/.test(flags)) return null;
472
499
  let ranges = null;
@@ -500,8 +527,8 @@ function regex(pattern, flags = "") {
500
527
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
501
528
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
502
529
  const scan = shortScanner(source, resolvedFlags);
503
- const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
504
- const meta = { firstSet, canMatchNewline, isTrivia: false };
530
+ const { firstSet: firstSet2, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
531
+ const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
505
532
  return {
506
533
  _tag: "regex",
507
534
  _meta: meta,
@@ -529,111 +556,266 @@ function regex(pattern, flags = "") {
529
556
  };
530
557
  }
531
558
 
532
- // src/combinators/regex-analyze.ts
533
- import regexpTree from "regexp-tree";
534
- function firstSetFromRegex(pattern) {
535
- try {
536
- const ast = regexpTree.parse(`/${pattern}/`);
537
- return extractFirstSet(ast.body);
538
- } catch {
539
- return { firstSet: any(), canMatchNewline: true };
540
- }
559
+ // src/regex/first-set.ts
560
+ var ANY = { t: "any" };
561
+ var EMPTY2 = { t: "empty" };
562
+ function charNode(set, nl) {
563
+ return { t: "char", set, nl };
541
564
  }
542
- function canBeEmpty(node2) {
543
- if (!node2) return true;
544
- switch (node2.type) {
545
- case "Repetition": {
546
- const q = node2.quantifier;
547
- return q.kind === "*" || q.kind === "?" || q.kind === "Range" && (q.from ?? 1) === 0;
565
+ function rangesToSet(ranges) {
566
+ let fs2 = empty();
567
+ for (const [lo, hi] of ranges) fs2 = union(fs2, fromRange(lo, hi));
568
+ return fs2;
569
+ }
570
+ function rangesHaveNewline(ranges) {
571
+ return ranges.some(([lo, hi]) => lo <= 10 && 10 <= hi);
572
+ }
573
+ var BAIL = /* @__PURE__ */ Symbol("regex-bail");
574
+ function parseRegex(src) {
575
+ let i = 0;
576
+ const parseAlt = () => {
577
+ const arms = [parseSeq()];
578
+ while (src[i] === "|") {
579
+ i++;
580
+ arms.push(parseSeq());
548
581
  }
549
- case "Alternative": {
550
- const exprs = node2.expressions;
551
- return !exprs || exprs.length === 0 || exprs.every((e) => canBeEmpty(e));
582
+ return arms.length === 1 ? arms[0] : { t: "alt", arms };
583
+ };
584
+ const parseSeq = () => {
585
+ const parts = [];
586
+ while (i < src.length) {
587
+ const ch = src[i];
588
+ if (ch === "|" || ch === ")") break;
589
+ parts.push(parseQuantified());
590
+ }
591
+ if (parts.length === 0) return EMPTY2;
592
+ return parts.length === 1 ? parts[0] : { t: "seq", parts };
593
+ };
594
+ const parseQuantified = () => {
595
+ const atom = parseAtom();
596
+ const ch = src[i];
597
+ let min = null;
598
+ if (ch === "*") {
599
+ min = 0;
600
+ i++;
601
+ } else if (ch === "+") {
602
+ min = 1;
603
+ i++;
604
+ } else if (ch === "?") {
605
+ min = 0;
606
+ i++;
607
+ } else if (ch === "{") {
608
+ min = parseBraceQuantifier();
552
609
  }
553
- case "Group":
554
- return canBeEmpty(node2.expression);
555
- case "Disjunction":
556
- return canBeEmpty(node2.left) || canBeEmpty(node2.right);
557
- default:
558
- return false;
559
- }
560
- }
561
- function extractFirstSet(node2) {
562
- if (!node2) return { firstSet: empty(), canMatchNewline: false };
563
- switch (node2.type) {
564
- case "Disjunction": {
565
- const left = extractFirstSet(node2.left);
566
- const right = extractFirstSet(node2.right);
567
- return {
568
- firstSet: union(left.firstSet, right.firstSet),
569
- canMatchNewline: left.canMatchNewline || right.canMatchNewline
570
- };
610
+ if (min === null) return atom;
611
+ if (src[i] === "?") i++;
612
+ return { t: "rep", node: atom, min };
613
+ };
614
+ const parseBraceQuantifier = () => {
615
+ const close = src.indexOf("}", i);
616
+ if (close === -1) return null;
617
+ const m = /^(\d+)(?:,(\d*))?$/.exec(src.slice(i + 1, close));
618
+ if (!m) return null;
619
+ i = close + 1;
620
+ return Number.parseInt(m[1], 10);
621
+ };
622
+ const parseAtom = () => {
623
+ const ch = src[i];
624
+ switch (ch) {
625
+ case "(":
626
+ return parseGroup();
627
+ case "[":
628
+ return parseClass();
629
+ case ".":
630
+ i++;
631
+ return charNode(any(), false);
632
+ // `.` matches any char except line terminators
633
+ case "\\":
634
+ return parseEscape();
635
+ case "^":
636
+ case "$":
637
+ i++;
638
+ return EMPTY2;
639
+ // anchors are zero-width
640
+ case "{":
641
+ i++;
642
+ return charNode(fromRange(123, 123), false);
643
+ // a `{` with nothing to quantify is a literal brace
644
+ default:
645
+ i += ch.length;
646
+ return charNode(fromRange(ch.codePointAt(0), ch.codePointAt(0)), ch === "\n");
571
647
  }
572
- case "Alternative": {
573
- const exprs = node2.expressions;
574
- if (!exprs || exprs.length === 0) return { firstSet: empty(), canMatchNewline: false };
575
- let fs2 = empty();
576
- let canNL = false;
577
- for (const expr of exprs) {
578
- const r = extractFirstSet(expr);
579
- fs2 = union(fs2, r.firstSet);
580
- canNL = canNL || r.canMatchNewline;
581
- if (!canBeEmpty(expr)) break;
582
- }
583
- return { firstSet: fs2, canMatchNewline: canNL };
648
+ };
649
+ const parseGroup = () => {
650
+ i++;
651
+ let lookaround = false;
652
+ if (src[i] === "?") {
653
+ const c1 = src[i + 1];
654
+ if (c1 === ":") i += 2;
655
+ else if (c1 === "=" || c1 === "!") {
656
+ i += 2;
657
+ lookaround = true;
658
+ } else if (c1 === "<") {
659
+ const c2 = src[i + 2];
660
+ if (c2 === "=" || c2 === "!") {
661
+ i += 3;
662
+ lookaround = true;
663
+ } else {
664
+ const gt = src.indexOf(">", i);
665
+ if (gt === -1) throw BAIL;
666
+ i = gt + 1;
667
+ }
668
+ } else throw BAIL;
584
669
  }
585
- case "Char": {
586
- const kind = node2.kind;
587
- if (kind === "simple") {
588
- const code = node2.codePoint ?? node2.value.codePointAt(0) ?? 0;
589
- return { firstSet: fromRange(code, code), canMatchNewline: code === 10 };
590
- }
591
- if (kind === "meta") {
592
- const val = node2.value;
593
- if (val === ".") return { firstSet: any(), canMatchNewline: false };
594
- if (val === "d") return { firstSet: fromRange(48, 57), canMatchNewline: false };
595
- if (val === "D") return { firstSet: any(), canMatchNewline: true };
596
- if (val === "w") return {
597
- firstSet: union(
598
- union(fromRange(48, 57), fromRange(65, 90)),
599
- union(fromRange(97, 122), fromRange(95, 95))
600
- ),
601
- canMatchNewline: false
602
- };
603
- if (val === "s") return { firstSet: fromRange(9, 13), canMatchNewline: true };
604
- if (val === "n") return { firstSet: fromRange(10, 10), canMatchNewline: true };
670
+ const inner = parseAlt();
671
+ if (src[i] !== ")") throw BAIL;
672
+ i++;
673
+ return lookaround ? EMPTY2 : inner;
674
+ };
675
+ const parseClass = () => {
676
+ let k = i + 1;
677
+ const negated = src[k] === "^";
678
+ if (negated) k++;
679
+ let body = "";
680
+ while (k < src.length && src[k] !== "]") {
681
+ if (src[k] === "\\") {
682
+ body += src[k] + (src[k + 1] ?? "");
683
+ k += 2;
684
+ } else {
685
+ body += src[k];
686
+ k++;
605
687
  }
606
- return { firstSet: any(), canMatchNewline: true };
607
688
  }
608
- case "CharacterClass": {
609
- const expressions = node2.expressions;
689
+ if (src[k] !== "]") throw BAIL;
690
+ i = k + 1;
691
+ if (negated) return charNode(any(), true);
692
+ const ranges = parseClassRanges(body);
693
+ if (!ranges) return charNode(any(), true);
694
+ return charNode(rangesToSet(ranges), rangesHaveNewline(ranges));
695
+ };
696
+ const parseEscape = () => {
697
+ const e = src[i + 1];
698
+ if (e === void 0) throw BAIL;
699
+ if (e === "u" && /^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) {
700
+ const cp = Number.parseInt(src.slice(i + 2, i + 6), 16);
701
+ i += 6;
702
+ return charNode(fromRange(cp, cp), cp === 10);
703
+ }
704
+ if (e === "x" && /^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) {
705
+ const cp = Number.parseInt(src.slice(i + 2, i + 4), 16);
706
+ i += 4;
707
+ return charNode(fromRange(cp, cp), cp === 10);
708
+ }
709
+ i += 2;
710
+ switch (e) {
711
+ case "d":
712
+ return charNode(rangesToSet(shorthandRanges("d")), false);
713
+ case "w":
714
+ return charNode(rangesToSet(shorthandRanges("w")), false);
715
+ case "s":
716
+ return charNode(rangesToSet(shorthandRanges("s")), true);
717
+ case "D":
718
+ case "W":
719
+ case "S":
720
+ return charNode(any(), true);
721
+ // negated shorthand → widen
722
+ case "b":
723
+ case "B":
724
+ return EMPTY2;
725
+ // word boundary: zero-width
726
+ case "n":
727
+ return charNode(fromRange(10, 10), true);
728
+ case "r":
729
+ return charNode(fromRange(13, 13), false);
730
+ case "t":
731
+ return charNode(fromRange(9, 9), false);
732
+ case "f":
733
+ return charNode(fromRange(12, 12), false);
734
+ case "v":
735
+ return charNode(fromRange(11, 11), false);
736
+ case "0":
737
+ return charNode(fromRange(0, 0), false);
738
+ default:
739
+ if (e >= "1" && e <= "9") return ANY;
740
+ return charNode(fromRange(e.codePointAt(0), e.codePointAt(0)), e === "\n");
741
+ }
742
+ };
743
+ const node2 = parseAlt();
744
+ if (i < src.length) throw BAIL;
745
+ return node2;
746
+ }
747
+ function nullable(n) {
748
+ switch (n.t) {
749
+ case "char":
750
+ return false;
751
+ case "any":
752
+ return true;
753
+ case "empty":
754
+ return true;
755
+ case "seq":
756
+ return n.parts.every(nullable);
757
+ case "alt":
758
+ return n.arms.some(nullable);
759
+ case "rep":
760
+ return n.min === 0 || nullable(n.node);
761
+ }
762
+ }
763
+ function firstSet(n) {
764
+ switch (n.t) {
765
+ case "char":
766
+ return n.set;
767
+ case "any":
768
+ return any();
769
+ case "empty":
770
+ return empty();
771
+ case "rep":
772
+ return firstSet(n.node);
773
+ case "alt": {
610
774
  let fs2 = empty();
611
- let canNL = false;
612
- for (const expr of expressions) {
613
- if (expr.type === "ClassRange") {
614
- const from = expr.from.codePoint;
615
- const to = expr.to.codePoint;
616
- fs2 = union(fs2, fromRange(from, to));
617
- if (from <= 10 && 10 <= to) canNL = true;
618
- } else if (expr.type === "Char") {
619
- const r = extractFirstSet(expr);
620
- fs2 = union(fs2, r.firstSet);
621
- canNL = canNL || r.canMatchNewline;
622
- }
623
- }
624
- if (node2.negative) return { firstSet: any(), canMatchNewline: true };
625
- return { firstSet: fs2, canMatchNewline: canNL };
775
+ for (const arm of n.arms) fs2 = union(fs2, firstSet(arm));
776
+ return fs2;
626
777
  }
627
- case "Repetition": {
628
- return extractFirstSet(node2.expression);
778
+ case "seq": {
779
+ let fs2 = empty();
780
+ for (const part of n.parts) {
781
+ fs2 = union(fs2, firstSet(part));
782
+ if (!nullable(part)) break;
783
+ }
784
+ return fs2;
629
785
  }
630
- case "Group": {
631
- return extractFirstSet(node2.expression);
786
+ }
787
+ }
788
+ function firstCanBeNewline(n) {
789
+ switch (n.t) {
790
+ case "char":
791
+ return n.nl;
792
+ case "any":
793
+ return true;
794
+ case "empty":
795
+ return false;
796
+ case "rep":
797
+ return firstCanBeNewline(n.node);
798
+ case "alt":
799
+ return n.arms.some(firstCanBeNewline);
800
+ case "seq": {
801
+ for (const part of n.parts) {
802
+ if (firstCanBeNewline(part)) return true;
803
+ if (!nullable(part)) break;
804
+ }
805
+ return false;
632
806
  }
633
- default:
634
- return { firstSet: any(), canMatchNewline: true };
635
807
  }
636
808
  }
809
+ function firstSetFromRegex(source) {
810
+ let ast;
811
+ try {
812
+ ast = parseRegex(source);
813
+ } catch {
814
+ return { firstSet: any(), canMatchNewline: true };
815
+ }
816
+ if (nullable(ast)) return { firstSet: any(), canMatchNewline: true };
817
+ return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
818
+ }
637
819
 
638
820
  // src/combinators/keywords.ts
639
821
  function word(str, boundary = "_0-9A-Za-z") {
@@ -648,19 +830,19 @@ function keywords(words, opts = {}) {
648
830
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
649
831
  const flags = opts.caseInsensitive ? "iuy" : "uy";
650
832
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
651
- let firstSet = empty();
833
+ let firstSet2 = empty();
652
834
  for (const w of sorted) {
653
835
  if (w.length === 0) continue;
654
836
  const cp = w.codePointAt(0);
655
- firstSet = union(firstSet, fromChar(cp));
837
+ firstSet2 = union(firstSet2, fromChar(cp));
656
838
  if (opts.caseInsensitive) {
657
839
  const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
658
840
  const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
659
- if (u !== void 0) firstSet = union(firstSet, fromChar(u));
660
- if (l !== void 0) firstSet = union(firstSet, fromChar(l));
841
+ if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
842
+ if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
661
843
  }
662
844
  }
663
- const meta = { firstSet, canMatchNewline: false, isTrivia: false };
845
+ const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
664
846
  return {
665
847
  _tag: "keywords",
666
848
  _meta: meta,
@@ -1054,10 +1236,10 @@ function needsDeferredTriviaCommit(ctx) {
1054
1236
  }
1055
1237
  function saveTriviaMark(ctx) {
1056
1238
  const m = saveCstMark(ctx);
1057
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1239
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1058
1240
  }
1059
1241
  function rollbackTrivia(ctx, mark) {
1060
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1242
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1061
1243
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1062
1244
  }
1063
1245
  function scanWithLabels(input, cur, ctx) {
@@ -1441,6 +1623,9 @@ function markUnusedValues(root) {
1441
1623
  case "transform":
1442
1624
  visit(def.parser, true);
1443
1625
  return;
1626
+ case "field":
1627
+ visit(def.parser, true);
1628
+ return;
1444
1629
  case "skip":
1445
1630
  visit(def.main, consumed);
1446
1631
  visit(def.skipped, false);
@@ -1492,6 +1677,11 @@ function markUnusedValues(root) {
1492
1677
  }
1493
1678
 
1494
1679
  // src/combinators/parser.ts
1680
+ function tagRule(r, key) {
1681
+ ;
1682
+ r._ruleName = key;
1683
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1684
+ }
1495
1685
  function rules(factory) {
1496
1686
  const cache = {};
1497
1687
  const proxy = new Proxy(cache, {
@@ -1500,7 +1690,7 @@ function rules(factory) {
1500
1690
  const record = target;
1501
1691
  if (!(key in record)) {
1502
1692
  const r = ref();
1503
- r._ruleName = key;
1693
+ tagRule(r, key);
1504
1694
  record[key] = r;
1505
1695
  }
1506
1696
  return record[key];
@@ -1511,12 +1701,12 @@ function rules(factory) {
1511
1701
  const placeholder = cache[key];
1512
1702
  const parser2 = definitions[key];
1513
1703
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1514
- ;
1704
+ tagRule(parser2, key);
1515
1705
  placeholder.define(parser2);
1516
1706
  placeholder._meta.firstSet = parser2._meta.firstSet;
1517
1707
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1518
1708
  } else {
1519
- ;
1709
+ tagRule(parser2, key);
1520
1710
  cache[key] = parser2;
1521
1711
  }
1522
1712
  }
@@ -1573,44 +1763,130 @@ function buildReadsTrivia(def) {
1573
1763
  const src = def.buildSrc ?? def.build.toString();
1574
1764
  const arity = confirmedBuildArity(src);
1575
1765
  if (arity === null) return true;
1576
- return arity >= 4;
1766
+ return arity >= 5;
1577
1767
  }
1578
1768
  function buildReadsState(def) {
1579
1769
  if (!def.build) return true;
1580
1770
  const src = def.buildSrc ?? def.build.toString();
1581
1771
  const arity = confirmedBuildArity(src);
1582
1772
  if (arity === null) return true;
1583
- return arity >= 5;
1773
+ return arity >= 6;
1774
+ }
1775
+
1776
+ // src/compiler/fields.ts
1777
+ function buildReadsFields(def) {
1778
+ if (!def.build) return true;
1779
+ const src = def.buildSrc ?? def.build.toString();
1780
+ const arity = confirmedBuildArity(src);
1781
+ if (arity === null) return true;
1782
+ return arity >= 2;
1783
+ }
1784
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1785
+ if (seen.has(p)) return false;
1786
+ seen.add(p);
1787
+ const d = p._def;
1788
+ switch (d.tag) {
1789
+ case "field":
1790
+ return true;
1791
+ case "node":
1792
+ return false;
1793
+ case "lazy": {
1794
+ try {
1795
+ return parserHasOwnFields(d.thunk(), seen);
1796
+ } catch {
1797
+ return false;
1798
+ }
1799
+ }
1800
+ case "sequence":
1801
+ case "choice":
1802
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1803
+ case "sepBy":
1804
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1805
+ case "skip":
1806
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1807
+ case "grammar":
1808
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1809
+ case "scanTo":
1810
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1811
+ case "recover":
1812
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1813
+ case "many":
1814
+ case "oneOrMore":
1815
+ case "optional":
1816
+ case "transform":
1817
+ case "trivia":
1818
+ case "token":
1819
+ case "label":
1820
+ case "expect":
1821
+ case "withCtx":
1822
+ case "not":
1823
+ return parserHasOwnFields(d.parser, seen);
1824
+ default:
1825
+ return false;
1826
+ }
1827
+ }
1828
+ function buildFieldMap(captures) {
1829
+ if (!captures?.length) return void 0;
1830
+ const out = {};
1831
+ for (const cap of captures) {
1832
+ const entry = { value: cap.value, span: cap.span };
1833
+ const current = out[cap.name];
1834
+ if (current === void 0) out[cap.name] = entry;
1835
+ else if (Array.isArray(current)) current.push(entry);
1836
+ else out[cap.name] = [current, entry];
1837
+ }
1838
+ return out;
1584
1839
  }
1585
1840
 
1586
1841
  // src/combinators/node.ts
1587
- function collapseChild(child) {
1842
+ function unwrapChild(child) {
1588
1843
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1589
1844
  }
1590
- function node(type, combinator, build, opts) {
1845
+ function isCstChild(value) {
1846
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1847
+ }
1848
+ function missingInferredType() {
1849
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1850
+ }
1851
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1852
+ const hasExplicitType = typeof typeOrCombinator === "string";
1853
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1854
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1855
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1856
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1857
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1591
1858
  const meta = {
1592
1859
  firstSet: combinator._meta.firstSet,
1593
1860
  canMatchNewline: combinator._meta.canMatchNewline,
1594
1861
  isTrivia: false
1595
1862
  };
1863
+ const unwrap = opts?.unwrap === true;
1596
1864
  const collapse = opts?.collapse === true;
1597
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1865
+ if (unwrap && collapse) {
1866
+ throw new Error("node() options cannot set both unwrap and collapse");
1867
+ }
1868
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1598
1869
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1599
1870
  const clonesState = build ? buildReadsState(def) : true;
1871
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1600
1872
  return {
1601
1873
  _tag: "node",
1602
1874
  _meta: meta,
1603
1875
  _def: def,
1604
1876
  parse(input, pos, ctx) {
1605
1877
  const saved = beginCstNodeCapture(ctx);
1878
+ const savedFields = ctx._fields;
1879
+ ctx._fields = capturesFields ? [] : void 0;
1606
1880
  if (!capturesTrivia) ctx.captureTrivia = false;
1607
1881
  const r = combinator.parse(input, pos, ctx);
1882
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1883
+ ctx._fields = savedFields;
1608
1884
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1609
1885
  if (!r.ok) return r;
1610
1886
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1611
- 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 };
1612
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1613
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1887
+ const nodeType = def.type ?? missingInferredType();
1888
+ 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 };
1889
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1614
1890
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1615
1891
  pushCstChild(ctx, built, rawEntry);
1616
1892
  }
@@ -1671,6 +1947,19 @@ function label(name, combinator) {
1671
1947
  }
1672
1948
  };
1673
1949
  }
1950
+ function field(name, combinator) {
1951
+ return {
1952
+ _tag: combinator._tag,
1953
+ _meta: combinator._meta,
1954
+ _def: { tag: "field", name, parser: combinator },
1955
+ parse(input, pos, ctx) {
1956
+ const result = combinator.parse(input, pos, ctx);
1957
+ if (!result.ok) return result;
1958
+ ctx._fields?.push({ name, value: result.value, span: result.span });
1959
+ return result;
1960
+ }
1961
+ };
1962
+ }
1674
1963
 
1675
1964
  // src/compiler/line-index.ts
1676
1965
  function buildLineIndex(input) {
@@ -1857,77 +2146,11 @@ function expect(combinator, label2) {
1857
2146
 
1858
2147
  // src/compiler/scannable-run.ts
1859
2148
  var BACKSLASH = 92;
1860
- var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1861
2149
  var META = new Set("()[]{}*+?|^$.".split(""));
1862
- var SPACE_RANGES2 = [
1863
- [9, 13],
1864
- [32, 32],
1865
- [160, 160],
1866
- [5760, 5760],
1867
- [8192, 8202],
1868
- [8232, 8232],
1869
- [8233, 8233],
1870
- [8239, 8239],
1871
- [8287, 8287],
1872
- [12288, 12288],
1873
- [65279, 65279]
1874
- ];
1875
- function shorthandRanges2(ch) {
1876
- if (ch === "d") return [[48, 57]];
1877
- if (ch === "s") return SPACE_RANGES2;
1878
- return [[48, 57], [65, 90], [97, 122], [95, 95]];
1879
- }
1880
- function readUnicodeEscape(body, i) {
1881
- if (body[i] !== "\\" || body[i + 1] !== "u") return null;
1882
- const hex = body.slice(i + 2, i + 6);
1883
- if (!/^[0-9a-fA-F]{4}$/.test(hex)) return null;
1884
- return { cp: Number.parseInt(hex, 16), next: i + 6 };
1885
- }
1886
- function parseClassRanges(body) {
1887
- const ranges = [];
1888
- let i = 0;
1889
- const readAtom = () => {
1890
- const ch = body[i];
1891
- if (ch === void 0) return null;
1892
- if (ch === "\\") {
1893
- const uni = readUnicodeEscape(body, i);
1894
- if (uni) {
1895
- i = uni.next;
1896
- return { cp: uni.cp };
1897
- }
1898
- const e = body[i + 1];
1899
- if (e === void 0) return null;
1900
- i += 2;
1901
- if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
1902
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1903
- if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1904
- return { cp: e.codePointAt(0) };
1905
- }
1906
- i += ch.length;
1907
- return { cp: ch.codePointAt(0) };
1908
- };
1909
- while (i < body.length) {
1910
- const lo = readAtom();
1911
- if (lo === null) return null;
1912
- if ("set" in lo) {
1913
- ranges.push(...lo.set);
1914
- continue;
1915
- }
1916
- if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
1917
- i += 1;
1918
- const hi = readAtom();
1919
- if (hi === null || "set" in hi) return null;
1920
- ranges.push([lo.cp, hi.cp]);
1921
- } else {
1922
- ranges.push([lo.cp, lo.cp]);
1923
- }
1924
- }
1925
- return ranges.length ? ranges : null;
1926
- }
1927
2150
  function classToRanges(cls) {
1928
- if (cls === "\\d") return shorthandRanges2("d");
1929
- if (cls === "\\w") return shorthandRanges2("w");
1930
- if (cls === "\\s") return shorthandRanges2("s");
2151
+ if (cls === "\\d") return shorthandRanges("d");
2152
+ if (cls === "\\w") return shorthandRanges("w");
2153
+ if (cls === "\\s") return shorthandRanges("s");
1931
2154
  const body = cls.slice(1, -1);
1932
2155
  if (body.startsWith("^")) return null;
1933
2156
  return parseClassRanges(body);
@@ -1945,7 +2168,7 @@ function literalCodePoints(frag) {
1945
2168
  if (ch === "\\") {
1946
2169
  const e = frag[i + 1];
1947
2170
  if (e === void 0) return null;
1948
- out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
2171
+ out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
1949
2172
  i += 2;
1950
2173
  continue;
1951
2174
  }
@@ -2038,7 +2261,7 @@ function parseSeqParts(source) {
2038
2261
  if (e === "d" || e === "w" || e === "s") {
2039
2262
  flush();
2040
2263
  i += 2;
2041
- i += runFrom(shorthandRanges2(e), false, source[i]);
2264
+ i += runFrom(shorthandRanges(e), false, source[i]);
2042
2265
  continue;
2043
2266
  }
2044
2267
  const uni = readUnicodeEscape(source, i);
@@ -2047,8 +2270,8 @@ function parseSeqParts(source) {
2047
2270
  cp2 = uni.cp;
2048
2271
  i = uni.next;
2049
2272
  } else if (e === void 0) return null;
2050
- else if (e in CLASS_ESCAPES2) {
2051
- cp2 = CLASS_ESCAPES2[e];
2273
+ else if (e in CLASS_ESCAPES) {
2274
+ cp2 = CLASS_ESCAPES[e];
2052
2275
  i += 2;
2053
2276
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
2054
2277
  else {
@@ -2155,7 +2378,7 @@ function rangesSubset(sub, sup) {
2155
2378
  }
2156
2379
  function parseClassOperand(body) {
2157
2380
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2158
- return { ranges: shorthandRanges2(body[1]), negated: false };
2381
+ return { ranges: shorthandRanges(body[1]), negated: false };
2159
2382
  }
2160
2383
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2161
2384
  let inner = body.slice(1, -1);
@@ -2588,7 +2811,7 @@ function parseScanShapeCore(source) {
2588
2811
  }
2589
2812
  m = /^\\([dws])([+*])$/.exec(source);
2590
2813
  if (m) {
2591
- return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2814
+ return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2592
2815
  }
2593
2816
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2594
2817
  if (m) {
@@ -3130,21 +3353,22 @@ function emitScannableTerminal(shape, c) {
3130
3353
  }
3131
3354
 
3132
3355
  // src/compiler/inline-build.ts
3133
- 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*$/;
3356
+ 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*$/;
3134
3357
  function escapeRegExp(s) {
3135
3358
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3136
3359
  }
3137
3360
  function looseMkBuildRe(type) {
3138
3361
  return new RegExp(
3139
- 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*$`
3362
+ 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*$`
3140
3363
  );
3141
3364
  }
3142
3365
  function analyzeMkInlineBuild(def) {
3143
3366
  if (!def.build) return null;
3367
+ if (def.type === void 0) return null;
3144
3368
  const src = (def.buildSrc ?? def.build.toString()).trim();
3145
3369
  const strict = src.match(MK_BUILD_RE);
3146
3370
  if (strict) {
3147
- const mkType = strict[2];
3371
+ const mkType = strict[6];
3148
3372
  return mkType === def.type ? def.type : null;
3149
3373
  }
3150
3374
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3479,8 +3703,9 @@ function planDisjointDispatch(parsers) {
3479
3703
  }
3480
3704
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3481
3705
  }
3482
- function captureRestoreBody(mL, mR, mTl, mLg) {
3483
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3706
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3707
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3708
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3484
3709
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3485
3710
  }
3486
3711
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3528,6 +3753,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3528
3753
  // Delegating wrappers: defer to the wrapped parser.
3529
3754
  case "transform":
3530
3755
  case "label":
3756
+ case "field":
3531
3757
  case "expect":
3532
3758
  case "withCtx":
3533
3759
  case "grammar":
@@ -3574,6 +3800,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3574
3800
  case "optional":
3575
3801
  case "transform":
3576
3802
  case "label":
3803
+ case "field":
3577
3804
  case "expect":
3578
3805
  case "withCtx":
3579
3806
  case "grammar":
@@ -3611,22 +3838,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3611
3838
  ctx.recordFail = savedRecord;
3612
3839
  const ind0 = ind(ctx);
3613
3840
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3841
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3614
3842
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3615
3843
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3616
3844
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3845
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3617
3846
  const stmts = [
3618
3847
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3619
3848
  ...mL ? [
3620
3849
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3621
3850
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3622
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3851
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3852
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3623
3853
  ] : [],
3624
3854
  `${ind0}${lbl}: {`,
3625
3855
  ...r.stmts,
3626
3856
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3627
3857
  `${ind0}}`,
3628
3858
  ...mL ? [
3629
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3859
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3630
3860
  ] : []
3631
3861
  ];
3632
3862
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3853,6 +4083,7 @@ function failsAtStart(p) {
3853
4083
  return true;
3854
4084
  case "transform":
3855
4085
  case "label":
4086
+ case "field":
3856
4087
  return failsAtStart(d.parser);
3857
4088
  default:
3858
4089
  return false;
@@ -3872,6 +4103,7 @@ function canMatchEmptyAtStart(p) {
3872
4103
  return true;
3873
4104
  case "transform":
3874
4105
  case "label":
4106
+ case "field":
3875
4107
  return canMatchEmptyAtStart(d.parser);
3876
4108
  case "literal":
3877
4109
  return d.value.length === 0;
@@ -4054,11 +4286,13 @@ function emitFirstMatch(def, ctx, pos) {
4054
4286
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
4055
4287
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
4056
4288
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4289
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
4057
4290
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
4058
4291
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
4059
4292
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
4060
4293
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
4061
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4294
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4295
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
4062
4296
  const failSlot = atStart ? staticFx : "_ctx._fx";
4063
4297
  stmts.push(`${ind0}if (${skipCond}) {`);
4064
4298
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -4068,7 +4302,8 @@ function emitFirstMatch(def, ctx, pos) {
4068
4302
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
4069
4303
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
4070
4304
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
4071
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4305
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4306
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
4072
4307
  );
4073
4308
  }
4074
4309
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4461,6 +4696,9 @@ function emitToken(def, ctx, pos) {
4461
4696
  };
4462
4697
  }
4463
4698
  function emitNode(def, ctx, pos) {
4699
+ if (def.type === void 0) {
4700
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4701
+ }
4464
4702
  const structural = def.build === void 0;
4465
4703
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4466
4704
  let buildIdx = null;
@@ -4472,25 +4710,36 @@ function emitNode(def, ctx, pos) {
4472
4710
  const i = ind(ctx);
4473
4711
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4474
4712
  const clonesState = !structural && buildReadsState(def);
4713
+ const hasFields = parserHasOwnFields(def.parser);
4714
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4475
4715
  const chV = v(ctx, "_ch");
4476
4716
  const rawV = v(ctx, "_raw");
4477
4717
  const capTLv = structural ? v(ctx, "_ctl") : null;
4478
4718
  const capSTv = structural ? v(ctx, "_cst") : null;
4719
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4479
4720
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4480
4721
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4481
4722
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4482
4723
  if (structural) ctx.needsHostReads = true;
4483
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4724
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4725
+ 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)` : ""}
4484
4726
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4485
4727
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4728
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4729
+ const sf = hasFields ? v(ctx, "_sf") : null;
4730
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4731
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4486
4732
  const stmts = [
4487
4733
  allocStmt,
4488
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4489
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4734
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4735
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4490
4736
  ];
4491
4737
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4492
4738
  stmts.push(...innerStmts);
4493
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4739
+ if (sf && fArr) {
4740
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4741
+ }
4742
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4494
4743
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4495
4744
  let stV = "undefined";
4496
4745
  if (structural) {
@@ -4500,14 +4749,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4500
4749
  stV = v(ctx, "_nst");
4501
4750
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4502
4751
  }
4752
+ if (sf && fArr) {
4753
+ const fe = v(ctx, "_fe");
4754
+ const cur = v(ctx, "_fc");
4755
+ stmts.push(
4756
+ `${i}let ${fObj} = undefined`,
4757
+ `${i}if (${fArr} && ${fArr}.length) {`,
4758
+ `${i} ${fObj} = {}`,
4759
+ `${i} for (const ${fe} of ${fArr}) {`,
4760
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4761
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4762
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4763
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4764
+ `${i} }`,
4765
+ `${i}}`
4766
+ );
4767
+ }
4503
4768
  const ndV = v(ctx, "_nd");
4504
- 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})`;
4505
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4506
- 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;
4769
+ 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})`;
4770
+ 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;
4771
+ 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;
4772
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4773
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4774
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4507
4775
  stmts.push(
4508
4776
  `${i}const ${ndV} = ${finalExpr}`,
4509
4777
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4510
- `${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} } })`
4778
+ `${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} } })`
4511
4779
  );
4512
4780
  return { stmts, valueVar: ndV, endVar };
4513
4781
  }
@@ -4722,6 +4990,17 @@ function emitDispatch(p, ctx, pos) {
4722
4990
  endVar: inner.endVar
4723
4991
  };
4724
4992
  }
4993
+ case "field": {
4994
+ const inner = emit(def.parser, ctx, pos);
4995
+ return {
4996
+ stmts: [
4997
+ ...inner.stmts,
4998
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
4999
+ ],
5000
+ valueVar: inner.valueVar,
5001
+ endVar: inner.endVar
5002
+ };
5003
+ }
4725
5004
  case "grammar": {
4726
5005
  const savedTrivia = ctx.activeTrivia;
4727
5006
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4823,6 +5102,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4823
5102
  case "trivia":
4824
5103
  case "token":
4825
5104
  case "label":
5105
+ case "field":
4826
5106
  case "optional":
4827
5107
  case "many":
4828
5108
  case "oneOrMore":
@@ -4859,6 +5139,7 @@ function childrenOf(def) {
4859
5139
  case "trivia":
4860
5140
  case "token":
4861
5141
  case "label":
5142
+ case "field":
4862
5143
  case "grammar":
4863
5144
  case "not":
4864
5145
  case "node":
@@ -5100,6 +5381,11 @@ function assertRuleName(name) {
5100
5381
  );
5101
5382
  }
5102
5383
  }
5384
+ function publicRuleWrapperSource(rule, fnSource) {
5385
+ const labels = rule._meta.triviaKindLabels;
5386
+ if (labels === void 0) return fnSource;
5387
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5388
+ }
5103
5389
  function compileRuleMap(ruleMap) {
5104
5390
  for (const [, rule] of ruleMap) markUnusedValues(rule);
5105
5391
  const ctx = {
@@ -5132,7 +5418,7 @@ function compileRuleMap(ruleMap) {
5132
5418
  ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
5133
5419
  }
5134
5420
  ctx.ruleNames = ruleNames;
5135
- const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
5421
+ const perEntry = ruleMap.map(([key, rule]) => ({ key, rule, r: emit(rule, ctx, "_pos") }));
5136
5422
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
5137
5423
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
5138
5424
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -5162,7 +5448,10 @@ function compileRuleMap(ruleMap) {
5162
5448
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5163
5449
  `}`
5164
5450
  ].join("\n");
5165
- const objBody = perEntry.map(({ key, r }) => ` ${JSON.stringify(key)}: ${entryFnText(r).split("\n").join("\n ")}`).join(",\n");
5451
+ const objBody = perEntry.map(({ key, rule, r }) => {
5452
+ const src = publicRuleWrapperSource(rule, entryFnText(r));
5453
+ return ` ${JSON.stringify(key)}: ${src.split("\n").join("\n ")}`;
5454
+ }).join(",\n");
5166
5455
  const replacement = [
5167
5456
  `/* @__PURE__ */ (() => {`,
5168
5457
  ...hoistedDecls,
@@ -5275,7 +5564,7 @@ function compileLinkable(ruleMapArg, ns) {
5275
5564
  ctx.recordFail = savedRec;
5276
5565
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5277
5566
  }
5278
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5567
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5279
5568
  }
5280
5569
  if (ctx.runtimeParsers.length !== 0) return null;
5281
5570
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5298,14 +5587,14 @@ function compileLinkable(ruleMapArg, ns) {
5298
5587
  ...privateFns
5299
5588
  ].filter(Boolean);
5300
5589
  const wrappers = /* @__PURE__ */ new Map();
5301
- for (const { key, r } of perEntry) {
5302
- wrappers.set(key, [
5590
+ for (const { key, rule, r } of perEntry) {
5591
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5303
5592
  `function(input, _pos, _ctx) {`,
5304
5593
  ` let pos = _pos`,
5305
5594
  ...r.stmts,
5306
5595
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5307
5596
  `}`
5308
- ].join("\n"));
5597
+ ].join("\n")));
5309
5598
  }
5310
5599
  const firstSets = /* @__PURE__ */ new Map();
5311
5600
  for (const [key, rule] of ruleMap) {
@@ -5480,6 +5769,7 @@ function childrenOf2(def) {
5480
5769
  case "trivia":
5481
5770
  case "token":
5482
5771
  case "label":
5772
+ case "field":
5483
5773
  case "not":
5484
5774
  case "node":
5485
5775
  case "expect":
@@ -5565,6 +5855,7 @@ function evalRuleMapIR(ir) {
5565
5855
  "skip",
5566
5856
  "trivia",
5567
5857
  "label",
5858
+ "field",
5568
5859
  "expect",
5569
5860
  "_tf",
5570
5861
  "_nd",
@@ -5591,6 +5882,7 @@ function evalRuleMapIR(ir) {
5591
5882
  skip,
5592
5883
  trivia,
5593
5884
  label,
5885
+ field,
5594
5886
  expect,
5595
5887
  _tf,
5596
5888
  _nd
@@ -5724,6 +6016,8 @@ ${body}
5724
6016
  return `token(${kid(def.parser)})`;
5725
6017
  case "label":
5726
6018
  return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
6019
+ case "field":
6020
+ return `field(${JSON.stringify(def.name)}, ${kid(def.parser)})`;
5727
6021
  case "expect":
5728
6022
  return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
5729
6023
  case "skip":
@@ -5736,7 +6030,11 @@ ${body}
5736
6030
  }
5737
6031
  case "node": {
5738
6032
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
5739
- const opts = def.collapse ? `, { collapse: true }` : "";
6033
+ const opts = def.unwrap || def.collapse ? `, { ${def.unwrap ? "unwrap: true" : "collapse: true"} }` : "";
6034
+ if (def.type === void 0) {
6035
+ if (def.buildSrc !== void 0) throw new Unserializable("inferred node build without inferred type");
6036
+ return opts ? `node(${kid(def.parser)}, undefined${opts})` : `node(${kid(def.parser)})`;
6037
+ }
5740
6038
  if (def.buildSrc !== void 0) return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts})`;
5741
6039
  return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
5742
6040
  }
@@ -5763,8 +6061,9 @@ function fusedBody(pieces) {
5763
6061
  }
5764
6062
  }
5765
6063
  const contributing = new Set(winner.values());
5766
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5767
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
6064
+ const contributingPieces = [...contributing];
6065
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
6066
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5768
6067
  const lines = [
5769
6068
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5770
6069
  "const _pfFail = {}",
@@ -5772,7 +6071,7 @@ function fusedBody(pieces) {
5772
6071
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5773
6072
  ...needsHostReads ? [HOST_READS_DECL] : [],
5774
6073
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5775
- ...[...contributing].flatMap((p) => p.prelude),
6074
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5776
6075
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5777
6076
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5778
6077
  ];
@@ -5795,7 +6094,7 @@ function fusedBody(pieces) {
5795
6094
  }
5796
6095
  );
5797
6096
  const env = {};
5798
- for (const p of contributing) {
6097
+ for (const p of contributingPieces) {
5799
6098
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5800
6099
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5801
6100
  }
@@ -5843,6 +6142,7 @@ var SUPPORTED = {
5843
6142
  sepBy: (...a) => sepBy(a[0], a[1]),
5844
6143
  trivia: (...a) => trivia(a[0]),
5845
6144
  label: (...a) => label(a[0], a[1]),
6145
+ field: (...a) => field(a[0], a[1]),
5846
6146
  noTrivia: (...a) => noTrivia(a[0]),
5847
6147
  token: (...a) => token(a[0]),
5848
6148
  expect: (...a) => expect(a[0], a[1])
@@ -5863,19 +6163,20 @@ function scopeGet(scope, name, mfs) {
5863
6163
  if (isCombinator(entry)) return entry;
5864
6164
  return null;
5865
6165
  }
5866
- function staticCollapseOption(expr) {
5867
- if (expr.type !== "ObjectExpression") return false;
6166
+ function staticNodeOptions(expr) {
6167
+ if (expr.type !== "ObjectExpression") return void 0;
6168
+ const opts = {};
5868
6169
  for (const prop of expr.properties) {
5869
6170
  const p = prop;
5870
6171
  if (p.computed) continue;
5871
6172
  const key = p.key;
5872
6173
  const name = key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
5873
- if (name === "collapse") {
6174
+ if (name === "unwrap" || name === "collapse") {
5874
6175
  const val = p.value;
5875
- return (val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true;
6176
+ if ((val.type === "Literal" || val.type === "BooleanLiteral") && val.value === true) opts[name] = true;
5876
6177
  }
5877
6178
  }
5878
- return false;
6179
+ return opts.unwrap || opts.collapse ? opts : void 0;
5879
6180
  }
5880
6181
  function exprToCombi(node2, scope, code, mfs) {
5881
6182
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -5925,18 +6226,22 @@ function exprToCombi(node2, scope, code, mfs) {
5925
6226
  }
5926
6227
  }
5927
6228
  if (callee.name === "node" && code !== void 0) {
5928
- const [typeArg, parserArg, buildArg, optsArg] = node2.arguments;
5929
- if (!typeArg || !parserArg || typeArg.type === "SpreadElement" || parserArg.type === "SpreadElement") return null;
5930
- const typeVal = anyValue(typeArg, scope, code);
5931
- if (typeof typeVal !== "string") return null;
6229
+ const [firstArg, secondArg, thirdArg, fourthArg] = node2.arguments;
6230
+ if (!firstArg || firstArg.type === "SpreadElement") return null;
6231
+ const firstVal = anyValue(firstArg, scope, code, mfs);
6232
+ const explicitType = typeof firstVal === "string" ? firstVal : void 0;
6233
+ const parserArg = explicitType !== void 0 ? secondArg : firstArg;
6234
+ const buildArg = explicitType !== void 0 ? thirdArg : secondArg;
6235
+ const optsArg = explicitType !== void 0 ? fourthArg : thirdArg;
6236
+ if (!parserArg || parserArg.type === "SpreadElement") return null;
5932
6237
  const inner = anyValue(parserArg, scope, code, mfs);
5933
6238
  if (!isCombinator(inner)) return null;
5934
6239
  const be = buildArg;
5935
6240
  const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !(be.type === "Identifier" && be.name === "undefined");
5936
6241
  const buildSrc = hasBuild ? code.slice(be.start, be.end) : void 0;
5937
- const collapse = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticCollapseOption(optsArg) : false;
6242
+ const opts = optsArg !== void 0 && optsArg.type !== "SpreadElement" ? staticNodeOptions(optsArg) : void 0;
5938
6243
  try {
5939
- const combi = node(typeVal, inner, hasBuild ? () => null : void 0, collapse ? { collapse: true } : void 0);
6244
+ const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
5940
6245
  if (combi._def.tag === "node" && buildSrc !== void 0) combi._def.buildSrc = buildSrc;
5941
6246
  return combi;
5942
6247
  } catch {
@@ -6202,7 +6507,9 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
6202
6507
  if (!keys) return null;
6203
6508
  const ruleRefs = /* @__PURE__ */ new Map();
6204
6509
  const tagRef = (r, name) => {
6510
+ ;
6205
6511
  r._ruleName = name;
6512
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = name;
6206
6513
  };
6207
6514
  for (const key of keys) {
6208
6515
  if (!ruleRefs.has(key)) {
@@ -6234,6 +6541,7 @@ function evaluateParserFactory(factoryNode, scope, code, mapFnSources) {
6234
6541
  for (const [key, e] of finalByKey) {
6235
6542
  const val = anyValue(e.value, e.scope, e.code, mapFnSources);
6236
6543
  if (!isCombinator(val)) return null;
6544
+ tagRef(val, key);
6237
6545
  ruleRefs.get(key).define(val);
6238
6546
  }
6239
6547
  return ruleRefs;