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
@@ -35,6 +35,7 @@ __export(plugin_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(plugin_exports);
37
37
  var fs = __toESM(require("node:fs"), 1);
38
+ var path = __toESM(require("node:path"), 1);
38
39
  var import_unplugin = require("unplugin");
39
40
  var import_oxc_parser2 = require("oxc-parser");
40
41
  var import_oxc_resolver = require("oxc-resolver");
@@ -101,6 +102,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
101
102
  case "label":
102
103
  case "trivia":
103
104
  case "token":
105
+ case "leaf":
104
106
  case "expect":
105
107
  case "withCtx":
106
108
  case "node":
@@ -164,6 +166,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
164
166
  case "label":
165
167
  case "trivia":
166
168
  case "token":
169
+ case "leaf":
167
170
  case "node":
168
171
  case "grammar":
169
172
  case "expect":
@@ -269,10 +272,10 @@ function beginCstNodeCapture(ctx) {
269
272
  }
270
273
  function finishCstBuf(buf) {
271
274
  if (!buf) return { children: EMPTY, rawChildren: EMPTY, triviaLog: EMPTY_TL };
272
- const children = buf.ch ?? (buf.single !== void 0 ? [buf.single] : EMPTY);
275
+ const children2 = buf.ch ?? (buf.single !== void 0 ? [buf.single] : EMPTY);
273
276
  const rawChildren = buf.raw ?? (buf.rawSingle !== void 0 ? [buf.rawSingle] : EMPTY);
274
277
  const triviaLog = buf.tl ?? EMPTY_TL;
275
- return { children, rawChildren, triviaLog };
278
+ return { children: children2, rawChildren, triviaLog };
276
279
  }
277
280
  function endCstNodeCapture(ctx, saved) {
278
281
  const materialized = finishCstBuf(ctx._cstBuf);
@@ -284,8 +287,8 @@ function endCstNodeCapture(ctx, saved) {
284
287
  ctx.captureTrivia = saved.cap;
285
288
  return materialized;
286
289
  }
287
- function pushCstLeaf(ctx, leaf) {
288
- pushCstChild(ctx, leaf, leaf);
290
+ function pushCstLeaf(ctx, leaf2) {
291
+ pushCstChild(ctx, leaf2, leaf2);
289
292
  }
290
293
  function pushCstChild(ctx, built, rawEntry) {
291
294
  const b = ctx._cstBuf;
@@ -1015,10 +1018,13 @@ function deriveExpected(c) {
1015
1018
  return def.parsers.flatMap(deriveExpected);
1016
1019
  case "sequence":
1017
1020
  return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1021
+ case "attempt":
1022
+ return deriveExpected(def.parser);
1018
1023
  case "node":
1019
1024
  case "grammar":
1020
1025
  case "trivia":
1021
1026
  case "token":
1027
+ case "leaf":
1022
1028
  case "optional":
1023
1029
  case "many":
1024
1030
  case "oneOrMore":
@@ -1793,6 +1799,24 @@ function sequence(...parsers) {
1793
1799
  };
1794
1800
  }
1795
1801
 
1802
+ // src/combinators/attempt.ts
1803
+ function attempt(parser2) {
1804
+ return {
1805
+ _tag: "attempt",
1806
+ _meta: parser2._meta,
1807
+ _def: { tag: "attempt", parser: parser2 },
1808
+ parse(input, pos, ctx) {
1809
+ const mark = saveCstMark(ctx);
1810
+ const log = ctx._triviaLog?.length;
1811
+ const result = parser2.parse(input, pos, ctx);
1812
+ if (result.ok) return result;
1813
+ rollbackCstCapture(ctx, mark);
1814
+ if (log !== void 0 && ctx._triviaLog) ctx._triviaLog.length = log;
1815
+ return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1816
+ }
1817
+ };
1818
+ }
1819
+
1796
1820
  // src/combinators/repeat.ts
1797
1821
  function repItem(combinator, input, cur, ctx) {
1798
1822
  const mark = saveTriviaMark(ctx);
@@ -1968,19 +1992,19 @@ function sepBy(combinator, separator) {
1968
1992
  sepPos = advanceTrivia(input, cur, ctx);
1969
1993
  }
1970
1994
  }
1971
- const sep = separator.parse(input, sepPos, ctx);
1972
- if (!sep.ok) {
1995
+ const sep2 = separator.parse(input, sepPos, ctx);
1996
+ if (!sep2.ok) {
1973
1997
  rollbackTrivia(ctx, loopMark);
1974
1998
  break;
1975
1999
  }
1976
- let nextPos = sep.span.end;
2000
+ let nextPos = sep2.span.end;
1977
2001
  if (ctx.trivia) {
1978
2002
  if (needsDeferredTriviaCommit(ctx)) {
1979
- const scan = scanTrivia(input, sep.span.end, ctx);
2003
+ const scan = scanTrivia(input, sep2.span.end, ctx);
1980
2004
  scan.commit();
1981
2005
  nextPos = scan.end;
1982
2006
  } else {
1983
- nextPos = advanceTrivia(input, sep.span.end, ctx);
2007
+ nextPos = advanceTrivia(input, sep2.span.end, ctx);
1984
2008
  }
1985
2009
  }
1986
2010
  const next = combinator.parse(input, nextPos, ctx);
@@ -2065,10 +2089,14 @@ function markUnusedValues(root) {
2065
2089
  case "label":
2066
2090
  case "trivia":
2067
2091
  case "token":
2092
+ case "attempt":
2068
2093
  case "withCtx":
2069
2094
  case "expect":
2070
2095
  visit(def.parser, consumed);
2071
2096
  return;
2097
+ case "leaf":
2098
+ visit(def.parser, true);
2099
+ return;
2072
2100
  case "lazy":
2073
2101
  return;
2074
2102
  default:
@@ -2191,6 +2219,20 @@ function confirmedBuildArity(src) {
2191
2219
  if (/\barguments\b/.test(s)) return null;
2192
2220
  return parts.length;
2193
2221
  }
2222
+ function buildReadsChildren(def) {
2223
+ if (!def.build) return true;
2224
+ const src = def.buildSrc ?? def.build.toString();
2225
+ const arity = confirmedBuildArity(src);
2226
+ if (arity === null) return true;
2227
+ return arity >= 1;
2228
+ }
2229
+ function buildReadsRaw(def) {
2230
+ if (!def.build) return true;
2231
+ const src = def.buildSrc ?? def.build.toString();
2232
+ const arity = confirmedBuildArity(src);
2233
+ if (arity === null) return true;
2234
+ return arity >= 4;
2235
+ }
2194
2236
  function buildReadsTrivia(def) {
2195
2237
  if (!def.build) return true;
2196
2238
  const src = def.buildSrc ?? def.build.toString();
@@ -2246,9 +2288,11 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2246
2288
  case "many":
2247
2289
  case "oneOrMore":
2248
2290
  case "optional":
2291
+ case "attempt":
2249
2292
  case "transform":
2250
2293
  case "trivia":
2251
2294
  case "token":
2295
+ case "leaf":
2252
2296
  case "label":
2253
2297
  case "expect":
2254
2298
  case "withCtx":
@@ -2281,9 +2325,11 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2281
2325
  case "keywords":
2282
2326
  case "guard":
2283
2327
  case "token":
2328
+ case "leaf":
2284
2329
  return false;
2285
2330
  // Transparent single-child wrappers: recurse.
2286
2331
  case "optional":
2332
+ case "attempt":
2287
2333
  case "transform":
2288
2334
  case "trivia":
2289
2335
  case "label":
@@ -2330,6 +2376,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2330
2376
  case "many":
2331
2377
  case "oneOrMore":
2332
2378
  case "optional":
2379
+ case "attempt":
2333
2380
  case "transform":
2334
2381
  case "trivia":
2335
2382
  case "token":
@@ -2339,6 +2386,10 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2339
2386
  case "withCtx":
2340
2387
  case "not":
2341
2388
  return parserEnablesTriviaCapture(d.parser, seen);
2389
+ // leaf() clears the inner CST/trivia collector and publishes only its own
2390
+ // terminal result. An inner parser() capture cannot reach this node.
2391
+ case "leaf":
2392
+ return false;
2342
2393
  case "lazy": {
2343
2394
  try {
2344
2395
  return parserEnablesTriviaCapture(d.thunk(), seen);
@@ -2417,12 +2468,12 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2417
2468
  const fields = capturesFields ? buildFieldMap(ctx._fields) : void 0;
2418
2469
  ctx._fields = savedFields;
2419
2470
  ctx._triviaCaptureMask = savedMask;
2420
- const { children, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
2471
+ const { children: children2, rawChildren, triviaLog } = endCstNodeCapture(ctx, saved);
2421
2472
  if (!r.ok) return r;
2422
2473
  const st = clonesState && ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
2423
2474
  const nodeType = def.type ?? missingInferredType();
2424
2475
  const cstOutput = ctx.build?._parsemanCstOutput === true;
2425
- 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 };
2476
+ 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 };
2426
2477
  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 };
2427
2478
  if (saved.buf !== void 0 || saved.ch !== void 0) {
2428
2479
  pushCstChild(ctx, built, rawEntry);
@@ -2628,6 +2679,141 @@ function token(root) {
2628
2679
  }
2629
2680
  };
2630
2681
  }
2682
+ function leaf(root, fn) {
2683
+ const meta = {
2684
+ firstSet: root._meta.firstSet,
2685
+ canMatchNewline: root._meta.canMatchNewline,
2686
+ isTrivia: false
2687
+ };
2688
+ return {
2689
+ _tag: "leaf",
2690
+ _meta: meta,
2691
+ _def: { tag: "leaf", parser: root, fn },
2692
+ parse(input, pos, ctx) {
2693
+ const savedBuf = ctx._cstBuf;
2694
+ const savedChildren = ctx._cstChildren;
2695
+ const savedLeaves = ctx._cstLeaves;
2696
+ const savedRaw = ctx._cstRawChildren;
2697
+ const savedTriviaLog = ctx._cstTriviaLog;
2698
+ const savedOuterTriviaLog = ctx._triviaLog;
2699
+ const wasCapturing = cstCaptureActive(ctx);
2700
+ ctx._cstBuf = void 0;
2701
+ ctx._cstChildren = void 0;
2702
+ ctx._cstLeaves = void 0;
2703
+ ctx._cstRawChildren = void 0;
2704
+ ctx._cstTriviaLog = void 0;
2705
+ delete ctx._triviaLog;
2706
+ let result;
2707
+ try {
2708
+ result = root.parse(input, pos, ctx);
2709
+ } finally {
2710
+ ctx._cstBuf = savedBuf;
2711
+ ctx._cstChildren = savedChildren;
2712
+ ctx._cstLeaves = savedLeaves;
2713
+ ctx._cstRawChildren = savedRaw;
2714
+ ctx._cstTriviaLog = savedTriviaLog;
2715
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
2716
+ else ctx._triviaLog = savedOuterTriviaLog;
2717
+ }
2718
+ if (!result.ok) return result;
2719
+ const value = fn(result.value, result.span);
2720
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span: result.span });
2721
+ return { ok: true, value, span: result.span };
2722
+ }
2723
+ };
2724
+ }
2725
+
2726
+ // src/compiler/grammar-coverage-ids.ts
2727
+ function children(def, winners) {
2728
+ switch (def.tag) {
2729
+ case "sequence":
2730
+ case "choice":
2731
+ return def.parsers;
2732
+ case "many":
2733
+ case "oneOrMore":
2734
+ case "optional":
2735
+ case "attempt":
2736
+ case "transform":
2737
+ case "trivia":
2738
+ case "token":
2739
+ case "leaf":
2740
+ case "label":
2741
+ case "field":
2742
+ case "grammar":
2743
+ case "not":
2744
+ case "node":
2745
+ case "guard":
2746
+ case "withCtx":
2747
+ case "recover":
2748
+ case "expect":
2749
+ return "parser" in def ? [def.parser] : [];
2750
+ case "sepBy":
2751
+ return [def.parser, def.separator];
2752
+ case "skip":
2753
+ return [def.main, def.skipped];
2754
+ case "scanTo":
2755
+ return [def.sentinel, ...def.skip];
2756
+ case "lazy": {
2757
+ let resolved;
2758
+ try {
2759
+ resolved = def.thunk();
2760
+ } catch {
2761
+ return [];
2762
+ }
2763
+ const name = resolved._ruleName;
2764
+ return name && winners?.[name] ? [winners[name]] : [resolved];
2765
+ }
2766
+ default:
2767
+ return [];
2768
+ }
2769
+ }
2770
+ function buildGrammarPlan(entry, winners) {
2771
+ const definitions = /* @__PURE__ */ new Map();
2772
+ const choices = /* @__PURE__ */ new WeakMap();
2773
+ const attempts = /* @__PURE__ */ new WeakMap();
2774
+ const labels = /* @__PURE__ */ new WeakMap();
2775
+ const rules2 = /* @__PURE__ */ new WeakMap();
2776
+ const winnerNames = /* @__PURE__ */ new Map();
2777
+ if (winners) {
2778
+ for (const [name, winner] of Object.entries(winners)) {
2779
+ winnerNames.set(winner, name);
2780
+ if (winner._def.tag === "lazy") {
2781
+ try {
2782
+ winnerNames.set(winner._def.thunk(), name);
2783
+ } catch {
2784
+ }
2785
+ }
2786
+ }
2787
+ }
2788
+ const seen = /* @__PURE__ */ new Set();
2789
+ const visit = (parser2, path2) => {
2790
+ if (seen.has(parser2)) return;
2791
+ seen.add(parser2);
2792
+ const rule = winnerNames.get(parser2) ?? parser2._ruleName;
2793
+ if (rule && (winnerNames.has(parser2) || parser2._def.tag !== "lazy")) {
2794
+ const id = `rule:${rule}`;
2795
+ definitions.set(id, { id, kind: "rule" });
2796
+ rules2.set(parser2, id);
2797
+ }
2798
+ if (parser2._def.tag === "choice") {
2799
+ const ids = parser2._def.parsers.map((_, index) => `choice:${path2}/arm:${index}`);
2800
+ choices.set(parser2, ids);
2801
+ ids.forEach((id) => definitions.set(id, { id, kind: "choice-arm" }));
2802
+ }
2803
+ if (parser2._def.tag === "attempt") attempts.set(parser2, `attempt:${path2}`);
2804
+ if (parser2._def.tag === "label") {
2805
+ const id = `label:${path2}`;
2806
+ labels.set(parser2, [id]);
2807
+ definitions.set(id, { id, kind: "label" });
2808
+ }
2809
+ children(parser2._def, winners).forEach((child, index) => visit(child, `${path2}/${parser2._def.tag}:${index}`));
2810
+ };
2811
+ const roots = Array.isArray(entry) ? entry : [entry];
2812
+ for (const root of roots) {
2813
+ visit(root, winnerNames.get(root) ?? root._ruleName ?? "entry");
2814
+ }
2815
+ return { definitions: [...definitions.values()].sort((a, b) => a.id.localeCompare(b.id)), choices, attempts, labels, rules: rules2 };
2816
+ }
2631
2817
 
2632
2818
  // src/compiler/scannable-run.ts
2633
2819
  var BACKSLASH = 92;
@@ -4020,21 +4206,23 @@ function probeUpdate(ctx, expectedArr, posExpr, constant = true) {
4020
4206
  }
4021
4207
  function failBody(ctx, expected, posExpr) {
4022
4208
  const probe = probeUpdate(ctx, `[${expected}]`, posExpr);
4209
+ const trace = ctx.activeCoverageRuleId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(ctx.activeCoverageRuleId)}, phase: 'failure', offset: ${posExpr} }); `;
4023
4210
  if (ctx.failLabel) {
4024
- if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4025
- return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; break ${ctx.failLabel} }`;
4211
+ if (!ctx.recordFail) return probe ? `{ ${probe}${trace}break ${ctx.failLabel} }` : trace ? `{ ${trace}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4212
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${hoistExpected(ctx, `[${expected}]`)}; ${trace}break ${ctx.failLabel} }`;
4026
4213
  }
4027
- return probe + failReturn(expected, posExpr);
4214
+ return probe + trace + failReturn(expected, posExpr);
4028
4215
  }
