parseman 0.15.0 → 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.
@@ -2,6 +2,14 @@ import MagicString from 'magic-string';
2
2
  export type ParsecraftPluginOptions = {
3
3
  /** Extra module specifiers to treat as parseman re-exports */
4
4
  moduleAliases?: string[];
5
+ /**
6
+ * Warn when a regex terminal can't LOWER to a fast `charCodeAt` scan and falls
7
+ * back to `RegExp.exec`. **Default `false` (opt-in).** `RegExp.exec` is an
8
+ * accepted, JIT-fast compiled path — most un-lowered regexes are perfectly fine
9
+ * and lowering them often shows no real gain — so this is a diagnostic you turn
10
+ * ON when specifically auditing lowering coverage, not a build-time nag.
11
+ */
12
+ warnUnloweredRegex?: boolean;
5
13
  };
6
14
  declare const _default: import("unplugin").UnpluginInstance<ParsecraftPluginOptions, boolean>;
7
15
  export default _default;
@@ -11,5 +19,5 @@ export type TransformMacroResult = {
11
19
  /** Diagnostics for macro-referencing shapes that fell back to the interpreter. */
12
20
  warnings: string[];
13
21
  };
14
- export declare function transformMacro(code: string, id: string, moduleAliases?: Set<string>): TransformMacroResult | null;
22
+ export declare function transformMacro(code: string, id: string, moduleAliases?: Set<string>, warnUnloweredRegex?: boolean): TransformMacroResult | null;
15
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAuBA,OAAO,WAAW,MAAM,cAAc,CAAA;AAgBtC,MAAM,MAAM,uBAAuB,GAAG;IACpC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;;;AA+KD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;IAC3C,kFAAkF;IAClF,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,CAAA;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,aAAa,cAA6B,GACzC,oBAAoB,GAAG,IAAI,CAshB7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAuBA,OAAO,WAAW,MAAM,cAAc,CAAA;AAgBtC,MAAM,MAAM,uBAAuB,GAAG;IACpC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;;;AA+KD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;IAC3C,kFAAkF;IAClF,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,CAAA;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,aAAa,cAA6B,EAC1C,kBAAkB,UAAQ,GACzB,oBAAoB,GAAG,IAAI,CA8hB7B"}
@@ -298,12 +298,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
298
298
  }
299
299
 
300
300
  // src/combinators/literal.ts
301
- var _collatorCache = null;
302
- function collator() {
303
- if (_collatorCache === null) {
304
- _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;
305
309
  }
306
- return _collatorCache;
310
+ return true;
307
311
  }
308
312
  function literal(value, opts = {}) {
309
313
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -318,8 +322,9 @@ function literal(value, opts = {}) {
318
322
  const lower = value.toLowerCase();
319
323
  const firstUpper = upper.codePointAt(0);
320
324
  const firstLower = lower.codePointAt(0);
321
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
322
- { 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 }
323
328
  ] } : firstSet;
324
329
  }
