parseman 0.27.1 → 0.28.1

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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/combinators/attempt.d.ts +8 -0
  3. package/dist/combinators/attempt.d.ts.map +1 -0
  4. package/dist/combinators/expect.d.ts.map +1 -1
  5. package/dist/combinators/first-set.d.ts.map +1 -1
  6. package/dist/combinators/scanTo.d.ts.map +1 -1
  7. package/dist/combinators/token.d.ts +12 -0
  8. package/dist/combinators/token.d.ts.map +1 -1
  9. package/dist/compiler/build-arity.d.ts +8 -4
  10. package/dist/compiler/build-arity.d.ts.map +1 -1
  11. package/dist/compiler/codegen.d.ts +12 -0
  12. package/dist/compiler/codegen.d.ts.map +1 -1
  13. package/dist/compiler/fields.d.ts.map +1 -1
  14. package/dist/compiler/grammar-coverage-ids.d.ts +19 -0
  15. package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -0
  16. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  17. package/dist/compiler/linker.d.ts +22 -9
  18. package/dist/compiler/linker.d.ts.map +1 -1
  19. package/dist/compiler/value-usage.d.ts.map +1 -1
  20. package/dist/coverage.d.ts +68 -0
  21. package/dist/coverage.d.ts.map +1 -0
  22. package/dist/functional/doc.d.ts.map +1 -1
  23. package/dist/functional/run.d.ts +13 -0
  24. package/dist/functional/run.d.ts.map +1 -1
  25. package/dist/index.cjs +816 -77
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +5 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +816 -77
  30. package/dist/index.js.map +4 -4
  31. package/dist/language-service/index.cjs +4 -1
  32. package/dist/language-service/index.cjs.map +2 -2
  33. package/dist/language-service/index.js +4 -1
  34. package/dist/language-service/index.js.map +2 -2
  35. package/dist/plugin/evaluator.d.ts.map +1 -1
  36. package/dist/plugin/index.cjs +786 -113
  37. package/dist/plugin/index.cjs.map +4 -4
  38. package/dist/plugin/index.d.ts +4 -1
  39. package/dist/plugin/index.d.ts.map +1 -1
  40. package/dist/plugin/index.js +786 -113
  41. package/dist/plugin/index.js.map +4 -4
  42. package/dist/spec/index.cjs +3 -0
  43. package/dist/spec/index.cjs.map +2 -2
  44. package/dist/spec/index.js +3 -0
  45. package/dist/spec/index.js.map +2 -2
  46. package/dist/spec/model.d.ts.map +1 -1
  47. package/dist/types.d.ts +12 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +2 -1
@@ -1,5 +1,6 @@
1
1
  // src/plugin/index.ts
2
2
  import * as fs from "node:fs";
3
+ import * as path from "node:path";
3
4
  import { createUnplugin } from "unplugin";
4
5
  import { parseSync as parseSync2 } from "oxc-parser";
5
6
  import { ResolverFactory } from "oxc-resolver";
@@ -66,6 +67,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
66
67
  case "label":
67
68
  case "trivia":
68
69
  case "token":
70
+ case "leaf":
69
71
  case "expect":
70
72
  case "withCtx":
71
73
  case "node":
@@ -129,6 +131,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
129
131
  case "label":
130
132
  case "trivia":
131
133
  case "token":
134
+ case "leaf":
132
135
  case "node":
133
136
  case "grammar":
134
137
  case "expect":
@@ -234,10 +237,10 @@ function beginCstNodeCapture(ctx) {
234
237
  }
235
238
  function finishCstBuf(buf) {
236
239
  if (!buf) return { children: EMPTY, rawChildren: EMPTY, triviaLog: EMPTY_TL };
237
- const children = buf.ch ?? (buf.single !== void 0 ? [buf.single] : EMPTY);
240
+ const children2 = buf.ch ?? (buf.single !== void 0 ? [buf.single] : EMPTY);
238
241
  const rawChildren = buf.raw ?? (buf.rawSingle !== void 0 ? [buf.rawSingle] : EMPTY);
239
242
  const triviaLog = buf.tl ?? EMPTY_TL;
240
- return { children, rawChildren, triviaLog };
243
+ return { children: children2, rawChildren, triviaLog };
241
244
  }
