parseman 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +94 -72
  2. package/dist/analysis/gating.d.ts +66 -1
  3. package/dist/analysis/gating.d.ts.map +1 -1
  4. package/dist/combinators/case-fold.d.ts +39 -0
  5. package/dist/combinators/case-fold.d.ts.map +1 -0
  6. package/dist/combinators/expect.d.ts +1 -1
  7. package/dist/combinators/expect.d.ts.map +1 -1
  8. package/dist/combinators/first-set.d.ts +38 -11
  9. package/dist/combinators/first-set.d.ts.map +1 -1
  10. package/dist/combinators/keywords.d.ts +12 -3
  11. package/dist/combinators/keywords.d.ts.map +1 -1
  12. package/dist/combinators/not.d.ts.map +1 -1
  13. package/dist/combinators/peek.d.ts +27 -0
  14. package/dist/combinators/peek.d.ts.map +1 -0
  15. package/dist/combinators/repeat.d.ts +68 -3
  16. package/dist/combinators/repeat.d.ts.map +1 -1
  17. package/dist/combinators/trivia-skip.d.ts +23 -1
  18. package/dist/combinators/trivia-skip.d.ts.map +1 -1
  19. package/dist/compiler/codegen.d.ts +40 -0
  20. package/dist/compiler/codegen.d.ts.map +1 -1
  21. package/dist/compiler/fields.d.ts.map +1 -1
  22. package/dist/compiler/ir-serialize.d.ts.map +1 -1
  23. package/dist/compiler/linker.d.ts +5 -0
  24. package/dist/compiler/linker.d.ts.map +1 -1
  25. package/dist/compiler/value-usage.d.ts.map +1 -1
  26. package/dist/coverage.d.ts.map +1 -1
  27. package/dist/cst/capture-buffer.d.ts.map +1 -1
  28. package/dist/index.cjs +462 -105
  29. package/dist/index.cjs.map +4 -4
  30. package/dist/index.d.ts +4 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +462 -105
  33. package/dist/index.js.map +4 -4
  34. package/dist/language-service/index.cjs.map +1 -1
  35. package/dist/language-service/index.js.map +1 -1
  36. package/dist/plugin/evaluator.d.ts.map +1 -1
  37. package/dist/plugin/index.cjs +529 -135
  38. package/dist/plugin/index.cjs.map +4 -4
  39. package/dist/plugin/index.d.ts.map +1 -1
  40. package/dist/plugin/index.js +529 -135
  41. package/dist/plugin/index.js.map +4 -4
  42. package/dist/run/index.cjs +216 -0
  43. package/dist/run/index.cjs.map +7 -0
  44. package/dist/run/index.d.ts +28 -0
  45. package/dist/run/index.d.ts.map +1 -0
  46. package/dist/run/index.js +193 -0
  47. package/dist/run/index.js.map +7 -0
  48. package/dist/spec/ebnf.d.ts.map +1 -1
  49. package/dist/spec/index.cjs +79 -17
  50. package/dist/spec/index.cjs.map +2 -2
  51. package/dist/spec/index.js +79 -17
  52. package/dist/spec/index.js.map +2 -2
  53. package/dist/spec/model.d.ts +23 -3
  54. package/dist/spec/model.d.ts.map +1 -1
  55. package/dist/spec/railroad.d.ts.map +1 -1
  56. package/dist/types.d.ts +9 -1
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/version.d.ts +1 -1
  59. package/package.json +11 -1
@@ -42,6 +42,11 @@ var import_oxc_resolver = require("oxc-resolver");
42
42
  var import_magic_string = __toESM(require("magic-string"), 1);
43
43
 
44
44
  // src/combinators/first-set.ts
