parseman 0.14.1 → 0.16.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.
@@ -69,6 +69,63 @@ function any() {
69
69
  function empty() {
70
70
  return { kind: "empty" };
71
71
  }
72
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
73
+ if (seen.has(p)) return true;
74
+ seen.add(p);
75
+ const me = (c) => matchesEmpty(c, seen);
76
+ const d = p._def;
77
+ switch (d.tag) {
78
+ case "literal":
79
+ return d.value.length === 0;
80
+ case "keywords":
81
+ return false;
82
+ case "regex":
83
+ try {
84
+ const m = new RegExp(d.source).exec("");
85
+ return m != null && m[0] === "";
86
+ } catch {
87
+ return true;
88
+ }
89
+ case "many":
90
+ case "optional":
91
+ case "not":
92
+ return true;
93
+ // zero repetitions / absent / lookahead
94
+ case "oneOrMore":
95
+ return me(d.parser);
96
+ case "sequence":
97
+ return d.parsers.every(me);
98
+ case "choice":
99
+ return d.parsers.some(me);
100
+ case "transform":
101
+ case "label":
102
+ case "trivia":
103
+ case "expect":
104
+ case "withCtx":
105
+ case "node":
106
+ case "grammar":
107
+ case "recover":
108
+ return me(d.parser);
109
+ case "skip":
110
+ return me(d.main);
111
+ case "lazy":
112
+ try {
113
+ return me(d.thunk());
114
+ } catch {
115
+ return true;
116
+ }
117
+ default:
118
+ return true;
119
+ }
120
+ }
121
+ function sequenceFirstSet(parsers) {
122
+ let fs2 = empty();
123
+ for (const p of parsers) {
124
+ fs2 = union(fs2, p._meta.firstSet);
125
+ if (!matchesEmpty(p)) return fs2;
126
+ }
127
+ return fs2;
128
+ }
72
129
  function mergeRanges(ranges) {
73
130
  if (ranges.length === 0) return [];
74
131
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -276,12 +333,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
276
333
  }
277
334
 
278
335
  // src/combinators/literal.ts
