parseman 0.18.1 → 0.19.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 (38) hide show
  1. package/README.md +7 -3
  2. package/dist/combinators/choice.d.ts.map +1 -1
  3. package/dist/combinators/literal.d.ts.map +1 -1
  4. package/dist/combinators/map.d.ts +5 -0
  5. package/dist/combinators/map.d.ts.map +1 -1
  6. package/dist/combinators/node.d.ts +14 -13
  7. package/dist/combinators/node.d.ts.map +1 -1
  8. package/dist/combinators/parser.d.ts.map +1 -1
  9. package/dist/combinators/ref.d.ts.map +1 -1
  10. package/dist/combinators/regex.d.ts.map +1 -1
  11. package/dist/combinators/repeat.d.ts.map +1 -1
  12. package/dist/combinators/trivia-skip.d.ts +1 -0
  13. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  14. package/dist/compiler/build-arity.d.ts +3 -3
  15. package/dist/compiler/codegen.d.ts.map +1 -1
  16. package/dist/compiler/fields.d.ts +16 -0
  17. package/dist/compiler/fields.d.ts.map +1 -0
  18. package/dist/compiler/inline-build.d.ts.map +1 -1
  19. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  20. package/dist/compiler/linker.d.ts +17 -3
  21. package/dist/compiler/linker.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 +498 -78
  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 +498 -78
  30. package/dist/index.js.map +4 -4
  31. package/dist/plugin/evaluator.d.ts.map +1 -1
  32. package/dist/plugin/index.cjs +498 -86
  33. package/dist/plugin/index.cjs.map +4 -4
  34. package/dist/plugin/index.js +498 -86
  35. package/dist/plugin/index.js.map +4 -4
  36. package/dist/types.d.ts +31 -8
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +4 -1
package/dist/index.cjs CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  createVisitor: () => createVisitor,
50
50
  cstBuildHost: () => cstBuildHost,
51
51
  expect: () => expect,
52
+ field: () => field,
52
53
  gapIsSignificant: () => gapIsSignificant,
53
54
  gapText: () => gapText,
54
55
  guard: () => guard,
@@ -349,7 +350,8 @@ function saveCstMark(ctx) {
349
350
  return {
350
351
  raw: cstRawLen(ctx),
351
352
  tlog: cstTlLen(ctx),
352
- leaves: cstLeavesLen(ctx)
353
+ leaves: cstLeavesLen(ctx),
354
+ fields: ctx._fields?.length ?? 0
353
355
  };
354
356
  }
355
357
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -373,11 +375,13 @@ function rollbackCstCapture(ctx, mark) {
373
375
  if (mark.tlog === 0) b.tl = void 0;
374
376
  else b.tl.length = mark.tlog;
375
377
  }
378
+ if (ctx._fields) ctx._fields.length = mark.fields;
376
379
  return;
377
380
  }
378
381
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
379
382
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
380
383
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
384
+ if (ctx._fields) ctx._fields.length = mark.fields;
381
385
  }
382
386
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
383
387
  const insertIdx = cstRawLen(ctx);
@@ -446,13 +450,12 @@ function literal(value, opts = {}) {
446
450
  if (end > input.length) {
447
451
  return failAt(ctx, [JSON.stringify(value)], pos);
448
452
  }
449
- const slice = input.slice(pos, end);
450
- const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
451
- if (matched) {
453
+ const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
454
+ if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
452
455
  const span = { start: pos, end };
453
- const leaf = { _tag: "leaf", value: slice, span };
456
+ const leaf = { _tag: "leaf", value: matchedValue, span };
454
457
  if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
455
- return { ok: true, value: slice, span };
458
+ return { ok: true, value: matchedValue, span };
456
459
  }
457
460
  return failAt(ctx, [JSON.stringify(value)], pos);
458
461
  }
@@ -465,10 +468,102 @@ function registerRegexAnalyzer(analyzer) {
465
468
  firstSetAnalyzer = analyzer;
466
469
  }
467
470
  var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