4029
4216
  function failArrBody(ctx, expectedArr, posExpr, constant = true) {
4030
4217
  const probe = probeUpdate(ctx, expectedArr, posExpr, constant);
4218
+ const trace = ctx.activeCoverageRuleId === void 0 ? "" : `_ctx._grammarTrace?.write({ id: ${JSON.stringify(ctx.activeCoverageRuleId)}, phase: 'failure', offset: ${posExpr} }); `;
4031
4219
  if (ctx.failLabel) {
4032
- if (!ctx.recordFail) return probe ? `{ ${probe}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4220
+ if (!ctx.recordFail) return probe ? `{ ${probe}${trace}break ${ctx.failLabel} }` : trace ? `{ ${trace}break ${ctx.failLabel} }` : `break ${ctx.failLabel}`;
4033
4221
  const fx = constant ? hoistExpected(ctx, expectedArr) : expectedArr;
4034
- return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; break ${ctx.failLabel} }`;
4222
+ return `{ ${probe}_ctx._fe = ${posExpr}; _ctx._fx = ${fx}; ${trace}break ${ctx.failLabel} }`;
4035
4223
  }
4036
- if (!constant) return `${probe}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
4037
- return probe + failReturnArr(expectedArr, posExpr);
4224
+ if (!constant) return `${probe}${trace}return { ok: false, expected: [...${expectedArr}], span: { start: ${posExpr}, end: ${posExpr} } }`;
4225
+ return probe + trace + failReturnArr(expectedArr, posExpr);
4038
4226
  }
4039
4227
  function resultFromRecorded(feExpr = "_ctx._fe", fxExpr = "_ctx._fx") {
4040
4228
  return `return { ok: false, expected: [...${fxExpr}], span: { start: ${feExpr}, end: ${feExpr} } }`;
@@ -4069,7 +4257,7 @@ var NAMED_FN_END = "_pfEnd";
4069
4257
  function namedFnPrelude() {
4070
4258
  return [`const ${NAMED_FN_FAIL} = {}`, `let ${NAMED_FN_END}`];
4071
4259
  }
4072
- function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
4260
+ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar, failureRuleId) {
4073
4261
  ctx.namedFnDecls.push([
4074
4262
  `function ${fnName}(input, _pos, _ctx) {`,
4075
4263
  ` _pfail: {`,
@@ -4077,6 +4265,7 @@ function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
4077
4265
  ` ${NAMED_FN_END} = ${endVar}`,
4078
4266
  ` return ${valueVar}`,
4079
4267
  ` }`,
4268
+ ...failureRuleId === void 0 ? [] : [` _ctx._grammarTrace?.write({ id: ${JSON.stringify(failureRuleId)}, phase: 'failure', offset: _ctx._fe ?? _pos })`],
4080
4269
  ` return ${NAMED_FN_FAIL}`,
4081
4270
  `}`
4082
4271
  ].join("\n"));
@@ -4238,6 +4427,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4238
4427
  case "not":
4239
4428
  case "trivia":
4240
4429
  case "token":
4430
+ case "leaf":
4241
4431
  case "scanTo":
4242
4432
  case "unknown":
4243
4433
  return false;
@@ -4249,6 +4439,8 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4249
4439
  // nothing committed remains.
4250
4440
  case "choice":
4251
4441
  return false;
4442
+ case "attempt":
4443
+ return false;
4252
4444
  // optional never fails; many/oneOrMore only "fail" with zero captured items.
4253
4445
  case "optional":
4254
4446
  case "many":
@@ -4301,6 +4493,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
4301
4493
  case "keywords":
4302
4494
  case "node":
4303
4495
  case "token":
4496
+ case "leaf":
4304
4497
  return true;
4305
4498
  case "not":
4306
4499
  case "guard":
@@ -4315,6 +4508,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
4315
4508
  case "many":
4316
4509
  case "oneOrMore":
4317
4510
  case "optional":
4511
+ case "attempt":
4318
4512
  case "transform":
4319
4513
  case "label":
4320
4514
  case "field":
@@ -4656,35 +4850,55 @@ function needsFirstSetGuard(p) {
4656
4850
  const fs2 = p._meta.firstSet;
4657
4851
  return fs2.kind !== "any" && !canMatchEmptyAtStart(p);
4658
4852
  }
4659
- function emitDisjointArm(p, ctx, pos, valV, endV) {
4660
- if (failsAtStart(p)) {
4853
+ function coverageHit(ctx, id, offset = "_pos", end) {
4854
+ return id === void 0 ? [] : [
4855
+ `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(id)})`,
4856
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'selected', offset: ${offset}${end === void 0 ? "" : `, end: ${end}`} })`,
4857
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'success', offset: ${offset}${end === void 0 ? "" : `, end: ${end}`} })`
4858
+ ];
4859
+ }
4860
+ function coverageAttempt(ctx, id, offset) {
4861
+ return id === void 0 ? [] : [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'attempt', offset: ${offset} })`];
4862
+ }
4863
+ function coverageFailureBacktrack(ctx, id, failureOffset, startOffset) {
4864
+ return id === void 0 ? [] : [
4865
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'failure', offset: ${failureOffset} })`,
4866
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'backtrack', offset: ${startOffset} })`
4867
+ ];
4868
+ }
4869
+ function emitDisjointArm(p, ctx, pos, valV, endV, coverageId) {
4870
+ if (failsAtStart(p) && coverageId === void 0) {
4661
4871
  const r = emit(p, ctx, pos);
4662
- return [...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`];
4872
+ return [...coverageAttempt(ctx, coverageId, pos), ...r.stmts, `${ind(ctx)}${valV} = ${r.valueVar}`, `${ind(ctx)}${endV} = ${r.endVar}`, ...coverageHit(ctx, coverageId, pos, r.endVar)];
4663
4873
  }
4664
4874
  const { stmts, okVar, valVar, endVar } = emitFallible(p, ctx, pos);
4665
4875
  return [
4876
+ ...coverageAttempt(ctx, coverageId, pos),
4666
4877
  ...stmts,
4667
- ...emitIfFail(ctx, `!${okVar}`, failArrBody(ctx, "_ctx._fx", pos, false)),
4878
+ ...emitIfFail(ctx, `!${okVar}`, `${coverageFailureBacktrack(ctx, coverageId, "_ctx._fe", pos).join("; ")}; ${failArrBody(ctx, "_ctx._fx", pos, false)}`),
4668
4879
  `${ind(ctx)}${valV} = ${valVar}`,
4669
- `${ind(ctx)}${endV} = ${endVar}`
4880
+ `${ind(ctx)}${endV} = ${endVar}`,
4881
+ ...coverageHit(ctx, coverageId, pos, endVar)
4670
4882
  ];
4671
4883
  }
4672
- function emitDisjointArmGated(def, i, ctx, pos, valV, endV) {
4884
+ function emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageId) {
4673
4885
  const p = def.parsers[i];
4674
4886
  const gate = def.gates[i];
4675
- if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV);
4887
+ if (!gate) return emitDisjointArm(p, ctx, pos, valV, endV, coverageId);
4676
4888
  const gateIdx = ctx.mapFns.length;
4677
4889
  ctx.mapFns.push(gate);
4678
4890
  ctx.mapFnSrcs.push(def.gateSrcs?.[i] ?? null);
4679
4891
  const gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
4680
4892
  const stmts = [`${ind(ctx)}if (${gateCond}) {`];
4681
4893
  ctx.indent++;
4682
- stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV));
4894
+ stmts.push(...emitDisjointArm(p, ctx, pos, valV, endV, coverageId));
4683
4895
  ctx.indent--;
4684
4896
  stmts.push(`${ind(ctx)}} else { ${failArrBody(ctx, deriveExpectedArr([p]), pos)} }`);
4685
4897
  return stmts;
4686
4898
  }
4687
- function emitChoice(def, ctx, pos) {
4899
+ function emitChoice(parser2, def, ctx, pos) {
4900
+ const coverageIds = ctx.coverage?.plan.choices.get(parser2);
4901
+ const coverageBase = coverageIds?.[0]?.slice(0, -1);
4688
4902
  const allExpected = deriveExpectedArr(def.parsers);
4689
4903
  if (def.disjoint) {
4690
4904
  const codeV = v(ctx, "_code");
@@ -4703,7 +4917,7 @@ function emitChoice(def, ctx, pos) {
4703
4917
  stmts.push(`${ind(ctx)}{`);
4704
4918
  ctx.indent++;
4705
4919
  stmts.push(
4706
- ...emitDisjointArmGated(def, i, ctx, pos, valV, endV),
4920
+ ...emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageIds?.[i]),
4707
4921
  `${ind(ctx)}break`
4708
4922
  );
4709
4923
  ctx.indent--;
@@ -4722,16 +4936,16 @@ function emitChoice(def, ctx, pos) {
4722
4936
  first = false;
4723
4937
  stmts.push(`${ind(ctx)}${kw} (${cond}) {`);
4724
4938
  ctx.indent++;
4725
- stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV));
4939
+ stmts.push(...emitDisjointArmGated(def, i, ctx, pos, valV, endV, coverageIds?.[i]));
4726
4940
  ctx.indent--;
4727
4941
  stmts.push(`${ind(ctx)}}`);
4728
4942
  }
4729
4943
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
4730
4944
  return { stmts, valueVar: valV, endVar: endV };
4731
4945
  }
4732
- return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos);
4946
+ return emitNonDisjoint(def, def.strategy, allExpected, ctx, pos, coverageBase);
4733
4947
  }
4734
- function emitGreedyClassify(def, superIndex, ctx, pos) {
4948
+ function emitGreedyClassify(def, superIndex, ctx, pos, coverageBase) {
4735
4949
  const superParser = def.parsers[superIndex];
4736
4950
  const regexDef = getCoreRegexDef(superParser);
4737
4951
  const cleanFlags = "y" + regexDef.flags.replace(/[gy]/g, "");
@@ -4747,10 +4961,12 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4747
4961
  const endV = v(ctx, "_ge");
4748
4962
  const valV = v(ctx, "_gcv");
4749
4963
  const regexExpected = JSON.stringify(deriveExpected(superParser));
4964
+ const superCoverageId = coverageBase === void 0 ? void 0 : `${coverageBase}${superIndex}`;
4750
4965
  const stmts = [
4966
+ ...coverageAttempt(ctx, superCoverageId, pos),
4751
4967
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
4752
4968
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
4753
- ...emitIfFail(ctx, `${matchV} === null`, failArrBody(ctx, regexExpected, pos)),
4969
+ ...emitIfFail(ctx, `${matchV} === null`, `${coverageFailureBacktrack(ctx, superCoverageId, pos, pos).join("; ")}; ${failArrBody(ctx, regexExpected, pos)}`),
4754
4970
  `${ind(ctx)}const ${wordV} = ${matchV}[0]`,
4755
4971
  `${ind(ctx)}const ${endV} = ${pos} + ${wordV}.length`,
4756
4972
  `${ind(ctx)}let ${valV}`
@@ -4766,8 +4982,9 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4766
4982
  stmts.push(`${ind(ctx)}${kw} (${wordV} === ${JSON.stringify(litVal)}) {`);
4767
4983
  ctx.indent++;
4768
4984
  const tR = emitTransformChain(p, JSON.stringify(litVal), endV, pos, ctx);
4985
+ stmts.push(...coverageFailureBacktrack(ctx, superCoverageId, pos, pos));
4769
4986
  stmts.push(...emitLeafCapture(ctx, JSON.stringify(litVal), pos, endV));
4770
- stmts.push(...tR.stmts, `${ind(ctx)}${valV} = ${tR.valueVar}`);
4987
+ stmts.push(...tR.stmts, `${ind(ctx)}${valV} = ${tR.valueVar}`, ...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${i}`, pos, endV));
4771
4988
  ctx.indent--;
4772
4989
  stmts.push(`${ind(ctx)}}`);
4773
4990
  }
@@ -4775,12 +4992,12 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
4775
4992
  const regexKw = first ? "if" : "else";
4776
4993
  stmts.push(`${ind(ctx)}${regexKw} {`);
4777
4994
  ctx.indent++;
4778
- stmts.push(...emitLeafCapture(ctx, wordV, pos, endV), ...rR.stmts, `${ind(ctx)}${valV} = ${rR.valueVar}`);
4995
+ 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));
4779
4996
  ctx.indent--;
