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.cjs CHANGED
@@ -181,6 +181,54 @@ function sequenceFirstSet(parsers) {
181
181
  }
182
182
  return fs;
183
183
  }
184
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
185
+ if (seen.has(p)) return any();
186
+ seen.add(p);
187
+ const fs = (c) => firstSetOf(c, seen);
188
+ const d = p._def;
189
+ switch (d.tag) {
190
+ case "literal":
191
+ case "regex":
192
+ case "keywords":
193
+ return p._meta.firstSet;
194
+ // terminals: no refs, cached set is exact
195
+ case "lazy":
196
+ try {
197
+ return fs(d.thunk());
198
+ } catch {
199
+ return any();
200
+ }
201
+ case "choice": {
202
+ let out = empty();
203
+ for (const arm of d.parsers) out = union(out, fs(arm));
204
+ return out;
205
+ }
206
+ case "sequence": {
207
+ let out = empty();
208
+ for (const term of d.parsers) {
209
+ out = union(out, fs(term));
210
+ if (!matchesEmpty(term)) return out;
211
+ }
212
+ return out;
213
+ }
214
+ case "oneOrMore":
215
+ case "many":
216
+ case "optional":
217
+ case "transform":
218
+ case "label":
219
+ case "trivia":
220
+ case "node":
221
+ case "grammar":
222
+ case "expect":
223
+ return fs(d.parser);
224
+ case "sepBy":
225
+ return fs(d.parser);
226
+ case "skip":
227
+ return fs(d.main);
228
+ default:
229
+ return p._meta.firstSet;
230
+ }
231
+ }
184
232
  function mergeRanges(ranges) {
185
233
  if (ranges.length === 0) return [];
186
234
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -358,12 +406,16 @@ function pushTriviaLogEntry(ctx, start, end, kindIndex) {
358
406
  }
359
407
 
360
408
  // src/combinators/literal.ts
361
- var _collatorCache = null;
362
- function collator() {
363
- if (_collatorCache === null) {
364
- _collatorCache = new Intl.Collator(void 0, { sensitivity: "accent" });
409
+ function asciiFoldEq(a, b) {
410
+ if (a.length !== b.length) return false;
411
+ for (let i = 0; i < a.length; i++) {
412
+ let ca = a.charCodeAt(i);
413
+ let cb = b.charCodeAt(i);
414
+ if (ca >= 65 && ca <= 90) ca += 32;
415
+ if (cb >= 65 && cb <= 90) cb += 32;
416
+ if (ca !== cb) return false;
365
417
  }
366
- return _collatorCache;
418
+ return true;
367
419
  }
368
420
  function literal(value, opts = {}) {
369
421
  const caseInsensitive = opts.caseInsensitive ?? false;
@@ -378,8 +430,9 @@ function literal(value, opts = {}) {
378
430
  const lower = value.toLowerCase();
379
431
  const firstUpper = upper.codePointAt(0);
380
432
  const firstLower = lower.codePointAt(0);
381
- meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? { kind: "ranges", ranges: [
382
- { lo: Math.min(firstLower, firstUpper), hi: Math.max(firstLower, firstUpper) }
433
+ meta.firstSet = firstLower !== void 0 && firstUpper !== void 0 ? firstLower === firstUpper ? { kind: "ranges", ranges: [{ lo: firstLower, hi: firstLower }] } : { kind: "ranges", ranges: [
434
+ { lo: firstLower, hi: firstLower },
435
+ { lo: firstUpper, hi: firstUpper }
383
436
  ] } : firstSet;
384
437
  }
385
438
  return {
@@ -392,7 +445,7 @@ function literal(value, opts = {}) {
392
445
  return failAt(ctx, [JSON.stringify(value)], pos);
393
446
  }
394
447
  const slice = input.slice(pos, end);
395
- const matched = caseInsensitive ? collator().compare(slice, value) === 0 : slice === value;
448
+ const matched = caseInsensitive ? asciiFoldEq(slice, value) : slice === value;
396
449
  if (matched) {
397
450
  const span = { start: pos, end };
398
451
  const leaf = { _tag: "leaf", value: slice, span };
@@ -1726,8 +1779,8 @@ function classToRanges(cls) {
1726
1779
  return parseClassRanges(body);
1727
1780
  }
1728
1781
  var foldEq = (cVar, cp) => {
1729
- if (cp >= 65 && cp <= 90) return `(${cVar} === ${cp} || ${cVar} === ${cp + 32})`;
1730
- if (cp >= 97 && cp <= 122) return `(${cVar} === ${cp} || ${cVar} === ${cp - 32})`;
1782
+ if (cp >= 65 && cp <= 90) return `(${cVar} | 32) === ${cp + 32}`;
1783
+ if (cp >= 97 && cp <= 122) return `(${cVar} | 32) === ${cp}`;
1731
1784
  return `${cVar} === ${cp}`;
1732
1785
  };
1733
1786
  function literalCodePoints(frag) {
@@ -1978,6 +2031,12 @@ function firstSetsDisjoint(a, b) {
1978
2031
  return classDisjoint(a, b);
1979
2032
  }
1980
2033
  function fixedClassSeq(shape) {
2034
+ if (shape.kind === "litFold") {
2035
+ return shape.open.map((cp) => {
2036
+ const other = cp >= 65 && cp <= 90 ? cp + 32 : cp >= 97 && cp <= 122 ? cp - 32 : cp;
2037
+ 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 };
2038
+ });
2039
+ }
1981
2040
  if (shape.kind !== "seq") return null;
1982
2041
  const out = [];
1983
2042
  for (const p of shape.parts) {
@@ -2399,12 +2458,34 @@ function parseScanShapeCore(source) {
2399
2458
  }
2400
2459
  function scanShapeFromRegex(source, flags) {
2401
2460
  if (/[msu]/.test(flags)) return null;
2402
- if (/i/.test(flags)) {
2403
- const open = literalCodePoints(source);
2404
- return open ? { kind: "litFold", open } : null;
2405
- }
2461
+ if (/i/.test(flags)) return caseFoldShape(source);
2406
2462
  return parseScanShape(source);
2407
2463
  }
2464
+ function caseFoldShape(source) {
2465
+ const la = stripTrailingLookahead(source);
2466
+ if (la) {
2467
+ const inner = caseFoldLiteralOrAlt(la.base);
2468
+ if (!inner) return null;
2469
+ const operand = { ranges: la.ranges, negated: la.classNegated };
2470
+ if (!lookaheadUnambiguous(inner, operand, la.negative)) return null;
2471
+ return { kind: "lookahead", inner, ranges: la.ranges, classNegated: la.classNegated, negative: la.negative };
2472
+ }
2473
+ return caseFoldLiteralOrAlt(source);
2474
+ }
2475
+ function caseFoldLiteralOrAlt(source) {
2476
+ const asciiOnly = (cps) => cps.every((cp) => cp <= 127);
2477
+ const open = literalCodePoints(source);
2478
+ if (open) return asciiOnly(open) ? { kind: "litFold", open } : null;
2479
+ const arms = splitTopLevelAlternation(source);
2480
+ if (!arms || arms.length < 2) return null;
2481
+ const litArms = [];
2482
+ for (const arm of arms) {
2483
+ const cp = literalCodePoints(arm);
2484
+ if (!cp || !asciiOnly(cp)) return null;
2485
+ litArms.push({ kind: "litFold", open: cp });
2486
+ }
2487
+ return { kind: "alt", arms: litArms, disjoint: false, firsts: litArms.map(() => null) };
2488
+ }
2408
2489
  var classCond = (cVar, ranges) => ranges.map(([lo, hi]) => lo === hi ? `${cVar} === ${lo}` : `(${cVar} >= ${lo} && ${cVar} <= ${hi})`).join(" || ");
2409
2490
  var litCond = (base, cps, firstVar) => cps.map(
2410
2491
  (cp, k) => k === 0 && firstVar ? `${firstVar} === ${cp}` : `input.charCodeAt(${base}${k ? ` + ${k}` : ""}) === ${cp}`
@@ -2416,6 +2497,25 @@ function stringHardStop(shape) {
2416
2497
  );
2417
2498
  }
2418
2499
  var codeAt = (start, firstChar) => firstChar ?? `input.charCodeAt(${start})`;
2500
+ var ALT_SWITCH_RANGE_LIMIT = 4;
2501
+ var ALT_SWITCH_MAX_CASES = 48;
2502
+ var ALT_SWITCH_MIN_CASES = 3;
2503
+ function planAltSwitch(firsts) {
2504
+ const cases = [];
2505
+ let total = 0;
2506
+ for (const fs of firsts) {
2507
+ if (!fs || fs.negated) return { kind: "if" };
2508
+ const pts = [];
2509
+ for (const [lo, hi] of fs.ranges) {
2510
+ if (hi - lo + 1 > ALT_SWITCH_RANGE_LIMIT) return { kind: "if" };
2511
+ for (let cp = lo; cp <= hi; cp++) pts.push(cp);
2512
+ }
2513
+ total += pts.length;
2514
+ if (total > ALT_SWITCH_MAX_CASES) return { kind: "if" };
2515
+ cases.push(pts);
2516
+ }
2517
+ return total >= ALT_SWITCH_MIN_CASES ? { kind: "switch", cases } : { kind: "if" };
2518
+ }
2419
2519
  function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2420
2520
  if (shape.kind === "lookahead") {
2421
2521
  const inner = emitShapeMatch(shape.inner, start, mint, ind2, firstChar);
@@ -2440,18 +2540,57 @@ function emitShapeMatch(shape, start, mint, ind2, firstChar) {
2440
2540
  const okV = mint("_ok");
2441
2541
  const endV2 = mint("_end");
2442
2542
  const lines = [`${ind2}let ${okV} = false`, `${ind2}let ${endV2} = ${start}`];
2543
+ if (shape.arms.length >= 2 && shape.arms.every((a) => a.kind === "litFold" && a.open.length >= 1)) {
2544
+ const groups = /* @__PURE__ */ new Map();
2545
+ for (const a of shape.arms) {
2546
+ const open = a.open;
2547
+ const f = open[0];
2548
+ const lower = f >= 65 && f <= 90 ? f + 32 : f;
2549
+ (groups.get(lower) ?? groups.set(lower, []).get(lower)).push(open);
2550
+ }
2551
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2552
+ for (const [lower, arms] of groups) {
2553
+ const labels = lower >= 97 && lower <= 122 ? `case ${lower}: case ${lower - 32}:` : `case ${lower}:`;
2554
+ lines.push(`${ind2} ${labels} {`);
2555
+ for (const open of arms) {
2556
+ const tail = open.slice(1).map((cp, i) => foldEq(`input.charCodeAt(${start} + ${i + 1})`, cp));
2557
+ const cond = tail.length ? ` && ${tail.join(" && ")}` : "";
2558
+ lines.push(`${ind2} if (${start} + ${open.length} <= input.length${cond}) { ${okV} = true; ${endV2} = ${start} + ${open.length}; break }`);
2559
+ }
2560
+ lines.push(`${ind2} break`);
2561
+ lines.push(`${ind2} }`);
2562
+ }
2563
+ lines.push(`${ind2}}`);
2564
+ return { setup: lines, ok: okV, end: endV2 };
2565
+ }
2443
2566
  if (shape.disjoint) {
2444
- const c0 = codeAt(start, firstChar);
2445
- shape.arms.forEach((arm, k) => {
2446
- const fs = shape.firsts[k];
2447
- const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2448
- const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2449
- lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2450
- lines.push(...m.setup);
2451
- lines.push(`${ind2} ${okV} = ${m.ok}`);
2452
- lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2567
+ const plan = planAltSwitch(shape.firsts);
2568
+ if (plan.kind === "switch") {
2569
+ lines.push(`${ind2}if (${start} < input.length) switch (${codeAt(start, firstChar)}) {`);
2570
+ shape.arms.forEach((arm, k) => {
2571
+ const labels = plan.cases[k].map((cp) => `case ${cp}:`).join(" ");
2572
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2573
+ lines.push(`${ind2} ${labels} {`);
2574
+ lines.push(...m.setup);
2575
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2576
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2577
+ lines.push(`${ind2} break`);
2578
+ lines.push(`${ind2} }`);
2579
+ });
2453
2580
  lines.push(`${ind2}}`);
2454
- });
2581
+ } else {
2582
+ const c0 = codeAt(start, firstChar);
2583
+ shape.arms.forEach((arm, k) => {
2584
+ const fs = shape.firsts[k];
2585
+ const cond = fs.negated ? `!(${classCond(c0, fs.ranges)})` : `(${classCond(c0, fs.ranges)})`;
2586
+ const m = emitShapeMatch(arm, start, mint, `${ind2} `, firstChar);
2587
+ lines.push(`${ind2}${k === 0 ? "if" : "else if"} (${start} < input.length && ${cond}) {`);
2588
+ lines.push(...m.setup);
2589
+ lines.push(`${ind2} ${okV} = ${m.ok}`);
2590
+ lines.push(`${ind2} ${endV2} = ${m.ok} ? ${m.end} : ${start}`);
2591
+ lines.push(`${ind2}}`);
2592
+ });
2593
+ }
2455
2594
  } else {
2456
2595
  const lbl = mint("_alt");
2457
2596
  lines.push(`${ind2}${lbl}: {`);
@@ -2939,6 +3078,8 @@ function tryInlineDestructureTransform(src, valueVars) {
2939
3078
  }
2940
3079
 
2941
3080
  // src/compiler/codegen.ts
3081
+ 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 }";
3082
+ var _loweringSink = null;
2942
3083
  function v(ctx, prefix = "_v") {
2943
3084
  return `${prefix}${ctx.vars++}`;
2944
3085
  }
@@ -3028,16 +3169,12 @@ function namedFnPrelude() {
3028
3169
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
3029
3170
  ctx.namedFnDecls.push([
3030
3171
  `function ${fnName}(input, _pos, _ctx) {`,
3031
- ` let _pfok = false, _pfv, _pfe = _pos`,
3032
3172
  ` _pfail: {`,
3033
3173
  ...reindentStmts(bodyStmts, 2),
3034
- ` _pfv = ${valueVar}`,
3035
- ` _pfe = ${endVar}`,
3036
- ` _pfok = true`,
3174
+ ` ${NAMED_FN_END} = ${endVar}`,
3175
+ ` return ${valueVar}`,
3037
3176
  ` }`,
3038
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
3039
- ` ${NAMED_FN_END} = _pfe`,
3040
- ` return _pfv`,
3177
+ ` return ${NAMED_FN_FAIL}`,
3041
3178
  `}`
3042
3179
  ].join("\n"));
3043
3180
  }
@@ -3337,13 +3474,14 @@ function emitLit(def, ctx, pos) {
3337
3474
  const vv = v(ctx);
3338
3475
  const expectedStr = JSON.stringify(JSON.stringify(value));
3339
3476
  const stmts = [];
3340
- if (caseInsensitive) {
3341
- ctx.needsCollator = true;
3477
+ if (caseInsensitive && len > 0) {
3478
+ const match = Array.from(
3479
+ { length: len },
3480
+ (_, i) => `(${foldEq(`input.charCodeAt(${pos}${i > 0 ? ` + ${i}` : ""})`, value.charCodeAt(i))})`
3481
+ ).join(" && ");
3342
3482
  stmts.push(
3343
- ...emitIfFail(ctx, `${pos} + ${len} > input.length`, failBody(ctx, expectedStr, pos)),
3344
- `${ind(ctx)}const ${vv}_s = input.slice(${pos}, ${pos} + ${len})`,
3345
- ...emitIfFail(ctx, `_collator.compare(${vv}_s, ${JSON.stringify(value)}) !== 0`, failBody(ctx, expectedStr, pos)),
3346
- `${ind(ctx)}const ${vv} = ${vv}_s`
3483
+ ...emitIfFail(ctx, `${pos} + ${len} > input.length || !(${match})`, failBody(ctx, expectedStr, pos)),
3484
+ `${ind(ctx)}const ${vv} = input.slice(${pos}, ${pos} + ${len})`
3347
3485
  );
3348
3486
  } else if (len === 0) {
3349
3487
  stmts.push(`${ind(ctx)}const ${vv} = ''`);
@@ -3464,6 +3602,7 @@ function emitRegex(def, ctx, pos) {
3464
3602
  return { stmts: stmts2, valueVar: vv2, endVar: scanned.endVar };
3465
3603
  }
3466
3604
  }
3605
+ _loweringSink?.add(`/${def.source}/${def.flags}`);
3467
3606
  const flags = "y" + def.flags.replace(/[gy]/g, "");
3468
3607
  const key = `${def.source}/${flags}`;
3469
3608
  let rName = ctx.regexMap.get(key);
@@ -3736,11 +3875,16 @@ function emitFirstMatch(def, ctx, pos) {
3736
3875
  const atStart = failsAtStart(p);
3737
3876
  const staticFx = armStaticExpected(ctx, p);
3738
3877
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3739
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3878
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3879
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3880
+ const deep = firstSetOf(p);
3881
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3882
+ })();
3740
3883
  let gateCond = null;
3741
3884
  if (gate) {
3742
3885
  const gateIdx = ctx.mapFns.length;
3743
3886
  ctx.mapFns.push(gate);
3887
+ ctx.mapFnSrcs.push(null);
3744
3888
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3745
3889
  }
3746
3890
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3799,13 +3943,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3799
3943
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3800
3944
  return emitFirstMatch(def, ctx, pos);
3801
3945
  }
3946
+ function pushMapFn(ctx, fn, src) {
3947
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3948
+ const hit = ctx.mapFnSrcs.indexOf(src);
3949
+ if (hit !== -1) return hit;
3950
+ }
3951
+ const idx = ctx.mapFns.length;
3952
+ ctx.mapFns.push(fn);
3953
+ ctx.mapFnSrcs.push(src);
3954
+ return idx;
3955
+ }
3802
3956
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3803
3957
  const def = p._def;
3804
3958
  if (def.tag === "transform") {
3805
3959
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3806
- const fnIdx = ctx.mapFns.length;
3807
- ctx.mapFns.push(def.fn);
3808
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3960
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3809
3961
  const vv = v(ctx);
3810
3962
  return {
3811
3963
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3988,7 +4140,7 @@ function emitScanTo(def, ctx, pos) {
3988
4140
  `${ind(ctx)}while (${curV} < input.length) {`
3989
4141
  ];
3990
4142
  ctx.indent++;
3991
- const probeCtx = { ...ctx, capturing: false };
4143
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3992
4144
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3993
4145
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3994
4146
  if (def.skip.length > 0) {
@@ -4044,15 +4196,19 @@ function emitNode(def, ctx, pos) {
4044
4196
  ctx.buildSrcs.push(def.buildSrc ?? null);
4045
4197
  }
4046
4198
  const i = ind(ctx);
4047
- const capturesTrivia = mkType !== null || structural || buildReadsTrivia(def);
4048
- const clonesState = structural || buildReadsState(def);
4199
+ const capturesTrivia = mkType !== null || !structural && buildReadsTrivia(def);
4200
+ const clonesState = !structural && buildReadsState(def);
4049
4201
  const chV = v(ctx, "_ch");
4050
4202
  const rawV = v(ctx, "_raw");
4051
- const tlV = capturesTrivia ? v(ctx, "_tl") : "_EMPTY_TL";
4203
+ const capTLv = structural ? v(ctx, "_ctl") : null;
4204
+ const capSTv = structural ? v(ctx, "_cst") : null;
4205
+ const tlV = capturesTrivia || structural ? v(ctx, "_tl") : "_EMPTY_TL";
4052
4206
  if (!capturesTrivia) ctx.needsEmptyTl = true;
4053
4207
  const sc = v(ctx, "_sc"), sl = v(ctx, "_sl"), sr = v(ctx, "_sr"), st = v(ctx, "_st"), stl = v(ctx, "_stl");
4054
- const allocStmt = capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4055
- const innerTl = capturesTrivia ? tlV : "undefined";
4208
+ if (structural) ctx.needsHostReads = true;
4209
+ const allocStmt = structural ? `${i}const ${capTLv} = _ctx._pmCapTL ??= _hostReads(_ctx.build, 4), ${capSTv} = _ctx._pmCapST ??= (_ctx.build === undefined || _hostReads(_ctx.build, 5))
4210
+ ${i}const ${chV} = [], ${rawV} = [], ${tlV} = ${capTLv} ? [] : _EMPTY_TL` : capturesTrivia ? `${i}const ${chV} = [], ${rawV} = [], ${tlV} = []` : `${i}const ${chV} = [], ${rawV} = []`;
4211
+ const innerTl = structural ? `${capTLv} ? ${tlV} : undefined` : capturesTrivia ? tlV : "undefined";
4056
4212
  const stmts = [
4057
4213
  allocStmt,
4058
4214
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog`,
@@ -4063,7 +4219,10 @@ function emitNode(def, ctx, pos) {
4063
4219
  stmts.push(`${i}_ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}`);
4064
4220
  stmts.push(...emitIfFail(ctx, `!${okVar}`, propagateFailBody(ctx)));
4065
4221
  let stV = "undefined";
4066
- if (clonesState) {
4222
+ if (structural) {
4223
+ stV = v(ctx, "_nst");
4224
+ stmts.push(`${i}const ${stV} = ${capSTv} && _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4225
+ } else if (clonesState) {
4067
4226
  stV = v(ctx, "_nst");
4068
4227
  stmts.push(`${i}const ${stV} = _ctx.state !== undefined ? Object.assign({}, _ctx.state) : undefined`);
4069
4228
  }
@@ -4172,6 +4331,32 @@ function emitExpect(def, ctx, pos) {
4172
4331
  return { stmts, valueVar: valVar, endVar };
4173
4332
  }
4174
4333
  function emit(p, ctx, pos) {
4334
+ const usage = ctx.lazyUsage;
4335
+ 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
4336
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4337
+ // faster than it calls and barely moves the byte count; the size explosion
4338
+ // is all in LARGE choices/sequences referenced from many positions.
4339
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4340
+ const existing = ctx.namedParsers.get(p);
4341
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4342
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4343
+ ctx.namedParsers.set(p, fnName);
4344
+ const savedIndent = ctx.indent;
4345
+ const savedFailLabel = ctx.failLabel;
4346
+ const savedRecord = ctx.recordFail;
4347
+ ctx.indent = 1;
4348
+ ctx.failLabel = "_pfail";
4349
+ ctx.recordFail = true;
4350
+ const r = emitDispatch(p, ctx, "_pos");
4351
+ ctx.indent = savedIndent;
4352
+ ctx.failLabel = savedFailLabel;
4353
+ ctx.recordFail = savedRecord;
4354
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4355
+ return emitNamedFnCall(ctx, fnName, pos);
4356
+ }
4357
+ return emitDispatch(p, ctx, pos);
4358
+ }
4359
+ function emitDispatch(p, ctx, pos) {
4175
4360
  const def = p._def;
4176
4361
  switch (def.tag) {
4177
4362
  case "literal":
@@ -4217,9 +4402,7 @@ function emit(p, ctx, pos) {
4217
4402
  };
4218
4403
  }
4219
4404
  }
4220
- const fnIdx = ctx.mapFns.length;
4221
- ctx.mapFns.push(def.fn);
4222
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4405
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4223
4406
  const mv = v(ctx, "_mapped");
4224
4407
  return {
4225
4408
  stmts: [
@@ -4284,6 +4467,7 @@ function emit(p, ctx, pos) {
4284
4467
  case "guard": {
4285
4468
  const fnIdx = ctx.mapFns.length;
4286
4469
  ctx.mapFns.push(def.predicate);
4470
+ ctx.mapFnSrcs.push(null);
4287
4471
  const vv = v(ctx);
4288
4472
  return {
4289
4473
  stmts: [
@@ -4298,6 +4482,7 @@ function emit(p, ctx, pos) {
4298
4482
  const evIdx = ctx.mapFns.length;
4299
4483
  const extra = def.extra;
4300
4484
  ctx.mapFns.push((() => extra));
4485
+ ctx.mapFnSrcs.push(null);
4301
4486
  const innerParser = def.parser;
4302
4487
  if (!ctx.namedParsers.has(innerParser)) {
4303
4488
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4417,9 +4602,58 @@ function childrenOf(def) {
4417
4602
  return [];
4418
4603
  }
4419
4604
  }
4605
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4606
+ "sequence",
4607
+ "choice",
4608
+ "many",
4609
+ "oneOrMore",
4610
+ "optional",
4611
+ "sepBy",
4612
+ "transform",
4613
+ "node",
4614
+ "not"
4615
+ ]);
4616
+ function isHoistableTag(tag) {
4617
+ return HOISTABLE_TAGS.has(tag);
4618
+ }
4619
+ var HOIST_MIN_SUBTREE = 3;
4420
4620
  function analyzeLazyUsage(root) {
4421
4621
  return analyzeLazyUsageMulti([root]);
4422
4622
  }
4623
+ function subtreeSizes(roots) {
4624
+ const sizes = /* @__PURE__ */ new Map();
4625
+ const sz = (p) => {
4626
+ const cached = sizes.get(p);
4627
+ if (cached !== void 0) return cached;
4628
+ const def = p._def;
4629
+ let s = 1;
4630
+ if (def.tag !== "lazy") {
4631
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4632
+ }
4633
+ sizes.set(p, s);
4634
+ return s;
4635
+ };
4636
+ const visitedLazy = /* @__PURE__ */ new Set();
4637
+ const visit = (p) => {
4638
+ const def = p._def;
4639
+ if (def.tag === "lazy") {
4640
+ if (visitedLazy.has(p)) return;
4641
+ visitedLazy.add(p);
4642
+ let resolved;
4643
+ try {
4644
+ resolved = def.thunk();
4645
+ } catch {
4646
+ return;
4647
+ }
4648
+ visit(resolved);
4649
+ return;
4650
+ }
4651
+ sz(p);
4652
+ for (const child of childrenOf(def)) visit(child);
4653
+ };
4654
+ for (const root of roots) visit(root);
4655
+ return sizes;
4656
+ }
4423
4657
  function analyzeLazyUsageMulti(roots) {
4424
4658
  const counts = /* @__PURE__ */ new Map();
4425
4659
  const recursive = /* @__PURE__ */ new Set();
@@ -4446,10 +4680,14 @@ function analyzeLazyUsageMulti(roots) {
4446
4680
  active.delete(p);
4447
4681
  return;
4448
4682
  }
4683
+ if (isHoistableTag(def.tag)) {
4684
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4685
+ }
4449
4686
  for (const child of childrenOf(def)) walk2(child);
4450
4687
  }
4451
- for (const root of roots) walk2(root);
4452
- return { counts, recursive };
4688
+ const rootsArr = [...roots];
4689
+ for (const root of rootsArr) walk2(root);
4690
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4453
4691
  }
4454
4692
  function ruleDependencies(ruleMap) {
4455
4693
  const nameOf = /* @__PURE__ */ new Map();
@@ -4499,7 +4737,6 @@ function compile(combinator, mapFnSources) {
4499
4737
  buildFns: [],
4500
4738
  buildSrcs: [],
4501
4739
  runtimeParsers: [],
4502
- needsCollator: false,
4503
4740
  namedParsers: /* @__PURE__ */ new Map(),
4504
4741
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4505
4742
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4508,18 +4745,18 @@ function compile(combinator, mapFnSources) {
4508
4745
  lazyUsage: analyzeLazyUsage(combinator)
4509
4746
  };
4510
4747
  const r = emit(combinator, ctx, "_pos");
4511
- const collatorDecl = ctx.needsCollator ? `const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })
4512
- ` : "";
4513
4748
  const namedPrelude = ctx.namedFnDecls.length > 0 ? [...namedFnPrelude(), ""] : [];
4514
4749
  const emptyTlDecls = ctx.needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [];
4750
+ const hostReadsDecls = ctx.needsHostReads ? [HOST_READS_DECL] : [];
4515
4751
  const source = [
4516
4752
  ...emptyTlDecls,
4753
+ ...hostReadsDecls,
4517
4754
  ...ctx.regexDecls,
4518
4755
  ...ctx.expectedDecls,
4519
4756
  "",
4520
4757
  ...namedPrelude,
4521
4758
  ctx.namedFnDecls.join("\n\n"),
4522
- `${collatorDecl}function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4759
+ `function _parse(input, _pos, _rp, _mf, _build, _ctx) {`,
4523
4760
  ` let pos = _pos`,
4524
4761
  ...r.stmts,
4525
4762
  ` return { ok: true, value: ${r.valueVar}, span: { start: _pos, end: ${r.endVar} } }`,
@@ -4527,9 +4764,9 @@ function compile(combinator, mapFnSources) {
4527
4764
  ].join("\n");
4528
4765
  const fn = new Function("input", "_pos", "_rp", "_mf", "_build", "_ctx", [
4529
4766
  ...emptyTlDecls,
4767
+ ...hostReadsDecls,
4530
4768
  ...ctx.regexDecls,
4531
4769
  ...ctx.expectedDecls,
4532
- collatorDecl,
4533
4770
  ...namedPrelude,
4534
4771
  ...ctx.namedFnDecls.flatMap((decl, i) => i > 0 ? ["", decl] : [decl]),
4535
4772
  `let pos = _pos`,
@@ -4546,7 +4783,7 @@ function compile(combinator, mapFnSources) {
4546
4783
  const buildSources = ctx.buildFns.length === 0 ? void 0 : ctx.buildSrcs;
4547
4784
  const mfCovered = ctx.mapFns.length === 0 || effectiveSources !== void 0 && effectiveSources.length === ctx.mapFns.length;
4548
4785
  const canInline = ctx.runtimeParsers.length === 0 && mfCovered && buildCovered;
4549
- const inlineExpression = canInline ? buildInlineExpression(ctx, r, collatorDecl, effectiveSources, buildSources) : null;
4786
+ const inlineExpression = canInline ? buildInlineExpression(ctx, r, effectiveSources, buildSources) : null;
4550
4787
  return {
4551
4788
  source,
4552
4789
  inlineExpression,
@@ -4602,7 +4839,6 @@ function compileLinkable(ruleMapArg, ns) {
4602
4839
  buildFns: [],
4603
4840
  buildSrcs: [],
4604
4841
  runtimeParsers: [],
4605
- needsCollator: false,
4606
4842
  namedParsers: /* @__PURE__ */ new Map(),
4607
4843
  triviaCaptureNames: /* @__PURE__ */ new Map(),
4608
4844
  triviaFnNames: /* @__PURE__ */ new Map(),
@@ -4733,12 +4969,12 @@ function compileLinkable(ruleMapArg, ns) {
4733
4969
  firstSets,
4734
4970
  deps: ruleDependencies(ruleMap),
4735
4971
  needsEmptyTl: !!ctx.needsEmptyTl,
4736
- needsCollator: ctx.needsCollator,
4972
+ needsHostReads: !!ctx.needsHostReads,
4737
4973
  mfFns: mfSrcs ? [] : ctx.mapFns,
4738
4974
  buildFns: buildSrcs ? [] : ctx.buildFns
4739
4975
  };
4740
4976
  }
4741
- function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources) {
4977
+ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4742
4978
  const bodyLines = [
4743
4979
  ` let pos = _pos`,
4744
4980
  ...r.stmts,
@@ -4752,14 +4988,15 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4752
4988
  const mfDecl = mapFnSources?.length ? ` const _mf = [${mapFnSources.join(", ")}]` : "";
4753
4989
  const buildDecl = buildSources?.length ? ` const _build = [${buildSources.join(", ")}]` : "";
4754
4990
  const emptyTlDecl = ctx.needsEmptyTl ? ` const _EMPTY_TL = Object.freeze([])` : "";
4755
- const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || !!collatorDecl || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl;
4991
+ const hostReadsDecl = ctx.needsHostReads ? ` ${HOST_READS_DECL}` : "";
4992
+ const needsWrapper = ctx.regexDecls.length > 0 || ctx.expectedDecls.length > 0 || ctx.namedFnDecls.length > 0 || !!mfDecl || !!buildDecl || !!emptyTlDecl || !!hostReadsDecl;
4756
4993
  if (!needsWrapper) return innerFn;
4757
4994
  const namedPrelude = ctx.namedFnDecls.length > 0 ? namedFnPrelude() : [];
4758
4995
  const hoistedDecls = [
4759
4996
  emptyTlDecl,
4997
+ hostReadsDecl,
4760
4998
  ...ctx.regexDecls.map((d) => ` ${d}`),
4761
4999
  ...ctx.expectedDecls.map((d) => ` ${d}`),
4762
- collatorDecl ? ` ${collatorDecl.trim()}` : "",
4763
5000
  mfDecl,
4764
5001
  buildDecl,
4765
5002
  ...namedPrelude.map((l) => ` ${l}`),
@@ -4776,6 +5013,172 @@ function buildInlineExpression(ctx, r, collatorDecl, mapFnSources, buildSources)
4776
5013
  ].join("\n");
4777
5014
  }
4778
5015
 
5016
+ // src/combinators/scanTo.ts
5017
+ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
5018
+ const meta = {
5019
+ firstSet: any(),
5020
+ canMatchNewline: true,
5021
+ isTrivia: false
5022
+ };
5023
+ return {
5024
+ _tag: "scanTo",
5025
+ _meta: meta,
5026
+ _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
5027
+ parse(input, pos, ctx) {
5028
+ let cur = pos;
5029
+ const probeCtx = {
5030
+ trackLines: ctx.trackLines,
5031
+ state: ctx.state,
5032
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
5033
+ };
5034
+ const emit2 = (end) => {
5035
+ if (end > pos && cstCaptureActive(ctx)) {
5036
+ const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
5037
+ pushCstLeaf(ctx, leaf);
5038
+ }
5039
+ };
5040
+ while (cur < input.length) {
5041
+ const s = sentinel.parse(input, cur, probeCtx);
5042
+ if (s.ok) {
5043
+ emit2(cur);
5044
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5045
+ }
5046
+ let advanced = false;
5047
+ for (const skipper of skip2) {
5048
+ const r = skipper.parse(input, cur, probeCtx);
5049
+ if (r.ok && r.span.end > cur) {
5050
+ cur = r.span.end;
5051
+ advanced = true;
5052
+ break;
5053
+ }
5054
+ }
5055
+ if (!advanced) cur++;
5056
+ }
5057
+ if (orEOF) {
5058
+ emit2(cur);
5059
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5060
+ }
5061
+ const sentDef = sentinel._def;
5062
+ const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5063
+ return { ok: false, expected, span: { start: pos, end: cur } };
5064
+ }
5065
+ };
5066
+ }
5067
+ function balanced(open, close, options = {}) {
5068
+ const self = ref();
5069
+ const skips = options.skip ?? [];
5070
+ const stop = /* @__PURE__ */ new Set([open, close]);
5071
+ let bounded = true;
5072
+ for (const sk of skips) {
5073
+ const cs = firstSetClassChars(sk);
5074
+ if (cs === null) {
5075
+ bounded = false;
5076
+ break;
5077
+ }
5078
+ for (const ch of cs) stop.add(ch);
5079
+ }
5080
+ const cls = [...stop].map(escapeClassChar).join("");
5081
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5082
+ const inner = many(choice(self, ...skips, content));
5083
+ const combi = transform(
5084
+ sequence(literal(open), inner, expect(literal(close), close)),
5085
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5086
+ // ParseError (recovered close). `c` is the close string or a ParseError.
5087
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5088
+ );
5089
+ if (combi._def.tag === "transform") {
5090
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5091
+ }
5092
+ self.define(combi);
5093
+ return combi;
5094
+ }
5095
+ function firstSetClassChars(c) {
5096
+ const fs = c._meta.firstSet;
5097
+ if (fs.kind === "empty") return [];
5098
+ if (fs.kind !== "ranges") return null;
5099
+ const out = [];
5100
+ for (const { lo, hi } of fs.ranges) {
5101
+ if (hi - lo > 8) return null;
5102
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5103
+ }
5104
+ return out;
5105
+ }
5106
+ function escapeClassChar(ch) {
5107
+ return ch.replace(/[\\\]^-]/g, "\\$&");
5108
+ }
5109
+
5110
+ // src/compiler/ir-serialize.ts
5111
+ function evalRuleMapIR(ir) {
5112
+ const _tf = (child, src) => {
5113
+ let fn2;
5114
+ try {
5115
+ fn2 = (0, eval)(`(${src})`);
5116
+ } catch {
5117
+ fn2 = () => {
5118
+ throw new Error("IR transform fn not materialized");
5119
+ };
5120
+ }
5121
+ const t = transform(child, fn2);
5122
+ t._def.fnSrc = src;
5123
+ return t;
5124
+ };
5125
+ const _nd = (type, child, src, opts) => {
5126
+ const n = node(type, child, void 0, opts);
5127
+ n._def.buildSrc = src;
5128
+ return n;
5129
+ };
5130
+ const fn = new Function(
5131
+ "rules",
5132
+ "ref",
5133
+ "regex",
5134
+ "literal",
5135
+ "keywords",
5136
+ "sequence",
5137
+ "choice",
5138
+ "many",
5139
+ "oneOrMore",
5140
+ "optional",
5141
+ "sepBy",
5142
+ "not",
5143
+ "node",
5144
+ "parser",
5145
+ "scanTo",
5146
+ "transform",
5147
+ "skip",
5148
+ "trivia",
5149
+ "label",
5150
+ "expect",
5151
+ "_tf",
5152
+ "_nd",
5153
+ `return (${ir})`
5154
+ );
5155
+ const map = fn(
5156
+ rules,
5157
+ ref,
5158
+ regex,
5159
+ literal,
5160
+ keywords,
5161
+ sequence,
5162
+ choice,
5163
+ many,
5164
+ oneOrMore,
5165
+ optional,
5166
+ sepBy,
5167
+ not,
5168
+ node,
5169
+ parser,
5170
+ scanTo,
5171
+ transform,
5172
+ skip,
5173
+ trivia,
5174
+ label,
5175
+ expect,
5176
+ _tf,
5177
+ _nd
5178
+ );
5179
+ return Object.entries(map);
5180
+ }
5181
+
4779
5182
  // src/compiler/linker.ts
4780
5183
  var _nsCounter = 0;
4781
5184
  function linkable(rulesMap, ns) {
@@ -4818,13 +5221,13 @@ function fusedBody(pieces) {
4818
5221
  }
4819
5222
  const contributing = new Set(winner.values());
4820
5223
  const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4821
- const needsCollator = [...contributing].some((p) => p.needsCollator);
5224
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4822
5225
  const lines = [
4823
5226
  // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4824
5227
  "const _pfFail = {}",
4825
5228
  "let _pfEnd",
4826
5229
  ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4827
- ...needsCollator ? ["const _collator = new Intl.Collator(undefined, { sensitivity: 'accent' })"] : [],
5230
+ ...needsHostReads ? [HOST_READS_DECL] : [],
4828
5231
  // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4829
5232
  ...[...contributing].flatMap((p) => p.prelude),
4830
5233
  // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
@@ -4860,9 +5263,18 @@ function fuseRules(pieces) {
4860
5263
  return new Function("_env", body)(env);
4861
5264
  }
4862
5265
  var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
5266
+ function isIRPiece(p) {
5267
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5268
+ }
5269
+ function materializePiece(p) {
5270
+ if (!isIRPiece(p)) return p;
5271
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5272
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5273
+ return pieces;
5274
+ }
4863
5275
  function itemPieces(item) {
4864
5276
  const carried = item[COMPOSED_PIECES];
4865
- if (Array.isArray(carried)) return carried;
5277
+ if (Array.isArray(carried)) return carried.map(materializePiece);
4866
5278
  if (item.ruleFns instanceof Map) return [item];
4867
5279
  return [linkable(item)];
4868
5280
  }
@@ -4958,100 +5370,6 @@ function deeperFail(a, b) {
4958
5370
  return a.span.start >= b.span.start ? a : b;
4959
5371
  }
4960
5372
 
4961
- // src/combinators/scanTo.ts
4962
- function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4963
- const meta = {
4964
- firstSet: any(),
4965
- canMatchNewline: true,
4966
- isTrivia: false
4967
- };
4968
- return {
4969
- _tag: "scanTo",
4970
- _meta: meta,
4971
- _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
4972
- parse(input, pos, ctx) {
4973
- let cur = pos;
4974
- const probeCtx = {
4975
- trackLines: ctx.trackLines,
4976
- state: ctx.state,
4977
- ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
4978
- };
4979
- const emit2 = (end) => {
4980
- if (end > pos && cstCaptureActive(ctx)) {
4981
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
4982
- pushCstLeaf(ctx, leaf);
4983
- }
4984
- };
4985
- while (cur < input.length) {
4986
- const s = sentinel.parse(input, cur, probeCtx);
4987
- if (s.ok) {
4988
- emit2(cur);
4989
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4990
- }
4991
- let advanced = false;
4992
- for (const skipper of skip2) {
4993
- const r = skipper.parse(input, cur, probeCtx);
4994
- if (r.ok && r.span.end > cur) {
4995
- cur = r.span.end;
4996
- advanced = true;
4997
- break;
4998
- }
4999
- }
5000
- if (!advanced) cur++;
5001
- }
5002
- if (orEOF) {
5003
- emit2(cur);
5004
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5005
- }
5006
- const sentDef = sentinel._def;
5007
- const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5008
- return { ok: false, expected, span: { start: pos, end: cur } };
5009
- }
5010
- };
5011
- }
5012
- function balanced(open, close, options = {}) {
5013
- const self = ref();
5014
- const skips = options.skip ?? [];
5015
- const stop = /* @__PURE__ */ new Set([open, close]);
5016
- let bounded = true;
5017
- for (const sk of skips) {
5018
- const cs = firstSetClassChars(sk);
5019
- if (cs === null) {
5020
- bounded = false;
5021
- break;
5022
- }
5023
- for (const ch of cs) stop.add(ch);
5024
- }
5025
- const cls = [...stop].map(escapeClassChar).join("");
5026
- const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5027
- const inner = many(choice(self, ...skips, content));
5028
- const combi = transform(
5029
- sequence(literal(open), inner, expect(literal(close), close)),
5030
- // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5031
- // ParseError (recovered close). `c` is the close string or a ParseError.
5032
- ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5033
- );
5034
- if (combi._def.tag === "transform") {
5035
- combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5036
- }
5037
- self.define(combi);
5038
- return combi;
5039
- }
5040
- function firstSetClassChars(c) {
5041
- const fs = c._meta.firstSet;
5042
- if (fs.kind === "empty") return [];
5043
- if (fs.kind !== "ranges") return null;
5044
- const out = [];
5045
- for (const { lo, hi } of fs.ranges) {
5046
- if (hi - lo > 8) return null;
5047
- for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5048
- }
5049
- return out;
5050
- }
5051
- function escapeClassChar(ch) {
5052
- return ch.replace(/[\\\]^-]/g, "\\$&");
5053
- }
5054
-
5055
5373
  // src/combinators/recover-list.ts
5056
5374
  function tolerantElement(item, sentinel, until) {
5057
5375
  const el = transform(