parseman 0.15.0 → 0.17.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.
package/dist/index.js CHANGED
@@ -84,6 +84,54 @@ function sequenceFirstSet(parsers) {
84
84
  }
85
85
  return fs;
86
86
  }
87
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
88
+ if (seen.has(p)) return any();
89
+ seen.add(p);
90
+ const fs = (c) => firstSetOf(c, seen);
91
+ const d = p._def;
92
+ switch (d.tag) {
93
+ case "literal":
94
+ case "regex":
95
+ case "keywords":
96
+ return p._meta.firstSet;
97
+ // terminals: no refs, cached set is exact
98
+ case "lazy":
99
+ try {
100
+ return fs(d.thunk());
101
+ } catch {
102
+ return any();
103
+ }
104
+ case "choice": {
105
+ let out = empty();
106
+ for (const arm of d.parsers) out = union(out, fs(arm));
107
+ return out;
108
+ }
109
+ case "sequence": {
110
+ let out = empty();
111
+ for (const term of d.parsers) {
112
+ out = union(out, fs(term));
113
+ if (!matchesEmpty(term)) return out;
114
+ }
115
+ return out;
116
+ }
117
+ case "oneOrMore":
118
+ case "many":
119
+ case "optional":
120
+ case "transform":
121
+ case "label":
122
+ case "trivia":
123
+ case "node":
124
+ case "grammar":
125
+ case "expect":
126
+ return fs(d.parser);
127
+ case "sepBy":
128
+ return fs(d.parser);
129
+ case "skip":
130
+ return fs(d.main);
131
+ default:
132
+ return p._meta.firstSet;
133
+ }
134
+ }
87
135
  function mergeRanges(ranges) {
88
136
  if (ranges.length === 0) return [];
89
137
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -261,12 +309,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
261
309
  }
262
310
 
263
311
  // src/combinators/literal.ts