242
245
  function endCstNodeCapture(ctx, saved) {
243
246
  const materialized = finishCstBuf(ctx._cstBuf);
@@ -249,8 +252,8 @@ function endCstNodeCapture(ctx, saved) {
249
252
  ctx.captureTrivia = saved.cap;
250
253
  return materialized;
251
254
  }
252
- function pushCstLeaf(ctx, leaf) {
253
- pushCstChild(ctx, leaf, leaf);
255
+ function pushCstLeaf(ctx, leaf2) {
256
+ pushCstChild(ctx, leaf2, leaf2);
254
257
  }
255
258
  function pushCstChild(ctx, built, rawEntry) {
256
259
  const b = ctx._cstBuf;
@@ -980,10 +983,13 @@ function deriveExpected(c) {
980
983
  return def.parsers.flatMap(deriveExpected);
981
984
  case "sequence":
982
985
  return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
986
+ case "attempt":
987
+ return deriveExpected(def.parser);
983
988
  case "node":
984
989
  case "grammar":
985
990
  case "trivia":
986
991
  case "token":
992
+ case "leaf":
987
993
  case "optional":
988
994
  case "many":
989
995
  case "oneOrMore":
@@ -1758,6 +1764,24 @@ function sequence(...parsers) {
1758
1764
  };
1759
1765
  }
1760
1766
 
1767
+ // src/combinators/attempt.ts
1768
+ function attempt(parser2) {
1769
+ return {
1770
+ _tag: "attempt",
1771
+ _meta: parser2._meta,
1772
+ _def: { tag: "attempt", parser: parser2 },
1773
+ parse(input, pos, ctx) {
1774
+ const mark = saveCstMark(ctx);
1775
+ const log = ctx._triviaLog?.length;
1776
+ const result = parser2.parse(input, pos, ctx);
1777
+ if (result.ok) return result;
1778
+ rollbackCstCapture(ctx, mark);
1779
+ if (log !== void 0 && ctx._triviaLog) ctx._triviaLog.length = log;
1780
+ return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1781
+ }
1782
+ };
1783
+ }
1784
+
1761
1785
  // src/combinators/repeat.ts
1762
1786
  function repItem(combinator, input, cur, ctx) {
1763
1787
  const mark = saveTriviaMark(ctx);
@@ -1933,19 +1957,19 @@ function sepBy(combinator, separator) {
1933
1957
  sepPos = advanceTrivia(input, cur, ctx);
1934
1958
  }
1935
1959
  }
1936
- const sep = separator.parse(input, sepPos, ctx);
1937
- if (!sep.ok) {
1960
+ const sep2 = separator.parse(input, sepPos, ctx);
1961
+ if (!sep2.ok) {
1938
1962
  rollbackTrivia(ctx, loopMark);
1939
1963
  break;
1940
1964
  }
1941
- let nextPos = sep.span.end;
1965
+ let nextPos = sep2.span.end;
1942
1966
  if (ctx.trivia) {
1943
1967
  if (needsDeferredTriviaCommit(ctx)) {
1944
- const scan = scanTrivia(input, sep.span.end, ctx);
1968
+ const scan = scanTrivia(input, sep2.span.end, ctx);
1945
1969
  scan.commit();
1946
1970
  nextPos = scan.end;
1947
1971
  } else {
1948
- nextPos = advanceTrivia(input, sep.span.end, ctx);
1972
+ nextPos = advanceTrivia(input, sep2.span.end, ctx);
1949
1973
  }
1950
1974
  }
1951
1975
  const next = combinator.parse(input, nextPos, ctx);
@@ -2030,10 +2054,14 @@ function markUnusedValues(root) {
2030
2054
  case "label":
2031
2055
  case "trivia":
2032
2056
  case "token":
2057
+ case "attempt":
2033
2058
  case "withCtx":
2034
2059
  case "expect":
2035
2060
  visit(def.parser, consumed);
2036
2061
  return;
2062
+ case "leaf":
2063
+ visit(def.parser, true);
2064
+ return;
2037
2065
  case "lazy":
2038
2066
  return;
2039
2067
  default:
@@ -2156,6 +2184,20 @@ function confirmedBuildArity(src) {
2156
2184
  if (/\barguments\b/.test(s)) return null;
2157
2185
  return parts.length;
2158
2186
  }
2187
+ function buildReadsChildren(def) {
2188
+ if (!def.build) return true;
2189
+ const src = def.buildSrc ?? def.build.toString();
2190
+ const arity = confirmedBuildArity(src);
2191
+ if (arity === null) return true;
2192
+ return arity >= 1;
2193
+ }
2194
+ function buildReadsRaw(def) {
2195
+ if (!def.build) return true;
2196
+ const src = def.buildSrc ?? def.build.toString();
2197
+ const arity = confirmedBuildArity(src);
2198
+ if (arity === null) return true;
2199
+ return arity >= 4;
2200
+ }
2159
2201
  function buildReadsTrivia(def) {
2160
2202
  if (!def.build) return true;
2161
2203
  const src = def.buildSrc ?? def.build.toString();
@@ -2211,9 +2253,11 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2211
2253
  case "many":
2212
2254
  case "oneOrMore":
2213
2255
  case "optional":
2256
+ case "attempt":
2214
2257
  case "transform":
2215
2258
  case "trivia":
2216
2259
  case "token":
2260
+ case "leaf":
2217
2261
  case "label":
2218
2262
  case "expect":
2219
2263
  case "withCtx":
@@ -2246,9 +2290,11 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2246
2290
  case "keywords":
2247
2291
  case "guard":
2248
2292
  case "token":
2293
+ case "leaf":
2249
2294
  return false;
2250
2295
  // Transparent single-child wrappers: recurse.
2251
2296
  case "optional":
2297
+ case "attempt":
2252
2298
  case "transform":
2253
2299
  case "trivia":
2254
2300
  case "label":
@@ -2295,6 +2341,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2295
2341
  case "many":
2296
2342
  case "oneOrMore":
2297
2343
  case "optional":
2344
+ case "attempt":
2298
2345
  case "transform":
2299
2346
  case "trivia":
2300
2347
  case "token":
@@ -2304,6 +2351,10 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2304
2351
  case "withCtx":
2305
2352
  case "not":
2306
2353
  return parserEnablesTriviaCapture(d.parser, seen);
2354
+ // leaf() clears the inner CST/trivia collector and publishes only its own
2355
+ // terminal result. An inner parser() capture cannot reach this node.
2356
+ case "leaf":
2357
+ return false;
2307
2358
  case "lazy": {
2308
2359
  try {
2309
2360
  return parserEnablesTriviaCapture(d.thunk(), seen);
@@ -2382,12 +2433,12 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2382
2433
  const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
2383
2434
  ctx._fields = savedFields;
2384
2435
  ctx._triviaCaptureMask = savedMask;
2385
- const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
2436
+ const { children: children2, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
2386
2437
  if (!r.ok) return r;
2387
2438
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
2388
2439
  const nodeType = def.type ?? missingInferredType();
2389
2440
  const cstOutput = ctx.build?._parsemanCstOutput === true;
2390
- 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 ? cstOutput && ctx.build ? ctx.build(nodeType, children, fields, r.span, rawChildren, triviaLog, st) : 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 };
2441
+ const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, st) : build(children2, fields, r.span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, r.span, rawChildren, triviaLog, st) : { _tag: "node", type: nodeType, span: { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
2391
2442
  const rawEntry = isCstChild(built) ? built : { _tag: "leaf", value: typeof built === "string" ? built : typeof built === "object" && built !== null ? input.slice(r.span.start, r.span.end) : "", span: r.span };
2392
2443
  if (saved.buf !== void 0 || saved.ch !== void 0) {
2393
2444
  pushCstChild(ctx, built, rawEntry);
@@ -2593,6 +2644,141 @@ function token(root) {
2593
2644
  }
2594
2645
  };
2595
2646
  }
2647
+ function leaf(root, fn) {
2648
+ const meta = {
2649
+ firstSet: root._meta.firstSet,
2650
+ canMatchNewline: root._meta.canMatchNewline,
2651
+ isTrivia: false
2652
+ };
2653
+ return {
2654
+ _tag: "leaf",
2655
+ _meta: meta,
2656
+ _def: { tag: "leaf", parser: root, fn },
2657
+ parse(input, pos, ctx) {
2658
+ const savedBuf = ctx._cstBuf;
2659
+ const savedChildren = ctx._cstChildren;
2660
+ const savedLeaves = ctx._cstLeaves;
2661
+ const savedRaw = ctx._cstRawChildren;
2662
+ const savedTriviaLog = ctx._cstTriviaLog;
2663
+ const savedOuterTriviaLog = ctx._triviaLog;
2664
+ const wasCapturing = cstCaptureActive(ctx);
2665
+ ctx._cstBuf = void 0;
2666
+ ctx._cstChildren = void 0;
2667
+ ctx._cstLeaves = void 0;
2668
+ ctx._cstRawChildren = void 0;
2669
+ ctx._cstTriviaLog = void 0;
2670
+ delete ctx._triviaLog;
2671
+ let result;
2672
+ try {
2673
+ result = root.parse(input, pos, ctx);
2674
+ } finally {
2675
+ ctx._cstBuf = savedBuf;
2676
+ ctx._cstChildren = savedChildren;
2677
+ ctx._cstLeaves = savedLeaves;
2678
+ ctx._cstRawChildren = savedRaw;
2679
+ ctx._cstTriviaLog = savedTriviaLog;
2680
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
2681
+ else ctx._triviaLog = savedOuterTriviaLog;
2682
+ }
2683
+ if (!result.ok) return result;
2684
+ const value = fn(result.value, result.span);
2685
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span: result.span });
2686
+ return { ok: true, value, span: result.span };
2687
+ }
2688
+ };
2689
+ }
2690
+
2691
+ // src/compiler/grammar-coverage-ids.ts
2692
+ function children(def, winners) {
2693
+ switch (def.tag) {
2694
+ case "sequence":
2695
+ case "choice":
2696
+ return def.parsers;
2697
+ case "many":
2698
+ case "oneOrMore":
2699
+ case "optional":
2700
+ case "attempt":
2701
+ case "transform":
2702
+ case "trivia":
2703
+ case "token":
2704
+ case "leaf":
2705
+ case "label":
2706
+ case "field":
2707
+ case "grammar":
2708
+ case "not":
2709
+ case "node":
2710
+ case "guard":
2711
+ case "withCtx":
2712
+ case "recover":
2713
+ case "expect":
2714
+ return "parser" in def ? [def.parser] : [];
2715
+ case "sepBy":
2716
+ return [def.parser, def.separator];
2717
+ case "skip":
2718
+ return [def.main, def.skipped];
2719
+ case "scanTo":
2720
+ return [def.sentinel, ...def.skip];
2721
+ case "lazy": {
2722
+ let resolved;
2723
+ try {
2724
+ resolved = def.thunk();
2725
+ } catch {
2726
+ return [];
2727
+ }
2728
+ const name = resolved._ruleName;
2729
+ return name && winners?.[name] ? [winners[name]] : [resolved];
2730
+ }
2731
+ default:
2732
+ return [];
2733
+ }
2734
+ }
2735
+ function buildGrammarPlan(entry, winners) {
2736
+ const definitions = /* @__PURE__ */ new Map();
2737
+ const choices = /* @__PURE__ */ new WeakMap();
2738
+ const attempts = /* @__PURE__ */ new WeakMap();
2739
+ const labels = /* @__PURE__ */ new WeakMap();
2740
+ const rules2 = /* @__PURE__ */ new WeakMap();
2741
+ const winnerNames = /* @__PURE__ */ new Map();
2742
+ if (winners) {
2743
+ for (const [name, winner] of Object.entries(winners)) {
2744
+ winnerNames.set(winner, name);
2745
+ if (winner._def.tag === "lazy") {
2746
+ try {
2747
+ winnerNames.set(winner._def.thunk(), name);
2748
+ } catch {
2749
+ }
2750
+ }
2751
+ }
2752
+ }
2753
+ const seen = /* @__PURE__ */ new Set();
2754
+ const visit = (parser2, path2) => {
2755
+ if (seen.has(parser2)) return;
2756
+ seen.add(parser2);
2757
+ const rule = winnerNames.get(parser2) ?? parser2._ruleName;
2758
+ if (rule && (winnerNames.has(parser2) || parser2._def.tag !== "lazy")) {
2759
+ const id = `rule:${rule}`;
2760
+ definitions.set(id, { id, kind: "rule" });
2761
+ rules2.set(parser2, id);
2762
+ }
2763
+ if (parser2._def.tag === "choice") {
2764
+ const ids = parser2._def.parsers.map((_, index) => `choice:${path2}/arm:${index}`);
2765
+ choices.set(parser2, ids);
2766
+ ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
2767
+ }
2768
+ if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
2769
+ if (parser2._def.tag === "label") {
2770
+ const id = `label:${path2}`;
2771
+ labels.set(parser2, [id]);
2772
+ definitions.set(id, { id, kind: "label" });
2773
+ }
2774
+ children(parser2._def, winners).forEach((child, index) => visit(child, `${path2}/${parser2._def.tag}:${index}`));
2775
+ };
2776
+ const roots = Array.isArray(entry) ? entry : [entry];
2777
+ for (const root of roots) {
2778
+ visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
2779
+ }
2780
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
2781
+ }
2596
2782
 
2597
2783
  // src/compiler/scannable-run.ts
2598
2784
  var BACKSLASH = 92;
@@ -3985,21 +4171,23 @@ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
3985
4171
  }
3986
4172
  function failBody(ctx, expected, posExpr) {
3987
4173
  const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
4174
+ const trace = ctx.activeCoverageRuleId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(ctx.activeCoverageRuleId)}, phase: 'failure', offset: ${posExpr} }); `;
3988
4175
  if (ctx.failLabel) {
3989
- if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3990
- return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
4176
+ if (!ctx.recordFail) return probe ? `{ ${probe}${trace}break ${ctx.failLabel} }` : trace ? `{ ${trace}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4177
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; ${trace}break ${ctx.failLabel} }`;
3991
4178
  }
3992
- return probe + failReturn(expected, posExpr);
4179
+ return probe + trace + failReturn(expected, posExpr);
3993
4180
  }
3994
4181
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
3995
4182
  const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
4183
+ const trace = ctx.activeCoverageRuleId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(ctx.activeCoverageRuleId)}, phase: 'failure', offset: ${posExpr} }); `;
3996
4184
  if (ctx.failLabel) {
3997
- if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4185
+ if (!ctx.recordFail) return probe ? `{ ${probe}${trace}break ${ctx.failLabel} }` : trace ? `{ ${trace}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
3998
4186
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
3999
- return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
4187
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; ${trace}break ${ctx.failLabel} }`;
4000
4188
  }
4001
- if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
4002
- return probe + failReturnArr(expectedArr, posExpr);
4189
+ if (!constant) return `${probe}${trace}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
4190
+ return probe + trace + failReturnArr(expectedArr, posExpr);
4003
4191
  }
4004
4192
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
4005
4193
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4034,7 +4222,7 @@ var NAMED_FN_END = "_pfEnd";
4034
4222
  function namedFnPrelude() {
4035
4223
  return [`const ${NAMED_FN_FAIL} = {}`, `let ${NAMED_FN_END}`];
4036
4224
  }
4037
- function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
4225
+ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar, failureRuleId) {
4038
4226
  ctx.namedFnDecls.push([
4039
4227
  `function ${fnName}(input, _pos, _ctx) {`,
4040
4228
  ` _pfail: {`,
@@ -4042,6 +4230,7 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
4042
4230
  ` ${NAMED_FN_END} = ${endVar}`,
4043
4231
  ` return ${valueVar}`,
4044
4232
  ` }`,
4233
+ ...failureRuleId === void 0 ? [] : [` _ctx._grammarTrace?.write({ id: ${JSON.stringify(failureRuleId)}, phase: 'failure', offset: _ctx._fe ?? _pos })`],
4045
4234
  ` return ${NAMED_FN_FAIL}`,
4046
4235
  `}`
4047
4236
  ].join("\n"));
@@ -4203,6 +4392,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4203
4392
  case "not":
4204
4393
  case "trivia":
4205
4394
  case "token":
4395
+ case "leaf":
4206
4396
  case "scanTo":
4207
4397
  case "unknown":
4208
4398
  return false;
@@ -4214,6 +4404,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4214
4404
  // nothing committed remains.
4215
4405
  case "choice":
4216
4406
  return false;
4407
+ case "attempt":
4408
+ return false;
4217
4409
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
4218
4410
  case "optional":
4219
4411
  case "many":
@@ -4266,6 +4458,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
4266
4458
  case "keywords":
4267
4459
  case "node":
4268
4460
  case "token":
4461
+ case "leaf":
4269
4462
  return true;
4270
4463
  case "not":
4271
4464
  case "guard":
@@ -4280,6 +4473,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
4280
4473
  case "many":
4281
4474
  case "oneOrMore":
4282
4475
  case "optional":
4476
+ case "attempt":
4283
4477
  case "transform":
4284
4478
  case "label":
4285
4479
  case "field":
@@ -4621,35 +4815,55 @@ function needsFirstSetGuard(p) {
4621
4815
  const fs2 = p._meta.firstSet;
4622
4816
  return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
4623
4817
  }
4624
- function emitDisjointArm(p, ctx, pos, valV, endV) {
4625
- if (failsAtStart(p)) {
4818
+ function coverageHit(ctx, id, offset = "_pos", end) {
4819
+ return id === void 0 ? [] : [
4820
+ `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(id)})`,
4821
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'selected', offset: ${offset}${end === void 0 ? "" : `, end: ${end}`} })`,
4822
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'success', offset: ${offset}${end === void 0 ? "" : `, end: ${end}`} })`
4823
+ ];
4824
+ }
4825
+ function coverageAttempt(ctx, id, offset) {
4826
+ return id === void 0 ? [] : [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'attempt', offset: ${offset} })`];
4827
+ }
4828
+ function coverageFailureBacktrack(ctx, id, failureOffset, startOffset) {
4829
+ return id === void 0 ? [] : [
4830
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'failure', offset: ${failureOffset} })`,
4831
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'backtrack', offset: ${startOffset} })`
4832
+ ];
4833
+ }
4834
+ function emitDisjointArm(p, ctx, pos, valV, endV, coverageId) {
4835
+ if (failsAtStart(p) && coverageId === void 0) {
4626
4836
  const r = emit(p, ctx, pos);
4627
- return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
4837
+ return [...coverageAttempt(ctx, coverageId, pos), ...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`, ...coverageHit(ctx, coverageId, pos, r.endVar)];
4628
4838
  }
4629
4839
  const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
4630
4840
  return [
4841
+ ...coverageAttempt(ctx, coverageId, pos),
4631
4842
  ...stmts,
4632
- ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
4843
+ ...emitIfFail(ctx, `!${okVar}`, `${coverageFailureBacktrack(ctx, coverageId, "_ctx._fe", pos).join("; ")}; ${failArrBody(ctx, "_ctx._fx", pos, false)}`),
4633
4844
  `${ind(ctx)}${valV} = ${valVar}`,
4634
- `${ind(ctx)}${endV} = ${endVar}`
4845
+ `${ind(ctx)}${endV} = ${endVar}`,
4846
+ ...coverageHit(ctx, coverageId, pos, endVar)
4635
4847
  ];
4636
4848
  }
4637
- function emitDisjointArmGated(def, i, ctx, pos, valV, endV) {
4849
+ function emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageId) {
4638
4850
  const p = def.parsers[i];
4639
4851
  const gate = def.gates[i];
4640
- if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV);
4852
+ if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV, coverageId);
4641
4853
  const gateIdx = ctx.mapFns.length;
4642
4854
  ctx.mapFns.push(gate);
4643
4855
  ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4644
4856
  const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4645
4857
  const stmts = [`${ind(ctx)}if (${gateCond}) {`];
4646
4858
  ctx.indent++;
4647
- stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4859
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV, coverageId));
4648
4860
  ctx.indent--;
4649
4861
  stmts.push(`${ind(ctx)}} else { ${failArrBody(ctx, deriveExpectedArr([p]), pos)} }`);
4650
4862
  return stmts;
4651
4863
  }
4652
- function emitChoice(def, ctx, pos) {
4864
+ function emitChoice(parser2, def, ctx, pos) {
4865
+ const coverageIds = ctx.coverage?.plan.choices.get(parser2);
4866
+ const coverageBase = coverageIds?.[0]?.slice(0, -1);
4653
4867
  const allExpected = deriveExpectedArr(def.parsers);
4654
4868
  if (def.disjoint) {
4655
4869
  const codeV = v(ctx, "_code");
@@ -4668,7 +4882,7 @@ function emitChoice(def, ctx, pos) {
4668
4882
  stmts.push(`${ind(ctx)}{`);
4669
4883
  ctx.indent++;
4670
4884
  stmts.push(
4671
- ...emitDisjointArmGated(def, i, ctx, pos, valV, endV),
4885
+ ...emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageIds?.[i]),
4672
4886
  `${ind(ctx)}break`
4673
4887
  );
4674
4888
  ctx.indent--;
@@ -4687,16 +4901,16 @@ function emitChoice(def, ctx, pos) {
4687
4901
  first = false;
4688
4902
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
4689
4903
  ctx.indent++;
4690
- stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV));
4904
+ stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageIds?.[i]));
4691
4905
  ctx.indent--;
4692
4906
  stmts.push(`${ind(ctx)}}`);
4693
4907
  }
4694
4908
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
4695
4909
  return { stmts, valueVar: valV, endVar: endV };
4696
4910
  }
4697
- return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
4911
+ return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
4698
4912
  }
4699
- function emitGreedyClassify(def, superIndex, ctx, pos) {
4913
+ function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
4700
4914
  const superParser = def.parsers[superIndex];
4701
4915
  const regexDef = getCoreRegexDef(superParser);
4702
4916
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -4712,10 +4926,12 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4712
4926
  const endV = v(ctx, "_ge");
4713
4927
  const valV = v(ctx, "_gcv");
4714
4928
  const regexExpected = JSON.stringify(deriveExpected(superParser));
4929
+ const superCoverageId = coverageBase === void 0 ? void 0 : `${coverageBase}${superIndex}`;
4715
4930
  const stmts = [
4931
+ ...coverageAttempt(ctx, superCoverageId, pos),
4716
4932
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
4717
4933
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
4718
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
4934
+ ...emitIfFail(ctx, `${matchV} === null`, `${coverageFailureBacktrack(ctx, superCoverageId, pos, pos).join("; ")}; ${failArrBody(ctx, regexExpected, pos)}`),
4719
4935
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
4720
4936
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
4721
4937
  `${ind(ctx)}let ${valV}`
@@ -4731,8 +4947,9 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4731
4947
  stmts.push(`${ind(ctx)}${kw} (${wordV} === ${JSON.stringify(litVal)}) {`);
4732
4948
  ctx.indent++;
4733
4949
  const tR = emitTransformChain(p, JSON.stringify(litVal), endV, pos, ctx);
4950
+ stmts.push(...coverageFailureBacktrack(ctx, superCoverageId, pos, pos));
4734
4951
  stmts.push(...emitLeafCapture(ctx, JSON.stringify(litVal), pos, endV));
4735
- stmts.push(...tR.stmts, `${ind(ctx)}${valV} = ${tR.valueVar}`);
4952
+ stmts.push(...tR.stmts, `${ind(ctx)}${valV} = ${tR.valueVar}`, ...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${i}`, pos, endV));
4736
4953
  ctx.indent--;
4737
4954
  stmts.push(`${ind(ctx)}}`);
4738
4955
  }
@@ -4740,12 +4957,12 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4740
4957
  const regexKw = first ? "if" : "else";
4741
4958
  stmts.push(`${ind(ctx)}${regexKw} {`);
4742
4959
  ctx.indent++;
4743
- stmts.push(...emitLeafCapture(ctx, wordV, pos, endV), ...rR.stmts, `${ind(ctx)}${valV} = ${rR.valueVar}`);
4960
+ stmts.push(...emitLeafCapture(ctx, wordV, pos, endV), ...rR.stmts, `${ind(ctx)}${valV} = ${rR.valueVar}`, ...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${superIndex}`, pos, endV));
4744
4961
  ctx.indent--;
4745
4962
  stmts.push(`${ind(ctx)}}`);
4746
4963
  return { stmts, valueVar: valV, endVar: endV };
4747
4964
  }
4748
- function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
4965
+ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos, coverageBase) {
4749
4966
  const valV = v(ctx, "_llv");
4750
4967
  const endV = v(ctx, "_lle");
4751
4968
  const stmts = [`${ind(ctx)}let ${valV}, ${endV} = ${pos}`];
@@ -4762,10 +4979,12 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
4762
4979
  ctx.indent++;
4763
4980
  const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
4764
4981
  stmts.push(
4982
+ ...coverageAttempt(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${idx}`, pos),
4765
4983
  ...emitLeafCapture(ctx, JSON.stringify(litVal), pos, litEnd),
