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.js CHANGED
@@ -252,7 +252,8 @@ function saveCstMark(ctx) {
252
252
  return {
253
253
  raw: cstRawLen(ctx),
254
254
  tlog: cstTlLen(ctx),
255
- leaves: cstLeavesLen(ctx)
255
+ leaves: cstLeavesLen(ctx),
256
+ fields: ctx._fields?.length ?? 0
256
257
  };
257
258
  }
258
259
  function rollbackBufList(b, keyMulti, keySingle, len) {
@@ -276,11 +277,13 @@ function rollbackCstCapture(ctx, mark) {
276
277
  if (mark.tlog === 0) b.tl = void 0;
277
278
  else b.tl.length = mark.tlog;
278
279
  }
280
+ if (ctx._fields) ctx._fields.length = mark.fields;
279
281
  return;
280
282
  }
281
283
  if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
282
284
  if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
283
285
  if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
286
+ if (ctx._fields) ctx._fields.length = mark.fields;
284
287
  }
285
288
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
286
289
  const insertIdx = cstRawLen(ctx);
@@ -349,13 +352,12 @@ function literal(value, opts = {}) {
349
352
  if (end > input.length) {
350
353
  return failAt(ctx, [JSON.stringify(value)], pos);
351
354
  }
352
- const slice = input.slice(pos, end);
353
- const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
354
- if (matched) {
355
+ const matchedValue = caseInsensitive ? input.slice(pos, end) : value;
356
+ if (caseInsensitive ? asciiFoldEq(matchedValue, value) : input.startsWith(value, pos)) {
355
357
  const span = { start: pos, end };
356
- const leaf = { _tag: "leaf", value: slice, span };
358
+ const leaf = { _tag: "leaf", value: matchedValue, span };
357
359
  if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf);
358
- return { ok: true, value: slice, span };
360
+ return { ok: true, value: matchedValue, span };
359
361
  }
360
362
  return failAt(ctx, [JSON.stringify(value)], pos);
361
363
  }
@@ -368,10 +370,102 @@ function registerRegexAnalyzer(analyzer) {
368
370
  firstSetAnalyzer = analyzer;
369
371
  }
370
372
  var permissiveFirstSet = () => ({ firstSet: any(), canMatchNewline: true });
