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.
@@ -34,6 +34,63 @@ function any() {
34
34
  function empty() {
35
35
  return { kind: "empty" };
36
36
  }
37
+ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
38
+ if (seen.has(p)) return true;
39
+ seen.add(p);
40
+ const me = (c) => matchesEmpty(c, seen);
41
+ const d = p._def;
42
+ switch (d.tag) {
43
+ case "literal":
44
+ return d.value.length === 0;
45
+ case "keywords":
46
+ return false;
47
+ case "regex":
48
+ try {
49
+ const m = new RegExp(d.source).exec("");
50
+ return m != null && m[0] === "";
51
+ } catch {
52
+ return true;
53
+ }
54
+ case "many":
55
+ case "optional":
56
+ case "not":
57
+ return true;
58
+ // zero repetitions / absent / lookahead
59
+ case "oneOrMore":
60
+ return me(d.parser);
61
+ case "sequence":
62
+ return d.parsers.every(me);
63
+ case "choice":
64
+ return d.parsers.some(me);
65
+ case "transform":
66
+ case "label":
67
+ case "trivia":
68
+ case "expect":
69
+ case "withCtx":
70
+ case "node":
71
+ case "grammar":
72
+ case "recover":
73
+ return me(d.parser);
74
+ case "skip":
75
+ return me(d.main);
76
+ case "lazy":
77
+ try {
78
+ return me(d.thunk());
79
+ } catch {
80
+ return true;
81
+ }
82
+ default:
83
+ return true;
84
+ }
85
+ }
86
+ function sequenceFirstSet(parsers) {
87
+ let fs2 = empty();
88
+ for (const p of parsers) {
89
+ fs2 = union(fs2, p._meta.firstSet);
90
+ if (!matchesEmpty(p)) return fs2;
91
+ }
92
+ return fs2;
93
+ }
37
94
  function mergeRanges(ranges) {
38
95
  if (ranges.length === 0) return [];
39
96
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -241,12 +298,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
241
298
  }
242
299
 
243
300
  // src/combinators/literal.ts