4780
4997
  stmts.push(`${ind(ctx)}}`);
4781
4998
  return { stmts, valueVar: valV, endVar: endV };
4782
4999
  }
4783
- function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
5000
+ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos, coverageBase) {
4784
5001
  const valV = v(ctx, "_llv");
4785
5002
  const endV = v(ctx, "_lle");
4786
5003
  const stmts = [`${ind(ctx)}let ${valV}, ${endV} = ${pos}`];
@@ -4797,10 +5014,12 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
4797
5014
  ctx.indent++;
4798
5015
  const tR = emitTransformChain(p, JSON.stringify(litVal), litEnd, pos, ctx);
4799
5016
  stmts.push(
5017
+ ...coverageAttempt(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${idx}`, pos),
4800
5018
  ...emitLeafCapture(ctx, JSON.stringify(litVal), pos, litEnd),
4801
5019
  ...tR.stmts,
4802
5020
  `${ind(ctx)}${valV} = ${tR.valueVar}`,
4803
- `${ind(ctx)}${endV} = ${litEnd}`
5021
+ `${ind(ctx)}${endV} = ${litEnd}`,
5022
+ ...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${idx}`, pos, litEnd)
4804
5023
  );
4805
5024
  ctx.indent--;
4806
5025
  stmts.push(`${ind(ctx)}}`);
@@ -4808,16 +5027,18 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos) {
4808
5027
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
4809
5028
  return { stmts, valueVar: valV, endVar: endV };
4810
5029
  }
4811
- function emitFirstMatch(def, ctx, pos) {
5030
+ function emitFirstMatch(def, ctx, pos, coverageBase) {
4812
5031
  const resValV = v(ctx, "_crv");
4813
5032
  const resEndV = v(ctx, "_cre");
4814
5033
  const resOkV = v(ctx, "_crok");
4815
5034
  const codeV = v(ctx, "_chcode");
4816
5035
  const slots = def.parsers.map(() => v(ctx, "_cfx"));
5036
+ const autoRejectedV = coverageBase === void 0 ? void 0 : v(ctx, "_carej");
4817
5037
  const ind0 = ind(ctx);
4818
5038
  const stmts = [
4819
5039
  `${ind0}let ${resValV}, ${resEndV} = ${pos}, ${resOkV} = false`,
4820
5040
  `${ind0}let ${slots.join(", ")}`,
5041
+ ...autoRejectedV === void 0 ? [] : [`${ind0}const ${autoRejectedV} = []`],
4821
5042
  `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
4822
5043
  ];
4823
5044
  for (let i = 0; i < def.parsers.length; i++) {
@@ -4853,6 +5074,7 @@ function emitFirstMatch(def, ctx, pos) {
4853
5074
  stmts.push(`${ind0}if (${skipCond}) {`);
4854
5075
  if (fsGuard) stmts.push(`${ind(ctx)}if (${fsGuard}) {`);
4855
5076
  ctx.indent += fsGuard ? 2 : 1;
5077
+ if (coverageBase !== void 0) stmts.push(`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(`${coverageBase}${i}`)}, phase: 'attempt', offset: ${pos} })`);
4856
5078
  if (markLeaves) {
4857
5079
  stmts.push(
4858
5080
  `${ind(ctx)}const ${markLeaves} = _ctx._cstLeaves?.length ?? 0`,
@@ -4875,13 +5097,21 @@ function emitFirstMatch(def, ctx, pos) {
4875
5097
  stmts.push(`${ind(ctx)} ${resValV} = ${valVar}`);
4876
5098
  stmts.push(`${ind(ctx)} ${resEndV} = ${endVar}`);
4877
5099
  stmts.push(`${ind(ctx)} ${resOkV} = true`);
5100
+ if (autoRejectedV !== void 0) {
5101
+ 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} }) }`);
5102
+ }
5103
+ stmts.push(...coverageHit(ctx, coverageBase === void 0 ? void 0 : `${coverageBase}${i}`, pos, endVar));
4878
5104
  stmts.push(`${ind(ctx)} }`);
5105
+ if (autoRejectedV !== void 0) {
5106
+ stmts.push(`${ind(ctx)} else { ${autoRejectedV}.push(${JSON.stringify(`${coverageBase}${i}`)}) }`);
5107
+ }
4879
5108
  stmts.push(`${ind(ctx)}}`);
4880
- stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot} }`);
5109
+ 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} });`} }`);
4881
5110
  if (rollback) stmts.push(`${ind(ctx)}if (!${resOkV}) { ${rollback} }`);
4882
5111
  } else {
4883
- stmts.push(`${ind(ctx)}if (${okVar}) { ${resValV} = ${valVar}; ${resEndV} = ${endVar}; ${resOkV} = true }`);
4884
- stmts.push(`${ind(ctx)}else { ${slots[i]} = ${failSlot}${rollback ? `; ${rollback}` : ""} }`);
5112
+ 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} }) }`;
5113
+ 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} })`} }`);
5114
+ 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} })`} }`);
4885
5115
  }