373
+ var SCAN_BAIL_AT = 64;
374
+ var SPACE_RANGES = [
375
+ [9, 13],
376
+ [32, 32],
377
+ [160, 160],
378
+ [5760, 5760],
379
+ [8192, 8202],
380
+ [8232, 8233],
381
+ [8239, 8239],
382
+ [8287, 8287],
383
+ [12288, 12288],
384
+ [65279, 65279]
385
+ ];
386
+ var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
387
+ function shorthandRanges(ch) {
388
+ if (ch === "d") return [[48, 57]];
389
+ if (ch === "s") return SPACE_RANGES;
390
+ return [[48, 57], [65, 90], [97, 122], [95, 95]];
391
+ }
392
+ function inRanges(cp, ranges) {
393
+ for (let i = 0; i < ranges.length; i++) {
394
+ const [lo, hi] = ranges[i];
395
+ if (cp >= lo && cp <= hi) return true;
396
+ }
397
+ return false;
398
+ }
399
+ function readClassRanges(body) {
400
+ if (body.startsWith("^")) return null;
401
+ const ranges = [];
402
+ let i = 0;
403
+ const readAtom = () => {
404
+ const ch = body[i];
405
+ if (ch === void 0) return null;
406
+ if (ch === "\\") {
407
+ const esc = body[i + 1];
408
+ if (esc === void 0) return null;
409
+ i += 2;
410
+ if (esc === "d" || esc === "w" || esc === "s") return { ranges: shorthandRanges(esc) };
411
+ if (esc in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[esc] };
412
+ if (esc >= "a" && esc <= "z" || esc >= "A" && esc <= "Z") return null;
413
+ return { cp: esc.charCodeAt(0) };
414
+ }
415
+ i++;
416
+ return { cp: ch.charCodeAt(0) };
417
+ };
418
+ while (i < body.length) {
419
+ const lo = readAtom();
420
+ if (!lo) return null;
421
+ if ("ranges" in lo) {
422
+ ranges.push(...lo.ranges);
423
+ continue;
424
+ }
425
+ if (body[i] === "-" && body[i + 1] !== void 0 && body[i + 1] !== "]") {
426
+ i++;
427
+ const hi = readAtom();
428
+ if (!hi || "ranges" in hi) return null;
429
+ ranges.push([lo.cp, hi.cp]);
430
+ } else {
431
+ ranges.push([lo.cp, lo.cp]);
432
+ }
433
+ }
434
+ return ranges.length ? ranges : null;
435
+ }
436
+ function shortScanner(source, flags) {
437
+ if (/[imsuvy]/.test(flags)) return null;
438
+ let ranges = null;
439
+ let quant = "";
440
+ if (source[0] === "[") {
441
+ let end = 1;
442
+ while (end < source.length && source[end] !== "]") {
443
+ if (source[end] === "\\") end += 2;
444
+ else end++;
445
+ }
446
+ if (source[end] !== "]") return null;
447
+ ranges = readClassRanges(source.slice(1, end));
448
+ quant = source.slice(end + 1);
449
+ } else if (source[0] === "\\" && (source[1] === "d" || source[1] === "w" || source[1] === "s")) {
450
+ ranges = shorthandRanges(source[1]);
451
+ quant = source.slice(2);
452
+ }
453
+ if (!ranges || quant !== "+" && quant !== "*") return null;
454
+ const minOne = quant === "+";
455
+ return (input, pos) => {
456
+ let end = pos;
457
+ while (end < input.length && inRanges(input.charCodeAt(end), ranges)) {
458
+ end++;
459
+ if (end - pos >= SCAN_BAIL_AT) return void 0;
460
+ }
461
+ return minOne && end === pos ? null : end;
462
+ };
463
+ }
371
464
  function regex(pattern, flags = "") {
372
465
  const source = typeof pattern === "string" ? pattern : pattern.source;
373
466
  const resolvedFlags = typeof pattern === "string" ? flags : pattern.flags;
374
467
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
468
+ const scan = shortScanner(source, resolvedFlags);
375
469
  const { firstSet, canMatchNewline } = (firstSetAnalyzer ?? permissiveFirstSet)(source);
376
470
  const meta = { firstSet, canMatchNewline, isTrivia: false };
377
471
  return {
@@ -379,6 +473,15 @@ function regex(pattern, flags = "") {
379
473
  _meta: meta,
380
474
  _def: { tag: "regex", source, flags: resolvedFlags },
381
475
  parse(input, pos, ctx) {
476
+ const scanEnd = scan?.(input, pos);
477
+ if (scanEnd !== void 0) {
478
+ if (scanEnd === null) return failAt(ctx, [`/${source}/`], pos);
479
+ const value = input.slice(pos, scanEnd);
480
+ const span2 = { start: pos, end: scanEnd };
481
+ const leaf2 = { _tag: "leaf", value, span: span2 };
482
+ if (cstCaptureActive(ctx)) pushCstLeaf(ctx, leaf2);
483
+ return { ok: true, value, span: span2 };
484
+ }
382
485
  anchored.lastIndex = pos;
383
486
  const m = anchored.exec(input);
384
487
  if (m === null) {
@@ -562,13 +665,10 @@ function choice(...args) {
562
665
  };
563
666
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
564
667
  const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
565
- let greedyRe = null;
566
668
  let greedyLitMap = null;
567
669
  let sortedParsers = null;
670
+ const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
568
671
  if (strategy?.tag === "greedyClassify") {
569
- const regexDef = getCoreRegexDef(parsers[strategy.superIndex]);
570
- const flags = "y" + regexDef.flags.replace(/[gy]/g, "");
571
- greedyRe = new RegExp(regexDef.source, flags);
572
672
  greedyLitMap = /* @__PURE__ */ new Map();
573
673
  for (let i = 0; i < parsers.length; i++) {
574
674
  if (i === strategy.superIndex) continue;
@@ -593,14 +693,21 @@ function choice(...args) {
593
693
  const expected = [];
594
694
  if (disjoint && pos < input.length) {
595
695
  const code = input.codePointAt(pos);
596
- for (const parser2 of parsers) {
597
- if (inFirstSet(code, parser2._meta.firstSet)) {
598
- const result = parser2.parse(input, pos, ctx);
599
- if (result.ok) return result;
600
- expected.push(...result.expected);
601
- return { ok: false, expected, span: { start: pos, end: pos } };
696
+ let parser2 = code < 128 ? asciiDispatch?.[code] ?? null : null;
697
+ if (!parser2) {
698
+ for (const p of parsers) {
699
+ if (inFirstSet(code, p._meta.firstSet)) {
700
+ parser2 = p;
701
+ break;
702
+ }
602
703
  }
603
704
  }
705
+ if (parser2) {
706
+ const result = parser2.parse(input, pos, ctx);
707
+ if (result.ok) return result;
708
+ expected.push(...result.expected);
709
+ return { ok: false, expected, span: { start: pos, end: pos } };
710
+ }
604
711
  return {
605
712
  ok: false,
606
713
  expected: parsers.flatMap((p) => {
@@ -776,6 +883,19 @@ function areDisjoint(sets) {
776
883
  if (intersects(sets[i], sets[j])) return false;
777
884
  return true;
778
885
  }
886
+ function buildAsciiDispatch(parsers) {
887
+ const table = Array(128).fill(null);
888
+ for (const parser2 of parsers) {
889
+ const fs = parser2._meta.firstSet;
890
+ if (fs.kind !== "ranges") continue;
891
+ for (const { lo, hi } of fs.ranges) {
892
+ for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) {
893
+ table[code] = parser2;
894
+ }
895
+ }
896
+ }
897
+ return table;
898
+ }
779
899
 
780
900
  // src/cst/trivia-kinds.ts
781
901
  function unwrapTrivia(p) {
@@ -897,15 +1017,16 @@ function recordTriviaChunks(ctx, chunks) {
897
1017
  // src/combinators/trivia-skip.ts
898
1018
  var NOOP_COMMIT = () => {
899
1019
  };
1020
+ var fastTriviaCache = /* @__PURE__ */ new WeakMap();
900
1021
  function needsDeferredTriviaCommit(ctx) {
901
1022
  return ctx._triviaLog !== void 0 || ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0;
902
1023
  }
903
1024
  function saveTriviaMark(ctx) {
904
1025
  const m = saveCstMark(ctx);
905
- return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
1026
+ return { raw: m.raw, tlog: m.tlog, leaves: m.leaves, fields: m.fields, log: ctx._triviaLog ? ctx._triviaLog.length : 0 };
906
1027
  }
907
1028
  function rollbackTrivia(ctx, mark) {
908
- rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves });
1029
+ rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields });
909
1030
  if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
910
1031
  }
911
1032
  function scanWithLabels(input, cur, ctx) {
@@ -923,6 +1044,8 @@ function scanWithLabels(input, cur, ctx) {
923
1044
  function advanceTrivia(input, cur, ctx) {
924
1045
  const triviaP = ctx.trivia;
925
1046
  if (!triviaP) return cur;
1047
+ const fast = fastTriviaScanner(triviaP);
1048
+ if (fast) return fast(input, cur);
926
1049
  if (ctx.triviaKindLabels) {
927
1050
  const scan = scanWithLabels(input, cur, ctx);
928
1051
  return scan.end;
@@ -935,6 +1058,10 @@ function scanTrivia(input, cur, ctx) {
935
1058
  if (!triviaP) return { end: cur, commit: NOOP_COMMIT };
936
1059
  const log = ctx._triviaLog;
937
1060
  const captureTl = ctx.captureTrivia && (ctx._cstBuf !== void 0 || ctx._cstTriviaLog !== void 0);
1061
+ const fast = !ctx.triviaKindLabels ? fastTriviaScanner(triviaP) : null;
1062
+ if (fast && log === void 0 && !captureTl) {
1063
+ return { end: fast(input, cur), commit: NOOP_COMMIT };
1064
+ }
938
1065
  if (ctx.triviaKindLabels && (log !== void 0 || captureTl)) {
939
1066
  return scanWithLabels(input, cur, ctx);
940
1067
  }
@@ -956,6 +1083,79 @@ function scanTrivia(input, cur, ctx) {
956
1083
  const tr = triviaP.parse(input, cur, { trackLines: ctx.trackLines, state: ctx.state });
957
1084
  return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT };
958
1085
  }
1086
+ function fastTriviaScanner(trivia2) {
1087
+ const cached = fastTriviaCache.get(trivia2);
1088
+ if (cached !== void 0) return cached;
1089
+ const scanner = buildFastTriviaScanner(trivia2);
1090
+ fastTriviaCache.set(trivia2, scanner);
1091
+ return scanner;
1092
+ }
1093
+ function buildFastTriviaScanner(trivia2) {
1094
+ const core = trivia2._def.tag === "trivia" ? trivia2._def.parser : trivia2;
1095
+ const direct = regexTriviaScanner(core);
1096
+ if (direct) return direct;
1097
+ const repeat = core._def.tag === "oneOrMore" || core._def.tag === "many" && core._def.min >= 1 ? core._def.parser : null;
1098
+ if (!repeat) return null;
1099
+ const one = regexTriviaScanner(repeat);
1100
+ if (one) return loopScanner([one]);
1101
+ if (repeat._def.tag !== "choice") return null;
1102
+ const arms = repeat._def.parsers.map(regexTriviaScanner);
1103
+ if (arms.some((s) => s === null)) return null;
1104
+ return loopScanner(arms);
1105
+ }
1106
+ function loopScanner(arms) {
1107
+ return (input, cur) => {
1108
+ let pos = cur;
1109
+ scan: while (pos < input.length) {
1110
+ for (const arm of arms) {
1111
+ const end = arm(input, pos);
1112
+ if (end > pos) {
1113
+ pos = end;
1114
+ continue scan;
1115
+ }
1116
+ }
1117
+ break;
1118
+ }
1119
+ return pos;
1120
+ };
1121
+ }
1122
+ function regexTriviaScanner(parser2) {
1123
+ if (parser2._def.tag !== "regex" || parser2._def.flags) return null;
1124
+ const ws = whitespaceSource(parser2._def.source);
1125
+ if (ws) return ws;
1126
+ return blockCommentSource(parser2._def.source) ? scanBlockComment : null;
1127
+ }
1128
+ function whitespaceSource(source) {
1129
+ const m = /^\[([ \\tnrf]+)\][*+]$/.exec(source);
1130
+ if (!m) return null;
1131
+ const chars = /* @__PURE__ */ new Set();
1132
+ for (let i = 0; i < m[1].length; i++) {
1133
+ const ch = m[1][i];
1134
+ if (ch !== "\\") {
1135
+ chars.add(ch.charCodeAt(0));
1136
+ continue;
1137
+ }
1138
+ const esc = m[1][++i];
1139
+ if (esc === "t") chars.add(9);
1140
+ else if (esc === "n") chars.add(10);
1141
+ else if (esc === "r") chars.add(13);
1142
+ else if (esc === "f") chars.add(12);
1143
+ else return null;
1144
+ }
1145
+ return (input, cur) => {
1146
+ let pos = cur;
1147
+ while (pos < input.length && chars.has(input.charCodeAt(pos))) pos++;
1148
+ return pos;
1149
+ };
1150
+ }
1151
+ function blockCommentSource(source) {
1152
+ return source === "\\/\\*(?:[^*]|\\*(?!\\/))*\\*\\/" || source === "\\/\\*[^]*?\\*\\/";
1153
+ }
1154
+ function scanBlockComment(input, cur) {
1155
+ if (input.charCodeAt(cur) !== 47 || input.charCodeAt(cur + 1) !== 42) return cur;
1156
+ const close = input.indexOf("*/", cur + 2);
1157
+ return close === -1 ? cur : close + 2;
1158
+ }
959
1159
 
960
1160
  // src/combinators/sequence.ts
961
1161
  function sequence(...parsers) {
@@ -1094,11 +1294,15 @@ function optional(combinator) {
1094
1294
  canMatchNewline: combinator._meta.canMatchNewline,
1095
1295
  isTrivia: false
1096
1296
  };
1297
+ const firstSetSkippable = !matchesEmpty(combinator);
1097
1298
  return {
1098
1299
  _tag: "optional",
1099
1300
  _meta: meta,
1100
1301
  _def: { tag: "optional", parser: combinator },
1101
1302
  parse(input, pos, ctx) {
1303
+ if (firstSetSkippable && ctx._probe === void 0 && !startsFirstSet(combinator, input, pos)) {
1304
+ return { ok: true, value: null, span: { start: pos, end: pos } };
1305
+ }
1102
1306
  const mark = saveTriviaMark(ctx);
1103
1307
  const result = combinator.parse(input, pos, ctx);
1104
1308
  if (result.ok) return result;
@@ -1107,6 +1311,15 @@ function optional(combinator) {
1107
1311
  }
1108
1312
  };
1109
1313
  }
1314
+ function startsFirstSet(combinator, input, pos) {
1315
+ const fs = combinator._meta.firstSet;
1316
+ if (fs.kind === "any") return true;
1317
+ if (fs.kind === "empty") return false;
1318
+ const code = input.codePointAt(pos);
1319
+ if (code === void 0) return false;
1320
+ for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
1321
+ return false;
1322
+ }
1110
1323
  function sepBy(combinator, separator) {
1111
1324
  const meta = {
1112
1325
  firstSet: combinator._meta.firstSet,
@@ -1187,6 +1400,13 @@ function ref() {
1187
1400
  define(p) {
1188
1401
  if (resolved) throw new Error("ref<T>() already defined");
1189
1402
  resolved = p;
1403
+ meta.firstSet = p._meta.firstSet;
1404
+ meta.canMatchNewline = p._meta.canMatchNewline;
1405
+ meta.isTrivia = p._meta.isTrivia;
1406
+ if (p._meta.triviaKindLabels !== void 0) meta.triviaKindLabels = p._meta.triviaKindLabels;
1407
+ else delete meta.triviaKindLabels;
1408
+ if (p._meta.disjoint !== void 0) meta.disjoint = p._meta.disjoint;
1409
+ else delete meta.disjoint;
1190
1410
  }
1191
1411
  };
1192
1412
  return slot;
@@ -1227,6 +1447,9 @@ function markUnusedValues(root) {
1227
1447
  case "transform":
1228
1448
  visit(def.parser, true);
1229
1449
  return;
1450
+ case "field":
1451
+ visit(def.parser, true);
1452
+ return;
1230
1453
  case "skip":
1231
1454
  visit(def.main, consumed);
1232
1455
  visit(def.skipped, false);
@@ -1278,6 +1501,11 @@ function markUnusedValues(root) {
1278
1501
  }
1279
1502
 
1280
1503
  // src/combinators/parser.ts
1504
+ function tagRule(r, key) {
1505
+ ;
1506
+ r._ruleName = key;
1507
+ if (r._def.tag === "node" && r._def.type === void 0) r._def.type = key;
1508
+ }
1281
1509
  function rules(factory) {
1282
1510
  const cache = {};
1283
1511
  const proxy = new Proxy(cache, {
@@ -1286,7 +1514,7 @@ function rules(factory) {
1286
1514
  const record = target;
1287
1515
  if (!(key in record)) {
1288
1516
  const r = ref();
1289
- r._ruleName = key;
1517
+ tagRule(r, key);
1290
1518
  record[key] = r;
1291
1519
  }
1292
1520
  return record[key];
@@ -1297,12 +1525,12 @@ function rules(factory) {
1297
1525
  const placeholder = cache[key];
1298
1526
  const parser2 = definitions[key];
1299
1527
  if (placeholder !== void 0 && typeof placeholder.define === "function") {
1300
- ;
1528
+ tagRule(parser2, key);
1301
1529
  placeholder.define(parser2);
1302
1530
  placeholder._meta.firstSet = parser2._meta.firstSet;
1303
1531
  placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1304
1532
  } else {
1305
- ;
1533
+ tagRule(parser2, key);
1306
1534
  cache[key] = parser2;
1307
1535
  }
1308
1536
  }
@@ -1359,44 +1587,130 @@ function buildReadsTrivia(def) {
1359
1587
  const src = def.buildSrc ?? def.build.toString();
1360
1588
  const arity = confirmedBuildArity(src);
1361
1589
  if (arity === null) return true;
1362
- return arity >= 4;
1590
+ return arity >= 5;
1363
1591
  }
1364
1592
  function buildReadsState(def) {
1365
1593
  if (!def.build) return true;
1366
1594
  const src = def.buildSrc ?? def.build.toString();
1367
1595
  const arity = confirmedBuildArity(src);
1368
1596
  if (arity === null) return true;
1369
- return arity >= 5;
1597
+ return arity >= 6;
1598
+ }
1599
+
1600
+ // src/compiler/fields.ts
1601
+ function buildReadsFields(def) {
1602
+ if (!def.build) return true;
1603
+ const src = def.buildSrc ?? def.build.toString();
1604
+ const arity = confirmedBuildArity(src);
1605
+ if (arity === null) return true;
1606
+ return arity >= 2;
1607
+ }
1608
+ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
1609
+ if (seen.has(p)) return false;
1610
+ seen.add(p);
1611
+ const d = p._def;
1612
+ switch (d.tag) {
1613
+ case "field":
1614
+ return true;
1615
+ case "node":
1616
+ return false;
1617
+ case "lazy": {
1618
+ try {
1619
+ return parserHasOwnFields(d.thunk(), seen);
1620
+ } catch {
1621
+ return false;
1622
+ }
1623
+ }
1624
+ case "sequence":
1625
+ case "choice":
1626
+ return d.parsers.some((x) => parserHasOwnFields(x, seen));
1627
+ case "sepBy":
1628
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
1629
+ case "skip":
1630
+ return parserHasOwnFields(d.main, seen) || parserHasOwnFields(d.skipped, seen);
1631
+ case "grammar":
1632
+ return parserHasOwnFields(d.parser, seen) || (d.triviaParser ? parserHasOwnFields(d.triviaParser, seen) : false);
1633
+ case "scanTo":
1634
+ return parserHasOwnFields(d.sentinel, seen) || d.skip.some((x) => parserHasOwnFields(x, seen));
1635
+ case "recover":
1636
+ return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.sentinel, seen);
1637
+ case "many":
1638
+ case "oneOrMore":
1639
+ case "optional":
1640
+ case "transform":
1641
+ case "trivia":
1642
+ case "token":
1643
+ case "label":
1644
+ case "expect":
1645
+ case "withCtx":
1646
+ case "not":
1647
+ return parserHasOwnFields(d.parser, seen);
1648
+ default:
1649
+ return false;
1650
+ }
1651
+ }
1652
+ function buildFieldMap(captures) {
1653
+ if (!captures?.length) return void 0;
1654
+ const out = {};
1655
+ for (const cap of captures) {
1656
+ const entry = { value: cap.value, span: cap.span };
1657
+ const current = out[cap.name];
1658
+ if (current === void 0) out[cap.name] = entry;
1659
+ else if (Array.isArray(current)) current.push(entry);
1660
+ else out[cap.name] = [current, entry];
1661
+ }
1662
+ return out;
1370
1663
  }
1371
1664
 
1372
1665
  // src/combinators/node.ts
1373
- function collapseChild(child) {
1666
+ function unwrapChild(child) {
1374
1667
  return child !== null && typeof child === "object" && child._tag === "leaf" ? child.value : child;
1375
1668
  }
1376
- function node(type, combinator, build, opts) {
1669
+ function isCstChild(value) {
1670
+ return typeof value === "object" && value !== null && (value._tag === "node" || value._tag === "leaf" || value._tag === "error");
1671
+ }
1672
+ function missingInferredType() {
1673
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
1674
+ }
1675
+ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
1676
+ const hasExplicitType = typeof typeOrCombinator === "string";
1677
+ const type = hasExplicitType ? typeOrCombinator : void 0;
1678
+ const combinator = hasExplicitType ? combinatorOrBuild : typeOrCombinator;
1679
+ const build = hasExplicitType ? buildOrOpts : combinatorOrBuild;
1680
+ const opts = hasExplicitType ? maybeOpts : buildOrOpts;
1681
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
1377
1682
  const meta = {
1378
1683
  firstSet: combinator._meta.firstSet,
1379
1684
  canMatchNewline: combinator._meta.canMatchNewline,
1380
1685
  isTrivia: false
1381
1686
  };
1687
+ const unwrap = opts?.unwrap === true;
1382
1688
  const collapse = opts?.collapse === true;
1383
- const def = collapse ? { tag: "node", type, parser: combinator, build, collapse: true } : { tag: "node", type, parser: combinator, build };
1689
+ if (unwrap && collapse) {
1690
+ throw new Error("node() options cannot set both unwrap and collapse");
1691
+ }
1692
+ const def = unwrap || collapse ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {} } : baseDef;
1384
1693
  const capturesTrivia = build ? buildReadsTrivia(def) : true;
1385
1694
  const clonesState = build ? buildReadsState(def) : true;
1695
+ const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
1386
1696
  return {
1387
1697
  _tag: "node",
1388
1698
  _meta: meta,
1389
1699
  _def: def,
1390
1700
  parse(input, pos, ctx) {
1391
1701
  const saved = beginCstNodeCapture(ctx);
1702
+ const savedFields = ctx._fields;
1703
+ ctx._fields = capturesFields ? [] : void 0;
1392
1704
  if (!capturesTrivia) ctx.captureTrivia = false;
1393
1705
  const r = combinator.parse(input, pos, ctx);
1706
+ const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
1707
+ ctx._fields = savedFields;
1394
1708
  const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
1395
1709
  if (!r.ok) return r;
1396
1710
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
1397
- 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 };
1398
- const isNodeLike = typeof built === "object" && built !== null && built._tag === "node";
1399
- const rawEntry = isNodeLike ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1711
+ const nodeType = def.type ?? missingInferredType();
1712
+ 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 };
1713
+ const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : "", span: r.span };
1400
1714
  if (saved.buf !== void 0 || saved.ch !== void 0) {
1401
1715
  pushCstChild(ctx, built, rawEntry);
1402
1716
  }
@@ -1414,7 +1728,7 @@ function transform(combinator, fn) {
1414
1728
  parse(input, pos, ctx) {
1415
1729
  const result = combinator.parse(input, pos, ctx);
1416
1730
  if (!result.ok) return result;
1417
- return { ...result, value: fn(result.value, result.span) };
1731
+ return { ok: true, value: fn(result.value, result.span), span: result.span };
1418
1732
  }
1419
1733
  };
1420
1734
  }
@@ -1428,7 +1742,7 @@ function skip(main, skipped) {
1428
1742
  if (!result.ok) return result;
1429
1743
  const s = skipped.parse(input, result.span.end, ctx);
1430
1744
  if (!s.ok) return result;
1431
- return { ...result, span: { start: result.span.start, end: s.span.end } };
1745
+ return { ok: true, value: result.value, span: { start: result.span.start, end: s.span.end } };
1432
1746
  }
1433
1747
  };
1434
1748
  }
@@ -1452,7 +1766,20 @@ function label(name, combinator) {
1452
1766
  _def: { tag: "label", label: name, parser: combinator },
1453
1767
  parse(input, pos, ctx) {
1454
1768
  const result = combinator.parse(input, pos, ctx);
1455
- if (!result.ok) return { ...result, expected: [name] };
1769
+ if (!result.ok) return { ok: false, expected: [name], span: result.span };
1770
+ return result;
1771
+ }
1772
+ };
1773
+ }
1774
+ function field(name, combinator) {
1775
+ return {
1776
+ _tag: combinator._tag,
1777
+ _meta: combinator._meta,
1778
+ _def: { tag: "field", name, parser: combinator },
1779
+ parse(input, pos, ctx) {
1780
+ const result = combinator.parse(input, pos, ctx);
1781
+ if (!result.ok) return result;
1782
+ ctx._fields?.push({ name, value: result.value, span: result.span });
1456
1783
  return result;
1457
1784
  }
1458
1785
  };
@@ -1666,9 +1993,9 @@ function expect(combinator, label2) {
1666
1993
 
1667
1994
  // src/compiler/scannable-run.ts
1668
1995
  var BACKSLASH = 92;
1669
- var CLASS_ESCAPES = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1996
+ var CLASS_ESCAPES2 = { t: 9, n: 10, r: 13, f: 12, v: 11, "0": 0 };
1670
1997
  var META = new Set("()[]{}*+?|^$.".split(""));
1671
- var SPACE_RANGES = [
1998
+ var SPACE_RANGES2 = [
1672
1999
  [9, 13],
1673
2000
  [32, 32],
1674
2001
  [160, 160],
@@ -1681,9 +2008,9 @@ var SPACE_RANGES = [
1681
2008
  [12288, 12288],
1682
2009
  [65279, 65279]
1683
2010
  ];
1684
- function shorthandRanges(ch) {
2011
+ function shorthandRanges2(ch) {
1685
2012
  if (ch === "d") return [[48, 57]];
1686
- if (ch === "s") return SPACE_RANGES;
2013
+ if (ch === "s") return SPACE_RANGES2;
1687
2014
  return [[48, 57], [65, 90], [97, 122], [95, 95]];
1688
2015
  }
1689
2016
  function readUnicodeEscape(body, i) {
@@ -1707,8 +2034,8 @@ function parseClassRanges(body) {
1707
2034
  const e = body[i + 1];
1708
2035
  if (e === void 0) return null;
1709
2036
  i += 2;
1710
- if (e in CLASS_ESCAPES) return { cp: CLASS_ESCAPES[e] };
1711
- if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges(e) };
2037
+ if (e in CLASS_ESCAPES2) return { cp: CLASS_ESCAPES2[e] };
2038
+ if (e === "d" || e === "w" || e === "s") return { set: shorthandRanges2(e) };
1712
2039
  if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1713
2040
  return { cp: e.codePointAt(0) };
1714
2041
  }
@@ -1734,9 +2061,9 @@ function parseClassRanges(body) {
1734
2061
  return ranges.length ? ranges : null;
1735
2062
  }
1736
2063
  function classToRanges(cls) {
1737
- if (cls === "\\d") return shorthandRanges("d");
1738
- if (cls === "\\w") return shorthandRanges("w");
1739
- if (cls === "\\s") return shorthandRanges("s");
2064
+ if (cls === "\\d") return shorthandRanges2("d");
2065
+ if (cls === "\\w") return shorthandRanges2("w");
2066
+ if (cls === "\\s") return shorthandRanges2("s");
1740
2067
  const body = cls.slice(1, -1);
1741
2068
  if (body.startsWith("^")) return null;
1742
2069
  return parseClassRanges(body);
@@ -1754,7 +2081,7 @@ function literalCodePoints(frag) {
1754
2081
  if (ch === "\\") {
1755
2082
  const e = frag[i + 1];
1756
2083
  if (e === void 0) return null;
1757
- out.push(e in CLASS_ESCAPES ? CLASS_ESCAPES[e] : e.codePointAt(0));
2084
+ out.push(e in CLASS_ESCAPES2 ? CLASS_ESCAPES2[e] : e.codePointAt(0));
1758
2085
  i += 2;
1759
2086
  continue;
1760
2087
  }
@@ -1847,7 +2174,7 @@ function parseSeqParts(source) {
1847
2174
  if (e === "d" || e === "w" || e === "s") {
1848
2175
  flush();
1849
2176
  i += 2;
1850
- i += runFrom(shorthandRanges(e), false, source[i]);
2177
+ i += runFrom(shorthandRanges2(e), false, source[i]);
1851
2178
  continue;
1852
2179
  }
1853
2180
  const uni = readUnicodeEscape(source, i);
@@ -1856,8 +2183,8 @@ function parseSeqParts(source) {
1856
2183
  cp2 = uni.cp;
1857
2184
  i = uni.next;
1858
2185
  } else if (e === void 0) return null;
1859
- else if (e in CLASS_ESCAPES) {
1860
- cp2 = CLASS_ESCAPES[e];
2186
+ else if (e in CLASS_ESCAPES2) {
2187
+ cp2 = CLASS_ESCAPES2[e];
1861
2188
  i += 2;
1862
2189
  } else if (e >= "a" && e <= "z" || e >= "A" && e <= "Z") return null;
1863
2190
  else {
@@ -1964,7 +2291,7 @@ function rangesSubset(sub, sup) {
1964
2291
  }
1965
2292
  function parseClassOperand(body) {
1966
2293
  if (body === "\\d" || body === "\\w" || body === "\\s") {
1967
- return { ranges: shorthandRanges(body[1]), negated: false };
2294
+ return { ranges: shorthandRanges2(body[1]), negated: false };
1968
2295
  }
1969
2296
  if (body.length >= 2 && body[0] === "[" && body[body.length - 1] === "]") {
1970
2297
  let inner = body.slice(1, -1);
@@ -2397,7 +2724,7 @@ function parseScanShapeCore(source) {
2397
2724
  }
2398
2725
  m = /^\\([dws])([+*])$/.exec(source);
2399
2726
  if (m) {
2400
- return { kind: "chars", ranges: shorthandRanges(m[1]), minOne: m[2] === "+" };
2727
+ return { kind: "chars", ranges: shorthandRanges2(m[1]), minOne: m[2] === "+" };
2401
2728
  }
2402
2729
  m = /^(\[(?:\\.|[^\]])+\]|\\[dws])(\[(?:\\.|[^\]])+\]|\\[dws])\*$/.exec(source);
2403
2730
  if (m) {
@@ -2939,21 +3266,22 @@ function emitScannableTerminal(shape, c) {
2939
3266
  }
2940
3267
 
2941
3268
  // src/compiler/inline-build.ts
2942
- 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*$/;
3269
+ 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*$/;
2943
3270
  function escapeRegExp(s) {
2944
3271
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2945
3272
  }
2946
3273
  function looseMkBuildRe(type) {
2947
3274
  return new RegExp(
2948
- 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*$`
3275
+ 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*$`
2949
3276
  );
2950
3277
  }
2951
3278
  function analyzeMkInlineBuild(def) {
2952
3279
  if (!def.build) return null;
3280
+ if (def.type === void 0) return null;
2953
3281
  const src = (def.buildSrc ?? def.build.toString()).trim();
2954
3282
  const strict = src.match(MK_BUILD_RE);
2955
3283
  if (strict) {
2956
- const mkType = strict[2];
3284
+ const mkType = strict[6];
2957
3285
  return mkType === def.type ? def.type : null;
2958
3286
  }
2959
3287
  if (looseMkBuildRe(def.type).test(src)) return def.type;
@@ -3280,8 +3608,9 @@ function planDisjointDispatch(parsers) {
3280
3608
  }
3281
3609
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
3282
3610
  }
3283
- function captureRestoreBody(mL, mR, mTl, mLg) {
3284
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}`;
3611
+ function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
3612
+ const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
3613
+ const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
3285
3614
  return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
3286
3615
  }
3287
3616
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
@@ -3329,6 +3658,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3329
3658
  // Delegating wrappers: defer to the wrapped parser.
3330
3659
  case "transform":
3331
3660
  case "label":
3661
+ case "field":
3332
3662
  case "expect":
3333
3663
  case "withCtx":
3334
3664
  case "grammar":
@@ -3375,6 +3705,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3375
3705
  case "optional":
3376
3706
  case "transform":
3377
3707
  case "label":
3708
+ case "field":
3378
3709
  case "expect":
3379
3710
  case "withCtx":
3380
3711
  case "grammar":
@@ -3412,22 +3743,25 @@ function emitFallible(inner, ctx, pos, swallow = false) {
3412
3743
  ctx.recordFail = savedRecord;
3413
3744
  const ind0 = ind(ctx);
3414
3745
  const needsRollback = ctx.capturing && mayLeavePartialCapture(inner);
3746
+ const needsFieldRollback = needsRollback && parserHasOwnFields(inner);
3415
3747
  const mL = needsRollback ? v(ctx, "_fcl") : null;
3416
3748
  const mR = needsRollback ? v(ctx, "_fcr") : null;
3417
3749
  const mTl = needsRollback ? v(ctx, "_fctl") : null;
3750
+ const mF = needsFieldRollback ? v(ctx, "_fcf") : null;
3418
3751
  const stmts = [
3419
3752
  `${ind0}let ${okV} = false, ${valV}, ${endV} = ${pos}`,
3420
3753
  ...mL ? [
3421
3754
  `${ind0}const ${mL} = _ctx._cstLeaves?.length ?? 0`,
3422
3755
  `${ind0}const ${mR} = _ctx._cstRawChildren?.length ?? 0`,
3423
- `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`
3756
+ `${ind0}const ${mTl} = _ctx._cstTriviaLog?.length ?? 0`,
3757
+ ...mF ? [`${ind0}const ${mF} = _ctx._fields?.length ?? 0`] : []
3424
3758
  ] : [],
3425
3759
  `${ind0}${lbl}: {`,
3426
3760
  ...r.stmts,
3427
3761
  `${ind0} ${valV} = ${r.valueVar}; ${endV} = ${r.endVar}; ${okV} = true`,
3428
3762
  `${ind0}}`,
3429
3763
  ...mL ? [
3430
- `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null)} }`
3764
+ `${ind0}if (!${okV}) { ${captureRestoreBody(mL, mR, mTl, null, mF)} }`
3431
3765
  ] : []
3432
3766
  ];
3433
3767
  return { stmts, okVar: okV, valVar: valV, endVar: endV };
@@ -3654,6 +3988,7 @@ function failsAtStart(p) {
3654
3988
  return true;
3655
3989
  case "transform":
3656
3990
  case "label":
3991
+ case "field":
3657
3992
  return failsAtStart(d.parser);
3658
3993
  default:
3659
3994
  return false;
@@ -3673,6 +4008,7 @@ function canMatchEmptyAtStart(p) {
3673
4008
  return true;
3674
4009
  case "transform":
3675
4010
  case "label":
4011
+ case "field":
3676
4012
  return canMatchEmptyAtStart(d.parser);
3677
4013
  case "literal":
3678
4014
  return d.value.length === 0;
@@ -3855,11 +4191,13 @@ function emitFirstMatch(def, ctx, pos) {
3855
4191
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
3856
4192
  const armHasAutoNot = !!(autoNot && autoNot.length > 0);
3857
4193
  const armNeedsRollback = ctx.capturing && (mayLeavePartialCapture(p) || armHasAutoNot && capturesLeaf(p));
4194
+ const armNeedsFieldRollback = armNeedsRollback && parserHasOwnFields(p);
3858
4195
  const markLeaves = armNeedsRollback ? v(ctx, "_cml") : null;
3859
4196
  const markRaw = armNeedsRollback ? v(ctx, "_cmr") : null;
3860
4197
  const markTl = armNeedsRollback ? v(ctx, "_cmtl") : null;
3861
4198
  const markLog = armNeedsRollback ? v(ctx, "_cmlg") : null;
3862
- const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog) : "";
4199
+ const markFields = armNeedsFieldRollback ? v(ctx, "_cmf") : null;
4200
+ const rollback = markLeaves ? captureRestoreBody(markLeaves, markRaw, markTl, markLog, markFields) : "";
3863
4201
  const failSlot = atStart ? staticFx : "_ctx._fx";
3864
4202
  stmts.push(`${ind0}if (${skipCond}) {`);
3865
4203
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
@@ -3869,7 +4207,8 @@ function emitFirstMatch(def, ctx, pos) {
3869
4207
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
3870
4208
  `${ind(ctx)}const ${markRaw} = _ctx._cstRawChildren?.length ?? 0`,
3871
4209
  `${ind(ctx)}const ${markTl} = _ctx._cstTriviaLog?.length ?? 0`,
3872
- `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`
4210
+ `${ind(ctx)}const ${markLog} = _ctx._triviaLog?.length ?? 0`,
4211
+ ...markFields ? [`${ind(ctx)}const ${markFields} = _ctx._fields?.length ?? 0`] : []
3873
4212
  );
3874
4213
  }
3875
4214
  const { stmts: armStmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos, atStart);
@@ -4262,6 +4601,9 @@ function emitToken(def, ctx, pos) {
4262
4601
  };
4263
4602
  }
4264
4603
  function emitNode(def, ctx, pos) {
4604
+ if (def.type === void 0) {
4605
+ throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
4606
+ }
4265
4607
  const structural = def.build === void 0;
4266
4608
  const mkType = structural ? null : analyzeMkInlineBuild(def);
4267
4609
  let buildIdx = null;
@@ -4273,25 +4615,36 @@ function emitNode(def, ctx, pos) {
4273
4615
  const i = ind(ctx);
4274
4616
  const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4275
4617
  const clonesState = !structural && buildReadsState(def);
4618
+ const hasFields = parserHasOwnFields(def.parser);
4619
+ const capturesFields = hasFields && !structural && buildReadsFields(def);
4276
4620
  const chV = v(ctx, "_ch");
4277
4621
  const rawV = v(ctx, "_raw");
4278
4622
  const capTLv = structural ? v(ctx, "_ctl") : null;
4279
4623
  const capSTv = structural ? v(ctx, "_cst") : null;
4624
+ const capFv = structural && hasFields ? v(ctx, "_cf") : null;
4280
4625
  const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4281
4626
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4282
4627
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4283
4628
  if (structural) ctx.needsHostReads = true;
4284
- const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4629
+ const hostTriviaGate = `_ctx.build !== undefined && (_ctx.build._parsemanCaptureTrivia !== undefined ? _ctx.build._parsemanCaptureTrivia(${JSON.stringify(def.type)}) : (_ctx._pmCapTL ??= _hostReads(_ctx.build, 5)))`;
4630
+ 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)` : ""}
4285
4631
  ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4286
4632
  const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4633
+ const fieldsOn = structural ? capFv ?? "false" : capturesFields ? "true" : "false";
4634
+ const sf = hasFields ? v(ctx, "_sf") : null;
4635
+ const fArr = hasFields ? v(ctx, "_fa") : null;
4636
+ const fObj = hasFields ? v(ctx, "_fields") : "undefined";
4287
4637
  const stmts = [
4288
4638
  allocStmt,
4289
- `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
4290
- `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}`
4639
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${sf ? `, ${sf} = _ctx._fields` : ""}`,
4640
+ `${i}_ctx._cstChildren = ${chV}; _ctx._cstLeaves = ${chV}; _ctx._cstRawChildren = ${rawV}; _ctx.captureTrivia = true; _ctx._cstTriviaLog = ${innerTl}${sf ? `; _ctx._fields = ${fieldsOn} ? [] : undefined` : ""}`
4291
4641
  ];
4292
4642
  const { stmts: innerStmts, okVar, endVar } = emitFallible(def.parser, ctx, pos);
4293
4643
  stmts.push(...innerStmts);
4294
- stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4644
+ if (sf && fArr) {
4645
+ stmts.push(`${i}const ${fArr} = _ctx._fields`);
4646
+ }
4647
+ stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${sf ? `; _ctx._fields = ${sf}` : ""}`);
4295
4648
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4296
4649
  let stV = "undefined";
4297
4650
  if (structural) {
@@ -4301,14 +4654,33 @@ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capt
4301
4654
  stV = v(ctx, "_nst");
4302
4655
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4303
4656
  }
4657
+ if (sf && fArr) {
4658
+ const fe = v(ctx, "_fe");
4659
+ const cur = v(ctx, "_fc");
4660
+ stmts.push(
4661
+ `${i}let ${fObj} = undefined`,
4662
+ `${i}if (${fArr} && ${fArr}.length) {`,
4663
+ `${i} ${fObj} = {}`,
4664
+ `${i} for (const ${fe} of ${fArr}) {`,
4665
+ `${i} const ${cur} = ${fObj}[${fe}.name], _entry = { value: ${fe}.value, span: ${fe}.span }`,
4666
+ `${i} if (${cur} === undefined) ${fObj}[${fe}.name] = _entry`,
4667
+ `${i} else if (Array.isArray(${cur})) ${cur}.push(_entry)`,
4668
+ `${i} else ${fObj}[${fe}.name] = [${cur}, _entry]`,
4669
+ `${i} }`,
4670
+ `${i}}`
4671
+ );
4672
+ }
4304
4673
  const ndV = v(ctx, "_nd");
4305
- 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})`;
4306
- const ndExpr = ctx.ns || structural ? `_ctx.build !== undefined ? _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${rawV}, { start: ${pos}, end: ${endVar} }, ${tlV}, ${stV}) : (${buildExpr})` : buildExpr;
4307
- 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;
4674
+ 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})`;
4675
+ 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;
4676
+ 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;
4677
+ const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
4678
+ const collapseExpr = `${chV}.length === 1 ? ${chV}[0] : (${ndExpr})`;
4679
+ const finalExpr = def.unwrap ? unwrapExpr : def.collapse ? collapseExpr : hostCollapseExpr;
4308
4680
  stmts.push(
4309
4681
  `${i}const ${ndV} = ${finalExpr}`,
4310
4682
  `${i}if (${sc}) ${sc}.push(${ndV})`,
4311
- `${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} } })`
4683
+ `${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} } })`
4312
4684
  );
4313
4685
  return { stmts, valueVar: ndV, endVar };
4314
4686
  }
@@ -4523,6 +4895,17 @@ function emitDispatch(p, ctx, pos) {
4523
4895
  endVar: inner.endVar
4524
4896
  };
4525
4897
  }
4898
+ case "field": {
4899
+ const inner = emit(def.parser, ctx, pos);
4900
+ return {
4901
+ stmts: [
4902
+ ...inner.stmts,
4903
+ `${ind(ctx)}if (_ctx._fields) _ctx._fields.push({ name: ${JSON.stringify(def.name)}, value: ${inner.valueVar}, span: { start: ${pos}, end: ${inner.endVar} } })`
4904
+ ],
4905
+ valueVar: inner.valueVar,
4906
+ endVar: inner.endVar
4907
+ };
4908
+ }
4526
4909
  case "grammar": {
4527
4910
  const savedTrivia = ctx.activeTrivia;
4528
4911
  const savedKindLabels = ctx.triviaKindLabels;
@@ -4624,6 +5007,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4624
5007
  case "trivia":
4625
5008
  case "token":
4626
5009
  case "label":
5010
+ case "field":
4627
5011
  case "optional":
4628
5012
  case "many":
4629
5013
  case "oneOrMore":
@@ -4660,6 +5044,7 @@ function childrenOf(def) {
4660
5044
  case "trivia":
4661
5045
  case "token":
4662
5046
  case "label":
5047
+ case "field":
4663
5048
  case "grammar":
4664
5049
  case "not":
4665
5050
  case "node":
@@ -4901,6 +5286,11 @@ function assertRuleName(name) {
4901
5286
  );
4902
5287
  }
4903
5288
  }
5289
+ function publicRuleWrapperSource(rule, fnSource) {
5290
+ const labels = rule._meta.triviaKindLabels;
5291
+ if (labels === void 0) return fnSource;
5292
+ return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
5293
+ }
4904
5294
  function compileLinkable(ruleMapArg, ns) {
4905
5295
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4906
5296
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
@@ -5003,7 +5393,7 @@ function compileLinkable(ruleMapArg, ns) {
5003
5393
  ctx.recordFail = savedRec;
5004
5394
  pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
5005
5395
  }
5006
- perEntry.push({ key, r: emitNamedFnCall(ctx, fn, "_pos") });
5396
+ perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
5007
5397
  }
5008
5398
  if (ctx.runtimeParsers.length !== 0) return null;
5009
5399
  const mfSrcs = ctx.mapFns.length > 0 && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : null;
@@ -5026,14 +5416,14 @@ function compileLinkable(ruleMapArg, ns) {
5026
5416
  ...privateFns
5027
5417
  ].filter(Boolean);
5028
5418
  const wrappers = /* @__PURE__ */ new Map();
5029
- for (const { key, r } of perEntry) {
5030
- wrappers.set(key, [
5419
+ for (const { key, rule, r } of perEntry) {
5420
+ wrappers.set(key, publicRuleWrapperSource(rule, [
5031
5421
  `function(input, _pos, _ctx) {`,
5032
5422
  ` let pos = _pos`,
5033
5423
  ...r.stmts,
5034
5424
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
5035
5425
  `}`
5036
- ].join("\n"));
5426
+ ].join("\n")));
5037
5427
  }
5038
5428
  const firstSets = /* @__PURE__ */ new Map();
5039
5429
  for (const [key, rule] of ruleMap) {
@@ -5236,6 +5626,7 @@ function evalRuleMapIR(ir) {
5236
5626
  "skip",
5237
5627
  "trivia",
5238
5628
  "label",
5629
+ "field",
5239
5630
  "expect",
5240
5631
  "_tf",
5241
5632
  "_nd",
@@ -5262,6 +5653,7 @@ function evalRuleMapIR(ir) {
5262
5653
  skip,
5263
5654
  trivia,
5264
5655
  label,
5656
+ field,
5265
5657
  expect,
5266
5658
  _tf,
5267
5659
  _nd
@@ -5276,9 +5668,26 @@ function linkable(rulesMap, ns) {
5276
5668
  if (!pieces) throw new Error("linkable(): this grammar cannot be compiled to a linkable artifact (contains a runtime-only parser fallback)");
5277
5669
  return pieces;
5278
5670
  }
5279
- function cstBuildHost(type, children, _rawChildren, span, _triviaLog, state) {
5671
+ function normalizeCstCollapse(collapse) {
5672
+ if (collapse === true) return () => true;
5673
+ if (Array.isArray(collapse)) {
5674
+ const types = new Set(collapse);
5675
+ return (type) => types.has(type);
5676
+ }
5677
+ return typeof collapse === "function" ? collapse : void 0;
5678
+ }
5679
+ function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
5280
5680
  return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
5281
5681
  }
5682
+ function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
5683
+ if (typeof typeOrOptions === "string") {
5684
+ return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
5685
+ }
5686
+ const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
5687
+ const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
5688
+ if (collapse) host._parsemanCstCollapse = collapse;
5689
+ return host;
5690
+ }
5282
5691
  function pick(grammar, names) {
5283
5692
  const p = grammar.ruleFns instanceof Map ? grammar : linkable(grammar);
5284
5693
  const keep = /* @__PURE__ */ new Set();
@@ -5310,8 +5719,9 @@ function fusedBody(pieces) {
5310
5719
  }
5311
5720
  }
5312
5721
  const contributing = new Set(winner.values());
5313
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5314
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5722
+ const contributingPieces = [...contributing];
5723
+ const needsEmptyTl = contributingPieces.some((p) => p.needsEmptyTl);
5724
+ const needsHostReads = contributingPieces.some((p) => p.needsHostReads);
5315
5725
  const lines = [
5316
5726
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5317
5727
  "const _pfFail = {}",
@@ -5319,7 +5729,7 @@ function fusedBody(pieces) {
5319
5729
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5320
5730
  ...needsHostReads ? [HOST_READS_DECL] : [],
5321
5731
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5322
- ...[...contributing].flatMap((p) => p.prelude),
5732
+ ...[...new Set(contributingPieces.flatMap((p) => p.prelude))],
5323
5733
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5324
5734
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5325
5735
  ];
@@ -5342,7 +5752,7 @@ function fusedBody(pieces) {
5342
5752
  }
5343
5753
  );
5344
5754
  const env = {};
5345
- for (const p of contributing) {
5755
+ for (const p of contributingPieces) {
5346
5756
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5347
5757
  if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5348
5758
  }
@@ -5362,14 +5772,23 @@ function materializePiece(p) {
5362
5772
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5363
5773
  return pieces;
5364
5774
  }
5365
- function itemPieces(item) {
5775
+ function nextComposeNs(used) {
5776
+ let ns;
5777
+ do {
5778
+ ns = `_lk${_nsCounter++}_`;
5779
+ } while (used.has(ns));
5780
+ used.add(ns);
5781
+ return ns;
5782
+ }
5783
+ function itemPieces(item, used) {
5366
5784
  const carried = item[COMPOSED_PIECES];
5367
- if (Array.isArray(carried)) return carried.map(materializePiece);
5368
- if (item.ruleFns instanceof Map) return [item];
5369
- return [linkable(item)];
5785
+ const pieces = Array.isArray(carried) ? carried.map(materializePiece) : item.ruleFns instanceof Map ? [item] : [linkable(item, nextComposeNs(used))];
5786
+ for (const p of pieces) used.add(p.ns);
5787
+ return pieces;
5370
5788
  }
5371
5789
  function compose(items) {
5372
- const pieces = items.flatMap(itemPieces);
5790
+ const used = /* @__PURE__ */ new Set();
5791
+ const pieces = items.flatMap((item) => itemPieces(item, used));
5373
5792
  const map = fuseRules(pieces);
5374
5793
  Object.defineProperty(map, COMPOSED_PIECES, { value: pieces, enumerable: false });
5375
5794
  return map;
@@ -6095,6 +6514,7 @@ export {
6095
6514
  createVisitor,
6096
6515
  cstBuildHost,
6097
6516
  expect,
6517
+ field,
6098
6518
  gapIsSignificant,
6099
6519
  gapText,
6100
6520
  guard,