parseman 0.42.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/combinators/node.d.ts +21 -7
  2. package/dist/combinators/node.d.ts.map +1 -1
  3. package/dist/combinators/parser.d.ts +17 -2
  4. package/dist/combinators/parser.d.ts.map +1 -1
  5. package/dist/compiler/codegen.d.ts +4 -0
  6. package/dist/compiler/codegen.d.ts.map +1 -1
  7. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  8. package/dist/compiler/linker.d.ts +6 -1
  9. package/dist/compiler/linker.d.ts.map +1 -1
  10. package/dist/cst/reflection.d.ts +24 -0
  11. package/dist/cst/reflection.d.ts.map +1 -0
  12. package/dist/cst/types.d.ts +2 -0
  13. package/dist/cst/types.d.ts.map +1 -1
  14. package/dist/cst/walk.d.ts +27 -62
  15. package/dist/cst/walk.d.ts.map +1 -1
  16. package/dist/index.cjs +227 -84
  17. package/dist/index.cjs.map +4 -4
  18. package/dist/index.d.ts +4 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +227 -84
  21. package/dist/index.js.map +4 -4
  22. package/dist/language-service/index.cjs +25 -33
  23. package/dist/language-service/index.cjs.map +4 -4
  24. package/dist/language-service/index.d.ts +1 -1
  25. package/dist/language-service/index.d.ts.map +1 -1
  26. package/dist/language-service/index.js +25 -33
  27. package/dist/language-service/index.js.map +4 -4
  28. package/dist/plugin/evaluator.d.ts +1 -0
  29. package/dist/plugin/evaluator.d.ts.map +1 -1
  30. package/dist/plugin/index.cjs +323 -78
  31. package/dist/plugin/index.cjs.map +4 -4
  32. package/dist/plugin/index.d.ts.map +1 -1
  33. package/dist/plugin/index.js +323 -78
  34. package/dist/plugin/index.js.map +4 -4
  35. package/dist/spec/index.cjs +107 -6
  36. package/dist/spec/index.cjs.map +4 -4
  37. package/dist/spec/index.js +107 -6
  38. package/dist/spec/index.js.map +4 -4
  39. package/dist/types.d.ts +2 -1
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/version.d.ts +1 -1
  42. package/package.json +1 -1
@@ -2810,6 +2810,123 @@ function noTrivia(root) {
2810
2810
  return parser({ trivia: null }, root);
2811
2811
  }
2812
2812
 
2813
+ // src/cst/reflection.ts
2814
+ var GRAMMAR_REFLECTION = /* @__PURE__ */ Symbol.for("parseman.grammarReflection");
2815
+ function childrenOf(def) {
2816
+ switch (def.tag) {
2817
+ case "sequence":
2818
+ case "choice":
2819
+ return def.parsers;
2820
+ case "dispatch":
2821
+ return [
2822
+ def.selector,
2823
+ ...def.cases.map((c) => c.parser),
2824
+ ...def.matchers ? def.matchers.map((c) => c.parser) : [],
2825
+ ...def.otherwise ? [def.otherwise] : []
2826
+ ];
2827
+ case "many":
2828
+ case "oneOrMore":
2829
+ case "optional":
2830
+ case "attempt":
2831
+ case "transform":
2832
+ case "trivia":
2833
+ case "token":
2834
+ case "leaf":
2835
+ case "label":
2836
+ case "field":
2837
+ case "not":
2838
+ case "peek":
2839
+ case "node":
2840
+ case "withCtx":
2841
+ case "recover":
2842
+ case "expect":
2843
+ return [def.parser];
2844
+ case "grammar":
2845
+ return def.triviaParser ? [def.parser, def.triviaParser] : [def.parser];
2846
+ case "sepBy":
2847
+ return [def.parser, def.separator];
2848
+ case "skip":
2849
+ return [def.main, def.skipped];
2850
+ case "scanTo":
2851
+ return [def.sentinel, ...def.skip];
2852
+ case "literal":
2853
+ case "regex":
2854
+ case "keywords":
2855
+ case "routed":
2856
+ case "guard":
2857
+ case "lazy":
2858
+ case "unknown":
2859
+ return [];
2860
+ }
2861
+ }
2862
+ function addNode(out, def) {
2863
+ if (def.type === void 0) return;
2864
+ const existing = out.get(def.type);
2865
+ if (existing === void 0) {
2866
+ out.set(def.type, def.tags === void 0 ? [] : [...def.tags]);
2867
+ return;
2868
+ }
2869
+ if (def.tags === void 0) return;
2870
+ for (const tag of def.tags) {
2871
+ if (!existing.includes(tag)) existing.push(tag);
2872
+ }
2873
+ }
2874
+ function collectFromCombinator(parser2, out, seen, followLazy = true) {
2875
+ if (seen.has(parser2)) return;
2876
+ seen.add(parser2);
2877
+ const def = parser2._def;
2878
+ if (def.tag === "lazy") {
2879
+ if (followLazy) {
2880
+ try {
2881
+ collectFromCombinator(def.thunk(), out, seen, followLazy);
2882
+ } catch {
2883
+ }
2884
+ }
2885
+ return;
2886
+ }
2887
+ if (def.tag === "node") addNode(out, def);
2888
+ for (const child of childrenOf(def)) collectFromCombinator(child, out, seen, followLazy);
2889
+ }
2890
+ function collectGrammarReflection(ruleMap, opts) {
2891
+ const byType = /* @__PURE__ */ new Map();
2892
+ const followLazy = opts?.followLazy !== false;
2893
+ for (const [, rule] of ruleMap) {
2894
+ if (!followLazy && rule._def.tag === "lazy") {
2895
+ try {
2896
+ const resolved = rule._def.thunk();
2897
+ if (resolved._def.tag !== "lazy") collectFromCombinator(resolved, byType, /* @__PURE__ */ new Set(), false);
2898
+ } catch {
2899
+ }
2900
+ continue;
2901
+ }
2902
+ collectFromCombinator(rule, byType, /* @__PURE__ */ new Set(), followLazy);
2903
+ }
2904
+ return { nodes: [...byType].map(([type, tags]) => ({ type, tags })) };
2905
+ }
2906
+ function mergeGrammarReflections(reflections) {
2907
+ const byType = /* @__PURE__ */ new Map();
2908
+ for (const reflection of reflections) {
2909
+ for (const node2 of reflection.nodes) {
2910
+ const tags = byType.get(node2.type);
2911
+ if (tags === void 0) {
2912
+ byType.set(node2.type, [...node2.tags]);
2913
+ continue;
2914
+ }
2915
+ for (const tag of node2.tags) {
2916
+ if (!tags.includes(tag)) tags.push(tag);
2917
+ }
2918
+ }
2919
+ }
2920
+ return { nodes: [...byType].map(([type, tags]) => ({ type, tags })) };
2921
+ }
2922
+ function attachGrammarReflection(grammar, reflection) {
2923
+ Object.defineProperty(grammar, GRAMMAR_REFLECTION, { value: reflection, enumerable: false, configurable: true });
2924
+ return grammar;
2925
+ }
2926
+ function grammarReflectionSource(reflection) {
2927
+ return `{ nodes: ${JSON.stringify(reflection.nodes)} }`;
2928
+ }
2929
+
2813
2930
  // src/combinators/parser.ts
2814
2931
  var RULE_ORDER = "__parsemanRuleOrder";
