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
@@ -7,6 +7,11 @@ import { ResolverFactory } from "oxc-resolver";
7
7
  import MagicString from "magic-string";
8
8
 
9
9
  // src/combinators/first-set.ts
10
+ function resolveNamedRef(p, resolve2) {
11
+ if (resolve2 === void 0) return void 0;
12
+ const name = p._ruleName;
13
+ return name === void 0 ? void 0 : resolve2(name);
14
+ }
10
15
  function union(a, b) {
11
16
  if (a.kind === "any" || b.kind === "any") return { kind: "any" };
12
17
  if (a.kind === "empty") return b;
@@ -44,10 +49,10 @@ function any() {
44
49
  function empty() {
45
50
  return { kind: "empty" };
46
51
  }
47
- function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
52
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
48
53
  if (seen.has(p)) return true;
49
54
  seen.add(p);
50
- const me = (c) => matchesEmpty(c, seen);
55
+ const me = (c) => matchesEmpty(c, seen, resolve2);
51
56
  const d = p._def;
52
57
  switch (d.tag) {
53
58
  case "literal":
@@ -64,8 +69,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
64
69
  case "many":
65
70
  case "optional":
66
71
  case "not":
72
+ case "peek":
67
73
  return true;
68
74
  // zero repetitions / absent / lookahead
75
+ // Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
76
+ // Any `min >= 1` requires that many ITEMS, so it is nullable only when the
77
+ // item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
78
+ // nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
79
+ // the gating diagnostic for a list that can never match empty.)
80
+ case "sepBy":
81
+ return d.min >= 1 ? me(d.parser) : true;
69
82
  case "oneOrMore":
70
83
  return me(d.parser);
71
84
  case "sequence":
@@ -89,7 +102,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
89
102
  try {
90
103
  return me(d.thunk());
91
104
  } catch {
92
- return true;
105
+ const t = resolveNamedRef(p, resolve2);
106
+ return t ? me(t) : true;
93
107
  }
94
108
  default:
95
109
  return true;
@@ -98,18 +112,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
98
112
  function isZeroWidthAssertion(p) {
99
113
  return p._def.tag === "not";
100
114
  }
115
+ function isPositiveLookahead(p) {
116
+ return p._def.tag === "peek";
117
+ }
118
+ function narrowBy(acc, constraint) {
119
+ if (constraint.kind === "any") return acc;
120
+ if (acc === null) return constraint;
121
+ if (acc.kind === "any") return constraint;
122
+ if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
123
+ const ranges = [];
124
+ for (const a of acc.ranges) for (const b of constraint.ranges) {
125
+ const lo = Math.max(a.lo, b.lo);
126
+ const hi = Math.min(a.hi, b.hi);
127
+ if (lo <= hi) ranges.push({ lo, hi });
128
+ }
129
+ return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
130
+ }
131
+ function applyAssertion(fs2, assertion) {
132
+ if (assertion === null) return fs2;
133
+ if (fs2.kind === "empty") return assertion;
134
+ return narrowBy(fs2, assertion) ?? fs2;
135
+ }
101
136
  function sequenceFirstSet(parsers) {
102
137
  let fs2 = empty();
138
+ let assertion = null;
103
139
  for (const p of parsers) {
140
+ if (isPositiveLookahead(p)) {
141
+ assertion = narrowBy(assertion, p._meta.firstSet);
142
+ continue;
143
+ }
104
144
  if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
105
- if (!matchesEmpty(p)) return fs2;
145
+ if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
106
146
  }
107
- return fs2;
147
+ return applyAssertion(fs2, assertion);
108
148
  }
109
- function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
149
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
110
150
  if (seen.has(p)) return any();
111
151
  seen.add(p);
112
- const fs2 = (c) => firstSetOf(c, seen);
152
+ const fs2 = (c) => firstSetOf(c, seen, resolve2);
153
+ const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
113
154
  const d = p._def;
114
155
  switch (d.tag) {
115
156
  case "literal":
@@ -121,7 +162,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
121
162
  try {
122
163
  return fs2(d.thunk());
123
164
  } catch {
124
- return any();
165
+ const t = resolveNamedRef(p, resolve2);
166
+ return t ? fs2(t) : any();
125
167
  }
126
168
  case "choice": {
127
169
  let out = empty();
@@ -130,11 +172,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
130
172
  }
131
173
  case "sequence": {
132
174
  let out = empty();
175
+ let assertion = null;
133
176
  for (const term of d.parsers) {
177
+ if (isPositiveLookahead(term)) {
178
+ assertion = narrowBy(assertion, fs2(term));
179
+ continue;
180
+ }
134
181
  if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
135
- if (!matchesEmpty(term)) return out;
182
+ if (!empties(term)) return applyAssertion(out, assertion);
136
183
  }
137
- return out;
184
+ return applyAssertion(out, assertion);
185
+ }
186
+ case "peek": {
187
+ const inner = d.parser;
188
+ return empties(inner) ? any() : fs2(inner);
138
189
  }
139
190
  case "oneOrMore":
140
191
  case "many":
@@ -150,6 +201,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
150
201
  return fs2(d.parser);
151
202
  case "sepBy":
152
203
  return fs2(d.parser);
204
+ // both min 0 and min 1 start with the item
153
205
  case "skip":
154
206
  return fs2(d.main);
155
207
  default:
@@ -328,28 +380,28 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
328
380
  else if (len === 1) {
329
381
  b[keySingle] = arr[0];
330
382
  b[keyMulti] = void 0;
331
- } else arr.length = len;
383
+ } else if (arr.length !== len) arr.length = len;
332
384
  return;
333
385
  }
334
386
  if (len === 0) b[keySingle] = void 0;
335
387
  }
336
388
  function rollbackCstCapture(ctx, mark) {
337
- if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
389
+ if (ctx._errors && mark.errors !== void 0 && ctx._errors.length !== mark.errors) ctx._errors.length = mark.errors;
338
390
  const b = ctx._cstBuf;
339
391
  if (b) {
340
392
  rollbackBufList(b, "raw", "rawSingle", mark.raw);
341
393
  rollbackBufList(b, "ch", "single", mark.leaves);
342
394
  if (b.tl) {
343
395
  if (mark.tlog === 0) b.tl = void 0;
344
- else b.tl.length = mark.tlog;
396
+ else if (b.tl.length !== mark.tlog) b.tl.length = mark.tlog;
345
397
  }
346
- if (ctx._fields) ctx._fields.length = mark.fields;
398
+ if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
347
399
  return;
348
400
  }
349
- if (ctx._cstRawChildren) ctx._cstRawChildren.length = mark.raw;
350
- if (ctx._cstTriviaLog) ctx._cstTriviaLog.length = mark.tlog;
351
- if (ctx._cstLeaves) ctx._cstLeaves.length = mark.leaves;
352
- if (ctx._fields) ctx._fields.length = mark.fields;
401
+ if (ctx._cstRawChildren && ctx._cstRawChildren.length !== mark.raw) ctx._cstRawChildren.length = mark.raw;
402
+ if (ctx._cstTriviaLog && ctx._cstTriviaLog.length !== mark.tlog) ctx._cstTriviaLog.length = mark.tlog;
403
+ if (ctx._cstLeaves && ctx._cstLeaves.length !== mark.leaves) ctx._cstLeaves.length = mark.leaves;
404
+ if (ctx._fields && ctx._fields.length !== mark.fields) ctx._fields.length = mark.fields;
353
405
  }
354
406
  function pushCstTriviaEntry(ctx, start, end, kindIndex) {
355
407
  const insertIdx = cstRawLen(ctx);
@@ -871,9 +923,46 @@ function firstSetFromRegex(source) {
871
923
  return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
872
924
  }
873
925
 
926
+ // src/combinators/case-fold.ts
927
+ function canonical(ch2) {
928
+ const u = ch2.toUpperCase();
929
+ if (u.length !== 1) return ch2;
930
+ if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
931
+ return u;
932
+ }
933
+ var foldIndex = null;
934
+ function buildFoldIndex() {
935
+ const byCanonical = /* @__PURE__ */ new Map();
936
+ for (let cp = 0; cp < 65536; cp++) {
937
+ if (cp >= 55296 && cp <= 57343) continue;
938
+ const key = canonical(String.fromCharCode(cp));
939
+ const bucket = byCanonical.get(key);
940
+ if (bucket) bucket.push(cp);
941
+ else byCanonical.set(key, [cp]);
942
+ }
943
+ const index = /* @__PURE__ */ new Map();
944
+ for (const members of byCanonical.values()) {
945
+ if (members.length < 2) continue;
946
+ for (const cp of members) index.set(cp, members);
947
+ }
948
+ return index;
949
+ }
950
+ function caseFoldVariants(cp) {
951
+ if (cp < 128) {
952
+ if (cp >= 65 && cp <= 90) return [cp, cp + 32];
953
+ if (cp >= 97 && cp <= 122) return [cp, cp - 32];
954
+ return [cp];
955
+ }
956
+ if (cp > 65535) return [cp];
957
+ foldIndex ??= buildFoldIndex();
958
+ return foldIndex.get(cp) ?? [cp];
959
+ }
960
+
874
961
  // src/combinators/keywords.ts
875
- function word(str, boundary = "_0-9A-Za-z") {
876
- return keywords([str], { boundary });
962
+ function word(str, boundaryOrOpts, opts) {
963
+ const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
964
+ const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
965
+ return keywords([str], { ...rest, boundary });
877
966
  }
878
967
  function escapeRe(s) {
879
968
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -882,7 +971,7 @@ function keywords(words, opts = {}) {
882
971
  const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
883
972
  const alt = sorted.map(escapeRe).join("|");
884
973
  const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
885
- const flags = opts.caseInsensitive ? "iuy" : "uy";
974
+ const flags = opts.caseInsensitive ? "iy" : "uy";
886
975
  const re = new RegExp(`(?:${alt})${boundary}`, flags);
887
976
  let firstSet2 = empty();
888
977
  for (const w of sorted) {
@@ -890,10 +979,7 @@ function keywords(words, opts = {}) {
890
979
  const cp = w.codePointAt(0);
891
980
  firstSet2 = union(firstSet2, fromChar(cp));
892
981
  if (opts.caseInsensitive) {
893
- const u = String.fromCodePoint(cp).toUpperCase().codePointAt(0);
894
- const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
895
- if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
896
- if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
982
+ for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
897
983
  }
898
984
  }
899
985
  const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
@@ -1004,6 +1090,10 @@ function captureError(ctx, error) {
1004
1090
 
1005
1091
  // src/combinators/expect.ts
1006
1092
  function deriveExpected(c) {
1093
+ return derive(c, /* @__PURE__ */ new Set());
1094
+ }
1095
+ function derive(c, seen) {
1096
+ const deriveExpected2 = (p) => derive(p, seen);
1007
1097
  const def = c._def;
1008
1098
  switch (def.tag) {
1009
1099
  case "literal":
@@ -1015,20 +1105,32 @@ function deriveExpected(c) {
1015
1105
  case "label":
1016
1106
  return [def.label];
1017
1107
  case "choice":
1018
- return def.parsers.flatMap(deriveExpected);
1019
- case "sequence":
1020
- return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1108
+ return def.parsers.flatMap(deriveExpected2);
1109
+ // Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
1110
+ // `optional(…)`/`many(…)` can match nothing, so the term after it is equally
1111
+ // able to fail first — deriving from term 0 alone named a token the parse
1112
+ // never actually required. A leading `not(…)` is zero-width and contributes
1113
+ // nothing (its own expectation is about what must NOT be here), but it is
1114
+ // nullable, so keep scanning past it. Stop at the first term that must match.
1115
+ case "sequence": {
1116
+ const out = [];
1117
+ for (const term of def.parsers) {
1118
+ if (!isZeroWidthAssertion(term)) out.push(...deriveExpected2(term));
1119
+ if (!matchesEmpty(term)) break;
1120
+ }
1121
+ return out;
1122
+ }
1021
1123
  case "attempt":
1022
- return deriveExpected(def.parser);
1124
+ return deriveExpected2(def.parser);
1023
1125
  // Delegating wrappers whose first-token failure is their inner parser's — must
1024
1126
  // pass through so a start-failure (and the first-set-miss fast-path in attempt/
1025
1127
  // node, which reads this) names the real expected token, not the wrapper tag.
1026
1128
  case "field":
1027
1129
  case "withCtx":
1028
1130
  case "recover":
1029
- return deriveExpected(def.parser);
1131
+ return deriveExpected2(def.parser);
1030
1132
  case "skip":
1031
- return deriveExpected(def.main);
1133
+ return deriveExpected2(def.main);
1032
1134
  case "expect":
1033
1135
  return def.expected;
1034
1136
  case "node":
@@ -1041,13 +1143,18 @@ function deriveExpected(c) {
1041
1143
  case "oneOrMore":
1042
1144
  case "transform":
1043
1145
  case "not":
1044
- return deriveExpected(def.parser);
1146
+ case "peek":
1147
+ return deriveExpected2(def.parser);
1045
1148
  case "lazy": {
1046
1149
  const name = c._ruleName;
1150
+ if (seen.has(c)) return name ? [name] : [];
1151
+ seen.add(c);
1047
1152
  try {
1048
- return deriveExpected(def.thunk());
1153
+ return deriveExpected2(def.thunk());
1049
1154
  } catch {
1050
1155
  return name ? [name] : [];
1156
+ } finally {
1157
+ seen.delete(c);
1051
1158
  }
1052
1159
  }
1053
1160
  default:
@@ -1175,14 +1282,14 @@ function choice(...args) {
1175
1282
  const result = parsers[i].parse(input, pos, ctx);
1176
1283
  if (!result.ok) {
1177
1284
  rollbackCstCapture(ctx, mark);
1178
- if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
1285
+ if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1179
1286
  expected.push(...result.expected);
1180
1287
  continue;
1181
1288
  }
1182
1289
  const checks = autoNot[i];
1183
1290
  if (checks && autoNotFires(input, result.span.end, checks)) {
1184
1291
  rollbackCstCapture(ctx, mark);
1185
- if (logLen !== void 0 && ctx._triviaLog) ctx._triviaLog.length = logLen;
1292
+ if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
1186
1293
  continue;
1187
1294
  }
1188
1295
  return result;
@@ -1509,7 +1616,14 @@ function saveTriviaMark(ctx) {
1509
1616
  }
1510
1617
  function rollbackTrivia(ctx, mark) {
1511
1618
  rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
1512
- if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
1619
+ if (ctx._triviaLog && ctx._triviaLog.length !== mark.log) ctx._triviaLog.length = mark.log;
1620
+ }
1621
+ function saveLookaheadMark(ctx) {
1622
+ return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
1623
+ }
1624
+ function rollbackLookahead(ctx, mark) {
1625
+ rollbackTrivia(ctx, mark);
1626
+ if (ctx._probe) ctx._probe.best = mark.probeBest;
1513
1627
  }
1514
1628
  function scanWithLabels(input, cur, ctx) {
1515
1629
  const triviaP = ctx.trivia;
@@ -1870,7 +1984,7 @@ function attempt(parser2) {
1870
1984
  const result = parser2.parse(input, pos, ctx);
1871
1985
  if (result.ok) return result;
1872
1986
  rollbackCstCapture(ctx, mark);
1873
- if (log !== void 0 && ctx._triviaLog) ctx._triviaLog.length = log;
1987
+ if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
1874
1988
  return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
1875
1989
  }
1876
1990
  };
@@ -1908,13 +2022,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
1908
2022
  }
1909
2023
  return { value: result.value, end: result.span.end };
1910
2024
  }
1911
- function many(combinator) {
2025
+ function resolveBounds(what, opts) {
2026
+ const min = opts.min ?? 0;
2027
+ const max = opts.max ?? Infinity;
2028
+ const bad = (msg) => {
2029
+ throw new RangeError(`parseman: ${what} ${msg}`);
2030
+ };
2031
+ if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
2032
+ if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
2033
+ if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
2034
+ return { min, max };
2035
+ }
2036
+ function many(combinator, opts = {}) {
2037
+ const { min, max } = resolveBounds("many()", opts);
2038
+ if (min >= 1) return atLeast(combinator, min, max);
1912
2039
  const meta = {
1913
2040
  firstSet: combinator._meta.firstSet,
1914
2041
  canMatchNewline: combinator._meta.canMatchNewline,
1915
2042
  isTrivia: false
1916
2043
  };
1917
- const def = { tag: "many", parser: combinator, min: 0 };
2044
+ const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
1918
2045
  let expected;
1919
2046
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1920
2047
  return {
@@ -1924,7 +2051,9 @@ function many(combinator) {
1924
2051
  parse(input, pos, ctx) {
1925
2052
  const values = def.valueUnused ? void 0 : [];
1926
2053
  let cur = pos;
2054
+ let count = 0;
1927
2055
  while (cur < input.length) {
2056
+ if (count >= max) break;
1928
2057
  const item = repItem(combinator, input, cur, ctx, guardable);
1929
2058
  if (item === "stop") break;
1930
2059
  if ("fail" in item) {
@@ -1935,23 +2064,28 @@ function many(combinator) {
1935
2064
  const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1936
2065
  if (values !== void 0) values.push(error);
1937
2066
  captureError(ctx, error);
2067
+ count++;
1938
2068
  cur = end;
1939
2069
  continue;
1940
2070
  }
1941
2071
  if (values !== void 0) values.push(item.value);
2072
+ count++;
1942
2073
  cur = item.end;
1943
2074
  }
1944
2075
  return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1945
2076
  }
1946
2077
  };
1947
2078
  }
1948
- function oneOrMore(combinator) {
2079
+ function oneOrMore(combinator, opts = {}) {
2080
+ return many(combinator, { ...opts, min: opts.min ?? 1 });
2081
+ }
2082
+ function atLeast(combinator, min, max) {
1949
2083
  const meta = {
1950
2084
  firstSet: combinator._meta.firstSet,
1951
2085
  canMatchNewline: combinator._meta.canMatchNewline,
1952
2086
  isTrivia: false
1953
2087
  };
1954
- const def = { tag: "oneOrMore", parser: combinator, min: 1 };
2088
+ const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
1955
2089
  let expected;
1956
2090
  const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1957
2091
  return {
@@ -1963,7 +2097,19 @@ function oneOrMore(combinator) {
1963
2097
  if (!first.ok) return first;
1964
2098
  const values = def.valueUnused ? void 0 : [first.value];
1965
2099
  let cur = first.span.end;
2100
+ let count = 1;
2101
+ while (count < min) {
2102
+ const item = repItem(combinator, input, cur, ctx, guardable);
2103
+ if (item === "stop" || "fail" in item) {
2104
+ expected ??= deriveExpected(combinator);
2105
+ return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
2106
+ }
2107
+ if (values !== void 0) values.push(item.value);
2108
+ cur = item.end;
2109
+ count++;
2110
+ }
1966
2111
  while (cur < input.length) {
2112
+ if (count >= max) break;
1967
2113
  const item = repItem(combinator, input, cur, ctx, guardable);
1968
2114
  if (item === "stop") break;
1969
2115
  if ("fail" in item) {
@@ -1974,10 +2120,12 @@ function oneOrMore(combinator) {
1974
2120
  const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
1975
2121
  if (values !== void 0) values.push(error);
1976
2122
  captureError(ctx, error);
2123
+ count++;
1977
2124
  cur = end;
1978
2125
  continue;
1979
2126
  }
1980
2127
  if (values !== void 0) values.push(item.value);
2128
+ count++;
1981
2129
  cur = item.end;
1982
2130
  }
1983
2131
  return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
@@ -2007,17 +2155,33 @@ function optional(combinator) {
2007
2155
  }
2008
2156
  };
2009
2157
  }
2010
- function sepBy(combinator, separator) {
2158
+ function oneOrMoreSep(combinator, separator, opts = {}) {
2159
+ return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
2160
+ }
2161
+ function sepBy(combinator, separator, opts = {}) {
2162
+ const { min, max } = resolveBounds("sepBy()", opts);
2163
+ const trailing = opts.trailing ?? "forbid";
2011
2164
  const meta = {
2012
2165
  firstSet: combinator._meta.firstSet,
2013
2166
  canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
2014
2167
  isTrivia: false
2015
2168
  };
2016
2169
  let expected;
2170
+ const failAt2 = (at) => {
2171
+ expected ??= deriveExpected(combinator);
2172
+ return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
2173
+ };
2017
2174
  return {
2018
2175
  _tag: "sepBy",
2019
2176
  _meta: meta,
2020
- _def: { tag: "sepBy", parser: combinator, separator },
2177
+ _def: {
2178
+ tag: "sepBy",
2179
+ parser: combinator,
2180
+ separator,
2181
+ min,
2182
+ ...max === Infinity ? {} : { max },
2183
+ ...trailing === "forbid" ? {} : { trailing }
2184
+ },
2021
2185
  parse(input, pos, ctx) {
2022
2186
  const first = combinator.parse(input, pos, ctx);
2023
2187
  const values = [];
@@ -2028,6 +2192,7 @@ function sepBy(combinator, separator) {
2028
2192
  } else {
2029
2193
  const term = ctx._tolerant ? ctx._sync : void 0;
2030
2194
  if (term === void 0 || matchesAt(term, input, pos, ctx)) {
2195
+ if (min >= 1) return failAt2(pos);
2031
2196
  return { ok: true, value: [], span: { start: pos, end: pos } };
2032
2197
  }
2033
2198
  expected ??= deriveExpected(combinator);
@@ -2037,6 +2202,7 @@ function sepBy(combinator, separator) {
2037
2202
  cur = rec.end;
2038
2203
  }
2039
2204
  while (cur < input.length) {
2205
+ if (values.length >= max) break;
2040
2206
  const loopMark = saveTriviaMark(ctx);
2041
2207
  let sepPos = cur;
2042
2208
  if (ctx.trivia) {
@@ -2053,6 +2219,7 @@ function sepBy(combinator, separator) {
2053
2219
  rollbackTrivia(ctx, loopMark);
2054
2220
  break;
2055
2221
  }
2222
+ const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
2056
2223
  let nextPos = sep2.span.end;
2057
2224
  if (ctx.trivia) {
2058
2225
  if (needsDeferredTriviaCommit(ctx)) {
@@ -2074,12 +2241,18 @@ function sepBy(combinator, separator) {
2074
2241
  cur = rec.end;
2075
2242
  continue;
2076
2243
  }
2244
+ if (sepMark !== void 0) {
2245
+ rollbackTrivia(ctx, sepMark);
2246
+ cur = sep2.span.end;
2247
+ break;
2248
+ }
2077
2249
  rollbackTrivia(ctx, loopMark);
2078
2250
  break;
2079
2251
  }
2080
2252
  values.push(next.value);
2081
2253
  cur = next.span.end;
2082
2254
  }
2255
+ if (values.length < min) return failAt2(cur);
2083
2256
  return { ok: true, value: values, span: { start: pos, end: cur } };
2084
2257
  }
2085
2258
  };
@@ -2132,6 +2305,7 @@ function markUnusedValues(root) {
2132
2305
  visit(def.separator, false);
2133
2306
  return;
2134
2307
  case "not":
2308
+ case "peek":
2135
2309
  visit(def.parser, false);
2136
2310
  return;
2137
2311
  case "grammar":
@@ -2250,9 +2424,9 @@ function not(combinator) {
2250
2424
  _meta: meta,
2251
2425
  _def: { tag: "not", parser: combinator },
2252
2426
  parse(input, pos, ctx) {
2253
- const mark = saveCstMark(ctx);
2427
+ const mark = saveLookaheadMark(ctx);
2254
2428
  const result = combinator.parse(input, pos, ctx);
2255
- rollbackCstCapture(ctx, mark);
2429
+ rollbackLookahead(ctx, mark);
2256
2430
  if (result.ok) {
2257
2431
  return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
2258
2432
  }
@@ -2261,6 +2435,28 @@ function not(combinator) {
2261
2435
  };
2262
2436
  }
2263
2437
 
2438
+ // src/combinators/peek.ts
2439
+ function peek(combinator) {
2440
+ const meta = {
2441
+ // A nullable body imposes no first-char constraint — `any()`, not its set.
2442
+ firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
2443
+ canMatchNewline: false,
2444
+ isTrivia: false
2445
+ };
2446
+ return {
2447
+ _tag: "peek",
2448
+ _meta: meta,
2449
+ _def: { tag: "peek", parser: combinator },
2450
+ parse(input, pos, ctx) {
2451
+ const mark = saveLookaheadMark(ctx);
2452
+ const result = combinator.parse(input, pos, ctx);
2453
+ rollbackLookahead(ctx, mark);
2454
+ if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
2455
+ return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
2456
+ }
2457
+ };
2458
+ }
2459
+
2264
2460
  // src/compiler/build-arity.ts
2265
2461
  var PARAM_LIST_RE = (
2266
2462
  // (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
@@ -2359,6 +2555,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
2359
2555
  case "expect":
2360
2556
  case "withCtx":
2361
2557
  case "not":
2558
+ case "peek":
2362
2559
  return parserHasOwnFields(d.parser, seen);
2363
2560
  default:
2364
2561
  return false;
@@ -2398,6 +2595,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
2398
2595
  case "expect":
2399
2596
  case "withCtx":
2400
2597
  case "not":
2598
+ case "peek":
2401
2599
  case "field":
2402
2600
  return parserHasTriviaSite(d.parser, seen);
2403
2601
  case "choice":
@@ -2447,6 +2645,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
2447
2645
  case "expect":
2448
2646
  case "withCtx":
2449
2647
  case "not":
2648
+ case "peek":
2450
2649
  return parserEnablesTriviaCapture(d.parser, seen);
2451
2650
  // leaf() clears the inner CST/trivia collector and publishes only its own
2452
2651
  // terminal result. An inner parser() capture cannot reach this node.
@@ -2795,7 +2994,7 @@ function leaf(root, fn) {
2795
2994
  }
2796
2995
 
2797
2996
  // src/version.ts
2798
- var PARSEMAN_VERSION = "0.33.0";
2997
+ var PARSEMAN_VERSION = "0.35.0";
2799
2998
 
2800
2999
  // src/analysis/gating.ts
2801
3000
  var isAny = (fs2) => fs2.kind === "any";
@@ -2835,14 +3034,15 @@ function firstSetToString(fs2) {
2835
3034
  var ruleNameOf = (p) => p._ruleName;
2836
3035
  var SUGGESTIONS = {
2837
3036
  "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.",
2838
- "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.",
2839
- "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.",
3037
+ "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.",
3038
+ "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.",
2840
3039
  "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.",
2841
3040
  "opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
2842
3041
  "ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
2843
3042
  };
2844
- function classifyBroadArm(arm) {
3043
+ function classifyBroadArm(arm, resolve2) {
2845
3044
  const seen = /* @__PURE__ */ new Set();
3045
+ const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
2846
3046
  const walk2 = (p) => {
2847
3047
  if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
2848
3048
  seen.add(p);
@@ -2854,6 +3054,10 @@ function classifyBroadArm(arm) {
2854
3054
  return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
2855
3055
  case "not":
2856
3056
  return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
3057
+ // peek() CARRIES its body's first-set, so it is only broad when the body is
3058
+ // (or the body is nullable) — walk in for the real cause.
3059
+ case "peek":
3060
+ return walk2(d.parser);
2857
3061
  case "scanTo":
2858
3062
  return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
2859
3063
  // `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
@@ -2870,18 +3074,23 @@ function classifyBroadArm(arm) {
2870
3074
  return walk2(d.parser);
2871
3075
  case "lazy": {
2872
3076
  const name = p._ruleName;
3077
+ let target;
2873
3078
  try {
2874
- const inner = walk2(d.thunk());
2875
- if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
2876
- return inner;
3079
+ target = d.thunk();
2877
3080
  } catch {
2878
- return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}` };
3081
+ const bound = name !== void 0 ? resolve2?.(name) : void 0;
3082
+ if (bound === void 0)
3083
+ return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
3084
+ target = bound;
2879
3085
  }
3086
+ const inner = walk2(target);
3087
+ if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
3088
+ return inner;
2880
3089
  }
2881
3090
  case "optional":
2882
3091
  case "many": {
2883
3092
  const inner = walk2(d.parser);
2884
- return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
3093
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
2885
3094
  }
2886
3095
  case "oneOrMore":
2887
3096
  case "transform":
@@ -2903,13 +3112,13 @@ function classifyBroadArm(arm) {
2903
3112
  sawNullablePrefix = true;
2904
3113
  continue;
2905
3114
  }
2906
- if (isAny(firstSetOf(t))) {
3115
+ if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
2907
3116
  const inner = walk2(t);
2908
3117
  if (sawNullablePrefix)
2909
- return { cause: "nullable-prefix", detail: inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term" };
3118
+ return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
2910
3119
  return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
2911
3120
  }
2912
- if (matchesEmpty(t)) {
3121
+ if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
2913
3122
  sawNullablePrefix = true;
2914
3123
  continue;
2915
3124
  }
@@ -2920,7 +3129,7 @@ function classifyBroadArm(arm) {
2920
3129
  case "choice": {
2921
3130
  for (const a of d.parsers) {
2922
3131
  const r = walk2(a);
2923
- if (r) return { cause: r.cause, detail: `choice arm \u2192 ${r.detail}` };
3132
+ if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
2924
3133
  }
2925
3134
  return null;
2926
3135
  }
@@ -2957,7 +3166,7 @@ function detectAntiPatterns(rule, arms) {
2957
3166
  kind: "double-not",
2958
3167
  rule,
2959
3168
  armIndex: i,
2960
- 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."
3169
+ 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."
2961
3170
  });
2962
3171
  } else {
2963
3172
  out.push({
@@ -2980,6 +3189,9 @@ function detectAntiPatterns(rule, arms) {
2980
3189
  return out;
2981
3190
  }
2982
3191
  function analyzeGating(entry, opts) {
3192
+ return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
3193
+ }
3194
+ function analyzeGatingRules(ruleMap, opts) {
2983
3195
  const raw = [];
2984
3196
  const antiPatterns = [];
2985
3197
  const visited = /* @__PURE__ */ new Set();
@@ -2989,7 +3201,7 @@ function analyzeGating(entry, opts) {
2989
3201
  const d = p._def;
2990
3202
  const rule = ruleNameOf(p) ?? enclosingRule;
2991
3203
  if (d.tag === "choice") {
2992
- raw.push({ g: analyzeChoice(p, d, rule), rule });
3204
+ raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
2993
3205
  antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
2994
3206
  }
2995
3207
  const rec = d;
@@ -3006,7 +3218,7 @@ function analyzeGating(entry, opts) {
3006
3218
  }
3007
3219
  for (const k of kids) visit(k, rule);
3008
3220
  };
3009
- visit(entry, ruleNameOf(entry) ?? "<entry>");
3221
+ for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
3010
3222
  const perRule = /* @__PURE__ */ new Map();
3011
3223
  for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
3012
3224
  const seenInRule = /* @__PURE__ */ new Map();
@@ -3022,18 +3234,19 @@ function analyzeGating(entry, opts) {
3022
3234
  });
3023
3235
  const gated = choices.filter((c) => c.gates === "yes").length;
3024
3236
  const recoverable = choices.filter((c) => c.gates === "recoverable").length;
3025
- const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
3237
+ const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
3026
3238
  const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
3239
+ const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
3027
3240
  const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
3028
- return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
3241
+ return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
3029
3242
  }
3030
- function analyzeChoice(p, d, rule) {
3243
+ function analyzeChoice(p, d, rule, resolve2) {
3031
3244
  const arms = d.parsers;
3032
3245
  const shallow = arms.map((a) => a._meta.firstSet);
3033
- const deep = arms.map((a) => firstSetOf(a));
3246
+ const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
3034
3247
  const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
3035
- const { cause, detail } = classifyBroadArm(arms[index]);
3036
- return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
3248
+ const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
3249
+ return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
3037
3250
  });
3038
3251
  const overlaps = [];
3039
3252
  for (let i = 0; i < deep.length; i++)
@@ -3049,10 +3262,12 @@ function analyzeChoice(p, d, rule) {
3049
3262
  if (d.disjoint) gates = "yes";
3050
3263
  else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
3051
3264
  else gates = "no";
3265
+ const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
3052
3266
  return {
3053
3267
  rule,
3054
3268
  strategy: d.strategy?.tag ?? "firstMatch",
3055
3269
  gates,
3270
+ deferred,
3056
3271
  combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
3057
3272
  anyArms,
3058
3273
  overlaps
@@ -3219,6 +3434,7 @@ function children(def, winners) {
3219
3434
  case "field":
3220
3435
  case "grammar":
3221
3436
  case "not":
3437
+ case "peek":
3222
3438
  case "node":
3223
3439
  case "guard":
3224
3440
  case "withCtx":
@@ -4977,9 +5193,9 @@ function planDisjointDispatch(parsers) {
4977
5193
  return total >= SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
4978
5194
  }
4979
5195
  function captureRestoreBody(mL, mR, mTl, mLg, mF = null) {
4980
- const fieldRestore = mF ? `; if (_ctx._fields) _ctx._fields.length = ${mF}` : "";
4981
- const base = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${mL}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${mR}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${mTl}${fieldRestore}`;
4982
- return mLg ? `${base}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${mLg}` : base;
5196
+ const fieldRestore = mF ? `; if (_ctx._fields && _ctx._fields.length !== ${mF}) _ctx._fields.length = ${mF}` : "";
5197
+ 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}`;
5198
+ return mLg ? `${base}; if (_ctx._triviaLog && _ctx._triviaLog.length !== ${mLg}) _ctx._triviaLog.length = ${mLg}` : base;
4983
5199
  }
4984
5200
  function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4985
5201
  if (seen.has(p)) return false;
@@ -4992,6 +5208,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
4992
5208
  case "keywords":
4993
5209
  case "guard":
4994
5210
  case "not":
5211
+ // peek(): emitted under a non-capturing probe ctx and zero-width on both
5212
+ // outcomes, so it can never leave a partial capture behind.
5213
+ case "peek":
4995
5214
  case "trivia":
4996
5215
  case "token":
4997
5216
  case "leaf":
@@ -5063,6 +5282,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
5063
5282
  case "leaf":
5064
5283
  return true;
5065
5284
  case "not":
5285
+ case "peek":
5066
5286
  case "guard":
5067
5287
  case "trivia":
5068
5288
  case "unknown":
@@ -5189,6 +5409,7 @@ function emitKeywordsFast(def, ctx, pos) {
5189
5409
  if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
5190
5410
  if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
5191
5411
  if (def.caseInsensitive && def.boundary) return null;
5412
+ if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
5192
5413
  let boundary = null;
5193
5414
  if (def.boundary) {
5194
5415
  let body = def.boundary;
@@ -5236,7 +5457,7 @@ function emitKeywords(def, ctx, pos) {
5236
5457
  if (fast) return fast;
5237
5458
  const alt = def.words.map(escapeKeywordRe).join("|");
5238
5459
  const boundary = def.boundary ? `(?![${def.boundary}])` : "";
5239
- const flags = def.caseInsensitive ? "iuy" : "uy";
5460
+ const flags = def.caseInsensitive ? "iy" : "uy";
5240
5461
  const source = `(?:${alt})${boundary}`;
5241
5462
  const key = `${source}/${flags}`;
5242
5463
  let rName = ctx.regexMap.get(key);
@@ -5336,7 +5557,7 @@ function emitSeqValues(def, ctx, pos) {
5336
5557
  const endAfterV = v(ctx, "_sea");
5337
5558
  stmts.push(
5338
5559
  `${ind(ctx)}const ${endAfterV} = ${r2.endVar}`,
5339
- `${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}; }`
5560
+ `${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}; }`
5340
5561
  );
5341
5562
  valueVars.push(r2.valueVar);
5342
5563
  continue;
@@ -5382,6 +5603,7 @@ function failsAtStart(p) {
5382
5603
  case "keywords":
5383
5604
  case "guard":
5384
5605
  case "not":
5606
+ case "peek":
5385
5607
  return true;
5386
5608
  case "transform":
5387
5609
  case "label":
@@ -5795,14 +6017,17 @@ function emitMany(def, ctx, pos) {
5795
6017
  ];
5796
6018
  const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
5797
6019
  if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
5798
- if (def.min === 1) {
6020
+ for (let i = 0; i < def.min; i++) {
5799
6021
  const firstR = emit(def.parser, ctx, curV);
5800
6022
  stmts.push(...firstR.stmts);
5801
6023
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
5802
6024
  stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
5803
6025
  }
6026
+ const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
6027
+ if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
5804
6028
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
5805
6029
  ctx.indent++;
6030
+ if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
5806
6031
  let itemPos = curV;
5807
6032
  let rollback = "";
5808
6033
  if (ctx.activeTrivia) {
@@ -5819,7 +6044,7 @@ function emitMany(def, ctx, pos) {
5819
6044
  `${ind(ctx)}const ${npV} = ${capFn}(input, ${curV}, _ctx, 1)`
5820
6045
  );
5821
6046
  itemPos = npV;
5822
- rollback = `if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
6047
+ 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}; `;
5823
6048
  } else {
5824
6049
  const trivFn = ensureTriviaFn(ctx);
5825
6050
  const npV = v(ctx, "_np");
@@ -5843,7 +6068,7 @@ function emitMany(def, ctx, pos) {
5843
6068
  `${ind(ctx)}if (!${iterOk}) {`,
5844
6069
  `${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
5845
6070
  `${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
5846
- ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
6071
+ ...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
5847
6072
  `${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
5848
6073
  `${ind(ctx)} ${curV} = ${rrV}.end`,
5849
6074
  `${ind(ctx)} continue`,
@@ -5856,6 +6081,7 @@ function emitMany(def, ctx, pos) {
5856
6081
  stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
5857
6082
  }
5858
6083
  if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
6084
+ else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
5859
6085
  stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
5860
6086
  ctx.indent--;
5861
6087
  stmts.push(`${ind(ctx)}}`);
@@ -5881,7 +6107,7 @@ function emitAttempt(p, def, ctx, pos) {
5881
6107
  const log = v(ctx, "_atg");
5882
6108
  const fields = v(ctx, "_atf");
5883
6109
  const errors = v(ctx, "_ate");
5884
- 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}`;
6110
+ 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}`;
5885
6111
  const traceId = ctx.coverage?.plan.attempts.get(p);
5886
6112
  const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5887
6113
  const preGuard = [];
@@ -5920,8 +6146,8 @@ function emitSepBy(_p, def, ctx, pos) {
5920
6146
  ...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
5921
6147
  ...firstStmts
5922
6148
  ];
5923
- const failItem = (nextOkVar, itemPosVar, rb, originalFail) => {
5924
- if (!rec) return [originalFail];
6149
+ const failItem = (nextOkVar, itemPosVar, breakStmt) => {
6150
+ if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
5925
6151
  const rr = v(ctx, "_rr");
5926
6152
  return [
5927
6153
  `${ind(ctx)}if (!${nextOkVar}) {`,
@@ -5929,10 +6155,24 @@ function emitSepBy(_p, def, ctx, pos) {
5929
6155
  `${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
5930
6156
  `${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
5931
6157
  `${ind(ctx)} }`,
5932
- `${ind(ctx)} ${rb}break`,
6158
+ `${ind(ctx)} ${breakStmt}`,
5933
6159
  `${ind(ctx)}}`
5934
6160
  ];
5935
6161
  };
6162
+ const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
6163
+ const postSepMarks = () => {
6164
+ if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
6165
+ const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
6166
+ return {
6167
+ decl: [
6168
+ `${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
6169
+ `${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
6170
+ `${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
6171
+ `${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
6172
+ ],
6173
+ 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}; `
6174
+ };
6175
+ };
5936
6176
  if (rec) {
5937
6177
  const rr0 = v(ctx, "_rr0");
5938
6178
  const didV = v(ctx, "_did");
@@ -5958,6 +6198,7 @@ function emitSepBy(_p, def, ctx, pos) {
5958
6198
  );
5959
6199
  ctx.indent++;
5960
6200
  }
6201
+ if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
5961
6202
  let sepAtPos = curV;
5962
6203
  if (ctx.activeTrivia) {
5963
6204
  if (ctx.capturing) {
@@ -5975,15 +6216,17 @@ function emitSepBy(_p, def, ctx, pos) {
5975
6216
  `${ind(ctx)}const ${spV} = ${capFn}(input, ${curV}, _ctx, 1)`
5976
6217
  );
5977
6218
  sepAtPos = spV;
5978
- 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}; `;
6219
+ 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}; `;
5979
6220
  const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
5980
6221
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
6222
+ const post = postSepMarks();
6223
+ stmts.push(...post.decl);
5981
6224
  const npV = v(ctx, "_np");
5982
6225
  stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
5983
6226
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
5984
6227
  stmts.push(
5985
6228
  ...nextStmts,
5986
- ...failItem(nextOk, npV, rollbackToSep, `${ind(ctx)}if (!${nextOk}) { ${rollbackToSep}break }`),
6229
+ ...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
5987
6230
  `${ind(ctx)}${arrV}.push(${nextVal})`,
5988
6231
  `${ind(ctx)}${curV} = ${nextEnd}`
5989
6232
  );
@@ -5999,7 +6242,7 @@ function emitSepBy(_p, def, ctx, pos) {
5999
6242
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
6000
6243
  stmts.push(
6001
6244
  ...nextStmts,
6002
- ...failItem(nextOk, npV, "", `${ind(ctx)}if (!${nextOk}) break`),
6245
+ ...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
6003
6246
  `${ind(ctx)}${arrV}.push(${nextVal})`,
6004
6247
  `${ind(ctx)}${curV} = ${nextEnd}`
6005
6248
  );
@@ -6015,11 +6258,13 @@ function emitSepBy(_p, def, ctx, pos) {
6015
6258
  }
6016
6259
  const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
6017
6260
  stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
6018
- const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
6261
+ 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}; ` : "";
6262
+ const post = postSepMarks();
6263
+ stmts.push(...post.decl);
6019
6264
  const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
6020
6265
  stmts.push(
6021
6266
  ...nextStmts,
6022
- ...failItem(nextOk, sepEnd, nextRb, `${ind(ctx)}if (!${nextOk}) { ${nextRb}break }`),
6267
+ ...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
6023
6268
  `${ind(ctx)}${arrV}.push(${nextVal})`,
6024
6269
  `${ind(ctx)}${curV} = ${nextEnd}`
6025
6270
  );
@@ -6028,6 +6273,9 @@ function emitSepBy(_p, def, ctx, pos) {
6028
6273
  stmts.push(`${ind(ctx)}}`);
6029
6274
  ctx.indent--;
6030
6275
  stmts.push(`${ind(ctx)}}`);
6276
+ if (def.min >= 1) {
6277
+ stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
6278
+ }
6031
6279
  return { stmts, valueVar: arrV, endVar: curV };
6032
6280
  }
6033
6281
  function emitScanTo(def, ctx, pos) {
@@ -6079,17 +6327,44 @@ function emitScanTo(def, ctx, pos) {
6079
6327
  return { stmts, valueVar: valV, endVar: curV };
6080
6328
  }
6081
6329
  function emitNot(def, ctx, pos) {
6330
+ const sinksLive = !!ctx.capturing || !!ctx.recovery;
6331
+ const leaves = sinksLive ? v(ctx, "_ntl") : null;
6332
+ const raw = sinksLive ? v(ctx, "_ntr") : null;
6333
+ const tl = sinksLive ? v(ctx, "_ntt") : null;
6334
+ const log = sinksLive ? v(ctx, "_ntg") : null;
6335
+ const fields = sinksLive ? v(ctx, "_ntf") : null;
6336
+ const errors = sinksLive ? v(ctx, "_nte") : null;
6082
6337
  const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
6083
6338
  const label2 = JSON.stringify(`not(${def.parser._tag})`);
6084
6339
  return {
6085
6340
  stmts: [
6341
+ ...sinksLive ? [
6342
+ `${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`
6343
+ ] : [],
6086
6344
  ...stmts,
6345
+ ...sinksLive ? [
6346
+ `${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}`
6347
+ ] : [],
6087
6348
  ...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
6088
6349
  ],
6089
6350
  valueVar: "null",
6090
6351
  endVar: pos
6091
6352
  };
6092
6353
  }
6354
+ function emitPeek(def, ctx, pos) {
6355
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
6356
+ const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
6357
+ ctx.vars = probeCtx.vars;
6358
+ const label2 = JSON.stringify(`peek(${def.parser._tag})`);
6359
+ return {
6360
+ stmts: [
6361
+ ...stmts,
6362
+ ...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
6363
+ ],
6364
+ valueVar: "null",
6365
+ endVar: pos
6366
+ };
6367
+ }
6093
6368
  function escapeTokenLiteral(value) {
6094
6369
  return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
6095
6370
  }
@@ -6377,9 +6652,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
6377
6652
  return { stmts, valueVar: vv, endVar: ev };
6378
6653
  }
6379
6654
  function emitLazy(p, def, ctx, pos) {
6380
- const canonical = ctx.ruleNames?.get(p);
6655
+ const canonical2 = ctx.ruleNames?.get(p);
6381
6656
  const usage = ctx.lazyUsage;
6382
- if (!canonical && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
6657
+ if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
6383
6658
  let resolved;
6384
6659
  try {
6385
6660
  resolved = def.thunk();
@@ -6389,7 +6664,7 @@ function emitLazy(p, def, ctx, pos) {
6389
6664
  return emit(resolved, ctx, pos);
6390
6665
  }
6391
6666
  if (!ctx.namedParsers.has(p)) {
6392
- const fnName2 = canonical ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
6667
+ const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
6393
6668
  ctx.namedParsers.set(p, fnName2);
6394
6669
  let resolved;
6395
6670
  try {
@@ -6672,6 +6947,8 @@ function emitDispatch(p, ctx, pos) {
6672
6947
  }
6673
6948
  case "not":
6674
6949
  return emitNot(def, ctx, pos);
6950
+ case "peek":
6951
+ return emitPeek(def, ctx, pos);
6675
6952
  case "node":
6676
6953
  return emitNode(def, ctx, pos);
6677
6954
  case "scanTo":
@@ -6760,6 +7037,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
6760
7037
  case "many":
6761
7038
  case "oneOrMore":
6762
7039
  case "not":
7040
+ case "peek":
6763
7041
  case "transform":
6764
7042
  return hasNodeDef(d.parser, seen);
6765
7043
  case "skip":
@@ -6811,17 +7089,18 @@ function childrenOf(def) {
6811
7089
  case "field":
6812
7090
  case "grammar":
6813
7091
  case "not":
7092
+ case "peek":
6814
7093
  case "node":
6815
7094
  case "withCtx":
6816
7095
  case "expect":
6817
7096
  return [def.parser];
6818
- // emitMany's oneOrMore branch and emitSepBy both codegen `def.parser` TWICE
6819
- // (a mandatory first match, then again inside the repeat loop) — two real
6820
- // emit() call sites, so the usage analysis must see two edges here or it
7097
+ // emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
7098
+ // once (`min` mandatory matches, then again inside the repeat loop) — each is a
7099
+ // real emit() call site, so the usage analysis must see one edge per site or it
6821
7100
  // undercounts a single-use `parser` ref as inline-safe when it's actually
6822
- // referenced from two positions within this one compiled function.
7101
+ // referenced from several positions within this one compiled function.
6823
7102
  case "oneOrMore":
6824
- return [def.parser, def.parser];
7103
+ return Array.from({ length: def.min + 1 }, () => def.parser);
6825
7104
  case "sepBy":
6826
7105
  return [def.parser, def.parser, def.separator];
6827
7106
  case "skip":
@@ -6967,12 +7246,41 @@ function resolveGatingLevel(opt) {
6967
7246
  return "warn";
6968
7247
  }
6969
7248
  function runGatingDiagnostic(combinator, opt) {
7249
+ return reportGating(opt, (o) => analyzeGating(combinator, o));
7250
+ }
7251
+ function runGatingDiagnosticRules(ruleMap, opt) {
7252
+ return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
7253
+ }
7254
+ function runFusedGatingDiagnostic(getRuleMaps, opt) {
7255
+ if (resolveGatingLevel(opt) === "off") return void 0;
7256
+ const ruleMaps = getRuleMaps();
7257
+ if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
7258
+ const winners = /* @__PURE__ */ new Map();
7259
+ for (const map of ruleMaps) for (const [name, rule] of map) {
7260
+ if (rule._def.tag === "lazy") {
7261
+ try {
7262
+ rule._def.thunk();
7263
+ } catch {
7264
+ continue;
7265
+ }
7266
+ }
7267
+ winners.set(name, rule);
7268
+ }
7269
+ const entries = [...winners];
7270
+ return reportGating(opt, (o) => {
7271
+ const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
7272
+ const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
7273
+ return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
7274
+ });
7275
+ }
7276
+ function reportGating(opt, analyze) {
6970
7277
  const level = resolveGatingLevel(opt);
6971
7278
  if (level === "off") return void 0;
6972
- const accept = opt !== null && typeof opt === "object" ? opt.accept : void 0;
7279
+ const obj = opt !== null && typeof opt === "object" ? opt : void 0;
7280
+ 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;
6973
7281
  let report;
6974
7282
  try {
6975
- report = analyzeGating(combinator, accept ? { accept } : void 0);
7283
+ report = analyze(analyzeOpts);
6976
7284
  } catch {
6977
7285
  return void 0;
6978
7286
  }
@@ -7108,6 +7416,7 @@ function publicRuleWrapperSource(rule, fnSource) {
7108
7416
  return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
7109
7417
  }
7110
7418
  function compileRuleMap(ruleMap, opts) {
7419
+ runGatingDiagnosticRules(ruleMap, opts?.gating);
7111
7420
  for (const [, rule] of ruleMap) markUnusedValues(rule);
7112
7421
  const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
7113
7422
  const grammarTrivia = opts?.trivia;
@@ -7226,6 +7535,7 @@ function compileRuleMap(ruleMap, opts) {
7226
7535
  }
7227
7536
  function compileLinkable(ruleMapArg, ns, opts) {
7228
7537
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
7538
+ if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
7229
7539
  for (const [, rule] of ruleMapArg) markUnusedValues(rule);
7230
7540
  const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
7231
7541
  const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
@@ -7282,6 +7592,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
7282
7592
  fnNameToKey.set(fn, key);
7283
7593
  }
7284
7594
  ctx.ruleNames = ruleNames;
7595
+ const externalRefs = /* @__PURE__ */ new Set();
7285
7596
  {
7286
7597
  const scanned = /* @__PURE__ */ new Set();
7287
7598
  const scanExternal = (p) => {
@@ -7297,11 +7608,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
7297
7608
  }
7298
7609
  if (resolved === void 0) {
7299
7610
  const name = p._ruleName;
7300
- if (name && !ctx.namedParsers.has(p)) {
7301
- assertRuleName(name);
7302
- const fn = `_r_${name}`;
7303
- ruleNames.set(p, fn);
7304
- ctx.namedParsers.set(p, fn);
7611
+ if (name) {
7612
+ externalRefs.add(p);
7613
+ if (!ctx.namedParsers.has(p)) {
7614
+ assertRuleName(name);
7615
+ const fn = `_r_${name}`;
7616
+ ruleNames.set(p, fn);
7617
+ ctx.namedParsers.set(p, fn);
7618
+ }
7305
7619
  }
7306
7620
  return;
7307
7621
  }
@@ -7414,12 +7728,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
7414
7728
  needsEmptyTl: !!ctx.needsEmptyTl,
7415
7729
  needsHostReads: !!ctx.needsHostReads,
7416
7730
  hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
7417
- isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
7731
+ isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
7418
7732
  mfFns: mfSrcs ? [] : ctx.mapFns,
7419
7733
  buildFns: buildSrcs ? [] : ctx.buildFns
7420
7734
  };
7421
7735
  }
7422
- function hasSemanticReduction(roots) {
7736
+ function hasExternalRuleRef(ruleMap) {
7737
+ const seen = /* @__PURE__ */ new Set();
7738
+ const visit = (p) => {
7739
+ if (seen.has(p)) return false;
7740
+ seen.add(p);
7741
+ const def = p._def;
7742
+ if (def.tag === "lazy") {
7743
+ let resolved;
7744
+ try {
7745
+ resolved = def.thunk();
7746
+ } catch {
7747
+ resolved = void 0;
7748
+ }
7749
+ if (resolved === void 0) return p._ruleName !== void 0;
7750
+ return visit(resolved);
7751
+ }
7752
+ return childrenOf(def).some(visit);
7753
+ };
7754
+ return ruleMap.some(([, rule]) => visit(rule));
7755
+ }
7756
+ function hasSemanticReduction(roots, externalRefs) {
7423
7757
  const seen = /* @__PURE__ */ new Set();
7424
7758
  const visit = (parser2) => {
7425
7759
  if (seen.has(parser2)) return false;
@@ -7430,6 +7764,7 @@ function hasSemanticReduction(roots) {
7430
7764
  if (def.tag === "guard" || def.tag === "withCtx") return true;
7431
7765
  if (def.tag === "node" && def.build !== void 0) return true;
7432
7766
  if (def.tag === "lazy") {
7767
+ if (externalRefs?.has(parser2)) return false;
7433
7768
  try {
7434
7769
  return visit(def.thunk());
7435
7770
  } catch {
@@ -7521,6 +7856,7 @@ function childrenOf2(def) {
7521
7856
  case "label":
7522
7857
  case "field":
7523
7858
  case "not":
7859
+ case "peek":
7524
7860
  case "node":
7525
7861
  case "withCtx":
7526
7862
  case "expect":
@@ -7554,6 +7890,13 @@ function lazyTarget(c) {
7554
7890
  }
7555
7891
  var Unserializable = class extends Error {
7556
7892
  };
7893
+ function repeatOpts(min, max, trailing) {
7894
+ const parts = [];
7895
+ if (min !== 0) parts.push(`min: ${min}`);
7896
+ if (max !== void 0) parts.push(`max: ${max}`);
7897
+ if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
7898
+ return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
7899
+ }
7557
7900
  function serializeRuleMap(ruleMap, scanSkip) {
7558
7901
  try {
7559
7902
  return new Serializer(ruleMap, scanSkip ?? []).run();
@@ -7657,6 +8000,7 @@ function evalRuleMapIR(ir) {
7657
8000
  "optional",
7658
8001
  "sepBy",
7659
8002
  "not",
8003
+ "peek",
7660
8004
  "node",
7661
8005
  "parser",
7662
8006
  "scanTo",
@@ -7689,6 +8033,7 @@ function evalRuleMapIR(ir) {
7689
8033
  optional,
7690
8034
  sepBy,
7691
8035
  not,
8036
+ peek,
7692
8037
  node,
7693
8038
  parser,
7694
8039
  scanTo,
@@ -7863,17 +8208,21 @@ ${outerDecls.map((d) => " " + d).join("\n")}
7863
8208
  return `_gch([${items.join(", ")}])`;
7864
8209
  }
7865
8210
  case "many":
7866
- return `many(${kid(def.parser)})`;
8211
+ return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
8212
+ // A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
8213
+ // it through `many` so the bounds survive (oneOrMore defaults min to 1).
7867
8214
  case "oneOrMore":
7868
- return `oneOrMore(${kid(def.parser)})`;
8215
+ return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
7869
8216
  case "optional":
7870
8217
  return `optional(${kid(def.parser)})`;
7871
8218
  case "attempt":
7872
8219
  return `attempt(${kid(def.parser)})`;
7873
8220
  case "sepBy":
7874
- return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
8221
+ return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
7875
8222
  case "not":
7876
8223
  return `not(${kid(def.parser)})`;
8224
+ case "peek":
8225
+ return `peek(${kid(def.parser)})`;
7877
8226
  case "trivia":
7878
8227
  return `trivia(${kid(def.parser)})`;
7879
8228
  case "token":
@@ -8309,15 +8658,12 @@ var SUPPORTED = {
8309
8658
  literal: (...a) => literal(a[0], a[1]),
8310
8659
  regex: (...a) => regex(a[0], a[1]),
8311
8660
  keywords: (...a) => keywords(a[0], a[1]),
8312
- word: (...a) => word(a[0], a[1]),
8661
+ word: (...a) => word(a[0], a[1], a[2]),
8313
8662
  sequence: (...a) => sequence(...a),
8314
8663
  choice: (...a) => choice(...a),
8315
8664
  attempt: (...a) => attempt(a[0]),
8316
- many: (...a) => many(a[0]),
8317
- oneOrMore: (...a) => oneOrMore(a[0]),
8318
8665
  optional: (...a) => optional(a[0]),
8319
8666
  skip: (...a) => skip(a[0], a[1]),
8320
- sepBy: (...a) => sepBy(a[0], a[1]),
8321
8667
  trivia: (...a) => trivia(a[0]),
8322
8668
  label: (...a) => label(a[0], a[1]),
8323
8669
  field: (...a) => field(a[0], a[1]),
@@ -8493,8 +8839,8 @@ function exprToCombi(node2, scope, code, mfs) {
8493
8839
  return null;
8494
8840
  }
8495
8841
  }
8496
- if (callee.name === "sepBy") {
8497
- const [itemArg, sepArg] = node2.arguments;
8842
+ if (callee.name === "sepBy" || callee.name === "oneOrMoreSep") {
8843
+ const [itemArg, sepArg, optsArg] = node2.arguments;
8498
8844
  if (!itemArg || !sepArg || itemArg.type === "SpreadElement" || sepArg.type === "SpreadElement") return null;
8499
8845
  const itemMfs = [];
8500
8846
  const itemCombi = anyValue(itemArg, scope, code, itemMfs);
@@ -8502,25 +8848,42 @@ function exprToCombi(node2, scope, code, mfs) {
8502
8848
  const sepMfs = [];
8503
8849
  const sepCombi = anyValue(sepArg, scope, code, sepMfs);
8504
8850
  if (!isCombinator(sepCombi)) return null;
8851
+ let opts;
8852
+ if (optsArg) {
8853
+ if (optsArg.type === "SpreadElement") return null;
8854
+ const v2 = anyValue(optsArg, scope, code, []);
8855
+ if (!v2 || typeof v2 !== "object") return null;
8856
+ opts = v2;
8857
+ }
8505
8858
  if (mfs) mfs.push(...itemMfs, ...sepMfs, ...itemMfs);
8506
8859
  try {
8507
- return sepBy(itemCombi, sepCombi);
8860
+ return callee.name === "sepBy" ? sepBy(itemCombi, sepCombi, opts) : oneOrMoreSep(itemCombi, sepCombi, opts);
8508
8861
  } catch {
8509
8862
  return null;
8510
8863
  }
8511
8864
  }
8512
- if (callee.name === "oneOrMore") {
8513
- const [itemArg] = node2.arguments;
8865
+ if (callee.name === "oneOrMore" || callee.name === "many") {
8866
+ const [itemArg, optsArg] = node2.arguments;
8514
8867
  if (!itemArg || itemArg.type === "SpreadElement") return null;
8515
8868
  const itemMfs = [];
8516
8869
  const itemCombi = anyValue(itemArg, scope, code, itemMfs);
8517
8870
  if (!isCombinator(itemCombi)) return null;
8518
- if (mfs) mfs.push(...itemMfs, ...itemMfs);
8871
+ let opts;
8872
+ if (optsArg) {
8873
+ if (optsArg.type === "SpreadElement") return null;
8874
+ const v2 = anyValue(optsArg, scope, code, []);
8875
+ if (!v2 || typeof v2 !== "object") return null;
8876
+ opts = v2;
8877
+ }
8878
+ let combi;
8519
8879
  try {
8520
- return oneOrMore(itemCombi);
8880
+ combi = callee.name === "many" ? many(itemCombi, opts) : oneOrMore(itemCombi, opts);
8521
8881
  } catch {
8522
8882
  return null;
8523
8883
  }
8884
+ const min = combi._def.tag === "oneOrMore" ? combi._def.min : 0;
8885
+ if (mfs) for (let i = 0; i <= min; i++) mfs.push(...itemMfs);
8886
+ return combi;
8524
8887
  }
8525
8888
  if (callee.name === "not") {
8526
8889
  const [innerArg] = node2.arguments;
@@ -8533,6 +8896,17 @@ function exprToCombi(node2, scope, code, mfs) {
8533
8896
  return null;
8534
8897
  }
8535
8898
  }
8899
+ if (callee.name === "peek") {
8900
+ const [innerArg] = node2.arguments;
8901
+ if (!innerArg || innerArg.type === "SpreadElement") return null;
8902
+ const inner = anyValue(innerArg, scope, code, mfs);
8903
+ if (!isCombinator(inner)) return null;
8904
+ try {
8905
+ return peek(inner);
8906
+ } catch {
8907
+ return null;
8908
+ }
8909
+ }
8536
8910
  if (callee.name === "balanced") {
8537
8911
  const [openArg, closeArg, optsArg] = node2.arguments;
8538
8912
  if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
@@ -9162,6 +9536,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9162
9536
  const warnings = [];
9163
9537
  beginLoweringCapture();
9164
9538
  let anyUnresolved = false;
9539
+ let keepMacroImport = false;
9165
9540
  let runtimeComposeFallback = false;
9166
9541
  let ruleMapHolderCounter = 0;
9167
9542
  const lineOf = (pos) => {
@@ -9210,11 +9585,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9210
9585
  const evaluated = evaluateRulesFactory(init, label2);
9211
9586
  if (!evaluated) return null;
9212
9587
  const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
9213
- if (!compiled) {
9214
- warn(init.start, `${label2}: rule map couldn't be inlined`);
9215
- return null;
9216
- }
9217
- return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
9588
+ return { replacement: compiled?.replacement ?? null, ruleMap: evaluated.ruleMap, ...compiled?.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
9218
9589
  };
9219
9590
  const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
9220
9591
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
@@ -9309,6 +9680,17 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9309
9680
  }
9310
9681
  return out;
9311
9682
  };
9683
+ const carriedRuleMaps = (items) => {
9684
+ const out = [];
9685
+ const add = (it) => {
9686
+ if (isIR(it)) out.push(evalRuleMapIR(it.ir));
9687
+ };
9688
+ for (const it of items) {
9689
+ if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
9690
+ else add(it);
9691
+ }
9692
+ return out;
9693
+ };
9312
9694
  const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
9313
9695
  const sources = [];
9314
9696
  const add = (item) => {
@@ -9442,6 +9824,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9442
9824
  }
9443
9825
  carried.push(...r.carried);
9444
9826
  }
9827
+ runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
9445
9828
  const pieces = materializeCarried(carried, composing);
9446
9829
  try {
9447
9830
  return { replacement: emitFusedSource(pieces), carried, ...composing ? { trivia: composing } : {} };
@@ -9490,6 +9873,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9490
9873
  }
9491
9874
  try {
9492
9875
  const localNs = nsFor(`composeLeaf${init.start}`);
9876
+ runFusedGatingDiagnostic(() => [...carriedRuleMaps(carried), [...localRules]]);
9493
9877
  const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
9494
9878
  if (!plainLocalPiece) {
9495
9879
  warn(init.start, "composeLeaf(): local rules could not be statically compiled");
@@ -9578,7 +9962,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9578
9962
  const refEntry = scope.get(varName);
9579
9963
  const refCombi = refEntry?.combi ?? null;
9580
9964
  if (refCombi) {
9581
- const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
9965
+ const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
9582
9966
  if (compiled2.inlineExpression === null) {
9583
9967
  warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
9584
9968
  continue;
@@ -9593,20 +9977,26 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9593
9977
  localRuleMaps.set(varName, compiledRules.ruleMap);
9594
9978
  if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
9595
9979
  if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
9980
+ const ns = nsFor(varName);
9981
+ const pieces = exportPrefix || compiledRules.replacement === null ? compileLinkable([...compiledRules.ruleMap], ns, {
9982
+ ...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
9983
+ recovery
9984
+ }) : null;
9985
+ if (compiledRules.replacement === null) {
9986
+ if (!pieces || !hasExternalRuleRef([...compiledRules.ruleMap])) {
9987
+ warn(init.start, `${varName}: rule map couldn't be inlined`);
9988
+ continue;
9989
+ }
9990
+ keepMacroImport = true;
9991
+ }
9992
+ const source = compiledRules.replacement ?? code.slice(init.start, init.end);
9596
9993
  let replacement = withCoverageDefinitions(
9597
- compiledRules.replacement,
9598
- compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(compiledRules.replacement)
9994
+ source,
9995
+ compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
9599
9996
  );
9600
- if (exportPrefix) {
9601
- const ns = nsFor(varName);
9602
- const pieces = compileLinkable([...compiledRules.ruleMap], ns, {
9603
- ...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
9604
- recovery
9605
- });
9606
- if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
9607
- const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
9608
- replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
9609
- }
9997
+ if (exportPrefix && pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
9998
+ const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
9999
+ replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
9610
10000
  }
9611
10001
  replacements.push({ start: init.start, end: init.end, replacement });
9612
10002
  continue;
@@ -9649,7 +10039,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9649
10039
  warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
9650
10040
  continue;
9651
10041
  }
9652
- const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
10042
+ const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
9653
10043
  if (compiled.inlineExpression === null) {
9654
10044
  warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
9655
10045
  continue;
@@ -9668,6 +10058,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9668
10058
  }
9669
10059
  const compiledRules = compileRulesFactory(init, "{ \u2026 }");
9670
10060
  if (!compiledRules) continue;
10061
+ if (compiledRules.replacement === null) {
10062
+ warn(init.start, `{ \u2026 }: rule map couldn't be inlined`);
10063
+ continue;
10064
+ }
9671
10065
  const pattern = d.id;
9672
10066
  const bindings = [];
9673
10067
  let allOk = true;
@@ -9709,7 +10103,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
9709
10103
  }
9710
10104
  if (replacements.length === 0 && warnings.length === 0) return null;
9711
10105
  for (const imp of macroImports) {
9712
- imp.fullyResolved = !anyUnresolved;
10106
+ imp.fullyResolved = !anyUnresolved && !keepMacroImport;
9713
10107
  }
9714
10108
  const ms = new MagicString(code);
9715
10109
  if (!anyUnresolved) {