471
+ var SCAN_BAIL_AT = 64;
472
+ var SPACE_RANGES = [
473
+ [9, 13],
474
+ [32, 32],
475
+ [160, 160],
476
+ [5760, 5760],
477
+ [8192, 8202],
478
+ [8232, 8233],
479
+ [8239, 8239],
480
+ [8287, 8287],
481
+ [12288, 12288],
482
+ [65279, 65279]
483
+ ];
484
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
485
+ function shorthandRanges(ch) {
486
+ if (ch === "d") return [[48, 57]];
487
+ if (ch === "s") return SPACE_RANGES;
488
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
489
+ }
490
+ function inRanges(cp, ranges) {
491
+ for (let i = 0; i < ranges.length; i++) {
492
+ const [lo, hi] = ranges[i];
493
+ if (cp >= lo && cp <= hi) return true;
494
+ }
495
+ return false;
496
+ }
497
+ function readClassRanges(body) {
498
+ if (body.startsWith("^")) return null;
499
+ const ranges = [];
500
+ let i = 0;
501
+ const readAtom = () => {
502
+ const ch = body[i];
503
+ if (ch === void 0) return null;
504
+ if (ch === "\\") {
505
+ const esc = body[i + 1];
506
+ if (esc === void 0) return null;
507
+ i += 2;
508
+ if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
509
+ if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
510
+ if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
511
+ return { cp: esc.charCodeAt(0) };
512
+ }
513
+ i++;
514
+ return { cp: ch.charCodeAt(0) };
515
+ };
516
+ while (i < body.length) {
517
+ const lo = readAtom();
518
+ if (!lo) return null;
519
+ if ("ranges" in lo) {
520
+ ranges.push(...lo.ranges);
521
+ continue;
522
+ }
523
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
524
+ i++;
525
+ const hi = readAtom();
526
+ if (!hi || "ranges" in hi) return null;
527
+ ranges.push([lo.cp, hi.cp]);
528
+ } else {
529
+ ranges.push([lo.cp, lo.cp]);
530
+ }
531
+ }
532
+ return ranges.length ? ranges : null;
533
+ }
534
+ function shortScanner(source, flags) {
535
+ if (/[imsuvy]/.test(flags)) return null;
536
+ let ranges = null;
537
+ let quant = "";
538
+ if (source[0] === "[") {
539
+ let end = 1;
540
+ while (end < source.length && source[end] !== "]") {
541
+ if (source[end] === "\\") end += 2;
542
+ else end++;
543
+ }
544
+ if (source[end] !== "]") return null;
545
+ ranges = readClassRanges(source.slice(1, end));
546
+ quant = source.slice(end + 1);
547
+ } else if (source[0] === "\\" && (source[1] === "d" || source[1] === "w" || source[1] === "s")) {
548
+ ranges = shorthandRanges(source[1]);
549
+ quant = source.slice(2);
550
+ }
551
+ if (!ranges || quant !== "+" && quant !== "*") return null;
552
+ const minOne = quant === "+";
553
+ return (input, pos) => {
554
+ let end = pos;
555
+ while (end < input.length && inRanges(input.charCodeAt(end), ranges)) {
556
+ end++;
557
+ if (end - pos >= SCAN_BAIL_AT) return void 0;
558
+ }
559
+ return minOne && end === pos ? null : end;
560
+ };
561
+ }
468
562
  function regex(pattern, flags = "") {
469
563
  const source = typeof pattern === "string" ? pattern : pattern.source;
470
564
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
471
565
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
566
+ const scan = shortScanner(source, resolvedFlags);
472
567
  const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
473
568
  const meta = { firstSet, canMatchNewline, isTrivia: false };
474
569
  return {
@@ -476,6 +571,15 @@ function regex(pattern, flags = "") {
476
571
  _meta: meta,
477
572
  _def: { tag: "regex", source, flags: resolvedFlags },
478
573
  parse(input, pos, ctx) {
574
+ const scanEnd = scan?.(input, pos);
575
+ if (scanEnd !== void 0) {
576
+ if (scanEnd === null) return failAt(ctx, [`/${source}/`], pos);
577
+ const value = input.slice(pos, scanEnd);
578
+ const span2 = { start: pos, end: scanEnd };
579
+ const leaf2 = { _tag: "leaf", value, span: span2 };
580
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf2);
581
+ return { ok: true, value, span: span2 };
582
+ }
479
583
  anchored.lastIndex = pos;
480
584
  const m = anchored.exec(input);
481
585
  if (m === null) {
@@ -659,13 +763,10 @@ function choice(...args) {
659
763
  };
660
764
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
661
765
  const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
662
- let greedyRe = null;
663
766
  let greedyLitMap = null;
664
767
  let sortedParsers = null;
768
+ const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
665
769
  if (strategy?.tag === "greedyClassify") {
666
- const regexDef = getCoreRegexDef(parsers[strategy.superIndex]);
667
- const flags = "y" + regexDef.flags.replace(/[gy]/g, "");
668
- greedyRe = new RegExp(regexDef.source, flags);
669
770
  greedyLitMap = /* @__PURE__ */ new Map();
670
771
  for (let i = 0; i < parsers.length; i++) {
671
772
  if (i === strategy.superIndex) continue;
@@ -690,14 +791,21 @@ function choice(...args) {
690
791
  const expected = [];
691
792
  if (disjoint && pos < input.length) {
692
793
  const code = input.codePointAt(pos);
693
- for (const parser2 of parsers) {
694
- if (inFirstSet(code, parser2._meta.firstSet)) {
695
- const result = parser2.parse(input, pos, ctx);
696
- if (result.ok) return result;
697
- expected.push(...result.expected);
698
- return { ok: false, expected, span: { start: pos, end: pos } };
794
+ let parser2 = code < 128 ? asciiDispatch?.[code] ?? null : null;
795
+ if (!parser2) {
796
+ for (const p of parsers) {
797
+ if (inFirstSet(code, p._meta.firstSet)) {
798
+ parser2 = p;
799
+ break;
800
+ }
699
801
  }
700
802
  }
803
+ if (parser2) {
804
+ const result = parser2.parse(input, pos, ctx);
805
+ if (result.ok) return result;
806
+ expected.push(...result.expected);
807
+ return { ok: false, expected, span: { start: pos, end: pos } };
808
+ }
701
809
  return {
702
810
  ok: false,
703
811
  expected: parsers.flatMap((p) => {
@@ -873,6 +981,19 @@ function areDisjoint(sets) {
873
981
  if (intersects(sets[i], sets[j])) return false;
874
982
  return true;
875
983
  }
984
+ function buildAsciiDispatch(parsers) {
985
+ const table = Array(128).fill(null);
986
+ for (const parser2 of parsers) {
987
+ const fs = parser2._meta.firstSet;
988
+ if (fs.kind !== "ranges") continue;
989
+ for (const { lo, hi } of fs.ranges) {
990
+ for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
991
+ table[code] = parser2;
992
+ }
993
+ }
994
+ }
995
+ return table;
996
+ }
876
997
 
877
998
  // src/cst/trivia-kinds.ts
878
999
  function unwrapTrivia(p) {
@@ -994,15 +1115,16 @@ function recordTriviaChunks(ctx, chunks) {
994
1115
  // src/combinators/trivia-skip.ts
995
1116
  var NOOP_COMMIT = () => {
996
1117
  };
1118
+ var fastTriviaCache = /* @__PURE__ */ new WeakMap();
997
1119
  function needsDeferredTriviaCommit(ctx) {
998
1120
  return ctx._triviaLog !== void 0 || ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0;
999
1121
  }
1000
1122
  function saveTriviaMark(ctx) {
1001
1123
  const m = saveCstMark(ctx);
1002
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1124
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1003
1125
  }
1004
1126
  function rollbackTrivia(ctx, mark) {
1005
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1127
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
1006
1128
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1007
1129
  }
1008
1130
  function scanWithLabels(input, cur, ctx) {
@@ -1020,6 +1142,8 @@ function scanWithLabels(input, cur, ctx) {
1020
1142
  function advanceTrivia(input, cur, ctx) {
1021
1143
  const triviaP = ctx.trivia;
1022
1144
  if (!triviaP) return cur;
1145
+ const fast = fastTriviaScanner(triviaP);
1146
+ if (fast) return fast(input, cur);
1023
1147
  if (ctx.triviaKindLabels) {
1024
1148
  const scan = scanWithLabels(input, cur, ctx);
1025
1149
  return scan.end;
@@ -1032,6 +1156,10 @@ function scanTrivia(input, cur, ctx) {
1032
1156
  if (!triviaP) return { end: cur, commit: NOOP_COMMIT };
1033
1157
  const log = ctx._triviaLog;
1034
1158
  const captureTl = ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0);
1159
+ const fast = !ctx.triviaKindLabels ? fastTriviaScanner(triviaP) : null;
1160
+ if (fast && log === void 0 && !captureTl) {
1161
+ return { end: fast(input, cur), commit: NOOP_COMMIT };
1162
+ }
1035
1163
  if (ctx.triviaKindLabels && (log !== void 0 || captureTl)) {
1036
1164
  return scanWithLabels(input, cur, ctx);
1037
1165
  }
@@ -1053,6 +1181,79 @@ function scanTrivia(input, cur, ctx) {
1053
1181
  const tr = triviaP.parse(input, cur, { trackLines: ctx.trackLines, state: ctx.state });
1054
1182
  return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT };
1055
1183
  }
1184
+ function fastTriviaScanner(trivia2) {
1185
+ const cached = fastTriviaCache.get(trivia2);
1186
+ if (cached !== void 0) return cached;
1187
+ const scanner = buildFastTriviaScanner(trivia2);
1188
+ fastTriviaCache.set(trivia2, scanner);
1189
+ return scanner;
1190
+ }
1191
+ function buildFastTriviaScanner(trivia2) {
1192
+ const core = trivia2._def.tag === "trivia" ? trivia2._def.parser : trivia2;
1193
+ const direct = regexTriviaScanner(core);
1194
+ if (direct) return direct;
1195
+ const repeat = core._def.tag === "oneOrMore" || core._def.tag === "many" && core._def.min >= 1 ? core._def.parser : null;
1196
+ if (!repeat) return null;
1197
+ const one = regexTriviaScanner(repeat);
1198
+ if (one) return loopScanner([one]);
1199
+ if (repeat._def.tag !== "choice") return null;
1200
+ const arms = repeat._def.parsers.map(regexTriviaScanner);
1201
+ if (arms.some((s) => s === null)) return null;
1202
+ return loopScanner(arms);
1203
+ }
1204
+ function loopScanner(arms) {
1205
+ return (input, cur) => {
1206
+ let pos = cur;
1207
+ scan: while (pos < input.length) {
1208
+ for (const arm of arms) {
1209
+ const end = arm(input, pos);
1210
+ if (end > pos) {
1211
+ pos = end;
1212
+ continue scan;
1213
+ }
1214
+ }
1215
+ break;
1216
+ }
1217
+ return pos;
1218
+ };
1219
+ }
1220
+ function regexTriviaScanner(parser2) {
1221
+ if (parser2._def.tag !== "regex" || parser2._def.flags) return null;
1222
+ const ws = whitespaceSource(parser2._def.source);
1223
+ if (ws) return ws;
1224
+ return blockCommentSource(parser2._def.source) ? scanBlockComment : null;
1225
+ }
1226
+ function whitespaceSource(source) {
1227
+ const m = /^\[([ \\tnrf]+)\][*+]$/.exec(source);
1228
+ if (!m) return null;
1229
+ const chars = /* @__PURE__ */ new Set();
1230
+ for (let i = 0; i < m[1].length; i++) {
1231
+ const ch = m[1][i];
1232
+ if (ch !== "\\") {
1233
+ chars.add(ch.charCodeAt(0));
1234
+ continue;
1235
+ }
1236
+ const esc = m[1][++i];
1237
+ if (esc === "t") chars.add(9);
1238
+ else if (esc === "n") chars.add(10);
1239
+ else if (esc === "r") chars.add(13);
1240
+ else if (esc === "f") chars.add(12);
1241
+ else return null;
1242
+ }
1243
+ return (input, cur) => {
1244
+ let pos = cur;
1245
+ while (pos < input.length && chars.has(input.charCodeAt(pos))) pos++;
1246
+ return pos;
1247
+ };
1248
+ }
1249
+ function blockCommentSource(source) {
1250
+ return source === "\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/" || source === "\\/\\*[^]*?\\*\\/";
1251
+ }
1252
+ function scanBlockComment(input, cur) {
1253
+ if (input.charCodeAt(cur) !== 47 || input.charCodeAt(cur + 1) !== 42) return cur;
1254
+ const close = input.indexOf("*/", cur + 2);
1255
+ return close === -1 ? cur : close + 2;
1256
+ }
1056
1257
 
1057
1258
  // src/combinators/sequence.ts
1058
1259
  function sequence(...parsers) {
@@ -1191,11 +1392,15 @@ function optional(combinator) {
1191
1392
  canMatchNewline: combinator._meta.canMatchNewline,
1192
1393
  isTrivia: false
1193
1394
  };
1395
+ const firstSetSkippable = !matchesEmpty(combinator);
1194
1396
  return {
1195
1397
  _tag: "optional",
1196
1398
  _meta: meta,
1197
1399
  _def: { tag: "optional", parser: combinator },
1198
1400
  parse(input, pos, ctx) {
1401
+ if (firstSetSkippable && ctx._probe === void 0 && !startsFirstSet(combinator, input, pos)) {
1402
+ return { ok: true, value: null, span: { start: pos, end: pos } };
1403
+ }
1199
1404
  const mark = saveTriviaMark(ctx);
1200
1405
  const result = combinator.parse(input, pos, ctx);
1201
1406
  if (result.ok) return result;
@@ -1204,6 +1409,15 @@ function optional(combinator) {
1204
1409
  }
1205
1410
  };
1206
1411
  }
1412
+ function startsFirstSet(combinator, input, pos) {
1413
+ const fs = combinator._meta.firstSet;
1414
+ if (fs.kind === "any") return true;
1415
+ if (fs.kind === "empty") return false;
1416
+ const code = input.codePointAt(pos);
1417
+ if (code === void 0) return false;
1418
+ for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
1419
+ return false;
1420
+ }
1207
1421
  function sepBy(combinator, separator) {
1208
1422
  const meta = {
1209
1423
  firstSet: combinator._meta.firstSet,
@@ -1284,6 +1498,13 @@ function ref() {
1284
1498
  define(p) {
1285
1499
  if (resolved) throw new Error("ref<T>() already defined");
1286
1500
  resolved = p;
1501
+ meta.firstSet = p._meta.firstSet;
1502
+ meta.canMatchNewline = p._meta.canMatchNewline;
1503
+ meta.isTrivia = p._meta.isTrivia;
1504
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
1505
+ else delete meta.triviaKindLabels;
1506
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
1507
+ else delete meta.disjoint;
1287
1508
  }
1288
1509
  };
1289
1510
  return slot;
@@ -1324,6 +1545,9 @@ function markUnusedValues(root) {
1324
1545
  case "transform":
1325
1546
  visit(def.parser, true);
1326
1547
  return;
1548
+ case "field":
1549
+ visit(def.parser, true);
1550
+ return;
1327
1551
  case "skip":
1328
1552
  visit(def.main, consumed);
1329
1553
  visit(def.skipped, false);
@@ -1375,6 +1599,11 @@ function markUnusedValues(root) {
1375
1599
  }
1376
1600
 
1377
1601
  // src/combinators/parser.ts
1602
+ function tagRule(r, key) {
1603
+ ;
1604
+ r._ruleName = key;
1605
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1606
+ }
1378
1607
  function rules(factory) {
1379
1608
  const cache = {};
1380
1609
  const proxy = new Proxy(cache, {
@@ -1383,7 +1612,7 @@ function rules(factory) {
1383
1612
  const record = target;
1384
1613
  if (!(key in record)) {
1385
1614
  const r = ref();
1386
- r._ruleName = key;
1615
+ tagRule(r, key);
1387
1616
  record[key] = r;
1388
1617
  }
1389
1618
  return record[key];
@@ -1394,12 +1623,12 @@ function rules(factory) {
1394
1623
  const placeholder = cache[key];
1395
1624
  const parser2 = definitions[key];
1396
1625
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1397
- ;
1626
+ tagRule(parser2, key);
1398
1627
  placeholder.define(parser2);
1399
1628
  placeholder._meta.firstSet = parser2._meta.firstSet;
1400
1629
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1401
1630
  } else {
1402
- ;
1631
+ tagRule(parser2, key);
1403
1632
  cache[key] = parser2;
1404
1633
  }
1405
1634
  }
@@ -1456,44 +1685,130 @@ function buildReadsTrivia(def) {
1456
1685
  const src = def.buildSrc ?? def.build.toString();
1457
1686
  const arity = confirmedBuildArity(src);
1458
1687
  if (arity === null) return true;
1459
- return arity >= 4;
1688
+ return arity >= 5;
1460
1689
  }
1461
1690
  function buildReadsState(def) {
1462
1691
  if (!def.build) return true;
1463
1692
  const src = def.buildSrc ?? def.build.toString();
1464
1693
  const arity = confirmedBuildArity(src);
1465
1694
  if (arity === null) return true;
1466
- return arity >= 5;
1695
+ return arity >= 6;
1696
+ }
1697
+
1698
+ // src/compiler/fields.ts
1699
+ function buildReadsFields(def) {
1700
+ if (!def.build) return true;
1701
+ const src = def.buildSrc ?? def.build.toString();
1702
+ const arity = confirmedBuildArity(src);
1703
+ if (arity === null) return true;
1704
+ return arity >= 2;
1705
+ }
1706
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1707
+ if (seen.has(p)) return false;
1708
+ seen.add(p);
1709
+ const d = p._def;
1710
+ switch (d.tag) {
1711
+ case "field":
1712
+ return true;
1713
+ case "node":
1714
+ return false;
1715
+ case "lazy": {
1716
+ try {
1717
+ return parserHasOwnFields(d.thunk(), seen);
1718
+ } catch {
1719
+ return false;
1720
+ }
1721
+ }
1722
+ case "sequence":
1723
+ case "choice":
1724
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1725
+ case "sepBy":
1726
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1727
+ case "skip":
1728
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1729
+ case "grammar":
1730
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1731
+ case "scanTo":
1732
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1733
+ case "recover":
1734
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1735
+ case "many":
1736
+ case "oneOrMore":
1737
+ case "optional":
1738
+ case "transform":
1739
+ case "trivia":
1740
+ case "token":
1741
+ case "label":
1742
+ case "expect":
1743
+ case "withCtx":
1744
+ case "not":
1745
+ return parserHasOwnFields(d.parser, seen);
1746
+ default:
1747
+ return false;
1748
+ }
1749
+ }
1750
+ function buildFieldMap(captures) {
1751
+ if (!captures?.length) return void 0;
1752
+ const out = {};
1753
+ for (const cap of captures) {
1754
+ const entry = { value: cap.value, span: cap.span };
1755
+ const current = out[cap.name];
1756
+ if (current === void 0) out[cap.name] = entry;
1757
+ else if (Array.isArray(current)) current.push(entry);
1758
+ else out[cap.name] = [current, entry];
1759
+ }
1760
+ return out;
1467
1761
  }
1468
1762
 
1469
1763
  // src/combinators/node.ts
1470
- function collapseChild(child) {
1764
+ function unwrapChild(child) {
1471
1765
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1472
1766
  }
1473
- function node(type, combinator, build, opts) {
1767
+ function isCstChild(value) {
1768
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1769
+ }
1770
+ function missingInferredType() {
1771
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1772
+ }
1773
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1774
+ const hasExplicitType = typeof typeOrCombinator === "string";
1775
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1776
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1777
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1778
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1779
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1474
1780
  const meta = {
1475
1781
  firstSet: combinator._meta.firstSet,
1476
1782
  canMatchNewline: combinator._meta.canMatchNewline,
1477
1783
  isTrivia: false
1478
1784
  };
1785
+ const unwrap = opts?.unwrap === true;
1479
1786
  const collapse = opts?.collapse === true;
1480
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1787
+ if (unwrap && collapse) {
1788
+ throw new Error("node() options cannot set both unwrap and collapse");
1789
+ }
1790
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1481
1791
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1482
1792
  const clonesState = build ? buildReadsState(def) : true;
1793
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1483
1794
  return {
1484
1795
  _tag: "node",
1485
1796
  _meta: meta,
1486
1797
  _def: def,
1487
1798
  parse(input, pos, ctx) {
1488
1799
  const saved = beginCstNodeCapture(ctx);
1800
+ const savedFields = ctx._fields;
1801
+ ctx._fields = capturesFields ? [] : void 0;
1489
1802
  if (!capturesTrivia) ctx.captureTrivia = false;
1490
1803
  const r = combinator.parse(input, pos, ctx);
1804
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1805
+ ctx._fields = savedFields;
1491
1806
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1492
1807
  if (!r.ok) return r;
1493
1808
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1494
- 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 };
1495
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1496
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1809
+ const nodeType = def.type ?? missingInferredType();
1810
+ 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 };
1811
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1497
1812
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1498
1813
  pushCstChild(ctx, built, rawEntry);
1499
1814
  }
@@ -1511,7 +1826,7 @@ function transform(combinator, fn) {
1511
1826
  parse(input, pos, ctx) {
1512
1827
  const result = combinator.parse(input, pos, ctx);
1513
1828
  if (!result.ok) return result;
1514
- return { ...result, value: fn(result.value, result.span) };
1829
+ return { ok: true, value: fn(result.value, result.span), span: result.span };
1515
1830
  }
1516
1831
  };
1517
1832
  }
@@ -1525,7 +1840,7 @@ function skip(main, skipped) {
1525
1840
  if (!result.ok) return result;
1526
1841
  const s = skipped.parse(input, result.span.end, ctx);
1527
1842
  if (!s.ok) return result;
1528
- return { ...result, span: { start: result.span.start, end: s.span.end } };
1843
+ return { ok: true, value: result.value, span: { start: result.span.start, end: s.span.end } };
1529
1844
  }
1530
1845
  };
1531
1846
  }
@@ -1549,7 +1864,20 @@ function label(name, combinator) {
1549
1864
  _def: { tag: "label", label: name, parser: combinator },
1550
1865
  parse(input, pos, ctx) {
1551
1866
  const result = combinator.parse(input, pos, ctx);
1552
- if (!result.ok) return { ...result, expected: [name] };
1867
+ if (!result.ok) return { ok: false, expected: [name], span: result.span };
1868
+ return result;
1869
+ }
1870
+ };
1871
+ }
1872
+ function field(name, combinator) {
1873
+ return {
1874
+ _tag: combinator._tag,
1875
+ _meta: combinator._meta,
1876
+ _def: { tag: "field", name, parser: combinator },
1877
+ parse(input, pos, ctx) {
1878
+ const result = combinator.parse(input, pos, ctx);
1879
+ if (!result.ok) return result;
1880
+ ctx._fields?.push({ name, value: result.value, span: result.span });
1553
1881
  return result;
1554
1882
  }
1555
1883
  };
@@ -1763,9 +2091,9 @@ function expect(combinator, label2) {
1763
2091
 
1764
2092
  // src/compiler/scannable-run.ts
1765
2093
  var BACKSLASH = 92;
1766
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
2094
+ var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1767
2095
  var META = new Set("()[]{}*+?|^$.".split(""));
1768
- var SPACE_RANGES = [
2096
+ var SPACE_RANGES2 = [
1769
2097
  [9, 13],
1770
2098
  [32, 32],
1771
2099
  [160, 160],
@@ -1778,9 +2106,9 @@ var SPACE_RANGES = [
1778
2106
  [12288, 12288],
1779
2107
  [65279, 65279]
1780
2108
  ];
1781
- function shorthandRanges(ch) {
2109
+ function shorthandRanges2(ch) {
1782
2110
  if (ch === "d") return [[48, 57]];
1783
- if (ch === "s") return SPACE_RANGES;
2111
+ if (ch === "s") return SPACE_RANGES2;
1784
2112
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
1785
2113
  }
1786
2114
  function readUnicodeEscape(body, i) {
@@ -1804,8 +2132,8 @@ function parseClassRanges(body) {
1804
2132
  const e = body[i + 1];
1805
2133
  if (e === void 0) return null;
1806
2134
  i += 2;
1807
- if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1808
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
2135
+ if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
2136
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1809
2137
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1810
2138
  return { cp: e.codePointAt(0) };
1811
2139
  }
@@ -1831,9 +2159,9 @@ function parseClassRanges(body) {
1831
2159
  return ranges.length ? ranges : null;
1832
2160
  }
1833
2161
  function classToRanges(cls) {
1834
- if (cls === "\\d") return shorthandRanges("d");
1835
- if (cls === "\\w") return shorthandRanges("w");
1836
- if (cls === "\\s") return shorthandRanges("s");
2162
+ if (cls === "\\d") return shorthandRanges2("d");
2163
+ if (cls === "\\w") return shorthandRanges2("w");
2164
+ if (cls === "\\s") return shorthandRanges2("s");
1837
2165
  const body = cls.slice(1, -1);
1838
2166
  if (body.startsWith("^")) return null;
1839
2167
  return parseClassRanges(body);
@@ -1851,7 +2179,7 @@ function literalCodePoints(frag) {
1851
2179
  if (ch === "\\") {
1852
2180
  const e = frag[i + 1];
1853
2181
  if (e === void 0) return null;
1854
- out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
2182
+ out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
1855
2183
  i += 2;
1856
2184
  continue;
1857
2185
  }
@@ -1944,7 +2272,7 @@ function parseSeqParts(source) {
1944
2272
  if (e === "d" || e === "w" || e === "s") {
1945
2273
  flush();
1946
2274
  i += 2;
1947
- i += runFrom(shorthandRanges(e), false, source[i]);
2275
+ i += runFrom(shorthandRanges2(e), false, source[i]);
1948
2276
  continue;
1949
2277
  }
1950
2278
  const uni = readUnicodeEscape(source, i);
@@ -1953,8 +2281,8 @@ function parseSeqParts(source) {
1953
2281
  cp2 = uni.cp;
1954
2282
  i = uni.next;
1955
2283
  } else if (e === void 0) return null;
1956
- else if (e in CLASS_ESCAPES) {
1957
- cp2 = CLASS_ESCAPES[e];
2284
+ else if (e in CLASS_ESCAPES2) {
2285
+ cp2 = CLASS_ESCAPES2[e];
1958
2286
  i += 2;
1959
2287
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1960
2288
  else {
@@ -2061,7 +2389,7 @@ function rangesSubset(sub, sup) {
2061
2389
  }
2062
2390
  function parseClassOperand(body) {
2063
2391
  if (body === "\\d" || body === "\\w" || body === "\\s") {
2064
- return { ranges: shorthandRanges(body[1]), negated: false };
2392
+ return { ranges: shorthandRanges2(body[1]), negated: false };
2065
2393
  }
2066
2394
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
2067
2395
  let inner = body.slice(1, -1);
@@ -2494,7 +2822,7 @@ function parseScanShapeCore(source) {
2494
2822
  }
2495
2823
  m = /^\\([dws])([+*])$/.exec(source);
2496
2824
  if (m) {
2497
- return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2825
+ return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2498
2826
  }
2499
2827
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2500
2828
  if (m) {
@@ -3036,21 +3364,22 @@ function emitScannableTerminal(shape, c) {
3036
3364
  }
3037
3365
 
3038
3366
  // src/compiler/inline-build.ts
3039
- 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*$/;
3367
+ 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*$/;
3040
3368
  function escapeRegExp(s) {
3041
3369
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3042
3370
  }
3043
3371
  function looseMkBuildRe(type) {
3044
3372
  return new RegExp(
3045
- 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*$`
3373
+ 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*$`
3046
3374
  );
3047
3375
  }
3048
3376
  function analyzeMkInlineBuild(def) {
3049
3377
  if (!def.build) return null;
3378
+ if (def.type === void 0) return null;
3050
3379
  const src = (def.buildSrc ?? def.build.toString()).trim();
3051
3380
  const strict = src.match(MK_BUILD_RE);
3052
3381
  if (strict) {
3053
- const mkType = strict[2];
3382
+ const mkType = strict[6];
3054
3383
  return mkType === def.type ? def.type : null;
3055
3384
  }
3056
3385
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3377,8 +3706,9 @@ function planDisjointDispatch(parsers) {
3377
3706
  }
3378
3707
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3379
3708
  }
3380
- function captureRestoreBody(mL, mR, mTl, mLg) {
3381
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3709
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3710
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3711
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3382
3712
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3383
3713
  }
3384
3714
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3426,6 +3756,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3426
3756
  // Delegating wrappers: defer to the wrapped parser.
3427
3757
  case "transform":
3428
3758
  case "label":
3759
+ case "field":
3429
3760
  case "expect":
3430
3761
  case "withCtx":
3431
3762
  case "grammar":
@@ -3472,6 +3803,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3472
3803
  case "optional":
3473
3804
  case "transform":
3474
3805
  case "label":
3806
+ case "field":
3475
3807
  case "expect":
3476
3808
  case "withCtx":
3477
3809
  case "grammar":
@@ -3509,22 +3841,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3509
3841
  ctx.recordFail = savedRecord;
3510
3842
  const ind0 = ind(ctx);
3511
3843
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3844
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3512
3845
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3513
3846
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3514
3847
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3848
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3515
3849
  const stmts = [
3516
3850
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3517
3851
  ...mL ? [
3518
3852
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3519
3853
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3520
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3854
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3855
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3521
3856
  ] : [],
3522
3857
  `${ind0}${lbl}: {`,
3523
3858
  ...r.stmts,
3524
3859
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3525
3860
  `${ind0}}`,
3526
3861
  ...mL ? [
3527
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3862
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3528
3863
  ] : []
3529
3864
  ];
3530
3865
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3751,6 +4086,7 @@ function failsAtStart(p) {
3751
4086
  return true;
3752
4087
  case "transform":
3753
4088
  case "label":
4089
+ case "field":
3754
4090
  return failsAtStart(d.parser);
3755
4091
  default:
3756
4092
  return false;
@@ -3770,6 +4106,7 @@ function canMatchEmptyAtStart(p) {
3770
4106
  return true;
3771
4107
  case "transform":
3772
4108
  case "label":
4109
+ case "field":
3773
4110
  return canMatchEmptyAtStart(d.parser);
3774
4111
  case "literal":
3775
4112
  return d.value.length === 0;
@@ -3952,11 +4289,13 @@ function emitFirstMatch(def, ctx, pos) {
3952
4289
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3953
4290
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
3954
4291
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4292
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
3955
4293
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
3956
4294
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
3957
4295
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
3958
4296
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
3959
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4297
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4298
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
3960
4299
  const failSlot = atStart ? staticFx : "_ctx._fx";
3961
4300
  stmts.push(`${ind0}if (${skipCond}) {`);
3962
4301
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -3966,7 +4305,8 @@ function emitFirstMatch(def, ctx, pos) {
3966
4305
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3967
4306
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3968
4307
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3969
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4308
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4309
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
3970
4310
  );
3971
4311
  }
3972
4312
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4359,6 +4699,9 @@ function emitToken(def, ctx, pos) {
4359
4699
  };
4360
4700
  }
4361
4701
  function emitNode(def, ctx, pos) {
4702
+ if (def.type === void 0) {
4703
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4704
+ }
4362
4705
  const structural = def.build === void 0;
4363
4706
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4364
4707
  let buildIdx = null;
@@ -4370,25 +4713,36 @@ function emitNode(def, ctx, pos) {
4370
4713
  const i = ind(ctx);
4371
4714
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4372
4715
  const clonesState = !structural && buildReadsState(def);
4716
+ const hasFields = parserHasOwnFields(def.parser);
4717
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4373
4718
  const chV = v(ctx, "_ch");
4374
4719
  const rawV = v(ctx, "_raw");
4375
4720
  const capTLv = structural ? v(ctx, "_ctl") : null;
4376
4721
  const capSTv = structural ? v(ctx, "_cst") : null;
4722
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4377
4723
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4378
4724
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4379
4725
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4380
4726
  if (structural) ctx.needsHostReads = true;
4381
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4727
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4728
+ 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)` : ""}
4382
4729
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4383
4730
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4731
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4732
+ const sf = hasFields ? v(ctx, "_sf") : null;
4733
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4734
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4384
4735
  const stmts = [
4385
4736
  allocStmt,
4386
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4387
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4737
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4738
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4388
4739
  ];
4389
4740
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4390
4741
  stmts.push(...innerStmts);
4391
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4742
+ if (sf && fArr) {
4743
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4744
+ }
4745
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4392
4746
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4393
4747
  let stV = "undefined";
4394
4748
  if (structural) {
@@ -4398,14 +4752,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4398
4752
  stV = v(ctx, "_nst");
4399
4753
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4400
4754
  }
4755
+ if (sf && fArr) {
4756
+ const fe = v(ctx, "_fe");
4757
+ const cur = v(ctx, "_fc");
4758
+ stmts.push(
4759
+ `${i}let ${fObj} = undefined`,
4760
+ `${i}if (${fArr} && ${fArr}.length) {`,
4761
+ `${i} ${fObj} = {}`,
4762
+ `${i} for (const ${fe} of ${fArr}) {`,
4763
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4764
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4765
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4766
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4767
+ `${i} }`,
4768
+ `${i}}`
4769
+ );
4770
+ }
4401
4771
  const ndV = v(ctx, "_nd");
4402
- 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})`;
4403
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4404
- 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;
4772
+ 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})`;
4773
+ 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;
4774
+ 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;
4775
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4776
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4777
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4405
4778
  stmts.push(
4406
4779
  `${i}const ${ndV} = ${finalExpr}`,
4407
4780
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4408
- `${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} } })`
4781
+ `${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} } })`
4409
4782
  );
4410
4783
  return { stmts, valueVar: ndV, endVar };
4411
4784
  }
@@ -4620,6 +4993,17 @@ function emitDispatch(p, ctx, pos) {
4620
4993
  endVar: inner.endVar
4621
4994
  };
4622
4995
  }
4996
+ case "field": {
4997
+ const inner = emit(def.parser, ctx, pos);
4998
+ return {
4999
+ stmts: [
5000
+ ...inner.stmts,
5001
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
5002
+ ],
5003
+ valueVar: inner.valueVar,
5004
+ endVar: inner.endVar
5005
+ };
5006
+ }
4623
5007
  case "grammar": {
4624
5008
  const savedTrivia = ctx.activeTrivia;
4625
5009
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4721,6 +5105,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4721
5105
  case "trivia":
4722
5106
  case "token":
4723
5107
  case "label":
5108
+ case "field":
4724
5109
  case "optional":
4725
5110
  case "many":
4726
5111
  case "oneOrMore":
@@ -4757,6 +5142,7 @@ function childrenOf(def) {
4757
5142
  case "trivia":
4758
5143
  case "token":
4759
5144
  case "label":
5145
+ case "field":
4760
5146
  case "grammar":
4761
5147
  case "not":
4762
5148
  case "node":
@@ -4998,6 +5384,11 @@ function assertRuleName(name) {
4998
5384
  );
4999
5385
  }
5000
5386
  }
5387
+ function publicRuleWrapperSource(rule, fnSource) {
5388
+ const labels = rule._meta.triviaKindLabels;
5389
+ if (labels === void 0) return fnSource;
5390
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5391
+ }
5001
5392
  function compileLinkable(ruleMapArg, ns) {
5002
5393
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
5003
5394
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
@@ -5100,7 +5491,7 @@ function compileLinkable(ruleMapArg, ns) {
5100
5491
  ctx.recordFail = savedRec;
5101
5492
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5102
5493
  }
5103
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5494
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5104
5495
  }
5105
5496
  if (ctx.runtimeParsers.length !== 0) return null;
5106
5497
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5123,14 +5514,14 @@ function compileLinkable(ruleMapArg, ns) {
5123
5514
  ...privateFns
5124
5515
  ].filter(Boolean);
5125
5516
  const wrappers = /* @__PURE__ */ new Map();
5126
- for (const { key, r } of perEntry) {
5127
- wrappers.set(key, [
5517
+ for (const { key, rule, r } of perEntry) {
5518
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5128
5519
  `function(input, _pos, _ctx) {`,
5129
5520
  ` let pos = _pos`,
5130
5521
  ...r.stmts,
5131
5522
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5132
5523
  `}`
5133
- ].join("\n"));
5524
+ ].join("\n")));
5134
5525
  }
5135
5526
  const firstSets = /* @__PURE__ */ new Map();
5136
5527
  for (const [key, rule] of ruleMap) {
@@ -5333,6 +5724,7 @@ function evalRuleMapIR(ir) {
5333
5724
  "skip",
5334
5725
  "trivia",
5335
5726
  "label",
5727
+ "field",
5336
5728
  "expect",
5337
5729
  "_tf",
5338
5730
  "_nd",
@@ -5359,6 +5751,7 @@ function evalRuleMapIR(ir) {
5359
5751
  skip,
5360
5752
  trivia,
5361
5753
  label,
5754
+ field,
5362
5755
  expect,
5363
5756
  _tf,
5364
5757
  _nd
@@ -5373,9 +5766,26 @@ function linkable(rulesMap, ns) {
5373
5766
  if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
5374
5767
  return pieces;
5375
5768
  }
5376
- function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
5769
+ function normalizeCstCollapse(collapse) {
5770
+ if (collapse === true) return () => true;
5771
+ if (Array.isArray(collapse)) {
5772
+ const types = new Set(collapse);
5773
+ return (type) => types.has(type);
5774
+ }
5775
+ return typeof collapse === "function" ? collapse : void 0;
5776
+ }
5777
+ function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
5377
5778
  return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
5378
5779
  }
5780
+ function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
5781
+ if (typeof typeOrOptions === "string") {
5782
+ return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
5783
+ }
5784
+ const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
5785
+ const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
5786
+ if (collapse) host._parsemanCstCollapse = collapse;
5787
+ return host;
5788
+ }
5379
5789
  function pick(grammar, names) {
5380
5790
  const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
5381
5791
  const keep = /* @__PURE__ */ new Set();
@@ -5407,8 +5817,9 @@ function fusedBody(pieces) {
5407
5817
  }
5408
5818
  }
5409
5819
  const contributing = new Set(winner.values());
5410
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5411
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5820
+ const contributingPieces = [...contributing];
5821
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
5822
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5412
5823
  const lines = [
5413
5824
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5414
5825
  "const _pfFail = {}",
@@ -5416,7 +5827,7 @@ function fusedBody(pieces) {
5416
5827
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5417
5828
  ...needsHostReads ? [HOST_READS_DECL] : [],
5418
5829
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5419
- ...[...contributing].flatMap((p) => p.prelude),
5830
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5420
5831
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5421
5832
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5422
5833
  ];
@@ -5439,7 +5850,7 @@ function fusedBody(pieces) {
5439
5850
  }
5440
5851
  );
5441
5852
  const env = {};
5442
- for (const p of contributing) {
5853
+ for (const p of contributingPieces) {
5443
5854
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5444
5855
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5445
5856
  }
@@ -5459,14 +5870,23 @@ function materializePiece(p) {
5459
5870
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5460
5871
  return pieces;
5461
5872
  }
5462
- function itemPieces(item) {
5873
+ function nextComposeNs(used) {
5874
+ let ns;
5875
+ do {
5876
+ ns = `_lk${_nsCounter++}_`;
5877
+ } while (used.has(ns));
5878
+ used.add(ns);
5879
+ return ns;
5880
+ }
5881
+ function itemPieces(item, used) {
5463
5882
  const carried = item[COMPOSED_PIECES];
5464
- if (Array.isArray(carried)) return carried.map(materializePiece);
5465
- if (item.ruleFns instanceof Map) return [item];
5466
- return [linkable(item)];
5883
+ const pieces = Array.isArray(carried) ? carried.map(materializePiece) : item.ruleFns instanceof Map ? [item] : [linkable(item, nextComposeNs(used))];
5884
+ for (const p of pieces) used.add(p.ns);
5885
+ return pieces;
5467
5886
  }
5468
5887
  function compose(items) {
5469
- const pieces = items.flatMap(itemPieces);
5888
+ const used = /* @__PURE__ */ new Set();
5889
+ const pieces = items.flatMap((item) => itemPieces(item, used));
5470
5890
  const map = fuseRules(pieces);
5471
5891
  Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
5472
5892
  return map;