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.
@@ -333,12 +333,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
333
333
  }
334
334
 
335
335
  // src/combinators/literal.ts
336
- var _collatorCache = null;
337
- function collator() {
338
- if (_collatorCache === null) {
339
- _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;
340
344
  }
341
- return _collatorCache;
345
+ return true;
342
346
  }
343
347
  function literal(value, opts = {}) {
344
348
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -353,8 +357,9 @@ function literal(value, opts = {}) {
353
357
  const lower = value.toLowerCase();
354
358
  const firstUpper = upper.codePointAt(0);
355
359
  const firstLower = lower.codePointAt(0);
356
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
357
- { 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 }
358
363
  ] } : firstSet;
359
364
  }
360
365
  return {
@@ -367,7 +372,7 @@ function literal(value, opts = {}) {
367
372
  return failAt(ctx, [JSON.stringify(value)], pos);
368
373
  }
369
374
  const slice = input.slice(pos, end);
370
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
375
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
371
376
  if (matched) {
372
377
  const span = { start: pos, end };
373
378
  const leaf = { _tag: "leaf", value: slice, span };
@@ -1596,8 +1601,8 @@ function classToRanges(cls) {
1596
1601
  return parseClassRanges(body);
1597
1602
  }
1598
1603
  var foldEq = (cVar, cp) => {
1599
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1600
- 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}`;
1601
1606
  return `${cVar} === ${cp}`;
1602
1607
  };
1603
1608
  function literalCodePoints(frag) {
@@ -1848,6 +1853,12 @@ function firstSetsDisjoint(a, b) {
1848
1853
  return classDisjoint(a, b);
1849
1854
  }
1850
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
+ }
1851
1862
  if (shape.kind !== "seq") return null;
1852
1863
  const out = [];
1853
1864
  for (const p of shape.parts) {
@@ -2269,12 +2280,34 @@ function parseScanShapeCore(source) {
2269
2280
  }
2270
2281
  function scanShapeFromRegex(source, flags) {
2271
2282
  if (/[msu]/.test(flags)) return null;
2272
- if (/i/.test(flags)) {
2273
- const open = literalCodePoints(source);
2274
- return open ? { kind: "litFold", open } : null;
2275
- }
2283
+ if (/i/.test(flags)) return caseFoldShape(source);
2276
2284
  return parseScanShape(source);
2277
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
+ }
2278
2311
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2279
2312
  var litCond = (base, cps, firstVar) => cps.map(
2280
2313
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2286,6 +2319,25 @@ function stringHardStop(shape) {
2286
2319
  );
2287
2320
  }
2288
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
+ }
2289
2341
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2290
2342
  if (shape.kind === "lookahead") {
2291
2343
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2310,18 +2362,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2310
2362
  const okV = mint("_ok");
2311
2363
  const endV2 = mint("_end");
2312
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
+ }
2313
2388
  if (shape.disjoint) {
2314
- const c0 = codeAt(start, firstChar);
2315
- shape.arms.forEach((arm, k) => {
2316
- const fs2 = shape.firsts[k];
2317
- const cond = fs2.negated ? `!(${classCond(c0, fs2.ranges)})` : `(${classCond(c0, fs2.ranges)})`;
2318
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2319
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2320
- lines.push(...m.setup);
2321
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2322
- 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
+ });
2323
2402
  lines.push(`${ind2}}`);
2324
- });
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
+ }
2325
2416
  } else {
2326
2417
  const lbl = mint("_alt");
2327
2418
  lines.push(`${ind2}${lbl}: {`);
@@ -2809,6 +2900,16 @@ function tryInlineDestructureTransform(src, valueVars) {
2809
2900
  }
2810
2901
 
2811
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
+ }
2812
2913
  function v(ctx, prefix = "_v") {
2813
2914
  return `${prefix}${ctx.vars++}`;
2814
2915
  }
@@ -3207,13 +3308,14 @@ function emitLit(def, ctx, pos) {
3207
3308
  const vv = v(ctx);
3208
3309
  const expectedStr = JSON.stringify(JSON.stringify(value));
3209
3310
  const stmts = [];
3210
- if (caseInsensitive) {
3211
- 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(" && ");
3212
3316
  stmts.push(
3213
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3214
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3215
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3216
- `${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})`
3217
3319
  );
3218
3320
  } else if (len === 0) {
3219
3321
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3334,6 +3436,7 @@ function emitRegex(def, ctx, pos) {
3334
3436
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3335
3437
  }
3336
3438
  }
3439
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3337
3440
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3338
3441
  const key = `${def.source}/${flags}`;
3339
3442
  let rName = ctx.regexMap.get(key);
@@ -3914,15 +4017,19 @@ function emitNode(def, ctx, pos) {
3914
4017
  ctx.buildSrcs.push(def.buildSrc ?? null);
3915
4018
  }
3916
4019
  const i = ind(ctx);
3917
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3918
- const clonesState = structural || buildReadsState(def);
4020
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4021
+ const clonesState = !structural && buildReadsState(def);
3919
4022
  const chV = v(ctx, "_ch");
3920
4023
  const rawV = v(ctx, "_raw");
3921
- 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";
3922
4027
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3923
4028
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3924
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3925
- 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";
3926
4033
  const stmts = [
3927
4034
  allocStmt,
3928
4035
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3933,7 +4040,10 @@ function emitNode(def, ctx, pos) {
3933
4040
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3934
4041
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3935
4042
  let stV = "undefined";
3936
- 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) {
3937
4047
  stV = v(ctx, "_nst");
3938
4048
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3939
4049
  }
@@ -4369,7 +4479,6 @@ function compile(combinator, mapFnSources) {
4369
4479
  buildFns: [],
4370
4480
  buildSrcs: [],
4371
4481
  runtimeParsers: [],
4372
- needsCollator: false,
4373
4482
  namedParsers: /* @__PURE__ */ new Map(),
4374
4483
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4375
4484
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4378,18 +4487,18 @@ function compile(combinator, mapFnSources) {
4378
4487
  lazyUsage: analyzeLazyUsage(combinator)
4379
4488
  };
4380
4489
  const r = emit(combinator, ctx, "_pos");
4381
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4382
- ` : "";
4383
4490
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4384
4491
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4492
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4385
4493
  const source = [
4386
4494
  ...emptyTlDecls,
4495
+ ...hostReadsDecls,
4387
4496
  ...ctx.regexDecls,
4388
4497
  ...ctx.expectedDecls,
4389
4498
  "",
4390
4499
  ...namedPrelude,
4391
4500
  ctx.namedFnDecls.join("\n\n"),
4392
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4501
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4393
4502
  ` let pos = _pos`,
4394
4503
  ...r.stmts,
4395
4504
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4397,9 +4506,9 @@ function compile(combinator, mapFnSources) {
4397
4506
  ].join("\n");
4398
4507
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4399
4508
  ...emptyTlDecls,
4509
+ ...hostReadsDecls,
4400
4510
  ...ctx.regexDecls,
4401
4511
  ...ctx.expectedDecls,
4402
- collatorDecl,
4403
4512
  ...namedPrelude,
4404
4513
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4405
4514
  `let pos = _pos`,
@@ -4416,7 +4525,7 @@ function compile(combinator, mapFnSources) {
4416
4525
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4417
4526
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4418
4527
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4419
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4528
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4420
4529
  return {
4421
4530
  source,
4422
4531
  inlineExpression,
@@ -4461,7 +4570,6 @@ function compileRuleMap(ruleMap) {
4461
4570
  buildFns: [],
4462
4571
  buildSrcs: [],
4463
4572
  runtimeParsers: [],
4464
- needsCollator: false,
4465
4573
  namedParsers: /* @__PURE__ */ new Map(),
4466
4574
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4467
4575
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4480,8 +4588,6 @@ function compileRuleMap(ruleMap) {
4480
4588
  }
4481
4589
  ctx.ruleNames = ruleNames;
4482
4590
  const perEntry = ruleMap.map(([key, rule]) => ({ key, r: emit(rule, ctx, "_pos") }));
4483
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4484
- ` : "";
4485
4591
  const derivedSrcs = ctx.mapFnSrcs.length === ctx.mapFns.length && ctx.mapFnSrcs.every((s) => s !== null) ? ctx.mapFnSrcs : void 0;
4486
4592
  const buildCovered = ctx.buildFns.length === 0 || ctx.buildSrcs.every((s) => s !== null);
4487
4593
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
@@ -4493,9 +4599,9 @@ function compileRuleMap(ruleMap) {
4493
4599
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4494
4600
  const hoistedDecls = [
4495
4601
  ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "",
4602
+ ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "",
4496
4603
  ...ctx.regexDecls.map((d) => ` ${d}`),
4497
4604
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4498
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4499
4605
  mfDecl,
4500
4606
  buildDecl,
4501
4607
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4548,7 +4654,6 @@ function compileLinkable(ruleMapArg, ns) {
4548
4654
  buildFns: [],
4549
4655
  buildSrcs: [],
4550
4656
  runtimeParsers: [],
4551
- needsCollator: false,
4552
4657
  namedParsers: /* @__PURE__ */ new Map(),
4553
4658
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4554
4659
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4679,12 +4784,12 @@ function compileLinkable(ruleMapArg, ns) {
4679
4784
  firstSets,
4680
4785
  deps: ruleDependencies(ruleMap),
4681
4786
  needsEmptyTl: !!ctx.needsEmptyTl,
4682
- needsCollator: ctx.needsCollator,
4787
+ needsHostReads: !!ctx.needsHostReads,
4683
4788
  mfFns: mfSrcs ? [] : ctx.mapFns,
4684
4789
  buildFns: buildSrcs ? [] : ctx.buildFns
4685
4790
  };
4686
4791
  }
4687
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4792
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4688
4793
  const bodyLines = [
4689
4794
  ` let pos = _pos`,
4690
4795
  ...r.stmts,
@@ -4698,14 +4803,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4698
4803
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4699
4804
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4700
4805
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4701
- 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;
4702
4808
  if (!needsWrapper) return innerFn;
4703
4809
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4704
4810
  const hoistedDecls = [
4705
4811
  emptyTlDecl,
4812
+ hostReadsDecl,
4706
4813
  ...ctx.regexDecls.map((d) => ` ${d}`),
4707
4814
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4708
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4709
4815
  mfDecl,
4710
4816
  buildDecl,
4711
4817
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4733,13 +4839,13 @@ function fusedBody(pieces) {
4733
4839
  }
4734
4840
  const contributing = new Set(winner.values());
4735
4841
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4736
- const needsCollator = [...contributing].some((p) => p.needsCollator);
4842
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4737
4843
  const lines = [
4738
4844
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4739
4845
  "const _pfFail = {}",
4740
4846
  "let _pfEnd",
4741
4847
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4742
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
4848
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4743
4849
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4744
4850
  ...[...contributing].flatMap((p) => p.prelude),
4745
4851
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -5385,7 +5491,7 @@ var plugin_default = (0, import_unplugin.createUnplugin)((opts = {}) => ({
5385
5491
  if (!code.includes("parseman")) return null;
5386
5492
  if (!code.includes("macro")) return null;
5387
5493
  const moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE, ...opts.moduleAliases ?? []]);
5388
- const result = transformMacro(code, id, moduleAliases);
5494
+ const result = transformMacro(code, id, moduleAliases, opts.warnUnloweredRegex === true);
5389
5495
  if (result?.warnings.length) {
5390
5496
  for (const w of result.warnings) {
5391
5497
  if (typeof this?.warn === "function") this.warn(`[parseman] ${w}`);
@@ -5467,7 +5573,7 @@ function findCarriedPiecesLiteral(root) {
5467
5573
  visit(root);
5468
5574
  return found;
5469
5575
  }
5470
- function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE])) {
5576
+ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5471
5577
  let result;
5472
5578
  try {
5473
5579
  result = (0, import_oxc_parser.parseSync)(id, code);
@@ -5506,6 +5612,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5506
5612
  const scope = /* @__PURE__ */ new Map();
5507
5613
  const replacements = [];
5508
5614
  const warnings = [];
5615
+ beginLoweringCapture();
5509
5616
  let anyUnresolved = false;
5510
5617
  let ruleMapHolderCounter = 0;
5511
5618
  const lineOf = (pos) => {
@@ -5542,7 +5649,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5542
5649
  const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
5543
5650
  const serializePieces = (p) => {
5544
5651
  const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5545
- 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: [] }`;
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: [] }`;
5546
5653
  };
5547
5654
  const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5548
5655
  const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
@@ -5813,6 +5920,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5813
5920
  for (const { start, end, replacement } of [...replacements].sort((a, b) => b.start - a.start)) {
5814
5921
  ms.overwrite(start, end, replacement);
5815
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
+ }
5816
5929
  return {
5817
5930
  code: ms.toString(),
5818
5931
  map: ms.generateMap({ hires: true }),