parseman 0.61.0 → 0.61.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.
@@ -53,9 +53,25 @@ export type RebuildResult = {
53
53
  */
54
54
  unapplied: Combinator<unknown>[];
55
55
  };
56
+ export type RebuildOptions = {
57
+ /**
58
+ * Also rebuild `dispatch`, a gated `choice`, `withCtx` and `routed` instead of
59
+ * freezing them. Each is reconstructed from its own def fields — the arms, gates,
60
+ * matchers and `extra` the grammar built it with. Off for `proposeFixes()`, whose
61
+ * reports name the frozen kinds; on for an instrumented rebuild, which must reach
62
+ * every terminal (`backtrack.ts`).
63
+ */
64
+ readonly thaw?: boolean;
65
+ /**
66
+ * Called with each node and what it was rebuilt to (itself, for a frozen or
67
+ * childless one), and its result is used in its place. Not called for a
68
+ * replacement or a `lazy` slot.
69
+ */
70
+ readonly after?: (original: Combinator<unknown>, rebuilt: Combinator<unknown>) => Combinator<unknown>;
71
+ };
56
72
  /**
57
73
  * Rebuild `root`, replacing every combinator that is a key of `replacements` with its
58
74
  * value. The replacement is spliced WITHOUT descending into the original target.
59
75
  */
60
- export declare function rebuildCombinator(root: Combinator<unknown>, replacements: ReadonlyMap<Combinator<unknown>, Combinator<unknown>>): RebuildResult;
76
+ export declare function rebuildCombinator(root: Combinator<unknown>, replacements: ReadonlyMap<Combinator<unknown>, Combinator<unknown>>, options?: RebuildOptions): RebuildResult;
61
77
  //# sourceMappingURL=rebuild.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../../src/analysis/rebuild.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,aAAa,CAAA;AAuBxD,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IACzB,wEAAwE;IACxE,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB;;;OAGG;IACH,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAA;CACjC,CAAA;AA0CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,EACzB,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,GAClE,aAAa,CAiKf"}
1
+ {"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../../src/analysis/rebuild.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,aAAa,CAAA;AA4BxD,6DAA6D;AAC7D,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;IACzB,wEAAwE;IACxE,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB;;;OAGG;IACH,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAA;CACjC,CAAA;AA0CD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,KAAK,UAAU,CAAC,OAAO,CAAC,CAAA;CACtG,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,EACzB,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,EACnE,OAAO,GAAE,cAAmB,GAC3B,aAAa,CA8Lf"}
package/dist/cli/index.js CHANGED
@@ -2552,6 +2552,206 @@ function asciiFoldKey(key) {
2552
2552
  }
2553
2553
  return out;
2554
2554
  }