264
- var _collatorCache = null;
265
- function collator() {
266
- if (_collatorCache === null) {
267
- _collatorCache = new Intl.Collator(void 0, { sensitivity: "accent" });
312
+ function asciiFoldEq(a, b) {
313
+ if (a.length !== b.length) return false;
314
+ for (let i = 0; i < a.length; i++) {
315
+ let ca = a.charCodeAt(i);
316
+ let cb = b.charCodeAt(i);
317
+ if (ca >= 65 && ca <= 90) ca += 32;
318
+ if (cb >= 65 && cb <= 90) cb += 32;
319
+ if (ca !== cb) return false;
268
320
  }
269
- return _collatorCache;
321
+ return true;
270
322
  }
271
323
  function literal(value, opts = {}) {
272
324
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -281,8 +333,9 @@ function literal(value, opts = {}) {
281
333
  const lower = value.toLowerCase();
282
334
  const firstUpper = upper.codePointAt(0);
283
335
  const firstLower = lower.codePointAt(0);
284
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
285
- { lo: Math.min(firstLower, firstUpper), hi: Math.max(firstLower, firstUpper) }
336
+ meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
337
+ { lo: firstLower, hi: firstLower },
338
+ { lo: firstUpper, hi: firstUpper }
286
339
  ] } : firstSet;
287
340
  }
288
341
  return {
@@ -295,7 +348,7 @@ function literal(value, opts = {}) {
295
348
  return failAt(ctx, [JSON.stringify(value)], pos);
296
349
  }
297
350
  const slice = input.slice(pos, end);
298
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
351
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
299
352
  if (matched) {
300
353
  const span = { start: pos, end };
301
354
  const leaf = { _tag: "leaf", value: slice, span };
@@ -1629,8 +1682,8 @@ function classToRanges(cls) {
1629
1682
  return parseClassRanges(body);
1630
1683
  }
1631
1684
  var foldEq = (cVar, cp) => {
1632
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1633
- if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1685
+ if (cp >= 65 && cp <= 90) return `(${cVar} | 32) === ${cp + 32}`;
1686
+ if (cp >= 97 && cp <= 122) return `(${cVar} | 32) === ${cp}`;
1634
1687
  return `${cVar} === ${cp}`;
1635
1688
  };
1636
1689
  function literalCodePoints(frag) {
@@ -1881,6 +1934,12 @@ function firstSetsDisjoint(a, b) {
1881
1934
  return classDisjoint(a, b);
1882
1935
  }
1883
1936
  function fixedClassSeq(shape) {
1937
+ if (shape.kind === "litFold") {
1938
+ return shape.open.map((cp) => {
1939
+ const other = cp >= 65 && cp <= 90 ? cp + 32 : cp >= 97 && cp <= 122 ? cp - 32 : cp;
1940
+ 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 };
1941
+ });
1942
+ }
1884
1943
  if (shape.kind !== "seq") return null;
1885
1944
  const out = [];
1886
1945
  for (const p of shape.parts) {
@@ -2302,12 +2361,34 @@ function parseScanShapeCore(source) {
2302
2361
  }
2303
2362
  function scanShapeFromRegex(source, flags) {
2304
2363
  if (/[msu]/.test(flags)) return null;
2305
- if (/i/.test(flags)) {
2306
- const open = literalCodePoints(source);
2307
- return open ? { kind: "litFold", open } : null;
2308
- }
2364
+ if (/i/.test(flags)) return caseFoldShape(source);
2309
2365
  return parseScanShape(source);
2310
2366
  }
2367
+ function caseFoldShape(source) {
2368
+ const la = stripTrailingLookahead(source);
2369
+ if (la) {
2370
+ const inner = caseFoldLiteralOrAlt(la.base);
2371
+ if (!inner) return null;
2372
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2373
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2374
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2375
+ }
2376
+ return caseFoldLiteralOrAlt(source);
2377
+ }
2378
+ function caseFoldLiteralOrAlt(source) {
2379
+ const asciiOnly = (cps) => cps.every((cp) => cp <= 127);
2380
+ const open = literalCodePoints(source);
2381
+ if (open) return asciiOnly(open) ? { kind: "litFold", open } : null;
2382
+ const arms = splitTopLevelAlternation(source);
2383
+ if (!arms || arms.length < 2) return null;
2384
+ const litArms = [];
2385
+ for (const arm of arms) {
2386
+ const cp = literalCodePoints(arm);
2387
+ if (!cp || !asciiOnly(cp)) return null;
2388
+ litArms.push({ kind: "litFold", open: cp });
2389
+ }
2390
+ return { kind: "alt", arms: litArms, disjoint: false, firsts: litArms.map(() => null) };
2391
+ }
2311
2392
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2312
2393
  var litCond = (base, cps, firstVar) => cps.map(
2313
2394
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2319,6 +2400,25 @@ function stringHardStop(shape) {
2319
2400
  );
2320
2401
  }
2321
2402
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2403
+ var ALT_SWITCH_RANGE_LIMIT = 4;
2404
+ var ALT_SWITCH_MAX_CASES = 48;
2405
+ var ALT_SWITCH_MIN_CASES = 3;
2406
+ function planAltSwitch(firsts) {
2407
+ const cases = [];
2408
+ let total = 0;
2409
+ for (const fs of firsts) {
2410
+ if (!fs || fs.negated) return { kind: "if" };
2411
+ const pts = [];
2412
+ for (const [lo, hi] of fs.ranges) {
2413
+ if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
2414
+ for (let cp = lo; cp <= hi; cp++) pts.push(cp);
2415
+ }
2416
+ total += pts.length;
2417
+ if (total > ALT_SWITCH_MAX_CASES) return { kind: "if" };
2418
+ cases.push(pts);
2419
+ }
2420
+ return total >= ALT_SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2421
+ }
2322
2422
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2323
2423
  if (shape.kind === "lookahead") {
2324
2424
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2343,18 +2443,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2343
2443
  const okV = mint("_ok");
2344
2444
  const endV2 = mint("_end");
2345
2445
  const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2446
+ if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
2447
+ const groups = /* @__PURE__ */ new Map();
2448
+ for (const a of shape.arms) {
2449
+ const open = a.open;
2450
+ const f = open[0];
2451
+ const lower = f >= 65 && f <= 90 ? f + 32 : f;
2452
+ (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
2453
+ }
2454
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2455
+ for (const [lower, arms] of groups) {
2456
+ const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
2457
+ lines.push(`${ind2} ${labels} {`);
2458
+ for (const open of arms) {
2459
+ const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
2460
+ const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
2461
+ lines.push(`${ind2} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
2462
+ }
2463
+ lines.push(`${ind2} break`);
2464
+ lines.push(`${ind2} }`);
2465
+ }
2466
+ lines.push(`${ind2}}`);
2467
+ return { setup: lines, ok: okV, end: endV2 };
2468
+ }
2346
2469
  if (shape.disjoint) {
2347
- const c0 = codeAt(start, firstChar);
2348
- shape.arms.forEach((arm, k) => {
2349
- const fs = shape.firsts[k];
2350
- const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2351
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2352
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2353
- lines.push(...m.setup);
2354
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2355
- lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2470
+ const plan = planAltSwitch(shape.firsts);
2471
+ if (plan.kind === "switch") {
2472
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2473
+ shape.arms.forEach((arm, k) => {
2474
+ const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
2475
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2476
+ lines.push(`${ind2} ${labels} {`);
2477
+ lines.push(...m.setup);
2478
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2479
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2480
+ lines.push(`${ind2} break`);
2481
+ lines.push(`${ind2} }`);
2482
+ });
2356
2483
  lines.push(`${ind2}}`);
2357
- });
2484
+ } else {
2485
+ const c0 = codeAt(start, firstChar);
2486
+ shape.arms.forEach((arm, k) => {
2487
+ const fs = shape.firsts[k];
2488
+ const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2489
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2490
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2491
+ lines.push(...m.setup);
2492
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2493
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2494
+ lines.push(`${ind2}}`);
2495
+ });
2496
+ }
2358
2497
  } else {
2359
2498
  const lbl = mint("_alt");
2360
2499
  lines.push(`${ind2}${lbl}: {`);
@@ -2842,6 +2981,8 @@ function tryInlineDestructureTransform(src, valueVars) {
2842
2981
  }
2843
2982
 
2844
2983
  // src/compiler/codegen.ts
2984
+ 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 }";
2985
+ var _loweringSink = null;
2845
2986
  function v(ctx, prefix = "_v") {
2846
2987
  return `${prefix}${ctx.vars++}`;
2847
2988
  }
@@ -2931,16 +3072,12 @@ function namedFnPrelude() {
2931
3072
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
2932
3073
  ctx.namedFnDecls.push([
2933
3074
  `function ${fnName}(input, _pos, _ctx) {`,
2934
- ` let _pfok = false, _pfv, _pfe = _pos`,
2935
3075
  ` _pfail: {`,
2936
3076
  ...reindentStmts(bodyStmts, 2),
2937
- ` _pfv = ${valueVar}`,
2938
- ` _pfe = ${endVar}`,
2939
- ` _pfok = true`,
3077
+ ` ${NAMED_FN_END} = ${endVar}`,
3078
+ ` return ${valueVar}`,
2940
3079
  ` }`,
2941
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
2942
- ` ${NAMED_FN_END} = _pfe`,
2943
- ` return _pfv`,
3080
+ ` return ${NAMED_FN_FAIL}`,
2944
3081
  `}`
2945
3082
  ].join("\n"));
2946
3083
  }
@@ -3240,13 +3377,14 @@ function emitLit(def, ctx, pos) {
3240
3377
  const vv = v(ctx);
3241
3378
  const expectedStr = JSON.stringify(JSON.stringify(value));
3242
3379
  const stmts = [];
3243
- if (caseInsensitive) {
3244
- ctx.needsCollator = true;
3380
+ if (caseInsensitive && len > 0) {
3381
+ const match = Array.from(
3382
+ { length: len },
3383
+ (_, i) => `(${foldEq(`input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""})`, value.charCodeAt(i))})`
3384
+ ).join(" && ");
3245
3385
  stmts.push(
3246
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3247
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3248
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3249
- `${ind(ctx)}const ${vv} = ${vv}_s`
3386
+ ...emitIfFail(ctx, `${pos} + ${len} > input.length || !(${match})`, failBody(ctx, expectedStr, pos)),
3387
+ `${ind(ctx)}const ${vv} = input.slice(${pos}, ${pos} + ${len})`
3250
3388
  );
3251
3389
  } else if (len === 0) {
3252
3390
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3367,6 +3505,7 @@ function emitRegex(def, ctx, pos) {
3367
3505
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3368
3506
  }
3369
3507
  }
3508
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3370
3509
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3371
3510
  const key = `${def.source}/${flags}`;
3372
3511
  let rName = ctx.regexMap.get(key);
@@ -3639,11 +3778,16 @@ function emitFirstMatch(def, ctx, pos) {
3639
3778
  const atStart = failsAtStart(p);
3640
3779
  const staticFx = armStaticExpected(ctx, p);
3641
3780
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3642
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3781
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3782
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3783
+ const deep = firstSetOf(p);
3784
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3785
+ })();
3643
3786
  let gateCond = null;
3644
3787
  if (gate) {
3645
3788
  const gateIdx = ctx.mapFns.length;
3646
3789
  ctx.mapFns.push(gate);
3790
+ ctx.mapFnSrcs.push(null);
3647
3791
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3648
3792
  }
3649
3793
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3702,13 +3846,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3702
3846
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3703
3847
  return emitFirstMatch(def, ctx, pos);
3704
3848
  }
3849
+ function pushMapFn(ctx, fn, src) {
3850
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3851
+ const hit = ctx.mapFnSrcs.indexOf(src);
3852
+ if (hit !== -1) return hit;
3853
+ }
3854
+ const idx = ctx.mapFns.length;
3855
+ ctx.mapFns.push(fn);
3856
+ ctx.mapFnSrcs.push(src);
3857
+ return idx;
3858
+ }
3705
3859
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3706
3860
  const def = p._def;
3707
3861
  if (def.tag === "transform") {
3708
3862
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3709
- const fnIdx = ctx.mapFns.length;
3710
- ctx.mapFns.push(def.fn);
3711
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3863
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3712
3864
  const vv = v(ctx);
3713
3865
  return {
3714
3866
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3891,7 +4043,7 @@ function emitScanTo(def, ctx, pos) {
3891
4043
  `${ind(ctx)}while (${curV} < input.length) {`
3892
4044
  ];
3893
4045
  ctx.indent++;
3894
- const probeCtx = { ...ctx, capturing: false };
4046
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3895
4047
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3896
4048
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3897
4049
  if (def.skip.length > 0) {
@@ -3947,15 +4099,19 @@ function emitNode(def, ctx, pos) {
3947
4099
  ctx.buildSrcs.push(def.buildSrc ?? null);
3948
4100
  }
3949
4101
  const i = ind(ctx);
3950
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
3951
- const clonesState = structural || buildReadsState(def);
4102
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4103
+ const clonesState = !structural && buildReadsState(def);
3952
4104
  const chV = v(ctx, "_ch");
3953
4105
  const rawV = v(ctx, "_raw");
3954
- const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
4106
+ const capTLv = structural ? v(ctx, "_ctl") : null;
4107
+ const capSTv = structural ? v(ctx, "_cst") : null;
4108
+ const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
3955
4109
  if (!capturesTrivia) ctx.needsEmptyTl = true;
3956
4110
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
3957
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
3958
- const innerTl = capturesTrivia ? tlV : "undefined";
4111
+ if (structural) ctx.needsHostReads = true;
4112
+ const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4113
+ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4114
+ const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
3959
4115
  const stmts = [
3960
4116
  allocStmt,
3961
4117
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -3966,7 +4122,10 @@ function emitNode(def, ctx, pos) {
3966
4122
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
3967
4123
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
3968
4124
  let stV = "undefined";
3969
- if (clonesState) {
4125
+ if (structural) {
4126
+ stV = v(ctx, "_nst");
4127
+ stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4128
+ } else if (clonesState) {
3970
4129
  stV = v(ctx, "_nst");
3971
4130
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
3972
4131
  }
@@ -4075,6 +4234,32 @@ function emitExpect(def, ctx, pos) {
4075
4234
  return { stmts, valueVar: valVar, endVar };
4076
4235
  }
4077
4236
  function emit(p, ctx, pos) {
4237
+ const usage = ctx.lazyUsage;
4238
+ if (usage && !ctx.noHoist && !ctx.capAsTrivia && !ctx.ruleNames?.has(p) && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && // Only hoist subtrees big enough that de-duplicating them beats the call
4239
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4240
+ // faster than it calls and barely moves the byte count; the size explosion
4241
+ // is all in LARGE choices/sequences referenced from many positions.
4242
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4243
+ const existing = ctx.namedParsers.get(p);
4244
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4245
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4246
+ ctx.namedParsers.set(p, fnName);
4247
+ const savedIndent = ctx.indent;
4248
+ const savedFailLabel = ctx.failLabel;
4249
+ const savedRecord = ctx.recordFail;
4250
+ ctx.indent = 1;
4251
+ ctx.failLabel = "_pfail";
4252
+ ctx.recordFail = true;
4253
+ const r = emitDispatch(p, ctx, "_pos");
4254
+ ctx.indent = savedIndent;
4255
+ ctx.failLabel = savedFailLabel;
4256
+ ctx.recordFail = savedRecord;
4257
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4258
+ return emitNamedFnCall(ctx, fnName, pos);
4259
+ }
4260
+ return emitDispatch(p, ctx, pos);
4261
+ }
4262
+ function emitDispatch(p, ctx, pos) {
4078
4263
  const def = p._def;
4079
4264
  switch (def.tag) {
4080
4265
  case "literal":
@@ -4120,9 +4305,7 @@ function emit(p, ctx, pos) {
4120
4305
  };
4121
4306
  }
4122
4307
  }
4123
- const fnIdx = ctx.mapFns.length;
4124
- ctx.mapFns.push(def.fn);
4125
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4308
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4126
4309
  const mv = v(ctx, "_mapped");
4127
4310
  return {
4128
4311
  stmts: [
@@ -4187,6 +4370,7 @@ function emit(p, ctx, pos) {
4187
4370
  case "guard": {
4188
4371
  const fnIdx = ctx.mapFns.length;
4189
4372
  ctx.mapFns.push(def.predicate);
4373
+ ctx.mapFnSrcs.push(null);
4190
4374
  const vv = v(ctx);
4191
4375
  return {
4192
4376
  stmts: [
@@ -4201,6 +4385,7 @@ function emit(p, ctx, pos) {
4201
4385
  const evIdx = ctx.mapFns.length;
4202
4386
  const extra = def.extra;
4203
4387
  ctx.mapFns.push((() => extra));
4388
+ ctx.mapFnSrcs.push(null);
4204
4389
  const innerParser = def.parser;
4205
4390
  if (!ctx.namedParsers.has(innerParser)) {
4206
4391
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4320,9 +4505,58 @@ function childrenOf(def) {
4320
4505
  return [];
4321
4506
  }
4322
4507
  }
4508
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4509
+ "sequence",
4510
+ "choice",
4511
+ "many",
4512
+ "oneOrMore",
4513
+ "optional",
4514
+ "sepBy",
4515
+ "transform",
4516
+ "node",
4517
+ "not"
4518
+ ]);
4519
+ function isHoistableTag(tag) {
4520
+ return HOISTABLE_TAGS.has(tag);
4521
+ }
4522
+ var HOIST_MIN_SUBTREE = 3;
4323
4523
  function analyzeLazyUsage(root) {
4324
4524
  return analyzeLazyUsageMulti([root]);
4325
4525
  }
4526
+ function subtreeSizes(roots) {
4527
+ const sizes = /* @__PURE__ */ new Map();
4528
+ const sz = (p) => {
4529
+ const cached = sizes.get(p);
4530
+ if (cached !== void 0) return cached;
4531
+ const def = p._def;
4532
+ let s = 1;
4533
+ if (def.tag !== "lazy") {
4534
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4535
+ }
4536
+ sizes.set(p, s);
4537
+ return s;
4538
+ };
4539
+ const visitedLazy = /* @__PURE__ */ new Set();
4540
+ const visit = (p) => {
4541
+ const def = p._def;
4542
+ if (def.tag === "lazy") {
4543
+ if (visitedLazy.has(p)) return;
4544
+ visitedLazy.add(p);
4545
+ let resolved;
4546
+ try {
4547
+ resolved = def.thunk();
4548
+ } catch {
4549
+ return;
4550
+ }
4551
+ visit(resolved);
4552
+ return;
4553
+ }
4554
+ sz(p);
4555
+ for (const child of childrenOf(def)) visit(child);
4556
+ };
4557
+ for (const root of roots) visit(root);
4558
+ return sizes;
4559
+ }
4326
4560
  function analyzeLazyUsageMulti(roots) {
4327
4561
  const counts = /* @__PURE__ */ new Map();
4328
4562
  const recursive = /* @__PURE__ */ new Set();
@@ -4349,10 +4583,14 @@ function analyzeLazyUsageMulti(roots) {
4349
4583
  active.delete(p);
4350
4584
  return;
4351
4585
  }
4586
+ if (isHoistableTag(def.tag)) {
4587
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4588
+ }
4352
4589
  for (const child of childrenOf(def)) walk2(child);
4353
4590
  }
4354
- for (const root of roots) walk2(root);
4355
- return { counts, recursive };
4591
+ const rootsArr = [...roots];
4592
+ for (const root of rootsArr) walk2(root);
4593
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4356
4594
  }
4357
4595
  function ruleDependencies(ruleMap) {
4358
4596
  const nameOf = /* @__PURE__ */ new Map();
@@ -4402,7 +4640,6 @@ function compile(combinator, mapFnSources) {
4402
4640
  buildFns: [],
4403
4641
  buildSrcs: [],
4404
4642
  runtimeParsers: [],
4405
- needsCollator: false,
4406
4643
  namedParsers: /* @__PURE__ */ new Map(),
4407
4644
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4408
4645
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4411,18 +4648,18 @@ function compile(combinator, mapFnSources) {
4411
4648
  lazyUsage: analyzeLazyUsage(combinator)
4412
4649
  };
4413
4650
  const r = emit(combinator, ctx, "_pos");
4414
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4415
- ` : "";
4416
4651
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4417
4652
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4653
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4418
4654
  const source = [
4419
4655
  ...emptyTlDecls,
4656
+ ...hostReadsDecls,
4420
4657
  ...ctx.regexDecls,
4421
4658
  ...ctx.expectedDecls,
4422
4659
  "",
4423
4660
  ...namedPrelude,
4424
4661
  ctx.namedFnDecls.join("\n\n"),
4425
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4662
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4426
4663
  ` let pos = _pos`,
4427
4664
  ...r.stmts,
4428
4665
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4430,9 +4667,9 @@ function compile(combinator, mapFnSources) {
4430
4667
  ].join("\n");
4431
4668
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4432
4669
  ...emptyTlDecls,
4670
+ ...hostReadsDecls,
4433
4671
  ...ctx.regexDecls,
4434
4672
  ...ctx.expectedDecls,
4435
- collatorDecl,
4436
4673
  ...namedPrelude,
4437
4674
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4438
4675
  `let pos = _pos`,
@@ -4449,7 +4686,7 @@ function compile(combinator, mapFnSources) {
4449
4686
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4450
4687
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4451
4688
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4452
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4689
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4453
4690
  return {
4454
4691
  source,
4455
4692
  inlineExpression,
@@ -4505,7 +4742,6 @@ function compileLinkable(ruleMapArg, ns) {
4505
4742
  buildFns: [],
4506
4743
  buildSrcs: [],
4507
4744
  runtimeParsers: [],
4508
- needsCollator: false,
4509
4745
  namedParsers: /* @__PURE__ */ new Map(),
4510
4746
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4511
4747
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4636,12 +4872,12 @@ function compileLinkable(ruleMapArg, ns) {
4636
4872
  firstSets,
4637
4873
  deps: ruleDependencies(ruleMap),
4638
4874
  needsEmptyTl: !!ctx.needsEmptyTl,
4639
- needsCollator: ctx.needsCollator,
4875
+ needsHostReads: !!ctx.needsHostReads,
4640
4876
  mfFns: mfSrcs ? [] : ctx.mapFns,
4641
4877
  buildFns: buildSrcs ? [] : ctx.buildFns
4642
4878
  };
4643
4879
  }
4644
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4880
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4645
4881
  const bodyLines = [
4646
4882
  ` let pos = _pos`,
4647
4883
  ...r.stmts,
@@ -4655,14 +4891,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4655
4891
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4656
4892
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4657
4893
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4658
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4894
+ const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
4895
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl;
4659
4896
  if (!needsWrapper) return innerFn;
4660
4897
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4661
4898
  const hoistedDecls = [
4662
4899
  emptyTlDecl,
4900
+ hostReadsDecl,
4663
4901
  ...ctx.regexDecls.map((d) => ` ${d}`),
4664
4902
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4665
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4666
4903
  mfDecl,
4667
4904
  buildDecl,
4668
4905
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4679,6 +4916,172 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4679
4916
  ].join("\n");
4680
4917
  }
4681
4918
 
4919
+ // src/combinators/scanTo.ts
4920
+ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4921
+ const meta = {
4922
+ firstSet: any(),
4923
+ canMatchNewline: true,
4924
+ isTrivia: false
4925
+ };
4926
+ return {
4927
+ _tag: "scanTo",
4928
+ _meta: meta,
4929
+ _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
4930
+ parse(input, pos, ctx) {
4931
+ let cur = pos;
4932
+ const probeCtx = {
4933
+ trackLines: ctx.trackLines,
4934
+ state: ctx.state,
4935
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
4936
+ };
4937
+ const emit2 = (end) => {
4938
+ if (end > pos && cstCaptureActive(ctx)) {
4939
+ const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
4940
+ pushCstLeaf(ctx, leaf);
4941
+ }
4942
+ };
4943
+ while (cur < input.length) {
4944
+ const s = sentinel.parse(input, cur, probeCtx);
4945
+ if (s.ok) {
4946
+ emit2(cur);
4947
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4948
+ }
4949
+ let advanced = false;
4950
+ for (const skipper of skip2) {
4951
+ const r = skipper.parse(input, cur, probeCtx);
4952
+ if (r.ok && r.span.end > cur) {
4953
+ cur = r.span.end;
4954
+ advanced = true;
4955
+ break;
4956
+ }
4957
+ }
4958
+ if (!advanced) cur++;
4959
+ }
4960
+ if (orEOF) {
4961
+ emit2(cur);
4962
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4963
+ }
4964
+ const sentDef = sentinel._def;
4965
+ const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
4966
+ return { ok: false, expected, span: { start: pos, end: cur } };
4967
+ }
4968
+ };
4969
+ }
4970
+ function balanced(open, close, options = {}) {
4971
+ const self = ref();
4972
+ const skips = options.skip ?? [];
4973
+ const stop = /* @__PURE__ */ new Set([open, close]);
4974
+ let bounded = true;
4975
+ for (const sk of skips) {
4976
+ const cs = firstSetClassChars(sk);
4977
+ if (cs === null) {
4978
+ bounded = false;
4979
+ break;
4980
+ }
4981
+ for (const ch of cs) stop.add(ch);
4982
+ }
4983
+ const cls = [...stop].map(escapeClassChar).join("");
4984
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
4985
+ const inner = many(choice(self, ...skips, content));
4986
+ const combi = transform(
4987
+ sequence(literal(open), inner, expect(literal(close), close)),
4988
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
4989
+ // ParseError (recovered close). `c` is the close string or a ParseError.
4990
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
4991
+ );
4992
+ if (combi._def.tag === "transform") {
4993
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
4994
+ }
4995
+ self.define(combi);
4996
+ return combi;
4997
+ }
4998
+ function firstSetClassChars(c) {
4999
+ const fs = c._meta.firstSet;
5000
+ if (fs.kind === "empty") return [];
5001
+ if (fs.kind !== "ranges") return null;
5002
+ const out = [];
5003
+ for (const { lo, hi } of fs.ranges) {
5004
+ if (hi - lo > 8) return null;
5005
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5006
+ }
5007
+ return out;
5008
+ }
5009
+ function escapeClassChar(ch) {
5010
+ return ch.replace(/[\\\]^-]/g, "\\$&");
5011
+ }
5012
+
5013
+ // src/compiler/ir-serialize.ts
5014
+ function evalRuleMapIR(ir) {
5015
+ const _tf = (child, src) => {
5016
+ let fn2;
5017
+ try {
5018
+ fn2 = (0, eval)(`(${src})`);
5019
+ } catch {
5020
+ fn2 = () => {
5021
+ throw new Error("IR transform fn not materialized");
5022
+ };
5023
+ }
5024
+ const t = transform(child, fn2);
5025
+ t._def.fnSrc = src;
5026
+ return t;
5027
+ };
5028
+ const _nd = (type, child, src, opts) => {
5029
+ const n = node(type, child, void 0, opts);
5030
+ n._def.buildSrc = src;
5031
+ return n;
5032
+ };
5033
+ const fn = new Function(
5034
+ "rules",
5035
+ "ref",
5036
+ "regex",
5037
+ "literal",
5038
+ "keywords",
5039
+ "sequence",
5040
+ "choice",
5041
+ "many",
5042
+ "oneOrMore",
5043
+ "optional",
5044
+ "sepBy",
5045
+ "not",
5046
+ "node",
5047
+ "parser",
5048
+ "scanTo",
5049
+ "transform",
5050
+ "skip",
5051
+ "trivia",
5052
+ "label",
5053
+ "expect",
5054
+ "_tf",
5055
+ "_nd",
5056
+ `return (${ir})`
5057
+ );
5058
+ const map = fn(
5059
+ rules,
5060
+ ref,
5061
+ regex,
5062
+ literal,
5063
+ keywords,
5064
+ sequence,
5065
+ choice,
5066
+ many,
5067
+ oneOrMore,
5068
+ optional,
5069
+ sepBy,
5070
+ not,
5071
+ node,
5072
+ parser,
5073
+ scanTo,
5074
+ transform,
5075
+ skip,
5076
+ trivia,
5077
+ label,
5078
+ expect,
5079
+ _tf,
5080
+ _nd
5081
+ );
5082
+ return Object.entries(map);
5083
+ }
5084
+
4682
5085
  // src/compiler/linker.ts
4683
5086
  var _nsCounter = 0;
4684
5087
  function linkable(rulesMap, ns) {
@@ -4721,13 +5124,13 @@ function fusedBody(pieces) {
4721
5124
  }
4722
5125
  const contributing = new Set(winner.values());
4723
5126
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4724
- const needsCollator = [...contributing].some((p) => p.needsCollator);
5127
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4725
5128
  const lines = [
4726
5129
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4727
5130
  "const _pfFail = {}",
4728
5131
  "let _pfEnd",
4729
5132
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4730
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
5133
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4731
5134
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4732
5135
  ...[...contributing].flatMap((p) => p.prelude),
4733
5136
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -4763,9 +5166,18 @@ function fuseRules(pieces) {
4763
5166
  return new Function("_env", body)(env);
4764
5167
  }
4765
5168
  var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
5169
+ function isIRPiece(p) {
5170
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5171
+ }
5172
+ function materializePiece(p) {
5173
+ if (!isIRPiece(p)) return p;
5174
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5175
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5176
+ return pieces;
5177
+ }
4766
5178
  function itemPieces(item) {
4767
5179
  const carried = item[COMPOSED_PIECES];
4768
- if (Array.isArray(carried)) return carried;
5180
+ if (Array.isArray(carried)) return carried.map(materializePiece);
4769
5181
  if (item.ruleFns instanceof Map) return [item];
4770
5182
  return [linkable(item)];
4771
5183
  }
@@ -4861,100 +5273,6 @@ function deeperFail(a, b) {
4861
5273
  return a.span.start >= b.span.start ? a : b;
4862
5274
  }
4863
5275
 
4864
- // src/combinators/scanTo.ts
4865
- function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4866
- const meta = {
4867
- firstSet: any(),
4868
- canMatchNewline: true,
4869
- isTrivia: false
4870
- };
4871
- return {
4872
- _tag: "scanTo",
4873
- _meta: meta,
4874
- _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
4875
- parse(input, pos, ctx) {
4876
- let cur = pos;
4877
- const probeCtx = {
4878
- trackLines: ctx.trackLines,
4879
- state: ctx.state,
4880
- ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
4881
- };
4882
- const emit2 = (end) => {
4883
- if (end > pos && cstCaptureActive(ctx)) {
4884
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
4885
- pushCstLeaf(ctx, leaf);
4886
- }
4887
- };
4888
- while (cur < input.length) {
4889
- const s = sentinel.parse(input, cur, probeCtx);
4890
- if (s.ok) {
4891
- emit2(cur);
4892
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4893
- }
4894
- let advanced = false;
4895
- for (const skipper of skip2) {
4896
- const r = skipper.parse(input, cur, probeCtx);
4897
- if (r.ok && r.span.end > cur) {
4898
- cur = r.span.end;
4899
- advanced = true;
4900
- break;
4901
- }
4902
- }
4903
- if (!advanced) cur++;
4904
- }
4905
- if (orEOF) {
4906
- emit2(cur);
4907
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4908
- }
4909
- const sentDef = sentinel._def;
4910
- const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
4911
- return { ok: false, expected, span: { start: pos, end: cur } };
4912
- }
4913
- };
4914
- }
4915
- function balanced(open, close, options = {}) {
4916
- const self = ref();
4917
- const skips = options.skip ?? [];
4918
- const stop = /* @__PURE__ */ new Set([open, close]);
4919
- let bounded = true;
4920
- for (const sk of skips) {
4921
- const cs = firstSetClassChars(sk);
4922
- if (cs === null) {
4923
- bounded = false;
4924
- break;
4925
- }
4926
- for (const ch of cs) stop.add(ch);
4927
- }
4928
- const cls = [...stop].map(escapeClassChar).join("");
4929
- const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
4930
- const inner = many(choice(self, ...skips, content));
4931
- const combi = transform(
4932
- sequence(literal(open), inner, expect(literal(close), close)),
4933
- // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
4934
- // ParseError (recovered close). `c` is the close string or a ParseError.
4935
- ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
4936
- );
4937
- if (combi._def.tag === "transform") {
4938
- combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
4939
- }
4940
- self.define(combi);
4941
- return combi;
4942
- }
4943
- function firstSetClassChars(c) {
4944
- const fs = c._meta.firstSet;
4945
- if (fs.kind === "empty") return [];
4946
- if (fs.kind !== "ranges") return null;
4947
- const out = [];
4948
- for (const { lo, hi } of fs.ranges) {
4949
- if (hi - lo > 8) return null;
4950
- for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
4951
- }
4952
- return out;
4953
- }
4954
- function escapeClassChar(ch) {
4955
- return ch.replace(/[\\\]^-]/g, "\\$&");
4956
- }
4957
-
4958
5276
  // src/combinators/recover-list.ts
4959
5277
  function tolerantElement(item, sentinel, until) {
4960
5278
  const el = transform(