4766
4984
  ...tR.stmts,
4767
4985
  `${ind(ctx)}${valV} = ${tR.valueVar}`,
4768
- `${ind(ctx)}${endV} = ${litEnd}`
4986
+ `${ind(ctx)}${endV} = ${litEnd}`,
4987
+ ...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${idx}`, pos, litEnd)
4769
4988
  );
4770
4989
  ctx.indent--;
4771
4990
  stmts.push(`${ind(ctx)}}`);
@@ -4773,16 +4992,18 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
4773
4992
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
4774
4993
  return { stmts, valueVar: valV, endVar: endV };
4775
4994
  }
4776
- function emitFirstMatch(def, ctx, pos) {
4995
+ function emitFirstMatch(def, ctx, pos, coverageBase) {
4777
4996
  const resValV = v(ctx, "_crv");
4778
4997
  const resEndV = v(ctx, "_cre");
4779
4998
  const resOkV = v(ctx, "_crok");
4780
4999
  const codeV = v(ctx, "_chcode");
4781
5000
  const slots = def.parsers.map(() => v(ctx, "_cfx"));
5001
+ const autoRejectedV = coverageBase === void 0 ? void 0 : v(ctx, "_carej");
4782
5002
  const ind0 = ind(ctx);
4783
5003
  const stmts = [
4784
5004
  `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
4785
5005
  `${ind0}let ${slots.join(", ")}`,
5006
+ ...autoRejectedV === void 0 ? [] : [`${ind0}const ${autoRejectedV} = []`],
4786
5007
  `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
4787
5008
  ];
4788
5009
  for (let i = 0; i < def.parsers.length; i++) {
@@ -4818,6 +5039,7 @@ function emitFirstMatch(def, ctx, pos) {
4818
5039
  stmts.push(`${ind0}if (${skipCond}) {`);
4819
5040
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
4820
5041
  ctx.indent += fsGuard ? 2 : 1;
5042
+ if (coverageBase !== void 0) stmts.push(`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'attempt', offset: ${pos} })`);
4821
5043
  if (markLeaves) {
4822
5044
  stmts.push(
4823
5045
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
@@ -4840,13 +5062,21 @@ function emitFirstMatch(def, ctx, pos) {
4840
5062
  stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
4841
5063
  stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
4842
5064
  stmts.push(`${ind(ctx)} ${resOkV} = true`);
5065
+ if (autoRejectedV !== void 0) {
5066
+ stmts.push(`${ind(ctx)} for (const _rejectedId of ${autoRejectedV}) { _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'failure', offset: ${pos} }); _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'backtrack', offset: ${pos} }) }`);
5067
+ }
5068
+ stmts.push(...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${i}`, pos, endVar));
4843
5069
  stmts.push(`${ind(ctx)} }`);