279
- var _collatorCache = null;
280
- function collator() {
281
- if (_collatorCache === null) {
282
- _collatorCache = new Intl.Collator(void 0, { sensitivity: "accent" });
336
+ function asciiFoldEq(a, b) {
337
+ if (a.length !== b.length) return false;
338
+ for (let i = 0; i < a.length; i++) {
339
+ let ca = a.charCodeAt(i);
340
+ let cb = b.charCodeAt(i);
341
+ if (ca >= 65 && ca <= 90) ca += 32;
342
+ if (cb >= 65 && cb <= 90) cb += 32;
343
+ if (ca !== cb) return false;
283
344
  }
284
- return _collatorCache;
345
+ return true;
285
346
  }
286
347
  function literal(value, opts = {}) {
287
348
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -296,8 +357,9 @@ function literal(value, opts = {}) {
296
357
  const lower = value.toLowerCase();
297
358
  const firstUpper = upper.codePointAt(0);
298
359
  const firstLower = lower.codePointAt(0);
299
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
300
- { lo: Math.min(firstLower, firstUpper), hi: Math.max(firstLower, firstUpper) }
360
+ meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
361
+ { lo: firstLower, hi: firstLower },
362
+ { lo: firstUpper, hi: firstUpper }
301
363
  ] } : firstSet;
302
364
  }
303
365
  return {
@@ -310,7 +372,7 @@ function literal(value, opts = {}) {
310
372
  return failAt(ctx, [JSON.stringify(value)], pos);
311
373
  }
312
374
  const slice = input.slice(pos, end);
313
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
375
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
314
376
  if (matched) {
315
377
  const span = { start: pos, end };
316
378
  const leaf = { _tag: "leaf", value: slice, span };
@@ -917,16 +979,19 @@ function scanTrivia(input, cur, ctx) {
917
979
  // src/combinators/sequence.ts
918
980
  function sequence(...parsers) {
919
981
  const meta = {
920
- firstSet: parsers[0]?._meta.firstSet ?? empty(),
982
+ // Union through the nullable prefix — a leading optional()/many() lets a later
983
+ // term's first char start the sequence. Just `parsers[0]` under-approximates.
984
+ firstSet: sequenceFirstSet(parsers),
921
985
  canMatchNewline: parsers.some((p) => p._meta.canMatchNewline),
922
986
  isTrivia: false
923
987
  };
988
+ const def = { tag: "sequence", parsers };
924
989
  return {
925
990
  _tag: "sequence",
926
991
  _meta: meta,
927
- _def: { tag: "sequence", parsers },
992
+ _def: def,
928
993
  parse(input, pos, ctx) {
929
- const values = [];
994
+ const values = def.valueUnused ? void 0 : [];
930
995
  let cur = pos;
931
996
  for (let i = 0; i < parsers.length; i++) {
932
997
  if (ctx.trivia && i > 0) {
@@ -946,17 +1011,18 @@ function sequence(...parsers) {
946
1011
  } else {
947
1012
  rollbackTrivia(ctx, mark);
948
1013
  }
949
- values.push(result2.value);
1014
+ if (values !== void 0) values.push(result2.value);
950
1015
  continue;
951
1016
  }
952
1017
  const result = parsers[i].parse(input, cur, ctx);
953
1018
  if (!result.ok) return result;
954
- values.push(result.value);
1019
+ if (values !== void 0) values.push(result.value);
955
1020
  cur = result.span.end;
956
1021
  }
957
1022
  return {
958
1023
  ok: true,
959
- value: values,
1024
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1025
+ value: values ?? void 0,
960
1026
  span: { start: pos, end: cur }
961
1027
  };
962
1028
  }
@@ -997,20 +1063,21 @@ function many(combinator) {
997
1063
  canMatchNewline: combinator._meta.canMatchNewline,
998
1064
  isTrivia: false
999
1065
  };
1066
+ const def = { tag: "many", parser: combinator, min: 0 };
1000
1067
  return {
1001
1068
  _tag: "many",
1002
1069
  _meta: meta,
1003
- _def: { tag: "many", parser: combinator, min: 0 },
1070
+ _def: def,
1004
1071
  parse(input, pos, ctx) {
1005
- const values = [];
1072
+ const values = def.valueUnused ? void 0 : [];
1006
1073
  let cur = pos;
1007
1074
  while (cur < input.length) {
1008
1075
  const item = repItem(combinator, input, cur, ctx);
1009
1076
  if (item === "stop" || "fail" in item) break;
1010
- values.push(item.value);
1077
+ if (values !== void 0) values.push(item.value);
1011
1078
  cur = item.end;
1012
1079
  }
1013
- return { ok: true, value: values, span: { start: pos, end: cur } };
1080
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1014
1081
  }
1015
1082
  };
1016
1083
  }
@@ -1020,22 +1087,23 @@ function oneOrMore(combinator) {
1020
1087
  canMatchNewline: combinator._meta.canMatchNewline,
1021
1088
  isTrivia: false
1022
1089
  };
1090
+ const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1023
1091
  return {
1024
1092
  _tag: "oneOrMore",
1025
1093
  _meta: meta,
1026
- _def: { tag: "oneOrMore", parser: combinator, min: 1 },
1094
+ _def: def,
1027
1095
  parse(input, pos, ctx) {
1028
1096
  const first = combinator.parse(input, pos, ctx);
1029
1097
  if (!first.ok) return first;
1030
- const values = [first.value];
1098
+ const values = def.valueUnused ? void 0 : [first.value];
1031
1099
  let cur = first.span.end;
1032
1100
  while (cur < input.length) {
1033
1101
  const item = repItem(combinator, input, cur, ctx);
1034
1102
  if (item === "stop" || "fail" in item) break;
1035
- values.push(item.value);
1103
+ if (values !== void 0) values.push(item.value);
1036
1104
  cur = item.end;
1037
1105
  }
1038
- return { ok: true, value: values, span: { start: pos, end: cur } };
1106
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1039
1107
  }
1040
1108
  };
1041
1109
  }
@@ -1113,6 +1181,90 @@ function sepBy(combinator, separator) {
1113
1181
  };
1114
1182
  }
1115
1183
 
1184
+ // src/compiler/value-usage.ts
1185
+ function markUnusedValues(root) {
1186
+ const seenConsumed = /* @__PURE__ */ new WeakSet();
1187
+ const seenUnconsumed = /* @__PURE__ */ new WeakSet();
1188
+ const mark = (def, consumed) => {
1189
+ if (consumed) def.valueUnused = false;
1190
+ else if (def.valueUnused !== false) def.valueUnused = true;
1191
+ };
1192
+ const visit = (c, consumed) => {
1193
+ const seen = consumed ? seenConsumed : seenUnconsumed;
1194
+ if (seen.has(c)) return;
1195
+ seen.add(c);
1196
+ const def = c._def;
1197
+ switch (def.tag) {
1198
+ case "many":
1199
+ case "oneOrMore":
1200
+ mark(def, consumed);
1201
+ visit(def.parser, consumed);
1202
+ return;
1203
+ case "optional":
1204
+ visit(def.parser, consumed);
1205
+ return;
1206
+ case "sequence":
1207
+ mark(def, consumed);
1208
+ for (const p of def.parsers) visit(p, consumed);
1209
+ return;
1210
+ case "choice":
1211
+ for (const p of def.parsers) visit(p, consumed);
1212
+ return;
1213
+ case "node":
1214
+ visit(def.parser, false);
1215
+ return;
1216
+ case "transform":
1217
+ visit(def.parser, true);
1218
+ return;
1219
+ case "skip":
1220
+ visit(def.main, consumed);
1221
+ visit(def.skipped, false);
1222
+ return;
1223
+ case "sepBy":
1224
+ visit(def.parser, true);
1225
+ visit(def.separator, false);
1226
+ return;
1227
+ case "not":
1228
+ visit(def.parser, false);
1229
+ return;
1230
+ case "grammar":
1231
+ visit(def.parser, consumed);
1232
+ if (def.triviaParser) visit(def.triviaParser, false);
1233
+ return;
1234
+ case "recover":
1235
+ visit(def.parser, consumed);
1236
+ visit(def.sentinel, false);
1237
+ return;
1238
+ case "label":
1239
+ case "trivia":
1240
+ case "withCtx":
1241
+ case "expect":
1242
+ visit(def.parser, consumed);
1243
+ return;
1244
+ case "lazy":
1245
+ return;
1246
+ default:
1247
+ return;
1248
+ }
1249
+ };
1250
+ const resolveRoot = (c) => {
1251
+ const guard2 = /* @__PURE__ */ new Set();
1252
+ let cur = c;
1253
+ while (!guard2.has(cur)) {
1254
+ const d = cur._def;
1255
+ if (d.tag !== "lazy") return cur;
1256
+ guard2.add(cur);
1257
+ try {
1258
+ cur = d.thunk();
1259
+ } catch {
1260
+ return c;
1261
+ }
1262
+ }
1263
+ return cur;
1264
+ };
1265
+ visit(resolveRoot(root), true);
1266
+ }
1267
+
1116
1268
  // src/combinators/not.ts
1117
1269
  function not(combinator) {
1118
1270
  const meta = {
@@ -1449,8 +1601,8 @@ function classToRanges(cls) {
1449
1601
  return parseClassRanges(body);
1450
1602
  }
1451
1603
  var foldEq = (cVar, cp) => {
1452
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1453
- if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1604
+ if (cp >= 65 && cp <= 90) return `(${cVar} | 32) === ${cp + 32}`;
1605
+ if (cp >= 97 && cp <= 122) return `(${cVar} | 32) === ${cp}`;
1454
1606
  return `${cVar} === ${cp}`;
1455
1607
  };
1456
1608
  function literalCodePoints(frag) {
@@ -1701,6 +1853,12 @@ function firstSetsDisjoint(a, b) {
1701
1853
  return classDisjoint(a, b);
1702
1854
  }
1703
1855
  function fixedClassSeq(shape) {
1856
+ if (shape.kind === "litFold") {
1857
+ return shape.open.map((cp) => {
1858
+ const other = cp >= 65 && cp <= 90 ? cp + 32 : cp >= 97 && cp <= 122 ? cp - 32 : cp;
1859
+ return other === cp ? { ranges: [[cp, cp]], negated: false } : { ranges: [[Math.min(cp, other), Math.min(cp, other)], [Math.max(cp, other), Math.max(cp, other)]], negated: false };
1860
+ });
1861
+ }
1704
1862
  if (shape.kind !== "seq") return null;
1705
1863
  const out = [];
1706
1864
  for (const p of shape.parts) {
@@ -2122,12 +2280,34 @@ function parseScanShapeCore(source) {
2122
2280
  }
2123
2281
  function scanShapeFromRegex(source, flags) {
2124
2282
  if (/[msu]/.test(flags)) return null;
2125
- if (/i/.test(flags)) {
2126
- const open = literalCodePoints(source);
2127
- return open ? { kind: "litFold", open } : null;
2128
- }
2283
+ if (/i/.test(flags)) return caseFoldShape(source);
2129
2284
  return parseScanShape(source);
2130
2285
  }
2286
+ function caseFoldShape(source) {
2287
+ const la = stripTrailingLookahead(source);
2288
+ if (la) {
2289
+ const inner = caseFoldLiteralOrAlt(la.base);
2290
+ if (!inner) return null;
2291
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2292
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2293
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2294
+ }
2295
+ return caseFoldLiteralOrAlt(source);
2296
+ }
2297
+ function caseFoldLiteralOrAlt(source) {
2298
+ const asciiOnly = (cps) => cps.every((cp) => cp <= 127);
2299
+ const open = literalCodePoints(source);
2300
+ if (open) return asciiOnly(open) ? { kind: "litFold", open } : null;
2301
+ const arms = splitTopLevelAlternation(source);
2302
+ if (!arms || arms.length < 2) return null;
2303
+ const litArms = [];
2304
+ for (const arm of arms) {
2305
+ const cp = literalCodePoints(arm);
2306
+ if (!cp || !asciiOnly(cp)) return null;
2307
+ litArms.push({ kind: "litFold", open: cp });
2308
+ }
2309
+ return { kind: "alt", arms: litArms, disjoint: false, firsts: litArms.map(() => null) };
2310
+ }
2131
2311
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2132
2312
  var litCond = (base, cps, firstVar) => cps.map(
2133
2313
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2139,6 +2319,25 @@ function stringHardStop(shape) {
2139
2319
  );
2140
2320
  }
2141
2321
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2322
+ var ALT_SWITCH_RANGE_LIMIT = 4;
2323
+ var ALT_SWITCH_MAX_CASES = 48;
2324
+ var ALT_SWITCH_MIN_CASES = 3;
2325
+ function planAltSwitch(firsts) {
2326
+ const cases = [];
2327
+ let total = 0;
2328
+ for (const fs2 of firsts) {
2329
+ if (!fs2 || fs2.negated) return { kind: "if" };
2330
+ const pts = [];
2331
+ for (const [lo, hi] of fs2.ranges) {
2332
+ if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
2333
+ for (let cp = lo; cp <= hi; cp++) pts.push(cp);
2334
+ }
2335
+ total += pts.length;
2336
+ if (total > ALT_SWITCH_MAX_CASES) return { kind: "if" };
2337
+ cases.push(pts);
2338
+ }
2339
+ return total >= ALT_SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2340
+ }
2142
2341
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2143
2342
  if (shape.kind === "lookahead") {
2144
2343
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2163,18 +2362,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2163
2362
  const okV = mint("_ok");
2164
2363
  const endV2 = mint("_end");
2165
2364
  const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2365
+ if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
2366
+ const groups = /* @__PURE__ */ new Map();
2367
+ for (const a of shape.arms) {
2368
+ const open = a.open;
2369
+ const f = open[0];
2370
+ const lower = f >= 65 && f <= 90 ? f + 32 : f;
2371
+ (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
2372
+ }
2373
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2374
+ for (const [lower, arms] of groups) {
2375
+ const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
2376
+ lines.push(`${ind2} ${labels} {`);
2377
+ for (const open of arms) {
2378
+ const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
2379
+ const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
2380
+ lines.push(`${ind2} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
2381
+ }
2382
+ lines.push(`${ind2} break`);
2383
+ lines.push(`${ind2} }`);
2384
+ }
2385
+ lines.push(`${ind2}}`);
2386
+ return { setup: lines, ok: okV, end: endV2 };
2387
+ }
2166
2388
  if (shape.disjoint) {
2167
- const c0 = codeAt(start, firstChar);
2168
- shape.arms.forEach((arm, k) => {
2169
- const fs2 = shape.firsts[k];
2170
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2171
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2172
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2173
- lines.push(...m.setup);
2174
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2175
- lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2389
+ const plan = planAltSwitch(shape.firsts);
2390
+ if (plan.kind === "switch") {
2391
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2392
+ shape.arms.forEach((arm, k) => {
2393
+ const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
2394
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2395
+ lines.push(`${ind2} ${labels} {`);
2396
+ lines.push(...m.setup);
2397
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2398
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2399
+ lines.push(`${ind2} break`);
2400
+ lines.push(`${ind2} }`);
2401
+ });
2176
2402
  lines.push(`${ind2}}`);
2177
- });
2403
+ } else {
2404
+ const c0 = codeAt(start, firstChar);
2405
+ shape.arms.forEach((arm, k) => {
2406
+ const fs2 = shape.firsts[k];
2407
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2408
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2409
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2410
+ lines.push(...m.setup);
2411
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2412
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2413
+ lines.push(`${ind2}}`);
2414
+ });
2415
+ }
2178
2416
  } else {
2179
2417
  const lbl = mint("_alt");
2180
2418
  lines.push(`${ind2}${lbl}: {`);
@@ -2662,6 +2900,16 @@ function tryInlineDestructureTransform(src, valueVars) {
2662
2900
  }
2663
2901
 
2664
2902
  // src/compiler/codegen.ts
2903
+ var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
2904
+ var _loweringSink = null;
2905
+ function beginLoweringCapture() {
2906
+ _loweringSink = /* @__PURE__ */ new Set();
2907
+ }
2908
+ function endLoweringCapture() {
2909
+ const misses = _loweringSink ? [..._loweringSink] : [];
2910
+ _loweringSink = null;
2911
+ return misses;
2912
+ }
2665
2913
  function v(ctx, prefix = "_v") {
2666
2914
  return `${prefix}${ctx.vars++}`;
2667
2915
  }
@@ -3060,13 +3308,14 @@ function emitLit(def, ctx, pos) {
3060
3308
  const vv = v(ctx);
3061
3309
  const expectedStr = JSON.stringify(JSON.stringify(value));
3062
3310
  const stmts = [];
3063
- if (caseInsensitive) {
3064
- ctx.needsCollator = true;
3311
+ if (caseInsensitive && len > 0) {
3312
+ const match = Array.from(
3313
+ { length: len },
3314
+ (_, i) => `(${foldEq(`input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""})`, value.charCodeAt(i))})`
3315
+ ).join(" && ");
3065
3316
  stmts.push(
3066
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3067
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3068
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3069
- `${ind(ctx)}const ${vv} = ${vv}_s`
3317
+ ...emitIfFail(ctx, `${pos} + ${len} > input.length || !(${match})`, failBody(ctx, expectedStr, pos)),
3318
+ `${ind(ctx)}const ${vv} = input.slice(${pos}, ${pos} + ${len})`
3070
3319
  );
3071
3320
  } else if (len === 0) {
3072
3321
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3187,6 +3436,7 @@ function emitRegex(def, ctx, pos) {
3187
3436
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3188
3437
  }
3189
3438
  }
3439
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3190
3440
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3191
3441
  const key = `${def.source}/${flags}`;
3192
3442
  let rName = ctx.regexMap.get(key);
@@ -3251,6 +3501,7 @@ function emitSeqValues(def, ctx, pos) {
3251
3501
  }
3252
3502
  function emitSeq(def, ctx, pos) {
3253
3503
  const { stmts, endVar, valueVars } = emitSeqValues(def, ctx, pos);
3504
+ if (def.valueUnused) return { stmts, valueVar: "undefined", endVar };
3254
3505
  const arrV = v(ctx, "_arr");
3255
3506
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3256
3507
  return { stmts, valueVar: arrV, endVar };
@@ -3457,7 +3708,8 @@ function emitFirstMatch(def, ctx, pos) {
3457
3708
  const autoNot = def.autoNot[i];
3458
3709
  const atStart = failsAtStart(p);
3459
3710
  const staticFx = armStaticExpected(ctx, p);
3460
- const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3711
+ const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3712
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3461
3713
  let gateCond = null;
3462
3714
  if (gate) {
3463
3715
  const gateIdx = ctx.mapFns.length;
@@ -3548,19 +3800,18 @@ function emitLiteralCondition(litVal, pos) {
3548
3800
  return checks.join(" && ");
3549
3801
  }
3550
3802
  function emitMany(def, ctx, pos) {
3803
+ const wantValue = !def.valueUnused;
3551
3804
  const arrV = v(ctx, "_arr");
3552
3805
  const curV = v(ctx, "_cur");
3553
3806
  const stmts = [
3554
- `${ind(ctx)}const ${arrV} = []`,
3807
+ ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
3555
3808
  `${ind(ctx)}let ${curV} = ${pos}`
3556
3809
  ];
3557
3810
  if (def.min === 1) {
3558
3811
  const firstR = emit(def.parser, ctx, curV);
3559
3812
  stmts.push(...firstR.stmts);
3560
- stmts.push(
3561
- `${ind(ctx)}${arrV}.push(${firstR.valueVar})`,
3562
- `${ind(ctx)}${curV} = ${firstR.endVar}`
3563
- );
3813
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
3814
+ stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
3564
3815
  }
3565
3816
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
3566
3817
  ctx.indent++;
@@ -3591,13 +3842,13 @@ function emitMany(def, ctx, pos) {
3591
3842
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
3592
3843
  stmts.push(
3593
3844
  ...iterStmts,
3594
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
3595
- `${ind(ctx)}${arrV}.push(${iterVal})`,
3596
- `${ind(ctx)}${curV} = ${iterEnd}`
3845
+ `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
3597
3846
  );
3847
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
3848
+ stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
3598
3849
  ctx.indent--;
3599
3850
  stmts.push(`${ind(ctx)}}`);
3600
- return { stmts, valueVar: arrV, endVar: curV };
3851
+ return { stmts, valueVar: wantValue ? arrV : "undefined", endVar: curV };
3601
3852
  }
3602
3853
  function emitOptional(def, ctx, pos) {
3603
3854
  const valV = v(ctx, "_opt");
@@ -3766,15 +4017,19 @@ function emitNode(def, ctx, pos) {
3766
4017
  ctx.buildSrcs.push(def.buildSrc ?? null);
3767
4018
  }
3768
4019
  const i = ind(ctx);
3769
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3770
- const clonesState = structural || buildReadsState(def);
4020
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4021
+ const clonesState = !structural && buildReadsState(def);
3771
4022
  const chV = v(ctx, "_ch");
3772
4023
  const rawV = v(ctx, "_raw");
3773
- const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
4024
+ const capTLv = structural ? v(ctx, "_ctl") : null;
4025
+ const capSTv = structural ? v(ctx, "_cst") : null;
4026
+ const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
3774
4027
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3775
4028
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3776
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3777
- const innerTl = capturesTrivia ? tlV : "undefined";
4029
+ if (structural) ctx.needsHostReads = true;
4030
+ const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4031
+ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4032
+ const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
3778
4033
  const stmts = [
3779
4034
  allocStmt,
3780
4035
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3785,7 +4040,10 @@ function emitNode(def, ctx, pos) {
3785
4040
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3786
4041
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3787
4042
  let stV = "undefined";
3788
- if (clonesState) {
4043
+ if (structural) {
4044
+ stV = v(ctx, "_nst");
4045
+ stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4046
+ } else if (clonesState) {
3789
4047
  stV = v(ctx, "_nst");
3790
4048
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3791
4049
  }
@@ -4207,6 +4465,7 @@ function ruleDependencies(ruleMap) {
4207
4465
  return deps;
4208
4466
  }
4209
4467
  function compile(combinator, mapFnSources) {
4468
+ markUnusedValues(combinator);
4210
4469
  const ctx = {
4211
4470
  vars: 0,
4212
4471
  indent: 1,
@@ -4220,7 +4479,6 @@ function compile(combinator, mapFnSources) {
4220
4479
  buildFns: [],
4221
4480
  buildSrcs: [],
4222
4481
  runtimeParsers: [],
4223
- needsCollator: false,
4224
4482
  namedParsers: /* @__PURE__ */ new Map(),
4225
4483
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4226
4484
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4229,18 +4487,18 @@ function compile(combinator, mapFnSources) {
4229
4487
  lazyUsage: analyzeLazyUsage(combinator)
4230
4488
  };
4231
4489
  const r = emit(combinator, ctx, "_pos");
4232
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4233
- ` : "";
4234
4490
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4235
4491
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4492
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4236
4493
  const source = [
4237
4494
  ...emptyTlDecls,
4495
+ ...hostReadsDecls,
4238
4496
  ...ctx.regexDecls,
4239
4497
  ...ctx.expectedDecls,
4240
4498
  "",
4241
4499
  ...namedPrelude,
4242
4500
  ctx.namedFnDecls.join("\n\n"),
4243
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4501
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4244
4502
  ` let pos = _pos`,
4245
4503
  ...r.stmts,
4246
4504
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4248,9 +4506,9 @@ function compile(combinator, mapFnSources) {
4248
4506
  ].join("\n");
4249
4507
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4250
4508
  ...emptyTlDecls,
4509
+ ...hostReadsDecls,
4251
4510
  ...ctx.regexDecls,
4252
4511
  ...ctx.expectedDecls,
4253
- collatorDecl,
4254
4512
  ...namedPrelude,
4255
4513
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4256
4514
  `let pos = _pos`,
@@ -4267,7 +4525,7 @@ function compile(combinator, mapFnSources) {
4267
4525
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4268
4526
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4269
4527
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4270
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4528
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4271
4529
  return {
4272
4530
  source,
4273
4531
  inlineExpression,
@@ -4289,7 +4547,16 @@ function compile(combinator, mapFnSources) {
4289
4547
  }
4290
4548
  };
4291
4549
  }
4550
+ var RULE_NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
4551
+ function assertRuleName(name) {
4552
+ if (!RULE_NAME_RE.test(name)) {
4553
+ throw new Error(
4554
+ `parseman: grammar rule name ${JSON.stringify(name)} is not a valid JS identifier. Rule names compile to _r_<Name> functions and dispatch guards \u2014 use letters, digits, _ or $ (and don't start with a digit).`
4555
+ );
4556
+ }
4557
+ }
4292
4558
  function compileRuleMap(ruleMap) {
4559
+ for (const [, rule] of ruleMap) markUnusedValues(rule);
4293
4560
  const ctx = {
4294
4561
  vars: 0,
4295
4562
  indent: 1,
@@ -4303,7 +4570,6 @@ function compileRuleMap(ruleMap) {
4303
4570
  buildFns: [],
4304
4571
  buildSrcs: [],
4305
4572
  runtimeParsers: [],
4306
- needsCollator: false,
4307
4573
  namedParsers: /* @__PURE__ */ new Map(),
4308
4574
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4309
4575
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4314,15 +4580,14 @@ function compileRuleMap(ruleMap) {
4314
4580
  const seenNames = /* @__PURE__ */ new Map();
4315
4581
  const ruleNames = /* @__PURE__ */ new Map();
4316
4582
  for (const [key, rule] of ruleMap) {
4317
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4583
+ assertRuleName(key);
4584
+ const base = `_r_${key}`;
4318
4585
  const n = seenNames.get(base) ?? 0;
4319
4586
  seenNames.set(base, n + 1);
4320
4587
  ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4321
4588
  }
4322
4589
  ctx.ruleNames = ruleNames;
4323
4590
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4324
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4325
- ` : "";
4326
4591
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
4327
4592
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
4328
4593
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -4334,9 +4599,9 @@ function compileRuleMap(ruleMap) {
4334
4599
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4335
4600
  const hoistedDecls = [
4336
4601
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
4602
+ ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
4337
4603
  ...ctx.regexDecls.map((d) => ` ${d}`),
4338
4604
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4339
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4340
4605
  mfDecl,
4341
4606
  buildDecl,
4342
4607
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4365,6 +4630,7 @@ function compileRuleMap(ruleMap) {
4365
4630
  }
4366
4631
  function compileLinkable(ruleMapArg, ns) {
4367
4632
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4633
+ for (const [, rule] of ruleMapArg) markUnusedValues(rule);
4368
4634
  const ruleMap = ruleMapArg.filter(([, val]) => {
4369
4635
  const d = val._def;
4370
4636
  if (d.tag !== "lazy") return true;
@@ -4388,20 +4654,21 @@ function compileLinkable(ruleMapArg, ns) {
4388
4654
  buildFns: [],
4389
4655
  buildSrcs: [],
4390
4656
  runtimeParsers: [],
4391
- needsCollator: false,
4392
4657
  namedParsers: /* @__PURE__ */ new Map(),
4393
4658
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4394
4659
  triviaFnNames: /* @__PURE__ */ new Map(),
4395
4660
  namedFnDecls: [],
4396
4661
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4397
4662
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4398
- ns
4663
+ ns,
4664
+ deferFirstSetRefs: true
4399
4665
  };
4400
4666
  const seen = /* @__PURE__ */ new Map();
4401
4667
  const ruleNames = /* @__PURE__ */ new Map();
4402
4668
  const fnNameToKey = /* @__PURE__ */ new Map();
4403
4669
  for (const [key, rule] of ruleMap) {
4404
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4670
+ assertRuleName(key);
4671
+ const base = `_r_${key}`;
4405
4672
  const n = seen.get(base) ?? 0;
4406
4673
  seen.set(base, n + 1);
4407
4674
  const fn = n === 0 ? base : `${base}$${n}`;
@@ -4425,7 +4692,8 @@ function compileLinkable(ruleMapArg, ns) {
4425
4692
  if (resolved === void 0) {
4426
4693
  const name = p._ruleName;
4427
4694
  if (name && !ctx.namedParsers.has(p)) {
4428
- const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4695
+ assertRuleName(name);
4696
+ const fn = `_r_${name}`;
4429
4697
  ruleNames.set(p, fn);
4430
4698
  ctx.namedParsers.set(p, fn);
4431
4699
  }
@@ -4494,20 +4762,34 @@ function compileLinkable(ruleMapArg, ns) {
4494
4762
  `}`
4495
4763
  ].join("\n"));
4496
4764
  }
4765
+ const firstSets = /* @__PURE__ */ new Map();
4766
+ for (const [key, rule] of ruleMap) {
4767
+ let resolved = rule;
4768
+ const d = rule._def;
4769
+ if (d.tag === "lazy") {
4770
+ try {
4771
+ resolved = d.thunk();
4772
+ } catch {
4773
+ resolved = rule;
4774
+ }
4775
+ }
4776
+ firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
4777
+ }
4497
4778
  return {
4498
4779
  ns,
4499
4780
  keys: perEntry.map((e) => e.key),
4500
4781
  prelude,
4501
4782
  ruleFns,
4502
4783
  wrappers,
4784
+ firstSets,
4503
4785
  deps: ruleDependencies(ruleMap),
4504
4786
  needsEmptyTl: !!ctx.needsEmptyTl,
4505
- needsCollator: ctx.needsCollator,
4787
+ needsHostReads: !!ctx.needsHostReads,
4506
4788
  mfFns: mfSrcs ? [] : ctx.mapFns,
4507
4789
  buildFns: buildSrcs ? [] : ctx.buildFns
4508
4790
  };
4509
4791
  }
4510
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4792
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4511
4793
  const bodyLines = [
4512
4794
  ` let pos = _pos`,
4513
4795
  ...r.stmts,
@@ -4521,14 +4803,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4521
4803
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4522
4804
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4523
4805
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4524
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4806
+ const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
4807
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl;
4525
4808
  if (!needsWrapper) return innerFn;
4526
4809
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4527
4810
  const hoistedDecls = [
4528
4811
  emptyTlDecl,
4812
+ hostReadsDecl,
4529
4813
  ...ctx.regexDecls.map((d) => ` ${d}`),
4530
4814
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4531
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4532
4815
  mfDecl,
4533
4816
  buildDecl,
4534
4817
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4556,20 +4839,36 @@ function fusedBody(pieces) {
4556
4839
  }
4557
4840
  const contributing = new Set(winner.values());
4558
4841
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4559
- const needsCollator = [...contributing].some((p) => p.needsCollator);
4842
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4560
4843
  const lines = [
4561
4844
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4562
4845
  "const _pfFail = {}",
4563
4846
  "let _pfEnd",
4564
4847
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4565
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4848
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4566
4849
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4567
4850
  ...[...contributing].flatMap((p) => p.prelude),
4568
4851
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4569
4852
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4570
4853
  ];
4571
4854
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4572
- const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4855
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4856
+ const finalFS = /* @__PURE__ */ new Map();
4857
+ for (const [k, p] of winner) {
4858
+ const fs2 = p.firstSets?.get(k);
4859
+ if (fs2) finalFS.set(k, fs2);
4860
+ }
4861
+ const body = rawBody.replace(
4862
+ // Rule name + code-point var are both JS identifiers (rule names are validated
4863
+ // at compile time — see assertRuleName in codegen), so an identifier class
4864
+ // matches every well-formed placeholder.
4865
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4866
+ (_m, name, codevar) => {
4867
+ const fs2 = finalFS.get(name);
4868
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4869
+ return `(${firstSetCond(codevar, fs2)})`;
4870
+ }
4871
+ );
4573
4872
  const env = {};
4574
4873
  for (const p of contributing) {
4575
4874
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -5192,7 +5491,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
5192
5491
  if (!code.includes("parseman")) return null;
5193
5492
  if (!code.includes("macro")) return null;
5194
5493
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
5195
- const result = transformMacro(code, id, moduleAliases);
5494
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
5196
5495
  if (result?.warnings.length) {
5197
5496
  for (const w of result.warnings) {
5198
5497
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -5274,7 +5573,7 @@ function findCarriedPiecesLiteral(root) {
5274
5573
  visit(root);
5275
5574
  return found;
5276
5575
  }
5277
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
5576
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5278
5577
  let result;
5279
5578
  try {
5280
5579
  result = (0, import_oxc_parser.parseSync)(id, code);
@@ -5313,6 +5612,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5313
5612
  const scope = /* @__PURE__ */ new Map();
5314
5613
  const replacements = [];
5315
5614
  const warnings = [];
5615
+ beginLoweringCapture();
5316
5616
  let anyUnresolved = false;
5317
5617
  let ruleMapHolderCounter = 0;
5318
5618
  const lineOf = (pos) => {
@@ -5349,7 +5649,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5349
5649
  const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5350
5650
  const serializePieces = (p) => {
5351
5651
  const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5352
- return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsCollator: ${p.needsCollator}, mfFns: [], buildFns: [] }`;
5652
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, mfFns: [], buildFns: [] }`;
5353
5653
  };
5354
5654
  const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5355
5655
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
@@ -5620,6 +5920,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5620
5920
  for (const { start, end, replacement } of [...replacements].sort((a, b) => b.start - a.start)) {
5621
5921
  ms.overwrite(start, end, replacement);
5622
5922
  }
5923
+ const unlowered = endLoweringCapture();
5924
+ if (warnUnloweredRegex) {
5925
+ for (const src of unlowered) {
5926
+ warnings.push(`${id}: regex ${src} did not lower to a fast charCodeAt scan (RegExp.exec fallback)`);
5927
+ }
5928
+ }
5623
5929
  return {
5624
5930
  code: ms.toString(),
5625
5931
  map: ms.generateMap({ hires: true }),