2555
+ function matchesDispatchMatcher(value, matcher) {
2556
+ const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
2557
+ const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
2558
+ switch (matcher.kind) {
2559
+ case "startsWith":
2560
+ return candidate.startsWith(expected);
2561
+ case "endsWith":
2562
+ return candidate.endsWith(expected);
2563
+ case "matches": {
2564
+ const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
2565
+ return new RegExp(matcher.value, flags).test(value);
2566
+ }
2567
+ }
2568
+ }
2569
+ function isStringMatcher(value) {
2570
+ if (typeof value !== "object" || value === null) return false;
2571
+ const kind = value.kind;
2572
+ return kind === "startsWith" || kind === "endsWith" || kind === "matches";
2573
+ }
2574
+ function fixedMatcher(kind, value) {
2575
+ if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
2576
+ if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
2577
+ return { kind, value };
2578
+ }
2579
+ function startsWith(prefix) {
2580
+ return fixedMatcher("startsWith", prefix);
2581
+ }
2582
+ function endsWith(suffix) {
2583
+ return fixedMatcher("endsWith", suffix);
2584
+ }
2585
+ function matches(pattern) {
2586
+ if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
2587
+ if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
2588
+ throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
2589
+ }
2590
+ return { kind: "matches", value: pattern.source, flags: pattern.flags };
2591
+ }
2592
+ function dispatchMatcher(entry) {
2593
+ switch (entry.kind) {
2594
+ case "startsWith":
2595
+ return startsWith(entry.value);
2596
+ case "endsWith":
2597
+ return endsWith(entry.value);
2598
+ case "matches":
2599
+ return matches(new RegExp(entry.value, entry.flags));
2600
+ }
2601
+ }
2602
+ function when(key, parser2, opts = {}) {
2603
+ const usesRouted = parserUsesRouted(parser2);
2604
+ if (isStringMatcher(key)) {
2605
+ return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2606
+ }
2607
+ const keys = Array.isArray(key) ? [...key] : [key];
2608
+ if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
2609
+ for (const item of keys) {
2610
+ if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
2611
+ }
2612
+ return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
2613
+ }
2614
+ function otherwise(parser2, opts = {}) {
2615
+ const usesRouted = parserUsesRouted(parser2);
2616
+ return opts.commit === false ? { kind: "otherwise", parser: parser2, usesRouted, commit: false } : { kind: "otherwise", parser: parser2, usesRouted };
2617
+ }
2618
+ function routed(fallback) {
2619
+ return {
2620
+ _tag: "routed",
2621
+ _meta: {
2622
+ // The routed token is whatever the selector matched, so the first set stays
2623
+ // `any` even with a fallback — `any` already subsumes the fallback's.
2624
+ firstSet: { kind: "any" },
2625
+ canMatchNewline: fallback?._meta.canMatchNewline ?? false,
2626
+ isTrivia: false
2627
+ },
2628
+ _def: fallback === void 0 ? { tag: "routed" } : { tag: "routed", fallback },
2629
+ parse(_input, pos, ctx) {
2630
+ const item = ctx._routed;
2631
+ if (item === void 0 || pos !== item.span.start) {
2632
+ if (fallback !== void 0) return fallback.parse(_input, pos, ctx);
2633
+ return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
2634
+ }
2635
+ if (cstCaptureActive(ctx)) {
2636
+ pushCstLeaf(ctx, new PlainLeaf2(item.value, item.span));
2637
+ }
2638
+ return { ok: true, value: item.value, span: item.span };
2639
+ }
2640
+ };
2641
+ }
2642
+ function dispatch(selector, ...arms) {
2643
+ if (parserUsesRouted(selector)) {
2644
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2645
+ }
2646
+ let fallback;
2647
+ let fallbackUsesRouted = false;
2648
+ let fallbackCommits = true;
2649
+ const matchers = [];
2650
+ const cases = [];
2651
+ const seen = [];
2652
+ for (let i = 0; i < arms.length; i++) {
2653
+ const arm = arms[i];
2654
+ if (arm.kind === "otherwise") {
2655
+ if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
2656
+ if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
2657
+ fallback = arm.parser;
2658
+ fallbackUsesRouted = arm.usesRouted === true;
2659
+ fallbackCommits = arm.commit !== false;
2660
+ continue;
2661
+ }
2662
+ if (arm.kind === "whenMatcher") {
2663
+ const matcher = arm.matcher;
2664
+ matchers.push({
2665
+ kind: matcher.kind,
2666
+ value: matcher.value,
2667
+ ...matcher.kind === "matches" ? { flags: matcher.flags } : {},
2668
+ parser: arm.parser,
2669
+ caseInsensitive: arm.caseInsensitive,
2670
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2671
+ });
2672
+ continue;
2673
+ }
2674
+ for (const key of arm.keys) {
2675
+ const folded = asciiFoldKey(key);
2676
+ for (const prior of seen) {
2677
+ const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
2678
+ if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
2679
+ }
2680
+ seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
2681
+ }
2682
+ cases.push({
2683
+ keys: arm.keys,
2684
+ parser: arm.parser,
2685
+ caseInsensitive: arm.caseInsensitive,
2686
+ ...arm.usesRouted === true ? { usesRouted: true } : {}
2687
+ });
2688
+ }
2689
+ const meta = {
2690
+ firstSet: selector._meta.firstSet,
2691
+ canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
2692
+ isTrivia: false
2693
+ };
2694
+ const byKey = /* @__PURE__ */ new Map();
2695
+ const byFoldedKey = /* @__PURE__ */ new Map();
2696
+ for (const entry of cases) {
2697
+ for (const key of entry.keys) {
2698
+ if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
2699
+ else byKey.set(key, entry);
2700
+ }
2701
+ }
2702
+ return {
2703
+ _tag: "dispatch",
2704
+ _meta: meta,
2705
+ _def: {
2706
+ tag: "dispatch",
2707
+ selector,
2708
+ cases,
2709
+ ...matchers.length === 0 ? {} : { matchers },
2710
+ ...fallback === void 0 ? {} : {
2711
+ otherwise: fallback,
2712
+ ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {},
2713
+ ...fallbackCommits ? {} : { otherwiseCommit: false }
2714
+ }
2715
+ },
2716
+ parse(input, pos, ctx) {
2717
+ if (parserUsesRouted(selector)) {
2718
+ throw new Error("parseman: routed() can only appear inside a dispatch() branch");
2719
+ }
2720
+ const selectorMark = saveTriviaMark(ctx);
2721
+ const selected = selector.parse(input, pos, ctx);
2722
+ if (!selected.ok) return selected;
2723
+ const chosen = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher));
2724
+ const branch = chosen ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
2725
+ if (branch === void 0) {
2726
+ const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
2727
+ return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
2728
+ }
2729
+ const savedRouted = ctx._routed;
2730
+ const usesRouted = branchUsesRouted(branch);
2731
+ let mark = saveTriviaMark(ctx);
2732
+ if (usesRouted) {
2733
+ rollbackTrivia(ctx, selectorMark);
2734
+ mark = saveTriviaMark(ctx);
2735
+ ctx._routed = { value: selected.value, span: selected.span };
2736
+ }
2737
+ let result;
2738
+ try {
2739
+ result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
2740
+ } finally {
2741
+ if (usesRouted) ctx._routed = savedRouted;
2742
+ }
2743
+ if (!result.ok) {
2744
+ rollbackTrivia(ctx, mark);
2745
+ return chosen === void 0 && !fallbackCommits ? result : { ...result, committed: true };
2746
+ }
2747
+ return {
2748
+ ok: true,
2749
+ value: [selected.value, result.value],
2750
+ span: { start: pos, end: result.span.end }
2751
+ };
2752
+ }
2753
+ };
2754
+ }
2555
2755
 