2815
2932
  function tagRule(r, key) {
@@ -2908,6 +3025,7 @@ function rules(a, b) {
2908
3025
  enumerable: false,
2909
3026
  configurable: true
2910
3027
  });
3028
+ attachGrammarReflection(cache, collectGrammarReflection(Object.keys(definitions).map((key) => [key, cache[key]])));
2911
3029
  return cache;
2912
3030
  }
2913
3031
 
@@ -3228,13 +3346,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3228
3346
  const collapse = opts?.collapse === true;
3229
3347
  const captureTrivia = opts?.captureTrivia === true;
3230
3348
  const trailingTrivia = opts?.trailingTrivia === true;
3349
+ const tags = opts?.tags;
3231
3350
  if (unwrap && collapse) {
3232
3351
  throw new Error("node() options cannot set both unwrap and collapse");
3233
3352
  }
3234
3353
  if (project !== void 0 && (unwrap || collapse)) {
3235
3354
  throw new Error("node() options cannot combine project with unwrap or collapse");
3236
3355
  }
3237
- const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {} } : baseDef;
3356
+ const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
3238
3357
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : project === void 0);
3239
3358
  const clonesState = build ? buildReadsState(def) : project === void 0;
3240
3359
  const hasOwnFields = parserHasOwnFields(combinator);
@@ -3280,7 +3399,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
3280
3399
  const cstOutput = cstOutputHost(ctx.build);
3281
3400
  const span = ctx.trackLines ? annotateSpanFromLineContext(r.span, ctx) : r.span;
3282
3401
  const hostState = clonesState ? st : ctx.state !== void 0 ? Object.assign({}, ctx.state) : void 0;
3283
- const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
3402
+ const built = unwrap && children2.length === 1 ? unwrapChild(children2[0]) : collapse && children2.length === 1 ? children2[0] : !build && project === void 0 && ctx.build?._parsemanCstCollapse && children2.length === 1 && rawChildren.length === 1 && ctx.build._parsemanCstCollapse(nodeType, children2[0], children2, rawChildren) ? children2[0] : project !== void 0 ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : projectChild(children2, project, nodeType) : build ? cstOutput && ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, hostState, tags) : build(children2, fields, span, rawChildren, triviaLog, st) : ctx.build ? ctx.build(nodeType, children2, fields, span, rawChildren, triviaLog, st, tags) : { _tag: "node", type: nodeType, span: ctx.trackLines ? { ...span } : { start: r.span.start, end: r.span.end }, state: st ?? null, children: children2 };
3284
3403
  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 };
