parseman 0.59.3 → 0.60.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 (94) hide show
  1. package/dist/analysis/diagnostics.js +876 -190
  2. package/dist/analysis/diagnostics.js.map +3 -3
  3. package/dist/cli/index.js +876 -190
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/combinators/dispatch.d.ts +13 -1
  6. package/dist/combinators/dispatch.d.ts.map +1 -1
  7. package/dist/combinators/first-set.d.ts +35 -2
  8. package/dist/combinators/first-set.d.ts.map +1 -1
  9. package/dist/combinators/regex.d.ts.map +1 -1
  10. package/dist/combinators/trivia-boundary.d.ts +11 -0
  11. package/dist/combinators/trivia-boundary.d.ts.map +1 -1
  12. package/dist/combinators/trivia-skip.d.ts +1 -1
  13. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  14. package/dist/compiler/token-alphabet.d.ts +2 -0
  15. package/dist/compiler/token-alphabet.d.ts.map +1 -1
  16. package/dist/compiler/token-capability.js +65 -53
  17. package/dist/compiler/token-capability.js.map +2 -2
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +896 -199
  21. package/dist/index.js.map +3 -3
  22. package/dist/plugin/evaluator.d.ts.map +1 -1
  23. package/dist/plugin/index.js +903 -201
  24. package/dist/plugin/index.js.map +3 -3
  25. package/dist/regex/first-set.d.ts +19 -0
  26. package/dist/regex/first-set.d.ts.map +1 -1
  27. package/dist/table/assemble.d.ts +15 -1
  28. package/dist/table/assemble.d.ts.map +1 -1
  29. package/dist/table/child-slots.d.ts.map +1 -1
  30. package/dist/table/compile-rule-map.d.ts +1 -1
  31. package/dist/table/compile-rule-map.d.ts.map +1 -1
  32. package/dist/table/compile.d.ts +1 -1
  33. package/dist/table/emit-assembly.d.ts +8 -2
  34. package/dist/table/emit-assembly.d.ts.map +1 -1
  35. package/dist/table/emit.d.ts +4 -3
  36. package/dist/table/emit.d.ts.map +1 -1
  37. package/dist/table/encode.d.ts.map +1 -1
  38. package/dist/table/exec.d.ts +0 -35
  39. package/dist/table/exec.d.ts.map +1 -1
  40. package/dist/table/factor-choice.d.ts +27 -0
  41. package/dist/table/factor-choice.d.ts.map +1 -1
  42. package/dist/table/gap-gate.d.ts +11 -0
  43. package/dist/table/gap-gate.d.ts.map +1 -0
  44. package/dist/table/index.js +926 -201
  45. package/dist/table/index.js.map +3 -3
  46. package/dist/table/lex-program.d.ts.map +1 -1
  47. package/dist/table/ops.d.ts +35 -16
  48. package/dist/table/ops.d.ts.map +1 -1
  49. package/dist/table/program.d.ts +6 -1
  50. package/dist/table/program.d.ts.map +1 -1
  51. package/dist/types.d.ts +16 -4
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/version.d.ts +1 -1
  54. package/package.json +2 -4
  55. package/src/analysis/duplication.ts +1 -1
  56. package/src/combinators/attempt.ts +1 -1
  57. package/src/combinators/dispatch.ts +30 -6
  58. package/src/combinators/first-set.ts +55 -12
  59. package/src/combinators/node.ts +1 -1
  60. package/src/combinators/regex.ts +9 -4
  61. package/src/combinators/repeat.ts +6 -6
  62. package/src/combinators/sequence.ts +1 -1
  63. package/src/combinators/trivia-boundary.ts +18 -0
  64. package/src/combinators/trivia-skip.ts +1 -1
  65. package/src/compiler/ir-serialize.ts +1 -1
  66. package/src/compiler/token-alphabet.ts +3 -1
  67. package/src/coverage.ts +1 -1
  68. package/src/index.ts +1 -1
  69. package/src/plugin/evaluator.ts +8 -3
  70. package/src/regex/first-set.ts +37 -3
  71. package/src/table/assemble.ts +176 -30
  72. package/src/table/child-slots.ts +8 -3
  73. package/src/table/compile-rule-map.ts +7 -4
  74. package/src/table/compile.ts +1 -1
  75. package/src/table/emit-assembly.ts +184 -33
  76. package/src/table/emit.ts +6 -4
  77. package/src/table/encode.ts +469 -74
  78. package/src/table/exec.ts +38 -82
  79. package/src/table/factor-choice.ts +62 -0
  80. package/src/table/fail-clean.ts +2 -2
  81. package/src/table/gap-gate.ts +55 -0
  82. package/src/table/hold-gap.ts +2 -2
  83. package/src/table/lex-program.ts +15 -6
  84. package/src/table/ops.ts +38 -16
  85. package/src/table/program.ts +7 -2
  86. package/src/table/select-hot.ts +7 -2
  87. package/src/types.ts +12 -5
  88. package/src/version.ts +1 -1
  89. package/dist/table/encode-baseline.d.ts +0 -16
  90. package/dist/table/encode-baseline.d.ts.map +0 -1
  91. package/dist/table/exec-baseline.d.ts +0 -9
  92. package/dist/table/exec-baseline.d.ts.map +0 -1
  93. package/src/table/encode-baseline.ts +0 -346
  94. package/src/table/exec-baseline.ts +0 -557
@@ -27,15 +27,56 @@ function implicitLeadsDropped(ranges) {
27
27
  return out;
28
28
  }
29
29
 
30
+ // src/combinators/trivia-boundary.ts
31
+ var MAX_DEPTH = 12;
32
+ function hasOwnTriviaBoundary(p, depth = MAX_DEPTH) {
33
+ if (depth === 0) return true;
34
+ const d = p._def;
35
+ switch (d.tag) {
36
+ // Multi-term forms: the gaps BETWEEN terms are the ambient-trivia positions.
37
+ case "sequence":
38
+ return d.parsers.length > 1;
39
+ case "many":
40
+ case "oneOrMore":
41
+ case "sepBy":
42
+ return true;
43
+ // Transparent wrappers — they add no position, so ask what they wrap.
44
+ case "node":
45
+ case "transform":
46
+ case "leaf":
47
+ case "label":
48
+ case "field":
49
+ case "token":
50
+ case "attempt":
51
+ case "optional":
52
+ case "expect":
53
+ case "withCtx":
54
+ case "recover":
55
+ case "trivia":
56
+ return hasOwnTriviaBoundary(d.parser, depth - 1);
57
+ // An alternation adds no position either, but ONE arm that has one is enough
58
+ // to make the scope meaningful for this rule.
59
+ case "choice":
60
+ return d.parsers.some((a) => hasOwnTriviaBoundary(a, depth - 1));
61
+ default:
62
+ return false;
63
+ }
64
+ }
65
+ function triviaThroughRef(ref2, target, trivia2) {
66
+ if (trivia2 !== void 0 && trivia2 !== null) return trivia2;
67
+ const amb = ref2._meta.grammarTrivia ?? target._meta.grammarTrivia;
68
+ return amb !== void 0 && hasOwnTriviaBoundary(target) ? amb : trivia2;
69
+ }
70
+
30
71
  // src/combinators/first-set.ts