2556
2756
  // src/recovery/scan.ts
2557
2757
  import { PlainSpan as PlainSpan2 } from "../cst/plain.js";
@@ -5500,7 +5700,7 @@ function regex(pattern, flags = "") {
5500
5700
  }
5501
5701
 
5502
5702
  // src/version.ts
5503
- var PARSEMAN_VERSION = "0.61.0";
5703
+ var PARSEMAN_VERSION = "0.61.1";
5504
5704
 
5505
5705
  // src/table/lex-body.ts
5506
5706
  function buildLexBodyRecognizer(spec, constants) {
@@ -8856,7 +9056,7 @@ var Encoder = class {
8856
9056
  const dispCovIds = this.plan?.dispatches.get(p);
8857
9057
  let dispCov = 0;
8858
9058
  const arms = [];
8859
- const routed = [];
9059
+ const routed2 = [];
8860
9060
  const key = [], keyArm = [];
8861
9061
  const fold = [], foldArm = [];
8862
9062
  const match = [];
@@ -8864,7 +9064,7 @@ var Encoder = class {
8864
9064
  for (const c of d.cases) {
8865
9065
  const arm = arms.length;
8866
9066
  arms.push(this.covWrap(this.node(c.parser).ip, dispCovIds?.[dispCov++], 1));
8867
- routed.push(branchUsesRouted(c) ? 1 : 0);
9067
+ routed2.push(branchUsesRouted(c) ? 1 : 0);
8868
9068
  for (const kk of c.keys) {
8869
9069
  expected.push(JSON.stringify(kk));
8870
9070
  if (c.caseInsensitive) {
@@ -8880,7 +9080,7 @@ var Encoder = class {
8880
9080
  for (const m of d.matchers ?? []) {
8881
9081
  const arm = arms.length;
8882
9082
  arms.push(this.covWrap(this.node(m.parser).ip, dispCovIds?.[dispCov++], 1));
8883
- routed.push(branchUsesRouted(m) ? 1 : 0);
9083
+ routed2.push(branchUsesRouted(m) ? 1 : 0);
8884
9084
  if (!m.caseInsensitive) {
8885
9085
  match.push([KIND[m.kind], m.value, m.flags ?? "", arm]);
8886
9086
  continue;
@@ -8894,7 +9094,7 @@ var Encoder = class {
8894
9094
  }
8895
9095
  const other = d.otherwise === void 0 ? -1 : this.covWrap(this.node(d.otherwise).ip, dispCovIds?.[dispCov++], 1);
8896
9096
  const dspIdx = this.dsp.length;
8897
- this.dsp.push({ key, keyArm, fold, foldArm, match, routed, expected });
9097
+ this.dsp.push({ key, keyArm, fold, foldArm, match, routed: routed2, expected });
8898
9098
  const head = this.emitHead(OP_DISPATCH, 5 + arms.length);
8899
9099
  this.code[head + 1] = sel;
8900
9100
  this.code[head + 2] = dspIdx;
@@ -10210,6 +10410,12 @@ function emitShapeMatch(shape, start, mint, ind, firstChar) {
10210
10410
 
10211
10411
  // src/table/child-slots.ts
10212
10412
  function childSlots(code, ip, out) {
10413
+ const from = out.length;
10414
+ if (!childSlotOffsets(code, ip, out)) return false;
10415
+ for (let i = from; i < out.length; i++) out[i] = code[out[i]];
10416
+ return true;
10417
+ }
10418
+ function childSlotOffsets(code, ip, out) {
10213
10419
  switch (code[ip]) {
10214
10420
  // ZERO-WIDTH / TERMINAL ROWS — no successor. `OP_GUARD` and `OP_ADJ` are both
10215
10421
  // childless tests. `OP_LIVE` holds a hand-written combinator in `prog.fns`; its
@@ -10242,7 +10448,7 @@ function childSlots(code, ip, out) {
10242
10448
  case OP_COV:
10243
10449
  case OP_REJECT:
10244
10450
  case OP_GAPGATE:
10245
- out.push(code[ip + 1]);
10451
+ out.push(ip + 1);
10246
10452
  return true;
10247
10453
  // Single child at `ip + 2`.
10248
10454
  case OP_GATE:
@@ -10259,55 +10465,55 @@ function childSlots(code, ip, out) {
10259
10465
  case OP_ARMGATE:
10260
10466
  case OP_RGATE:
10261
10467
  case OP_RGATE2:
10262
- out.push(code[ip + 2]);
10468
+ out.push(ip + 2);
10263
10469
  return true;
10264
10470
  // `STRICT strict tolerant` — both encodings are reachable; an assembly keeps one.
10265
10471
  case OP_STRICT:
10266
- out.push(code[ip + 1], code[ip + 2]);
10472
+ out.push(ip + 1, ip + 2);
10267
10473
  return true;
10268
10474
  // `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
10269
10475
  // data the enclosing `dispatch()` already consumed, so the only edge is the
10270
10476
  // fallback.
10271
10477
  case OP_ROUTED:
10272
- if (code[ip + 1] >= 0) out.push(code[ip + 1]);
10478
+ if (code[ip + 1] >= 0) out.push(ip + 1);
10273
10479
  return true;
10274
10480
  // `DISPATCH sel d other otherRouted n a1 … an` — the SELECTOR is a child on
10275
10481
  // exactly the same footing as the arms: it runs inside whatever node and scope
10276
10482
  // this site sits in.
10277
10483
  case OP_DISPATCH: {
10278
- out.push(code[ip + 1]);
10279
- if (code[ip + 3] >= 0) out.push(code[ip + 3]);
10484
+ out.push(ip + 1);
10485
+ if (code[ip + 3] >= 0) out.push(ip + 3);
10280
10486
  const n = code[ip + 5];
10281
- for (let i = 0; i < n; i++) out.push(code[ip + 6 + i]);
10487
+ for (let i = 0; i < n; i++) out.push(ip + 6 + i);
10282
10488
  return true;
10283
10489
  }
10284
10490
  case OP_SEQ:
10285
10491
  case OP_SEQV: {
10286
10492
  const n = code[ip + 1];
10287
- for (let i = 0; i < n; i++) out.push(code[ip + 2 + i]);
10493
+ for (let i = 0; i < n; i++) out.push(ip + 2 + i);
10288
10494
  return true;
10289
10495
  }
10290
10496
  case OP_SEQX: {
10291
10497
  const n = code[ip + 2];
10292
- for (let i = 0; i < n; i++) out.push(code[ip + 3 + i]);
10498
+ for (let i = 0; i < n; i++) out.push(ip + 3 + i);
10293
10499
  return true;
10294
10500
  }
10295
10501
  case OP_CHOICE: {
10296
10502
  const n = code[ip + 2];
10297
- for (let i = 0; i < n; i++) out.push(code[ip + 4 + i]);
10503
+ for (let i = 0; i < n; i++) out.push(ip + 4 + i);
10298
10504
  return true;
10299
10505
  }
10300
10506
  // Arms are stride-2 from `ip + 4` — the gate word sits beside each arm offset.
10301
10507
  case OP_GREEDY: {
10302
- out.push(code[ip + 1]);
10508
+ out.push(ip + 1);
10303
10509
  const n = code[ip + 2];
10304
- for (let i = 0; i < n; i++) out.push(code[ip + 4 + 2 * i]);
10510
+ for (let i = 0; i < n; i++) out.push(ip + 4 + 2 * i);
10305
10511
  return true;
10306
10512
  }
10307
10513
  case OP_REP:
10308
10514
  case OP_REPV:
10309
- out.push(code[ip + 1]);
10310
- if (code[ip + 4] >= 0) out.push(code[ip + 4]);
10515
+ out.push(ip + 1);
10516
+ if (code[ip + 4] >= 0) out.push(ip + 4);
10311
10517
  return true;
10312
10518
  default:
10313
10519
  return false;
@@ -16618,10 +16824,10 @@ function assemble(t2, prog, cfg) {
16618
16824
  const hasFold = spec.byFold.size > 0;
16619
16825
  const match = spec.match;
16620
16826
  const matchN = match.length;
16621
- const routed = spec.routed;
16827
+ const routed2 = spec.routed;
16622
16828
  const expected = spec.expected;
16623
16829
  const validArm = (arm) => Number.isInteger(arm) && arm >= 0 && arm < n;
16624
- if (routed.length !== n) throw new TypeError("table assembler: malformed dispatch routed arity");
16830
+ if (routed2.length !== n) throw new TypeError("table assembler: malformed dispatch routed arity");
16625
16831
  for (const arm of byKey.values()) if (!validArm(arm)) throw new TypeError("table assembler: malformed dispatch exact arm");
16626
16832
  for (const arm of byFold.values()) if (!validArm(arm)) throw new TypeError("table assembler: malformed dispatch folded arm");
16627
16833
  for (const m of match) if (!validArm(m[3])) throw new TypeError("table assembler: malformed dispatch matcher arm");
@@ -16646,7 +16852,7 @@ function assemble(t2, prog, cfg) {
16646
16852
  const classify = hasFold ? matchBlock === void 0 ? (key) => byKey.get(key) ?? byFold.get(asciiFoldKey(key)) : (key) => byKey.get(key) ?? byFold.get(asciiFoldKey(key)) ?? matchBlock(key) : matchBlock === void 0 ? (key) => byKey.get(key) : (key) => byKey.get(key) ?? matchBlock(key);
16647
16853
  const neverBranch = () => FAIL;
16648
16854
  const bindArms = (start) => {
16649
- const branch = (i) => i < n ? dispatchBranch(link(code[armBase + i]), routed[i] === 1) : neverBranch;
16855
+ const branch = (i) => i < n ? dispatchBranch(link(code[armBase + i]), routed2[i] === 1) : neverBranch;
16650
16856
  const next = start + 4 < n ? bindArms(start + 4) : void 0;
16651
16857
  return dispatchArmBlock(
16652
16858
  start,
@@ -20261,6 +20467,38 @@ function not(combinator) {
20261
20467
  };
20262
20468
  }
20263
20469
 
20470
+ // src/combinators/withCtx.ts
20471
+ function withCtx(extra, combinator) {
20472
+ const meta = {
20473
+ firstSet: combinator._meta.firstSet,
20474
+ canMatchNewline: combinator._meta.canMatchNewline,
20475
+ isTrivia: false
20476
+ };
20477
+ const parse2 = typeof extra === "function" ? (input, pos, ctx) => {
20478
+ const saved = ctx.state;
20479
+ ctx.state = extra(saved);
20480
+ try {
20481
+ return combinator.parse(input, pos, ctx);
20482
+ } finally {
20483
+ ctx.state = saved;
20484
+ }
20485
+ } : (input, pos, ctx) => {
20486
+ const saved = ctx.state;
20487
+ ctx.state = extra;
20488
+ try {
20489
+ return combinator.parse(input, pos, ctx);
20490
+ } finally {
20491
+ ctx.state = saved;
20492
+ }
20493
+ };
20494
+ return {
20495
+ _tag: "withCtx",
20496
+ _meta: meta,
20497
+ _def: { tag: "withCtx", extra, parser: combinator },
20498
+ parse: parse2
20499
+ };
20500
+ }
20501
+
20264
20502
  // src/analysis/rebuild.ts
20265
20503
  var FROZEN_TAGS = /* @__PURE__ */ new Set([
20266
20504
  "dispatch",
@@ -20274,6 +20512,7 @@ var FROZEN_TAGS = /* @__PURE__ */ new Set([
20274
20512
  "lines",
20275
20513
  "columns"
20276
20514
  ]);
20515
+ var THAWED_TAGS = /* @__PURE__ */ new Set(["dispatch", "withCtx", "routed", "guard"]);
20277
20516
  var CARRIED_META = [
20278
20517
  "grammarTrivia",
20279
20518
  "grammarScanSkip",
@@ -20312,7 +20551,9 @@ function subtreeContains(p, targets) {
20312
20551
  walk(p);
20313
20552
  return hit;
20314
20553
  }
20315
- function rebuildCombinator(root, replacements) {
20554
+ function rebuildCombinator(root, replacements, options = {}) {
20555
+ const thaw = options.thaw === true;
20556
+ const after = options.after ?? ((_p, out) => out);
20316
20557
  const memo = /* @__PURE__ */ new Map();
20317
20558
  const frozenKeys = /* @__PURE__ */ new Set();
20318
20559
  const frozen = [];
@@ -20332,15 +20573,16 @@ function rebuildCombinator(root, replacements) {
20332
20573
  return p;
20333
20574
  }
20334
20575
  const here = p._ruleName ?? rule2;
20335
- if (FROZEN_TAGS.has(d.tag)) {
20576
+ if (FROZEN_TAGS.has(d.tag) && !(thaw && THAWED_TAGS.has(d.tag))) {
20336
20577
  const key = JSON.stringify([here, d.tag]);
20337
20578
  if (!frozenKeys.has(key)) {
20338
20579
  frozenKeys.add(key);
20339
20580
  frozen.push({ tag: d.tag, rule: here });
20340
20581
  }
20341
20582
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20342
- memo.set(p, p);
20343
- return p;
20583
+ const kept = after(p, p);
20584
+ memo.set(p, kept);
20585
+ return kept;
20344
20586
  }
20345
20587
  if (d.tag === "lazy") {
20346
20588
  let target;
@@ -20377,6 +20619,14 @@ function rebuildCombinator(root, replacements) {
20377
20619
  break;
20378
20620
  }
20379
20621
  case "choice": {
20622
+ if (d.gates.some((g) => g !== null) && thaw) {
20623
+ const unsafeChoice = choice;
20624
+ out = unsafeChoice(...d.parsers.map((arm, i) => {
20625
+ const gate = d.gates[i];
20626
+ return gate ? { combinator: one(arm), gate } : one(arm);
20627
+ }));
20628
+ break;
20629
+ }
20380
20630
  if (d.gates.some((g) => g !== null)) {
20381
20631
  const key = JSON.stringify([here, "choice(gated)"]);
20382
20632
  if (!frozenKeys.has(key)) {
@@ -20384,8 +20634,9 @@ function rebuildCombinator(root, replacements) {
20384
20634
  frozen.push({ tag: "choice(gated)", rule: here });
20385
20635
  }
20386
20636
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20387
- memo.set(p, p);
20388
- return p;
20637
+ const kept = after(p, p);
20638
+ memo.set(p, kept);
20639
+ return kept;
20389
20640
  }
20390
20641
  const kids = d.parsers.map(one);
20391
20642
  out = choice(...kids);
@@ -20463,13 +20714,33 @@ function rebuildCombinator(root, replacements) {
20463
20714
  frozen.push({ tag: "node(untyped+build+opts)", rule: here });
20464
20715
  }
20465
20716
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20466
- memo.set(p, p);
20467
- return p;
20717
+ const kept = after(p, p);
20718
+ memo.set(p, kept);
20719
+ return kept;
20468
20720
  }
20469
20721
  out = d.type === void 0 ? d.build === void 0 ? node(one(d.parser), opts) : node(one(d.parser), d.build) : node(d.type, one(d.parser), d.build, opts);
20470
20722
  if (d.buildRawUnused === true && out._def.tag === "node") out._def.buildRawUnused = true;
20471
20723
  break;
20472
20724
  }
20725
+ case "dispatch": {
20726
+ const arms = d.cases.map((e) => when(e.keys, one(e.parser), e.caseInsensitive ? { caseInsensitive: true } : void 0));
20727
+ for (const e of d.matchers ?? []) {
20728
+ arms.push(when(dispatchMatcher(e), one(e.parser), e.caseInsensitive ? { caseInsensitive: true } : void 0));
20729
+ }
20730
+ if (d.otherwise) arms.push(otherwise(one(d.otherwise), d.otherwiseCommit === false ? { commit: false } : void 0));
20731
+ const unsafeDispatch = dispatch;
20732
+ out = unsafeDispatch(one(d.selector), ...arms);
20733
+ break;
20734
+ }
20735
+ case "withCtx":
20736
+ out = withCtx(d.extra, one(d.parser));
20737
+ break;
20738
+ case "routed":
20739
+ out = d.fallback === void 0 ? routed() : routed(one(d.fallback));
20740
+ break;
20741
+ case "guard":
20742
+ out = p;
20743
+ break;
20473
20744
  case "grammar": {
20474
20745
  const inner = one(d.parser);
20475
20746
  out = parser({
@@ -20487,13 +20758,15 @@ function rebuildCombinator(root, replacements) {
20487
20758
  frozen.push({ tag: d.tag, rule: here });
20488
20759
  }
20489
20760
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20490
- memo.set(p, p);
20491
- return p;
20761
+ const kept = after(p, p);
20762
+ memo.set(p, kept);
20763
+ return kept;
20492
20764
  }
20493
20765
  }
20494
20766
  carryStamps(p, out);
20495
- memo.set(p, out);
20496
- return out;
20767
+ const done = after(p, out);
20768
+ memo.set(p, done);
20769
+ return done;
20497
20770
  };
20498
20771
  const rebuilt = go(root, root._ruleName ?? "<entry>");
20499
20772
  frozen.sort((a, b) => a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : a.tag < b.tag ? -1 : a.tag > b.tag ? 1 : 0);