244
- var _collatorCache = null;
245
- function collator() {
246
- if (_collatorCache === null) {
247
- _collatorCache = new Intl.Collator(void 0, { sensitivity: "accent" });
301
+ function asciiFoldEq(a, b) {
302
+ if (a.length !== b.length) return false;
303
+ for (let i = 0; i < a.length; i++) {
304
+ let ca = a.charCodeAt(i);
305
+ let cb = b.charCodeAt(i);
306
+ if (ca >= 65 && ca <= 90) ca += 32;
307
+ if (cb >= 65 && cb <= 90) cb += 32;
308
+ if (ca !== cb) return false;
248
309
  }
249
- return _collatorCache;
310
+ return true;
250
311
  }
251
312
  function literal(value, opts = {}) {
252
313
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -261,8 +322,9 @@ function literal(value, opts = {}) {
261
322
  const lower = value.toLowerCase();
262
323
  const firstUpper = upper.codePointAt(0);
263
324
  const firstLower = lower.codePointAt(0);
264
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
265
- { lo: Math.min(firstLower, firstUpper), hi: Math.max(firstLower, firstUpper) }
325
+ meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
326
+ { lo: firstLower, hi: firstLower },
327
+ { lo: firstUpper, hi: firstUpper }
266
328
  ] } : firstSet;
267
329
  }
268
330
  return {
@@ -275,7 +337,7 @@ function literal(value, opts = {}) {
275
337
  return failAt(ctx, [JSON.stringify(value)], pos);
276
338
  }
277
339
  const slice = input.slice(pos, end);
278
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
340
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
279
341
  if (matched) {
280
342
  const span = { start: pos, end };
281
343
  const leaf = { _tag: "leaf", value: slice, span };
@@ -882,16 +944,19 @@ function scanTrivia(input, cur, ctx) {
882
944
  // src/combinators/sequence.ts
883
945
  function sequence(...parsers) {
884
946
  const meta = {
885
- firstSet: parsers[0]?._meta.firstSet ?? empty(),
947
+ // Union through the nullable prefix — a leading optional()/many() lets a later
948
+ // term's first char start the sequence. Just `parsers[0]` under-approximates.
949
+ firstSet: sequenceFirstSet(parsers),
886
950
  canMatchNewline: parsers.some((p) => p._meta.canMatchNewline),
887
951
  isTrivia: false
888
952
  };
953
+ const def = { tag: "sequence", parsers };
889
954
  return {
890
955
  _tag: "sequence",
891
956
  _meta: meta,
892
- _def: { tag: "sequence", parsers },
957
+ _def: def,
893
958
  parse(input, pos, ctx) {
894
- const values = [];
959
+ const values = def.valueUnused ? void 0 : [];
895
960
  let cur = pos;
896
961
  for (let i = 0; i < parsers.length; i++) {
897
962
  if (ctx.trivia && i > 0) {
@@ -911,17 +976,18 @@ function sequence(...parsers) {
911
976
  } else {
912
977
  rollbackTrivia(ctx, mark);
913
978
  }
914
- values.push(result2.value);
979
+ if (values !== void 0) values.push(result2.value);
915
980
  continue;
916
981
  }
917
982
  const result = parsers[i].parse(input, cur, ctx);
918
983
  if (!result.ok) return result;
919
- values.push(result.value);
984
+ if (values !== void 0) values.push(result.value);
920
985
  cur = result.span.end;
921
986
  }
922
987
  return {
923
988
  ok: true,
924
- value: values,
989
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
990
+ value: values ?? void 0,
925
991
  span: { start: pos, end: cur }
926
992
  };
927
993
  }
@@ -962,20 +1028,21 @@ function many(combinator) {
962
1028
  canMatchNewline: combinator._meta.canMatchNewline,
963
1029
  isTrivia: false
964
1030
  };
1031
+ const def = { tag: "many", parser: combinator, min: 0 };
965
1032
  return {
966
1033
  _tag: "many",
967
1034
  _meta: meta,
968
- _def: { tag: "many", parser: combinator, min: 0 },
1035
+ _def: def,
969
1036
  parse(input, pos, ctx) {
970
- const values = [];
1037
+ const values = def.valueUnused ? void 0 : [];
971
1038
  let cur = pos;
972
1039
  while (cur < input.length) {
973
1040
  const item = repItem(combinator, input, cur, ctx);
974
1041
  if (item === "stop" || "fail" in item) break;
975
- values.push(item.value);
1042
+ if (values !== void 0) values.push(item.value);
976
1043
  cur = item.end;
977
1044
  }
978
- return { ok: true, value: values, span: { start: pos, end: cur } };
1045
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
979
1046
  }
980
1047
  };
981
1048
  }
@@ -985,22 +1052,23 @@ function oneOrMore(combinator) {
985
1052
  canMatchNewline: combinator._meta.canMatchNewline,
986
1053
  isTrivia: false
987
1054
  };
1055
+ const def = { tag: "oneOrMore", parser: combinator, min: 1 };
988
1056
  return {
989
1057
  _tag: "oneOrMore",
990
1058
  _meta: meta,
991
- _def: { tag: "oneOrMore", parser: combinator, min: 1 },
1059
+ _def: def,
992
1060
  parse(input, pos, ctx) {
993
1061
  const first = combinator.parse(input, pos, ctx);
994
1062
  if (!first.ok) return first;
995
- const values = [first.value];
1063
+ const values = def.valueUnused ? void 0 : [first.value];
996
1064
  let cur = first.span.end;
997
1065
  while (cur < input.length) {
998
1066
  const item = repItem(combinator, input, cur, ctx);
999
1067
  if (item === "stop" || "fail" in item) break;
1000
- values.push(item.value);
1068
+ if (values !== void 0) values.push(item.value);
1001
1069
  cur = item.end;
1002
1070
  }
1003
- return { ok: true, value: values, span: { start: pos, end: cur } };
1071
+ return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
1004
1072
  }
1005
1073
  };
1006
1074
  }
@@ -1078,6 +1146,90 @@ function sepBy(combinator, separator) {
1078
1146
  };
1079
1147
  }
1080
1148
 
1149
+ // src/compiler/value-usage.ts
1150
+ function markUnusedValues(root) {
1151
+ const seenConsumed = /* @__PURE__ */ new WeakSet();
1152
+ const seenUnconsumed = /* @__PURE__ */ new WeakSet();
1153
+ const mark = (def, consumed) => {
1154
+ if (consumed) def.valueUnused = false;
1155
+ else if (def.valueUnused !== false) def.valueUnused = true;
1156
+ };
1157
+ const visit = (c, consumed) => {
1158
+ const seen = consumed ? seenConsumed : seenUnconsumed;
1159
+ if (seen.has(c)) return;
1160
+ seen.add(c);
1161
+ const def = c._def;
1162
+ switch (def.tag) {
1163
+ case "many":
1164
+ case "oneOrMore":
1165
+ mark(def, consumed);
1166
+ visit(def.parser, consumed);
1167
+ return;
1168
+ case "optional":
1169
+ visit(def.parser, consumed);
1170
+ return;
1171
+ case "sequence":
1172
+ mark(def, consumed);
1173
+ for (const p of def.parsers) visit(p, consumed);
1174
+ return;
1175
+ case "choice":
1176
+ for (const p of def.parsers) visit(p, consumed);
1177
+ return;
1178
+ case "node":
1179
+ visit(def.parser, false);
1180
+ return;
1181
+ case "transform":
1182
+ visit(def.parser, true);
1183
+ return;
1184
+ case "skip":
1185
+ visit(def.main, consumed);
1186
+ visit(def.skipped, false);
1187
+ return;
1188
+ case "sepBy":
1189
+ visit(def.parser, true);
1190
+ visit(def.separator, false);
1191
+ return;
1192
+ case "not":
1193
+ visit(def.parser, false);
1194
+ return;
1195
+ case "grammar":
1196
+ visit(def.parser, consumed);
1197
+ if (def.triviaParser) visit(def.triviaParser, false);
1198
+ return;
1199
+ case "recover":
1200
+ visit(def.parser, consumed);
1201
+ visit(def.sentinel, false);
1202
+ return;
1203
+ case "label":
1204
+ case "trivia":
1205
+ case "withCtx":
1206
+ case "expect":
1207
+ visit(def.parser, consumed);
1208
+ return;
1209
+ case "lazy":
1210
+ return;
1211
+ default:
1212
+ return;
1213
+ }
1214
+ };
1215
+ const resolveRoot = (c) => {
1216
+ const guard2 = /* @__PURE__ */ new Set();
1217
+ let cur = c;
1218
+ while (!guard2.has(cur)) {
1219
+ const d = cur._def;
1220
+ if (d.tag !== "lazy") return cur;
1221
+ guard2.add(cur);
1222
+ try {
1223
+ cur = d.thunk();
1224
+ } catch {
1225
+ return c;
1226
+ }
1227
+ }
1228
+ return cur;
1229
+ };
1230
+ visit(resolveRoot(root), true);
1231
+ }
1232
+
1081
1233
  // src/combinators/not.ts
1082
1234
  function not(combinator) {
1083
1235
  const meta = {
@@ -1414,8 +1566,8 @@ function classToRanges(cls) {
1414
1566
  return parseClassRanges(body);
1415
1567
  }
1416
1568
  var foldEq = (cVar, cp) => {
1417
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1418
- if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1569
+ if (cp >= 65 && cp <= 90) return `(${cVar} | 32) === ${cp + 32}`;
1570
+ if (cp >= 97 && cp <= 122) return `(${cVar} | 32) === ${cp}`;
1419
1571
  return `${cVar} === ${cp}`;
1420
1572
  };
1421
1573
  function literalCodePoints(frag) {
@@ -1666,6 +1818,12 @@ function firstSetsDisjoint(a, b) {
1666
1818
  return classDisjoint(a, b);
1667
1819
  }
1668
1820
  function fixedClassSeq(shape) {
1821
+ if (shape.kind === "litFold") {
1822
+ return shape.open.map((cp) => {
1823
+ const other = cp >= 65 && cp <= 90 ? cp + 32 : cp >= 97 && cp <= 122 ? cp - 32 : cp;
1824
+ 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 };
1825
+ });
1826
+ }
1669
1827
  if (shape.kind !== "seq") return null;
1670
1828
  const out = [];
1671
1829
  for (const p of shape.parts) {
@@ -2087,12 +2245,34 @@ function parseScanShapeCore(source) {
2087
2245
  }
2088
2246
  function scanShapeFromRegex(source, flags) {
2089
2247
  if (/[msu]/.test(flags)) return null;
2090
- if (/i/.test(flags)) {
2091
- const open = literalCodePoints(source);
2092
- return open ? { kind: "litFold", open } : null;
2093
- }
2248
+ if (/i/.test(flags)) return caseFoldShape(source);
2094
2249
  return parseScanShape(source);
2095
2250
  }
2251
+ function caseFoldShape(source) {
2252
+ const la = stripTrailingLookahead(source);
2253
+ if (la) {
2254
+ const inner = caseFoldLiteralOrAlt(la.base);
2255
+ if (!inner) return null;
2256
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2257
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2258
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2259
+ }
2260
+ return caseFoldLiteralOrAlt(source);
2261
+ }
2262
+ function caseFoldLiteralOrAlt(source) {
2263
+ const asciiOnly = (cps) => cps.every((cp) => cp <= 127);
2264
+ const open = literalCodePoints(source);
2265
+ if (open) return asciiOnly(open) ? { kind: "litFold", open } : null;
2266
+ const arms = splitTopLevelAlternation(source);
2267
+ if (!arms || arms.length < 2) return null;
2268
+ const litArms = [];
2269
+ for (const arm of arms) {
2270
+ const cp = literalCodePoints(arm);
2271
+ if (!cp || !asciiOnly(cp)) return null;
2272
+ litArms.push({ kind: "litFold", open: cp });
2273
+ }
2274
+ return { kind: "alt", arms: litArms, disjoint: false, firsts: litArms.map(() => null) };
2275
+ }
2096
2276
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2097
2277
  var litCond = (base, cps, firstVar) => cps.map(
2098
2278
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2104,6 +2284,25 @@ function stringHardStop(shape) {
2104
2284
  );
2105
2285
  }
2106
2286
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2287
+ var ALT_SWITCH_RANGE_LIMIT = 4;
2288
+ var ALT_SWITCH_MAX_CASES = 48;
2289
+ var ALT_SWITCH_MIN_CASES = 3;
2290
+ function planAltSwitch(firsts) {
2291
+ const cases = [];
2292
+ let total = 0;
2293
+ for (const fs2 of firsts) {
2294
+ if (!fs2 || fs2.negated) return { kind: "if" };
2295
+ const pts = [];
2296
+ for (const [lo, hi] of fs2.ranges) {
2297
+ if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
2298
+ for (let cp = lo; cp <= hi; cp++) pts.push(cp);
2299
+ }
2300
+ total += pts.length;
2301
+ if (total > ALT_SWITCH_MAX_CASES) return { kind: "if" };
2302
+ cases.push(pts);
2303
+ }
2304
+ return total >= ALT_SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2305
+ }
2107
2306
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2108
2307
  if (shape.kind === "lookahead") {
2109
2308
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2128,18 +2327,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2128
2327
  const okV = mint("_ok");
2129
2328
  const endV2 = mint("_end");
2130
2329
  const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2330
+ if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
2331
+ const groups = /* @__PURE__ */ new Map();
2332
+ for (const a of shape.arms) {
2333
+ const open = a.open;
2334
+ const f = open[0];
2335
+ const lower = f >= 65 && f <= 90 ? f + 32 : f;
2336
+ (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
2337
+ }
2338
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2339
+ for (const [lower, arms] of groups) {
2340
+ const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
2341
+ lines.push(`${ind2} ${labels} {`);
2342
+ for (const open of arms) {
2343
+ const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
2344
+ const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
2345
+ lines.push(`${ind2} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
2346
+ }
2347
+ lines.push(`${ind2} break`);
2348
+ lines.push(`${ind2} }`);
2349
+ }
2350
+ lines.push(`${ind2}}`);
2351
+ return { setup: lines, ok: okV, end: endV2 };
2352
+ }
2131
2353
  if (shape.disjoint) {
2132
- const c0 = codeAt(start, firstChar);
2133
- shape.arms.forEach((arm, k) => {
2134
- const fs2 = shape.firsts[k];
2135
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2136
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2137
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2138
- lines.push(...m.setup);
2139
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2140
- lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2354
+ const plan = planAltSwitch(shape.firsts);
2355
+ if (plan.kind === "switch") {
2356
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2357
+ shape.arms.forEach((arm, k) => {
2358
+ const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
2359
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2360
+ lines.push(`${ind2} ${labels} {`);
2361
+ lines.push(...m.setup);
2362
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2363
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2364
+ lines.push(`${ind2} break`);
2365
+ lines.push(`${ind2} }`);
2366
+ });
2141
2367
  lines.push(`${ind2}}`);
2142
- });
2368
+ } else {
2369
+ const c0 = codeAt(start, firstChar);
2370
+ shape.arms.forEach((arm, k) => {
2371
+ const fs2 = shape.firsts[k];
2372
+ const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2373
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2374
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2375
+ lines.push(...m.setup);
2376
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2377
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2378
+ lines.push(`${ind2}}`);
2379
+ });
2380
+ }
2143
2381
  } else {
2144
2382
  const lbl = mint("_alt");
2145
2383
  lines.push(`${ind2}${lbl}: {`);
@@ -2627,6 +2865,16 @@ function tryInlineDestructureTransform(src, valueVars) {
2627
2865
  }
2628
2866
 
2629
2867
  // src/compiler/codegen.ts
2868
+ 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 }";
2869
+ var _loweringSink = null;
2870
+ function beginLoweringCapture() {
2871
+ _loweringSink = /* @__PURE__ */ new Set();
2872
+ }
2873
+ function endLoweringCapture() {
2874
+ const misses = _loweringSink ? [..._loweringSink] : [];
2875
+ _loweringSink = null;
2876
+ return misses;
2877
+ }
2630
2878
  function v(ctx, prefix = "_v") {
2631
2879
  return `${prefix}${ctx.vars++}`;
2632
2880
  }
@@ -3025,13 +3273,14 @@ function emitLit(def, ctx, pos) {
3025
3273
  const vv = v(ctx);
3026
3274
  const expectedStr = JSON.stringify(JSON.stringify(value));
3027
3275
  const stmts = [];
3028
- if (caseInsensitive) {
3029
- ctx.needsCollator = true;
3276
+ if (caseInsensitive && len > 0) {
3277
+ const match = Array.from(
3278
+ { length: len },
3279
+ (_, i) => `(${foldEq(`input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""})`, value.charCodeAt(i))})`
3280
+ ).join(" && ");
3030
3281
  stmts.push(
3031
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3032
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3033
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3034
- `${ind(ctx)}const ${vv} = ${vv}_s`
3282
+ ...emitIfFail(ctx, `${pos} + ${len} > input.length || !(${match})`, failBody(ctx, expectedStr, pos)),
3283
+ `${ind(ctx)}const ${vv} = input.slice(${pos}, ${pos} + ${len})`
3035
3284
  );
3036
3285
  } else if (len === 0) {
3037
3286
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3152,6 +3401,7 @@ function emitRegex(def, ctx, pos) {
3152
3401
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3153
3402
  }
3154
3403
  }
3404
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3155
3405
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3156
3406
  const key = `${def.source}/${flags}`;
3157
3407
  let rName = ctx.regexMap.get(key);
@@ -3216,6 +3466,7 @@ function emitSeqValues(def, ctx, pos) {
3216
3466
  }
3217
3467
  function emitSeq(def, ctx, pos) {
3218
3468
  const { stmts, endVar, valueVars } = emitSeqValues(def, ctx, pos);
3469
+ if (def.valueUnused) return { stmts, valueVar: "undefined", endVar };
3219
3470
  const arrV = v(ctx, "_arr");
3220
3471
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3221
3472
  return { stmts, valueVar: arrV, endVar };
@@ -3422,7 +3673,8 @@ function emitFirstMatch(def, ctx, pos) {
3422
3673
  const autoNot = def.autoNot[i];
3423
3674
  const atStart = failsAtStart(p);
3424
3675
  const staticFx = armStaticExpected(ctx, p);
3425
- const fsGuard = needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3676
+ const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3677
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3426
3678
  let gateCond = null;
3427
3679
  if (gate) {
3428
3680
  const gateIdx = ctx.mapFns.length;
@@ -3513,19 +3765,18 @@ function emitLiteralCondition(litVal, pos) {
3513
3765
  return checks.join(" && ");
3514
3766
  }
3515
3767
  function emitMany(def, ctx, pos) {
3768
+ const wantValue = !def.valueUnused;
3516
3769
  const arrV = v(ctx, "_arr");
3517
3770
  const curV = v(ctx, "_cur");
3518
3771
  const stmts = [
3519
- `${ind(ctx)}const ${arrV} = []`,
3772
+ ...wantValue ? [`${ind(ctx)}const ${arrV} = []`] : [],
3520
3773
  `${ind(ctx)}let ${curV} = ${pos}`
3521
3774
  ];
3522
3775
  if (def.min === 1) {
3523
3776
  const firstR = emit(def.parser, ctx, curV);
3524
3777
  stmts.push(...firstR.stmts);
3525
- stmts.push(
3526
- `${ind(ctx)}${arrV}.push(${firstR.valueVar})`,
3527
- `${ind(ctx)}${curV} = ${firstR.endVar}`
3528
- );
3778
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
3779
+ stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
3529
3780
  }
3530
3781
  stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
3531
3782
  ctx.indent++;
@@ -3556,13 +3807,13 @@ function emitMany(def, ctx, pos) {
3556
3807
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
3557
3808
  stmts.push(
3558
3809
  ...iterStmts,
3559
- `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`,
3560
- `${ind(ctx)}${arrV}.push(${iterVal})`,
3561
- `${ind(ctx)}${curV} = ${iterEnd}`
3810
+ `${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`
3562
3811
  );
3812
+ if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
3813
+ stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
3563
3814
  ctx.indent--;
3564
3815
  stmts.push(`${ind(ctx)}}`);
3565
- return { stmts, valueVar: arrV, endVar: curV };
3816
+ return { stmts, valueVar: wantValue ? arrV : "undefined", endVar: curV };
3566
3817
  }
3567
3818
  function emitOptional(def, ctx, pos) {
3568
3819
  const valV = v(ctx, "_opt");
@@ -3731,15 +3982,19 @@ function emitNode(def, ctx, pos) {
3731
3982
  ctx.buildSrcs.push(def.buildSrc ?? null);
3732
3983
  }
3733
3984
  const i = ind(ctx);
3734
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3735
- const clonesState = structural || buildReadsState(def);
3985
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
3986
+ const clonesState = !structural && buildReadsState(def);
3736
3987
  const chV = v(ctx, "_ch");
3737
3988
  const rawV = v(ctx, "_raw");
3738
- const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
3989
+ const capTLv = structural ? v(ctx, "_ctl") : null;
3990
+ const capSTv = structural ? v(ctx, "_cst") : null;
3991
+ const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
3739
3992
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3740
3993
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3741
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3742
- const innerTl = capturesTrivia ? tlV : "undefined";
3994
+ if (structural) ctx.needsHostReads = true;
3995
+ const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
3996
+ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3997
+ const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
3743
3998
  const stmts = [
3744
3999
  allocStmt,
3745
4000
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3750,7 +4005,10 @@ function emitNode(def, ctx, pos) {
3750
4005
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3751
4006
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3752
4007
  let stV = "undefined";
3753
- if (clonesState) {
4008
+ if (structural) {
4009
+ stV = v(ctx, "_nst");
4010
+ stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4011
+ } else if (clonesState) {
3754
4012
  stV = v(ctx, "_nst");
3755
4013
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3756
4014
  }
@@ -4172,6 +4430,7 @@ function ruleDependencies(ruleMap) {
4172
4430
  return deps;
4173
4431
  }
4174
4432
  function compile(combinator, mapFnSources) {
4433
+ markUnusedValues(combinator);
4175
4434
  const ctx = {
4176
4435
  vars: 0,
4177
4436
  indent: 1,
@@ -4185,7 +4444,6 @@ function compile(combinator, mapFnSources) {
4185
4444
  buildFns: [],
4186
4445
  buildSrcs: [],
4187
4446
  runtimeParsers: [],
4188
- needsCollator: false,
4189
4447
  namedParsers: /* @__PURE__ */ new Map(),
4190
4448
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4191
4449
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4194,18 +4452,18 @@ function compile(combinator, mapFnSources) {
4194
4452
  lazyUsage: analyzeLazyUsage(combinator)
4195
4453
  };
4196
4454
  const r = emit(combinator, ctx, "_pos");
4197
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4198
- ` : "";
4199
4455
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4200
4456
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4457
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4201
4458
  const source = [
4202
4459
  ...emptyTlDecls,
4460
+ ...hostReadsDecls,
4203
4461
  ...ctx.regexDecls,
4204
4462
  ...ctx.expectedDecls,
4205
4463
  "",
4206
4464
  ...namedPrelude,
4207
4465
  ctx.namedFnDecls.join("\n\n"),
4208
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4466
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4209
4467
  ` let pos = _pos`,
4210
4468
  ...r.stmts,
4211
4469
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4213,9 +4471,9 @@ function compile(combinator, mapFnSources) {
4213
4471
  ].join("\n");
4214
4472
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4215
4473
  ...emptyTlDecls,
4474
+ ...hostReadsDecls,
4216
4475
  ...ctx.regexDecls,
4217
4476
  ...ctx.expectedDecls,
4218
- collatorDecl,
4219
4477
  ...namedPrelude,
4220
4478
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4221
4479
  `let pos = _pos`,
@@ -4232,7 +4490,7 @@ function compile(combinator, mapFnSources) {
4232
4490
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4233
4491
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4234
4492
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4235
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4493
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4236
4494
  return {
4237
4495
  source,
4238
4496
  inlineExpression,
@@ -4254,7 +4512,16 @@ function compile(combinator, mapFnSources) {
4254
4512
  }
4255
4513
  };
4256
4514
  }
4515
+ var RULE_NAME_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
4516
+ function assertRuleName(name) {
4517
+ if (!RULE_NAME_RE.test(name)) {
4518
+ throw new Error(
4519
+ `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).`
4520
+ );
4521
+ }
4522
+ }
4257
4523
  function compileRuleMap(ruleMap) {
4524
+ for (const [, rule] of ruleMap) markUnusedValues(rule);
4258
4525
  const ctx = {
4259
4526
  vars: 0,
4260
4527
  indent: 1,
@@ -4268,7 +4535,6 @@ function compileRuleMap(ruleMap) {
4268
4535
  buildFns: [],
4269
4536
  buildSrcs: [],
4270
4537
  runtimeParsers: [],
4271
- needsCollator: false,
4272
4538
  namedParsers: /* @__PURE__ */ new Map(),
4273
4539
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4274
4540
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4279,15 +4545,14 @@ function compileRuleMap(ruleMap) {
4279
4545
  const seenNames = /* @__PURE__ */ new Map();
4280
4546
  const ruleNames = /* @__PURE__ */ new Map();
4281
4547
  for (const [key, rule] of ruleMap) {
4282
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4548
+ assertRuleName(key);
4549
+ const base = `_r_${key}`;
4283
4550
  const n = seenNames.get(base) ?? 0;
4284
4551
  seenNames.set(base, n + 1);
4285
4552
  ruleNames.set(rule, n === 0 ? base : `${base}$${n}`);
4286
4553
  }
4287
4554
  ctx.ruleNames = ruleNames;
4288
4555
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4289
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4290
- ` : "";
4291
4556
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
4292
4557
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
4293
4558
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -4299,9 +4564,9 @@ function compileRuleMap(ruleMap) {
4299
4564
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4300
4565
  const hoistedDecls = [
4301
4566
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
4567
+ ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
4302
4568
  ...ctx.regexDecls.map((d) => ` ${d}`),
4303
4569
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4304
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4305
4570
  mfDecl,
4306
4571
  buildDecl,
4307
4572
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4330,6 +4595,7 @@ function compileRuleMap(ruleMap) {
4330
4595
  }
4331
4596
  function compileLinkable(ruleMapArg, ns) {
4332
4597
  if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
4598
+ for (const [, rule] of ruleMapArg) markUnusedValues(rule);
4333
4599
  const ruleMap = ruleMapArg.filter(([, val]) => {
4334
4600
  const d = val._def;
4335
4601
  if (d.tag !== "lazy") return true;
@@ -4353,20 +4619,21 @@ function compileLinkable(ruleMapArg, ns) {
4353
4619
  buildFns: [],
4354
4620
  buildSrcs: [],
4355
4621
  runtimeParsers: [],
4356
- needsCollator: false,
4357
4622
  namedParsers: /* @__PURE__ */ new Map(),
4358
4623
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4359
4624
  triviaFnNames: /* @__PURE__ */ new Map(),
4360
4625
  namedFnDecls: [],
4361
4626
  capturing: ruleMap.some(([, rule]) => hasNodeDef(rule)),
4362
4627
  lazyUsage: analyzeLazyUsageMulti(ruleMap.map(([, rule]) => rule)),
4363
- ns
4628
+ ns,
4629
+ deferFirstSetRefs: true
4364
4630
  };
4365
4631
  const seen = /* @__PURE__ */ new Map();
4366
4632
  const ruleNames = /* @__PURE__ */ new Map();
4367
4633
  const fnNameToKey = /* @__PURE__ */ new Map();
4368
4634
  for (const [key, rule] of ruleMap) {
4369
- const base = `_r_${key.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4635
+ assertRuleName(key);
4636
+ const base = `_r_${key}`;
4370
4637
  const n = seen.get(base) ?? 0;
4371
4638
  seen.set(base, n + 1);
4372
4639
  const fn = n === 0 ? base : `${base}$${n}`;
@@ -4390,7 +4657,8 @@ function compileLinkable(ruleMapArg, ns) {
4390
4657
  if (resolved === void 0) {
4391
4658
  const name = p._ruleName;
4392
4659
  if (name && !ctx.namedParsers.has(p)) {
4393
- const fn = `_r_${name.replace(/[^A-Za-z0-9_$]/g, "_")}`;
4660
+ assertRuleName(name);
4661
+ const fn = `_r_${name}`;
4394
4662
  ruleNames.set(p, fn);
4395
4663
  ctx.namedParsers.set(p, fn);
4396
4664
  }
@@ -4459,20 +4727,34 @@ function compileLinkable(ruleMapArg, ns) {
4459
4727
  `}`
4460
4728
  ].join("\n"));
4461
4729
  }
4730
+ const firstSets = /* @__PURE__ */ new Map();
4731
+ for (const [key, rule] of ruleMap) {
4732
+ let resolved = rule;
4733
+ const d = rule._def;
4734
+ if (d.tag === "lazy") {
4735
+ try {
4736
+ resolved = d.thunk();
4737
+ } catch {
4738
+ resolved = rule;
4739
+ }
4740
+ }
4741
+ firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
4742
+ }
4462
4743
  return {
4463
4744
  ns,
4464
4745
  keys: perEntry.map((e) => e.key),
4465
4746
  prelude,
4466
4747
  ruleFns,
4467
4748
  wrappers,
4749
+ firstSets,
4468
4750
  deps: ruleDependencies(ruleMap),
4469
4751
  needsEmptyTl: !!ctx.needsEmptyTl,
4470
- needsCollator: ctx.needsCollator,
4752
+ needsHostReads: !!ctx.needsHostReads,
4471
4753
  mfFns: mfSrcs ? [] : ctx.mapFns,
4472
4754
  buildFns: buildSrcs ? [] : ctx.buildFns
4473
4755
  };
4474
4756
  }
4475
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4757
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4476
4758
  const bodyLines = [
4477
4759
  ` let pos = _pos`,
4478
4760
  ...r.stmts,
@@ -4486,14 +4768,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4486
4768
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4487
4769
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4488
4770
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4489
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4771
+ const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
4772
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl;
4490
4773
  if (!needsWrapper) return innerFn;
4491
4774
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4492
4775
  const hoistedDecls = [
4493
4776
  emptyTlDecl,
4777
+ hostReadsDecl,
4494
4778
  ...ctx.regexDecls.map((d) => ` ${d}`),
4495
4779
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4496
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4497
4780
  mfDecl,
4498
4781
  buildDecl,
4499
4782
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4521,20 +4804,36 @@ function fusedBody(pieces) {
4521
4804
  }
4522
4805
  const contributing = new Set(winner.values());
4523
4806
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4524
- const needsCollator = [...contributing].some((p) => p.needsCollator);
4807
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4525
4808
  const lines = [
4526
4809
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4527
4810
  "const _pfFail = {}",
4528
4811
  "let _pfEnd",
4529
4812
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4530
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4813
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4531
4814
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4532
4815
  ...[...contributing].flatMap((p) => p.prelude),
4533
4816
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4534
4817
  ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4535
4818
  ];
4536
4819
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4537
- const body = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4820
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4821
+ const finalFS = /* @__PURE__ */ new Map();
4822
+ for (const [k, p] of winner) {
4823
+ const fs2 = p.firstSets?.get(k);
4824
+ if (fs2) finalFS.set(k, fs2);
4825
+ }
4826
+ const body = rawBody.replace(
4827
+ // Rule name + code-point var are both JS identifiers (rule names are validated
4828
+ // at compile time — see assertRuleName in codegen), so an identifier class
4829
+ // matches every well-formed placeholder.
4830
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4831
+ (_m, name, codevar) => {
4832
+ const fs2 = finalFS.get(name);
4833
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4834
+ return `(${firstSetCond(codevar, fs2)})`;
4835
+ }
4836
+ );
4538
4837
  const env = {};
4539
4838
  for (const p of contributing) {
4540
4839
  if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
@@ -5157,7 +5456,7 @@ var plugin_default = createUnplugin((opts = {}) => ({
5157
5456
  if (!code.includes("parseman")) return null;
5158
5457
  if (!code.includes("macro")) return null;
5159
5458
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
5160
- const result = transformMacro(code, id, moduleAliases);
5459
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
5161
5460
  if (result?.warnings.length) {
5162
5461
  for (const w of result.warnings) {
5163
5462
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -5239,7 +5538,7 @@ function findCarriedPiecesLiteral(root) {
5239
5538
  visit(root);
5240
5539
  return found;
5241
5540
  }
5242
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
5541
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5243
5542
  let result;
5244
5543
  try {
5245
5544
  result = parseSync(id, code);
@@ -5278,6 +5577,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5278
5577
  const scope = /* @__PURE__ */ new Map();
5279
5578
  const replacements = [];
5280
5579
  const warnings = [];
5580
+ beginLoweringCapture();
5281
5581
  let anyUnresolved = false;
5282
5582
  let ruleMapHolderCounter = 0;
5283
5583
  const lineOf = (pos) => {
@@ -5314,7 +5614,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5314
5614
  const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5315
5615
  const serializePieces = (p) => {
5316
5616
  const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5317
- 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: [] }`;
5617
+ 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: [] }`;
5318
5618
  };
5319
5619
  const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5320
5620
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
@@ -5585,6 +5885,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5585
5885
  for (const { start, end, replacement } of [...replacements].sort((a, b) => b.start - a.start)) {
5586
5886
  ms.overwrite(start, end, replacement);
5587
5887
  }
5888
+ const unlowered = endLoweringCapture();
5889
+ if (warnUnloweredRegex) {
5890
+ for (const src of unlowered) {
5891
+ warnings.push(`${id}: regex ${src} did not lower to a fast charCodeAt scan (RegExp.exec fallback)`);
5892
+ }
5893
+ }
5588
5894
  return {
5589
5895
  code: ms.toString(),
5590
5896
  map: ms.generateMap({ hires: true }),