325
330
  return {
@@ -332,7 +337,7 @@ function literal(value, opts = {}) {
332
337
  return failAt(ctx, [JSON.stringify(value)], pos);
333
338
  }
334
339
  const slice = input.slice(pos, end);
335
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
340
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
336
341
  if (matched) {
337
342
  const span = { start: pos, end };
338
343
  const leaf = { _tag: "leaf", value: slice, span };
@@ -1561,8 +1566,8 @@ function classToRanges(cls) {
1561
1566
  return parseClassRanges(body);
1562
1567
  }
1563
1568
  var foldEq = (cVar, cp) => {
1564
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1565
- 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}`;
1566
1571
  return `${cVar} === ${cp}`;
1567
1572
  };
1568
1573
  function literalCodePoints(frag) {
@@ -1813,6 +1818,12 @@ function firstSetsDisjoint(a, b) {
1813
1818
  return classDisjoint(a, b);
1814
1819
  }
1815
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
+ }
1816
1827
  if (shape.kind !== "seq") return null;
1817
1828
  const out = [];
1818
1829
  for (const p of shape.parts) {
@@ -2234,12 +2245,34 @@ function parseScanShapeCore(source) {
2234
2245
  }
2235
2246
  function scanShapeFromRegex(source, flags) {
2236
2247
  if (/[msu]/.test(flags)) return null;
2237
- if (/i/.test(flags)) {
2238
- const open = literalCodePoints(source);
2239
- return open ? { kind: "litFold", open } : null;
2240
- }
2248
+ if (/i/.test(flags)) return caseFoldShape(source);
2241
2249
  return parseScanShape(source);
2242
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
+ }
2243
2276
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2244
2277
  var litCond = (base, cps, firstVar) => cps.map(
2245
2278
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2251,6 +2284,25 @@ function stringHardStop(shape) {
2251
2284
  );
2252
2285
  }
2253
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
+ }
2254
2306
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2255
2307
  if (shape.kind === "lookahead") {
2256
2308
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2275,18 +2327,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2275
2327
  const okV = mint("_ok");
2276
2328
  const endV2 = mint("_end");
2277
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
+ }
2278
2353
  if (shape.disjoint) {
2279
- const c0 = codeAt(start, firstChar);
2280
- shape.arms.forEach((arm, k) => {
2281
- const fs2 = shape.firsts[k];
2282
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2283
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2284
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2285
- lines.push(...m.setup);
2286
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2287
- 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
+ });
2288
2367
  lines.push(`${ind2}}`);
2289
- });
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
+ }
2290
2381
  } else {
2291
2382
  const lbl = mint("_alt");
2292
2383
  lines.push(`${ind2}${lbl}: {`);
@@ -2774,6 +2865,16 @@ function tryInlineDestructureTransform(src, valueVars) {
2774
2865
  }
2775
2866
 
2776
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
+ }
2777
2878
  function v(ctx, prefix = "_v") {
2778
2879
  return `${prefix}${ctx.vars++}`;
2779
2880
  }
@@ -3172,13 +3273,14 @@ function emitLit(def, ctx, pos) {
3172
3273
  const vv = v(ctx);
3173
3274
  const expectedStr = JSON.stringify(JSON.stringify(value));
3174
3275
  const stmts = [];
3175
- if (caseInsensitive) {
3176
- 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(" && ");
3177
3281
  stmts.push(
3178
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3179
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3180
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3181
- `${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})`
3182
3284
  );
3183
3285
  } else if (len === 0) {
3184
3286
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3299,6 +3401,7 @@ function emitRegex(def, ctx, pos) {
3299
3401
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3300
3402
  }
3301
3403
  }
3404
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3302
3405
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3303
3406
  const key = `${def.source}/${flags}`;
3304
3407
  let rName = ctx.regexMap.get(key);
@@ -3879,15 +3982,19 @@ function emitNode(def, ctx, pos) {
3879
3982
  ctx.buildSrcs.push(def.buildSrc ?? null);
3880
3983
  }
3881
3984
  const i = ind(ctx);
3882
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3883
- const clonesState = structural || buildReadsState(def);
3985
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
3986
+ const clonesState = !structural && buildReadsState(def);
3884
3987
  const chV = v(ctx, "_ch");
3885
3988
  const rawV = v(ctx, "_raw");
3886
- 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";
3887
3992
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3888
3993
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3889
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3890
- 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";
3891
3998
  const stmts = [
3892
3999
  allocStmt,
3893
4000
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3898,7 +4005,10 @@ function emitNode(def, ctx, pos) {
3898
4005
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3899
4006
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3900
4007
  let stV = "undefined";
3901
- 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) {
3902
4012
  stV = v(ctx, "_nst");
3903
4013
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3904
4014
  }
@@ -4334,7 +4444,6 @@ function compile(combinator, mapFnSources) {
4334
4444
  buildFns: [],
4335
4445
  buildSrcs: [],
4336
4446
  runtimeParsers: [],
4337
- needsCollator: false,
4338
4447
  namedParsers: /* @__PURE__ */ new Map(),
4339
4448
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4340
4449
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4343,18 +4452,18 @@ function compile(combinator, mapFnSources) {
4343
4452
  lazyUsage: analyzeLazyUsage(combinator)
4344
4453
  };
4345
4454
  const r = emit(combinator, ctx, "_pos");
4346
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4347
- ` : "";
4348
4455
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4349
4456
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4457
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4350
4458
  const source = [
4351
4459
  ...emptyTlDecls,
4460
+ ...hostReadsDecls,
4352
4461
  ...ctx.regexDecls,
4353
4462
  ...ctx.expectedDecls,
4354
4463
  "",
4355
4464
  ...namedPrelude,
4356
4465
  ctx.namedFnDecls.join("\n\n"),
4357
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4466
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4358
4467
  ` let pos = _pos`,
4359
4468
  ...r.stmts,
4360
4469
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4362,9 +4471,9 @@ function compile(combinator, mapFnSources) {
4362
4471
  ].join("\n");
4363
4472
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4364
4473
  ...emptyTlDecls,
4474
+ ...hostReadsDecls,
4365
4475
  ...ctx.regexDecls,
4366
4476
  ...ctx.expectedDecls,
4367
- collatorDecl,
4368
4477
  ...namedPrelude,
4369
4478
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4370
4479
  `let pos = _pos`,
@@ -4381,7 +4490,7 @@ function compile(combinator, mapFnSources) {
4381
4490
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4382
4491
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4383
4492
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4384
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4493
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4385
4494
  return {
4386
4495
  source,
4387
4496
  inlineExpression,
@@ -4426,7 +4535,6 @@ function compileRuleMap(ruleMap) {
4426
4535
  buildFns: [],
4427
4536
  buildSrcs: [],
4428
4537
  runtimeParsers: [],
4429
- needsCollator: false,
4430
4538
  namedParsers: /* @__PURE__ */ new Map(),
4431
4539
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4432
4540
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4445,8 +4553,6 @@ function compileRuleMap(ruleMap) {
4445
4553
  }
4446
4554
  ctx.ruleNames = ruleNames;
4447
4555
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4448
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4449
- ` : "";
4450
4556
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
4451
4557
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
4452
4558
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -4458,9 +4564,9 @@ function compileRuleMap(ruleMap) {
4458
4564
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4459
4565
  const hoistedDecls = [
4460
4566
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
4567
+ ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
4461
4568
  ...ctx.regexDecls.map((d) => ` ${d}`),
4462
4569
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4463
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4464
4570
  mfDecl,
4465
4571
  buildDecl,
4466
4572
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4513,7 +4619,6 @@ function compileLinkable(ruleMapArg, ns) {
4513
4619
  buildFns: [],
4514
4620
  buildSrcs: [],
4515
4621
  runtimeParsers: [],
4516
- needsCollator: false,
4517
4622
  namedParsers: /* @__PURE__ */ new Map(),
4518
4623
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4519
4624
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4644,12 +4749,12 @@ function compileLinkable(ruleMapArg, ns) {
4644
4749
  firstSets,
4645
4750
  deps: ruleDependencies(ruleMap),
4646
4751
  needsEmptyTl: !!ctx.needsEmptyTl,
4647
- needsCollator: ctx.needsCollator,
4752
+ needsHostReads: !!ctx.needsHostReads,
4648
4753
  mfFns: mfSrcs ? [] : ctx.mapFns,
4649
4754
  buildFns: buildSrcs ? [] : ctx.buildFns
4650
4755
  };
4651
4756
  }
4652
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4757
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4653
4758
  const bodyLines = [
4654
4759
  ` let pos = _pos`,
4655
4760
  ...r.stmts,
@@ -4663,14 +4768,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4663
4768
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4664
4769
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4665
4770
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4666
- 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;
4667
4773
  if (!needsWrapper) return innerFn;
4668
4774
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4669
4775
  const hoistedDecls = [
4670
4776
  emptyTlDecl,
4777
+ hostReadsDecl,
4671
4778
  ...ctx.regexDecls.map((d) => ` ${d}`),
4672
4779
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4673
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4674
4780
  mfDecl,
4675
4781
  buildDecl,
4676
4782
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4698,13 +4804,13 @@ function fusedBody(pieces) {
4698
4804
  }
4699
4805
  const contributing = new Set(winner.values());
4700
4806
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4701
- const needsCollator = [...contributing].some((p) => p.needsCollator);
4807
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4702
4808
  const lines = [
4703
4809
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4704
4810
  "const _pfFail = {}",
4705
4811
  "let _pfEnd",
4706
4812
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4707
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4813
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4708
4814
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4709
4815
  ...[...contributing].flatMap((p) => p.prelude),
4710
4816
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -5350,7 +5456,7 @@ var plugin_default = createUnplugin((opts = {}) => ({
5350
5456
  if (!code.includes("parseman")) return null;
5351
5457
  if (!code.includes("macro")) return null;
5352
5458
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
5353
- const result = transformMacro(code, id, moduleAliases);
5459
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
5354
5460
  if (result?.warnings.length) {
5355
5461
  for (const w of result.warnings) {
5356
5462
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -5432,7 +5538,7 @@ function findCarriedPiecesLiteral(root) {
5432
5538
  visit(root);
5433
5539
  return found;
5434
5540
  }
5435
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
5541
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5436
5542
  let result;
5437
5543
  try {
5438
5544
  result = parseSync(id, code);
@@ -5471,6 +5577,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5471
5577
  const scope = /* @__PURE__ */ new Map();
5472
5578
  const replacements = [];
5473
5579
  const warnings = [];
5580
+ beginLoweringCapture();
5474
5581
  let anyUnresolved = false;
5475
5582
  let ruleMapHolderCounter = 0;
5476
5583
  const lineOf = (pos) => {
@@ -5507,7 +5614,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5507
5614
  const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5508
5615
  const serializePieces = (p) => {
5509
5616
  const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5510
- 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}, 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: [] }`;
5511
5618
  };
5512
5619
  const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5513
5620
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
@@ -5778,6 +5885,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5778
5885
  for (const { start, end, replacement } of [...replacements].sort((a, b) => b.start - a.start)) {
5779
5886
  ms.overwrite(start, end, replacement);
5780
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
+ }
5781
5894
  return {
5782
5895
  code: ms.toString(),
5783
5896
  map: ms.generateMap({ hires: true }),