45
+ function resolveNamedRef(p, resolve2) {
46
+ if (resolve2 === void 0) return void 0;
47
+ const name = p._ruleName;
48
+ return name === void 0 ? void 0 : resolve2(name);
49
+ }
45
50
  function union(a, b) {
46
51
  if (a.kind === "any" || b.kind === "any") return { kind: "any" };
47
52
  if (a.kind === "empty") return b;
@@ -79,10 +84,10 @@ function any() {
79
84
  function empty() {
80
85
  return { kind: "empty" };
81
86
  }
82
- function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
87
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
83
88
  if (seen.has(p)) return true;
84
89
  seen.add(p);
85
- const me = (c) => matchesEmpty(c, seen);
90
+ const me = (c) => matchesEmpty(c, seen, resolve2);
86
91
  const d = p._def;
87
92
  switch (d.tag) {
88
93
  case "literal":
@@ -99,8 +104,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
99
104
  case "many":
100
105
  case "optional":
101
106
  case "not":
107
+ case "peek":
102
108
  return true;
103
109
  // zero repetitions / absent / lookahead
110
+ // Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
111
+ // Any `min >= 1` requires that many ITEMS, so it is nullable only when the
112
+ // item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
113
+ // nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
114
+ // the gating diagnostic for a list that can never match empty.)
115
+ case "sepBy":
116
+ return d.min >= 1 ? me(d.parser) : true;
104
117
  case "oneOrMore":
105
118
  return me(d.parser);
106
119
  case "sequence":
@@ -124,7 +137,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
124
137
  try {
125
138
  return me(d.thunk());
126
139
  } catch {
127
- return true;
140
+ const t = resolveNamedRef(p, resolve2);
141
+ return t ? me(t) : true;
128
142
  }
129
143
  default:
130
144
  return true;
@@ -133,18 +147,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
133
147
  function isZeroWidthAssertion(p) {
134
148
  return p._def.tag === "not";
135
149
  }
150
+ function isPositiveLookahead(p) {
151
+ return p._def.tag === "peek";
152
+ }
153
+ function narrowBy(acc, constraint) {
154
+ if (constraint.kind === "any") return acc;
155
+ if (acc === null) return constraint;
156
+ if (acc.kind === "any") return constraint;
157
+ if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
158
+ const ranges = [];
159
+ for (const a of acc.ranges) for (const b of constraint.ranges) {
160
+ const lo = Math.max(a.lo, b.lo);
161
+ const hi = Math.min(a.hi, b.hi);
162
+ if (lo <= hi) ranges.push({ lo, hi });
163
+ }
164
+ return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
165
+ }
166
+ function applyAssertion(fs2, assertion) {
167
+ if (assertion === null) return fs2;
168
+ if (fs2.kind === "empty") return assertion;
169
+ return narrowBy(fs2, assertion) ?? fs2;
170
+ }
136
171
  function sequenceFirstSet(parsers) {
137
172
  let fs2 = empty();
173
+ let assertion = null;
138
174
  for (const p of parsers) {
175
+ if (isPositiveLookahead(p)) {
176
+ assertion = narrowBy(assertion, p._meta.firstSet);
177
+ continue;
178
+ }
139
179
  if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
140
- if (!matchesEmpty(p)) return fs2;
180
+ if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
141
181
  }
142
- return fs2;
182
+ return applyAssertion(fs2, assertion);
143
183
  }
144
- function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
184
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
145
185
  if (seen.has(p)) return any();
146
186
  seen.add(p);
147
- const fs2 = (c) => firstSetOf(c, seen);
187
+ const fs2 = (c) => firstSetOf(c, seen, resolve2);
188
+ const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
148
189
  const d = p._def;
149
190
  switch (d.tag) {
150
191
  case "literal":
@@ -156,7 +197,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
156
197
  try {
157
198
  return fs2(d.thunk());
158
199
  } catch {
159
- return any();
200
+ const t = resolveNamedRef(p, resolve2);
201
+ return t ? fs2(t) : any();
160
202
  }
161
203
  case "choice": {
162
204
  let out = empty();
@@ -165,11 +207,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
165
207
  }
166
208
  case "sequence": {
167
209
  let out = empty();
210
+ let assertion = null;
168
211
  for (const term of d.parsers) {
212
+ if (isPositiveLookahead(term)) {
213
+ assertion = narrowBy(assertion, fs2(term));
214
+ continue;
215
+ }
169
216
  if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
170
- if (!matchesEmpty(term)) return out;
217
+ if (!empties(term)) return applyAssertion(out, assertion);
171
218
  }
172
- return out;
219
+ return applyAssertion(out, assertion);
220
+ }
221
+ case "peek": {
222
+ const inner = d.parser;
223
+ return empties(inner) ? any() : fs2(inner);
173
224
  }
174
225
  case "oneOrMore":
175
226
  case "many":
@@ -185,6 +236,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
185
236
  return fs2(d.parser);
186
237
  case "sepBy":
187
238
  return fs2(d.parser);
239
+ // both min 0 and min 1 start with the item
188
240
  case "skip":
189
241
  return fs2(d.main);
190
242
  default:
@@ -363,28 +415,28 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
363
415
  else if (len === 1) {
364
416
  b[keySingle] = arr[0];
365
417
  b[keyMulti] = void 0;
366
- } else arr.length = len;
418
+ } else if (arr.length !== len) arr.length = len;
367
419
  return;
368
420
  }
369
421
  if (len === 0) b[keySingle] = void 0;
370
422
  }
371
423
  function rollbackCstCapture(ctx, mark) {
372
- if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
424
+ if (ctx._errors && mark.errors !== void 0 && ctx._errors.length !== mark.errors) ctx._errors.length = mark.errors;
373
425
  const b = ctx._cstBuf;
374
426
  if (b) {
375
427
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
376
428
  rollbackBufList(b, "ch", "single", mark.leaves);
377
429
  if (b.tl) {
378
430
  if (mark.tlog === 0) b.tl = void 0;
379
- else b.tl.length = mark.tlog;
431
+ else if (b.tl.length !== mark.tlog) b.tl.length = mark.tlog;
380
432
  }
381
- if (ctx._fields) ctx._fields.length = mark.fields;
433
+ if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
382
434
  return;
383
435
  }
384
- if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
385
- if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
386
- if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
387
- if (ctx._fields) ctx._fields.length = mark.fields;
436
+ if (ctx._cstRawChildren && ctx._cstRawChildren.length !== mark.raw) ctx._cstRawChildren.length = mark.raw;
437
+ if (ctx._cstTriviaLog && ctx._cstTriviaLog.length !== mark.tlog) ctx._cstTriviaLog.length = mark.tlog;
438
+ if (ctx._cstLeaves && ctx._cstLeaves.length !== mark.leaves) ctx._cstLeaves.length = mark.leaves;
439
+ if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
388
440
  }
389
441
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
390
442
  const insertIdx = cstRawLen(ctx);
@@ -906,9 +958,46 @@ function firstSetFromRegex(source) {
906
958
  return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
907
959
  }
908
960
 
961
+ // src/combinators/case-fold.ts
962
+ function canonical(ch2) {
963
+ const u = ch2.toUpperCase();
964
+ if (u.length !== 1) return ch2;
965
+ if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
966
+ return u;
967
+ }
968
+ var foldIndex = null;
969
+ function buildFoldIndex() {
970
+ const byCanonical = /* @__PURE__ */ new Map();
971
+ for (let cp = 0; cp < 65536; cp++) {
972
+ if (cp >= 55296 && cp <= 57343) continue;
973
+ const key = canonical(String.fromCharCode(cp));
974
+ const bucket = byCanonical.get(key);
975
+ if (bucket) bucket.push(cp);
976
+ else byCanonical.set(key, [cp]);
977
+ }
978
+ const index = /* @__PURE__ */ new Map();
979
+ for (const members of byCanonical.values()) {
980
+ if (members.length < 2) continue;
981
+ for (const cp of members) index.set(cp, members);
982
+ }
983
+ return index;
984
+ }
985
+ function caseFoldVariants(cp) {
986
+ if (cp < 128) {
987
+ if (cp >= 65 && cp <= 90) return [cp, cp + 32];
988
+ if (cp >= 97 && cp <= 122) return [cp, cp - 32];
989
+ return [cp];
990
+ }
991
+ if (cp > 65535) return [cp];
992
+ foldIndex ??= buildFoldIndex();
993
+ return foldIndex.get(cp) ?? [cp];
994
+ }
995
+
909
996
  // src/combinators/keywords.ts
910
- function word(str, boundary = "_0-9A-Za-z") {
911
- return keywords([str], { boundary });
997
+ function word(str, boundaryOrOpts, opts) {
998
+ const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
999
+ const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
1000
+ return keywords([str], { ...rest, boundary });
912
1001
  }
913
1002
  function escapeRe(s) {
914
1003
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -917,7 +1006,7 @@ function keywords(words, opts = {}) {
917
1006
  const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
918
1007
  const alt = sorted.map(escapeRe).join("|");
919
1008
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
920
- const flags = opts.caseInsensitive ? "iuy" : "uy";
1009
+ const flags = opts.caseInsensitive ? "iy" : "uy";
921
1010
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
922
1011
  let firstSet2 = empty();
923
1012
  for (const w of sorted) {
@@ -925,10 +1014,7 @@ function keywords(words, opts = {}) {
925
1014
  const cp = w.codePointAt(0);
926
1015
  firstSet2 = union(firstSet2, fromChar(cp));
927
1016
  if (opts.caseInsensitive) {
928
- const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
929
- const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
930
- if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
931
- if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
1017
+ for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
932
1018
  }
933
1019
  }
934
1020
  const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
@@ -1039,6 +1125,10 @@ function captureError(ctx, error) {
1039
1125
 
1040
1126
  // src/combinators/expect.ts
1041
1127
  function deriveExpected(c) {
1128
+ return derive(c, /* @__PURE__ */ new Set());
1129
+ }
1130
+ function derive(c, seen) {
1131
+ const deriveExpected2 = (p) => derive(p, seen);
1042
1132
  const def = c._def;
1043
1133
  switch (def.tag) {
1044
1134
  case "literal":
@@ -1050,20 +1140,32 @@ function deriveExpected(c) {
1050
1140
  case "label":
1051
1141
  return [def.label];
1052
1142
  case "choice":
1053
- return def.parsers.flatMap(deriveExpected);
1054
- case "sequence":
1055
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1143
+ return def.parsers.flatMap(deriveExpected2);
1144
+ // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1145
+ // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1146
+ // able to fail first — deriving from term 0 alone named a token the parse
1147
+ // never actually required. A leading `not(…)` is zero-width and contributes
1148
+ // nothing (its own expectation is about what must NOT be here), but it is
1149
+ // nullable, so keep scanning past it. Stop at the first term that must match.
1150
+ case "sequence": {
1151
+ const out = [];
1152
+ for (const term of def.parsers) {
1153
+ if (!isZeroWidthAssertion(term)) out.push(...deriveExpected2(term));
1154
+ if (!matchesEmpty(term)) break;
1155
+ }
1156
+ return out;
1157
+ }
1056
1158
  case "attempt":
1057
- return deriveExpected(def.parser);
1159
+ return deriveExpected2(def.parser);
1058
1160
  // Delegating wrappers whose first-token failure is their inner parser's — must
1059
1161
  // pass through so a start-failure (and the first-set-miss fast-path in attempt/
1060
1162
  // node, which reads this) names the real expected token, not the wrapper tag.
1061
1163
  case "field":
1062
1164
  case "withCtx":
1063
1165
  case "recover":
1064
- return deriveExpected(def.parser);
1166
+ return deriveExpected2(def.parser);
1065
1167
  case "skip":
1066
- return deriveExpected(def.main);
1168
+ return deriveExpected2(def.main);
1067
1169
  case "expect":
1068
1170
  return def.expected;
1069
1171
  case "node":
@@ -1076,13 +1178,18 @@ function deriveExpected(c) {
1076
1178
  case "oneOrMore":
1077
1179
  case "transform":
1078
1180
  case "not":
1079
- return deriveExpected(def.parser);
1181
+ case "peek":
1182
+ return deriveExpected2(def.parser);
1080
1183
  case "lazy": {
1081
1184
  const name = c._ruleName;
1185
+ if (seen.has(c)) return name ? [name] : [];
1186
+ seen.add(c);
1082
1187
  try {
1083
- return deriveExpected(def.thunk());
1188
+ return deriveExpected2(def.thunk());
1084
1189
  } catch {
1085
1190
  return name ? [name] : [];
1191
+ } finally {
1192
+ seen.delete(c);
1086
1193
  }
1087
1194
  }
1088
1195
  default:
@@ -1210,14 +1317,14 @@ function choice(...args) {
1210
1317
  const result = parsers[i].parse(input, pos, ctx);
1211
1318
  if (!result.ok) {
1212
1319
  rollbackCstCapture(ctx, mark);
1213
- if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
1320
+ if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1214
1321
  expected.push(...result.expected);
1215
1322
  continue;
1216
1323
  }
1217
1324
  const checks = autoNot[i];
1218
1325
  if (checks && autoNotFires(input, result.span.end, checks)) {
1219
1326
  rollbackCstCapture(ctx, mark);
1220
- if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
1327
+ if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1221
1328
  continue;
1222
1329
  }
1223
1330
  return result;
@@ -1544,7 +1651,14 @@ function saveTriviaMark(ctx) {
1544
1651
  }
1545
1652
  function rollbackTrivia(ctx, mark) {
1546
1653
  rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1547
- if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1654
+ if (ctx._triviaLog && ctx._triviaLog.length !== mark.log) ctx._triviaLog.length = mark.log;
1655
+ }
1656
+ function saveLookaheadMark(ctx) {
1657
+ return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
1658
+ }
1659
+ function rollbackLookahead(ctx, mark) {
1660
+ rollbackTrivia(ctx, mark);
1661
+ if (ctx._probe) ctx._probe.best = mark.probeBest;
1548
1662
  }
1549
1663
  function scanWithLabels(input, cur, ctx) {
1550
1664
  const triviaP = ctx.trivia;
@@ -1905,7 +2019,7 @@ function attempt(parser2) {
1905
2019
  const result = parser2.parse(input, pos, ctx);
1906
2020
  if (result.ok) return result;
1907
2021
  rollbackCstCapture(ctx, mark);
1908
- if (log !== void 0 && ctx._triviaLog) ctx._triviaLog.length = log;
2022
+ if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
1909
2023
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1910
2024
  }
1911
2025
  };
@@ -1943,13 +2057,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
1943
2057
  }
1944
2058
  return { value: result.value, end: result.span.end };
1945
2059
  }
1946
- function many(combinator) {
2060
+ function resolveBounds(what, opts) {
2061
+ const min = opts.min ?? 0;
2062
+ const max = opts.max ?? Infinity;
2063
+ const bad = (msg) => {
2064
+ throw new RangeError(`parseman: ${what} ${msg}`);
2065
+ };
2066
+ if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
2067
+ if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
2068
+ if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
2069
+ return { min, max };
2070
+ }
2071
+ function many(combinator, opts = {}) {
2072
+ const { min, max } = resolveBounds("many()", opts);
2073
+ if (min >= 1) return atLeast(combinator, min, max);
1947
2074
  const meta = {
1948
2075
  firstSet: combinator._meta.firstSet,
1949
2076
  canMatchNewline: combinator._meta.canMatchNewline,
1950
2077
  isTrivia: false
1951
2078
  };
1952
- const def = { tag: "many", parser: combinator, min: 0 };
2079
+ const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
1953
2080
  let expected;
1954
2081
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1955
2082
  return {
@@ -1959,7 +2086,9 @@ function many(combinator) {
1959
2086
  parse(input, pos, ctx) {
1960
2087
  const values = def.valueUnused ? void 0 : [];
1961
2088
  let cur = pos;
2089
+ let count = 0;
1962
2090
  while (cur < input.length) {
2091
+ if (count >= max) break;
1963
2092
  const item = repItem(combinator, input, cur, ctx, guardable);
1964
2093
  if (item === "stop") break;
1965
2094
  if ("fail" in item) {
@@ -1970,23 +2099,28 @@ function many(combinator) {
1970
2099
  const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1971
2100
  if (values !== void 0) values.push(error);
1972
2101
  captureError(ctx, error);
2102
+ count++;
1973
2103
  cur = end;
1974
2104
  continue;
1975
2105
  }
1976
2106
  if (values !== void 0) values.push(item.value);
2107
+ count++;
1977
2108
  cur = item.end;
1978
2109
  }
1979
2110
  return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1980
2111
  }
1981
2112
  };
1982
2113
  }
1983
- function oneOrMore(combinator) {
2114
+ function oneOrMore(combinator, opts = {}) {
2115
+ return many(combinator, { ...opts, min: opts.min ?? 1 });
2116
+ }
2117
+ function atLeast(combinator, min, max) {
1984
2118
  const meta = {
1985
2119
  firstSet: combinator._meta.firstSet,
1986
2120
  canMatchNewline: combinator._meta.canMatchNewline,
1987
2121
  isTrivia: false
1988
2122
  };
1989
- const def = { tag: "oneOrMore", parser: combinator, min: 1 };
2123
+ const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
1990
2124
  let expected;
1991
2125
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1992
2126
  return {
@@ -1998,7 +2132,19 @@ function oneOrMore(combinator) {
1998
2132
  if (!first.ok) return first;
1999
2133
  const values = def.valueUnused ? void 0 : [first.value];
2000
2134
  let cur = first.span.end;
2135
+ let count = 1;
2136
+ while (count < min) {
2137
+ const item = repItem(combinator, input, cur, ctx, guardable);
2138
+ if (item === "stop" || "fail" in item) {
2139
+ expected ??= deriveExpected(combinator);
2140
+ return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2141
+ }
2142
+ if (values !== void 0) values.push(item.value);
2143
+ cur = item.end;
2144
+ count++;
2145
+ }
2001
2146
  while (cur < input.length) {
2147
+ if (count >= max) break;
2002
2148
  const item = repItem(combinator, input, cur, ctx, guardable);
2003
2149
  if (item === "stop") break;
2004
2150
  if ("fail" in item) {
@@ -2009,10 +2155,12 @@ function oneOrMore(combinator) {
2009
2155
  const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
2010
2156
  if (values !== void 0) values.push(error);
2011
2157
  captureError(ctx, error);
2158
+ count++;
2012
2159
  cur = end;
2013
2160
  continue;
2014
2161
  }
2015
2162
  if (values !== void 0) values.push(item.value);
2163
+ count++;
2016
2164
  cur = item.end;
2017
2165
  }
2018
2166
  return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
@@ -2042,17 +2190,33 @@ function optional(combinator) {
2042
2190
  }
2043
2191
  };
2044
2192
  }
2045
- function sepBy(combinator, separator) {
2193
+ function oneOrMoreSep(combinator, separator, opts = {}) {
2194
+ return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
2195
+ }
2196
+ function sepBy(combinator, separator, opts = {}) {
2197
+ const { min, max } = resolveBounds("sepBy()", opts);
2198
+ const trailing = opts.trailing ?? "forbid";
2046
2199
  const meta = {
2047
2200
  firstSet: combinator._meta.firstSet,
2048
2201
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
2049
2202
  isTrivia: false
2050
2203
  };
2051
2204
  let expected;
2205
+ const failAt2 = (at) => {
2206
+ expected ??= deriveExpected(combinator);
2207
+ return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
2208
+ };
2052
2209
  return {
2053
2210
  _tag: "sepBy",
2054
2211
  _meta: meta,
2055
- _def: { tag: "sepBy", parser: combinator, separator },
2212
+ _def: {
2213
+ tag: "sepBy",
2214
+ parser: combinator,
2215
+ separator,
2216
+ min,
2217
+ ...max === Infinity ? {} : { max },
2218
+ ...trailing === "forbid" ? {} : { trailing }
2219
+ },
2056
2220
  parse(input, pos, ctx) {
2057
2221
  const first = combinator.parse(input, pos, ctx);
2058
2222
  const values = [];
@@ -2063,6 +2227,7 @@ function sepBy(combinator, separator) {
2063
2227
  } else {
2064
2228
  const term = ctx._tolerant ? ctx._sync : void 0;
2065
2229
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2230
+ if (min >= 1) return failAt2(pos);
2066
2231
  return { ok: true, value: [], span: { start: pos, end: pos } };
2067
2232
  }
2068
2233
  expected ??= deriveExpected(combinator);
@@ -2072,6 +2237,7 @@ function sepBy(combinator, separator) {
2072
2237
  cur = rec.end;
2073
2238
  }
2074
2239
  while (cur < input.length) {
2240
+ if (values.length >= max) break;
2075
2241
  const loopMark = saveTriviaMark(ctx);
2076
2242
  let sepPos = cur;
2077
2243
  if (ctx.trivia) {
@@ -2088,6 +2254,7 @@ function sepBy(combinator, separator) {
2088
2254
  rollbackTrivia(ctx, loopMark);
2089
2255
  break;
2090
2256
  }
2257
+ const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
2091
2258
  let nextPos = sep2.span.end;
2092
2259
  if (ctx.trivia) {
2093
2260
  if (needsDeferredTriviaCommit(ctx)) {
@@ -2109,12 +2276,18 @@ function sepBy(combinator, separator) {
2109
2276
  cur = rec.end;
2110
2277
  continue;
2111
2278
  }
2279
+ if (sepMark !== void 0) {
2280
+ rollbackTrivia(ctx, sepMark);
2281
+ cur = sep2.span.end;
2282
+ break;
2283
+ }
2112
2284
  rollbackTrivia(ctx, loopMark);
2113
2285
  break;
2114
2286
  }
2115
2287
  values.push(next.value);
2116
2288
  cur = next.span.end;
2117
2289
  }
2290
+ if (values.length < min) return failAt2(cur);
2118
2291
  return { ok: true, value: values, span: { start: pos, end: cur } };
2119
2292
  }
2120
2293
  };
@@ -2167,6 +2340,7 @@ function markUnusedValues(root) {
2167
2340
  visit(def.separator, false);
2168
2341
  return;
2169
2342
  case "not":
2343
+ case "peek":
2170
2344
  visit(def.parser, false);
2171
2345
  return;
2172
2346
  case "grammar":
@@ -2285,9 +2459,9 @@ function not(combinator) {
2285
2459
  _meta: meta,
2286
2460
  _def: { tag: "not", parser: combinator },
2287
2461
  parse(input, pos, ctx) {
2288
- const mark = saveCstMark(ctx);
2462
+ const mark = saveLookaheadMark(ctx);
2289
2463
  const result = combinator.parse(input, pos, ctx);
2290
- rollbackCstCapture(ctx, mark);
2464
+ rollbackLookahead(ctx, mark);
2291
2465
  if (result.ok) {
2292
2466
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
2293
2467
  }
@@ -2296,6 +2470,28 @@ function not(combinator) {
2296
2470
  };
2297
2471
  }
2298
2472
 
2473
+ // src/combinators/peek.ts
2474
+ function peek(combinator) {
2475
+ const meta = {
2476
+ // A nullable body imposes no first-char constraint — `any()`, not its set.
2477
+ firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
2478
+ canMatchNewline: false,
2479
+ isTrivia: false
2480
+ };
2481
+ return {
2482
+ _tag: "peek",
2483
+ _meta: meta,
2484
+ _def: { tag: "peek", parser: combinator },
2485
+ parse(input, pos, ctx) {
2486
+ const mark = saveLookaheadMark(ctx);
2487
+ const result = combinator.parse(input, pos, ctx);
2488
+ rollbackLookahead(ctx, mark);
2489
+ if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
2490
+ return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
2491
+ }
2492
+ };
2493
+ }
2494
+
2299
2495
  // src/compiler/build-arity.ts
2300
2496
  var PARAM_LIST_RE = (
2301
2497
  // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
@@ -2394,6 +2590,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2394
2590
  case "expect":
2395
2591
  case "withCtx":
2396
2592
  case "not":
2593
+ case "peek":
2397
2594
  return parserHasOwnFields(d.parser, seen);
2398
2595
  default:
2399
2596
  return false;
@@ -2433,6 +2630,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2433
2630
  case "expect":
2434
2631
  case "withCtx":
2435
2632
  case "not":
2633
+ case "peek":
2436
2634
  case "field":
2437
2635
  return parserHasTriviaSite(d.parser, seen);
2438
2636
  case "choice":
@@ -2482,6 +2680,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2482
2680
  case "expect":
2483
2681
  case "withCtx":
2484
2682
  case "not":
2683
+ case "peek":
2485
2684
  return parserEnablesTriviaCapture(d.parser, seen);
2486
2685
  // leaf() clears the inner CST/trivia collector and publishes only its own
2487
2686
  // terminal result. An inner parser() capture cannot reach this node.
@@ -2830,7 +3029,7 @@ function leaf(root, fn) {
2830
3029
  }
2831
3030
 
2832
3031
  // src/version.ts
2833
- var PARSEMAN_VERSION = "0.33.0";
3032
+ var PARSEMAN_VERSION = "0.35.0";
2834
3033
 
2835
3034
  // src/analysis/gating.ts
2836
3035
  var isAny = (fs2) => fs2.kind === "any";
@@ -2870,14 +3069,15 @@ function firstSetToString(fs2) {
2870
3069
  var ruleNameOf = (p) => p._ruleName;
2871
3070
  var SUGGESTIONS = {
2872
3071
  "broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
2873
- "leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating.",
2874
- "nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char.",
3072
+ "leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating \u2014 use peek(X), which is zero-width AND carries X first-set.",
3073
+ "nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char. A plain sepBy(item, sep) is ALSO nullable \u2014 pass { min: 1 } for a list that cannot be empty.",
2875
3074
  "cross-artifact-ref": "parseman >=0.32.0 resolves a g.Foo ref first-set at fuse time; if still ANY the target rule is itself ungated \u2014 analyze it and give it a concrete non-nullable lead.",
2876
3075
  "opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
2877
3076
  "ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
2878
3077
  };
2879
- function classifyBroadArm(arm) {
3078
+ function classifyBroadArm(arm, resolve2) {
2880
3079
  const seen = /* @__PURE__ */ new Set();
3080
+ const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
2881
3081
  const walk2 = (p) => {
2882
3082
  if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
2883
3083
  seen.add(p);
@@ -2889,6 +3089,10 @@ function classifyBroadArm(arm) {
2889
3089
  return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
2890
3090
  case "not":
2891
3091
  return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
3092
+ // peek() CARRIES its body's first-set, so it is only broad when the body is
3093
+ // (or the body is nullable) — walk in for the real cause.
3094
+ case "peek":
3095
+ return walk2(d.parser);
2892
3096
  case "scanTo":
2893
3097
  return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
2894
3098
  // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
@@ -2905,18 +3109,23 @@ function classifyBroadArm(arm) {
2905
3109
  return walk2(d.parser);
2906
3110
  case "lazy": {
2907
3111
  const name = p._ruleName;
3112
+ let target;
2908
3113
  try {
2909
- const inner = walk2(d.thunk());
2910
- if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
2911
- return inner;
3114
+ target = d.thunk();
2912
3115
  } catch {
2913
- return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}` };
3116
+ const bound = name !== void 0 ? resolve2?.(name) : void 0;
3117
+ if (bound === void 0)
3118
+ return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
3119
+ target = bound;
2914
3120
  }
3121
+ const inner = walk2(target);
3122
+ if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
3123
+ return inner;
2915
3124
  }
2916
3125
  case "optional":
2917
3126
  case "many": {
2918
3127
  const inner = walk2(d.parser);
2919
- return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
3128
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
2920
3129
  }
2921
3130
  case "oneOrMore":
2922
3131
  case "transform":
@@ -2938,13 +3147,13 @@ function classifyBroadArm(arm) {
2938
3147
  sawNullablePrefix = true;
2939
3148
  continue;
2940
3149
  }
2941
- if (isAny(firstSetOf(t))) {
3150
+ if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
2942
3151
  const inner = walk2(t);
2943
3152
  if (sawNullablePrefix)
2944
- return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
3153
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
2945
3154
  return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
2946
3155
  }
2947
- if (matchesEmpty(t)) {
3156
+ if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
2948
3157
  sawNullablePrefix = true;
2949
3158
  continue;
2950
3159
  }
@@ -2955,7 +3164,7 @@ function classifyBroadArm(arm) {
2955
3164
  case "choice": {
2956
3165
  for (const a of d.parsers) {
2957
3166
  const r = walk2(a);
2958
- if (r) return { cause: r.cause, detail: `choice arm \u2192 ${r.detail}` };
3167
+ if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
2959
3168
  }
2960
3169
  return null;
2961
3170
  }
@@ -2992,7 +3201,7 @@ function detectAntiPatterns(rule, arms) {
2992
3201
  kind: "double-not",
2993
3202
  rule,
2994
3203
  armIndex: i,
2995
- message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Remove it; let the arm lead with its consuming terminal, or use word()/keywords() for an exact first-set."
3204
+ message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Prefer letting the arm lead with its consuming terminal; where a real positive lookahead IS needed, use peek(X) \u2014 zero-width like not(not(X)) but it CARRIES X's first-set, so the arm still dispatches."
2996
3205
  });
2997
3206
  } else {
2998
3207
  out.push({
@@ -3015,6 +3224,9 @@ function detectAntiPatterns(rule, arms) {
3015
3224
  return out;
3016
3225
  }
3017
3226
  function analyzeGating(entry, opts) {
3227
+ return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3228
+ }
3229
+ function analyzeGatingRules(ruleMap, opts) {
3018
3230
  const raw = [];
3019
3231
  const antiPatterns = [];
3020
3232
  const visited = /* @__PURE__ */ new Set();
@@ -3024,7 +3236,7 @@ function analyzeGating(entry, opts) {
3024
3236
  const d = p._def;
3025
3237
  const rule = ruleNameOf(p) ?? enclosingRule;
3026
3238
  if (d.tag === "choice") {
3027
- raw.push({ g: analyzeChoice(p, d, rule), rule });
3239
+ raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
3028
3240
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
3029
3241
  }
3030
3242
  const rec = d;
@@ -3041,7 +3253,7 @@ function analyzeGating(entry, opts) {
3041
3253
  }
3042
3254
  for (const k of kids) visit(k, rule);
3043
3255
  };
3044
- visit(entry, ruleNameOf(entry) ?? "<entry>");
3256
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3045
3257
  const perRule = /* @__PURE__ */ new Map();
3046
3258
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3047
3259
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3057,18 +3269,19 @@ function analyzeGating(entry, opts) {
3057
3269
  });
3058
3270
  const gated = choices.filter((c) => c.gates === "yes").length;
3059
3271
  const recoverable = choices.filter((c) => c.gates === "recoverable").length;
3060
- const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
3272
+ const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
3061
3273
  const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
3274
+ const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
3062
3275
  const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
3063
- return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
3276
+ return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
3064
3277
  }
3065
- function analyzeChoice(p, d, rule) {
3278
+ function analyzeChoice(p, d, rule, resolve2) {
3066
3279
  const arms = d.parsers;
3067
3280
  const shallow = arms.map((a) => a._meta.firstSet);
3068
- const deep = arms.map((a) => firstSetOf(a));
3281
+ const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
3069
3282
  const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
3070
- const { cause, detail } = classifyBroadArm(arms[index]);
3071
- return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
3283
+ const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
3284
+ return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
3072
3285
  });
3073
3286
  const overlaps = [];
3074
3287
  for (let i = 0; i < deep.length; i++)
@@ -3084,10 +3297,12 @@ function analyzeChoice(p, d, rule) {
3084
3297
  if (d.disjoint) gates = "yes";
3085
3298
  else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
3086
3299
  else gates = "no";
3300
+ const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
3087
3301
  return {
3088
3302
  rule,
3089
3303
  strategy: d.strategy?.tag ?? "firstMatch",
3090
3304
  gates,
3305
+ deferred,
3091
3306
  combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
3092
3307
  anyArms,
3093
3308
  overlaps
@@ -3254,6 +3469,7 @@ function children(def, winners) {
3254
3469
  case "field":
3255
3470
  case "grammar":
3256
3471
  case "not":
3472
+ case "peek":
3257
3473
  case "node":
3258
3474
  case "guard":
3259
3475
  case "withCtx":
@@ -5012,9 +5228,9 @@ function planDisjointDispatch(parsers) {
5012
5228
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
5013
5229
  }
5014
5230
  function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
5015
- const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
5016
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
5017
- return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
5231
+ const fieldRestore = mF ? `; if (_ctx._fields && _ctx._fields.length !== ${mF}) _ctx._fields.length = ${mF}` : "";
5232
+ const base = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${mL}) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${mR}) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${mTl}) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
5233
+ return mLg ? `${base}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${mLg}) _ctx._triviaLog.length = ${mLg}` : base;
5018
5234
  }
5019
5235
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
5020
5236
  if (seen.has(p)) return false;
@@ -5027,6 +5243,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
5027
5243
  case "keywords":
5028
5244
  case "guard":
5029
5245
  case "not":
5246
+ // peek(): emitted under a non-capturing probe ctx and zero-width on both
5247
+ // outcomes, so it can never leave a partial capture behind.
5248
+ case "peek":
5030
5249
  case "trivia":
5031
5250
  case "token":
5032
5251
  case "leaf":
@@ -5098,6 +5317,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
5098
5317
  case "leaf":
5099
5318
  return true;
5100
5319
  case "not":
5320
+ case "peek":
5101
5321
  case "guard":
5102
5322
  case "trivia":
5103
5323
  case "unknown":
@@ -5224,6 +5444,7 @@ function emitKeywordsFast(def, ctx, pos) {
5224
5444
  if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
5225
5445
  if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
5226
5446
  if (def.caseInsensitive && def.boundary) return null;
5447
+ if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
5227
5448
  let boundary = null;
5228
5449
  if (def.boundary) {
5229
5450
  let body = def.boundary;
@@ -5271,7 +5492,7 @@ function emitKeywords(def, ctx, pos) {
5271
5492
  if (fast) return fast;
5272
5493
  const alt = def.words.map(escapeKeywordRe).join("|");
5273
5494
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
5274
- const flags = def.caseInsensitive ? "iuy" : "uy";
5495
+ const flags = def.caseInsensitive ? "iy" : "uy";
5275
5496
  const source = `(?:${alt})${boundary}`;
5276
5497
  const key = `${source}/${flags}`;
5277
5498
  let rName = ctx.regexMap.get(key);
@@ -5371,7 +5592,7 @@ function emitSeqValues(def, ctx, pos) {
5371
5592
  const endAfterV = v(ctx, "_sea");
5372
5593
  stmts.push(
5373
5594
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
5374
- `${ind(ctx)}if (${endAfterV} > ${scanEndV}) { ${curV} = ${endAfterV} } else { if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; }`
5595
+ `${ind(ctx)}if (${endAfterV} > ${scanEndV}) { ${curV} = ${endAfterV} } else { if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; }`
5375
5596
  );
5376
5597
  valueVars.push(r2.valueVar);
5377
5598
  continue;
@@ -5417,6 +5638,7 @@ function failsAtStart(p) {
5417
5638
  case "keywords":
5418
5639
  case "guard":
5419
5640
  case "not":
5641
+ case "peek":
5420
5642
  return true;
5421
5643
  case "transform":
5422
5644
  case "label":
@@ -5830,14 +6052,17 @@ function emitMany(def, ctx, pos) {
5830
6052
  ];
5831
6053
  const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
5832
6054
  if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
5833
- if (def.min === 1) {
6055
+ for (let i = 0; i < def.min; i++) {
5834
6056
  const firstR = emit(def.parser, ctx, curV);
5835
6057
  stmts.push(...firstR.stmts);
5836
6058
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
5837
6059
  stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
5838
6060
  }
6061
+ const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
6062
+ if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
5839
6063
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
5840
6064
  ctx.indent++;
6065
+ if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
5841
6066
  let itemPos = curV;
5842
6067
  let rollback = "";
5843
6068
  if (ctx.activeTrivia) {
@@ -5854,7 +6079,7 @@ function emitMany(def, ctx, pos) {
5854
6079
  `${ind(ctx)}const ${npV} = ${capFn}(input, ${curV}, _ctx, 1)`
5855
6080
  );
5856
6081
  itemPos = npV;
5857
- rollback = `if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
6082
+ rollback = `if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
5858
6083
  } else {
5859
6084
  const trivFn = ensureTriviaFn(ctx);
5860
6085
  const npV = v(ctx, "_np");
@@ -5878,7 +6103,7 @@ function emitMany(def, ctx, pos) {
5878
6103
  `${ind(ctx)}if (!${iterOk}) {`,
5879
6104
  `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
5880
6105
  `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
5881
- ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
6106
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
5882
6107
  `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
5883
6108
  `${ind(ctx)} ${curV} = ${rrV}.end`,
5884
6109
  `${ind(ctx)} continue`,
@@ -5891,6 +6116,7 @@ function emitMany(def, ctx, pos) {
5891
6116
  stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
5892
6117
  }
5893
6118
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
6119
+ else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
5894
6120
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
5895
6121
  ctx.indent--;
5896
6122
  stmts.push(`${ind(ctx)}}`);
@@ -5916,7 +6142,7 @@ function emitAttempt(p, def, ctx, pos) {
5916
6142
  const log = v(ctx, "_atg");
5917
6143
  const fields = v(ctx, "_atf");
5918
6144
  const errors = v(ctx, "_ate");
5919
- const rollback = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`;
6145
+ const rollback = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${leaves}) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${raw}) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${trivia2}) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${log}) _ctx._triviaLog.length = ${log}; if (_ctx._fields && _ctx._fields.length !== ${fields}) _ctx._fields.length = ${fields}; if (_ctx._errors && _ctx._errors.length !== ${errors}) _ctx._errors.length = ${errors}`;
5920
6146
  const traceId = ctx.coverage?.plan.attempts.get(p);
5921
6147
  const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5922
6148
  const preGuard = [];
@@ -5955,8 +6181,8 @@ function emitSepBy(_p, def, ctx, pos) {
5955
6181
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
5956
6182
  ...firstStmts
5957
6183
  ];
5958
- const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
5959
- if (!rec) return [originalFail];
6184
+ const failItem = (nextOkVar, itemPosVar, breakStmt) => {
6185
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
5960
6186
  const rr = v(ctx, "_rr");
5961
6187
  return [
5962
6188
  `${ind(ctx)}if (!${nextOkVar}) {`,
@@ -5964,10 +6190,24 @@ function emitSepBy(_p, def, ctx, pos) {
5964
6190
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
5965
6191
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
5966
6192
  `${ind(ctx)} }`,
5967
- `${ind(ctx)} ${rb}break`,
6193
+ `${ind(ctx)} ${breakStmt}`,
5968
6194
  `${ind(ctx)}}`
5969
6195
  ];
5970
6196
  };
6197
+ const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
6198
+ const postSepMarks = () => {
6199
+ if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
6200
+ const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
6201
+ return {
6202
+ decl: [
6203
+ `${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
6204
+ `${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
6205
+ `${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
6206
+ `${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
6207
+ ],
6208
+ rb: `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${lv}) _ctx._cstLeaves.length = ${lv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${rw}) _ctx._cstRawChildren.length = ${rw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${tl}) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${lg}) _ctx._triviaLog.length = ${lg}; `
6209
+ };
6210
+ };
5971
6211
  if (rec) {
5972
6212
  const rr0 = v(ctx, "_rr0");
5973
6213
  const didV = v(ctx, "_did");
@@ -5993,6 +6233,7 @@ function emitSepBy(_p, def, ctx, pos) {
5993
6233
  );
5994
6234
  ctx.indent++;
5995
6235
  }
6236
+ if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
5996
6237
  let sepAtPos = curV;
5997
6238
  if (ctx.activeTrivia) {
5998
6239
  if (ctx.capturing) {
@@ -6010,15 +6251,17 @@ function emitSepBy(_p, def, ctx, pos) {
6010
6251
  `${ind(ctx)}const ${spV} = ${capFn}(input, ${curV}, _ctx, 1)`
6011
6252
  );
6012
6253
  sepAtPos = spV;
6013
- const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
6254
+ const rollbackToSep = `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markV}) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${markTl}) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${markLog}) _ctx._triviaLog.length = ${markLog}; `;
6014
6255
  const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
6015
6256
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
6257
+ const post = postSepMarks();
6258
+ stmts.push(...post.decl);
6016
6259
  const npV = v(ctx, "_np");
6017
6260
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
6018
6261
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
6019
6262
  stmts.push(
6020
6263
  ...nextStmts,
6021
- ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
6264
+ ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
6022
6265
  `${ind(ctx)}${arrV}.push(${nextVal})`,
6023
6266
  `${ind(ctx)}${curV} = ${nextEnd}`
6024
6267
  );
@@ -6034,7 +6277,7 @@ function emitSepBy(_p, def, ctx, pos) {
6034
6277
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
6035
6278
  stmts.push(
6036
6279
  ...nextStmts,
6037
- ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
6280
+ ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
6038
6281
  `${ind(ctx)}${arrV}.push(${nextVal})`,
6039
6282
  `${ind(ctx)}${curV} = ${nextEnd}`
6040
6283
  );
@@ -6050,11 +6293,13 @@ function emitSepBy(_p, def, ctx, pos) {
6050
6293
  }
6051
6294
  const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
6052
6295
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
6053
- const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
6296
+ const nextRb = markLv ? `if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${markLv}) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${markRw}) _ctx._cstRawChildren.length = ${markRw}; ` : "";
6297
+ const post = postSepMarks();
6298
+ stmts.push(...post.decl);
6054
6299
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
6055
6300
  stmts.push(
6056
6301
  ...nextStmts,
6057
- ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
6302
+ ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
6058
6303
  `${ind(ctx)}${arrV}.push(${nextVal})`,
6059
6304
  `${ind(ctx)}${curV} = ${nextEnd}`
6060
6305
  );
@@ -6063,6 +6308,9 @@ function emitSepBy(_p, def, ctx, pos) {
6063
6308
  stmts.push(`${ind(ctx)}}`);
6064
6309
  ctx.indent--;
6065
6310
  stmts.push(`${ind(ctx)}}`);
6311
+ if (def.min >= 1) {
6312
+ stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
6313
+ }
6066
6314
  return { stmts, valueVar: arrV, endVar: curV };
6067
6315
  }
6068
6316
  function emitScanTo(def, ctx, pos) {
@@ -6114,17 +6362,44 @@ function emitScanTo(def, ctx, pos) {
6114
6362
  return { stmts, valueVar: valV, endVar: curV };
6115
6363
  }
6116
6364
  function emitNot(def, ctx, pos) {
6365
+ const sinksLive = !!ctx.capturing || !!ctx.recovery;
6366
+ const leaves = sinksLive ? v(ctx, "_ntl") : null;
6367
+ const raw = sinksLive ? v(ctx, "_ntr") : null;
6368
+ const tl = sinksLive ? v(ctx, "_ntt") : null;
6369
+ const log = sinksLive ? v(ctx, "_ntg") : null;
6370
+ const fields = sinksLive ? v(ctx, "_ntf") : null;
6371
+ const errors = sinksLive ? v(ctx, "_nte") : null;
6117
6372
  const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
6118
6373
  const label2 = JSON.stringify(`not(${def.parser._tag})`);
6119
6374
  return {
6120
6375
  stmts: [
6376
+ ...sinksLive ? [
6377
+ `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${tl} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`
6378
+ ] : [],
6121
6379
  ...stmts,
6380
+ ...sinksLive ? [
6381
+ `${ind(ctx)}if (_ctx._cstLeaves && _ctx._cstLeaves.length !== ${leaves}) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren && _ctx._cstRawChildren.length !== ${raw}) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog && _ctx._cstTriviaLog.length !== ${tl}) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${log}) _ctx._triviaLog.length = ${log}; if (_ctx._fields && _ctx._fields.length !== ${fields}) _ctx._fields.length = ${fields}; if (_ctx._errors && _ctx._errors.length !== ${errors}) _ctx._errors.length = ${errors}`
6382
+ ] : [],
6122
6383
  ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
6123
6384
  ],
6124
6385
  valueVar: "null",
6125
6386
  endVar: pos
6126
6387
  };
6127
6388
  }
6389
+ function emitPeek(def, ctx, pos) {
6390
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
6391
+ const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
6392
+ ctx.vars = probeCtx.vars;
6393
+ const label2 = JSON.stringify(`peek(${def.parser._tag})`);
6394
+ return {
6395
+ stmts: [
6396
+ ...stmts,
6397
+ ...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
6398
+ ],
6399
+ valueVar: "null",
6400
+ endVar: pos
6401
+ };
6402
+ }
6128
6403
  function escapeTokenLiteral(value) {
6129
6404
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
6130
6405
  }
@@ -6412,9 +6687,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
6412
6687
  return { stmts, valueVar: vv, endVar: ev };
6413
6688
  }
6414
6689
  function emitLazy(p, def, ctx, pos) {
6415
- const canonical = ctx.ruleNames?.get(p);
6690
+ const canonical2 = ctx.ruleNames?.get(p);
6416
6691
  const usage = ctx.lazyUsage;
6417
- if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
6692
+ if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
6418
6693
  let resolved;
6419
6694
  try {
6420
6695
  resolved = def.thunk();
@@ -6424,7 +6699,7 @@ function emitLazy(p, def, ctx, pos) {
6424
6699
  return emit(resolved, ctx, pos);
6425
6700
  }
6426
6701
  if (!ctx.namedParsers.has(p)) {
6427
- const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
6702
+ const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
6428
6703
  ctx.namedParsers.set(p, fnName2);
6429
6704
  let resolved;
6430
6705
  try {
@@ -6707,6 +6982,8 @@ function emitDispatch(p, ctx, pos) {
6707
6982
  }
6708
6983
  case "not":
6709
6984
  return emitNot(def, ctx, pos);
6985
+ case "peek":
6986
+ return emitPeek(def, ctx, pos);
6710
6987
  case "node":
6711
6988
  return emitNode(def, ctx, pos);
6712
6989
  case "scanTo":
@@ -6795,6 +7072,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
6795
7072
  case "many":
6796
7073
  case "oneOrMore":
6797
7074
  case "not":
7075
+ case "peek":
6798
7076
  case "transform":
6799
7077
  return hasNodeDef(d.parser, seen);
6800
7078
  case "skip":
@@ -6846,17 +7124,18 @@ function childrenOf(def) {
6846
7124
  case "field":
6847
7125
  case "grammar":
6848
7126
  case "not":
7127
+ case "peek":
6849
7128
  case "node":
6850
7129
  case "withCtx":
6851
7130
  case "expect":
6852
7131
  return [def.parser];
6853
- // emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
6854
- // (a mandatory first match, then again inside the repeat loop) — two real
6855
- // emit() call sites, so the usage analysis must see two edges here or it
7132
+ // emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
7133
+ // once (`min` mandatory matches, then again inside the repeat loop) — each is a
7134
+ // real emit() call site, so the usage analysis must see one edge per site or it
6856
7135
  // undercounts a single-use `parser` ref as inline-safe when it's actually
6857
- // referenced from two positions within this one compiled function.
7136
+ // referenced from several positions within this one compiled function.
6858
7137
  case "oneOrMore":
6859
- return [def.parser, def.parser];
7138
+ return Array.from({ length: def.min + 1 }, () => def.parser);
6860
7139
  case "sepBy":
6861
7140
  return [def.parser, def.parser, def.separator];
6862
7141
  case "skip":
@@ -7002,12 +7281,41 @@ function resolveGatingLevel(opt) {
7002
7281
  return "warn";
7003
7282
  }
7004
7283
  function runGatingDiagnostic(combinator, opt) {
7284
+ return reportGating(opt, (o) => analyzeGating(combinator, o));
7285
+ }
7286
+ function runGatingDiagnosticRules(ruleMap, opt) {
7287
+ return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
7288
+ }
7289
+ function runFusedGatingDiagnostic(getRuleMaps, opt) {
7290
+ if (resolveGatingLevel(opt) === "off") return void 0;
7291
+ const ruleMaps = getRuleMaps();
7292
+ if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
7293
+ const winners = /* @__PURE__ */ new Map();
7294
+ for (const map of ruleMaps) for (const [name, rule] of map) {
7295
+ if (rule._def.tag === "lazy") {
7296
+ try {
7297
+ rule._def.thunk();
7298
+ } catch {
7299
+ continue;
7300
+ }
7301
+ }
7302
+ winners.set(name, rule);
7303
+ }
7304
+ const entries = [...winners];
7305
+ return reportGating(opt, (o) => {
7306
+ const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
7307
+ const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
7308
+ return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
7309
+ });
7310
+ }
7311
+ function reportGating(opt, analyze) {
7005
7312
  const level = resolveGatingLevel(opt);
7006
7313
  if (level === "off") return void 0;
7007
- const accept = opt !== null && typeof opt === "object" ? opt.accept : void 0;
7314
+ const obj = opt !== null && typeof opt === "object" ? opt : void 0;
7315
+ const analyzeOpts = obj?.accept !== void 0 || obj?.entryName !== void 0 ? { ...obj.accept !== void 0 ? { accept: obj.accept } : {}, ...obj.entryName !== void 0 ? { entryName: obj.entryName } : {} } : void 0;
7008
7316
  let report;
7009
7317
  try {
7010
- report = analyzeGating(combinator, accept ? { accept } : void 0);
7318
+ report = analyze(analyzeOpts);
7011
7319
  } catch {
7012
7320
  return void 0;
7013
7321
  }
@@ -7143,6 +7451,7 @@ function publicRuleWrapperSource(rule, fnSource) {
7143
7451
  return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
7144
7452
  }
7145
7453
  function compileRuleMap(ruleMap, opts) {
7454
+ runGatingDiagnosticRules(ruleMap, opts?.gating);
7146
7455
  for (const [, rule] of ruleMap) markUnusedValues(rule);
7147
7456
  const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
7148
7457
  const grammarTrivia = opts?.trivia;
@@ -7261,6 +7570,7 @@ function compileRuleMap(ruleMap, opts) {
7261
7570
  }
7262
7571
  function compileLinkable(ruleMapArg, ns, opts) {
7263
7572
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
7573
+ if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
7264
7574
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
7265
7575
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
7266
7576
  const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
@@ -7317,6 +7627,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
7317
7627
  fnNameToKey.set(fn, key);
7318
7628
  }
7319
7629
  ctx.ruleNames = ruleNames;
7630
+ const externalRefs = /* @__PURE__ */ new Set();
7320
7631
  {
7321
7632
  const scanned = /* @__PURE__ */ new Set();
7322
7633
  const scanExternal = (p) => {
@@ -7332,11 +7643,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
7332
7643
  }
7333
7644
  if (resolved === void 0) {
7334
7645
  const name = p._ruleName;
7335
- if (name && !ctx.namedParsers.has(p)) {
7336
- assertRuleName(name);
7337
- const fn = `_r_${name}`;
7338
- ruleNames.set(p, fn);
7339
- ctx.namedParsers.set(p, fn);
7646
+ if (name) {
7647
+ externalRefs.add(p);
7648
+ if (!ctx.namedParsers.has(p)) {
7649
+ assertRuleName(name);
7650
+ const fn = `_r_${name}`;
7651
+ ruleNames.set(p, fn);
7652
+ ctx.namedParsers.set(p, fn);
7653
+ }
7340
7654
  }
7341
7655
  return;
7342
7656
  }
@@ -7449,12 +7763,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
7449
7763
  needsEmptyTl: !!ctx.needsEmptyTl,
7450
7764
  needsHostReads: !!ctx.needsHostReads,
7451
7765
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
7452
- isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
7766
+ isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
7453
7767
  mfFns: mfSrcs ? [] : ctx.mapFns,
7454
7768
  buildFns: buildSrcs ? [] : ctx.buildFns
7455
7769
  };
7456
7770
  }
7457
- function hasSemanticReduction(roots) {
7771
+ function hasExternalRuleRef(ruleMap) {
7772
+ const seen = /* @__PURE__ */ new Set();
7773
+ const visit = (p) => {
7774
+ if (seen.has(p)) return false;
7775
+ seen.add(p);
7776
+ const def = p._def;
7777
+ if (def.tag === "lazy") {
7778
+ let resolved;
7779
+ try {
7780
+ resolved = def.thunk();
7781
+ } catch {
7782
+ resolved = void 0;
7783
+ }
7784
+ if (resolved === void 0) return p._ruleName !== void 0;
7785
+ return visit(resolved);
7786
+ }
7787
+ return childrenOf(def).some(visit);
7788
+ };
7789
+ return ruleMap.some(([, rule]) => visit(rule));
7790
+ }
7791
+ function hasSemanticReduction(roots, externalRefs) {
7458
7792
  const seen = /* @__PURE__ */ new Set();
7459
7793
  const visit = (parser2) => {
7460
7794
  if (seen.has(parser2)) return false;
@@ -7465,6 +7799,7 @@ function hasSemanticReduction(roots) {
7465
7799
  if (def.tag === "guard" || def.tag === "withCtx") return true;
7466
7800
  if (def.tag === "node" && def.build !== void 0) return true;
7467
7801
  if (def.tag === "lazy") {
7802
+ if (externalRefs?.has(parser2)) return false;
7468
7803
  try {
7469
7804
  return visit(def.thunk());
7470
7805
  } catch {
@@ -7556,6 +7891,7 @@ function childrenOf2(def) {
7556
7891
  case "label":
7557
7892
  case "field":
7558
7893
  case "not":
7894
+ case "peek":
7559
7895
  case "node":
7560
7896
  case "withCtx":
7561
7897
  case "expect":
@@ -7589,6 +7925,13 @@ function lazyTarget(c) {
7589
7925
  }
7590
7926
  var Unserializable = class extends Error {
7591
7927
  };
7928
+ function repeatOpts(min, max, trailing) {
7929
+ const parts = [];
7930
+ if (min !== 0) parts.push(`min: ${min}`);
7931
+ if (max !== void 0) parts.push(`max: ${max}`);
7932
+ if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
7933
+ return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
7934
+ }
7592
7935
  function serializeRuleMap(ruleMap, scanSkip) {
7593
7936
  try {
7594
7937
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -7692,6 +8035,7 @@ function evalRuleMapIR(ir) {
7692
8035
  "optional",
7693
8036
  "sepBy",
7694
8037
  "not",
8038
+ "peek",
7695
8039
  "node",
7696
8040
  "parser",
7697
8041
  "scanTo",
@@ -7724,6 +8068,7 @@ function evalRuleMapIR(ir) {
7724
8068
  optional,
7725
8069
  sepBy,
7726
8070
  not,
8071
+ peek,
7727
8072
  node,
7728
8073
  parser,
7729
8074
  scanTo,
@@ -7898,17 +8243,21 @@ ${outerDecls.map((d) => " " + d).join("\n")}
7898
8243
  return `_gch([${items.join(", ")}])`;
7899
8244
  }
7900
8245
  case "many":
7901
- return `many(${kid(def.parser)})`;
8246
+ return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
8247
+ // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
8248
+ // it through `many` so the bounds survive (oneOrMore defaults min to 1).
7902
8249
  case "oneOrMore":
7903
- return `oneOrMore(${kid(def.parser)})`;
8250
+ return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
7904
8251
  case "optional":
7905
8252
  return `optional(${kid(def.parser)})`;
7906
8253
  case "attempt":
7907
8254
  return `attempt(${kid(def.parser)})`;
7908
8255
  case "sepBy":
7909
- return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
8256
+ return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
7910
8257
  case "not":
7911
8258
  return `not(${kid(def.parser)})`;
8259
+ case "peek":
8260
+ return `peek(${kid(def.parser)})`;
7912
8261
  case "trivia":
7913
8262
  return `trivia(${kid(def.parser)})`;
7914
8263
  case "token":
@@ -8344,15 +8693,12 @@ var SUPPORTED = {
8344
8693
  literal: (...a) => literal(a[0], a[1]),
8345
8694
  regex: (...a) => regex(a[0], a[1]),
8346
8695
  keywords: (...a) => keywords(a[0], a[1]),
8347
- word: (...a) => word(a[0], a[1]),
8696
+ word: (...a) => word(a[0], a[1], a[2]),
8348
8697
  sequence: (...a) => sequence(...a),
8349
8698
  choice: (...a) => choice(...a),
8350
8699
  attempt: (...a) => attempt(a[0]),
8351
- many: (...a) => many(a[0]),
8352
- oneOrMore: (...a) => oneOrMore(a[0]),
8353
8700
  optional: (...a) => optional(a[0]),
8354
8701
  skip: (...a) => skip(a[0], a[1]),
8355
- sepBy: (...a) => sepBy(a[0], a[1]),
8356
8702
  trivia: (...a) => trivia(a[0]),
8357
8703
  label: (...a) => label(a[0], a[1]),
8358
8704
  field: (...a) => field(a[0], a[1]),
@@ -8528,8 +8874,8 @@ function exprToCombi(node2, scope, code, mfs) {
8528
8874
  return null;
8529
8875
  }
8530
8876
  }
8531
- if (callee.name === "sepBy") {
8532
- const [itemArg, sepArg] = node2.arguments;
8877
+ if (callee.name === "sepBy" || callee.name === "oneOrMoreSep") {
8878
+ const [itemArg, sepArg, optsArg] = node2.arguments;
8533
8879
  if (!itemArg || !sepArg || itemArg.type === "SpreadElement" || sepArg.type === "SpreadElement") return null;
8534
8880
  const itemMfs = [];
8535
8881
  const itemCombi = anyValue(itemArg, scope, code, itemMfs);
@@ -8537,25 +8883,42 @@ function exprToCombi(node2, scope, code, mfs) {
8537
8883
  const sepMfs = [];
8538
8884
  const sepCombi = anyValue(sepArg, scope, code, sepMfs);
8539
8885
  if (!isCombinator(sepCombi)) return null;
8886
+ let opts;
8887
+ if (optsArg) {
8888
+ if (optsArg.type === "SpreadElement") return null;
8889
+ const v2 = anyValue(optsArg, scope, code, []);
8890
+ if (!v2 || typeof v2 !== "object") return null;
8891
+ opts = v2;
8892
+ }
8540
8893
  if (mfs) mfs.push(...itemMfs, ...sepMfs, ...itemMfs);
8541
8894
  try {
8542
- return sepBy(itemCombi, sepCombi);
8895
+ return callee.name === "sepBy" ? sepBy(itemCombi, sepCombi, opts) : oneOrMoreSep(itemCombi, sepCombi, opts);
8543
8896
  } catch {
8544
8897
  return null;
8545
8898
  }
8546
8899
  }
8547
- if (callee.name === "oneOrMore") {
8548
- const [itemArg] = node2.arguments;
8900
+ if (callee.name === "oneOrMore" || callee.name === "many") {
8901
+ const [itemArg, optsArg] = node2.arguments;
8549
8902
  if (!itemArg || itemArg.type === "SpreadElement") return null;
8550
8903
  const itemMfs = [];
8551
8904
  const itemCombi = anyValue(itemArg, scope, code, itemMfs);
8552
8905
  if (!isCombinator(itemCombi)) return null;
8553
- if (mfs) mfs.push(...itemMfs, ...itemMfs);
8906
+ let opts;
8907
+ if (optsArg) {
8908
+ if (optsArg.type === "SpreadElement") return null;
8909
+ const v2 = anyValue(optsArg, scope, code, []);
8910
+ if (!v2 || typeof v2 !== "object") return null;
8911
+ opts = v2;
8912
+ }
8913
+ let combi;
8554
8914
  try {
8555
- return oneOrMore(itemCombi);
8915
+ combi = callee.name === "many" ? many(itemCombi, opts) : oneOrMore(itemCombi, opts);
8556
8916
  } catch {
8557
8917
  return null;
8558
8918
  }
8919
+ const min = combi._def.tag === "oneOrMore" ? combi._def.min : 0;
8920
+ if (mfs) for (let i = 0; i <= min; i++) mfs.push(...itemMfs);
8921
+ return combi;
8559
8922
  }
8560
8923
  if (callee.name === "not") {
8561
8924
  const [innerArg] = node2.arguments;
@@ -8568,6 +8931,17 @@ function exprToCombi(node2, scope, code, mfs) {
8568
8931
  return null;
8569
8932
  }
8570
8933
  }
8934
+ if (callee.name === "peek") {
8935
+ const [innerArg] = node2.arguments;
8936
+ if (!innerArg || innerArg.type === "SpreadElement") return null;
8937
+ const inner = anyValue(innerArg, scope, code, mfs);
8938
+ if (!isCombinator(inner)) return null;
8939
+ try {
8940
+ return peek(inner);
8941
+ } catch {
8942
+ return null;
8943
+ }
8944
+ }
8571
8945
  if (callee.name === "balanced") {
8572
8946
  const [openArg, closeArg, optsArg] = node2.arguments;
8573
8947
  if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
@@ -9197,6 +9571,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9197
9571
  const warnings = [];
9198
9572
  beginLoweringCapture();
9199
9573
  let anyUnresolved = false;
9574
+ let keepMacroImport = false;
9200
9575
  let runtimeComposeFallback = false;
9201
9576
  let ruleMapHolderCounter = 0;
9202
9577
  const lineOf = (pos) => {
@@ -9245,11 +9620,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9245
9620
  const evaluated = evaluateRulesFactory(init, label2);
9246
9621
  if (!evaluated) return null;
9247
9622
  const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
9248
- if (!compiled) {
9249
- warn(init.start, `${label2}: rule map couldn't be inlined`);
9250
- return null;
9251
- }
9252
- return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
9623
+ return { replacement: compiled?.replacement ?? null, ruleMap: evaluated.ruleMap, ...compiled?.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
9253
9624
  };
9254
9625
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
9255
9626
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
@@ -9344,6 +9715,17 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9344
9715
  }
9345
9716
  return out;
9346
9717
  };
9718
+ const carriedRuleMaps = (items) => {
9719
+ const out = [];
9720
+ const add = (it) => {
9721
+ if (isIR(it)) out.push(evalRuleMapIR(it.ir));
9722
+ };
9723
+ for (const it of items) {
9724
+ if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
9725
+ else add(it);
9726
+ }
9727
+ return out;
9728
+ };
9347
9729
  const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
9348
9730
  const sources = [];
9349
9731
  const add = (item) => {
@@ -9477,6 +9859,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9477
9859
  }
9478
9860
  carried.push(...r.carried);
9479
9861
  }
9862
+ runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
9480
9863
  const pieces = materializeCarried(carried, composing);
9481
9864
  try {
9482
9865
  return { replacement: emitFusedSource(pieces), carried, ...composing ? { trivia: composing } : {} };
@@ -9525,6 +9908,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9525
9908
  }
9526
9909
  try {
9527
9910
  const localNs = nsFor(`composeLeaf${init.start}`);
9911
+ runFusedGatingDiagnostic(() => [...carriedRuleMaps(carried), [...localRules]]);
9528
9912
  const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
9529
9913
  if (!plainLocalPiece) {
9530
9914
  warn(init.start, "composeLeaf(): local rules could not be statically compiled");
@@ -9613,7 +9997,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9613
9997
  const refEntry = scope.get(varName);
9614
9998
  const refCombi = refEntry?.combi ?? null;
9615
9999
  if (refCombi) {
9616
- const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
10000
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
9617
10001
  if (compiled2.inlineExpression === null) {
9618
10002
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
9619
10003
  continue;
@@ -9628,20 +10012,26 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9628
10012
  localRuleMaps.set(varName, compiledRules.ruleMap);
9629
10013
  if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
9630
10014
  if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
10015
+ const ns = nsFor(varName);
10016
+ const pieces = exportPrefix || compiledRules.replacement === null ? compileLinkable([...compiledRules.ruleMap], ns, {
10017
+ ...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
10018
+ recovery
10019
+ }) : null;
10020
+ if (compiledRules.replacement === null) {
10021
+ if (!pieces || !hasExternalRuleRef([...compiledRules.ruleMap])) {
10022
+ warn(init.start, `${varName}: rule map couldn't be inlined`);
10023
+ continue;
10024
+ }
10025
+ keepMacroImport = true;
10026
+ }
10027
+ const source = compiledRules.replacement ?? code.slice(init.start, init.end);
9631
10028
  let replacement = withCoverageDefinitions(
9632
- compiledRules.replacement,
9633
- compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
10029
+ source,
10030
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
9634
10031
  );
9635
- if (exportPrefix) {
9636
- const ns = nsFor(varName);
9637
- const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
9638
- ...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
9639
- recovery
9640
- });
9641
- if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
9642
- const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
9643
- replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
9644
- }
10032
+ if (exportPrefix && pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
10033
+ const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
10034
+ replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
9645
10035
  }
9646
10036
  replacements.push({ start: init.start, end: init.end, replacement });
9647
10037
  continue;
@@ -9684,7 +10074,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9684
10074
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
9685
10075
  continue;
9686
10076
  }
9687
- const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
10077
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
9688
10078
  if (compiled.inlineExpression === null) {
9689
10079
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
9690
10080
  continue;
@@ -9703,6 +10093,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9703
10093
  }
9704
10094
  const compiledRules = compileRulesFactory(init, "{ \u2026 }");
9705
10095
  if (!compiledRules) continue;
10096
+ if (compiledRules.replacement === null) {
10097
+ warn(init.start, `{ \u2026 }: rule map couldn't be inlined`);
10098
+ continue;
10099
+ }
9706
10100
  const pattern = d.id;
9707
10101
  const bindings = [];
9708
10102
  let allOk = true;
@@ -9744,7 +10138,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9744
10138
  }
9745
10139
  if (replacements.length === 0 && warnings.length === 0) return null;
9746
10140
  for (const imp of macroImports) {
9747
- imp.fullyResolved = !anyUnresolved;
10141
+ imp.fullyResolved = !anyUnresolved && !keepMacroImport;
9748
10142
  }
9749
10143
  const ms = new import_magic_string.default(code);
9750
10144
  if (!anyUnresolved) {