5070
+ if (autoRejectedV !== void 0) {
5071
+ stmts.push(`${ind(ctx)} else { ${autoRejectedV}.push(${JSON.stringify(`${coverageBase}${i}`)}) }`);
5072
+ }
4844
5073
  stmts.push(`${ind(ctx)}}`);
4845
- stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
5074
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}; ${coverageBase === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} });`} }`);
4846
5075
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
4847
5076
  } else {
4848
- stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
4849
- stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
5077
+ const rejectedFlush = autoRejectedV === void 0 ? "" : `; for (const _rejectedId of ${autoRejectedV}) { _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'failure', offset: ${pos} }); _ctx._grammarTrace?.write({ id: _rejectedId, phase: 'backtrack', offset: ${pos} }) }`;
5078
+ stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true${coverageBase === void 0 ? "" : `${rejectedFlush}; _ctx._grammarCoverage?.(${JSON.stringify(`${coverageBase}${i}`)}); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'selected', offset: ${pos}, end: ${endVar} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'success', offset: ${pos}, end: ${endVar} })`} }`);
5079
+ stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""}${coverageBase === void 0 ? "" : `; _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'failure', offset: ${atStart ? pos : "_ctx._fe"} }); _ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'backtrack', offset: ${pos} })`} }`);
4850
5080
  }
4851
5081
  ctx.indent -= fsGuard ? 2 : 1;
4852
5082
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -4856,12 +5086,12 @@ function emitFirstMatch(def, ctx, pos) {
4856
5086
  stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
4857
5087
  return { stmts, valueVar: resValV, endVar: resEndV };
4858
5088
  }
4859
- function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
5089
+ function emitNonDisjoint(def, strategy, allExpected, ctx, pos, coverageBase) {
4860
5090
  if (strategy.tag === "greedyClassify")
4861
- return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
5091
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos, coverageBase);
4862
5092
  if (strategy.tag === "literalsLongestFirst")
4863
- return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
4864
- return emitFirstMatch(def, ctx, pos);
5093
+ return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos, coverageBase);
5094
+ return emitFirstMatch(def, ctx, pos, coverageBase);
4865
5095
  }
4866
5096
  function pushMapFn(ctx, fn, src) {
4867
5097
  if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
@@ -4979,6 +5209,27 @@ function emitOptional(def, ctx, pos) {
4979
5209
  ];
4980
5210
  return { stmts, valueVar: valV, endVar: endV };
4981
5211
  }
5212
+ function emitAttempt(p, def, ctx, pos) {
5213
+ const inner = emitFallible(def.parser, ctx, pos);
5214
+ const leaves = v(ctx, "_atl");
5215
+ const raw = v(ctx, "_atr");
5216
+ const trivia2 = v(ctx, "_att");
5217
+ const log = v(ctx, "_atg");
5218
+ const fields = v(ctx, "_atf");
5219
+ const errors = v(ctx, "_ate");
5220
+ const rollback = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`;
5221
+ const traceId = ctx.coverage?.plan.attempts.get(p);
5222
+ const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5223
+ return {
5224
+ stmts: [
5225
+ `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${trivia2} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`,
5226
+ ...inner.stmts,
5227
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
5228
+ ],
5229
+ valueVar: inner.valVar,
5230
+ endVar: inner.endVar
5231
+ };
5232
+ }
4982
5233
  function emitSepBy(_p, def, ctx, pos) {
4983
5234
  const arrV = v(ctx, "_arr");
4984
5235
  const curV = v(ctx, "_cur");
@@ -5224,11 +5475,11 @@ function tokenNullableRegex(def) {
5224
5475
  }
5225
5476
  if (inner.tag === "sepBy") {
5226
5477
  const item = tokenSequenceSource(inner.parser);
5227
- const sep = tokenSequenceSource(inner.separator);
5228
- if (!item || !sep) return null;
5229
- const combined = combineTokenRegexSources([item, sep]);
5478
+ const sep2 = tokenSequenceSource(inner.separator);
5479
+ if (!item || !sep2) return null;
5480
+ const combined = combineTokenRegexSources([item, sep2]);
5230
5481
  if (!combined) return null;
5231
- return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
5482
+ return { tag: "regex", source: `(?:${item.source}(?:${sep2.source}${item.source})*)?`, flags: combined.flags };
5232
5483
  }
5233
5484
  return null;
5234
5485
  }
@@ -5269,6 +5520,36 @@ function emitToken(def, ctx, pos) {
5269
5520
  endVar: inner.endVar
5270
5521
  };
5271
5522
  }
5523
+ function emitLeaf(def, ctx, pos) {
5524
+ const inner = emitFallible(def.parser, ctx, pos);
5525
+ const i = ind(ctx);
5526
+ const sc = v(ctx, "_leafCh");
5527
+ const sl = v(ctx, "_leafLv");
5528
+ const sr = v(ctx, "_leafRaw");
5529
+ const stl = v(ctx, "_leafTl");
5530
+ const sol = v(ctx, "_leafLog");
5531
+ const sb = v(ctx, "_leafBuf");
5532
+ const rawV = v(ctx, "_leafRawValue");
5533
+ const valV = v(ctx, "_leaf");
5534
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
5535
+ return {
5536
+ stmts: [
5537
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${stl} = _ctx._cstTriviaLog, ${sol} = _ctx._triviaLog, ${sb} = _ctx._cstBuf`,
5538
+ `${i}_ctx._cstChildren = undefined; _ctx._cstLeaves = undefined; _ctx._cstRawChildren = undefined; _ctx._cstTriviaLog = undefined; _ctx._triviaLog = undefined; _ctx._cstBuf = undefined`,
5539
+ `${i}try {`,
5540
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
5541
+ `${i}} finally {`,
5542
+ `${i} _ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx._cstTriviaLog = ${stl}; _ctx._triviaLog = ${sol}; _ctx._cstBuf = ${sb}`,
5543
+ `${i}}`,
5544
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
5545
+ `${i}const ${rawV} = ${inner.valVar}`,
5546
+ `${i}const ${valV} = ${mfRef(ctx)}[${fnIdx}](${rawV}, { start: ${pos}, end: ${inner.endVar} })`,
5547
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
5548
+ ],
5549
+ valueVar: valV,
5550
+ endVar: inner.endVar
5551
+ };
5552
+ }
5272
5553
  function emitNode(def, ctx, pos) {
5273
5554
  if (def.type === void 0) {
5274
5555
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -5284,6 +5565,8 @@ function emitNode(def, ctx, pos) {
5284
5565
  const i = ind(ctx);
5285
5566
  const capturesTrivia = mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && buildReadsTrivia(def);
5286
5567
  const clonesState = !structural && buildReadsState(def);
5568
+ const capturesChildren = !structural && (mkType !== null || def.unwrap || def.collapse || buildReadsChildren(def));
5569
+ const capturesRaw = !structural && (mkType !== null || buildReadsRaw(def));
5287
5570
  const hasFields = parserHasOwnFields(def.parser);
5288
5571
  const capturesFields = hasFields && !structural && buildReadsFields(def);
5289
5572
  const innerEnablesTriviaCapture = parserEnablesTriviaCapture(def.parser);
@@ -5302,9 +5585,13 @@ function emitNode(def, ctx, pos) {
5302
5585
  const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
5303
5586
  const profileRecognizer = recV;
5304
5587
  const profileCapture = capV;
5588
+ const directCstV = !structural && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
5589
+ const directCstGate = directCstV === null ? "false" : `${profileCapture} || _ctx.build?._parsemanCstOutput === true`;
5305
5590
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
5306
5591
  const allocStmt = structural ? `${i}const ${capTLv} = !(${profileRecognizer}) && (${profileCapture} || ${structuralCapturesTrivia ? "true" : hostTriviaGate}), ${capSTv} = !(${profileRecognizer} || ${profileCapture}) && (_ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 6)))${capFv ? `, ${capFv} = !(${profileRecognizer}) && (${profileCapture} || (_ctx.build !== undefined && _hostReads(_ctx.build, 2)))` : ""}
5307
- ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : []` : `${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRecognizer} ? undefined : []`;
5592
+ ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
5593
+ ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
5594
+ ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
5308
5595
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
5309
5596
  const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
5310
5597
  const sf = hasFields ? v(ctx, "_sf") : null;
@@ -5421,14 +5708,23 @@ function emitLazy(p, def, ctx, pos) {
5421
5708
  const savedIndent = ctx.indent;
5422
5709
  const savedFailLabel = ctx.failLabel;
5423
5710
  const savedRecord = ctx.recordFail;
5711
+ const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
5712
+ const savedActiveRule = ctx.activeCoverageRuleId;
5713
+ const savedSuppressFailure = ctx.suppressCoverageFailure;
5424
5714
  ctx.indent = 1;
5425
5715
  ctx.failLabel = "_pfail";
5426
5716
  ctx.recordFail = true;
5717
+ if (failureRuleId !== void 0) {
5718
+ ctx.activeCoverageRuleId = void 0;
5719
+ ctx.suppressCoverageFailure = true;
5720
+ }
5427
5721
  const r = emit(resolved, ctx, "_pos");
5428
5722
  ctx.indent = savedIndent;
5429
5723
  ctx.failLabel = savedFailLabel;
5430
5724
  ctx.recordFail = savedRecord;
5431
- pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
5725
+ ctx.activeCoverageRuleId = savedActiveRule;
5726
+ ctx.suppressCoverageFailure = savedSuppressFailure;
5727
+ pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
5432
5728
  }
5433
5729
  const fnName = ctx.namedParsers.get(p);
5434
5730
  return emitNamedFnCall(ctx, fnName, pos);
@@ -5479,6 +5775,30 @@ function emitExpect(def, ctx, pos) {
5479
5775
  return { stmts, valueVar: valVar, endVar };
5480
5776
  }
5481
5777
  function emit(p, ctx, pos) {
5778
+ const dispatch = (emissionPos = pos) => {
5779
+ const savedRule = ctx.activeCoverageRuleId;
5780
+ const rule = ctx.coverage?.plan.rules.get(p);
5781
+ if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
5782
+ const result = emitDispatch(p, ctx, emissionPos);
5783
+ ctx.activeCoverageRuleId = savedRule;
5784
+ return result;
5785
+ };
5786
+ const instrument = (r) => {
5787
+ const ruleId = ctx.coverage?.plan.rules.get(p);
5788
+ const labelIds = ctx.coverage?.plan.labels.get(p);
5789
+ if (!ruleId && !labelIds) return r;
5790
+ return {
5791
+ ...r,
5792
+ stmts: [
5793
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`] : [],
5794
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'enter', offset: ${pos} })`] : [],
5795
+ ...r.stmts,
5796
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'success', offset: ${pos}, end: ${r.endVar} })`] : [],
5797
+ ...labelIds?.flatMap((id) => [`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(id)})`]) ?? [],
5798
+ ...labelIds?.flatMap((id) => [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'success', offset: ${pos}, end: ${r.endVar} })`]) ?? []
5799
+ ]
5800
+ };
5801
+ };
5482
5802
  const usage = ctx.lazyUsage;
