parseman 0.60.6 → 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.
Files changed (60) hide show
  1. package/dist/analysis/backtrack.d.ts +50 -0
  2. package/dist/analysis/backtrack.d.ts.map +1 -0
  3. package/dist/analysis/diagnostics.d.ts +3 -0
  4. package/dist/analysis/diagnostics.d.ts.map +1 -1
  5. package/dist/analysis/diagnostics.js +1162 -158
  6. package/dist/analysis/diagnostics.js.map +3 -3
  7. package/dist/analysis/rebuild.d.ts +17 -1
  8. package/dist/analysis/rebuild.d.ts.map +1 -1
  9. package/dist/cli/index.js +353 -69
  10. package/dist/cli/index.js.map +3 -3
  11. package/dist/combinators/choice.d.ts.map +1 -1
  12. package/dist/combinators/dispatch.d.ts +2 -0
  13. package/dist/combinators/dispatch.d.ts.map +1 -1
  14. package/dist/combinators/expect.d.ts.map +1 -1
  15. package/dist/combinators/link-scope.d.ts +7 -0
  16. package/dist/combinators/link-scope.d.ts.map +1 -0
  17. package/dist/combinators/node.d.ts.map +1 -1
  18. package/dist/combinators/parser.d.ts.map +1 -1
  19. package/dist/compiler/token-capability.js +1 -0
  20. package/dist/compiler/token-capability.js.map +1 -1
  21. package/dist/compiler/value-usage.d.ts.map +1 -1
  22. package/dist/coverage.d.ts.map +1 -1
  23. package/dist/functional/run.d.ts.map +1 -1
  24. package/dist/index.js +121 -90
  25. package/dist/index.js.map +3 -3
  26. package/dist/language-service/index.js +1 -1
  27. package/dist/language-service/index.js.map +1 -1
  28. package/dist/plugin/index.js +274 -243
  29. package/dist/plugin/index.js.map +3 -3
  30. package/dist/run/index.js +1 -1
  31. package/dist/run/index.js.map +1 -1
  32. package/dist/spec/index.js.map +1 -1
  33. package/dist/table/backtrack-probe.d.ts +41 -0
  34. package/dist/table/backtrack-probe.d.ts.map +1 -0
  35. package/dist/table/child-slots.d.ts +10 -0
  36. package/dist/table/child-slots.d.ts.map +1 -1
  37. package/dist/table/emit.d.ts.map +1 -1
  38. package/dist/table/index.js +31 -20
  39. package/dist/table/index.js.map +3 -3
  40. package/dist/table/program.d.ts +3 -1
  41. package/dist/table/program.d.ts.map +1 -1
  42. package/dist/version.d.ts +1 -1
  43. package/package.json +5 -1
  44. package/src/analysis/backtrack.ts +249 -0
  45. package/src/analysis/diagnostics.ts +4 -0
  46. package/src/analysis/rebuild.ts +63 -11
  47. package/src/combinators/choice.ts +3 -1
  48. package/src/combinators/dispatch.ts +9 -0
  49. package/src/combinators/expect.ts +1 -0
  50. package/src/combinators/link-scope.ts +19 -0
  51. package/src/combinators/node.ts +11 -2
  52. package/src/combinators/parser.ts +37 -31
  53. package/src/compiler/value-usage.ts +16 -29
  54. package/src/coverage.ts +2 -14
  55. package/src/functional/run.ts +3 -1
  56. package/src/table/backtrack-probe.ts +326 -0
  57. package/src/table/child-slots.ts +30 -14
  58. package/src/table/emit.ts +5 -1
  59. package/src/table/program.ts +8 -2
  60. package/src/version.ts +1 -1
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";
@@ -2718,6 +2918,7 @@ function derive(c, seen) {
2718
2918
  case "optional":
2719
2919
  case "many":
2720
2920
  case "oneOrMore":
2921
+ case "sepBy":
2721
2922
  case "transform":
2722
2923
  case "not":
2723
2924
  case "peek":
@@ -2899,7 +3100,7 @@ function selectOptionalSuffixLexBody(ir) {
2899
3100
  function reinstallsRuleTrivia(reference, target) {
2900
3101
  return (reference._meta.grammarTrivia ?? target._meta.grammarTrivia) !== void 0 && hasOwnTriviaBoundary(target);
2901
3102
  }
2902
- function directTokenBodyParser(parser2, resolve2, scope) {
3103
+ function directTokenBodyParser(parser2, resolve2, scope2) {
2903
3104
  const outer = parser2._def;
2904
3105
  if (outer.tag !== "token") return void 0;
2905
3106
  let body = outer.parser;
@@ -2909,7 +3110,7 @@ function directTokenBodyParser(parser2, resolve2, scope) {
2909
3110
  const wrapper = body._def;
2910
3111
  if (wrapper.tag === "grammar") {
2911
3112
  if (wrapper.clearTrivia !== true || wrapper.triviaParser !== void 0 || wrapper.captureTrivia === true || wrapper.rootCapture === "opaque" || wrapper.constructionTrackLines !== "inherit" || wrapper.constructionCaptureTriviaKinds !== void 0) return void 0;
2912
- if (scope !== void 0) scope.cleared = true;
3113
+ if (scope2 !== void 0) scope2.cleared = true;
2913
3114
  body = wrapper.parser;
2914
3115
  continue;
2915
3116
  }
@@ -3166,10 +3367,10 @@ function directFixedLexNode(parser2, resolve2, active, pureOnly = false, cleared
3166
3367
  }
3167
3368
  }
3168
3369
  function directFixedTokenBody(parser2, resolve2) {
3169
- const scope = { cleared: false };
3170
- const body = directTokenBodyParser(parser2, resolve2, scope);
3370
+ const scope2 = { cleared: false };
3371
+ const body = directTokenBodyParser(parser2, resolve2, scope2);
3171
3372
  if (body === void 0) return void 0;
3172
- const root = directFixedLexNode(body, resolve2, /* @__PURE__ */ new Set(), false, scope.cleared);
3373
+ const root = directFixedLexNode(body, resolve2, /* @__PURE__ */ new Set(), false, scope2.cleared);
3173
3374
  return root === void 0 ? void 0 : { kind: "fixed-tree", root };
3174
3375
  }
3175
3376
  function directBalancedTokenBody(parser2, resolve2) {
@@ -4257,15 +4458,20 @@ function markUnusedValues(root) {
4257
4458
  case "many":
4258
4459
  case "oneOrMore":
4259
4460
  mark(def, consumed);
4260
- visit(def.parser, consumed);
4261
- return;
4262
4461
  case "optional":
4462
+ // `optional` returns `inner | null` — no aggregate array/tuple to elide, so
4463
+ // it carries no valueUnused flag. Still descend so an inner many/sequence
4464
+ // under a node() is analyzed (it benefits even when its optional wrapper
4465
+ // doesn't).
4466
+ case "label":
4467
+ case "trivia":
4468
+ case "attempt":
4469
+ case "withCtx":
4470
+ case "expect":
4263
4471
  visit(def.parser, consumed);
4264
4472
  return;
4265
4473
  case "sequence":
4266
4474
  mark(def, consumed);
4267
- for (const p of def.parsers) visit(p, consumed);
4268
- return;
4269
4475
  case "choice":
4270
4476
  for (const p of def.parsers) visit(p, consumed);
4271
4477
  return;
@@ -4276,22 +4482,24 @@ function markUnusedValues(root) {
4276
4482
  if (def.otherwise) visit(def.otherwise, consumed);
4277
4483
  return;
4278
4484
  case "node":
4485
+ // builds from captured children, NOT the inner parser value.
4486
+ case "not":
4487
+ case "peek":
4488
+ // lookahead — value never observed
4489
+ case "token":
4279
4490
  visit(def.parser, false);
4280
4491
  return;
4281
4492
  case "transform":
4282
- visit(def.parser, true);
4283
- return;
4493
+ // the map fn reads the value → always consumed.
4284
4494
  case "field":
4495
+ // the field capture records the parsed value → always consumed.
4496
+ case "leaf":
4285
4497
  visit(def.parser, true);
4286
4498
  return;
4287
4499
  case "sepBy":
4288
4500
  visit(def.parser, true);
4289
4501
  visit(def.separator, false);
4290
4502
  return;
4291
- case "not":
4292
- case "peek":
4293
- visit(def.parser, false);
4294
- return;
4295
4503
  case "grammar":
4296
4504
  visit(def.parser, consumed);
4297
4505
  if (def.triviaParser) visit(def.triviaParser, false);
@@ -4300,19 +4508,6 @@ function markUnusedValues(root) {
4300
4508
  visit(def.parser, consumed);
4301
4509
  visit(def.sentinel, false);
4302
4510
  return;
4303
- case "token":
4304
- visit(def.parser, false);
4305
- return;
4306
- case "label":
4307
- case "trivia":
4308
- case "attempt":
4309
- case "withCtx":
4310
- case "expect":
4311
- visit(def.parser, consumed);
4312
- return;
4313
- case "leaf":
4314
- visit(def.parser, true);
4315
- return;
4316
4511
  case "lazy":
4317
4512
  return;
4318
4513
  default:
@@ -4342,6 +4537,12 @@ function markUnusedValues(root) {
4342
4537
  visit(resolveRoot(root), true);
4343
4538
  }
4344
4539
 
4540
+ // src/combinators/link-scope.ts
4541
+ var scope;
4542
+ function afterLink(settle) {
4543
+ scope?.push(settle);
4544
+ }
4545
+
4345
4546
  // src/cst/root-trivia-scope.ts
4346
4547
  var ROOT_TRIVIA_SCOPE_REFUSAL = "parser(): selected root trivia requires classifiedTrivia() for every local trivia scope, or rootCapture: 'opaque'.";
4347
4548
  function refuseUnclassifiedRootScope(strict) {
@@ -4598,7 +4799,7 @@ function choice(...args) {
4598
4799
  const result = parsers[idx].parse(input, pos, ctx);
4599
4800
  if (result.ok) return result;
4600
4801
  expected.push(...result.expected);
4601
- if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
4802
+ if (result.committed) return { ok: false, expected, span: result.span, committed: true };
4602
4803
  return { ok: false, expected, span: { start: pos, end: pos } };
4603
4804
  }
4604
4805
  if (ctx._probe !== void 0 || ctx._tolerant) for (const p of parsers) p.parse(input, pos, ctx);
@@ -5498,6 +5699,9 @@ function regex(pattern, flags = "") {
5498
5699
  };
5499
5700
  }
5500
5701
 
5702
+ // src/version.ts
5703
+ var PARSEMAN_VERSION = "0.61.1";
5704
+
5501
5705
  // src/table/lex-body.ts
5502
5706
  function buildLexBodyRecognizer(spec, constants) {
5503
5707
  const raw = constants[spec[0]];
@@ -6113,11 +6317,12 @@ function assemblyExtraIps(prog) {
6113
6317
  }
6114
6318
  function expandCompact(p) {
6115
6319
  if ("code" in p) return p;
6320
+ if (p.es === void 0) throw new Error(`parseman: this parser was printed by an earlier parseman; reprint it with parseman ${PARSEMAN_VERSION}`);
6116
6321
  return ownTableProgram({
6117
6322
  code: p.c,
6118
6323
  k: p.k,
6119
6324
  cc: p.x,
6120
- fx: p.e,
6325
+ fx: p.e.map((set) => set.map((i) => p.es[i])),
6121
6326
  disp: p.d,
6122
6327
  rules: p.r,
6123
6328
  fns: p.f,
@@ -7318,8 +7523,14 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
7318
7523
  const def = unwrap || collapse || project !== void 0 || captureTrivia || trailingTrivia || buildArity !== void 0 || tags !== void 0 && tags.length > 0 ? { ...baseDef, ...unwrap ? { unwrap: true } : {}, ...collapse ? { collapse: true } : {}, ...project !== void 0 ? { project } : {}, ...captureTrivia ? { captureTrivia: true } : {}, ...trailingTrivia ? { trailingTrivia: true } : {}, ...buildArity !== void 0 ? { buildArity } : {}, ...tags !== void 0 && tags.length > 0 ? { tags } : {} } : baseDef;
7319
7524
  const capturesTrivia = captureTrivia || (build ? buildReadsTrivia(def) : project === void 0);
7320
7525
  const readsState = build ? buildReadsState(def) : project === void 0;
7321
- const hasOwnFields = parserHasOwnFields(combinator);
7322
- const capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === void 0);
7526
+ let hasOwnFields = parserHasOwnFields(combinator);
7527
+ let capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === void 0);
7528
+ if (!hasOwnFields) {
7529
+ afterLink(() => {
7530
+ hasOwnFields = parserHasOwnFields(combinator);
7531
+ capturesFields = hasOwnFields && (build ? buildReadsFields(def) : project === void 0);
7532
+ });
7533
+ }
7323
7534
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
7324
7535
  let failExpected;
7325
7536
  return {
@@ -8845,7 +9056,7 @@ var Encoder = class {
8845
9056
  const dispCovIds = this.plan?.dispatches.get(p);
8846
9057
  let dispCov = 0;
8847
9058
  const arms = [];
8848
- const routed = [];
9059
+ const routed2 = [];
8849
9060
  const key = [], keyArm = [];
8850
9061
  const fold = [], foldArm = [];
8851
9062
  const match = [];
@@ -8853,7 +9064,7 @@ var Encoder = class {
8853
9064
  for (const c of d.cases) {
8854
9065
  const arm = arms.length;
8855
9066
  arms.push(this.covWrap(this.node(c.parser).ip, dispCovIds?.[dispCov++], 1));
8856
- routed.push(branchUsesRouted(c) ? 1 : 0);
9067
+ routed2.push(branchUsesRouted(c) ? 1 : 0);
8857
9068
  for (const kk of c.keys) {
8858
9069
  expected.push(JSON.stringify(kk));
8859
9070
  if (c.caseInsensitive) {
@@ -8869,7 +9080,7 @@ var Encoder = class {
8869
9080
  for (const m of d.matchers ?? []) {
8870
9081
  const arm = arms.length;
8871
9082
  arms.push(this.covWrap(this.node(m.parser).ip, dispCovIds?.[dispCov++], 1));
8872
- routed.push(branchUsesRouted(m) ? 1 : 0);
9083
+ routed2.push(branchUsesRouted(m) ? 1 : 0);
8873
9084
  if (!m.caseInsensitive) {
8874
9085
  match.push([KIND[m.kind], m.value, m.flags ?? "", arm]);
8875
9086
  continue;
@@ -8883,7 +9094,7 @@ var Encoder = class {
8883
9094
  }
8884
9095
  const other = d.otherwise === void 0 ? -1 : this.covWrap(this.node(d.otherwise).ip, dispCovIds?.[dispCov++], 1);
8885
9096
  const dspIdx = this.dsp.length;
8886
- this.dsp.push({ key, keyArm, fold, foldArm, match, routed, expected });
9097
+ this.dsp.push({ key, keyArm, fold, foldArm, match, routed: routed2, expected });
8887
9098
  const head = this.emitHead(OP_DISPATCH, 5 + arms.length);
8888
9099
  this.code[head + 1] = sel;
8889
9100
  this.code[head + 2] = dspIdx;
@@ -10199,6 +10410,12 @@ function emitShapeMatch(shape, start, mint, ind, firstChar) {
10199
10410
 
10200
10411
  // src/table/child-slots.ts
10201
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) {
10202
10419
  switch (code[ip]) {
10203
10420
  // ZERO-WIDTH / TERMINAL ROWS — no successor. `OP_GUARD` and `OP_ADJ` are both
10204
10421
  // childless tests. `OP_LIVE` holds a hand-written combinator in `prog.fns`; its
@@ -10231,7 +10448,7 @@ function childSlots(code, ip, out) {
10231
10448
  case OP_COV:
10232
10449
  case OP_REJECT:
10233
10450
  case OP_GAPGATE:
10234
- out.push(code[ip + 1]);
10451
+ out.push(ip + 1);
10235
10452
  return true;
10236
10453
  // Single child at `ip + 2`.
10237
10454
  case OP_GATE:
@@ -10248,55 +10465,55 @@ function childSlots(code, ip, out) {
10248
10465
  case OP_ARMGATE:
10249
10466
  case OP_RGATE:
10250
10467
  case OP_RGATE2:
10251
- out.push(code[ip + 2]);
10468
+ out.push(ip + 2);
10252
10469
  return true;
10253
10470
  // `STRICT strict tolerant` — both encodings are reachable; an assembly keeps one.
10254
10471
  case OP_STRICT:
10255
- out.push(code[ip + 1], code[ip + 2]);
10472
+ out.push(ip + 1, ip + 2);
10256
10473
  return true;
10257
10474
  // `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
10258
10475
  // data the enclosing `dispatch()` already consumed, so the only edge is the
10259
10476
  // fallback.
10260
10477
  case OP_ROUTED:
10261
- if (code[ip + 1] >= 0) out.push(code[ip + 1]);
10478
+ if (code[ip + 1] >= 0) out.push(ip + 1);
10262
10479
  return true;
10263
10480
  // `DISPATCH sel d other otherRouted n a1 … an` — the SELECTOR is a child on
10264
10481
  // exactly the same footing as the arms: it runs inside whatever node and scope
10265
10482
  // this site sits in.
10266
10483
  case OP_DISPATCH: {
10267
- out.push(code[ip + 1]);
10268
- 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);
10269
10486
  const n = code[ip + 5];
10270
- 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);
10271
10488
  return true;
10272
10489
  }
10273
10490
  case OP_SEQ:
10274
10491
  case OP_SEQV: {
10275
10492
  const n = code[ip + 1];
10276
- 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);
10277
10494
  return true;
10278
10495
  }
10279
10496
  case OP_SEQX: {
10280
10497
  const n = code[ip + 2];
10281
- 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);
10282
10499
  return true;
10283
10500
  }
10284
10501
  case OP_CHOICE: {
10285
10502
  const n = code[ip + 2];
10286
- 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);
10287
10504
  return true;
10288
10505
  }
10289
10506
  // Arms are stride-2 from `ip + 4` — the gate word sits beside each arm offset.
10290
10507
  case OP_GREEDY: {
10291
- out.push(code[ip + 1]);
10508
+ out.push(ip + 1);
10292
10509
  const n = code[ip + 2];
10293
- 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);
10294
10511
  return true;
10295
10512
  }
10296
10513
  case OP_REP:
10297
10514
  case OP_REPV:
10298
- out.push(code[ip + 1]);
10299
- 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);
10300
10517
  return true;
10301
10518
  default:
10302
10519
  return false;
@@ -13430,9 +13647,6 @@ function clearOf(ranges, input, from, to) {
13430
13647
  return true;
13431
13648
  }
13432
13649
 
13433
- // src/version.ts
13434
- var PARSEMAN_VERSION = "0.60.6";
13435
-
13436
13650
  // src/table/assemble.ts
13437
13651
  import { PlainBuf as PlainBuf2, PlainField, PlainLeaf as PlainLeaf9, PlainNode as PlainNode2, PlainSpan as PlainSpan10 } from "../cst/plain.js";
13438
13652
 
@@ -16610,10 +16824,10 @@ function assemble(t2, prog, cfg) {
16610
16824
  const hasFold = spec.byFold.size > 0;
16611
16825
  const match = spec.match;
16612
16826
  const matchN = match.length;
16613
- const routed = spec.routed;
16827
+ const routed2 = spec.routed;
16614
16828
  const expected = spec.expected;
16615
16829
  const validArm = (arm) => Number.isInteger(arm) && arm >= 0 && arm < n;
16616
- 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");
16617
16831
  for (const arm of byKey.values()) if (!validArm(arm)) throw new TypeError("table assembler: malformed dispatch exact arm");
16618
16832
  for (const arm of byFold.values()) if (!validArm(arm)) throw new TypeError("table assembler: malformed dispatch folded arm");
16619
16833
  for (const m of match) if (!validArm(m[3])) throw new TypeError("table assembler: malformed dispatch matcher arm");
@@ -16638,7 +16852,7 @@ function assemble(t2, prog, cfg) {
16638
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);
16639
16853
  const neverBranch = () => FAIL;
16640
16854
  const bindArms = (start) => {
16641
- 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;
16642
16856
  const next = start + 4 < n ? bindArms(start + 4) : void 0;
16643
16857
  return dispatchArmBlock(
16644
16858
  start,
@@ -17412,12 +17626,15 @@ function assertPrintable(prog, who) {
17412
17626
  }
17413
17627
  }
17414
17628
  function programFields(prog, fns, opts = {}) {
17629
+ const tokens = [...new Set(prog.fx.flat())];
17630
+ const tokenAt = new Map(tokens.map((token2, i) => [token2, i]));
17415
17631
  return [
17416
17632
  ...emitAssemblies(prog, opts.assemblies ?? []),
17417
17633
  `c:[${prog.code.join(",")}],`,
17418
17634
  `k:[${prog.k.map(emitConst).join(",")}],`,
17419
17635
  `x:${emitClassPool(prog.cc)},`,
17420
- `e:[${prog.fx.map((f) => `[${f.map(jsString).join(",")}]`).join(",")}],`,
17636
+ `e:[${prog.fx.map((f) => `[${f.map((token2) => tokenAt.get(token2)).join(",")}]`).join(",")}],`,
17637
+ `es:[${tokens.map(jsString).join(",")}],`,
17421
17638
  `d:[${prog.disp.map((a) => `[${a.join(",")}]`).join(",")}],`,
17422
17639
  `r:${JSON.stringify(prog.rules)},`,
17423
17640
  ...prog.lines === 1 ? ["l:1,"] : [],
@@ -20250,6 +20467,38 @@ function not(combinator) {
20250
20467
  };
20251
20468
  }
20252
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
+
20253
20502
  // src/analysis/rebuild.ts
20254
20503
  var FROZEN_TAGS = /* @__PURE__ */ new Set([
20255
20504
  "dispatch",
@@ -20263,6 +20512,7 @@ var FROZEN_TAGS = /* @__PURE__ */ new Set([
20263
20512
  "lines",
20264
20513
  "columns"
20265
20514
  ]);
20515
+ var THAWED_TAGS = /* @__PURE__ */ new Set(["dispatch", "withCtx", "routed", "guard"]);
20266
20516
  var CARRIED_META = [
20267
20517
  "grammarTrivia",
20268
20518
  "grammarScanSkip",
@@ -20301,7 +20551,9 @@ function subtreeContains(p, targets) {
20301
20551
  walk(p);
20302
20552
  return hit;
20303
20553
  }
20304
- function rebuildCombinator(root, replacements) {
20554
+ function rebuildCombinator(root, replacements, options = {}) {
20555
+ const thaw = options.thaw === true;
20556
+ const after = options.after ?? ((_p, out) => out);
20305
20557
  const memo = /* @__PURE__ */ new Map();
20306
20558
  const frozenKeys = /* @__PURE__ */ new Set();
20307
20559
  const frozen = [];
@@ -20321,15 +20573,16 @@ function rebuildCombinator(root, replacements) {
20321
20573
  return p;
20322
20574
  }
20323
20575
  const here = p._ruleName ?? rule2;
20324
- if (FROZEN_TAGS.has(d.tag)) {
20576
+ if (FROZEN_TAGS.has(d.tag) && !(thaw && THAWED_TAGS.has(d.tag))) {
20325
20577
  const key = JSON.stringify([here, d.tag]);
20326
20578
  if (!frozenKeys.has(key)) {
20327
20579
  frozenKeys.add(key);
20328
20580
  frozen.push({ tag: d.tag, rule: here });
20329
20581
  }
20330
20582
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20331
- memo.set(p, p);
20332
- return p;
20583
+ const kept = after(p, p);
20584
+ memo.set(p, kept);
20585
+ return kept;
20333
20586
  }
20334
20587
  if (d.tag === "lazy") {
20335
20588
  let target;
@@ -20366,6 +20619,14 @@ function rebuildCombinator(root, replacements) {
20366
20619
  break;
20367
20620
  }
20368
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
+ }
20369
20630
  if (d.gates.some((g) => g !== null)) {
20370
20631
  const key = JSON.stringify([here, "choice(gated)"]);
20371
20632
  if (!frozenKeys.has(key)) {
@@ -20373,8 +20634,9 @@ function rebuildCombinator(root, replacements) {
20373
20634
  frozen.push({ tag: "choice(gated)", rule: here });
20374
20635
  }
20375
20636
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20376
- memo.set(p, p);
20377
- return p;
20637
+ const kept = after(p, p);
20638
+ memo.set(p, kept);
20639
+ return kept;
20378
20640
  }
20379
20641
  const kids = d.parsers.map(one);
20380
20642
  out = choice(...kids);
@@ -20452,13 +20714,33 @@ function rebuildCombinator(root, replacements) {
20452
20714
  frozen.push({ tag: "node(untyped+build+opts)", rule: here });
20453
20715
  }
20454
20716
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20455
- memo.set(p, p);
20456
- return p;
20717
+ const kept = after(p, p);
20718
+ memo.set(p, kept);
20719
+ return kept;
20457
20720
  }
20458
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);
20459
20722
  if (d.buildRawUnused === true && out._def.tag === "node") out._def.buildRawUnused = true;
20460
20723
  break;
20461
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;
20462
20744
  case "grammar": {
20463
20745
  const inner = one(d.parser);
20464
20746
  out = parser({
@@ -20476,13 +20758,15 @@ function rebuildCombinator(root, replacements) {
20476
20758
  frozen.push({ tag: d.tag, rule: here });
20477
20759
  }
20478
20760
  for (const t2 of subtreeContains(p, targets)) if (!unapplied.includes(t2)) unapplied.push(t2);
20479
- memo.set(p, p);
20480
- return p;
20761
+ const kept = after(p, p);
20762
+ memo.set(p, kept);
20763
+ return kept;
20481
20764
  }
20482
20765
  }
20483
20766
  carryStamps(p, out);
20484
- memo.set(p, out);
20485
- return out;
20767
+ const done = after(p, out);
20768
+ memo.set(p, done);
20769
+ return done;
20486
20770
  };
20487
20771
  const rebuilt = go(root, root._ruleName ?? "<entry>");
20488
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);