4886
5116
  ctx.indent -= fsGuard ? 2 : 1;
4887
5117
  if (fsGuard) stmts.push(`${ind(ctx)}} else { ${slots[i]} = ${staticFx} }`);
@@ -4891,12 +5121,12 @@ function emitFirstMatch(def, ctx, pos) {
4891
5121
  stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
4892
5122
  return { stmts, valueVar: resValV, endVar: resEndV };
4893
5123
  }
4894
- function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
5124
+ function emitNonDisjoint(def, strategy, allExpected, ctx, pos, coverageBase) {
4895
5125
  if (strategy.tag === "greedyClassify")
4896
- return emitGreedyClassify(def, strategy.superIndex, ctx, pos);
5126
+ return emitGreedyClassify(def, strategy.superIndex, ctx, pos, coverageBase);
4897
5127
  if (strategy.tag === "literalsLongestFirst")
4898
- return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
4899
- return emitFirstMatch(def, ctx, pos);
5128
+ return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos, coverageBase);
5129
+ return emitFirstMatch(def, ctx, pos, coverageBase);
4900
5130
  }
4901
5131
  function pushMapFn(ctx, fn, src) {
4902
5132
  if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
@@ -5014,6 +5244,27 @@ function emitOptional(def, ctx, pos) {
5014
5244
  ];
5015
5245
  return { stmts, valueVar: valV, endVar: endV };
5016
5246
  }
5247
+ function emitAttempt(p, def, ctx, pos) {
5248
+ const inner = emitFallible(def.parser, ctx, pos);
5249
+ const leaves = v(ctx, "_atl");
5250
+ const raw = v(ctx, "_atr");
5251
+ const trivia2 = v(ctx, "_att");
5252
+ const log = v(ctx, "_atg");
5253
+ const fields = v(ctx, "_atf");
5254
+ const errors = v(ctx, "_ate");
5255
+ 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}`;
5256
+ const traceId = ctx.coverage?.plan.attempts.get(p);
5257
+ const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5258
+ return {
5259
+ stmts: [
5260
+ `${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`,
5261
+ ...inner.stmts,
5262
+ ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
5263
+ ],
5264
+ valueVar: inner.valVar,
5265
+ endVar: inner.endVar
5266
+ };
5267
+ }
5017
5268
  function emitSepBy(_p, def, ctx, pos) {
5018
5269
  const arrV = v(ctx, "_arr");
5019
5270
  const curV = v(ctx, "_cur");
@@ -5259,11 +5510,11 @@ function tokenNullableRegex(def) {
5259
5510
  }
5260
5511
  if (inner.tag === "sepBy") {
5261
5512
  const item = tokenSequenceSource(inner.parser);
5262
- const sep = tokenSequenceSource(inner.separator);
5263
- if (!item || !sep) return null;
5264
- const combined = combineTokenRegexSources([item, sep]);
5513
+ const sep2 = tokenSequenceSource(inner.separator);
5514
+ if (!item || !sep2) return null;
5515
+ const combined = combineTokenRegexSources([item, sep2]);
5265
5516
  if (!combined) return null;
5266
- return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
5517
+ return { tag: "regex", source: `(?:${item.source}(?:${sep2.source}${item.source})*)?`, flags: combined.flags };
5267
5518
  }
5268
5519
  return null;
5269
5520
  }
@@ -5304,6 +5555,36 @@ function emitToken(def, ctx, pos) {
5304
5555
  endVar: inner.endVar
5305
5556
  };
5306
5557
  }
5558
+ function emitLeaf(def, ctx, pos) {
5559
+ const inner = emitFallible(def.parser, ctx, pos);
5560
+ const i = ind(ctx);
5561
+ const sc = v(ctx, "_leafCh");
5562
+ const sl = v(ctx, "_leafLv");
5563
+ const sr = v(ctx, "_leafRaw");
5564
+ const stl = v(ctx, "_leafTl");
5565
+ const sol = v(ctx, "_leafLog");
5566
+ const sb = v(ctx, "_leafBuf");
5567
+ const rawV = v(ctx, "_leafRawValue");
5568
+ const valV = v(ctx, "_leaf");
5569
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
5570
+ return {
5571
+ stmts: [
5572
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${stl} = _ctx._cstTriviaLog, ${sol} = _ctx._triviaLog, ${sb} = _ctx._cstBuf`,
5573
+ `${i}_ctx._cstChildren = undefined; _ctx._cstLeaves = undefined; _ctx._cstRawChildren = undefined; _ctx._cstTriviaLog = undefined; _ctx._triviaLog = undefined; _ctx._cstBuf = undefined`,
5574
+ `${i}try {`,
5575
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
5576
+ `${i}} finally {`,
5577
+ `${i} _ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx._cstTriviaLog = ${stl}; _ctx._triviaLog = ${sol}; _ctx._cstBuf = ${sb}`,
5578
+ `${i}}`,
5579
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
5580
+ `${i}const ${rawV} = ${inner.valVar}`,
5581
+ `${i}const ${valV} = ${mfRef(ctx)}[${fnIdx}](${rawV}, { start: ${pos}, end: ${inner.endVar} })`,
5582
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
5583
+ ],
5584
+ valueVar: valV,
5585
+ endVar: inner.endVar
5586
+ };
5587
+ }
5307
5588
  function emitNode(def, ctx, pos) {
5308
5589
  if (def.type === void 0) {
5309
5590
  throw new Error('node(): inferred node type requires a rules() key; pass node("Type", parser) outside rules()');
@@ -5319,6 +5600,8 @@ function emitNode(def, ctx, pos) {
5319
5600
  const i = ind(ctx);
5320
5601
  const capturesTrivia = mkType !== null || def.captureTrivia === true || def.trailingTrivia === true || !structural && buildReadsTrivia(def);
5321
5602
  const clonesState = !structural && buildReadsState(def);
5603
+ const capturesChildren = !structural && (mkType !== null || def.unwrap || def.collapse || buildReadsChildren(def));
5604
+ const capturesRaw = !structural && (mkType !== null || buildReadsRaw(def));
5322
5605
  const hasFields = parserHasOwnFields(def.parser);
5323
5606
  const capturesFields = hasFields && !structural && buildReadsFields(def);
5324
5607
  const innerEnablesTriviaCapture = parserEnablesTriviaCapture(def.parser);
@@ -5337,9 +5620,13 @@ function emitNode(def, ctx, pos) {
5337
5620
  const profHoist = `${i}const ${pmV} = _ctx._pmProfile, ${recV} = ${pmV}?.phase === 'recognizer', ${capV} = ${pmV}?.phase === 'capture'`;
5338
5621
  const profileRecognizer = recV;
5339
5622
  const profileCapture = capV;
5623
+ const directCstV = !structural && (!capturesChildren || !capturesRaw) ? v(ctx, "_dcst") : null;
5624
+ const directCstGate = directCstV === null ? "false" : `${profileCapture} || _ctx.build?._parsemanCstOutput === true`;
5340
5625
  const structuralCapturesTrivia = structural && (def.captureTrivia === true || def.trailingTrivia === true);
5341
5626
  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)))` : ""}
5342
- ${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 : []`;
5627
+ ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRecognizer} ? undefined : [], ${tlV} = ${profileRecognizer} ? undefined : ${innerEnablesTriviaCapture ? "[]" : `${capTLv} ? [] : _EMPTY_TL`}` : capturesTrivia ? `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
5628
+ ` : ""}${i}const ${capTLv} = !(${profileRecognizer}), ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}, ${tlV} = ${profileRecognizer} ? undefined : []` : `${directCstV ? `${i}const ${directCstV} = ${directCstGate}
5629
+ ` : ""}${i}const ${chV} = ${profileRecognizer} ? undefined : ${capturesChildren ? "[]" : `${directCstV} ? [] : undefined`}, ${rawV} = ${profileRecognizer} ? undefined : ${capturesRaw ? "[]" : `${directCstV} ? [] : undefined`}`;
5343
5630
  const innerTl = structural || capturesTrivia ? structural && !innerEnablesTriviaCapture ? `${capTLv} ? ${tlV} : undefined` : tlV : "undefined";
5344
5631
  const fieldsOn = structural ? capFv ?? "false" : `${profileRecognizer} ? false : ${capturesFields ? "true" : "false"}`;
5345
5632
  const sf = hasFields ? v(ctx, "_sf") : null;
@@ -5456,14 +5743,23 @@ function emitLazy(p, def, ctx, pos) {
5456
5743
  const savedIndent = ctx.indent;
5457
5744
  const savedFailLabel = ctx.failLabel;
5458
5745
  const savedRecord = ctx.recordFail;
5746
+ const failureRuleId = p === ctx.coverage?.entry ? void 0 : ctx.coverage?.plan.rules.get(p) ?? ctx.coverage?.plan.rules.get(resolved);
5747
+ const savedActiveRule = ctx.activeCoverageRuleId;
5748
+ const savedSuppressFailure = ctx.suppressCoverageFailure;
5459
5749
  ctx.indent = 1;
5460
5750
  ctx.failLabel = "_pfail";
5461
5751
  ctx.recordFail = true;
5752
+ if (failureRuleId !== void 0) {
5753
+ ctx.activeCoverageRuleId = void 0;
5754
+ ctx.suppressCoverageFailure = true;
5755
+ }
5462
5756
  const r = emit(resolved, ctx, "_pos");
5463
5757
  ctx.indent = savedIndent;
5464
5758
  ctx.failLabel = savedFailLabel;
5465
5759
  ctx.recordFail = savedRecord;
5466
- pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar);
5760
+ ctx.activeCoverageRuleId = savedActiveRule;
5761
+ ctx.suppressCoverageFailure = savedSuppressFailure;
5762
+ pushNamedFnDecl(ctx, fnName2, r.stmts, r.valueVar, r.endVar, failureRuleId);
5467
5763
  }
5468
5764
  const fnName = ctx.namedParsers.get(p);
5469
5765
  return emitNamedFnCall(ctx, fnName, pos);
@@ -5514,6 +5810,30 @@ function emitExpect(def, ctx, pos) {
5514
5810
  return { stmts, valueVar: valVar, endVar };
5515
5811
  }