5483
5803
  if (usage && !ctx.noHoist && !ctx.capAsTrivia && !ctx.ruleNames?.has(p) && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && // Only hoist subtrees big enough that de-duplicating them beats the call
5484
5804
  // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
@@ -5495,14 +5815,14 @@ function emit(p, ctx, pos) {
5495
5815
  ctx.indent = 1;
5496
5816
  ctx.failLabel = "_pfail";
5497
5817
  ctx.recordFail = true;
5498
- const r = emitDispatch(p, ctx, "_pos");
5818
+ const r = dispatch("_pos");
5499
5819
  ctx.indent = savedIndent;
5500
5820
  ctx.failLabel = savedFailLabel;
5501
5821
  ctx.recordFail = savedRecord;
5502
5822
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
5503
- return emitNamedFnCall(ctx, fnName, pos);
5823
+ return instrument(emitNamedFnCall(ctx, fnName, pos));
5504
5824
  }
5505
- return emitDispatch(p, ctx, pos);
5825
+ return instrument(dispatch());
5506
5826
  }
5507
5827
  function emitDispatch(p, ctx, pos) {
5508
5828
  const def = p._def;
@@ -5516,7 +5836,9 @@ function emitDispatch(p, ctx, pos) {
5516
5836
  case "sequence":
5517
5837
  return emitSeq(def, ctx, pos);
5518
5838
  case "choice":
5519
- return emitChoice(def, ctx, pos);
5839
+ return emitChoice(p, def, ctx, pos);
5840
+ case "attempt":
5841
+ return emitAttempt(p, def, ctx, pos);
5520
5842
  case "many":
5521
5843
  case "oneOrMore":
5522
5844
  return emitMany(def, ctx, pos);
@@ -5585,6 +5907,8 @@ function emitDispatch(p, ctx, pos) {
5585
5907
  return emit(def.parser, ctx, pos);
5586
5908
  case "token":
5587
5909
  return emitToken(def, ctx, pos);
5910
+ case "leaf":
5911
+ return emitLeaf(def, ctx, pos);
5588
5912
  case "label": {
5589
5913
  const inner = emitFallible(def.parser, ctx, pos);
5590
5914
  return {
@@ -5716,6 +6040,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5716
6040
  case "grammar":
5717
6041
  case "trivia":
5718
6042
  case "token":
6043
+ case "leaf":
5719
6044
  case "label":
5720
6045
  case "field":
5721
6046
  case "optional":
@@ -5743,16 +6068,32 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5743
6068
  return false;
5744
6069
  }
5745
6070
  }
6071
+ function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
6072
+ if (seen.has(p)) return false;
6073
+ seen.add(p);
6074
+ const d = p._def;
6075
+ if (d.tag === "node" && d.build !== void 0) return true;
6076
+ if (d.tag === "lazy") {
6077
+ try {
6078
+ return hasDirectBuildDef(d.thunk(), seen);
6079
+ } catch {
6080
+ return false;
6081
+ }
6082
+ }
6083
+ return childrenOf(d).some((child) => hasDirectBuildDef(child, seen));
6084
+ }
5746
6085
  function childrenOf(def) {
5747
6086
  switch (def.tag) {
5748
6087
  case "sequence":
5749
6088
  case "choice":
5750
6089
  return def.parsers;
5751
6090
  case "many":
6091
+ case "attempt":
5752
6092
  case "optional":
5753
6093
  case "transform":
5754
6094
  case "trivia":
5755
6095
  case "token":
6096
+ case "leaf":
5756
6097
  case "label":
5757
6098
  case "field":
5758
6099
  case "grammar":
@@ -5927,10 +6268,25 @@ function compile(combinator, mapFnSources, opts) {
5927
6268
  namedFnDecls: [],
5928
6269
  capturing: hasNodeDef(combinator),
5929
6270
  recovery: opts?.recovery ?? false,
6271
+ ...opts?.coverage ? { coverage: { plan: buildGrammarPlan(combinator), entry: combinator } } : {},
5930
6272
  lazyUsage: analyzeLazyUsage(combinator),
5931
6273
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5932
6274
  };
5933
6275
  const r = emit(combinator, ctx, "_pos");
6276
+ const coverageRootRuleId = ctx.coverage?.plan.rules.get(combinator);
6277
+ const resultBody = coverageRootRuleId === void 0 ? [
6278
+ ` let pos = _pos`,
6279
+ ...r.stmts,
6280
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6281
+ ] : [
6282
+ ` const _coverageResult = (() => {`,
6283
+ ` let pos = _pos`,
6284
+ ...r.stmts,
6285
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6286
+ ` })()`,
6287
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6288
+ ` return _coverageResult`
6289
+ ];
5934
6290
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
5935
6291
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
5936
6292
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
@@ -5943,9 +6299,7 @@ function compile(combinator, mapFnSources, opts) {
5943
6299
  ...namedPrelude,
5944
6300
  ctx.namedFnDecls.join("\n\n"),
5945
6301
  `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
5946
- ` let pos = _pos`,
5947
- ...r.stmts,
5948
- ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6302
+ ...resultBody,
5949
6303
  `}`
5950
6304
  ].join("\n");
5951
6305
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
@@ -5955,9 +6309,15 @@ function compile(combinator, mapFnSources, opts) {
5955
6309
  ...ctx.expectedDecls,
5956
6310
  ...namedPrelude,
5957
6311
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
5958
- `let pos = _pos`,
5959
- ...r.stmts,
5960
- `return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6312
+ ...coverageRootRuleId === void 0 ? [`let pos = _pos`, ...r.stmts, `return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`] : [
6313
+ `const _coverageResult = (() => {`,
6314
+ ` let pos = _pos`,
6315
+ ...r.stmts,
6316
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6317
+ `})()`,
6318
+ `if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6319
+ `return _coverageResult`
6320
+ ]
5961
6321
  ].join("\n"));
5962
6322
  const defaultCtx = {
5963
6323
  trackLines: false,
@@ -5969,10 +6329,11 @@ function compile(combinator, mapFnSources, opts) {
5969
6329
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
5970
6330
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
5971
6331
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
5972
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
6332
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources, coverageRootRuleId) : null;
5973
6333
  return {
5974
6334
  source,
5975
6335
  inlineExpression,
6336
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
5976
6337
  parse(input, pos = 0) {
5977
6338
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, defaultCtx);
5978
6339
  },
@@ -6006,6 +6367,7 @@ function publicRuleWrapperSource(rule, fnSource) {
6006
6367
  }
6007
6368
  function compileRuleMap(ruleMap, opts) {
6008
6369
  for (const [, rule] of ruleMap) markUnusedValues(rule);
6370
+ const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
6009
6371
  const grammarTrivia = opts?.trivia;
6010
6372
  const ctx = {
6011
6373
  vars: 0,
@@ -6026,6 +6388,7 @@ function compileRuleMap(ruleMap, opts) {
6026
6388
  namedFnDecls: [],
6027
6389
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
6028
6390
  recovery: opts?.recovery ?? false,
6391
+ ...opts?.coverage ? { coverage: { plan: buildGrammarPlan(ruleMap.map(([, rule]) => rule), coverageWinners) } } : {},
6029
6392
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
6030
6393
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
6031
6394
  };
@@ -6068,15 +6431,34 @@ function compileRuleMap(ruleMap, opts) {
6068
6431
  return i > 0 ? ["", ...lines] : lines;
6069
6432
  })
6070
6433
  ].filter(Boolean);
6071
- const entryFnText = (r) => [
6072
- `function(input, _pos, _ctx) {`,
6073
- ` let pos = _pos`,
6074
- ...r.stmts,
6075
- ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6076
- `}`
6077
- ].join("\n");
6434
+ const entryRuleId = (rule) => {
6435
+ const direct = ctx.coverage?.plan.rules.get(rule);
6436
+ if (direct !== void 0 || rule._def.tag !== "lazy") return direct;
6437
+ try {
6438
+ return ctx.coverage?.plan.rules.get(rule._def.thunk());
6439
+ } catch {
6440
+ return void 0;
6441
+ }
6442
+ };
6443
+ const entryFnText = (r, rule) => {
6444
+ const ruleId = entryRuleId(rule);
6445
+ const body = ruleId === void 0 ? [
6446
+ ` let pos = _pos`,
6447
+ ...r.stmts,
6448
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6449
+ ] : [
6450
+ ` const _coverageResult = (() => {`,
6451
+ ` let pos = _pos`,
6452
+ ...r.stmts,
6453
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6454
+ ` })()`,
6455
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6456
+ ` return _coverageResult`
6457
+ ];
6458
+ return [`function(input, _pos, _ctx) {`, ...body, `}`].join("\n");
6459
+ };
6078
6460
  const objBody = perEntry.map(({ key, rule, r }) => {
6079
- const src = publicRuleWrapperSource(rule, entryFnText(r));
6461
+ const src = publicRuleWrapperSource(rule, entryFnText(r, rule));
6080
6462
  return ` ${JSON.stringify(key)}: ${src.split("\n").join("\n ")}`;
6081
6463
  }).join(",\n");
6082
6464
  const replacement = [
@@ -6087,7 +6469,11 @@ function compileRuleMap(ruleMap, opts) {
6087
6469
  ` }`,
6088
6470
  `})()`
6089
6471
  ].join("\n");
6090
- return { keys: perEntry.map((e) => e.key), replacement };
6472
+ return {
6473
+ keys: perEntry.map((e) => e.key),
6474
+ replacement,
6475
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
6476
+ };
6091
6477
  }
6092
6478
  function compileLinkable(ruleMapArg, ns, opts) {
6093
6479
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
@@ -6120,8 +6506,13 @@ function compileLinkable(ruleMapArg, ns, opts) {
6120
6506
  triviaCaptureNames: /* @__PURE__ */ new Map(),
6121
6507
  triviaFnNames: /* @__PURE__ */ new Map(),
6122
6508
  namedFnDecls: [],
6123
- capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
6509
+ // A recognition-only fragment normally has no node collector of its own.
6510
+ // Leaf composition can re-lower it beneath a local semantic node, though;
6511
+ // in that case its terminals must feed the caller's collector rather than
6512
+ // merely returning their scalar parse values.
6513
+ capturing: opts?.captureTerminals === true || ruleMap.some(([, rule]) => hasNodeDef(rule)),
6124
6514
  recovery: opts?.recovery ?? false,
6515
+ ...opts?.coverage ? { coverage: { plan: opts.coverage } } : {},
6125
6516
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
6126
6517
  ns,
6127
6518
  deferFirstSetRefs: true,
@@ -6195,7 +6586,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
6195
6586
  ctx.indent = savedIndent;
6196
6587
  ctx.failLabel = savedFail;
6197
6588
  ctx.recordFail = savedRec;
6198
- pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
6589
+ const ruleId = ctx.coverage?.plan.rules.get(rule);
6590
+ const stmts = ruleId === void 0 ? body.stmts : [
6591
+ `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
6592
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'enter', offset: _pos })`,
6593
+ ...body.stmts,
6594
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'success', offset: _pos, end: ${body.endVar} })`
6595
+ ];
6596
+ pushNamedFnDecl(ctx, fn, stmts, body.valueVar, body.endVar);
6199
6597
  }