31
- function classifyFinalChoice(arms, resolve, activeTrivia) {
72
+ function classifyFinalChoice(arms, resolve, activeTrivia, consuming = true, constructionSets = false) {
32
73
  const firstSets = [];
33
74
  const nullable2 = [];
34
75
  let exclusive = true;
35
76
  for (const arm of arms) {
36
77
  const empty2 = matchesEmpty(arm, /* @__PURE__ */ new Set(), resolve);
37
78
  nullable2.push(empty2);
38
- firstSets.push(empty2 ? { kind: "any" } : firstSetOf(arm, /* @__PURE__ */ new Set(), resolve, activeTrivia));
79
+ firstSets.push(empty2 ? { kind: "any" } : constructionSets ? arm._meta.firstSet : firstSetOf(arm, /* @__PURE__ */ new Set(), resolve, activeTrivia, consuming));
39
80
  if (empty2) exclusive = false;
40
81
  }
41
82
  if (exclusive) {
@@ -220,7 +261,7 @@ function narrowBy(acc, constraint) {
220
261
  function applyAssertion(fs, assertion) {
221
262
  return assertion === null ? fs : fs.kind === "empty" ? assertion : narrowBy(fs, assertion);
222
263
  }
223
- function sequenceSet(parsers, fs, empties, trivia2) {
264
+ function sequenceSet(parsers, fs, empties, trivia2, consuming = true) {
224
265
  const afterBoundary = (i) => {
225
266
  const rest = at(i);
226
267
  return trivia2 ? union(rest, fs(trivia2)) : rest;
@@ -236,24 +277,24 @@ function sequenceSet(parsers, fs, empties, trivia2) {
236
277
  }
237
278
  const first = fs(p);
238
279
  if (!empties(p) || i + 1 === parsers.length) return first;
239
- return union(first, afterBoundary(i + 1));
280
+ return union((consuming ? p._meta.consumingFirstSet : void 0) ?? first, afterBoundary(i + 1));
240
281
  };
241
282
  return at(0);
242
283
  }
243
284
  function sequenceFirstSet(parsers) {
244
- return sequenceSet(parsers, (p) => p._meta.firstSet, matchesEmpty);
285
+ return sequenceSet(parsers, (p) => p._meta.firstSet, matchesEmpty, void 0, false);
245
286
  }
246
- function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve, activeTrivia) {
287
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve, activeTrivia, consuming = true) {
247
288
  if (seen.has(p)) return any();
248
289
  seen.add(p);
249
290
  try {
250
- return firstSetBody(p, seen, resolve, activeTrivia);
291
+ return firstSetBody(p, seen, resolve, activeTrivia, consuming);
251
292
  } finally {
252
293
  seen.delete(p);
253
294
  }
254
295
  }
255
- function firstSetBody(p, seen, resolve, activeTrivia) {
256
- const fs = (c) => firstSetOf(c, seen, resolve, activeTrivia);
296
+ function firstSetBody(p, seen, resolve, activeTrivia, consuming) {
297
+ const fs = (c) => firstSetOf(c, seen, resolve, activeTrivia, consuming);
257
298
  const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve);
258
299
  const d = p._def;
259
300
  switch (d.tag) {
@@ -265,13 +306,16 @@ function firstSetBody(p, seen, resolve, activeTrivia) {
265
306
  case "dispatch":
266
307
  return fs(d.selector);
267
308
  case "lazy": {
268
- const w = winnerForRef(p, resolve);
269
- if (w !== void 0) return fs(w);
270
- try {
271
- return fs(d.thunk());
272
- } catch {
273
- return any();
309
+ let target = winnerForRef(p, resolve);
310
+ if (target === void 0) {
311
+ try {
312
+ target = d.thunk();
313
+ } catch {
314
+ return any();
315
+ }
274
316
  }
317
+ const through = activeTrivia === null && resolve !== void 0;
318
+ return firstSetOf(target, seen, resolve, through ? triviaThroughRef(p, target, null) : activeTrivia, consuming);
275
319
  }
276
320
  case "choice": {
277
321
  let out = empty();
@@ -279,7 +323,7 @@ function firstSetBody(p, seen, resolve, activeTrivia) {
279
323
  return out;
280
324
  }
281
325
  case "sequence": {
282
- return sequenceSet(d.parsers, fs, empties, activeTrivia);
326
+ return sequenceSet(d.parsers, fs, empties, activeTrivia, consuming);
283
327
  }
284
328
  case "peek": {
285
329
  const inner = d.parser;
@@ -291,7 +335,6 @@ function firstSetBody(p, seen, resolve, activeTrivia) {
291
335
  case "transform":
292
336
  case "label":
293
337
  case "trivia":
294
- case "token":
295
338
  case "leaf":
296
339
  case "node":
297
340
  case "sepBy":
@@ -301,12 +344,16 @@ function firstSetBody(p, seen, resolve, activeTrivia) {
301
344
  // with a rule reference — resolve through it like any other wrapper.
302
345
  case "attempt":
303
346
  return fs(d.parser);
347
+ // A token runs its body with trivia cleared (`token.ts`).
348
+ case "token":
349
+ return firstSetOf(d.parser, seen, resolve, activeTrivia === void 0 ? void 0 : null, consuming);
304
350
  case "grammar":
305
351
  return firstSetOf(
306
352
  d.parser,
307
353
  seen,
308
354
  resolve,
309
- d.clearTrivia ? null : d.triviaParser ?? activeTrivia
355
+ d.clearTrivia ? null : d.triviaParser ?? activeTrivia,
356
+ consuming
310
357
  );
311
358
  // A run starts with its first line's prefix; a nullable prefix starts with anything.
312
359
  case "lines": {
@@ -1783,10 +1830,6 @@ function scanTriviaCompact(input, cur, ctx) {
1783
1830
  const tr = parseTriviaNoCapture(triviaP, input, cur, ctx);
1784
1831
  return { end: tr.ok ? tr.span.end : cur, commit: NOOP_COMMIT };
1785
1832
  }
1786
- function scanTrivia(input, cur, ctx) {
1787
- const scan = scanTriviaCompact(input, cur, ctx);
1788
- return typeof scan === "number" ? { end: scan, commit: NOOP_COMMIT } : scan;
1789
- }
1790
1833
  function skipTriviaScanned(s, input, cur, ctx) {
1791
1834
  const end = s(input, cur);
1792
1835
  if (end !== cur) {
@@ -3093,9 +3136,9 @@ function many(combinator, opts = {}) {
3093
3136
  canMatchNewline: combinator._meta.canMatchNewline,
3094
3137
  isTrivia: false
3095
3138
  };
3096
- const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
3097
- let expected;
3098
3139
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
3140
+ const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max }, ...guardable ? { guarded: true } : {} };
3141
+ let expected;
3099
3142
  const scalarChild = scalarOf(combinator);
3100
3143
  const parseScalar = (input, pos, ctx) => repeatScalarTail(combinator, scalarChild, input, pos, ctx, guardable, def.valueUnused ? void 0 : [], max);
3101
3144
  return {
@@ -3142,9 +3185,9 @@ function atLeast(combinator, min, max) {
3142
3185
  canMatchNewline: combinator._meta.canMatchNewline,
3143
3186
  isTrivia: false
3144
3187
  };
3145
- const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
3146
- let expected;
3147
3188
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
3189
+ const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max }, ...guardable ? { guarded: true } : {} };
3190
+ let expected;
3148
3191
  const scalarChild = scalarOf(combinator);
3149
3192
  const parseScalar = (input, pos, ctx) => {
3150
3193
  let cur = scalarChild(input, pos, ctx);
@@ -3632,10 +3675,12 @@ function parseRegex(src, unicode = false, sets = false) {
3632
3675
  const parseGroup = () => {
3633
3676
  i++;
3634
3677
  let lookaround = false;
3678
+ let lookahead = false;
3635
3679
  if (src[i] === "?") {
3636
3680
  const c1 = src[i + 1];
3637
3681
  if (c1 === ":") i += 2;
3638
3682
  else if (c1 === "=" || c1 === "!") {
3683
+ lookahead = c1 === "=";
3639
3684
  i += 2;
3640
3685
  lookaround = true;
3641
3686
  } else if (c1 === "<") {
@@ -3653,7 +3698,8 @@ function parseRegex(src, unicode = false, sets = false) {
3653
3698
  const inner = parseAlt();
3654
3699
  if (src[i] !== ")") throw BAIL;
3655
3700
  i++;
3656
- return lookaround ? EMPTY2 : inner;
3701
+ if (lookaround) return lookahead ? { t: "empty", look: inner } : EMPTY2;
3702
+ return inner;
3657
3703
  };
3658
3704
  const parseClass = () => {
3659
3705
  let k = i + 1;
@@ -3846,6 +3892,13 @@ function regexCanMatchEmpty(source) {
3846
3892
  return true;
3847
3893
  }
3848
3894
  }
3895
+ function consumingFirstSetFromRegex(source, flags = "") {
3896
+ try {
3897
+ return firstSet(parseRegex(source, flags.includes("u") || flags.includes("v"), flags.includes("v")));
3898
+ } catch {
3899
+ return any();
3900
+ }
3901
+ }
3849
3902
  function firstSetFromRegex(source, flags = "") {
3850
3903
  let ast;
3851
3904
  try {
@@ -3856,6 +3909,20 @@ function firstSetFromRegex(source, flags = "") {
3856
3909
  if (nullable(ast)) return { firstSet: any(), canMatchNewline: canConsumeNewline(ast) };
3857
3910
  return { firstSet: firstSet(ast), canMatchNewline: canConsumeNewline(ast) };
3858
3911
  }
3912
+ function regexFirstTwo(source, flags = "") {
3913
+ let ast;
3914
+ try {
3915
+ ast = parseRegex(source, flags.includes("u") || flags.includes("v"), flags.includes("v"));
3916
+ } catch {
3917
+ return void 0;
3918
+ }
3919
+ const parts = ast.t === "seq" ? ast.parts : [ast];
3920
+ const a = parts[0];
3921
+ const b = parts[1];
3922
+ if (a === void 0 || a.t !== "char" || a.set.kind !== "ranges" || b === void 0) return void 0;
3923
+ const second = b.t === "char" ? b.set : b.t === "empty" && b.look !== void 0 && !nullable(b.look) ? firstSet(b.look) : void 0;
3924
+ return second !== void 0 && second.kind === "ranges" ? { first: a.set, second } : void 0;
3925
+ }
3859
3926
 
3860
3927
  // src/combinators/regex.ts
3861
3928
  import { PlainLeaf as PlainLeaf3, PlainSpan as PlainSpan5 } from "../cst/plain.js";
@@ -3936,9 +4003,14 @@ function regex(pattern, flags = "") {
3936
4003
  const anchored = new RegExp(source, "y" + resolvedFlags.replace(/[gy]/g, ""));
3937
4004
  const scan = shortScanner(source, resolvedFlags);
3938
4005
  const raw = firstSetFromRegex(source, resolvedFlags);
3939
- const firstSet2 = !resolvedFlags.includes("i") ? raw.firstSet : resolvedFlags.includes("u") || resolvedFlags.includes("v") ? any() : asciiCaseFold(raw.firstSet);
4006
+ const forFlags = (fs) => !resolvedFlags.includes("i") ? fs : resolvedFlags.includes("u") || resolvedFlags.includes("v") ? any() : asciiCaseFold(fs);
4007
+ const firstSet2 = forFlags(raw.firstSet);
3940
4008
  const canMatchNewline = raw.canMatchNewline;
3941
4009
  const meta = { firstSet: firstSet2, canMatchNewline, isTrivia: false };
4010
+ if (firstSet2.kind === "any" && regexCanMatchEmpty(source)) {
4011
+ const consuming = forFlags(consumingFirstSetFromRegex(source, resolvedFlags));
4012
+ if (consuming.kind !== "any") meta.consumingFirstSet = consuming;
4013
+ }
3942
4014
  const def = { tag: "regex", source, flags: resolvedFlags };
3943
4015
  const expected = directTerminalFailureExpected(def);
3944
4016
  const parseScalar = (input, pos, ctx) => {
@@ -4213,14 +4285,15 @@ function choiceNode(arms, classes, armExpected, choiceExpected) {
4213
4285
  return end < 0 ? next(input, pos) : end;
4214
4286
  };
4215
4287
  }
4216
- let tail = (_input, pos, ctx, _lead, acc) => {
4288
+ let tail = (input, pos, ctx, _lead, acc, _best, entered) => {
4289
+ if (!entered && ctx._probe !== void 0) for (const arm of arms) arm.run(input, pos, ctx);
4217
4290
  ctx._fe = pos;
4218
4291
  ctx._fx = acc ?? choiceExpected;
4219
4292
  return -1;
4220
4293
  };
4221
4294
  for (let i = arms.length - 1; i >= 0; i--) {
4222
4295
  const armRun = arms[i].run, cls = classes[i], staticExpected = armExpected[i], next = tail;
4223
- tail = (input, pos, ctx, lead2, acc, best) => {
4296
+ tail = (input, pos, ctx, lead2, acc, best, entered) => {
4224
4297
  if (cls !== void 0 && !classHas(cls, lead2)) {
4225
4298
  return next(
4226
4299
  input,
@@ -4228,7 +4301,8 @@ function choiceNode(arms, classes, armExpected, choiceExpected) {
4228
4301
  ctx,
4229
4302
  lead2,
4230
4303
  best === pos ? appendExpected(acc, staticExpected) : acc,
4231
- best
4304
+ best,
4305
+ entered
4232
4306
  );
4233
4307
  }
4234
4308
  ctx._fc = false;
@@ -4244,7 +4318,7 @@ function choiceNode(arms, classes, armExpected, choiceExpected) {
4244
4318
  if (nextAcc !== void 0) ctx._fx = nextAcc;
4245
4319
  return -1;
4246
4320
  }
4247
- return next(input, pos, ctx, lead2, nextAcc, best);
4321
+ return next(input, pos, ctx, lead2, nextAcc, best, true);
4248
4322
  };
4249
4323
  }
4250
4324
  return {
@@ -4255,7 +4329,8 @@ function choiceNode(arms, classes, armExpected, choiceExpected) {
4255
4329
  ctx,
4256
4330
  pos < input.length ? input.codePointAt(pos) : -1,
4257
4331
  void 0,
4258
- pos
4332
+ pos,
4333
+ false
4259
4334
  )
4260
4335
  };
4261
4336
  }
@@ -4293,6 +4368,7 @@ function exclusiveChoiceNode(arms, classes, choiceFx) {
4293
4368
  return -1;
4294
4369
  }
4295
4370
  }
4371
+ if (arm < 0 && ctx._probe !== void 0) for (const a of arms) a.run(input, pos, ctx);
4296
4372
  ctx._fe = pos;
4297
4373
  ctx._fx = choiceFx;
4298
4374
  return -1;
@@ -4517,6 +4593,7 @@ function buildLexProgramRunner(spec, matchers, classes, expected) {
4517
4593
  if (!classHas(cls, lead2)) {
4518
4594
  ctx._fe = pos;
4519
4595
  ctx._fx = choiceFx.slice();
4596
+ if (ctx._probe !== void 0) failAt(ctx, [...x0, ...x1, ...x2, ...x3], pos);
4520
4597
  return -1;
4521
4598
  }
4522
4599
  let acc;
@@ -4628,7 +4705,7 @@ function validateDispatchSpec(spec, arity, fallbackRouted) {
4628
4705
  for (const arm of arms) if (!Number.isInteger(arm) || arm < 0 || arm >= arity) return true;
4629
4706
  return false;
4630
4707
  };
4631
- if (spec === void 0 || !Number.isInteger(arity) || arity < 0 || fallbackRouted !== 0 && fallbackRouted !== 1 || spec.routed.length !== arity || spec.routed.some((value) => value !== 0 && value !== 1) || invalid(spec.byKey.values()) || invalid(spec.byFold.values()) || spec.match.some((matcher) => invalid([matcher[3]]))) {
4708
+ if (spec === void 0 || !Number.isInteger(arity) || arity < 0 || !Number.isInteger(fallbackRouted) || fallbackRouted < 0 || fallbackRouted > 3 || spec.routed.length !== arity || spec.routed.some((value) => value !== 0 && value !== 1) || invalid(spec.byKey.values()) || invalid(spec.byFold.values()) || spec.match.some((matcher) => invalid([matcher[3]]))) {
4632
4709
  throw new TypeError("table: malformed dispatch");
4633
4710
  }
4634
4711
  }
@@ -4942,7 +5019,7 @@ function sequence(...parsers) {
4942
5019
  if (tolerant) {
4943
5020
  followSentinels ??= parsers.map((_, i) => firstSetSentinel(
4944
5021
  parsers.slice(i + 1).reduce(
4945
- (set, parser2) => union(set, firstSetOf(parser2)),
5022
+ (set, parser2) => union(set, firstSetOf(parser2, /* @__PURE__ */ new Set(), void 0, void 0, false)),
4946
5023
  { kind: "empty" }
4947
5024
  )
4948
5025
  ));
@@ -5398,7 +5475,7 @@ function attempt(parser2, options) {
5398
5475
  return {
5399
5476
  _tag: "attempt",
5400
5477
  _meta: parser2._meta,
5401
- _def: contain ? { tag: "attempt", parser: parser2, contain: true } : { tag: "attempt", parser: parser2 },
5478
+ _def: { tag: "attempt", parser: parser2, ...contain ? { contain: true } : {}, ...guardable ? { guarded: true } : {} },
5402
5479
  parse(input, pos, ctx) {
5403
5480
  if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(parser2, input, pos)) {
5404
5481
  if (failExpected === void 0) {
@@ -6215,6 +6292,7 @@ function reachesMarked(root, successors, memo) {
6215
6292
  }
6216
6293
 
6217
6294
  // src/table/factor-choice.ts
6295
+ var factoredChoices = /* @__PURE__ */ new WeakSet();
6218
6296
  var DERIVED = /* @__PURE__ */ new Set(["_tag", "_meta", "_def", "_parseScalar", "_stickyRegex", "parse"]);
6219
6297
  function isCombinator(v) {
6220
6298
  return typeof v === "object" && v !== null && "_def" in v && "_meta" in v;
@@ -6306,6 +6384,7 @@ function factorSharedEnds(d) {
6306
6384
  for (let i = at + 1; i < n; i++) if (!never(first.terms[i])) return void 0;
6307
6385
  if (arms.some((a) => mayCommitFailure(a))) return void 0;
6308
6386
  const middle = choice(...shapes.map((s) => s.terms[at]));
6387
+ factoredChoices.add(middle);
6309
6388
  if (middle._def.tag === "choice" && middle._def.autoNot.some((a) => a !== null)) return void 0;
6310
6389
  const st = middle._def.tag === "choice" ? middle._def.strategy : void 0;
6311
6390
  if (st === void 0 || st.tag !== "firstMatch" && st.tag !== "sharedPrefix" && !(st.tag === "literalsLongestFirst" && st.sortedIndices.every((x, i) => x === i))) return void 0;
@@ -6318,6 +6397,37 @@ function factorSharedEnds(d) {
6318
6397
  for (let j = first.wrappers.length - 1; j >= 0 && out !== void 0; j--) out = rewrap(first.wrappers[j], out);
6319
6398
  return out === void 0 ? void 0 : attempt(out);
6320
6399
  }
6400
+ function factorPrefixArms(d) {
6401
+ const arms = d.parsers;
6402
+ if (arms.length < 2) return void 0;
6403
+ if (d.strategy.tag !== "firstMatch" && d.strategy.tag !== "sharedPrefix") return void 0;
6404
+ if (d.gates.some((g) => g !== null) || d.autoNot.some((a) => a !== null)) return void 0;
6405
+ const out = [...arms];
6406
+ let changed = false;
6407
+ for (let i = 0; i + 1 < arms.length; i++) {
6408
+ const arm = arms[i];
6409
+ if (arm._def.tag !== "sequence" || arm._def.valueUnused !== true) continue;
6410
+ const terms = arm._def.parsers;
6411
+ const next = arms[i + 1];
6412
+ const prefix = next._def.tag === "sequence" && next._def.valueUnused === true ? next._def.parsers : [next];
6413
+ if (prefix.length >= terms.length) continue;
6414
+ if (!prefix.every((t2, j) => sameComb(t2, terms[j], 0))) continue;
6415
+ const rest = terms.slice(prefix.length);
6416
+ if (rest.every(never)) continue;
6417
+ const tail = rest.length === 1 ? rest[0] : sequence(...rest);
6418
+ if (tail !== rest[0]) tail._def.valueUnused = true;
6419
+ const parts = [...terms.slice(0, prefix.length), optional(tail)];
6420
+ const seq = sequence(...parts);
6421
+ seq._def.valueUnused = true;
6422
+ out[i] = seq;
6423
+ changed = true;
6424
+ }
6425
+ if (!changed) return void 0;
6426
+ const rebuilt = choice(...out);
6427
+ factoredChoices.add(rebuilt);
6428
+ const st = rebuilt._def.tag === "choice" ? rebuilt._def.strategy : void 0;
6429
+ return st !== void 0 && (st.tag === "firstMatch" || st.tag === "sharedPrefix") ? rebuilt : void 0;
6430
+ }
6321
6431
 
6322
6432
  // src/compiler/fields.ts
6323
6433
  function buildReadsFields(def) {
@@ -6503,6 +6613,9 @@ var OP_LEX_BODY = 42;
6503
6613
  var OP_LEX_PROGRAM = 43;
6504
6614
  var OP_SOURCE_LEAF = 44;
6505
6615
  var OP_RGATE = 45;
6616
+ var OP_GAPGATE = 46;
6617
+ var OP_RGATE2 = 47;
6618
+ var OP_STRICT = 48;
6506
6619
  var OP_DISPATCH = 25;
6507
6620
  var OP_ROUTED = 26;
6508
6621
  var OP_NAMES = {
@@ -6550,7 +6663,10 @@ var OP_NAMES = {
6550
6663
  [OP_COV]: "COV",
6551
6664
  [OP_SCOPE_PLAIN]: "SCOPE_PLAIN",
6552
6665
  [OP_LEX_BODY]: "LEX_BODY",
6553
- [OP_LEX_PROGRAM]: "LEX_PROGRAM"
6666
+ [OP_LEX_PROGRAM]: "LEX_PROGRAM",
6667
+ [OP_GAPGATE]: "GAPGATE",
6668
+ [OP_RGATE2]: "RGATE2",
6669
+ [OP_STRICT]: "STRICT"
6554
6670
  };
6555
6671
 
6556
6672
  // src/combinators/node.ts
@@ -6595,7 +6711,7 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
6595
6711
  if (project !== void 0 && build !== void 0) {
6596
6712
  throw new Error("node() options cannot combine project with a build callback");
6597
6713
  }
6598
- const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build } };
6714
+ const baseDef = { tag: "node", parser: combinator, ...type === void 0 ? {} : { type }, ...build === void 0 ? {} : { build }, ...combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator) ? { guarded: true } : {} };
6599
6715
  const meta = {
6600
6716
  firstSet: combinator._meta.firstSet,
6601
6717
  canMatchNewline: combinator._meta.canMatchNewline,
@@ -6670,42 +6786,6 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
6670
6786
  };
6671
6787
  }
6672
6788
 
6673
- // src/combinators/trivia-boundary.ts
6674
- var MAX_DEPTH = 12;
6675
- function hasOwnTriviaBoundary(p, depth = MAX_DEPTH) {
6676
- if (depth === 0) return true;
6677
- const d = p._def;
6678
- switch (d.tag) {
6679
- // Multi-term forms: the gaps BETWEEN terms are the ambient-trivia positions.
6680
- case "sequence":
6681
- return d.parsers.length > 1;
6682
- case "many":
6683
- case "oneOrMore":
6684
- case "sepBy":
6685
- return true;
6686
- // Transparent wrappers — they add no position, so ask what they wrap.
6687
- case "node":
6688
- case "transform":
6689
- case "leaf":
6690
- case "label":
6691
- case "field":
6692
- case "token":
6693
- case "attempt":
6694
- case "optional":
6695
- case "expect":
6696
- case "withCtx":
6697
- case "recover":
6698
- case "trivia":
6699
- return hasOwnTriviaBoundary(d.parser, depth - 1);
6700
- // An alternation adds no position either, but ONE arm that has one is enough
6701
- // to make the scope meaningful for this rule.
6702
- case "choice":
6703
- return d.parsers.some((a) => hasOwnTriviaBoundary(a, depth - 1));
6704
- default:
6705
- return false;
6706
- }
6707
- }
6708
-
6709
6789
  // src/compiler/direct-projection.ts
6710
6790
  function directArrayProjection(fn, fnSrc, arity) {
6711
6791
  let source = fnSrc;
@@ -6994,6 +7074,7 @@ function directFixedLexNode(parser2, resolve, active, pureOnly = false, cleared
6994
7074
  arms,
6995
7075
  parsers: def.parsers,
6996
7076
  cleared,
7077
+ disjoint: def.disjoint,
6997
7078
  armExpected: def.parsers.map(deriveExpected),
6998
7079
  expected: def.parsers.flatMap(deriveExpected)
6999
7080
  };
@@ -7103,6 +7184,29 @@ var UnsupportedConstruct = class extends Error {
7103
7184
  this.tag = tag;
7104
7185
  }
7105
7186
  };
7187
+ function firstTwo(p) {
7188
+ const d = p._def;
7189
+ if (d.tag === "label" || d.tag === "grammar") return firstTwo(d.parser);
7190
+ let two;
7191
+ if (d.tag === "literal" && d.value.length >= 2 && d.caseInsensitive !== true) {
7192
+ const second = d.value.codePointAt(1);
7193
+ two = { first: { kind: "ranges", ranges: [{ lo: d.value.charCodeAt(0), hi: d.value.charCodeAt(0) }] }, second: { kind: "ranges", ranges: [{ lo: second, hi: second }] } };
7194
+ } else if (d.tag === "regex" && !d.flags.includes("i")) {
7195
+ const units = regexFirstTwo(d.source, d.flags);
7196
+ if (units !== void 0) two = units;
7197
+ }
7198
+ if (two === void 0 || two.first.kind !== "ranges") return void 0;
7199
+ return two.first.ranges.every((r) => r.hi < 55296 || r.lo > 57343 && r.hi <= 65535) ? two : void 0;
7200
+ }
7201
+ var GAP_CHARS = /* @__PURE__ */ new Set([9, 10, 12, 13, 32, 47]);
7202
+ function isGap(p) {
7203
+ if (matchesEmpty(p)) return true;
7204
+ const fs = p._meta.firstSet;
7205
+ return fs.kind === "ranges" && fs.ranges.every((r) => {
7206
+ for (let c = r.lo; c <= r.hi; c++) if (!GAP_CHARS.has(c)) return false;
7207
+ return true;
7208
+ });
7209
+ }
7106
7210
  var Encoder = class {
7107
7211
  code = [];
7108
7212
  k = [];
@@ -7150,8 +7254,33 @@ var Encoder = class {
7150
7254
  */
7151
7255
  commitFailureMemo = /* @__PURE__ */ new Map();
7152
7256
  labels = void 0;
7257
+ /** The choice being encoded as written, for `OP_STRICT`'s tolerant child. */
7258
+ unfactoredChoice = void 0;
7153
7259
  /** Trivia table in scope at the row being encoded — codegen's `ctx.activeTrivia`. */
7154
7260
  activeTrivia = void 0;
7261
+ /**
7262
+ * Is the trivia live at this row known? It is not in the body of an entry
7263
+ * that installs none of its own: that body runs under whatever trivia its
7264
+ * caller has (a compiled rule inside another grammar, `run({ trivia })`).
7265
+ * Every scope (`parser({ trivia })`, `noTrivia`, `token()`, a rule's install)
7266
+ * makes it known again.
7267
+ */
7268
+ triviaKnown = true;
7269
+ /**
7270
+ * The trivia a gate's first set is derived under: the live table, `null` for a
7271
+ * scope known to have none, or `undefined` where it is not known.
7272
+ */
7273
+ gateTrivia() {
7274
+ return this.activeTrivia ?? (this.triviaKnown ? null : void 0);
7275
+ }
7276
+ /**
7277
+ * Whether a gate may read a nullable pad through its non-empty matches
7278
+ * (`firstSetOf`'s `consuming`): only where the trivia that may follow the pad
7279
+ * is known. Elsewhere a gate reads first sets as 0.58.0 did.
7280
+ */
7281
+ gateConsuming() {
7282
+ return this.activeTrivia !== void 0 || this.triviaKnown;
7283
+ }
7155
7284
  classified = false;
7156
7285
  scans = [];
7157
7286
  /** Ambient `scanSkip` sets, pooled by the ARRAY's identity (stable per grammar). */
@@ -7278,10 +7407,12 @@ var Encoder = class {
7278
7407
  const final = classifyFinalChoice(
7279
7408
  node2.parsers,
7280
7409
  this.refResolver(),
7281
- node2.cleared ? void 0 : this.activeTrivia
7410
+ node2.cleared ? null : this.gateTrivia(),
7411
+ false,
7412
+ node2.disjoint
7282
7413
  );
7283
7414
  const classes = final.firstSets.map((first, i) => final.nullable[i] ? -1 : this.charClass(first));
7284
- const exclusive = final.exclusive && classes.every((cls) => cls >= 0);
7415
+ const exclusive = final.exclusive && classes.every((cls) => cls >= 0) && this.wideArms(node2.parsers, final.firstSets, node2.disjoint, node2.cleared ? null : this.gateTrivia(), node2.cleared ? void 0 : this.activeTrivia).length === 0;
7285
7416
  words.push(LEX_NODE_CHOICE, node2.arms.length, exclusive ? 1 : 0, this.expected(node2.expected));
7286
7417
  for (let i = 0; i < node2.arms.length; i++) {
7287
7418
  words.push(classes[i], this.expected(node2.armExpected[i]));
@@ -7436,16 +7567,117 @@ var Encoder = class {
7436
7567
  kIndex = /* @__PURE__ */ new Map();
7437
7568
  ccIndex = /* @__PURE__ */ new Map();
7438
7569
  fxIndex = /* @__PURE__ */ new Map();
7439
- /** Memoized by combinator identity: a shared sub-combinator is ONE row. */
7440
- memo = /* @__PURE__ */ new Map();
7441
7570
  /**
7442
- * Memo for `lazy` REFERENCES, keyed by the trivia scope the reference is made
7443
- * from. A cross-rule reference's row depends on that scope (`scopedRef`), so the
7444
- * single `memo` slot every other construct uses would freeze the FIRST
7445
- * reference's scope onto every later one and undo the fix.
7571
+ * Memoized by combinator identity AND the trivia scope it is encoded under
7572
+ * (`scopeKey`): a shared sub-combinator is ONE row per scope it runs in.
7573
+ *
7574
+ * A construct's rows depend on that scope. A first set derived through a
7575
+ * nullable term takes in the trivia that may follow it, a gap gate holds only
7576
+ * where no trivia runs, a routed gate reads what may follow its token, and a
7577
+ * cross-rule reference installs its rule's trivia only from a cleared region
7578
+ * (`scopedRef`). One memo slot per combinator froze the FIRST scope it was
7579
+ * reached under onto every later one: a rule body encoded as an entry with no
7580
+ * trivia, then run under a caller's comment trivia, gated its repeats as if no
7581
+ * comment could come between their terms.
7446
7582
  */
7447
- refMemo = /* @__PURE__ */ new Map();
7583
+ memo = /* @__PURE__ */ new Map();
7448
7584
  pending = /* @__PURE__ */ new Map();
7585
+ triviaSignatures = /* @__PURE__ */ new Map();
7586
+ /**
7587
+ * What of the live trivia an encoding can depend on: none, or its first set.
7588
+ * Every derivation reads trivia only through `firstSetOf(trivia)` or whether it
7589
+ * is live, so two scopes whose trivia opens on the same characters encode the
7590
+ * same rows and share them. A construct that reads no scope (`readsScope`)
7591
+ * has one encoding.
7592
+ */
7593
+ scopeKey(p) {
7594
+ const t2 = this.activeTrivia;
7595
+ if (!this.readsScope(p)) return "";
7596
+ if (t2 === void 0) return this.triviaKnown ? "" : "?";
7597
+ let sig = this.triviaSignatures.get(t2);
7598
+ if (sig === void 0) {
7599
+ sig = `t${JSON.stringify(firstSetOf(t2, /* @__PURE__ */ new Set(), this.refResolver()))}`;
7600
+ this.triviaSignatures.set(t2, sig);
7601
+ }
7602
+ return sig;
7603
+ }
7604
+ readsScopeMemo = /* @__PURE__ */ new Map();
7605
+ /**
7606
+ * Can `p`'s rows depend on the trivia scope it is encoded under? No for a
7607
+ * terminal, a scope of its own (`parser({ trivia })`, `noTrivia`, `token()`),
7608
+ * and a wrapper over a construct that reads none; yes wherever a first set, a gate or
7609
+ * a reference's install is derived with the scope: a sequence with a nullable
7610
+ * term before another (the trivia between them), a routed arm (the trivia after
7611
+ * its token), a reference that installs its rule's trivia from a cleared
7612
+ * region, and anything not modelled here — a cycle included.
7613
+ */
7614
+ readsScope(p) {
7615
+ const hit = this.readsScopeMemo.get(p);
7616
+ if (hit !== void 0) return hit;
7617
+ this.readsScopeMemo.set(p, true);
7618
+ const d = p._def;
7619
+ const rr = this.refResolver();
7620
+ let out;
7621
+ switch (d.tag) {
7622
+ case "literal":
7623
+ case "regex":
7624
+ case "keywords":
7625
+ case "token":
7626
+ out = false;
7627
+ break;
7628
+ // `routed()` encodes its fallback under the scope that reaches it.
7629
+ case "routed":
7630
+ out = d.fallback !== void 0 && this.readsScope(d.fallback);
7631
+ break;
7632
+ case "grammar":
7633
+ out = d.clearTrivia !== true && d.triviaParser === void 0 && this.readsScope(d.parser);
7634
+ break;
7635
+ case "node":
7636
+ case "label":
7637
+ case "leaf":
7638
+ case "transform":
7639
+ case "attempt":
7640
+ case "expect":
7641
+ case "not":
7642
+ case "peek":
7643
+ case "optional":
7644
+ case "many":
7645
+ case "oneOrMore":
7646
+ case "field":
7647
+ out = this.readsScope(d.parser);
7648
+ break;
7649
+ case "sepBy":
7650
+ out = this.readsScope(d.parser) || this.readsScope(d.separator);
7651
+ break;
7652
+ case "choice":
7653
+ out = d.parsers.some((a) => this.readsScope(a));
7654
+ break;
7655
+ case "dispatch":
7656
+ out = this.readsScope(d.selector) || d.cases.some((c) => this.readsScope(c.parser)) || (d.matchers?.some((m) => this.readsScope(m.parser)) ?? false) || d.otherwise !== void 0 && this.readsScope(d.otherwise);
7657
+ break;
7658
+ case "sequence": {
7659
+ const ps = d.parsers;
7660
+ out = ps.some((t2) => this.readsScope(t2)) || ps[0] !== void 0 && (ps[0]._def.tag === "routed" || ps[0]._def.tag === "regex" && isGap(ps[0])) || ps.some((t2, i) => i + 1 < ps.length && matchesEmpty(t2, /* @__PURE__ */ new Set(), rr));
7661
+ break;
7662
+ }
7663
+ case "lazy": {
7664
+ let target = winnerForRef(p, rr);
7665
+ if (target === void 0) {
7666
+ try {
7667
+ target = d.thunk();
7668
+ } catch {
7669
+ target = void 0;
7670
+ }
7671
+ }
7672
+ out = target === void 0 || triviaThroughRef(p, target, null) !== null || this.readsScope(target);
7673
+ break;
7674
+ }
7675
+ default:
7676
+ out = true;
7677
+ }
7678
+ this.readsScopeMemo.set(p, out);
7679
+ return out;
7680
+ }
7449
7681
  settings;
7450
7682
  /** Resolved once, HERE, at table-build time — never consulted at run time. */
7451
7683
  track;
@@ -7473,7 +7705,7 @@ var Encoder = class {
7473
7705
  let fs = { kind: "empty" };
7474
7706
  const rr = this.refResolver();
7475
7707
  for (let j = i + 1; j < parsers.length; j++) {
7476
- fs = union(fs, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr, this.activeTrivia));
7708
+ fs = union(fs, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr, void 0, false));
7477
7709
  }
7478
7710
  return this.charClass(fs);
7479
7711
  }
@@ -7551,7 +7783,7 @@ var Encoder = class {
7551
7783
  * simply parses it with the gate switched off at every choice arm that reaches
7552
7784
  * a hole.
7553
7785
  *
7554
- * Measured on jess's less grammar (`bench/jess/macro-program-diff.ts`): 195 of
7786
+ * Measured on jess's less grammar (with the one-off `macro-program-diff` bench, last at `4a5d0341`): 195 of
7555
7787
  * 562 reachable choice arms carried NO first set in the shipped artifact
7556
7788
  * against 103 of 540 in the runtime encode, and 10 dispatch sites lost the O(1)
7557
7789
  * `exclusive` piece (`assemble.ts:1758`) for the candidate-mask loop. With this
@@ -7641,7 +7873,7 @@ var Encoder = class {
7641
7873
  if (op === OP_GATE || op === OP_LIT || op === OP_LIT_CI || op === OP_LIT_TRACK || op === OP_LIT_CI_TRACK || op === OP_RX || op === OP_RX_TRACK) return child;
7642
7874
  const rr = this.refResolver();
7643
7875
  if (matchesEmpty(p, /* @__PURE__ */ new Set(), rr)) return child;
7644
- const cls = this.charClass(firstSetOf(p, /* @__PURE__ */ new Set(), rr, this.activeTrivia));
7876
+ const cls = this.charClass(firstSetOf(p, /* @__PURE__ */ new Set(), rr, this.activeTrivia, false));
7645
7877
  if (cls < 0) return child;
7646
7878
  const e = deriveExpected(p);
7647
7879
  return this.emit(OP_GATE, cls, child, this.expected(e.length > 0 ? e : [p._tag]));
@@ -7667,9 +7899,27 @@ var Encoder = class {
7667
7899
  * `{`, `Call` at `}`). The resolved set is the one a choice arm's gate uses,
7668
7900
  * over the final merged map.
7669
7901
  */
7670
- gateFirstSet(p) {
7902
+ /**
7903
+ * The arms whose first set under `gateTrivia()` is wider than under the
7904
+ * active trivia alone — a reference's installed trivia read through from a
7905
+ * region with none. Empty for construction-set (disjoint) choices, whose
7906
+ * sets do not depend on trivia.
7907
+ */
7908
+ wideArms(arms, wide, constructionSets, wideTrivia = this.gateTrivia(), base = this.activeTrivia) {
7909
+ if (constructionSets || wideTrivia === base) return [];
7910
+ const narrow = classifyFinalChoice(arms, this.refResolver(), base, false, false).firstSets;
7911
+ const out = [];
7912
+ for (let i = 0; i < arms.length; i++) {
7913
+ if (this.charClass(wide[i]) !== this.charClass(narrow[i])) out.push(i);
7914
+ }
7915
+ return out;
7916
+ }
7917
+ gateFirstSet(p, guarded, narrowed) {
7671
7918
  const cached = p._meta.firstSet;
7672
- return p._def.tag === "lazy" || cached.kind === "any" ? firstSetOf(p, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia) : cached;
7919
+ if (p._def.tag !== "lazy" && cached.kind !== "any") return cached;
7920
+ const rr = this.refResolver();
7921
+ if (guarded) return firstSetOf(p, /* @__PURE__ */ new Set(), rr, void 0, false);
7922
+ return firstSetOf(p, /* @__PURE__ */ new Set(), rr, this.gateTrivia(), narrowed && this.gateConsuming());
7673
7923
  }
7674
7924
  /**
7675
7925
  * Wrap one choice arm in its `autoNot` checks (`OP_REJECT`).
@@ -7701,6 +7951,135 @@ var Encoder = class {
7701
7951
  for (const w of words) this.code.push(w);
7702
7952
  return ip;
7703
7953
  }
7954
+ /**
7955
+ * An optional repeat item tests the item's class before it runs it (`OP_REP`
7956
+ * ip+7), wherever the item's own first-set gate would also run. So a sequence
7957
+ * item whose first term is a gate on that same class gets its own copy of the
7958
+ * sequence row with the gate's child as that term: the test runs once. The
7959
+ * shared row is left as it is for its other references.
7960
+ */
7961
+ withoutLeadGate(seq, cls) {
7962
+ const op = this.code[seq];
7963
+ if (op !== OP_SEQ && op !== OP_SEQV) return seq;
7964
+ const first = this.code[seq + 2];
7965
+ if (this.code[first] !== OP_GATE || this.code[first + 1] !== cls) return seq;
7966
+ const n = this.code[seq + 1];
7967
+ const width = 1 + 2 * n;
7968
+ const head = this.emitHead(op, width);
7969
+ for (let i = 1; i <= width; i++) this.code[head + i] = this.code[seq + i];
7970
+ this.code[head + 2] = this.code[first + 2];
7971
+ return head;
7972
+ }
7973
+ /**
7974
+ * An optional repeat item that opens with a GAP — a regex that may match
7975
+ * empty, or one that starts only with whitespace or a comment's `/` — and
7976
+ * then a term with a finite first set gets an `OP_GAPGATE` row
7977
+ * (`gap-gate.ts`): the repeat reads the gap once, ends where the character
7978
+ * after it opens neither that term nor an arm without the gap, and hands the
7979
+ * gap's end to the regex row. Anything the walk cannot prove keeps the item as
7980
+ * written.
7981
+ */
7982
+ gapGated(item, ip) {
7983
+ if (this.activeTrivia === void 0 && !this.triviaKnown) return ip;
7984
+ const lead2 = this.gapLead(item, this.activeTrivia, 0);
7985
+ if (lead2 === void 0) return ip;
7986
+ const cls = this.charClass(lead2.follow);
7987
+ if (cls < 0) return ip;
7988
+ const other = lead2.other.kind === "empty" ? -1 : this.charClass(lead2.other);
7989
+ if (lead2.other.kind !== "empty" && other < 0) return ip;
7990
+ const p = this.node(lead2.p).ip;
7991
+ if (this.code[p] !== OP_RX && this.code[p] !== OP_RX_TRACK) return ip;
7992
+ return this.emit(OP_GAPGATE, ip, p, cls, other);
7993
+ }
7994
+ /**
7995
+ * The gap an item opens with: the regex `p`, the first set `follow` of what
7996
+ * must come straight after it, and the first set `other` of choice arms that
7997
+ * do not open with `p`. Only through wrappers that consume nothing before
7998
+ * their body, a sequence's first term, and a choice's arms; the gap must sit
7999
+ * in a sequence with no trivia between its terms.
8000
+ */
8001
+ gapLead(c, trivia2, depth) {
8002
+ if (depth > 24) return void 0;
8003
+ const rr = this.refResolver();
8004
+ const d = c._def;
8005
+ switch (d.tag) {
8006
+ case "lazy": {
8007
+ let target = winnerForRef(c, rr);
8008
+ if (target === void 0) {
8009
+ try {
8010
+ target = d.thunk();
8011
+ } catch {
8012
+ return void 0;
8013
+ }
8014
+ }
8015
+ return this.gapLead(target, triviaThroughRef(c, target, trivia2), depth + 1);
8016
+ }
8017
+ case "node":
8018
+ case "leaf":
8019
+ case "label":
8020
+ case "attempt":
8021
+ return this.gapLead(d.parser, trivia2, depth + 1);
8022
+ case "field":
8023
+ return d.leadingTrivia === true ? void 0 : this.gapLead(d.parser, trivia2, depth + 1);
8024
+ case "grammar":
8025
+ return this.gapLead(d.parser, d.clearTrivia === true ? null : d.triviaParser ?? trivia2, depth + 1);
8026
+ case "sequence": {
8027
+ const [first, ...rest] = d.parsers;
8028
+ if (first === void 0) return void 0;
8029
+ if (first._def.tag !== "regex" || !isGap(first)) {
8030
+ return this.gapLead(first, trivia2, depth + 1);
8031
+ }
8032
+ if (trivia2 !== void 0 && trivia2 !== null || rest.length === 0) return void 0;
8033
+ let follow = empty();
8034
+ let closed = false;
8035
+ for (const t2 of rest) {
8036
+ const fs = firstSetOf(t2, /* @__PURE__ */ new Set(), rr, null);
8037
+ if (fs.kind === "any") return void 0;
8038
+ follow = union(follow, fs);
8039
+ if (!matchesEmpty(t2, /* @__PURE__ */ new Set(), rr)) {
8040
+ closed = true;
8041
+ break;
8042
+ }
8043
+ }
8044
+ return closed ? { p: first, follow, other: empty() } : void 0;
8045
+ }
8046
+ case "choice": {
8047
+ let p;
8048
+ let follow = empty();
8049
+ let other = empty();
8050
+ for (const arm of d.parsers) {
8051
+ const lead2 = this.gapLead(arm, trivia2, depth + 1);
8052
+ if (lead2 !== void 0 && (p === void 0 || lead2.p === p)) {
8053
+ p = lead2.p;
8054
+ follow = union(follow, lead2.follow);
8055
+ other = union(other, lead2.other);
8056
+ continue;
8057
+ }
8058
+ if (matchesEmpty(arm, /* @__PURE__ */ new Set(), rr)) return void 0;
8059
+ const fs = firstSetOf(arm, /* @__PURE__ */ new Set(), rr, trivia2 ?? null);
8060
+ if (fs.kind === "any") return void 0;
8061
+ other = union(other, fs);
8062
+ }
8063
+ return p === void 0 ? void 0 : { p, follow, other };
8064
+ }
8065
+ default:
8066
+ return void 0;
8067
+ }
8068
+ }
8069
+ /**
8070
+ * The class a strict list may end on — a character its separator `p` (row
8071
+ * `ip`) cannot start — without entering the separator, or −1.
8072
+ *
8073
+ * The test runs before every separator, to save the one that fails at the
8074
+ * list's end, so it is laid down only where entering the separator costs more
8075
+ * than the test: not on a terminal row, which is itself one character test.
8076
+ */
8077
+ sepGate(p, ip) {
8078
+ const op = this.code[ip];
8079
+ if (op === OP_LIT || op === OP_LIT_CI || op === OP_RX || op === OP_LIT_TRACK || op === OP_RX_TRACK || op === OP_LIT_CI_TRACK) return -1;
8080
+ if (matchesEmpty(p, /* @__PURE__ */ new Set(), this.refResolver())) return -1;
8081
+ return this.charClass(firstSetOf(p, /* @__PURE__ */ new Set(), this.refResolver(), this.gateTrivia(), this.gateConsuming()));
8082
+ }
7704
8083
  /** Reserve `n` operand slots to patch once children are laid out. */
7705
8084
  emitHead(op, n) {
7706
8085
  const ip = this.code.length;
@@ -7744,8 +8123,10 @@ var Encoder = class {
7744
8123
  }
7745
8124
  encodeRule(name, p) {
7746
8125
  this.activeTrivia = p._meta.grammarTrivia;
8126
+ this.triviaKnown = p._meta.grammarTrivia !== void 0;
7747
8127
  const body = this.node(p).ip;
7748
8128
  this.activeTrivia = void 0;
8129
+ this.triviaKnown = true;
7749
8130
  const gss = p._meta.grammarScanSkip;
7750
8131
  this.scanSkipOf.push(gss === void 0 ? -1 : this.scanSkipSlot(gss));
7751
8132
  const amb = p._meta.grammarTrivia;
@@ -7791,11 +8172,15 @@ var Encoder = class {
7791
8172
  * silently produced the ONE keyword `bc`.
7792
8173
  */
7793
8174
  scopedRef(p, target) {
8175
+ const runsUnder = triviaThroughRef(p, target, this.activeTrivia);
8176
+ if (this.activeTrivia !== void 0 || runsUnder === void 0 || runsUnder === null) return this.node(target).ip;
8177
+ const savedKnown = this.triviaKnown;
8178
+ this.activeTrivia = runsUnder;
8179
+ this.triviaKnown = true;
7794
8180
  const ip = this.node(target).ip;
7795
- const amb = p._meta.grammarTrivia ?? target._meta.grammarTrivia;
7796
- if (amb === void 0 || this.activeTrivia !== void 0) return ip;
7797
- if (!hasOwnTriviaBoundary(target)) return ip;
7798
- return this.emit(OP_SCOPE_PLAIN, this.triviaSlot(amb), ip);
8181
+ this.activeTrivia = void 0;
8182
+ this.triviaKnown = savedKnown;
8183
+ return this.emit(OP_SCOPE_PLAIN, this.triviaSlot(runsUnder), ip);
7799
8184
  }
7800
8185
  /**
7801
8186
  * The `OP_RGATE` operands of a choice arm that opens with `routed()`, or
@@ -7836,38 +8221,43 @@ var Encoder = class {
7836
8221
  if (matchesEmpty(next, /* @__PURE__ */ new Set(), rr) || mayCommitFailure(next, rr, this.commitFailureMemo)) return void 0;
7837
8222
  const nextDef = next._def;
7838
8223
  if (nextDef.tag !== "literal" && nextDef.tag !== "regex" && nextDef.tag !== "keywords" && nextDef.tag !== "label") return void 0;
7839
- const first = firstSetOf(next, /* @__PURE__ */ new Set(), rr, boundary);
8224
+ if (boundary === void 0 && !this.triviaKnown) return void 0;
8225
+ const first = firstSetOf(next, /* @__PURE__ */ new Set(), rr, boundary ?? null);
7840
8226
  const cls = this.charClass(boundary === void 0 ? first : union(first, firstSetOf(boundary, /* @__PURE__ */ new Set(), rr)));
7841
8227
  if (cls < 0) return void 0;
7842
- const miss = nextDef.tag === "label" ? deriveExpected(next) : directTerminalFailureExpected(nextDef);
7843
- return { cls, fx: this.expected(miss) };
8228
+ const fx = this.expected(nextDef.tag === "label" ? deriveExpected(next) : directTerminalFailureExpected(nextDef));
8229
+ const two = firstTwo(next);
8230
+ if (two === void 0) return { cls, fx };
8231
+ if (boundary !== void 0 && intersects(two.first, firstSetOf(boundary, /* @__PURE__ */ new Set(), rr))) return { cls, fx };
8232
+ const a = this.charClass(two.first);
8233
+ const b = this.charClass(two.second);
8234
+ return a < 0 || b < 0 ? { cls, fx } : { cls, fx, two: [a, b] };
7844
8235
  }
7845
8236
  node(p) {
7846
- const lazyRef = p._def.tag === "lazy";
7847
- const scopeKey = lazyRef ? this.activeTrivia : void 0;
7848
- const refMemo = this.refMemo;
7849
- const hit = lazyRef ? refMemo.get(p)?.get(scopeKey) : this.memo.get(p);
8237
+ const key = this.scopeKey(p);
8238
+ let byScope = this.memo.get(p);
8239
+ const hit = byScope?.get(key);
7850
8240
  if (hit !== void 0) return { ip: hit };
7851
- const inFlight = this.pending.get(p);
8241
+ let inFlightByScope = this.pending.get(p);
8242
+ const inFlight = inFlightByScope?.get(key);
7852
8243
  if (inFlight !== void 0) {
7853
8244
  const ip2 = this.emitHead(OP_RULE, 1);
7854
8245
  inFlight.push(ip2 + 1);
7855
8246
  return { ip: ip2 };
7856
8247
  }
7857
8248
  const patches = [];
7858
- this.pending.set(p, patches);
8249
+ if (inFlightByScope === void 0) {
8250
+ inFlightByScope = /* @__PURE__ */ new Map();
8251
+ this.pending.set(p, inFlightByScope);
8252
+ }
8253
+ inFlightByScope.set(key, patches);
7859
8254
  const ip = this.covWrap(this.encodeDef(p), this.plan?.rules.get(p), 0);
7860
- this.pending.delete(p);
7861
- if (lazyRef) {
7862
- let byScope = refMemo.get(p);
7863
- if (byScope === void 0) {
7864
- byScope = /* @__PURE__ */ new Map();
7865
- refMemo.set(p, byScope);
7866
- }
7867
- byScope.set(scopeKey, ip);
7868
- } else {
7869
- this.memo.set(p, ip);
8255
+ inFlightByScope.delete(key);
8256
+ if (byScope === void 0) {
8257
+ byScope = /* @__PURE__ */ new Map();
8258
+ this.memo.set(p, byScope);
7870
8259
  }
8260
+ byScope.set(key, ip);
7871
8261
  for (const slot of patches) {
7872
8262
  if (slot - 1 === ip) {
7873
8263
  const rule2 = p._ruleName;
@@ -7965,8 +8355,19 @@ var Encoder = class {
7965
8355
  }
7966
8356
  case "choice": {
7967
8357
  if (this.plan === void 0) {
7968
- const factored = factorSharedEnds(d);
7969
- if (factored !== void 0) return this.node(factored).ip;
8358
+ const shared = factorSharedEnds(d);
8359
+ if (shared !== void 0) return this.node(shared).ip;
8360
+ if (this.unfactoredChoice !== p) {
8361
+ const prefix = factorPrefixArms(d);
8362
+ if (prefix !== void 0) {
8363
+ const strict = this.node(prefix).ip;
8364
+ const saved = this.unfactoredChoice;
8365
+ this.unfactoredChoice = p;
8366
+ const tolerant = this.encodeDef(p);
8367
+ this.unfactoredChoice = saved;
8368
+ return this.emit(OP_STRICT, strict, tolerant);
8369
+ }
8370
+ }
7970
8371
  }
7971
8372
  if (d.strategy.tag === "greedyClassify") {
7972
8373
  const superIndex = d.strategy.superIndex;
@@ -7995,18 +8396,20 @@ var Encoder = class {
7995
8396
  const plain2 = (checks === null || checks === void 0 || checks.length === 0) && (gate === null || gate === void 0);
7996
8397
  const routedGate = plain2 ? this.routedArmGate(c) : void 0;
7997
8398
  const body = this.node(c).ip;
7998
- const inner = routedGate === void 0 ? body : this.emit(OP_RGATE, routedGate.cls, body, routedGate.fx);
8399
+ const inner = routedGate === void 0 ? body : routedGate.two === void 0 ? this.emit(OP_RGATE, routedGate.cls, body, routedGate.fx) : this.emit(OP_RGATE2, routedGate.cls, body, routedGate.fx, routedGate.two[0], routedGate.two[1]);
7999
8400
  const ip = checks === null || checks === void 0 || checks.length === 0 ? inner : this.reject(inner, checks);
8000
8401
  const gated = gate === null || gate === void 0 ? ip : this.emit(OP_ARMGATE, this.fn(gate, d.gateSrcs?.[src] ?? null), ip, this.expected(deriveExpected(c)));
8001
8402
  return this.covWrap(gated, armCovIds?.[src], 1);
8002
8403
  });
8003
8404
  const rr = this.refResolver();
8004
- const finalChoice = classifyFinalChoice(arms, rr, this.activeTrivia);
8405
+ const constructionSets = d.disjoint && !factoredChoices.has(p);
8406
+ const finalChoice = classifyFinalChoice(arms, rr, this.gateTrivia(), false, constructionSets);
8407
+ const wideArms = this.wideArms(arms, finalChoice.firstSets, constructionSets);
8005
8408
  const transactionalFirst = arms.map((arm, i) => {
8006
8409
  if (!finalChoice.nullable[i]) return void 0;
8007
8410
  const def = arm._def;
8008
8411
  if (def.tag !== "attempt" || matchesEmpty(def.parser, /* @__PURE__ */ new Set(), rr)) return void 0;
8009
- return firstSetOf(def.parser, /* @__PURE__ */ new Set(), rr, this.activeTrivia);
8412
+ return firstSetOf(def.parser, /* @__PURE__ */ new Set(), rr, this.activeTrivia, false);
8010
8413
  });
8011
8414
  const gateFirst = finalChoice.firstSets.map((first, i) => transactionalFirst[i] ?? first);
8012
8415
  const classes = gateFirst.map((first, i) => finalChoice.nullable[i] && transactionalFirst[i] === void 0 ? -1 : this.charClass(first));
@@ -8024,6 +8427,9 @@ var Encoder = class {
8024
8427
  if (transactionalFirst[i] !== void 0) classes[i] = -1;
8025
8428
  }
8026
8429
  }
8430
+ if (wideArms.length > 0 && dispatchIsExclusive(classes, this.cc)) {
8431
+ for (const i of wideArms) classes[i] = -1;
8432
+ }
8027
8433
  if (d.gates.some((g) => g !== null && g !== void 0) && !dispatchIsExclusive(classes, this.cc)) {
8028
8434
  for (let k = 0; k < classes.length; k++) {
8029
8435
  const gate = d.gates[order[k]];
@@ -8057,27 +8463,34 @@ var Encoder = class {
8057
8463
  }
8058
8464
  case "many":
8059
8465
  case "oneOrMore": {
8060
- const child = this.node(d.parser).ip;
8061
8466
  const rr = this.refResolver();
8062
- const itemClass = matchesEmpty(d.parser, /* @__PURE__ */ new Set(), rr) ? -1 : this.charClass(firstSetOf(d.parser, /* @__PURE__ */ new Set(), rr, this.activeTrivia));
8467
+ const itemClass = matchesEmpty(d.parser, /* @__PURE__ */ new Set(), rr) ? -1 : this.charClass(this.gateFirstSet(d.parser, d.guarded === true, true));
8063
8468
  const op = d.valueUnused ? OP_REPV : OP_REP;
8064
8469
  const min = d.tag === "many" ? 0 : d.min;
8470
+ const item = min === 0 && itemClass >= 0 ? this.withoutLeadGate(this.node(d.parser).ip, itemClass) : this.node(d.parser).ip;
8471
+ const child = min === 0 && itemClass >= 0 ? this.gapGated(d.parser, item) : item;
8065
8472
  return this.emit(op, child, min, d.max ?? -1, -1, 0, this.expected(deriveExpected(d.parser)), itemClass);
8066
8473
  }
8067
8474
  case "sepBy": {
8068
8475
  const child = this.node(d.parser).ip;
8069
8476
  const sep = this.node(d.separator).ip;
8070
- const flags = (d.trailing === "allow" ? 1 : 0) | (d.keepSeparators === true ? 2 : 0) | (d.min >= 2 ? 4 : 0);
8071
- return this.emit(
8072
- OP_REP,
8477
+ const gate = this.sepGate(d.separator, sep);
8478
+ const itemSet = deriveExpected(d.parser);
8479
+ const itemFx = this.expected(itemSet.length > 0 ? itemSet : [d.parser._tag]);
8480
+ const flags = (d.trailing === "allow" ? 1 : 0) | (d.keepSeparators === true ? 2 : 0) | (d.min >= 1 ? 4 : 0) | (gate >= 0 ? 8 : 0) | (itemSet.length === 0 ? 16 : 0);
8481
+ const ops = [
8073
8482
  child,
8074
8483
  d.min,
8075
8484
  d.max ?? -1,
8076
8485
  sep,
8077
8486
  flags,
8078
- this.expected(deriveExpected(d.parser)),
8079
- this.charClass(firstSetOf(d.separator, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia))
8080
- );
8487
+ itemFx,
8488
+ // As 0.58.0: the interpreter resyncs on the separator itself
8489
+ // (`orSentinel`), which runs under the live trivia.
8490
+ this.charClass(firstSetOf(d.separator, /* @__PURE__ */ new Set(), this.refResolver(), this.activeTrivia, false))
8491
+ ];
8492
+ if (gate >= 0) ops.push(gate);
8493
+ return this.emit(OP_REP, ...ops);
8081
8494
  }
8082
8495
  case "optional": {
8083
8496
  const head = this.emit(OP_OPT, this.lookaheadGate(d.parser, this.node(d.parser).ip));
@@ -8086,7 +8499,7 @@ var Encoder = class {
8086
8499
  }
8087
8500
  case "transform": {
8088
8501
  const inner = d.parser._def;
8089
- if (inner.tag === "sequence" && !this.memo.has(d.parser) && !this.pending.has(d.parser)) {
8502
+ if (inner.tag === "sequence" && !this.memo.get(d.parser)?.has(this.scopeKey(d.parser)) && !this.pending.get(d.parser)?.has(this.scopeKey(d.parser))) {
8090
8503
  const kids = inner.parsers.map((c) => this.node(c).ip);
8091
8504
  const head = this.emitHead(OP_SEQX, 2 + 2 * kids.length);
8092
8505
  const projection = directArrayProjection(d.fn, d.fnSrc, kids.length);
@@ -8127,7 +8540,7 @@ var Encoder = class {
8127
8540
  d.tags === void 0 ? -1 : this.constant(d.tags)
8128
8541
  );
8129
8542
  if (matchesEmpty(d.parser, /* @__PURE__ */ new Set(), this.refResolver())) return body;
8130
- const fs = this.gateFirstSet(d.parser);
8543
+ const fs = this.gateFirstSet(d.parser, d.guarded === true, false);
8131
8544
  if (fs.kind === "any") return body;
8132
8545
  const cls = this.charClass(fs);
8133
8546
  if (cls < 0) return body;
@@ -8135,7 +8548,14 @@ var Encoder = class {
8135
8548
  return this.emit(OP_GATE, cls, body, this.expected(e.length > 0 ? e : [d.parser._tag]));
8136
8549
  }
8137
8550
  case "token": {
8138
- return this.emit(OP_TOKEN, this.node(d.parser).ip);
8551
+ const saved = this.activeTrivia;
8552
+ const savedKnown = this.triviaKnown;
8553
+ this.activeTrivia = void 0;
8554
+ this.triviaKnown = true;
8555
+ const body = this.node(d.parser).ip;
8556
+ this.activeTrivia = saved;
8557
+ this.triviaKnown = savedKnown;
8558
+ return this.emit(OP_TOKEN, body);
8139
8559
  }
8140
8560
  case "routed":
8141
8561
  return this.emit(OP_ROUTED, d.fallback === void 0 ? -1 : this.node(d.fallback).ip);
@@ -8191,7 +8611,7 @@ var Encoder = class {
8191
8611
  this.code[head + 2] = dspIdx;
8192
8612
  this.code[head + 3] = other;
8193
8613
  const otherRouted = d.otherwise === void 0 ? false : branchUsesRouted({ parser: d.otherwise, usesRouted: d.otherwiseUsesRouted === true });
8194
- this.code[head + 4] = otherRouted ? 1 : 0;
8614
+ this.code[head + 4] = (otherRouted ? 1 : 0) | (d.otherwiseCommit === false ? 2 : 0);
8195
8615
  this.code[head + 5] = arms.length;
8196
8616
  for (let i = 0; i < arms.length; i++) this.code[head + 6 + i] = arms[i];
8197
8617
  return head;
@@ -8246,7 +8666,7 @@ var Encoder = class {
8246
8666
  case "attempt": {
8247
8667
  const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip, d.contain === true ? 1 : 0);
8248
8668
  if (!this.failureNeedsRollback(d.parser, void 0)) this.failureRollbackCleanSites.add(inner);
8249
- const fs = this.gateFirstSet(d.parser);
8669
+ const fs = this.gateFirstSet(d.parser, d.guarded === true, false);
8250
8670
  if (fs.kind === "any" || matchesEmpty(d.parser, /* @__PURE__ */ new Set(), this.refResolver())) return inner;
8251
8671
  const cls = this.charClass(fs);
8252
8672
  if (cls < 0) return inner;
@@ -8272,10 +8692,17 @@ var Encoder = class {
8272
8692
  const op = cap ? OP_SCOPE_CAP : OP_SCOPE;
8273
8693
  const flags = (d.rootCapture === "opaque" ? 1 : 0) | (d.triviaParser !== void 0 && d.triviaParser._meta.rootTriviaClassified !== true && d.rootCapture !== "opaque" ? 2 : 0);
8274
8694
  const savedTrivia = this.activeTrivia;
8275
- if (d.clearTrivia === true) this.activeTrivia = void 0;
8276
- else if (d.triviaParser !== void 0) this.activeTrivia = d.triviaParser;
8695
+ const savedKnown = this.triviaKnown;
8696
+ if (d.clearTrivia === true) {
8697
+ this.activeTrivia = void 0;
8698
+ this.triviaKnown = true;
8699
+ } else if (d.triviaParser !== void 0) {
8700
+ this.activeTrivia = d.triviaParser;
8701
+ this.triviaKnown = true;
8702
+ }
8277
8703
  const bodyIp = this.node(d.parser).ip;
8278
8704
  this.activeTrivia = savedTrivia;
8705
+ this.triviaKnown = savedKnown;
8279
8706
  if (d.clearTrivia === true) return this.emit(op, -1, bodyIp, flags);
8280
8707
  if (d.triviaParser === void 0) {
8281
8708
  return cap || flags !== 0 ? this.emit(op, -1, bodyIp, flags) : bodyIp;
@@ -8316,6 +8743,7 @@ var Encoder = class {
8316
8743
  switch (this.code[ip]) {
8317
8744
  case OP_GATE:
8318
8745
  case OP_RGATE:
8746
+ case OP_RGATE2:
8319
8747
  return [ip + 2];
8320
8748
  case OP_RULE:
8321
8749
  case OP_OPT:
@@ -8325,6 +8753,7 @@ var Encoder = class {
8325
8753
  case OP_ATTEMPT:
8326
8754
  case OP_LABEL:
8327
8755
  case OP_COV:
8756
+ case OP_GAPGATE:
8328
8757
  return [ip + 1];
8329
8758
  case OP_SCOPE:
8330
8759
  case OP_SCOPE_CAP:
@@ -8361,6 +8790,8 @@ var Encoder = class {
8361
8790
  case OP_REP:
8362
8791
  case OP_REPV:
8363
8792
  return this.code[ip + 4] >= 0 ? [ip + 1, ip + 4] : [ip + 1];
8793
+ case OP_STRICT:
8794
+ return [ip + 1, ip + 2];
8364
8795
  default:
8365
8796
  return [];
8366
8797
  }
@@ -9518,6 +9949,7 @@ function childSlots(code, ip, out) {
9518
9949
  case OP_LABEL:
9519
9950
  case OP_COV:
9520
9951
  case OP_REJECT:
9952
+ case OP_GAPGATE:
9521
9953
  out.push(code[ip + 1]);
9522
9954
  return true;
9523
9955
  // Single child at `ip + 2`.
@@ -9534,8 +9966,13 @@ function childSlots(code, ip, out) {
9534
9966
  case OP_SOURCE_LEAF:
9535
9967
  case OP_ARMGATE:
9536
9968
  case OP_RGATE:
9969
+ case OP_RGATE2:
9537
9970
  out.push(code[ip + 2]);
9538
9971
  return true;
9972
+ // `STRICT strict tolerant` — both encodings are reachable; an assembly keeps one.
9973
+ case OP_STRICT:
9974
+ out.push(code[ip + 1], code[ip + 2]);
9975
+ return true;
9539
9976
  // `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
9540
9977
  // data the enclosing `dispatch()` already consumed, so the only edge is the
9541
9978
  // fallback.
@@ -9983,7 +10420,8 @@ var WRAPPERS = /* @__PURE__ */ new Set([
9983
10420
  OP_WITHCTX,
9984
10421
  OP_RULE,
9985
10422
  OP_ATTEMPT,
9986
- OP_COV
10423
+ OP_COV,
10424
+ OP_GAPGATE
9987
10425
  ]);
9988
10426
  function rowNullable(code, k, ip, memo) {
9989
10427
  const hit = memo.get(ip);
@@ -10020,7 +10458,31 @@ function holdsGap(code, k, base, n, i, memo) {
10020
10458
  return !rowNullable(code, k, next, memo);
10021
10459
  }
10022
10460
 
10461
+ // src/table/gap-gate.ts
10462
+ function newGapSlot() {
10463
+ return { row: -1, at: -1, end: -1, input: void 0 };
10464
+ }
10465
+ function gapRows(code, roots) {
10466
+ const rows = /* @__PURE__ */ new Set();
10467
+ const seen = /* @__PURE__ */ new Set();
10468
+ const stack = [...roots];
10469
+ const kids = [];
10470
+ while (stack.length > 0) {
10471
+ const ip = stack.pop();
10472
+ if (seen.has(ip)) continue;
10473
+ seen.add(ip);
10474
+ if (code[ip] === OP_GAPGATE) rows.add(code[ip + 2]);
10475
+ kids.length = 0;
10476
+ childSlots(code, ip, kids);
10477
+ for (const c of kids) stack.push(c);
10478
+ }
10479
+ return rows;
10480
+ }
10481
+
10023
10482
  // src/table/emit-assembly.ts
10483
+ function gapRegexLowers(re) {
10484
+ return re instanceof RegExp && scanShapeFromRegex(re.source, re.flags) !== null;
10485
+ }
10024
10486
  var Unemittable = class extends Error {
10025
10487
  construct;
10026
10488
  constructor(construct) {
@@ -10039,7 +10501,6 @@ var EMITTED_PARAMS = [
10039
10501
  "CLS",
10040
10502
  "AFX",
10041
10503
  "TRIVIA",
10042
- "TRIVIALABELS",
10043
10504
  "TRIVIASCAN",
10044
10505
  "SCANS",
10045
10506
  "DISP",
@@ -10063,7 +10524,6 @@ var EMITTED_PARAMS = [
10063
10524
  "cstLeavesLen",
10064
10525
  "skipTriviaScanned",
10065
10526
  "needsDeferredTriviaCommit",
10066
- "scanTrivia",
10067
10527
  "advanceTrivia",
10068
10528
  "refuseUnclassifiedRootScope",
10069
10529
  "spanLines",
@@ -10083,34 +10543,31 @@ var EMITTED_PARAMS = [
10083
10543
  "orSentinel",
10084
10544
  "captureError",
10085
10545
  // Pure scalar terminal recognizers, indexed by the terminal's existing const
10086
- // operand. Appended for compatibility with older precompiled factories.
10546
+ // operand; selected lexical bodies; the adjacency test.
10087
10547
  "RECOG",
10088
- // Appended so precompiled factories produced by earlier runtimes keep every
10089
- // positional helper binding. Older factories ignore this trailing argument.
10090
- "commitTriviaScan",
10091
- "scanTriviaCompact",
10092
10548
  "LEX",
10093
- // Appended for old precompiled-factory ABI: old factories ignore it.
10094
10549
  "adjacencyHolds",
10095
- // Appended for selected composite lexical programs; old factories ignore it.
10550
+ // Selected composite lexical programs.
10096
10551
  "LEXPROG",
10097
- // Appended for trivia-attributing fields (`field(…, { leadingTrivia })`);
10098
- // old factories ignore them.
10552
+ // Trivia-attributing fields (`field(…, { leadingTrivia })`).
10099
10553
  "LEAD",
10100
10554
  "triviaFieldCapture",
10101
- // Appended for the deferred-commit trivia skip (`trivia-gap.ts`); old
10102
- // factories ignore it.
10555
+ // The deferred-commit trivia skip (`trivia-gap.ts`).
10103
10556
  "skipDeferredTrivia",
10104
- // Appended for the pass-through collapse (`run-support.ts`); old factories
10105
- // ignore it.
10557
+ // The pass-through collapse (`run-support.ts`).
10106
10558
  "collapseEmpty",
10107
- // Appended for constructor-built tree objects (`cst/plain.d.ts`): a `new` call has
10108
- // no allocation site, so V8 cannot pretenure them. Old factories ignore them.
10559
+ // Constructor-built tree objects (`cst/plain.d.ts`): a `new` call has no
10560
+ // allocation site, so V8 cannot pretenure them.
10109
10561
  "_SP",
10110
10562
  "_LF",
10111
10563
  "_ND",
10112
10564
  "_FC",
10113
- "_BUF"
10565
+ "_BUF",
10566
+ // The gap gate's slot (`gap-gate.ts`).
10567
+ "GAP"
10568
+ // Artifacts are version-locked (docs/design/artifact-format.md): a factory is
10569
+ // only ever called by the runtime that emitted it, so a name nothing emitted
10570
+ // reads is removed rather than kept for an older factory's positions.
10114
10571
  ];
10115
10572
  var RUNTIME_PRELUDE = `
10116
10573
  let _pfScan=null
@@ -10361,7 +10818,7 @@ function rebuildPools(cc, fx, disp, plan) {
10361
10818
  })
10362
10819
  };
10363
10820
  }
10364
- function emitAssemblySource(t2, prog, cfg, extraIps = [], staticBuild = false, select, scanSinkNeutral = false) {
10821
+ function emitAssemblySource(t2, prog, cfg, extraIps = [], staticBuild = false, select, scanSinkNeutral = false, gapNeutral = false) {
10365
10822
  const { code, k, fx, disp, dsp, triviaLabelled } = t2;
10366
10823
  const swapLegal = !cfg.trackLines;
10367
10824
  const hostCst = cfg.hostCst;
@@ -10369,6 +10826,7 @@ function emitAssemblySource(t2, prog, cfg, extraIps = [], staticBuild = false, s
10369
10826
  if (cfg.coverage) throw new Unemittable("a coverage assembly");
10370
10827
  const roots = [...Object.values(prog.rules), ...extraIps];
10371
10828
  const reachable = [...reachableSites(code, roots)];
10829
+ const GAP_ROWS = gapNeutral ? /* @__PURE__ */ new Set() : new Set([...gapRows(code, roots)].filter((row) => !gapRegexLowers(k[code[row + 1]])));
10372
10830
  const labels = computeSiteLabels(code, roots, hostCst);
10373
10831
  const scalarSpecs = /* @__PURE__ */ new Set();
10374
10832
  const nullable2 = /* @__PURE__ */ new Map();
@@ -10706,13 +11164,15 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)
10706
11164
  function aliasOf(ip) {
10707
11165
  const op = code[ip];
10708
11166
  if (op === OP_GATE && (cfg.tolerant || cfg.probe)) return code[ip + 2];
10709
- if (op === OP_RULE) return code[ip + 1];
11167
+ if (op === OP_RULE || op === OP_GAPGATE) return code[ip + 1];
11168
+ if (op === OP_STRICT) return code[cfg.tolerant || cfg.probe ? ip + 2 : ip + 1];
10710
11169
  if (scopeIsIdentity(code, ip, labels.at(ip))) return code[ip + 2];
10711
11170
  return void 0;
10712
11171
  }
10713
11172
  function lower(ip, fname) {
10714
11173
  const op = code[ip];
10715
11174
  const head = `function ${fname}(input,pos,ctx){`;
11175
+ const outerHead = head;
10716
11176
  const L = labels.at(ip);
10717
11177
  const sinks = sinksAt(ip);
10718
11178
  const pushLeaf = L.buf && !cfg.trackLines ? L.raw === RAW_OMIT ? "_pushLeafNoRawBuf" : L.raw === RAW_CAPTURE ? "_pushLeafBuf" : "_pushLeaf" : "_pushLeaf";
@@ -10788,9 +11248,27 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)
10788
11248
  case OP_RX_TRACK: {
10789
11249
  const xf = fxRef(code[ip + 2]);
10790
11250
  const track = op === OP_RX_TRACK;
11251
+ if (GAP_ROWS.has(ip)) {
11252
+ const re2 = hoist("gx", `K[${code[ip + 1]}]`);
11253
+ return `${outerHead}
11254
+ let e
11255
+ if(GAP.row===${ip}&&GAP.at===pos&&GAP.input===input)e=GAP.end
11256
+ else{${re2}.lastIndex=pos;e=${re2}.test(input)?${re2}.lastIndex:-1}
11257
+ if(e>=0){
11258
+ const v=input.slice(pos,e)
11259
+ ${captureLeaf("v")}
11260
+ ${track ? "_trackLines(ctx,input,e)\n" : ""}EC.e=e
11261
+ return v
11262
+ }
11263
+ ctx._fe=pos;ctx._fx=${xf}
11264
+ ${cfg.probe ? `failAt(ctx,${xf},pos)
11265
+ ` : ""}return FAIL
11266
+ }`;
11267
+ }
11268
+ const head2 = outerHead;
10791
11269
  if (!track && scalarSpecs.has(code[ip + 1])) {
10792
11270
  const recognize = recognizerRef(code[ip + 1]);
10793
- return `${head}
11271
+ return `${head2}
10794
11272
  const e=${recognize}(input,pos)
10795
11273
  if(e>=0){
10796
11274
  const v=input.slice(pos,e)
@@ -10803,11 +11281,11 @@ return FAIL
10803
11281
  }`;
10804
11282
  }
10805
11283
  const scanned = emitScan(code[ip + 1], xf, track, captureLeaf);
10806
- if (scanned !== void 0) return `${head}${scanned}}`;
11284
+ if (scanned !== void 0) return `${head2}${scanned}}`;
10807
11285
  const rxk = k[code[ip + 1]];
10808
11286
  if (!(rxk instanceof RegExp) || !rxk.sticky) throw new Unemittable("a non-sticky regex row");
10809
11287
  const re = kRef(code[ip + 1]);
10810
- return `${head}
11288
+ return `${head2}
10811
11289
  ${re}.lastIndex=pos
10812
11290
  if(${re}.test(input)){
10813
11291
  const e=${re}.lastIndex
@@ -10845,6 +11323,28 @@ return ${child}(input,pos,ctx)
10845
11323
  const it=ctx._routed
10846
11324
  if(it!==undefined&&it.span.start===pos&&!classHas(${cls},lead(input,it.span.end))){ctx._fe=it.span.end;ctx._fx=${xf};return FAIL}
10847
11325
  return ${child}(input,pos,ctx)
11326
+ }`;
11327
+ }
11328
+ case OP_RGATE2: {
11329
+ const child = link(code[ip + 2]);
11330
+ if (cfg.tolerant || cfg.probe) return `${head}
11331
+ return ${child}(input,pos,ctx)
11332
+ }`;
11333
+ const idxs = [code[ip + 1], code[ip + 4], code[ip + 5]];
11334
+ const ascii = idxs.every((i) => isAsciiOnlyClass(t2.cc[i]));
11335
+ const ref2 = (idx, tag) => {
11336
+ const ci = classes.push([t2.cc[idx]]) - 1;
11337
+ classPlan.push([idx]);
11338
+ return hoist(tag, `CLS[${ci}][0]`);
11339
+ };
11340
+ const [cls, first, second] = [ref2(idxs[0], "cl"), ref2(idxs[1], "c1"), ref2(idxs[2], "c2")];
11341
+ const has = (c, at) => ascii ? `${c}.ascii[${at}]===1` : `classHas(${c},${at})`;
11342
+ const xf = fxRef(code[ip + 3]);
11343
+ return `${head}
11344
+ const it=ctx._routed
11345
+ if(it!==undefined&&it.span.start===pos){const e=it.span.end;const c=${ascii ? "input.charCodeAt(e)" : "lead(input,e)"}
11346
+ if(!(${has(cls, "c")})||(${has(first, "c")}&&!(${has(second, ascii ? "input.charCodeAt(e+1)" : "lead(input,e+1)")}))){ctx._fe=e;ctx._fx=${xf};return FAIL}}
11347
+ return ${child}(input,pos,ctx)
10848
11348
  }`;
10849
11349
  }
10850
11350
  case OP_ARMGATE: {
@@ -11254,7 +11754,27 @@ if(ctx._fc===true)return FAIL
11254
11754
  const af=ctx._fx
11255
11755
  if(af!==undefined&&af.length>0){ctx._fe=pos;return FAIL}
11256
11756
  }
11257
- ctx._fe=pos;ctx._fx=${choiceFx}
11757
+ ${cfg.probe ? `if(arm<0){${arms.map((a) => `${a}(input,pos,ctx)`).join(";")}}
11758
+ ` : ""}ctx._fe=pos;ctx._fx=${choiceFx}
11759
+ return FAIL
11760
+ }`;
11761
+ }
11762
+ if (cfg.probe) {
11763
+ const p = tmp();
11764
+ return `${head}
11765
+ ${emitMark(p, L.buf, L.raw, sinks)}
11766
+ let acc
11767
+ let best=pos
11768
+ ${arms.map((arm) => `{ctx._fc=false
11769
+ const v=${arm}(input,pos,ctx)
11770
+ if(v!==FAIL)return v
11771
+ const at=ctx._fe??pos
11772
+ if(at>best){best=at;acc=undefined}
11773
+ if(at===best)acc=_accSet(ctx._fx,acc)
11774
+ if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
11775
+ ${emitRollback(p, L.buf, L.raw, sinks)}
11776
+ }`).join("\n")}
11777
+ ctx._fe=pos;ctx._fx=acc??${choiceFx}
11258
11778
  return FAIL
11259
11779
  }`;
11260
11780
  }
@@ -11461,7 +11981,8 @@ return FAIL
11461
11981
  const selector = link(code[ip + 1]);
11462
11982
  const otherIp = code[ip + 3];
11463
11983
  const other = otherIp >= 0 ? link(otherIp) : void 0;
11464
- const otherRouted = code[ip + 4] === 1;
11984
+ const otherRouted = (code[ip + 4] & 1) === 1;
11985
+ const otherCommits = (code[ip + 4] & 2) === 0;
11465
11986
  const n = code[ip + 5];
11466
11987
  validateDispatchSpec(spec, n, code[ip + 4]);
11467
11988
  const armBase = ip + 6;
@@ -11515,7 +12036,7 @@ ${fallbackCase}
11515
12036
  }
11516
12037
  if(v===FAIL){
11517
12038
  ${emitRollback(m2, L.buf, L.raw, sinks)}
11518
- ctx._fc=true
12039
+ ${other !== void 0 && !otherCommits ? "if(arm!==undefined)" : ""}ctx._fc=true
11519
12040
  return FAIL
11520
12041
  }
11521
12042
  return [key,v]
@@ -11547,6 +12068,63 @@ return [key,v]
11547
12068
  }
11548
12069
  }
11549
12070
  const itemMiss = (at) => itemAscii ? `${itemCls}.ascii[input.charCodeAt(${at})]!==1` : `!classHas(${itemCls},lead(input,${at}))`;
12071
+ let sepCls;
12072
+ let sepAscii = false;
12073
+ if (!REC && sepIp >= 0 && (code[ip + 5] & 8) !== 0 && !leadingTriviaFieldAt(code, k, sepIp) && !rowNullable(code, k, sepIp, nullable2)) {
12074
+ const resolved = t2.cc[code[ip + 8]];
12075
+ const ci = classes.push([resolved]) - 1;
12076
+ classPlan.push([code[ip + 8]]);
12077
+ sepCls = hoist("rs", `CLS[${ci}][0]`);
12078
+ sepAscii = isAsciiOnlyClass(resolved);
12079
+ }
12080
+ const gapIp = !gapNeutral && sepIp < 0 && min === 0 && !REC && code[code[ip + 1]] === OP_GAPGATE ? code[ip + 1] : -1;
12081
+ let gapCode = (_rb) => "";
12082
+ if (gapIp >= 0) {
12083
+ const row = code[gapIp + 2];
12084
+ const rx = k[code[row + 1]];
12085
+ const shape = gapRegexLowers(rx) ? scanShapeFromRegex(rx.source, rx.flags) : null;
12086
+ const cls = (idx) => {
12087
+ const resolved = t2.cc[idx];
12088
+ const ci = classes.push([resolved]) - 1;
12089
+ classPlan.push([idx]);
12090
+ const ref2 = hoist("gc", `CLS[${ci}][0]`);
12091
+ return isAsciiOnlyClass(resolved) ? `${ref2}.ascii[input.charCodeAt(${"@"})]!==1` : `!classHas(${ref2},lead(input,${"@"}))`;
12092
+ };
12093
+ const follow = cls(code[gapIp + 3]).replaceAll("@", "gq");
12094
+ const other = code[gapIp + 4] >= 0 ? cls(code[gapIp + 4]).replaceAll("@", "itemStart") : "true";
12095
+ if (shape !== null) {
12096
+ let n = 0;
12097
+ const q2 = tmp();
12098
+ const m = emitShapeMatch(shape, "itemStart", (prefix = "_v") => `${q2}${prefix}${n++}`, "");
12099
+ gapCode = (rbCode) => `let gapped=false
12100
+ if(ctx._probe===undefined){
12101
+ ${m.setup.join("\n")}
12102
+ const gq=${m.ok}?${m.end}:-1
12103
+ if((gq<0||${follow})&&${other}){
12104
+ ${rbCode}
12105
+ break
12106
+ }
12107
+ }
12108
+ `;
12109
+ } else {
12110
+ const re = hoist("gr", `K[${code[row + 1]}]`);
12111
+ gapCode = (rbCode) => `let gapped=false
12112
+ if(ctx._probe===undefined){
12113
+ ${re}.lastIndex=itemStart
12114
+ const gq=${re}.test(input)?${re}.lastIndex:-1
12115
+ if((gq<0||${follow})&&${other}){
12116
+ ${rbCode}
12117
+ break
12118
+ }
12119
+ if(gq>=0){
12120
+ GAP.row=${row};GAP.at=itemStart;GAP.end=gq;GAP.input=input
12121
+ gapped=true
12122
+ }
12123
+ }
12124
+ `;
12125
+ }
12126
+ }
12127
+ const sepMiss = (at) => sepAscii ? `${sepCls}.ascii[input.charCodeAt(${at})]!==1` : `!classHas(${sepCls},lead(input,${at}))`;
11550
12128
  const skipBeforeFirst = sepIp < 0 && min === 0;
11551
12129
  const p = tmp();
11552
12130
  const via = sep !== void 0 ? "false" : min === 0 ? skipBeforeFirst ? "true" : "count>0" : `count>=${min}&&count>0`;
@@ -11584,7 +12162,7 @@ ${p}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
11584
12162
  ${p}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${markSinks}
11585
12163
  }`;
11586
12164
  const my = `${p}my`;
11587
- const recFx = REC ? fxRef(code[ip + 6]) : "";
12165
+ const recFx = !REC ? "" : (flags & 16) !== 0 ? "[]" : fxRef(code[ip + 6]);
11588
12166
  const sepSent = REC ? sentRef(code[ip + 7]) : "undefined";
11589
12167
  const recSent = sep === void 0 ? my : sepSent === "undefined" ? `orSentinel(${my},undefined)` : `orSentinel(${sepSent}??${my},${sepSent}===undefined?undefined:${my})`;
11590
12168
  const recGate = sep !== void 0 || min === 0 ? "" : `count>=${min}&&`;
@@ -11600,7 +12178,7 @@ continue
11600
12178
  return `${head}
11601
12179
  const out=${collect ? "[]" : "undefined"}
11602
12180
  ${knownTrivia === void 0 ? "const hasTrivia=ctx.trivia!==undefined\n" : ""}${L.buf ? "" : "const needMark=_rollbackNeeded(ctx)\n"}${REC ? `const ${my}=ctx._sync
11603
- ` : ""}${itemCls === void 0 ? "" : `const ${p}gate=ctx._probe===undefined
12181
+ ` : ""}${itemCls === void 0 && sepCls === void 0 ? "" : `const ${p}gate=ctx._probe===undefined
11604
12182
  `}let cur=pos
11605
12183
  let count=0
11606
12184
  for(;;){
@@ -11616,6 +12194,10 @@ const lb=cstLeavesLen(ctx)
11616
12194
  let sp=cur
11617
12195
  ${hasTrivia === "false" ? "" : hasTrivia === "true" ? `sp=${skip}(input,sp,ctx)
11618
12196
  ` : `if(hasTrivia)sp=${skip}(input,sp,ctx)
12197
+ `}${sepCls === void 0 ? "" : `if(count>=${min}&&${p}gate&&${sepMiss("sp")}){
12198
+ ${rb}
12199
+ break
12200
+ }
11619
12201
  `}ctx._fc=false
11620
12202
  ${sepIp >= 0 && leadingTriviaFieldAt(code, k, sepIp) ? "LEAD.at=cur\n" : ""}const sv=${sep}(input,sp,ctx)
11621
12203
  if(sv===FAIL){
@@ -11631,13 +12213,13 @@ itemStart=${hasTrivia === "false" ? "EC.e" : hasTrivia === "true" ? `${skip}(inp
11631
12213
  ${rb}
11632
12214
  ${trailingAllowed ? "if(sepEnd>=0)cur=sepEnd\n" : ""}break
11633
12215
  }
11634
- ${itemCls === void 0 ? "" : `if(count>=${min}&&${p}gate&&${itemMiss("itemStart")}){
12216
+ ${itemCls === void 0 || sep === void 0 && hasTrivia === "false" ? "" : `if(count>=${min}&&${p}gate&&${sep === void 0 && hasTrivia === "hasTrivia" ? "hasTrivia&&" : ""}${itemMiss("itemStart")}){
11635
12217
  ${rb}
11636
12218
  ${trailingAllowed ? "if(sepEnd>=0)cur=sepEnd\n" : ""}break
11637
12219
  }
11638
- `}ctx._fc=false
12220
+ `}${gapCode(rb)}ctx._fc=false
11639
12221
  const v=${child}(input,itemStart,ctx)
11640
- if(v===FAIL){
12222
+ ${gapIp >= 0 ? "if(gapped)GAP.at=-1\n" : ""}if(v===FAIL){
11641
12223
  ${REC ? `if(ctx._fc===true){
11642
12224
  ${rb}
11643
12225
  return FAIL
@@ -12609,6 +13191,9 @@ function clearOf(ranges, input, from, to) {
12609
13191
  return true;
12610
13192
  }
12611
13193
 
13194
+ // src/version.ts
13195
+ var PARSEMAN_VERSION = "0.60.1";
13196
+
12612
13197
  // src/table/assemble.ts
12613
13198
  import { PlainBuf as PlainBuf2, PlainField, PlainLeaf as PlainLeaf9, PlainNode as PlainNode2, PlainSpan as PlainSpan10 } from "../cst/plain.js";
12614
13199
 
@@ -12619,6 +13204,7 @@ function passChild(code, ip) {
12619
13204
  case OP_LABEL:
12620
13205
  case OP_COV:
12621
13206
  case OP_TOKEN:
13207
+ case OP_GAPGATE:
12622
13208
  return code[ip + 1];
12623
13209
  case OP_GATE:
12624
13210
  case OP_NODE:
@@ -12871,6 +13457,13 @@ function cfgKeyOf(hostCst, trackLines, tolerant, coverage, probe, hostReadsChild
12871
13457
  function cfgKey(c) {
12872
13458
  return cfgKeyOf(c.hostCst, c.trackLines, c.tolerant, c.coverage, c.probe, c.hostReadsChildren);
12873
13459
  }
13460
+ function assertFactoryArity(pre) {
13461
+ if (pre.arity === EMITTED_PARAMS.length) return;
13462
+ const printed = pre.arity === void 0 ? "carries no helper count, so it was printed before parseman 0.60.0" : `takes ${pre.arity} helpers, but parseman ${PARSEMAN_VERSION} passes ${EMITTED_PARAMS.length}: it was printed by an older or newer parseman`;
13463
+ throw new Error(
13464
+ `parseman: this parser's printed assembly ${printed}. Reprint the parser with parseman ${PARSEMAN_VERSION}, or run it on the parseman version that printed it.`
13465
+ );
13466
+ }
12874
13467
  function assemble(t2, prog, cfg) {
12875
13468
  const { code, k, fns, cc, fx, disp, dsp, trivia: trivia2, triviaLabelled } = t2;
12876
13469
  const triviaScan = t2.triviaScan;
@@ -12905,6 +13498,8 @@ function assemble(t2, prog, cfg) {
12905
13498
  return recognize;
12906
13499
  }
12907
13500
  const labelExtraIps = assemblyExtraIps(prog);
13501
+ const GAP_ROWS = gapRows(code, [...Object.values(prog.rules), ...labelExtraIps]);
13502
+ const GAP = newGapSlot();
12908
13503
  const closureLabels = computeSiteLabels(
12909
13504
  code,
12910
13505
  [...Object.values(prog.rules), ...labelExtraIps],
@@ -13123,6 +13718,27 @@ function assemble(t2, prog, cfg) {
13123
13718
  return node2;
13124
13719
  };
13125
13720
  }
13721
+ function gapRegexPiece(row, re, xf, track) {
13722
+ return (input, pos, ctx) => {
13723
+ let e;
13724
+ if (GAP.row === row && GAP.at === pos && GAP.input === input) e = GAP.end;
13725
+ else {
13726
+ re.lastIndex = pos;
13727
+ e = re.test(input) ? re.lastIndex : -1;
13728
+ }
13729
+ if (e >= 0) {
13730
+ const v = input.slice(pos, e);
13731
+ if (cstCaptureActive(ctx)) pushLeaf(ctx, v, pos, e);
13732
+ if (track) trackLinesInto(ctx, input, e);
13733
+ EC.e = e;
13734
+ return v;
13735
+ }
13736
+ ctx._fe = pos;
13737
+ ctx._fx = xf;
13738
+ if (ctx._probe !== void 0) failAt(ctx, xf, pos);
13739
+ return FAIL;
13740
+ };
13741
+ }
13126
13742
  function passCollapseNode(child, build, readsRaw) {
13127
13743
  return (input, pos, ctx) => {
13128
13744
  const v = child(input, pos, ctx);
@@ -13525,7 +14141,7 @@ function assemble(t2, prog, cfg) {
13525
14141
  return next?.(key);
13526
14142
  };
13527
14143
  }
13528
- function dispatchBranch(child, usesRouted) {
14144
+ function dispatchBranch(child, usesRouted, commits = true) {
13529
14145
  if (!usesRouted) {
13530
14146
  return (input, _pos, selEnd, key, ctx) => {
13531
14147
  const need = markCst(ctx);
@@ -13539,7 +14155,7 @@ function assemble(t2, prog, cfg) {
13539
14155
  const v = child(input, selEnd, ctx);
13540
14156
  if (v === FAIL) {
13541
14157
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
13542
- ctx._fc = true;
14158
+ if (commits) ctx._fc = true;
13543
14159
  return FAIL;
13544
14160
  }
13545
14161
  return [key, v];
@@ -13557,7 +14173,7 @@ function assemble(t2, prog, cfg) {
13557
14173
  }
13558
14174
  if (v === FAIL) {
13559
14175
  if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
13560
- ctx._fc = true;
14176
+ if (commits) ctx._fc = true;
13561
14177
  return FAIL;
13562
14178
  }
13563
14179
  return [key, v];
@@ -13798,6 +14414,7 @@ function assemble(t2, prog, cfg) {
13798
14414
  case OP_RX: {
13799
14415
  const re = k[code[ip + 1]];
13800
14416
  const xf = fx[code[ip + 2]];
14417
+ if (GAP_ROWS.has(ip)) return gapRegexPiece(ip, re, xf, false);
13801
14418
  const shared = rawScalarSpecs.has(code[ip + 1]) ? scalarFor(ip) : void 0;
13802
14419
  if (shared !== void 0) {
13803
14420
  return (input, pos, ctx) => {
@@ -13832,6 +14449,7 @@ function assemble(t2, prog, cfg) {
13832
14449
  case OP_RX_TRACK: {
13833
14450
  const re = k[code[ip + 1]];
13834
14451
  const xf = fx[code[ip + 2]];
14452
+ if (GAP_ROWS.has(ip)) return gapRegexPiece(ip, re, xf, true);
13835
14453
  return (input, pos, ctx) => {
13836
14454
  re.lastIndex = pos;
13837
14455
  if (re.test(input)) {
@@ -13962,6 +14580,12 @@ function assemble(t2, prog, cfg) {
13962
14580
  return v;
13963
14581
  };
13964
14582
  }
14583
+ // Transparent: only the repeat holding it reads its operands.
14584
+ case OP_GAPGATE:
14585
+ return link(code[ip + 1]);
14586
+ // One encoding per assembly: strict, or the construct as written.
14587
+ case OP_STRICT:
14588
+ return link(code[cfg.tolerant || cfg.probe ? ip + 2 : ip + 1]);
13965
14589
  case OP_RULE:
13966
14590
  return link(code[ip + 1]);
13967
14591
  case OP_LIVE: {
@@ -14887,7 +15511,7 @@ function assemble(t2, prog, cfg) {
14887
15511
  if (table.exclusive) {
14888
15512
  const ascii = table.ascii;
14889
15513
  const hi = table.hi;
14890
- if (n === 2) {
15514
+ if (n === 2 && !cfg.probe) {
14891
15515
  const a0 = link(code[base]), a1 = link(code[base + 1]);
14892
15516
  return (input, pos, ctx) => {
14893
15517
  const c = lead(input, pos);
@@ -14919,7 +15543,7 @@ function assemble(t2, prog, cfg) {
14919
15543
  return FAIL;
14920
15544
  };
14921
15545
  }
14922
- if (n === 3) {
15546
+ if (n === 3 && !cfg.probe) {
14923
15547
  const a0 = link(code[base]), a1 = link(code[base + 1]), a2 = link(code[base + 2]);
14924
15548
  return (input, pos, ctx) => {
14925
15549
  const c = lead(input, pos);
@@ -14964,6 +15588,7 @@ function assemble(t2, prog, cfg) {
14964
15588
  );
14965
15589
  };
14966
15590
  const selected = bindExclusive(0);
15591
+ const probeArms = cfg.probe ? Array.from({ length: n }, (_, i) => link(code[base + i])) : void 0;
14967
15592
  return (input, pos, ctx) => {
14968
15593
  const c = lead(input, pos);
14969
15594
  let arm = -1;
@@ -14989,12 +15614,13 @@ function assemble(t2, prog, cfg) {
14989
15614
  return FAIL;
14990
15615
  }
14991
15616
  }
15617
+ if (arm < 0 && probeArms !== void 0) for (const a of probeArms) a(input, pos, ctx);
14992
15618
  ctx._fe = pos;
14993
15619
  ctx._fx = choiceFx;
14994
15620
  return FAIL;
14995
15621
  };
14996
15622
  }
14997
- const armCls = table.armCls;
15623
+ const armCls = cfg.probe ? table.armCls.map(() => null) : table.armCls;
14998
15624
  if (n === 2 || n === 3) {
14999
15625
  const a0 = link(code[base]), a1 = link(code[base + 1]);
15000
15626
  const a2 = n === 3 ? link(code[base + 2]) : void 0;
@@ -15414,6 +16040,43 @@ function assemble(t2, prog, cfg) {
15414
16040
  return child(input, pos, ctx);
15415
16041
  };
15416
16042
  }
16043
+ case OP_RGATE2: {
16044
+ const cls = cc[code[ip + 1]];
16045
+ const xf = fx[code[ip + 3]];
16046
+ const first = cc[code[ip + 4]];
16047
+ const second = cc[code[ip + 5]];
16048
+ const child = link(code[ip + 2]);
16049
+ if (cfg.tolerant || cfg.probe) return child;
16050
+ if (cls.hi.length === 0 && first.hi.length === 0 && second.hi.length === 0) {
16051
+ const a0 = cls.ascii, a1 = first.ascii, a2 = second.ascii;
16052
+ return (input, pos, ctx) => {
16053
+ const item = ctx._routed;
16054
+ if (item !== void 0 && item.span.start === pos) {
16055
+ const e = item.span.end;
16056
+ const c = input.charCodeAt(e);
16057
+ if (a0[c] !== 1 || a1[c] === 1 && a2[input.charCodeAt(e + 1)] !== 1) {
16058
+ ctx._fe = e;
16059
+ ctx._fx = xf;
16060
+ return FAIL;
16061
+ }
16062
+ }
16063
+ return child(input, pos, ctx);
16064
+ };
16065
+ }
16066
+ return (input, pos, ctx) => {
16067
+ const item = ctx._routed;
16068
+ if (item !== void 0 && item.span.start === pos) {
16069
+ const e = item.span.end;
16070
+ const c = lead(input, e);
16071
+ if (!classHas2(cls, c) || classHas2(first, c) && !classHas2(second, lead(input, e + 1))) {
16072
+ ctx._fe = e;
16073
+ ctx._fx = xf;
16074
+ return FAIL;
16075
+ }
16076
+ }
16077
+ return child(input, pos, ctx);
16078
+ };
16079
+ }
15417
16080
  case OP_ARMGATE: {
15418
16081
  const pred = fns[code[ip + 1]];
15419
16082
  const child = link(code[ip + 2]);
@@ -15481,10 +16144,18 @@ function assemble(t2, prog, cfg) {
15481
16144
  const itemClassIndex = sepIp < 0 ? code[ip + 7] : -1;
15482
16145
  const itemClass = !REC && itemClassIndex >= 0 ? cc[itemClassIndex] : void 0;
15483
16146
  const itemMayStart = itemClass === void 0 ? void 0 : (input, pos) => classHas2(itemClass, lead(input, pos));
16147
+ const sepClassIndex = sepIp >= 0 && !sepLeads && (flags & 8) !== 0 ? code[ip + 8] : -1;
16148
+ const sepClass = !REC && sepClassIndex >= 0 && !rowNullable(code, k, sepIp, NULLABLE) ? cc[sepClassIndex] : void 0;
16149
+ const gapIp = sepIp < 0 && min === 0 && !REC && code[code[ip + 1]] === OP_GAPGATE ? code[ip + 1] : -1;
16150
+ const gapRow = gapIp >= 0 ? code[gapIp + 2] : -1;
16151
+ const gapRe = gapIp >= 0 ? k[code[gapRow + 1]] : void 0;
16152
+ const gapFollow = gapIp >= 0 ? cc[code[gapIp + 3]] : void 0;
16153
+ const gapOther = gapIp >= 0 && code[gapIp + 4] >= 0 ? cc[code[gapIp + 4]] : void 0;
15484
16154
  const skipBeforeFirst = sepIp < 0 && min === 0;
15485
16155
  const itemClean = sepIp < 0 && CLEAN(code[ip + 1]) && !rowNullable(code, k, code[ip + 1], NULLABLE);
15486
16156
  if (REC) {
15487
16157
  const itemFx2 = fx[code[ip + 6]];
16158
+ const recFx = (flags & 16) !== 0 ? EMPTY_FX2 : itemFx2;
15488
16159
  const sepSent = sentinelFor(code[ip + 7]);
15489
16160
  return (input, pos, ctx) => {
15490
16161
  const out = collect ? [] : void 0;
@@ -15544,7 +16215,7 @@ function assemble(t2, prog, cfg) {
15544
16215
  itemStart,
15545
16216
  ctx,
15546
16217
  sep === void 0 ? mySync : orSentinel(sepSent ?? mySync, sepSent === void 0 ? void 0 : mySync),
15547
- itemFx2
16218
+ recFx
15548
16219
  );
15549
16220
  if (out !== void 0) out.push(rr.error);
15550
16221
  captureError(ctx, rr.error);
@@ -15582,6 +16253,7 @@ function assemble(t2, prog, cfg) {
15582
16253
  const markLater = needMark && (!itemClean || hasTrivia);
15583
16254
  const markFirst = needMark && (!itemClean || hasTrivia && skipBeforeFirst);
15584
16255
  const gateItems = itemMayStart !== void 0 && ctx._probe === void 0;
16256
+ const gateSep = sepClass !== void 0 && ctx._probe === void 0;
15585
16257
  let cur = pos;
15586
16258
  let count = 0;
15587
16259
  for (; ; ) {
@@ -15604,6 +16276,10 @@ function assemble(t2, prog, cfg) {
15604
16276
  const leavesBefore = cstLeavesLen(ctx);
15605
16277
  let sp = cur;
15606
16278
  if (hasTrivia) sp = skipTrivia(input, sp, ctx);
16279
+ if (count >= min && gateSep && !classHas2(sepClass, lead(input, sp))) {
16280
+ if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
16281
+ break;
16282
+ }
15607
16283
  ctx._fc = false;
15608
16284
  if (sepLeads) LEAD.at = cur;
15609
16285
  const sv = sep(input, sp, ctx);
@@ -15623,13 +16299,30 @@ function assemble(t2, prog, cfg) {
15623
16299
  if (trailingAllowed && sepEnd >= 0) cur = sepEnd;
15624
16300
  break;
15625
16301
  }
15626
- if (count >= min && gateItems && !itemMayStart(input, itemStart)) {
16302
+ if (count >= min && gateItems && (sep !== void 0 || hasTrivia) && !itemMayStart(input, itemStart)) {
15627
16303
  if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
15628
16304
  if (trailingAllowed && sepEnd >= 0) cur = sepEnd;
15629
16305
  break;
15630
16306
  }
16307
+ let gapped = false;
16308
+ if (gapRe !== void 0 && ctx._probe === void 0) {
16309
+ gapRe.lastIndex = itemStart;
16310
+ const q2 = gapRe.test(input) ? gapRe.lastIndex : -1;
16311
+ if ((q2 < 0 || !classHas2(gapFollow, lead(input, q2))) && (gapOther === void 0 || !classHas2(gapOther, lead(input, itemStart)))) {
16312
+ if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
16313
+ break;
16314
+ }
16315
+ if (q2 >= 0) {
16316
+ GAP.row = gapRow;
16317
+ GAP.at = itemStart;
16318
+ GAP.end = q2;
16319
+ GAP.input = input;
16320
+ gapped = true;
16321
+ }
16322
+ }
15631
16323
  ctx._fc = false;
15632
16324
  const v = child(input, itemStart, ctx);
16325
+ if (gapped) GAP.at = -1;
15633
16326
  if (v === FAIL) {
15634
16327
  if (markIt) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot);
15635
16328
  if (committed3(ctx)) return FAIL;
@@ -15661,7 +16354,8 @@ function assemble(t2, prog, cfg) {
15661
16354
  const selector = link(code[ip + 1]);
15662
16355
  const otherIp = code[ip + 3];
15663
16356
  const other = otherIp >= 0 ? link(otherIp) : void 0;
15664
- const otherRouted = code[ip + 4] === 1;
16357
+ const otherRouted = (code[ip + 4] & 1) === 1;
16358
+ const otherCommits = (code[ip + 4] & 2) === 0;
15665
16359
  const n = code[ip + 5];
15666
16360
  const armBase = ip + 6;
15667
16361
  const byKey = spec.byKey;
@@ -15709,7 +16403,7 @@ function assemble(t2, prog, cfg) {
15709
16403
  );
15710
16404
  };
15711
16405
  const runArm = n === 0 ? void 0 : bindArms(0);
15712
- const runOther = other === void 0 ? void 0 : dispatchBranch(other, otherRouted);
16406
+ const runOther = other === void 0 ? void 0 : dispatchBranch(other, otherRouted, otherCommits);
15713
16407
  return (input, pos, ctx) => {
15714
16408
  const need = markCst(ctx);
15715
16409
  const mRaw = MRAW;
@@ -15970,6 +16664,7 @@ function assemble(t2, prog, cfg) {
15970
16664
  }
15971
16665
  const pre = cfg.hostCaptureTrivia === void 0 ? prog.asm?.find((a) => a.key === cfgKey(cfg)) : void 0;
15972
16666
  if (pre !== void 0) {
16667
+ assertFactoryArity(pre);
15973
16668
  const pools = rebuildPools(t2.cc, t2.fx, t2.disp, pre.plan);
15974
16669
  emitted = pre.factory(
15975
16670
  EC,
@@ -15981,7 +16676,6 @@ function assemble(t2, prog, cfg) {
15981
16676
  pools.classes,
15982
16677
  pools.armExpected,
15983
16678
  trivia2,
15984
- trivia2.map((tv) => tv?._meta.triviaKindLabels),
15985
16679
  triviaScan,
15986
16680
  scansArr,
15987
16681
  disp,
@@ -16005,7 +16699,6 @@ function assemble(t2, prog, cfg) {
16005
16699
  cstLeavesLen,
16006
16700
  skipTriviaScanned,
16007
16701
  needsDeferredTriviaCommit,
16008
- scanTrivia,
16009
16702
  advanceTrivia,
16010
16703
  refuseUnclassifiedRootScope,
16011
16704
  spanLines,
@@ -16019,8 +16712,6 @@ function assemble(t2, prog, cfg) {
16019
16712
  orSentinel,
16020
16713
  captureError,
16021
16714
  scalarRecognizers,
16022
- commitTriviaScan,
16023
- scanTriviaCompact,
16024
16715
  t2.lex,
16025
16716
  adjacencyHolds,
16026
16717
  t2.lexPrograms,
@@ -16032,7 +16723,8 @@ function assemble(t2, prog, cfg) {
16032
16723
  PlainLeaf9,
16033
16724
  PlainNode2,
16034
16725
  PlainField,
16035
- PlainBuf2
16726
+ PlainBuf2,
16727
+ GAP
16036
16728
  );
16037
16729
  emitReached = new Set(pre.reached);
16038
16730
  if (emitted.ext !== void 0) HYBRID = emitted;
@@ -16068,7 +16760,6 @@ function assemble(t2, prog, cfg) {
16068
16760
  em.classes,
16069
16761
  em.armExpected,
16070
16762
  trivia2,
16071
- trivia2.map((tv) => tv?._meta.triviaKindLabels),
16072
16763
  triviaScan,
16073
16764
  scansArr,
16074
16765
  disp,
@@ -16092,7 +16783,6 @@ function assemble(t2, prog, cfg) {
16092
16783
  cstLeavesLen,
16093
16784
  skipTriviaScanned,
16094
16785
  needsDeferredTriviaCommit,
16095
- scanTrivia,
16096
16786
  advanceTrivia,
16097
16787
  refuseUnclassifiedRootScope,
16098
16788
  spanLines,
@@ -16111,8 +16801,6 @@ function assemble(t2, prog, cfg) {
16111
16801
  orSentinel,
16112
16802
  captureError,
16113
16803
  scalarRecognizers,
16114
- commitTriviaScan,
16115
- scanTriviaCompact,
16116
16804
  t2.lex,
16117
16805
  adjacencyHolds,
16118
16806
  t2.lexPrograms,
@@ -16124,7 +16812,8 @@ function assemble(t2, prog, cfg) {
16124
16812
  PlainLeaf9,
16125
16813
  PlainNode2,
16126
16814
  PlainField,
16127
- PlainBuf2
16815
+ PlainBuf2,
16816
+ GAP
16128
16817
  );
16129
16818
  emitReached = em.reached;
16130
16819
  } catch (e) {
@@ -16290,7 +16979,7 @@ var AssemblyCache = class {
16290
16979
  * That is not a hole in G5, it is G5's own first clause: "quickly building the
16291
16980
  * grammar reference ON RUN START, making some swaps on rules or sub-rules, and
16292
16981
  * then the run actually runs with no logic branching for that option input"
16293
- * (`notes/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
16982
+ * (`notes/history/TABLE-DRIVER.md`). This IS the run-start step. What the criterion
16294
16983
  * forbids is the second sentence, and past this call there is no option read
16295
16984
  * anywhere — `scripts/check-invariants.mjs` INV-6 decides that mechanically.
16296
16985
  *
@@ -16382,11 +17071,8 @@ function tableRules(source, metadataOrEntry = {}) {
16382
17071
  return map[entry];
16383
17072
  }
16384
17073
 
16385
- // src/version.ts
16386
- var PARSEMAN_VERSION = "0.59.3";
16387
-
16388
17074
  // src/table/emit.ts
16389
- var FACTORY_ARITY_GUARD = `if(arguments.length<${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; upgrade the parseman runtime to at least that version`)});`;
17075
+ var FACTORY_ARITY_GUARD = `if(arguments.length!==${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; run it on parseman ${PARSEMAN_VERSION} or reprint it`)});`;
16390
17076
  function jsString(s) {
16391
17077
  return JSON.stringify(s);
16392
17078
  }
@@ -16461,7 +17147,7 @@ function emitAssemblies(prog, cfgs) {
16461
17147
  throw e;
16462
17148
  }
16463
17149
  const plan = em.plan;
16464
- out.push(`{key:${key},factory:function(${EMITTED_PARAMS.join(",")}){${FACTORY_ARITY_GUARD}${em.source}
17150
+ out.push(`{key:${key},arity:${EMITTED_PARAMS.length},factory:function(${EMITTED_PARAMS.join(",")}){${FACTORY_ARITY_GUARD}${em.source}
16465
17151
  },plan:{classes:[${plan.classes.map((r) => `[${r.join(",")}]`).join(",")}],armExpected:[${plan.armExpected.map((r) => `[${r.join(",")}]`).join(",")}],masks:[${plan.masks.join(",")}]},reached:[${[...em.reached].join(",")}]}`);
16466
17152
  }
16467
17153
  return [`a:[${out.join(",")}],`];
@@ -16737,7 +17423,7 @@ function payloadKey(p, d) {
16737
17423
  case "sepBy":
16738
17424
  return `sepBy\0${d.min}\0${d.max ?? ""}\0${d.trailing ?? ""}`;
16739
17425
  case "dispatch":
16740
- return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}`;
17426
+ return `dispatch\0${d.cases.map((c) => JSON.stringify(c.keys)).join("")}\0${d.otherwise !== void 0}${d.otherwiseCommit === false ? "\0uncommitted" : ""}`;
16741
17427
  case "expect":
16742
17428
  return `expect\0${d.label ?? ""}\0${d.expected.join("")}`;
16743
17429
  case "scanTo":