5516
5812
  function emit(p, ctx, pos) {
5813
+ const dispatch = (emissionPos = pos) => {
5814
+ const savedRule = ctx.activeCoverageRuleId;
5815
+ const rule = ctx.coverage?.plan.rules.get(p);
5816
+ if (rule && p !== ctx.coverage?.entry && !ctx.suppressCoverageFailure) ctx.activeCoverageRuleId = rule;
5817
+ const result = emitDispatch(p, ctx, emissionPos);
5818
+ ctx.activeCoverageRuleId = savedRule;
5819
+ return result;
5820
+ };
5821
+ const instrument = (r) => {
5822
+ const ruleId = ctx.coverage?.plan.rules.get(p);
5823
+ const labelIds = ctx.coverage?.plan.labels.get(p);
5824
+ if (!ruleId && !labelIds) return r;
5825
+ return {
5826
+ ...r,
5827
+ stmts: [
5828
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`] : [],
5829
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'enter', offset: ${pos} })`] : [],
5830
+ ...r.stmts,
5831
+ ...ruleId ? [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'success', offset: ${pos}, end: ${r.endVar} })`] : [],
5832
+ ...labelIds?.flatMap((id) => [`${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(id)})`]) ?? [],
5833
+ ...labelIds?.flatMap((id) => [`${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(id)}, phase: 'success', offset: ${pos}, end: ${r.endVar} })`]) ?? []
5834
+ ]
5835
+ };
5836
+ };
5517
5837
  const usage = ctx.lazyUsage;
5518
5838
  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
5519
5839
  // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
@@ -5530,14 +5850,14 @@ function emit(p, ctx, pos) {
5530
5850
  ctx.indent = 1;
5531
5851
  ctx.failLabel = "_pfail";
5532
5852
  ctx.recordFail = true;
5533
- const r = emitDispatch(p, ctx, "_pos");
5853
+ const r = dispatch("_pos");
5534
5854
  ctx.indent = savedIndent;
5535
5855
  ctx.failLabel = savedFailLabel;
5536
5856
  ctx.recordFail = savedRecord;
5537
5857
  pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
5538
- return emitNamedFnCall(ctx, fnName, pos);
5858
+ return instrument(emitNamedFnCall(ctx, fnName, pos));
5539
5859
  }
5540
- return emitDispatch(p, ctx, pos);
5860
+ return instrument(dispatch());
5541
5861
  }
5542
5862
  function emitDispatch(p, ctx, pos) {
5543
5863
  const def = p._def;
@@ -5551,7 +5871,9 @@ function emitDispatch(p, ctx, pos) {
5551
5871
  case "sequence":
5552
5872
  return emitSeq(def, ctx, pos);
5553
5873
  case "choice":
5554
- return emitChoice(def, ctx, pos);
5874
+ return emitChoice(p, def, ctx, pos);
5875
+ case "attempt":
5876
+ return emitAttempt(p, def, ctx, pos);
5555
5877
  case "many":
5556
5878
  case "oneOrMore":
5557
5879
  return emitMany(def, ctx, pos);
@@ -5620,6 +5942,8 @@ function emitDispatch(p, ctx, pos) {
5620
5942
  return emit(def.parser, ctx, pos);
5621
5943
  case "token":
5622
5944
  return emitToken(def, ctx, pos);
5945
+ case "leaf":
5946
+ return emitLeaf(def, ctx, pos);
5623
5947
  case "label": {
5624
5948
  const inner = emitFallible(def.parser, ctx, pos);
5625
5949
  return {
@@ -5751,6 +6075,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5751
6075
  case "grammar":
5752
6076
  case "trivia":
5753
6077
  case "token":
6078
+ case "leaf":
5754
6079
  case "label":
5755
6080
  case "field":
5756
6081
  case "optional":
@@ -5778,16 +6103,32 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
5778
6103
  return false;
5779
6104
  }
5780
6105
  }
6106
+ function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
6107
+ if (seen.has(p)) return false;
6108
+ seen.add(p);
6109
+ const d = p._def;
6110
+ if (d.tag === "node" && d.build !== void 0) return true;
6111
+ if (d.tag === "lazy") {
6112
+ try {
6113
+ return hasDirectBuildDef(d.thunk(), seen);
6114
+ } catch {
6115
+ return false;
6116
+ }
6117
+ }
6118
+ return childrenOf(d).some((child) => hasDirectBuildDef(child, seen));
6119
+ }
5781
6120
  function childrenOf(def) {
5782
6121
  switch (def.tag) {
5783
6122
  case "sequence":
5784
6123
  case "choice":
5785
6124
  return def.parsers;
5786
6125
  case "many":
6126
+ case "attempt":
5787
6127
  case "optional":
5788
6128
  case "transform":
5789
6129
  case "trivia":
5790
6130
  case "token":
6131
+ case "leaf":
5791
6132
  case "label":
5792
6133
  case "field":
5793
6134
  case "grammar":
@@ -5962,10 +6303,25 @@ function compile(combinator, mapFnSources, opts) {
5962
6303
  namedFnDecls: [],
5963
6304
  capturing: hasNodeDef(combinator),
5964
6305
  recovery: opts?.recovery ?? false,
6306
+ ...opts?.coverage ? { coverage: { plan: buildGrammarPlan(combinator), entry: combinator } } : {},
5965
6307
  lazyUsage: analyzeLazyUsage(combinator),
5966
6308
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
5967
6309
  };
5968
6310
  const r = emit(combinator, ctx, "_pos");
6311
+ const coverageRootRuleId = ctx.coverage?.plan.rules.get(combinator);
6312
+ const resultBody = coverageRootRuleId === void 0 ? [
6313
+ ` let pos = _pos`,
6314
+ ...r.stmts,
6315
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6316
+ ] : [
6317
+ ` const _coverageResult = (() => {`,
6318
+ ` let pos = _pos`,
6319
+ ...r.stmts,
6320
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6321
+ ` })()`,
6322
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6323
+ ` return _coverageResult`
6324
+ ];
5969
6325
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
5970
6326
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
5971
6327
  const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
@@ -5978,9 +6334,7 @@ function compile(combinator, mapFnSources, opts) {
5978
6334
  ...namedPrelude,
5979
6335
  ctx.namedFnDecls.join("\n\n"),
5980
6336
  `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
5981
- ` let pos = _pos`,
5982
- ...r.stmts,
5983
- ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6337
+ ...resultBody,
5984
6338
  `}`
5985
6339
  ].join("\n");
5986
6340
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
@@ -5990,9 +6344,15 @@ function compile(combinator, mapFnSources, opts) {
5990
6344
  ...ctx.expectedDecls,
5991
6345
  ...namedPrelude,
5992
6346
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
5993
- `let pos = _pos`,
5994
- ...r.stmts,
5995
- `return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6347
+ ...coverageRootRuleId === void 0 ? [`let pos = _pos`, ...r.stmts, `return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`] : [
6348
+ `const _coverageResult = (() => {`,
6349
+ ` let pos = _pos`,
6350
+ ...r.stmts,
6351
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6352
+ `})()`,
6353
+ `if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6354
+ `return _coverageResult`
6355
+ ]
5996
6356
  ].join("\n"));