6200
6598
  perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
6201
6599
  }
@@ -6252,15 +6650,46 @@ function compileLinkable(ruleMapArg, ns, opts) {
6252
6650
  deps: ruleDependencies(ruleMap),
6253
6651
  needsEmptyTl: !!ctx.needsEmptyTl,
6254
6652
  needsHostReads: !!ctx.needsHostReads,
6653
+ hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
6654
+ isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
6255
6655
  mfFns: mfSrcs ? [] : ctx.mapFns,
6256
6656
  buildFns: buildSrcs ? [] : ctx.buildFns
6257
6657
  };
6258
6658
  }
6259
- function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
6260
- const bodyLines = [
6659
+ function hasSemanticReduction(roots) {
6660
+ const seen = /* @__PURE__ */ new Set();
6661
+ const visit = (parser2) => {
6662
+ if (seen.has(parser2)) return false;
6663
+ seen.add(parser2);
6664
+ const def = parser2._def;
6665
+ if (def.tag === "transform" && !def.recognitionOnly) return true;
6666
+ if (def.tag === "choice" && def.gates.some(Boolean)) return true;
6667
+ if (def.tag === "guard" || def.tag === "withCtx") return true;
6668
+ if (def.tag === "node" && def.build !== void 0) return true;
6669
+ if (def.tag === "lazy") {
6670
+ try {
6671
+ return visit(def.thunk());
6672
+ } catch {
6673
+ return true;
6674
+ }
6675
+ }
6676
+ return childrenOf(def).some(visit);
6677
+ };
6678
+ return roots.some(visit);
6679
+ }
6680
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootRuleId) {
6681
+ const bodyLines = coverageRootRuleId === void 0 ? [
6261
6682
  ` let pos = _pos`,
6262
6683
  ...r.stmts,
6263
6684
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6685
+ ] : [
6686
+ ` const _coverageResult = (() => {`,
6687
+ ` let pos = _pos`,
6688
+ ...r.stmts,
6689
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6690
+ ` })()`,
6691
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6692
+ ` return _coverageResult`
6264
6693
  ];
6265
6694
  const innerFn = [
6266
6695
  `function(input, _pos, _ctx) {`,
@@ -6315,8 +6744,8 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
6315
6744
  };
6316
6745
  const emit2 = (end) => {
6317
6746
  if (end > pos && cstCaptureActive(ctx)) {
6318
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
6319
- pushCstLeaf(ctx, leaf);
6747
+ const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
6748
+ pushCstLeaf(ctx, leaf2);
6320
6749
  }
6321
6750
  };
6322
6751
  while (cur < input.length) {
@@ -6370,6 +6799,7 @@ function balanced(open, close, options = {}) {
6370
6799
  );
6371
6800
  if (combi._def.tag === "transform") {
6372
6801
  combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
6802
+ combi._def.recognitionOnly = true;
6373
6803
  }
6374
6804
  self.define(combi);
6375
6805
  return combi;
@@ -6415,9 +6845,11 @@ function childrenOf2(def) {
6415
6845
  case "many":
6416
6846
  case "oneOrMore":
6417
6847
  case "optional":
6848
+ case "attempt":
6418
6849
  case "transform":
6419
6850
  case "trivia":
6420
6851
  case "token":
6852
+ case "leaf":
6421
6853
  case "label":
6422
6854
  case "field":
6423
6855
  case "not":
@@ -6466,7 +6898,7 @@ function serializeRuleMap(ruleMap) {
6466
6898
  }
6467
6899
  }
6468
6900
  function evalRuleMapIR(ir) {
6469
- const _tf = (child, src) => {
6901
+ const _tf = (child, src, recognitionOnly = false) => {
6470
6902
  let fn2;
6471
6903
  try {
6472
6904
  fn2 = (0, eval)(`(${src})`);
@@ -6477,8 +6909,22 @@ function evalRuleMapIR(ir) {
6477
6909
  }
6478
6910
  const t = transform(child, fn2);
6479
6911
  t._def.fnSrc = src;
6912
+ if (recognitionOnly) t._def.recognitionOnly = true;
6480
6913
  return t;
6481
6914
  };
6915
+ const _lf = (child, src) => {
6916
+ let fn2;
6917
+ try {
6918
+ fn2 = (0, eval)(`(${src})`);
6919
+ } catch {
6920
+ fn2 = () => {
6921
+ throw new Error("IR leaf fn not materialized");
6922
+ };
6923
+ }
6924
+ const l = leaf(child, fn2);
6925
+ l._def.fnSrc = src;
6926
+ return l;
6927
+ };
6482
6928
  const _nd = (type, child, src, opts, staticError) => {
6483
6929
  if (staticError !== void 0 && staticError.length > 0) {
6484
6930
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
@@ -6537,6 +6983,7 @@ function evalRuleMapIR(ir) {
6537
6983
  "keywords",
6538
6984
  "sequence",
6539
6985
  "choice",
6986
+ "attempt",
6540
6987
  "many",
6541
6988
  "oneOrMore",
6542
6989
  "optional",
@@ -6546,6 +6993,7 @@ function evalRuleMapIR(ir) {
6546
6993
  "parser",
6547
6994
  "scanTo",
6548
6995
  "token",
6996
+ "leaf",
6549
6997
  "transform",
6550
6998
  "skip",
6551
6999
  "trivia",
@@ -6553,6 +7001,7 @@ function evalRuleMapIR(ir) {
6553
7001
  "field",
6554
7002
  "expect",
6555
7003
  "_tf",
7004
+ "_lf",
6556
7005
  "_nd",
6557
7006
  "_gch",
6558
7007
  "_wc",
@@ -6566,6 +7015,7 @@ function evalRuleMapIR(ir) {
6566
7015
  keywords,
6567
7016
  sequence,
6568
7017
  choice,
7018
+ attempt,
6569
7019
  many,
6570
7020
  oneOrMore,
6571
7021
  optional,
@@ -6575,6 +7025,7 @@ function evalRuleMapIR(ir) {
6575
7025
  parser,
6576
7026
  scanTo,
6577
7027
  token,
7028
+ leaf,
6578
7029
  transform,
6579
7030
  skip,
6580
7031
  trivia,
@@ -6582,6 +7033,7 @@ function evalRuleMapIR(ir) {
6582
7033
  field,
6583
7034
  expect,
6584
7035
  _tf,
7036
+ _lf,
6585
7037
  _nd,
6586
7038
  _gch,
6587
7039
  _wc
@@ -6713,6 +7165,8 @@ ${body}
6713
7165
  return `oneOrMore(${kid(def.parser)})`;
6714
7166
  case "optional":
6715
7167
  return `optional(${kid(def.parser)})`;
7168
+ case "attempt":
7169
+ return `attempt(${kid(def.parser)})`;
6716
7170
  case "sepBy":
6717
7171
  return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
6718
7172
  case "not":
@@ -6721,6 +7175,10 @@ ${body}
6721
7175
  return `trivia(${kid(def.parser)})`;
6722
7176
  case "token":
6723
7177
  return `token(${kid(def.parser)})`;
7178
+ case "leaf": {
7179
+ if (def.fnSrc === void 0) throw new Unserializable("leaf without fnSrc");
7180
+ return `_lf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
7181
+ }
6724
7182
  case "label":
6725
7183
  return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
6726
7184
  case "field":
@@ -6733,7 +7191,7 @@ ${body}
6733
7191
  return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
6734
7192
  case "transform": {
6735
7193
  if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
6736
- return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
7194
+ return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
6737
7195
  }
6738
7196
  case "node": {
6739
7197
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
@@ -6779,15 +7237,15 @@ function normalizeCstCollapse(collapse) {
6779
7237
  }
6780
7238
  return typeof collapse === "function" ? collapse : void 0;
6781
7239
  }
6782
- function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
6783
- return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
7240
+ function buildCstNode(type, children2, _fields, span, _rawChildren, _triviaLog, state) {
7241
+ return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children2] };
6784
7242
  }
6785
- function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
7243
+ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triviaLog, state) {
6786
7244
  if (typeof typeOrOptions === "string") {
6787
- return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
7245
+ return buildCstNode(typeOrOptions, children2 ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
6788
7246
  }
6789
7247
  const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
6790
- const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
7248
+ const host = (type, children3, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children3, fields, span2, rawChildren2, triviaLog2, state2);
6791
7249
  host._parsemanCstOutput = true;
6792
7250
  if (collapse) host._parsemanCstCollapse = collapse;
6793
7251
  return host;
@@ -6870,9 +7328,12 @@ ${body}
6870
7328
  function isIRPiece(p) {
6871
7329
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
6872
7330
  }
6873
- function materializePiece(p, trivia2) {
7331
+ function materializePiece(p, trivia2, captureTerminals = false) {
6874
7332
  if (!isIRPiece(p)) return p;
6875
- const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, trivia2 ? { trivia: trivia2 } : void 0);
7333
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, {
7334
+ ...trivia2 ? { trivia: trivia2 } : {},
7335
+ ...captureTerminals ? { captureTerminals: true } : {}
7336
+ });
6876
7337
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
6877
7338
  return pieces;
6878
7339
  }
@@ -7101,6 +7562,7 @@ var SUPPORTED = {
7101
7562
  word: (...a) => word(a[0], a[1]),
7102
7563
  sequence: (...a) => sequence(...a),
7103
7564
  choice: (...a) => choice(...a),
7565
+ attempt: (...a) => attempt(a[0]),
7104
7566
  many: (...a) => many(a[0]),
7105
7567
  oneOrMore: (...a) => oneOrMore(a[0]),
7106
7568
  optional: (...a) => optional(a[0]),
@@ -7111,6 +7573,7 @@ var SUPPORTED = {
7111
7573
  field: (...a) => field(a[0], a[1]),
7112
7574
  noTrivia: (...a) => noTrivia(a[0]),
7113
7575
  token: (...a) => token(a[0]),
7576
+ leaf: (...a) => leaf(a[0], a[1]),
7114
7577
  expect: (...a) => expect(a[0], a[1])
7115
7578
  };
7116
7579
  function isScopeEntry(v2) {
@@ -7210,7 +7673,7 @@ function exprToCombi(node2, scope, code, mfs) {
7210
7673
  if (!parserArg || !fnArg || parserArg.type === "SpreadElement" || fnArg.type === "SpreadElement") return null;
7211
7674
  const inner = anyValue(parserArg, scope, code, mfs);
7212
7675
  if (!isCombinator(inner)) return null;
7213
- const fnSrc = code.slice(fnArg.start, fnArg.end);
7676
+ const fnSrc = stripTsFromSource(fnArg, code);
7214
7677
  mfs.push(fnSrc);
7215
7678
  try {
7216
7679
  const combi = transform(inner, (v2) => v2);
@@ -7220,6 +7683,21 @@ function exprToCombi(node2, scope, code, mfs) {
7220
7683
  return null;
7221
7684
  }
7222
7685
  }
7686
+ if (callee.name === "leaf" && code !== void 0 && mfs !== void 0) {
7687
+ const [parserArg, fnArg] = node2.arguments;
7688
+ if (!parserArg || !fnArg || parserArg.type === "SpreadElement" || fnArg.type === "SpreadElement") return null;
7689
+ const inner = anyValue(parserArg, scope, code, mfs);
7690
+ if (!isCombinator(inner)) return null;
7691
+ const fnSrc = stripTsFromSource(fnArg, code);
7692
+ mfs.push(fnSrc);
7693
+ try {
7694
+ const combi = leaf(inner, (v2) => v2);
7695
+ if (combi._def.tag === "leaf") combi._def.fnSrc = fnSrc;
7696
+ return combi;
7697
+ } catch {
7698
+ return null;
7699
+ }
7700
+ }
7223
7701
  if (callee.name === "node" && code !== void 0) {
7224
7702
  const [firstArg, secondArg, thirdArg, fourthArg] = node2.arguments;
7225
7703
  if (!firstArg || firstArg.type === "SpreadElement") return null;
@@ -7672,6 +8150,63 @@ function getCompiledResolver() {
7672
8150
  mainFields: ["module", "main"]
7673
8151
  });
7674
8152
  }
8153
+ var PRIVATE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
8154
+ function nearestPackageRoot(file) {
8155
+ let dir = path.dirname(file);
8156
+ for (; ; ) {
8157
+ try {
8158
+ if (fs.statSync(path.join(dir, "package.json")).isFile()) return fs.realpathSync(dir);
8159
+ } catch {
8160
+ }
8161
+ const parent = path.dirname(dir);
8162
+ if (parent === dir) return null;
8163
+ dir = parent;
8164
+ }
8165
+ }
8166
+ function isWithinPackageRoot(file, root) {
8167
+ const relative2 = path.relative(root, file);
8168
+ return relative2 !== ".." && !relative2.startsWith(`..${path.sep}`) && !path.isAbsolute(relative2);
8169
+ }
8170
+ function resolvePrivateSourceModule(from, specifier) {
8171
+ if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return null;
8172
+ const packageRoot = nearestPackageRoot(from);
8173
+ if (!packageRoot) return null;
8174
+ const base = path.resolve(path.dirname(from), specifier);
8175
+ const ext = path.extname(base);
8176
+ const candidates = [
8177
+ base,
8178
+ ...ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base.slice(0, -ext.length)}${next}`) : [],
8179
+ ...!ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base}${next}`) : [],
8180
+ ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path.join(base, next)) : []
8181
+ ];
8182
+ for (const candidate of candidates) {
8183
+ try {
8184
+ if (!fs.statSync(candidate).isFile()) continue;
8185
+ const real = fs.realpathSync(candidate);
8186
+ if (isWithinPackageRoot(real, packageRoot)) return real;
8187
+ } catch {
8188
+ }
8189
+ }
8190
+ return null;
8191
+ }
8192
+ function resolveGrammarModule(from, specifier) {
8193
+ if (PRIVATE_SOURCE_EXTENSIONS.has(path.extname(specifier))) {
8194
+ const file2 = resolvePrivateSourceModule(from, specifier);
8195
+ return file2 ? { file: file2, source: true } : null;
8196
+ }
8197
+ try {
8198
+ const file2 = getCompiledResolver().resolveFileSync(from, specifier).path;
8199
+ if (file2) return { file: file2, source: false };
8200
+ } catch {
8201
+ }
8202
+ const file = resolvePrivateSourceModule(from, specifier);
8203
+ return file ? { file, source: true } : null;
8204
+ }
8205
+ var _sourceLowering = /* @__PURE__ */ new Set();
8206
+ var _sourceLoweringCache = /* @__PURE__ */ new Map();
8207
+ function sourceLoweringCacheKey(file, moduleAliases, warnUnloweredRegex, recovery) {
8208
+ return `${file}\0${[...moduleAliases].sort().join("\0")}\0${warnUnloweredRegex ? "warn" : ""}\0${recovery ? "recovery" : ""}`;
8209
+ }
7675
8210
  var _moduleParseCache = /* @__PURE__ */ new Map();
7676
8211
  function parseModuleCached(filePath) {
7677
8212
  let mtimeMs;
@@ -7705,7 +8240,7 @@ var plugin_default = createUnplugin((opts = {}) => ({
7705
8240
  if (!code.includes("parseman")) return null;
7706
8241
  if (!code.includes("macro")) return null;
7707
8242
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
7708
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
8243
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true, opts.grammarCoverage === true);
7709
8244
  if (result?.warnings.length) {
7710
8245
  for (const w of result.warnings) {
7711
8246
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -7809,7 +8344,34 @@ function extractImportBindings(body) {
7809
8344
  }
7810
8345
  return out;
7811
8346
  }
7812
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
8347
+ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recovery) {
8348
+ if (_sourceLowering.has(file)) return null;
8349
+ let mtimeMs;
8350
+ try {
8351
+ mtimeMs = fs.statSync(file).mtimeMs;
8352
+ } catch {
8353
+ return null;
8354
+ }
8355
+ const cacheKey = sourceLoweringCacheKey(file, moduleAliases, warnUnloweredRegex, recovery);
8356
+ const hit = _sourceLoweringCache.get(cacheKey);
8357
+ if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
8358
+ _sourceLowering.add(file);
8359
+ let parsedModule = null;
8360
+ try {
8361
+ const source = fs.readFileSync(file, "utf8");
8362
+ const transformed = transformMacro(source, file, moduleAliases, warnUnloweredRegex, recovery);
8363
+ if (!transformed || transformed.warnings.length > 0) return null;
8364
+ const parsed = parseSync2(file, transformed.code);
8365
+ parsedModule = parsed.errors.length === 0 ? { body: parsed.program.body, src: transformed.code } : null;
8366
+ return parsedModule;
8367
+ } catch {
8368
+ return null;
8369
+ } finally {
8370
+ _sourceLoweringCache.set(cacheKey, { mtimeMs, parsed: parsedModule });
8371
+ _sourceLowering.delete(file);
8372
+ }
8373
+ }
8374
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false, grammarCoverage = false) {
7813
8375
  let result;
7814
8376
  try {
7815
8377
  result = parseSync2(id, code);
@@ -7861,7 +8423,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7861
8423
  anyUnresolved = true;
7862
8424
  warnings.push(`${id}:${lineOf(pos)} \u2014 ${msg} (running via the interpreter; add the plugin or simplify the declaration to compile it)`);
7863
8425
  };
7864
- const compileRulesFactory = (init, label2) => {
8426
+ const evaluateRulesFactory = (init, label2) => {
7865
8427
  const args = init.arguments;
7866
8428
  const arg0 = args[0];
7867
8429
  const arg1 = args[1];
@@ -7881,15 +8443,21 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7881
8443
  (p) => p.key?.name === "trivia"
7882
8444
  )?.value : void 0;
7883
8445
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
7884
- const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
8446
+ return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {} };
8447
+ };
8448
+ const compileRulesFactory = (init, label2) => {
8449
+ const evaluated = evaluateRulesFactory(init, label2);
8450
+ if (!evaluated) return null;
8451
+ const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, recovery, coverage: grammarCoverage });
7885
8452
  if (!compiled) {
7886
8453
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7887
8454
  return null;
7888
8455
  }
7889
- return { replacement: compiled.replacement, ruleMap, ...gTrivia ? { trivia: gTrivia } : {} };
8456
+ return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {} };
7890
8457
  };
7891
8458
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
7892
8459
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
8460
+ const isComposeLeafCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "composeLeaf";
7893
8461
  const isPickCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "pick";
7894
8462
  const localRuleMaps = /* @__PURE__ */ new Map();
7895
8463
  const localGrammarTrivia = /* @__PURE__ */ new Map();
@@ -7899,18 +8467,29 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7899
8467
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
7900
8468
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
7901
8469
  ).join(", ")}])`;
7902
- return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, mfFns: [], buildFns: [] }`;
8470
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
7903
8471
  };
7904
8472
  const isSpread = (it) => "__spreadLocal" in it;
7905
8473
  const isIR = (it) => "ir" in it && !("ruleFns" in it);
7906
8474
  const serializeItem = (it) => isSpread(it) ? `...(${it.__spreadLocal}[Symbol.for('parseman.composedPieces')] ?? [])` : isIR(it) ? `{ ns: ${JSON.stringify(it.ns)}, ir: ${JSON.stringify(it.ir)} }` : serializePieces(it);
7907
8475
  const serializeList = (list) => `[${list.map(serializeItem).join(", ")}]`;
7908
8476
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
8477
+ const withLeafMarker = (grammarExpr) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.leafComposed'), { value: true, enumerable: false })`;
8478
+ const withCoverageDefinitions = (grammarExpr, definitions) => !grammarCoverage ? grammarExpr : `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.grammarCoverageDefinitions'), { value: Object.freeze(${JSON.stringify(definitions)}.map(Object.freeze)), enumerable: false })`;
8479
+ const emittedCoverageDefinitions = (source) => {
8480
+ const ids = /* @__PURE__ */ new Set();
8481
+ for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
8482
+ return [...ids].sort().map((id2) => ({
8483
+ id: id2,
8484
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
8485
+ }));
8486
+ };
7909
8487
  const localComposedCarried = /* @__PURE__ */ new Map();
7910
8488
  const localComposedTrivia = /* @__PURE__ */ new Map();
7911
8489
  const importedPiecesCache = /* @__PURE__ */ new Map();
7912
- const resolveModulePieces = (file, exportName, seen) => {
7913
- const mod = parseModuleCached(file);
8490
+ const resolveModulePieces = (module, exportName, seen) => {
8491
+ const { file } = module;
8492
+ const mod = module.source ? lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recovery) : parseModuleCached(file);
7914
8493
  if (!mod) return null;
7915
8494
  const localFor = exportLocalName(mod.body, exportName);
7916
8495
  const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
@@ -7929,12 +8508,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7929
8508
  const b = imports.get(local);
7930
8509
  let subPieces = [];
7931
8510
  if (b) {
7932
- let subFile = null;
7933
- try {
7934
- subFile = getCompiledResolver().resolveFileSync(file, b.source).path ?? null;
7935
- } catch {
8511
+ const subModule = resolveGrammarModule(file, b.source);
8512
+ if (subModule && !seen.has(subModule.file)) {
8513
+ subPieces = resolveModulePieces(subModule, b.imported, new Set(seen).add(subModule.file)) ?? [];
7936
8514
  }
7937
- if (subFile && !seen.has(subFile)) subPieces = resolveModulePieces(subFile, b.imported, new Set(seen).add(subFile)) ?? [];
7938
8515
  }
7939
8516
  stubNames.push(local);
7940
8517
  stubVals.push({ [/* @__PURE__ */ Symbol.for("parseman.composedPieces")]: subPieces });
@@ -7951,29 +8528,56 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7951
8528
  let result2 = null;
7952
8529
  const binding = importBindings.get(localName);
7953
8530
  if (binding) {
7954
- let file = null;
7955
- try {
7956
- file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
7957
- } catch {
7958
- }
7959
- result2 = file ? resolveModulePieces(file, binding.imported, /* @__PURE__ */ new Set([file])) : null;
8531
+ const module = resolveGrammarModule(id, binding.source);
8532
+ result2 = module ? resolveModulePieces(module, binding.imported, /* @__PURE__ */ new Set([module.file])) : null;
7960
8533
  }
7961
8534
  importedPiecesCache.set(localName, result2);
7962
8535
  return result2;
7963
8536
  };
7964
- const materializeCarried = (items, composing) => {
8537
+ const materializeCarried = (items, composing, captureTerminals = false) => {
7965
8538
  const out = [];
7966
8539
  for (const it of items) {
7967
8540
  if (isSpread(it)) {
7968
- for (const p of importedPieces(it.__spreadLocal) ?? []) out.push(materializePiece(p, composing));
8541
+ for (const p of importedPieces(it.__spreadLocal) ?? []) out.push(materializePiece(p, composing, captureTerminals));
7969
8542
  } else if (isIR(it)) {
7970
- out.push(materializePiece(it, composing));
8543
+ out.push(materializePiece(it, composing, captureTerminals));
7971
8544
  } else {
7972
8545
  out.push(it);
7973
8546
  }
7974
8547
  }
7975
8548
  return out;
7976
8549
  };
8550
+ const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
8551
+ const sources = [];
8552
+ const add = (item) => {
8553
+ if (!isIR(item)) return false;
8554
+ sources.push({ ns: item.ns, rules: evalRuleMapIR(item.ir) });
8555
+ return true;
8556
+ };
8557
+ for (const item of items) {
8558
+ if (isSpread(item)) {
8559
+ const imported = importedPieces(item.__spreadLocal);
8560
+ if (!imported || !imported.every(add)) return null;
8561
+ } else if (!add(item)) return null;
8562
+ }
8563
+ sources.push({ ns: localNs, rules: [...localRules] });
8564
+ const winners = {};
8565
+ for (const source of sources) for (const [name, rule] of source.rules) winners[name] = rule;
8566
+ const plan = buildGrammarPlan(Object.values(winners), winners);
8567
+ const pieces = [];
8568
+ for (let index = 0; index < sources.length; index++) {
8569
+ const source = sources[index];
8570
+ const piece = compileLinkable(source.rules, source.ns, {
8571
+ ...composing ? { trivia: composing } : {},
8572
+ recovery,
8573
+ ...index < sources.length - 1 && captureTerminals ? { captureTerminals: true } : {},
8574
+ coverage: plan
8575
+ });
8576
+ if (!piece) return null;
8577
+ pieces.push(piece);
8578
+ }
8579
+ return pieces;
8580
+ };
7977
8581
  const localCarried = (rm, label2, composing) => {
7978
8582
  const entries = [...rm];
7979
8583
  const ns = nsFor(label2);
@@ -8089,6 +8693,64 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8089
8693
  return null;
8090
8694
  }
8091
8695
  };
8696
+ const compileComposeLeafCall = (init) => {
8697
+ const args = init.arguments;
8698
+ const arr = args[0];
8699
+ if (!arr || arr.type !== "ArrayExpression") {
8700
+ warn(init.start, "composeLeaf(): expected a static array of grammars");
8701
+ return null;
8702
+ }
8703
+ const elements = arr.elements;
8704
+ if (elements.length < 2) {
8705
+ warn(init.start, "composeLeaf(): needs imported recognition grammar(s) and one local rules() map");
8706
+ return null;
8707
+ }
8708
+ const localArg = elements[elements.length - 1];
8709
+ let localRules = null;
8710
+ if (isRulesCall(localArg)) {
8711
+ const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`);
8712
+ localRules = evaluated?.ruleMap ?? null;
8713
+ } else if (localArg.type === "Identifier") {
8714
+ localRules = localRuleMaps.get(localArg.name) ?? null;
8715
+ }
8716
+ if (!localRules) {
8717
+ warn(init.start, "composeLeaf(): final argument must be a local rules() map");
8718
+ return null;
8719
+ }
8720
+ const composing = composingTrivia(elements);
8721
+ const carried = [];
8722
+ for (let i = 0; i < elements.length - 1; i++) {
8723
+ const r = argPieces(elements[i], `composeLeaf${init.start}_${i}`, composing);
8724
+ if (!r) {
8725
+ warn(init.start, `composeLeaf(): argument ${i} isn't a build-resolvable recognition grammar`);
8726
+ return null;
8727
+ }
8728
+ carried.push(...r.carried);
8729
+ }
8730
+ try {
8731
+ const localNs = nsFor(`composeLeaf${init.start}`);
8732
+ const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, recovery });
8733
+ if (!plainLocalPiece) {
8734
+ warn(init.start, "composeLeaf(): local rules could not be statically compiled");
8735
+ return null;
8736
+ }
8737
+ const recognitionPieces = grammarCoverage ? materializeLeafCoverage(carried, localRules, localNs, composing, plainLocalPiece.hasDirectBuilders === true) : materializeCarried(carried, composing, plainLocalPiece.hasDirectBuilders === true);
8738
+ if (!recognitionPieces) {
8739
+ warn(init.start, "composeLeaf(): coverage needs re-lowerable recognition IR");
8740
+ return null;
8741
+ }
8742
+ const importedRecognitionPieces = grammarCoverage ? recognitionPieces.slice(0, -1) : recognitionPieces;
8743
+ if (importedRecognitionPieces.some((piece) => piece.hasDirectBuilders !== false || piece.isRecognitionOnly !== true)) {
8744
+ warn(init.start, "composeLeaf(): every pre-final grammar must explicitly prove recognition-only");
8745
+ return null;
8746
+ }
8747
+ const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece]));
8748
+ return { replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) };
8749
+ } catch (e) {
8750
+ warn(init.start, `composeLeaf(): ${e.message}`);
8751
+ return null;
8752
+ }
8753
+ };
8092
8754
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
8093
8755
  const refNames = /* @__PURE__ */ new Set();
8094
8756
  for (const stmt of body) {
@@ -8155,7 +8817,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8155
8817
  const refEntry = scope.get(varName);
8156
8818
  const refCombi = refEntry?.combi ?? null;
8157
8819
  if (refCombi) {
8158
- const compiled2 = compile(refCombi, void 0, { recovery });
8820
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
8159
8821
  if (compiled2.inlineExpression === null) {
8160
8822
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
8161
8823
  continue;
@@ -8169,7 +8831,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8169
8831
  if (!compiledRules) continue;
8170
8832
  localRuleMaps.set(varName, compiledRules.ruleMap);
8171
8833
  if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
8172
- let replacement = compiledRules.replacement;
8834
+ let replacement = withCoverageDefinitions(
8835
+ compiledRules.replacement,
8836
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
8837
+ );
8173
8838
  if (exportPrefix) {
8174
8839
  const ns = nsFor(varName);
8175
8840
  const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
@@ -8190,7 +8855,15 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8190
8855
  localComposedCarried.set(varName, fused.carried);
8191
8856
  if (fused.trivia) localComposedTrivia.set(varName, fused.trivia);
8192
8857
  const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
8193
- replacements.push({ start: init.start, end: init.end, replacement });
8858
+ replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) });
8859
+ continue;
8860
+ }
8861
+ if (isComposeLeafCall(init)) {
8862
+ const fused = compileComposeLeafCall(init);
8863
+ if (!fused) {
8864
+ throw new Error(`${id}:${lineOf(init.start)} \u2014 composeLeaf() must macro-fuse; runtime composition is forbidden`);
8865
+ }
8866
+ replacements.push({ start: init.start, end: init.end, replacement: fused.replacement });
8194
8867
  continue;
8195
8868
  }
8196
8869
  const mapFnSources = [];
@@ -8211,7 +8884,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8211
8884
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
8212
8885
  continue;
8213
8886
  }
8214
- const compiled = compile(parser2, void 0, { recovery });
8887
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
8215
8888
  if (compiled.inlineExpression === null) {
8216
8889
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
8217
8890
  continue;