3285
3404
  if (saved.buf !== void 0 || saved.ch !== void 0) {
3286
3405
  pushCstChild(ctx, built, rawEntry);
@@ -3452,7 +3571,7 @@ function leaf(root, fn) {
3452
3571
  }
3453
3572
 
3454
3573
  // src/version.ts
3455
- var PARSEMAN_VERSION = "0.42.1";
3574
+ var PARSEMAN_VERSION = "0.43.0";
3456
3575
 
3457
3576
  // src/cst/host-mode.ts
3458
3577
  var FUSED_HOST_MODE = /* @__PURE__ */ Symbol.for("parseman.fusedHostMode");
@@ -3518,7 +3637,7 @@ var SUGGESTIONS = {
3518
3637
  function classifyBroadArm(arm, resolve2) {
3519
3638
  const seen = /* @__PURE__ */ new Set();
3520
3639
  const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
3521
- const walk2 = (p) => {
3640
+ const walk = (p) => {
3522
3641
  if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
3523
3642
  seen.add(p);
3524
3643
  const d = p._def;
@@ -3532,7 +3651,7 @@ function classifyBroadArm(arm, resolve2) {
3532
3651
  // peek() CARRIES its body's first-set, so it is only broad when the body is
3533
3652
  // (or the body is nullable) — walk in for the real cause.
3534
3653
  case "peek":
3535
- return walk2(d.parser);
3654
+ return walk(d.parser);
3536
3655
  case "scanTo":
3537
3656
  return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
3538
3657
  // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
@@ -3546,7 +3665,7 @@ function classifyBroadArm(arm, resolve2) {
3546
3665
  case "attempt":
3547
3666
  case "withCtx":
3548
3667
  case "recover":
3549
- return walk2(d.parser);
3668
+ return walk(d.parser);
3550
3669
  case "lazy": {
3551
3670
  const name = p._ruleName;
3552
3671
  let target;
@@ -3558,13 +3677,13 @@ function classifyBroadArm(arm, resolve2) {
3558
3677
  return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
3559
3678
  target = bound;
3560
3679
  }
3561
- const inner = walk2(target);
3680
+ const inner = walk(target);
3562
3681
  if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
3563
3682
  return inner;
3564
3683
  }
3565
3684
  case "optional":
3566
3685
  case "many": {
3567
- const inner = walk2(d.parser);
3686
+ const inner = walk(d.parser);
3568
3687
  return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3569
3688
  }
3570
3689
  case "oneOrMore":
@@ -3577,9 +3696,9 @@ function classifyBroadArm(arm, resolve2) {
3577
3696
  case "node":
3578
3697
  case "grammar":
3579
3698
  case "expect":
3580
- return walk2(d.parser);
3699
+ return walk(d.parser);
3581
3700
  case "skip":
3582
- return walk2(d.main);
3701
+ return walk(d.main);
3583
3702
  case "sequence": {
3584
3703
  let sawNullablePrefix = false;
3585
3704
  for (const t of d.parsers) {
@@ -3588,7 +3707,7 @@ function classifyBroadArm(arm, resolve2) {
3588
3707
  continue;
3589
3708
  }
3590
3709
  if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
3591
- const inner = walk2(t);
3710
+ const inner = walk(t);
3592
3711
  if (sawNullablePrefix)
3593
3712
  return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
3594
3713
  return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
@@ -3603,18 +3722,18 @@ function classifyBroadArm(arm, resolve2) {
3603
3722
  }
3604
3723
  case "choice": {
3605
3724
  for (const a of d.parsers) {
3606
- const r = walk2(a);
3725
+ const r = walk(a);
3607
3726
  if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
3608
3727
  }
3609
3728
  return null;
3610
3729
  }
3611
3730
  case "dispatch":
3612
- return walk2(d.selector);
3731
+ return walk(d.selector);
3613
3732
  default:
3614
3733
  return { cause: "broad-recognizer", detail: `unmodeled construct (${d.tag})` };
3615
3734
  }
3616
3735
  };
3617
- return walk2(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
3736
+ return walk(arm) ?? { cause: "broad-recognizer", detail: "broad (cause not localized)" };
3618
3737
  }
3619
3738
  function peelToLeading(arm) {
3620
3739
  let d = arm._def;
@@ -3816,7 +3935,7 @@ function formatGatingWarnings(report) {
3816
3935
 
3817
3936
  // src/analysis/duplication.ts
3818
3937
  var siteToString = (s) => s.path === "" ? s.rule : `${s.rule} \u203A ${s.path}`;
3819
- function childrenOf(d) {
3938
+ function childrenOf2(d) {
3820
3939
  switch (d.tag) {
3821
3940
  case "sequence":
3822
3941
  case "choice":
@@ -3950,7 +4069,7 @@ function render(p, depth = 3) {
3950
4069
  break;
3951
4070
  }
3952
4071
  if (depth <= 0) return `${d.tag}(\u2026)`;
3953
- const kids = childrenOf(d).map((k) => render(k, depth - 1));
4072
+ const kids = childrenOf2(d).map((k) => render(k, depth - 1));
3954
4073
  switch (d.tag) {
3955
4074
  case "sequence":
3956
4075
  return `sequence(${kids.join(", ")})`;
@@ -4145,7 +4264,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
4145
4264
  if (hit !== void 0) return hit;
4146
4265
  const d = p._def;
4147
4266
  shapeCache.set(p, interner.hole);
4148
- const kids = childrenOf(d);
4267
+ const kids = childrenOf2(d);
4149
4268
  const id = interner.of(`${payloadKey(p, d)}|${kids.map(shapeOf).join(",")}`);
4150
4269
  shapeCache.set(p, id);
4151
4270
  return id;
@@ -4155,7 +4274,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
4155
4274
  if (hit !== void 0) return hit;
4156
4275
  sizeCache.set(p, 1);
4157
4276
  let n = 1;
4158
- for (const k of childrenOf(p._def)) n += sizeOf(k);
4277
+ for (const k of childrenOf2(p._def)) n += sizeOf(k);
4159
4278
  sizeCache.set(p, n);
4160
4279
  return n;
4161
4280
  };
@@ -4168,7 +4287,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
4168
4287
  visited.set(p, { p, d, site: here, shape: shapeOf(p), size: sizeOf(p), enclosingNode, enclosingReducer });
4169
4288
  const inner = d.tag === "node" ? d.type ?? "(anonymous)" : enclosingNode;
4170
4289
  const innerReducer = enclosingReducer || d.tag === "node" && (d.build !== void 0 || d.buildSrc !== void 0) || d.tag === "transform" || d.tag === "leaf";
4171
- const kids = childrenOf(d);
4290
+ const kids = childrenOf2(d);
4172
4291
  kids.forEach((k, i) => {
4173
4292
  const seg = segment(d, i);
4174
4293
  visit(k, here.rule, here.path === "" ? seg : `${here.path} \u203A ${seg}`, inner, innerReducer);
@@ -4228,7 +4347,7 @@ function analyzeDuplicationRules(ruleMap, opts) {
4228
4347
  function markSubtree(p, into) {
4229
4348
  if (into.has(p)) return;
4230
4349
  into.add(p);
4231
- for (const k of childrenOf(p._def)) markSubtree(k, into);
4350
+ for (const k of childrenOf2(p._def)) markSubtree(k, into);
4232
4351
  }
4233
4352
  var stripMembers = (f) => {
4234
4353
  const { _members, ...rest } = f;
@@ -4268,7 +4387,7 @@ function findNearDuplicates(visited, interner, shapeOf, minSize) {
4268
4387
  if (hit !== void 0) return hit;
4269
4388
  holeCache.set(p, []);
4270
4389
  const d = p._def;
4271
- const kids = childrenOf(d);
4390
+ const kids = childrenOf2(d);
4272
4391
  const kidShapes = kids.map(shapeOf);
4273
4392
  const payload = payloadKey(p, d);
4274
4393
  const out2 = [];
@@ -4329,7 +4448,7 @@ function findDivergentSlot(members, shapeOf) {
4329
4448
  let cur = [...members];
4330
4449
  for (let guard2 = 0; guard2 < 64; guard2++) {
4331
4450
  const d0 = cur[0]._def;
4332
- const kidLists = cur.map((c) => childrenOf(c._def));
4451
+ const kidLists = cur.map((c) => childrenOf2(c._def));
4333
4452
  const arity = kidLists[0].length;
4334
4453
  if (kidLists.some((k) => k.length !== arity) || arity === 0) break;
4335
4454
  const diff = [];
@@ -4342,7 +4461,7 @@ function findDivergentSlot(members, shapeOf) {
4342
4461
  path2.push(i);
4343
4462
  defs.push(d0);
4344
4463
  cur = kidLists.map((k) => k[i]);
4345
- const nextArity = childrenOf(cur[0]._def).length;
4464
+ const nextArity = childrenOf2(cur[0]._def).length;
4346
4465
  const samePayload = cur.every((c) => payloadKey(c, c._def) === payloadKey(cur[0], cur[0]._def));
4347
4466
  if (!samePayload || nextArity === 0) break;
4348
4467
  }
@@ -4350,7 +4469,7 @@ function findDivergentSlot(members, shapeOf) {
4350
4469
  let slotSize = 0;
4351
4470
  const sizeOf = (p) => {
4352
4471
  let n = 1;
4353
- for (const k of childrenOf(p._def)) n += sizeOf(k);
4472
+ for (const k of childrenOf2(p._def)) n += sizeOf(k);
4354
4473
  return n;
4355
4474
  };
4356
4475
  for (const c of cur) slotSize = Math.max(slotSize, sizeOf(c));
@@ -4359,7 +4478,7 @@ function findDivergentSlot(members, shapeOf) {
4359
4478
  function renderWithHole(p, path2, depth = 0) {
4360
4479
  if (depth === path2.length) return "\u2039slot\u203A";
4361
4480
  const d = p._def;
4362
- const kids = childrenOf(d);
4481
+ const kids = childrenOf2(d);
4363
4482
  const idx = path2[depth];
4364
4483
  const parts = kids.map((k, i) => i === idx ? renderWithHole(k, path2, depth + 1) : render(k, 2));
4365
4484
  switch (d.tag) {
@@ -4817,7 +4936,7 @@ function capturesPositionally(p, seen = /* @__PURE__ */ new Set()) {
4817
4936
  seen.add(p);
4818
4937
  const d = p._def;
4819
4938
  if (d.tag === "field" || d.tag === "label") return true;
4820
- for (const k of childrenOf(d)) if (capturesPositionally(k, seen)) return true;
4939
+ for (const k of childrenOf2(d)) if (capturesPositionally(k, seen)) return true;
4821
4940
  return false;
4822
4941
  }
4823
4942
  function detectHandRolledSepBy(terms, shapeOf) {
@@ -4923,7 +5042,7 @@ function nestedNodeTypes(root, out, seen, isRoot) {
4923
5042
  }
4924
5043
  return;
4925
5044
  }
4926
- for (const k of childrenOf(d)) nestedNodeTypes(k, out, seen, false);
5045
+ for (const k of childrenOf2(d)) nestedNodeTypes(k, out, seen, false);
4927
5046
  }
4928
5047
  var MAX_LOST_TYPES = 8;
4929
5048
  function findStructureLoss(visited, resolve2) {
@@ -8118,7 +8237,7 @@ function routedNeedsContextBridge(p, ctx, seen = /* @__PURE__ */ new Set()) {
8118
8237
  const d = p._def;
8119
8238
  if (d.tag === "dispatch") return false;
8120
8239
  if (d.tag === "withCtx") return parserUsesRouted2(d.parser);
8121
- return childrenOf2(d).some((child) => routedNeedsContextBridge(child, ctx, seen));
8240
+ return childrenOf3(d).some((child) => routedNeedsContextBridge(child, ctx, seen));
8122
8241
  }
8123
8242
  function armReplayInScope(arm, ctx) {
8124
8243
  let p = arm;
@@ -8881,7 +9000,8 @@ ${i}const ${chV} = ${chAlloc}, ${rawV} = ${profileRecognizer} ? undefined : [],
8881
9000
  const nodeSpanV = ctx.lineTracking ? v(ctx, "_nspan") : nodeSpanExpr;
8882
9001
  if (ctx.lineTracking) stmts.push(`${i}const ${nodeSpanV} = ${nodeSpanExpr}`);
8883
9002
  const buildExpr = structural ? `{ _tag: 'node', type: ${JSON.stringify(def.type)}, span: ${nodeSpanV}, state: ${stV} ?? null, children: ${chV} }` : mkType ? emitInlineMkNodeExpr(mkType, chV, rawV, nodeSpanV, tlV) : def.project !== void 0 ? emitNodeProjectExpr(def.type, def.project, chV) : `${buildRef(ctx)}[${buildIdx}](${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV})`;
8884
- const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV})`;
9003
+ const hostBuildArgs = `${JSON.stringify(def.type)}, ${chV}, ${fObj}, ${nodeSpanV}, ${rawV}, ${tlV}, ${stV}${def.tags !== void 0 && def.tags.length > 0 ? `, ${JSON.stringify(def.tags)}` : ""}`;
9004
+ const hostBuildExpr = `_ctx._pmProfile?.phase === 'host' && _ctx._pmProfile.hostCalls++, _ctx.build(${hostBuildArgs})`;
8885
9005
  const ndExpr = structural ? `_ctx.build !== undefined ? (${hostBuildExpr}) : (${buildExpr})` : cstOut ? hostBuildExpr : buildExpr;
8886
9006
  const hostCollapseExpr = structural ? `_ctx.build !== undefined && _ctx.build._parsemanCstCollapse !== undefined && ${chV}.length === 1 && ${rawV}.length === 1 && _ctx.build._parsemanCstCollapse(${JSON.stringify(def.type)}, ${chV}[0], ${chV}, ${rawV}) ? ${chV}[0] : (${ndExpr})` : ndExpr;
8887
9007
  const unwrapExpr = `${chV}.length === 1 ? (${chV}[0] !== null && typeof ${chV}[0] === 'object' && ${chV}[0]._tag === 'leaf' ? ${chV}[0].value : ${chV}[0]) : (${ndExpr})`;
@@ -9344,7 +9464,7 @@ function hasLineTrackingDef(p, seen = /* @__PURE__ */ new Set()) {
9344
9464
  return false;
9345
9465
  }
9346
9466
  }
9347
- return childrenOf2(d).some((child) => hasLineTrackingDef(child, seen));
9467
+ return childrenOf3(d).some((child) => hasLineTrackingDef(child, seen));
9348
9468
  }
9349
9469
  function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9350
9470
  if (seen.has(p)) return false;
@@ -9363,7 +9483,7 @@ function parserUsesRouted2(p, seen = /* @__PURE__ */ new Set()) {
9363
9483
  case "dispatch":
9364
9484
  return false;
9365
9485
  default:
9366
- return childrenOf2(d).some((child) => parserUsesRouted2(child, seen));
9486
+ return childrenOf3(d).some((child) => parserUsesRouted2(child, seen));
9367
9487
  }
9368
9488
  }
9369
9489
  function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
@@ -9378,9 +9498,9 @@ function hasDirectBuildDef(p, seen = /* @__PURE__ */ new Set()) {
9378
9498
  return false;
9379
9499
  }
9380
9500
  }
9381
- return childrenOf2(d).some((child) => hasDirectBuildDef(child, seen));
9501
+ return childrenOf3(d).some((child) => hasDirectBuildDef(child, seen));
9382
9502
  }
9383
- function childrenOf2(def) {
9503
+ function childrenOf3(def) {
9384
9504
  switch (def.tag) {
9385
9505
  case "sequence":
9386
9506
  case "choice":
@@ -9455,7 +9575,7 @@ function subtreeSizes(roots) {
9455
9575
  const def = p._def;
9456
9576
  let s = 1;
9457
9577
  if (def.tag !== "lazy") {
9458
- for (const child of childrenOf2(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
9578
+ for (const child of childrenOf3(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
9459
9579
  }
9460
9580
  sizes.set(p, s);
9461
9581
  return s;
@@ -9476,7 +9596,7 @@ function subtreeSizes(roots) {
9476
9596
  return;
9477
9597
  }
9478
9598
  sz(p);
9479
- for (const child of childrenOf2(def)) visit(child);
9599
+ for (const child of childrenOf3(def)) visit(child);
9480
9600
  };
9481
9601
  for (const root of roots) visit(root);
9482
9602
  return sizes;
@@ -9486,7 +9606,7 @@ function analyzeLazyUsageMulti(roots) {
9486
9606
  const recursive = /* @__PURE__ */ new Set();
9487
9607
  const descended = /* @__PURE__ */ new Set();
9488
9608
  const active = /* @__PURE__ */ new Set();
9489
- function walk2(p) {
9609
+ function walk(p) {
9490
9610
  const def = p._def;
9491
9611
  if (def.tag === "lazy") {
9492
9612
  counts.set(p, (counts.get(p) ?? 0) + 1);
@@ -9503,17 +9623,17 @@ function analyzeLazyUsageMulti(roots) {
9503
9623
  return;
9504
9624
  }
9505
9625
  active.add(p);
9506
- walk2(resolved);
9626
+ walk(resolved);
9507
9627
  active.delete(p);
9508
9628
  return;
9509
9629
  }
9510
9630
  if (isHoistableTag(def.tag)) {
9511
9631
  counts.set(p, (counts.get(p) ?? 0) + 1);
9512
9632
  }
9513
- for (const child of childrenOf2(def)) walk2(child);
9633
+ for (const child of childrenOf3(def)) walk(child);
9514
9634
  }
9515
9635
  const rootsArr = [...roots];
9516
- for (const root of rootsArr) walk2(root);
9636
+ for (const root of rootsArr) walk(root);
9517
9637
  return { counts, recursive, sizes: subtreeSizes(rootsArr) };
9518
9638
  }
9519
9639
  function ruleDependencies(ruleMap) {
@@ -9523,7 +9643,7 @@ function ruleDependencies(ruleMap) {
9523
9643
  for (const [name, comb] of ruleMap) {
9524
9644
  const found = /* @__PURE__ */ new Set();
9525
9645
  const seen = /* @__PURE__ */ new Set();
9526
- const walk2 = (p, isRoot) => {
9646
+ const walk = (p, isRoot) => {
9527
9647
  const def = p._def;
9528
9648
  const boundary = def.tag === "lazy" ? nameOf.get(p) ?? p._ruleName : nameOf.get(p);
9529
9649
  if (!isRoot && boundary !== void 0) {
@@ -9539,12 +9659,12 @@ function ruleDependencies(ruleMap) {
9539
9659
  } catch {
9540
9660
  return;
9541
9661
  }
9542
- walk2(resolved, false);
9662
+ walk(resolved, false);
9543
9663
  return;
9544
9664
  }
9545
- for (const child of childrenOf2(def)) walk2(child, false);
9665
+ for (const child of childrenOf3(def)) walk(child, false);
9546
9666
  };
9547
- walk2(comb, true);
9667
+ walk(comb, true);
9548
9668
  deps.set(name, [...found]);
9549
9669
  }
9550
9670
  return deps;
@@ -10009,6 +10129,7 @@ function compileRuleMap(ruleMap, opts) {
10009
10129
  // host check passes vacuously — see `withHostMode` in the plugin.
10010
10130
  hostMode: ctx.hostMode ?? "ast",
10011
10131
  hostBranchElided: !!ctx.hostBranchElided,
10132
+ reflection: collectGrammarReflection(ruleMap),
10012
10133
  ...ctx.coverage === void 0 ? {} : { coverageDefinitions: ctx.coverage.plan.definitions }
10013
10134
  };
10014
10135
  }
@@ -10031,6 +10152,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10031
10152
  return false;
10032
10153
  }
10033
10154
  });
10155
+ const nodeMeta = new Map(ruleMap.map(([name, rule]) => [name, collectGrammarReflection([[name, rule]], { followLazy: false })]));
10034
10156
  const ctx = {
10035
10157
  vars: 0,
10036
10158
  indent: 1,
@@ -10106,7 +10228,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10106
10228
  scanExternal(resolved);
10107
10229
  return;
10108
10230
  }
10109
- for (const child of childrenOf2(def)) scanExternal(child);
10231
+ for (const child of childrenOf3(def)) scanExternal(child);
10110
10232
  };
10111
10233
  for (const [, rule] of ruleMap) scanExternal(rule);
10112
10234
  }
@@ -10228,6 +10350,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
10228
10350
  hostBranchElided: !!ctx.hostBranchElided,
10229
10351
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
10230
10352
  isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
10353
+ nodeMeta,
10231
10354
  mfFns: mfSrcs ? [] : ctx.mapFns,
10232
10355
  buildFns: buildSrcs ? [] : ctx.buildFns
10233
10356
  };
@@ -10248,7 +10371,7 @@ function hasExternalRuleRef(ruleMap) {
10248
10371
  if (resolved === void 0) return p._ruleName !== void 0;
10249
10372
  return visit(resolved);
10250
10373
  }
10251
- return childrenOf2(def).some(visit);
10374
+ return childrenOf3(def).some(visit);
10252
10375
  };
10253
10376
  return ruleMap.some(([, rule]) => visit(rule));
10254
10377
  }
@@ -10270,7 +10393,7 @@ function hasSemanticReduction(roots, externalRefs) {
10270
10393
  return true;
10271
10394
  }
10272
10395
  }
10273
- return childrenOf2(def).some(visit);
10396
+ return childrenOf3(def).some(visit);
10274
10397
  };
10275
10398
  return roots.some(visit);
10276
10399
  }
@@ -10343,7 +10466,7 @@ function withCtx(extra, combinator) {
10343
10466
  }
10344
10467
 
10345
10468
  // src/compiler/ir-serialize.ts
10346
- function childrenOf3(def) {
10469
+ function childrenOf4(def) {
10347
10470
  switch (def.tag) {
10348
10471
  case "sequence":
10349
10472
  case "choice":
@@ -10407,6 +10530,10 @@ function projectOpt(project) {
10407
10530
  if (project === void 0) return void 0;
10408
10531
  return `project: ${project}`;
10409
10532
  }
10533
+ function tagsOpt(tags) {
10534
+ if (tags === void 0 || tags.length === 0) return void 0;
10535
+ return `tags: ${JSON.stringify(tags)}`;
10536
+ }
10410
10537
  function matcherExpr(entry) {
10411
10538
  switch (entry.kind) {
10412
10539
  case "startsWith":
@@ -10620,7 +10747,7 @@ var Serializer = class {
10620
10747
  if (target) this.assertNoRuleRef(target, seen);
10621
10748
  return;
10622
10749
  }
10623
- for (const child of childrenOf3(c._def)) this.assertNoRuleRef(child, seen);
10750
+ for (const child of childrenOf4(c._def)) this.assertNoRuleRef(child, seen);
10624
10751
  }
10625
10752
  /** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
10626
10753
  * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
@@ -10684,7 +10811,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10684
10811
  this.counts.set(c, (this.counts.get(c) ?? 0) + 1);
10685
10812
  if ((this.counts.get(c) ?? 0) > 1) return;
10686
10813
  active.add(c);
10687
- for (const child of childrenOf3(def)) this.analyze(child, active);
10814
+ for (const child of childrenOf4(def)) this.analyze(child, active);
10688
10815
  active.delete(c);
10689
10816
  }
10690
10817
  /** Reference `c`: a const var if it has one (emitting its decl first), else inline.
@@ -10801,12 +10928,14 @@ ${outerDecls.map((d) => " " + d).join("\n")}
10801
10928
  case "node": {
10802
10929
  if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
10803
10930
  const projectEntry = projectOpt(def.project);
10931
+ const tagEntry = tagsOpt(def.tags);
10804
10932
  const optEntries = [
10805
10933
  ...def.unwrap ? ["unwrap: true"] : [],
10806
10934
  ...def.collapse ? ["collapse: true"] : [],
10807
10935
  ...projectEntry === void 0 ? [] : [projectEntry],
10808
10936
  ...def.captureTrivia ? ["captureTrivia: true"] : [],
10809
- ...def.trailingTrivia ? ["trailingTrivia: true"] : []
10937
+ ...def.trailingTrivia ? ["trailingTrivia: true"] : [],
10938
+ ...tagEntry === void 0 ? [] : [tagEntry]
10810
10939
  ];
10811
10940
  const opts = optEntries.length > 0 ? `, { ${optEntries.join(", ")} }` : "";
10812
10941
  if (def.type === void 0) {
@@ -10844,15 +10973,16 @@ function normalizeCstCollapse(collapse) {
10844
10973
  }
10845
10974
  return typeof collapse === "function" ? collapse : void 0;
10846
10975
  }
10847
- function buildCstNode(type, children2, _fields, span, _rawChildren, _triviaLog, state) {
10848
- return { _tag: "node", type, span: { ...span }, state: state ?? null, children: [...children2] };
10976
+ function buildCstNode(type, children2, _fields, span, _rawChildren, _triviaLog, state, tags) {
10977
+ return tags !== void 0 && tags.length > 0 ? { _tag: "node", type, tags, span: { ...span }, state: state ?? null, children: [...children2] } : { _tag: "node", type, span: { ...span }, state: state ?? null, children: [...children2] };
10849
10978
  }
10850
- function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triviaLog, state) {
10979
+ function cstBuildHost(typeOrOptions, children2, _fields, span, rawChildren, triviaLog, state, _tags) {
10851
10980
  if (typeof typeOrOptions === "string") {
10852
10981
  return buildCstNode(typeOrOptions, children2 ?? [], _fields, span ?? { start: 0, end: 0 }, rawChildren, triviaLog, state);
10853
10982
  }
10854
10983
  const collapse = normalizeCstCollapse(typeOrOptions?.collapse);
10855
- const host = (type, children3, fields, span2, rawChildren2, triviaLog2, state2) => buildCstNode(type, children3 ?? [], fields, span2, rawChildren2, triviaLog2, state2);
10984
+ const materializeTags = typeOrOptions?.tags === true;
10985
+ const host = (type, children3, fields, span2, rawChildren2, triviaLog2, state2, tags) => buildCstNode(type, children3 ?? [], fields, span2, rawChildren2, triviaLog2, state2, materializeTags ? tags : void 0);
10856
10986
  host._parsemanCstOutput = true;
10857
10987
  if (collapse) host._parsemanCstCollapse = collapse;
10858
10988
  return host;
@@ -10873,7 +11003,7 @@ function pickPieces(pieces, names) {
10873
11003
  };
10874
11004
  for (const n of names) visit(n);
10875
11005
  const filt = (m, pc) => new Map([...m].filter(([k]) => keep.has(k) && winner.get(k) === pc));
10876
- return pieces.map((pc) => ({ ...pc, keys: pc.keys.filter((k) => keep.has(k) && winner.get(k) === pc), ruleFns: filt(pc.ruleFns, pc), wrappers: filt(pc.wrappers, pc), deps: filt(pc.deps, pc) })).filter((pc) => pc.keys.length > 0);
11006
+ return pieces.map((pc) => ({ ...pc, keys: pc.keys.filter((k) => keep.has(k) && winner.get(k) === pc), ruleFns: filt(pc.ruleFns, pc), wrappers: filt(pc.wrappers, pc), deps: filt(pc.deps, pc), nodeMeta: filt(pc.nodeMeta, pc) })).filter((pc) => pc.keys.length > 0);
10877
11007
  }
10878
11008
  function fusedBody(pieces) {
10879
11009
  for (const p of pieces) {
@@ -10911,6 +11041,7 @@ function fusedBody(pieces) {
10911
11041
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
10912
11042
  ];
10913
11043
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
11044
+ const reflection = mergeGrammarReflections([...winner].map(([k, p]) => p.nodeMeta.get(k) ?? { nodes: [] }));
10914
11045
  const { mode, elided } = hostModeOfPieces(pieces);
10915
11046
  const rawBody = [
10916
11047
  ...lines,
@@ -10919,6 +11050,7 @@ function fusedBody(pieces) {
10919
11050
  "}",
10920
11051
  `Object.defineProperty(_map, Symbol.for(${JSON.stringify(FUSED_HOST_MODE.description)}), { value: ${JSON.stringify(mode)}, enumerable: false })`,
10921
11052
  `Object.defineProperty(_map, Symbol.for(${JSON.stringify(FUSED_HOST_ELIDED.description)}), { value: ${JSON.stringify(elided)}, enumerable: false })`,
11053
+ `Object.defineProperty(_map, Symbol.for('parseman.grammarReflection'), { value: ${grammarReflectionSource(reflection)}, enumerable: false })`,
10922
11054
  // …and on each rule FUNCTION, because `run(map.Rule, …)` is handed the rule, not the
10923
11055
  // map, and would otherwise have nothing to check against. Done once at fuse time,
10924
11056
  // before any rule has been called, so it costs nothing per parse.
@@ -11209,19 +11341,19 @@ function directBuilderUnsupportedBindings(src) {
11209
11341
  // src/plugin/evaluator.ts
11210
11342
  function stripTsFromSource(node2, code) {
11211
11343
  const cuts = [];
11212
- const walk2 = (n) => {
11344
+ const walk = (n) => {
11213
11345
  if (!n || typeof n !== "object") return;
11214
11346
  const rec = n;
11215
11347
  if (typeof rec.type === "string" && rec.type.startsWith("TS") && typeof rec.start === "number" && typeof rec.end === "number") {
11216
11348
  const ex = rec.expression;
11217
11349
  if ((rec.type === "TSAsExpression" || rec.type === "TSSatisfiesExpression" || rec.type === "TSNonNullExpression" || rec.type === "TSInstantiationExpression") && ex && typeof ex.end === "number") {
11218
11350
  cuts.push([ex.end, rec.end]);
11219
- walk2(rec.expression);
11351
+ walk(rec.expression);
11220
11352
  return;
11221
11353
  }
11222
11354
  if (rec.type === "TSTypeAssertion" && ex && typeof ex.start === "number") {
11223
11355
  cuts.push([rec.start, ex.start]);
11224
- walk2(rec.expression);
11356
+ walk(rec.expression);
11225
11357
  return;
11226
11358
  }
11227
11359
  cuts.push([rec.start, rec.end]);
@@ -11231,11 +11363,11 @@ function stripTsFromSource(node2, code) {
11231
11363
  if (key === "type" || key === "start" || key === "end") continue;
11232
11364
  const v2 = rec[key];
11233
11365
  if (Array.isArray(v2)) {
11234
- for (const item of v2) walk2(item);
11235
- } else if (v2 && typeof v2 === "object") walk2(v2);
11366
+ for (const item of v2) walk(item);
11367
+ } else if (v2 && typeof v2 === "object") walk(v2);
11236
11368
  }
11237
11369
  };
11238
- walk2(node2);
11370
+ walk(node2);
11239
11371
  const start = node2.start;
11240
11372
  const end = node2.end;
11241
11373
  if (cuts.length === 0) return code.slice(start, end);
@@ -11414,10 +11546,38 @@ function scopeGet(scope, name, mfs) {
11414
11546
  if (isCombinator(entry)) return entry;
11415
11547
  return null;
11416
11548
  }
11549
+ function unwrapStaticExpr(expr) {
11550
+ let cur = expr;
11551
+ while (cur.type === "TSAsExpression" || cur.type === "TSSatisfiesExpression" || cur.type === "TSNonNullExpression" || cur.type === "TSTypeAssertion" || cur.type === "TSInstantiationExpression" || cur.type === "ParenthesizedExpression") {
11552
+ if (!cur.expression) break;
11553
+ cur = cur.expression;
11554
+ }
11555
+ return cur;
11556
+ }
11417
11557
  function staticLiteralValue(expr) {
11418
- const val = expr;
11558
+ const val = unwrapStaticExpr(expr);
11419
11559
  return val.type === "Literal" || val.type === "BooleanLiteral" || val.type === "NumericLiteral" ? val.value : void 0;
11420
11560
  }
11561
+ function staticStringArray(expr, scope) {
11562
+ const id = unwrapStaticExpr(expr);
11563
+ if (id.type === "Identifier" && scope !== void 0 && id.name !== void 0) {
11564
+ const scoped = scope.get(id.name);
11565
+ const value = isStaticValueEntry(scoped) ? scoped.value : scoped;
11566
+ return Array.isArray(value) && value.every((v2) => typeof v2 === "string") ? value : void 0;
11567
+ }
11568
+ const arr = id;
11569
+ if (arr.type !== "ArrayExpression" || !Array.isArray(arr.elements)) return void 0;
11570
+ const out = [];
11571
+ for (const el of arr.elements) {
11572
+ if (!el || el.type === "SpreadElement") return void 0;
11573
+ const value = staticLiteralValue(el);
11574
+ if (typeof value !== "string") return void 0;
11575
+ out.push(value);
11576
+ }
11577
+ return out;
11578
+ }
11579
+ var STATIC_NODE_OPTIONS_FAILED = /* @__PURE__ */ Symbol("parseman.staticNodeOptions.failed");
11580
+ var STATIC_NODE_OPTIONS_NOT_OPTIONS = /* @__PURE__ */ Symbol("parseman.staticNodeOptions.notOptions");
11421
11581
  function staticNodeProject(expr) {
11422
11582
  const literalValue = staticLiteralValue(expr);
11423
11583
  if (typeof literalValue === "number" && Number.isInteger(literalValue) && literalValue >= 0) {
@@ -11426,23 +11586,60 @@ function staticNodeProject(expr) {
11426
11586
  if (typeof literalValue === "number" || literalValue !== void 0) return { ok: false };
11427
11587
  return { ok: false };
11428
11588
  }
11429
- function staticNodeOptions(expr) {
11430
- if (expr.type !== "ObjectExpression") return void 0;
11589
+ function scopedStaticValue(expr, scope) {
11590
+ const unwrapped = unwrapStaticExpr(expr);
11591
+ if (unwrapped.type !== "Identifier" || unwrapped.name === "undefined") return { found: false };
11592
+ if (!scope.has(unwrapped.name)) return { found: false };
11593
+ const scoped = scope.get(unwrapped.name);
11594
+ const value = isStaticValueEntry(scoped) ? scoped.value : scoped;
11595
+ return { found: true, value };
11596
+ }
11597
+ function staticNodeOptionsFromValue(value) {
11598
+ if (value === null || typeof value !== "object" || Array.isArray(value) || isCombinator(value)) return STATIC_NODE_OPTIONS_NOT_OPTIONS;
11599
+ const opts = {};
11600
+ const rec = value;
11601
+ for (const name of Object.keys(rec)) {
11602
+ const v2 = rec[name];
11603
+ if (name === "unwrap" || name === "collapse" || name === "captureTrivia" || name === "trailingTrivia") {
11604
+ if (v2 === true) opts[name] = true;
11605
+ else if (v2 !== false && v2 !== void 0) return STATIC_NODE_OPTIONS_FAILED;
11606
+ } else if (name === "project") {
11607
+ if (typeof v2 !== "number" || !Number.isInteger(v2) || v2 < 0) return STATIC_NODE_OPTIONS_FAILED;
11608
+ opts.project = v2;
11609
+ } else if (name === "tags") {
11610
+ if (!Array.isArray(v2) || !v2.every((item) => typeof item === "string")) return STATIC_NODE_OPTIONS_FAILED;
11611
+ opts.tags = v2;
11612
+ }
11613
+ }
11614
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11615
+ }
11616
+ function staticNodeOptions(expr, scope) {
11617
+ const unwrapped = unwrapStaticExpr(expr);
11618
+ const scoped = scopedStaticValue(unwrapped, scope);
11619
+ if (scoped.found) return staticNodeOptionsFromValue(scoped.value);
11620
+ if (unwrapped.type !== "ObjectExpression") return STATIC_NODE_OPTIONS_NOT_OPTIONS;
11431
11621
  const opts = {};
11432
- for (const prop of expr.properties) {
11622
+ for (const prop of unwrapped.properties) {
11623
+ if (prop.type !== "Property") return STATIC_NODE_OPTIONS_FAILED;
11433
11624
  const p = prop;
11434
- if (p.computed) continue;
11625
+ if (p.computed) return STATIC_NODE_OPTIONS_FAILED;
11435
11626
  const key = p.key;
11436
11627
  const name = key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
11437
11628
  if (name === "unwrap" || name === "collapse" || name === "captureTrivia" || name === "trailingTrivia") {
11438
- if (staticLiteralValue(p.value) === true) opts[name] = true;
11629
+ const value = staticLiteralValue(p.value);
11630
+ if (value === true) opts[name] = true;
11631
+ else if (value !== false && value !== void 0) return STATIC_NODE_OPTIONS_FAILED;
11439
11632
  } else if (name === "project") {
11440
11633
  const project = staticNodeProject(p.value);
11441
- if (project?.ok === false) return { project: -1 };
11634
+ if (project?.ok === false) return STATIC_NODE_OPTIONS_FAILED;
11442
11635
  if (project !== void 0) opts.project = project.value;
11636
+ } else if (name === "tags") {
11637
+ const tags = staticStringArray(p.value, scope);
11638
+ if (tags === void 0) return STATIC_NODE_OPTIONS_FAILED;
11639
+ opts.tags = tags;
11443
11640
  }
11444
11641
  }
11445
- return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia ? opts : void 0;
11642
+ return opts.unwrap || opts.collapse || opts.project !== void 0 || opts.captureTrivia || opts.trailingTrivia || opts.tags !== void 0 ? opts : void 0;
11446
11643
  }
11447
11644
  function exprToCombi(node2, scope, code, mfs) {
11448
11645
  if (node2.type === "Identifier") return scopeGet(scope, node2.name, mfs);
@@ -11517,10 +11714,23 @@ function exprToCombi(node2, scope, code, mfs) {
11517
11714
  const inner = anyValue(parserArg, scope, code, mfs);
11518
11715
  if (!isCombinator(inner)) return null;
11519
11716
  const be = buildArg;
11520
- const hasBuild = be !== void 0 && be.type !== "SpreadElement" && be.type !== "ObjectExpression" && !(be.type === "Identifier" && be.name === "undefined");
11717
+ const buildExpr = be === void 0 || be.type === "SpreadElement" ? void 0 : unwrapStaticExpr(be);
11718
+ const buildArgOptions = be !== void 0 && be.type !== "SpreadElement" ? staticNodeOptions(buildArg, scope) : STATIC_NODE_OPTIONS_NOT_OPTIONS;
11719
+ if (buildArgOptions === STATIC_NODE_OPTIONS_FAILED) return null;
11720
+ const buildArgIsOptions = buildArgOptions !== STATIC_NODE_OPTIONS_NOT_OPTIONS;
11721
+ const scopedBuild = buildExpr === void 0 ? { found: false } : scopedStaticValue(buildExpr, scope);
11722
+ const absentBuild = buildExpr?.type === "Identifier" && buildExpr.name === "undefined" || buildExpr !== void 0 && staticLiteralValue(buildExpr) === null || scopedBuild.found && (scopedBuild.value === void 0 || scopedBuild.value === null);
11723
+ const hasBuild = be !== void 0 && be.type !== "SpreadElement" && !buildArgIsOptions && !absentBuild;
11521
11724
  const buildSrc = hasBuild ? stripTsFromSource(be, code) : void 0;
11522
- const optionsArg = hasBuild ? optsArg : be?.type === "ObjectExpression" ? buildArg : optsArg;
11523
- const opts = optionsArg !== void 0 && optionsArg.type !== "SpreadElement" ? staticNodeOptions(optionsArg) : void 0;
11725
+ let opts;
11726
+ if (buildArgIsOptions) {
11727
+ opts = buildArgOptions;
11728
+ } else if (optsArg !== void 0) {
11729
+ if (optsArg.type === "SpreadElement") return null;
11730
+ const optsResult = staticNodeOptions(optsArg, scope);
11731
+ if (optsResult === STATIC_NODE_OPTIONS_FAILED || optsResult === STATIC_NODE_OPTIONS_NOT_OPTIONS) return null;
11732
+ opts = optsResult;
11733
+ }
11524
11734
  try {
11525
11735
  const combi = explicitType !== void 0 ? node(explicitType, inner, hasBuild ? () => null : void 0, opts) : node(inner, hasBuild ? () => null : void 0, opts);
11526
11736
  if (combi._def.tag === "node" && buildSrc !== void 0) {
@@ -11743,6 +11953,10 @@ function exprToCombi(node2, scope, code, mfs) {
11743
11953
  }
11744
11954
  }
11745
11955
  function anyValue(node2, scope, code, mfs) {
11956
+ if (node2.type === "TSAsExpression" || node2.type === "TSSatisfiesExpression" || node2.type === "TSNonNullExpression" || node2.type === "TSTypeAssertion" || node2.type === "TSInstantiationExpression" || node2.type === "ParenthesizedExpression") {
11957
+ const inner = node2.expression;
11958
+ return inner ? anyValue(inner, scope, code, mfs) : null;
11959
+ }
11746
11960
  if (node2.type === "Literal") {
11747
11961
  if ("regex" in node2 && node2.regex !== null && node2.regex !== void 0) {
11748
11962
  return new RegExp(node2.regex.pattern, node2.regex.flags);
@@ -11866,6 +12080,9 @@ function collectRuleKeys(retObj) {
11866
12080
  }
11867
12081
  return out;
11868
12082
  }
12083
+ function evaluateStaticValue(node2, scope, code) {
12084
+ return anyValue(node2, scope, code, []);
12085
+ }
11869
12086
  function flattenRuleEntries(retObj, scope, code) {
11870
12087
  const out = [];
11871
12088
  for (const prop of retObj.properties) {
@@ -12031,6 +12248,18 @@ function referencesAny(node2, names, scope) {
12031
12248
  // src/plugin/index.ts
12032
12249
  import { createHash } from "node:crypto";
12033
12250
  var PARSEMAN_MODULE = "parseman";
12251
+ function unwrapStaticExpression(expr) {
12252
+ let cur = expr;
12253
+ while (cur.type === "TSAsExpression" || cur.type === "TSSatisfiesExpression" || cur.type === "TSNonNullExpression" || cur.type === "TSTypeAssertion" || cur.type === "TSInstantiationExpression" || cur.type === "ParenthesizedExpression") {
12254
+ if (!cur.expression) break;
12255
+ cur = cur.expression;
12256
+ }
12257
+ return cur;
12258
+ }
12259
+ function isStaticNullishExpression(expr) {
12260
+ const unwrapped = unwrapStaticExpression(expr);
12261
+ return unwrapped.type === "Identifier" && unwrapped.name === "undefined" || unwrapped.type === "Literal" && unwrapped.value === null;
12262
+ }
12034
12263
  var _compiledResolver = null;
12035
12264
  function getCompiledResolver() {
12036
12265
  return _compiledResolver ??= new ResolverFactory({
@@ -12513,6 +12742,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12513
12742
  ruleMap: evaluated.ruleMap,
12514
12743
  hostMode: compiled?.hostMode ?? "ast",
12515
12744
  hostBranchElided: compiled?.hostBranchElided ?? false,
12745
+ reflection: compiled?.reflection ?? { nodes: [] },
12516
12746
  ...compiled?.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {},
12517
12747
  ...evaluated.trivia ? { trivia: evaluated.trivia } : {},
12518
12748
  ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {},
@@ -12533,7 +12763,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12533
12763
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
12534
12764
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
12535
12765
  ).join(", ")}])`;
12536
- return `{ v: ${JSON.stringify(p.v)}, 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)}, nullable: ${p.nullable ? mapLit(p.nullable) : "new Map()"}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, hostMode: ${JSON.stringify(p.hostMode ?? "ast")}, hostBranchElided: ${p.hostBranchElided === true}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
12766
+ return `{ v: ${JSON.stringify(p.v)}, 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)}, nullable: ${p.nullable ? mapLit(p.nullable) : "new Map()"}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, nodeMeta: ${mapLit(p.nodeMeta)}, hostMode: ${JSON.stringify(p.hostMode ?? "ast")}, hostBranchElided: ${p.hostBranchElided === true}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
12537
12767
  };
12538
12768
  const isSpread = (it) => "__spreadLocal" in it;
12539
12769
  const isIR = (it) => "ir" in it && !("ruleFns" in it);
@@ -12542,6 +12772,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12542
12772
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
12543
12773
  const withLeafMarker = (grammarExpr) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.leafComposed'), { value: true, enumerable: false })`;
12544
12774
  const withHostMode = (grammarExpr, mode, elided) => `/* @__PURE__ */ (m => { for (const k of Object.keys(m)) { Object.defineProperty(m[k], Symbol.for('parseman.fusedHostMode'), { value: ${JSON.stringify(mode)}, enumerable: false }); Object.defineProperty(m[k], Symbol.for('parseman.fusedHostElided'), { value: ${JSON.stringify(elided)}, enumerable: false }) } Object.defineProperty(m, Symbol.for('parseman.fusedHostMode'), { value: ${JSON.stringify(mode)}, enumerable: false }); Object.defineProperty(m, Symbol.for('parseman.fusedHostElided'), { value: ${JSON.stringify(elided)}, enumerable: false }); return m })(${grammarExpr})`;
12775
+ const withGrammarReflection = (grammarExpr, reflection) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.grammarReflection'), { value: ${grammarReflectionSource(reflection)}, enumerable: false })`;
12545
12776
  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 })`;
12546
12777
  const emittedCoverageDefinitions = (source) => {
12547
12778
  const ids = /* @__PURE__ */ new Set();
@@ -12937,7 +13168,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12937
13168
  const kind = vd.kind ?? "const";
12938
13169
  if (d.id.type === "Identifier") {
12939
13170
  const varName = d.id.name;
12940
- if (!referencesAny(init, allNames, scope)) continue;
13171
+ if (!referencesAny(init, allNames, scope)) {
13172
+ const staticValue = evaluateStaticValue(init, scope, code);
13173
+ if (staticValue !== null && staticValue !== void 0 || isStaticNullishExpression(init)) {
13174
+ ;
13175
+ scope.set(varName, staticValue);
13176
+ }
13177
+ continue;
13178
+ }
12941
13179
  if (refNames.has(varName)) {
12942
13180
  const refEntry = scope.get(varName);
12943
13181
  const refCombi = refEntry?.combi ?? null;
@@ -12977,6 +13215,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
12977
13215
  );
12978
13216
  if (compiledRules.replacement !== null) {
12979
13217
  replacement = withHostMode(replacement, compiledRules.hostMode, compiledRules.hostBranchElided);
13218
+ replacement = withGrammarReflection(replacement, compiledRules.reflection);
12980
13219
  }
12981
13220
  if (exportPrefix && pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
12982
13221
  const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
@@ -13033,6 +13272,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13033
13272
  scope.set(varName, combiArray);
13034
13273
  continue;
13035
13274
  }
13275
+ const staticValue = evaluateStaticValue(init, scope, code);
13276
+ if (staticValue !== null && staticValue !== void 0 || isStaticNullishExpression(init)) {
13277
+ ;
13278
+ scope.set(varName, staticValue);
13279
+ continue;
13280
+ }
13036
13281
  if (factoryDecls.has(varName)) continue;
13037
13282
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
13038
13283
  continue;
@@ -13108,7 +13353,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13108
13353
  const replacementRanges = runtimeComposeFallback ? [] : replacements.map(({ start, end }) => ({ start, end }));
13109
13354
  const isInsideReplacement = (start, end) => replacementRanges.some((r) => start >= r.start && end <= r.end);
13110
13355
  const importedBindingStillReferenced = (local) => {
13111
- const walk2 = (node2, parent, parentKey) => {
13356
+ const walk = (node2, parent, parentKey) => {
13112
13357
  if (!node2 || typeof node2 !== "object") return false;
13113
13358
  const rec = node2;
13114
13359
  if (rec.type === "ImportDeclaration") return false;
@@ -13123,14 +13368,14 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
13123
13368
  if (key === "type" || key === "start" || key === "end") continue;
13124
13369
  const value = rec[key];
13125
13370
  if (Array.isArray(value)) {
13126
- if (value.some((child) => walk2(child, rec, key))) return true;
13127
- } else if (walk2(value, rec, key)) {
13371
+ if (value.some((child) => walk(child, rec, key))) return true;
13372
+ } else if (walk(value, rec, key)) {
13128
13373
  return true;
13129
13374
  }
13130
13375
  }
13131
13376
  return false;
13132
13377
  };
13133
- return body.some((stmt) => walk2(stmt));
13378
+ return body.some((stmt) => walk(stmt));
13134
13379
  };
13135
13380
  for (const imp of ordinaryImports) {
13136
13381
  if (!runtimeComposeFallback && imp.specifiers.length > 0 && imp.specifiers.every(