5997
6357
  const defaultCtx = {
5998
6358
  trackLines: false,
@@ -6004,10 +6364,11 @@ function compile(combinator, mapFnSources, opts) {
6004
6364
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
6005
6365
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
6006
6366
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
6007
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
6367
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources, coverageRootRuleId) : null;
6008
6368
  return {
6009
6369
  source,
6010
6370
  inlineExpression,
6371
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions },
6011
6372
  parse(input, pos = 0) {
6012
6373
  return fn(input, pos, ctx.runtimeParsers, ctx.mapFns, ctx.buildFns, defaultCtx);
6013
6374
  },
@@ -6041,6 +6402,7 @@ function publicRuleWrapperSource(rule, fnSource) {
6041
6402
  }
6042
6403
  function compileRuleMap(ruleMap, opts) {
6043
6404
  for (const [, rule] of ruleMap) markUnusedValues(rule);
6405
+ const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
6044
6406
  const grammarTrivia = opts?.trivia;
6045
6407
  const ctx = {
6046
6408
  vars: 0,
@@ -6061,6 +6423,7 @@ function compileRuleMap(ruleMap, opts) {
6061
6423
  namedFnDecls: [],
6062
6424
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
6063
6425
  recovery: opts?.recovery ?? false,
6426
+ ...opts?.coverage ? { coverage: { plan: buildGrammarPlan(ruleMap.map(([, rule]) => rule), coverageWinners) } } : {},
6064
6427
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
6065
6428
  ...grammarTrivia ? { activeTrivia: grammarTrivia, triviaKindLabels: grammarTrivia._meta.triviaKindLabels } : {}
6066
6429
  };
@@ -6103,15 +6466,34 @@ function compileRuleMap(ruleMap, opts) {
6103
6466
  return i > 0 ? ["", ...lines] : lines;
6104
6467
  })
6105
6468
  ].filter(Boolean);
6106
- const entryFnText = (r) => [
6107
- `function(input, _pos, _ctx) {`,
6108
- ` let pos = _pos`,
6109
- ...r.stmts,
6110
- ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6111
- `}`
6112
- ].join("\n");
6469
+ const entryRuleId = (rule) => {
6470
+ const direct = ctx.coverage?.plan.rules.get(rule);
6471
+ if (direct !== void 0 || rule._def.tag !== "lazy") return direct;
6472
+ try {
6473
+ return ctx.coverage?.plan.rules.get(rule._def.thunk());
6474
+ } catch {
6475
+ return void 0;
6476
+ }
6477
+ };
6478
+ const entryFnText = (r, rule) => {
6479
+ const ruleId = entryRuleId(rule);
6480
+ const body = ruleId === void 0 ? [
6481
+ ` let pos = _pos`,
6482
+ ...r.stmts,
6483
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6484
+ ] : [
6485
+ ` const _coverageResult = (() => {`,
6486
+ ` let pos = _pos`,
6487
+ ...r.stmts,
6488
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6489
+ ` })()`,
6490
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6491
+ ` return _coverageResult`
6492
+ ];
6493
+ return [`function(input, _pos, _ctx) {`, ...body, `}`].join("\n");
6494
+ };
6113
6495
  const objBody = perEntry.map(({ key, rule, r }) => {
6114
- const src = publicRuleWrapperSource(rule, entryFnText(r));
6496
+ const src = publicRuleWrapperSource(rule, entryFnText(r, rule));
6115
6497
  return ` ${JSON.stringify(key)}: ${src.split("\n").join("\n ")}`;
6116
6498
  }).join(",\n");
6117
6499
  const replacement = [
@@ -6122,7 +6504,11 @@ function compileRuleMap(ruleMap, opts) {
6122
6504
  ` }`,
6123
6505
  `})()`
6124
6506
  ].join("\n");
6125
- return { keys: perEntry.map((e) => e.key), replacement };
6507
+ return {
6508
+ keys: perEntry.map((e) => e.key),
6509
+ replacement,
6510
+ ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
6511
+ };
6126
6512
  }
6127
6513
  function compileLinkable(ruleMapArg, ns, opts) {
6128
6514
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
@@ -6155,8 +6541,13 @@ function compileLinkable(ruleMapArg, ns, opts) {
6155
6541
  triviaCaptureNames: /* @__PURE__ */ new Map(),
6156
6542
  triviaFnNames: /* @__PURE__ */ new Map(),
6157
6543
  namedFnDecls: [],
6158
- capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
6544
+ // A recognition-only fragment normally has no node collector of its own.
6545
+ // Leaf composition can re-lower it beneath a local semantic node, though;
6546
+ // in that case its terminals must feed the caller's collector rather than
6547
+ // merely returning their scalar parse values.
6548
+ capturing: opts?.captureTerminals === true || ruleMap.some(([, rule]) => hasNodeDef(rule)),
6159
6549
  recovery: opts?.recovery ?? false,
6550
+ ...opts?.coverage ? { coverage: { plan: opts.coverage } } : {},
6160
6551
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
6161
6552
  ns,
6162
6553
  deferFirstSetRefs: true,
@@ -6230,7 +6621,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
6230
6621
  ctx.indent = savedIndent;
6231
6622
  ctx.failLabel = savedFail;
6232
6623
  ctx.recordFail = savedRec;
6233
- pushNamedFnDecl(ctx, fn, body.stmts, body.valueVar, body.endVar);
6624
+ const ruleId = ctx.coverage?.plan.rules.get(rule);
6625
+ const stmts = ruleId === void 0 ? body.stmts : [
6626
+ `${ind(ctx)}_ctx._grammarCoverage?.(${JSON.stringify(ruleId)})`,
6627
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'enter', offset: _pos })`,
6628
+ ...body.stmts,
6629
+ `${ind(ctx)}_ctx._grammarTrace?.write({ id: ${JSON.stringify(ruleId)}, phase: 'success', offset: _pos, end: ${body.endVar} })`
6630
+ ];
6631
+ pushNamedFnDecl(ctx, fn, stmts, body.valueVar, body.endVar);
6234
6632
  }
6235
6633
  perEntry.push({ key, rule, r: emitNamedFnCall(ctx, fn, "_pos") });
6236
6634
  }
@@ -6287,15 +6685,46 @@ function compileLinkable(ruleMapArg, ns, opts) {
6287
6685
  deps: ruleDependencies(ruleMap),
6288
6686
  needsEmptyTl: !!ctx.needsEmptyTl,
6289
6687
  needsHostReads: !!ctx.needsHostReads,
6688
+ hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
6689
+ isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
6290
6690
  mfFns: mfSrcs ? [] : ctx.mapFns,
6291
6691
  buildFns: buildSrcs ? [] : ctx.buildFns
6292
6692
  };
6293
6693
  }
6294
- function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
6295
- const bodyLines = [
6694
+ function hasSemanticReduction(roots) {
6695
+ const seen = /* @__PURE__ */ new Set();
6696
+ const visit = (parser2) => {
6697
+ if (seen.has(parser2)) return false;
6698
+ seen.add(parser2);
6699
+ const def = parser2._def;
6700
+ if (def.tag === "transform" && !def.recognitionOnly) return true;
6701
+ if (def.tag === "choice" && def.gates.some(Boolean)) return true;
6702
+ if (def.tag === "guard" || def.tag === "withCtx") return true;
6703
+ if (def.tag === "node" && def.build !== void 0) return true;
6704
+ if (def.tag === "lazy") {
6705
+ try {
6706
+ return visit(def.thunk());
6707
+ } catch {
6708
+ return true;
6709
+ }
6710
+ }
6711
+ return childrenOf(def).some(visit);
6712
+ };
6713
+ return roots.some(visit);
6714
+ }
6715
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources, coverageRootRuleId) {
6716
+ const bodyLines = coverageRootRuleId === void 0 ? [
6296
6717
  ` let pos = _pos`,
6297
6718
  ...r.stmts,
6298
6719
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`
6720
+ ] : [
6721
+ ` const _coverageResult = (() => {`,
6722
+ ` let pos = _pos`,
6723
+ ...r.stmts,
6724
+ ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
6725
+ ` })()`,
6726
+ ` if (!_coverageResult.ok) _ctx._grammarTrace?.write({ id: ${JSON.stringify(coverageRootRuleId)}, phase: 'failure', offset: _coverageResult.span.start })`,
6727
+ ` return _coverageResult`
6299
6728
  ];
6300
6729
  const innerFn = [
6301
6730
  `function(input, _pos, _ctx) {`,
@@ -6350,8 +6779,8 @@ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
6350
6779
  };
6351
6780
  const emit2 = (end) => {
6352
6781
  if (end > pos && cstCaptureActive(ctx)) {
6353
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
6354
- pushCstLeaf(ctx, leaf);
6782
+ const leaf2 = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
6783
+ pushCstLeaf(ctx, leaf2);
6355
6784
  }
6356
6785
  };
6357
6786
  while (cur < input.length) {
@@ -6405,6 +6834,7 @@ function balanced(open, close, options = {}) {
6405
6834
  );
6406
6835
  if (combi._def.tag === "transform") {
6407
6836
  combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
6837
+ combi._def.recognitionOnly = true;
6408
6838
  }
6409
6839
  self.define(combi);
6410
6840
  return combi;
@@ -6450,9 +6880,11 @@ function childrenOf2(def) {
6450
6880
  case "many":
6451
6881
  case "oneOrMore":
6452
6882
  case "optional":
6883
+ case "attempt":
6453
6884
  case "transform":
6454
6885
  case "trivia":
6455
6886
  case "token":
6887
+ case "leaf":
6456
6888
  case "label":
6457
6889
  case "field":
6458
6890
  case "not":
@@ -6501,7 +6933,7 @@ function serializeRuleMap(ruleMap) {
6501
6933
  }
6502
6934
  }
6503
6935
  function evalRuleMapIR(ir) {
6504
- const _tf = (child, src) => {
6936
+ const _tf = (child, src, recognitionOnly = false) => {
6505
6937
  let fn2;
6506
6938
  try {
6507
6939
  fn2 = (0, eval)(`(${src})`);
@@ -6512,8 +6944,22 @@ function evalRuleMapIR(ir) {
6512
6944
  }
6513
6945
  const t = transform(child, fn2);
6514
6946
  t._def.fnSrc = src;
6947
+ if (recognitionOnly) t._def.recognitionOnly = true;
6515
6948
  return t;
6516
6949
  };
6950
+ const _lf = (child, src) => {
6951
+ let fn2;
6952
+ try {
6953
+ fn2 = (0, eval)(`(${src})`);
6954
+ } catch {
6955
+ fn2 = () => {
6956
+ throw new Error("IR leaf fn not materialized");
6957
+ };
6958
+ }
6959
+ const l = leaf(child, fn2);
6960
+ l._def.fnSrc = src;
6961
+ return l;
6962
+ };
6517
6963
  const _nd = (type, child, src, opts, staticError) => {
6518
6964
  if (staticError !== void 0 && staticError.length > 0) {
6519
6965
  throw new Error(`IR direct node builder for ${type} must be macro-static and self-contained; unsupported binding(s): ${staticError.join(", ")}`);
@@ -6572,6 +7018,7 @@ function evalRuleMapIR(ir) {
6572
7018
  "keywords",
6573
7019
  "sequence",
6574
7020
  "choice",
7021
+ "attempt",
6575
7022
  "many",
6576
7023
  "oneOrMore",
6577
7024
  "optional",
@@ -6581,6 +7028,7 @@ function evalRuleMapIR(ir) {
6581
7028
  "parser",
6582
7029
  "scanTo",
6583
7030
  "token",
7031
+ "leaf",
6584
7032
  "transform",
6585
7033
  "skip",
6586
7034
  "trivia",
@@ -6588,6 +7036,7 @@ function evalRuleMapIR(ir) {
6588
7036
  "field",
6589
7037
  "expect",
6590
7038
  "_tf",
7039
+ "_lf",
6591
7040
  "_nd",
6592
7041
  "_gch",
6593
7042
  "_wc",
@@ -6601,6 +7050,7 @@ function evalRuleMapIR(ir) {
6601
7050
  keywords,
6602
7051
  sequence,
6603
7052
  choice,
7053
+ attempt,
6604
7054
  many,
6605
7055
  oneOrMore,
6606
7056
  optional,
@@ -6610,6 +7060,7 @@ function evalRuleMapIR(ir) {
6610
7060
  parser,
6611
7061
  scanTo,
6612
7062
  token,
7063
+ leaf,
6613
7064
  transform,
6614
7065
  skip,
6615
7066
  trivia,
@@ -6617,6 +7068,7 @@ function evalRuleMapIR(ir) {
6617
7068
  field,
6618
7069
  expect,
6619
7070
  _tf,
7071
+ _lf,
6620
7072
  _nd,
6621
7073
  _gch,
6622
7074
  _wc
@@ -6748,6 +7200,8 @@ ${body}
6748
7200
  return `oneOrMore(${kid(def.parser)})`;
6749
7201
  case "optional":
6750
7202
  return `optional(${kid(def.parser)})`;
7203
+ case "attempt":
7204
+ return `attempt(${kid(def.parser)})`;
6751
7205
  case "sepBy":
6752
7206
  return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
6753
7207
  case "not":
@@ -6756,6 +7210,10 @@ ${body}
6756
7210
  return `trivia(${kid(def.parser)})`;
6757
7211
  case "token":
6758
7212
  return `token(${kid(def.parser)})`;
7213
+ case "leaf": {
7214
+ if (def.fnSrc === void 0) throw new Unserializable("leaf without fnSrc");
7215
+ return `_lf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
7216
+ }
6759
7217
  case "label":
6760
7218
  return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
6761
7219
  case "field":
@@ -6768,7 +7226,7 @@ ${body}
6768
7226
  return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
6769
7227
  case "transform": {
6770
7228
  if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
6771
- return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
7229
+ return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)}${def.recognitionOnly ? ", true" : ""})`;
6772
7230
  }
6773
7231
  case "node": {
6774
7232
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
@@ -6814,15 +7272,15 @@ function normalizeCstCollapse(collapse) {
6814
7272
  }
6815
7273
  return typeof collapse === "function" ? collapse : void 0;
6816
7274
  }
6817
- function buildCstNode(type, children, _fields, span, _rawChildren, _triviaLog, state) {
6818
- return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children] };
7275
+ function buildCstNode(type, children2, _fields, span, _rawChildren, _triviaLog, state) {
7276
+ return { _tag: "node", type, span: { start: span.start, end: span.end }, state: state ?? null, children: [...children2] };
6819
7277
  }
6820
- function cstBuildHost(typeOrOptions, children, _fields, span, rawChildren, triviaLog, state) {
7278
+ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triviaLog, state) {
6821
7279
  if (typeof typeOrOptions === "string") {
6822
- return buildCstNode(typeOrOptions, children ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
7280
+ return buildCstNode(typeOrOptions, children2 ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
6823
7281
  }
6824
7282
  const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
6825
- const host = (type, children2, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children2, fields, span2, rawChildren2, triviaLog2, state2);
7283
+ const host = (type, children3, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children3, fields, span2, rawChildren2, triviaLog2, state2);
6826
7284
  host._parsemanCstOutput = true;
6827
7285
  if (collapse) host._parsemanCstCollapse = collapse;
6828
7286
  return host;
@@ -6905,9 +7363,12 @@ ${body}
6905
7363
  function isIRPiece(p) {
6906
7364
  return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
6907
7365
  }
6908
- function materializePiece(p, trivia2) {
7366
+ function materializePiece(p, trivia2, captureTerminals = false) {
6909
7367
  if (!isIRPiece(p)) return p;
6910
- const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, trivia2 ? { trivia: trivia2 } : void 0);
7368
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns, {
7369
+ ...trivia2 ? { trivia: trivia2 } : {},
7370
+ ...captureTerminals ? { captureTerminals: true } : {}
7371
+ });
6911
7372
  if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
6912
7373
  return pieces;
6913
7374
  }
@@ -7136,6 +7597,7 @@ var SUPPORTED = {
7136
7597
  word: (...a) => word(a[0], a[1]),
7137
7598
  sequence: (...a) => sequence(...a),
7138
7599
  choice: (...a) => choice(...a),
7600
+ attempt: (...a) => attempt(a[0]),
7139
7601
  many: (...a) => many(a[0]),
7140
7602
  oneOrMore: (...a) => oneOrMore(a[0]),
7141
7603
  optional: (...a) => optional(a[0]),
@@ -7146,6 +7608,7 @@ var SUPPORTED = {
7146
7608
  field: (...a) => field(a[0], a[1]),
7147
7609
  noTrivia: (...a) => noTrivia(a[0]),
7148
7610
  token: (...a) => token(a[0]),
7611
+ leaf: (...a) => leaf(a[0], a[1]),
7149
7612
  expect: (...a) => expect(a[0], a[1])
7150
7613
  };
7151
7614
  function isScopeEntry(v2) {
@@ -7245,7 +7708,7 @@ function exprToCombi(node2, scope, code, mfs) {
7245
7708
  if (!parserArg || !fnArg || parserArg.type === "SpreadElement" || fnArg.type === "SpreadElement") return null;
7246
7709
  const inner = anyValue(parserArg, scope, code, mfs);
7247
7710
  if (!isCombinator(inner)) return null;
7248
- const fnSrc = code.slice(fnArg.start, fnArg.end);
7711
+ const fnSrc = stripTsFromSource(fnArg, code);
7249
7712
  mfs.push(fnSrc);
7250
7713
  try {
7251
7714
  const combi = transform(inner, (v2) => v2);
@@ -7255,6 +7718,21 @@ function exprToCombi(node2, scope, code, mfs) {
7255
7718
  return null;
7256
7719
  }
7257
7720
  }
7721
+ if (callee.name === "leaf" && code !== void 0 && mfs !== void 0) {
7722
+ const [parserArg, fnArg] = node2.arguments;
7723
+ if (!parserArg || !fnArg || parserArg.type === "SpreadElement" || fnArg.type === "SpreadElement") return null;
7724
+ const inner = anyValue(parserArg, scope, code, mfs);
7725
+ if (!isCombinator(inner)) return null;
7726
+ const fnSrc = stripTsFromSource(fnArg, code);
7727
+ mfs.push(fnSrc);
7728
+ try {
7729
+ const combi = leaf(inner, (v2) => v2);
7730
+ if (combi._def.tag === "leaf") combi._def.fnSrc = fnSrc;
7731
+ return combi;
7732
+ } catch {
7733
+ return null;
7734
+ }
7735
+ }
7258
7736
  if (callee.name === "node" && code !== void 0) {
7259
7737
  const [firstArg, secondArg, thirdArg, fourthArg] = node2.arguments;
7260
7738
  if (!firstArg || firstArg.type === "SpreadElement") return null;
@@ -7707,6 +8185,63 @@ function getCompiledResolver() {
7707
8185
  mainFields: ["module", "main"]
7708
8186
  });
7709
8187
  }
8188
+ var PRIVATE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".mts", ".cts"]);
8189
+ function nearestPackageRoot(file) {
8190
+ let dir = path.dirname(file);
8191
+ for (; ; ) {
8192
+ try {
8193
+ if (fs.statSync(path.join(dir, "package.json")).isFile()) return fs.realpathSync(dir);
8194
+ } catch {
8195
+ }
8196
+ const parent = path.dirname(dir);
8197
+ if (parent === dir) return null;
8198
+ dir = parent;
8199
+ }
8200
+ }
8201
+ function isWithinPackageRoot(file, root) {
8202
+ const relative2 = path.relative(root, file);
8203
+ return relative2 !== ".." && !relative2.startsWith(`..${path.sep}`) && !path.isAbsolute(relative2);
8204
+ }
8205
+ function resolvePrivateSourceModule(from, specifier) {
8206
+ if (!specifier.startsWith(".") && !path.isAbsolute(specifier)) return null;
8207
+ const packageRoot = nearestPackageRoot(from);
8208
+ if (!packageRoot) return null;
8209
+ const base = path.resolve(path.dirname(from), specifier);
8210
+ const ext = path.extname(base);
8211
+ const candidates = [
8212
+ base,
8213
+ ...ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base.slice(0, -ext.length)}${next}`) : [],
8214
+ ...!ext ? [".ts", ".tsx", ".mts", ".cts"].map((next) => `${base}${next}`) : [],
8215
+ ...!ext ? ["index.ts", "index.tsx", "index.mts", "index.cts"].map((next) => path.join(base, next)) : []
8216
+ ];
8217
+ for (const candidate of candidates) {
8218
+ try {
8219
+ if (!fs.statSync(candidate).isFile()) continue;
8220
+ const real = fs.realpathSync(candidate);
8221
+ if (isWithinPackageRoot(real, packageRoot)) return real;
8222
+ } catch {
8223
+ }
8224
+ }
8225
+ return null;
8226
+ }
8227
+ function resolveGrammarModule(from, specifier) {
8228
+ if (PRIVATE_SOURCE_EXTENSIONS.has(path.extname(specifier))) {
8229
+ const file2 = resolvePrivateSourceModule(from, specifier);
8230
+ return file2 ? { file: file2, source: true } : null;
8231
+ }
8232
+ try {
8233
+ const file2 = getCompiledResolver().resolveFileSync(from, specifier).path;
8234
+ if (file2) return { file: file2, source: false };
8235
+ } catch {
8236
+ }
8237
+ const file = resolvePrivateSourceModule(from, specifier);
8238
+ return file ? { file, source: true } : null;
8239
+ }
8240
+ var _sourceLowering = /* @__PURE__ */ new Set();
8241
+ var _sourceLoweringCache = /* @__PURE__ */ new Map();
8242
+ function sourceLoweringCacheKey(file, moduleAliases, warnUnloweredRegex, recovery) {
8243
+ return `${file}\0${[...moduleAliases].sort().join("\0")}\0${warnUnloweredRegex ? "warn" : ""}\0${recovery ? "recovery" : ""}`;
8244
+ }
7710
8245
  var _moduleParseCache = /* @__PURE__ */ new Map();
7711
8246
  function parseModuleCached(filePath) {
7712
8247
  let mtimeMs;
@@ -7740,7 +8275,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
7740
8275
  if (!code.includes("parseman")) return null;
7741
8276
  if (!code.includes("macro")) return null;
7742
8277
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
7743
- const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true);
8278
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true, opts.recovery === true, opts.grammarCoverage === true);
7744
8279
  if (result?.warnings.length) {
7745
8280
  for (const w of result.warnings) {
7746
8281
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -7844,7 +8379,34 @@ function extractImportBindings(body) {
7844
8379
  }
7845
8380
  return out;
7846
8381
  }
7847
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false) {
8382
+ function lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recovery) {
8383
+ if (_sourceLowering.has(file)) return null;
8384
+ let mtimeMs;
8385
+ try {
8386
+ mtimeMs = fs.statSync(file).mtimeMs;
8387
+ } catch {
8388
+ return null;
8389
+ }
8390
+ const cacheKey = sourceLoweringCacheKey(file, moduleAliases, warnUnloweredRegex, recovery);
8391
+ const hit = _sourceLoweringCache.get(cacheKey);
8392
+ if (hit && hit.mtimeMs === mtimeMs) return hit.parsed;
8393
+ _sourceLowering.add(file);
8394
+ let parsedModule = null;
8395
+ try {
8396
+ const source = fs.readFileSync(file, "utf8");
8397
+ const transformed = transformMacro(source, file, moduleAliases, warnUnloweredRegex, recovery);
8398
+ if (!transformed || transformed.warnings.length > 0) return null;
8399
+ const parsed = (0, import_oxc_parser2.parseSync)(file, transformed.code);
8400
+ parsedModule = parsed.errors.length === 0 ? { body: parsed.program.body, src: transformed.code } : null;
8401
+ return parsedModule;
8402
+ } catch {
8403
+ return null;
8404
+ } finally {
8405
+ _sourceLoweringCache.set(cacheKey, { mtimeMs, parsed: parsedModule });
8406
+ _sourceLowering.delete(file);
8407
+ }
8408
+ }
8409
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false, recovery = false, grammarCoverage = false) {
7848
8410
  let result;
7849
8411
  try {
7850
8412
  result = (0, import_oxc_parser2.parseSync)(id, code);
@@ -7896,7 +8458,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7896
8458
  anyUnresolved = true;
7897
8459
  warnings.push(`${id}:${lineOf(pos)} \u2014 ${msg} (running via the interpreter; add the plugin or simplify the declaration to compile it)`);
7898
8460
  };
7899
- const compileRulesFactory = (init, label2) => {
8461
+ const evaluateRulesFactory = (init, label2) => {
7900
8462
  const args = init.arguments;
7901
8463
  const arg0 = args[0];
7902
8464
  const arg1 = args[1];
@@ -7916,15 +8478,21 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7916
8478
  (p) => p.key?.name === "trivia"
7917
8479
  )?.value : void 0;
7918
8480
  const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
7919
- const compiled = compileRuleMap([...ruleMap], { ...gTrivia ? { trivia: gTrivia } : {}, recovery });
8481
+ return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {} };
8482
+ };
8483
+ const compileRulesFactory = (init, label2) => {
8484
+ const evaluated = evaluateRulesFactory(init, label2);
8485
+ if (!evaluated) return null;
8486
+ const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, recovery, coverage: grammarCoverage });
7920
8487
  if (!compiled) {
7921
8488
  warn(init.start, `${label2}: rule map couldn't be inlined`);
7922
8489
  return null;
7923
8490
  }
7924
- return { replacement: compiled.replacement, ruleMap, ...gTrivia ? { trivia: gTrivia } : {} };
8491
+ return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {} };
7925
8492
  };
7926
8493
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
7927
8494
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
8495
+ const isComposeLeafCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "composeLeaf";
7928
8496
  const isPickCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "pick";
7929
8497
  const localRuleMaps = /* @__PURE__ */ new Map();
7930
8498
  const localGrammarTrivia = /* @__PURE__ */ new Map();
@@ -7934,18 +8502,29 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7934
8502
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
7935
8503
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
7936
8504
  ).join(", ")}])`;
7937
- 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: [] }`;
8505
+ 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: [] }`;
7938
8506
  };
7939
8507
  const isSpread = (it) => "__spreadLocal" in it;
7940
8508
  const isIR = (it) => "ir" in it && !("ruleFns" in it);
7941
8509
  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);
7942
8510
  const serializeList = (list) => `[${list.map(serializeItem).join(", ")}]`;
7943
8511
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
8512
+ const withLeafMarker = (grammarExpr) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.leafComposed'), { value: true, enumerable: false })`;
8513
+ 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 })`;
8514
+ const emittedCoverageDefinitions = (source) => {
8515
+ const ids = /* @__PURE__ */ new Set();
8516
+ for (const match of source.matchAll(/id:\s*"([^"]+)"/g)) ids.add(match[1]);
8517
+ return [...ids].sort().map((id2) => ({
8518
+ id: id2,
8519
+ kind: id2.startsWith("rule:") ? "rule" : id2.startsWith("label:") ? "label" : "choice-arm"
8520
+ }));
8521
+ };
7944
8522
  const localComposedCarried = /* @__PURE__ */ new Map();
7945
8523
  const localComposedTrivia = /* @__PURE__ */ new Map();
7946
8524
  const importedPiecesCache = /* @__PURE__ */ new Map();
7947
- const resolveModulePieces = (file, exportName, seen) => {
7948
- const mod = parseModuleCached(file);
8525
+ const resolveModulePieces = (module2, exportName, seen) => {
8526
+ const { file } = module2;
8527
+ const mod = module2.source ? lowerPrivateSourceModule(file, moduleAliases, warnUnloweredRegex, recovery) : parseModuleCached(file);
7949
8528
  if (!mod) return null;
7950
8529
  const localFor = exportLocalName(mod.body, exportName);
7951
8530
  const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
@@ -7964,12 +8543,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7964
8543
  const b = imports.get(local);
7965
8544
  let subPieces = [];
7966
8545
  if (b) {
7967
- let subFile = null;
7968
- try {
7969
- subFile = getCompiledResolver().resolveFileSync(file, b.source).path ?? null;
7970
- } catch {
8546
+ const subModule = resolveGrammarModule(file, b.source);
8547
+ if (subModule && !seen.has(subModule.file)) {
8548
+ subPieces = resolveModulePieces(subModule, b.imported, new Set(seen).add(subModule.file)) ?? [];
7971
8549
  }
7972
- if (subFile && !seen.has(subFile)) subPieces = resolveModulePieces(subFile, b.imported, new Set(seen).add(subFile)) ?? [];
7973
8550
  }
7974
8551
  stubNames.push(local);
7975
8552
  stubVals.push({ [/* @__PURE__ */ Symbol.for("parseman.composedPieces")]: subPieces });
@@ -7986,29 +8563,56 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
7986
8563
  let result2 = null;
7987
8564
  const binding = importBindings.get(localName);
7988
8565
  if (binding) {
7989
- let file = null;
7990
- try {
7991
- file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
7992
- } catch {
7993
- }
7994
- result2 = file ? resolveModulePieces(file, binding.imported, /* @__PURE__ */ new Set([file])) : null;
8566
+ const module2 = resolveGrammarModule(id, binding.source);
8567
+ result2 = module2 ? resolveModulePieces(module2, binding.imported, /* @__PURE__ */ new Set([module2.file])) : null;
7995
8568
  }
7996
8569
  importedPiecesCache.set(localName, result2);
7997
8570
  return result2;
7998
8571
  };
7999
- const materializeCarried = (items, composing) => {
8572
+ const materializeCarried = (items, composing, captureTerminals = false) => {
8000
8573
  const out = [];
8001
8574
  for (const it of items) {
8002
8575
  if (isSpread(it)) {
8003
- for (const p of importedPieces(it.__spreadLocal) ?? []) out.push(materializePiece(p, composing));
8576
+ for (const p of importedPieces(it.__spreadLocal) ?? []) out.push(materializePiece(p, composing, captureTerminals));
8004
8577
  } else if (isIR(it)) {
8005
- out.push(materializePiece(it, composing));
8578
+ out.push(materializePiece(it, composing, captureTerminals));
8006
8579
  } else {
8007
8580
  out.push(it);
8008
8581
  }
8009
8582
  }
8010
8583
  return out;
8011
8584
  };
8585
+ const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
8586
+ const sources = [];
8587
+ const add = (item) => {
8588
+ if (!isIR(item)) return false;
8589
+ sources.push({ ns: item.ns, rules: evalRuleMapIR(item.ir) });
8590
+ return true;
8591
+ };
8592
+ for (const item of items) {
8593
+ if (isSpread(item)) {
8594
+ const imported = importedPieces(item.__spreadLocal);
8595
+ if (!imported || !imported.every(add)) return null;
8596
+ } else if (!add(item)) return null;
8597
+ }
8598
+ sources.push({ ns: localNs, rules: [...localRules] });
8599
+ const winners = {};
8600
+ for (const source of sources) for (const [name, rule] of source.rules) winners[name] = rule;
8601
+ const plan = buildGrammarPlan(Object.values(winners), winners);
8602
+ const pieces = [];
8603
+ for (let index = 0; index < sources.length; index++) {
8604
+ const source = sources[index];
8605
+ const piece = compileLinkable(source.rules, source.ns, {
8606
+ ...composing ? { trivia: composing } : {},
8607
+ recovery,
8608
+ ...index < sources.length - 1 && captureTerminals ? { captureTerminals: true } : {},
8609
+ coverage: plan
8610
+ });
8611
+ if (!piece) return null;
8612
+ pieces.push(piece);
8613
+ }
8614
+ return pieces;
8615
+ };
8012
8616
  const localCarried = (rm, label2, composing) => {
8013
8617
  const entries = [...rm];
8014
8618
  const ns = nsFor(label2);
@@ -8124,6 +8728,64 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8124
8728
  return null;
8125
8729
  }
8126
8730
  };
8731
+ const compileComposeLeafCall = (init) => {
8732
+ const args = init.arguments;
8733
+ const arr = args[0];
8734
+ if (!arr || arr.type !== "ArrayExpression") {
8735
+ warn(init.start, "composeLeaf(): expected a static array of grammars");
8736
+ return null;
8737
+ }
8738
+ const elements = arr.elements;
8739
+ if (elements.length < 2) {
8740
+ warn(init.start, "composeLeaf(): needs imported recognition grammar(s) and one local rules() map");
8741
+ return null;
8742
+ }
8743
+ const localArg = elements[elements.length - 1];
8744
+ let localRules = null;
8745
+ if (isRulesCall(localArg)) {
8746
+ const evaluated = evaluateRulesFactory(localArg, `composeLeaf${init.start}`);
8747
+ localRules = evaluated?.ruleMap ?? null;
8748
+ } else if (localArg.type === "Identifier") {
8749
+ localRules = localRuleMaps.get(localArg.name) ?? null;
8750
+ }
8751
+ if (!localRules) {
8752
+ warn(init.start, "composeLeaf(): final argument must be a local rules() map");
8753
+ return null;
8754
+ }
8755
+ const composing = composingTrivia(elements);
8756
+ const carried = [];
8757
+ for (let i = 0; i < elements.length - 1; i++) {
8758
+ const r = argPieces(elements[i], `composeLeaf${init.start}_${i}`, composing);
8759
+ if (!r) {
8760
+ warn(init.start, `composeLeaf(): argument ${i} isn't a build-resolvable recognition grammar`);
8761
+ return null;
8762
+ }
8763
+ carried.push(...r.carried);
8764
+ }
8765
+ try {
8766
+ const localNs = nsFor(`composeLeaf${init.start}`);
8767
+ const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, recovery });
8768
+ if (!plainLocalPiece) {
8769
+ warn(init.start, "composeLeaf(): local rules could not be statically compiled");
8770
+ return null;
8771
+ }
8772
+ const recognitionPieces = grammarCoverage ? materializeLeafCoverage(carried, localRules, localNs, composing, plainLocalPiece.hasDirectBuilders === true) : materializeCarried(carried, composing, plainLocalPiece.hasDirectBuilders === true);
8773
+ if (!recognitionPieces) {
8774
+ warn(init.start, "composeLeaf(): coverage needs re-lowerable recognition IR");
8775
+ return null;
8776
+ }
8777
+ const importedRecognitionPieces = grammarCoverage ? recognitionPieces.slice(0, -1) : recognitionPieces;
8778
+ if (importedRecognitionPieces.some((piece) => piece.hasDirectBuilders !== false || piece.isRecognitionOnly !== true)) {
8779
+ warn(init.start, "composeLeaf(): every pre-final grammar must explicitly prove recognition-only");
8780
+ return null;
8781
+ }
8782
+ const replacement = withLeafMarker(emitFusedSource(grammarCoverage ? recognitionPieces : [...recognitionPieces, plainLocalPiece]));
8783
+ return { replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) };
8784
+ } catch (e) {
8785
+ warn(init.start, `composeLeaf(): ${e.message}`);
8786
+ return null;
8787
+ }
8788
+ };
8127
8789
  const unwrapVd = (stmt) => stmt.type === "VariableDeclaration" ? stmt : stmt.type === "ExportNamedDeclaration" && stmt.declaration?.type === "VariableDeclaration" ? stmt.declaration : null;
8128
8790
  const refNames = /* @__PURE__ */ new Set();
8129
8791
  for (const stmt of body) {
@@ -8190,7 +8852,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8190
8852
  const refEntry = scope.get(varName);
8191
8853
  const refCombi = refEntry?.combi ?? null;
8192
8854
  if (refCombi) {
8193
- const compiled2 = compile(refCombi, void 0, { recovery });
8855
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
8194
8856
  if (compiled2.inlineExpression === null) {
8195
8857
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
8196
8858
  continue;
@@ -8204,7 +8866,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8204
8866
  if (!compiledRules) continue;
8205
8867
  localRuleMaps.set(varName, compiledRules.ruleMap);
8206
8868
  if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
8207
- let replacement = compiledRules.replacement;
8869
+ let replacement = withCoverageDefinitions(
8870
+ compiledRules.replacement,
8871
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
8872
+ );
8208
8873
  if (exportPrefix) {
8209
8874
  const ns = nsFor(varName);
8210
8875
  const pieces = compileLinkable([...compiledRules.ruleMap], ns, { recovery });
@@ -8225,7 +8890,15 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8225
8890
  localComposedCarried.set(varName, fused.carried);
8226
8891
  if (fused.trivia) localComposedTrivia.set(varName, fused.trivia);
8227
8892
  const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
8228
- replacements.push({ start: init.start, end: init.end, replacement });
8893
+ replacements.push({ start: init.start, end: init.end, replacement: withCoverageDefinitions(replacement, emittedCoverageDefinitions(replacement)) });
8894
+ continue;
8895
+ }
8896
+ if (isComposeLeafCall(init)) {
8897
+ const fused = compileComposeLeafCall(init);
8898
+ if (!fused) {
8899
+ throw new Error(`${id}:${lineOf(init.start)} \u2014 composeLeaf() must macro-fuse; runtime composition is forbidden`);
8900
+ }
8901
+ replacements.push({ start: init.start, end: init.end, replacement: fused.replacement });
8229
8902
  continue;
8230
8903
  }
8231
8904
  const mapFnSources = [];
@@ -8246,7 +8919,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8246
8919
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
8247
8920
  continue;
8248
8921
  }
8249
- const compiled = compile(parser2, void 0, { recovery });
8922
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
8250
8923
  if (compiled.inlineExpression === null) {
8251
8924
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
8252
8925
  continue;