parseman 0.16.0 → 0.18.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
@@ -85,7 +85,7 @@ __export(index_exports, {
85
85
  sequence: () => sequence,
86
86
  shiftAbsolute: () => shiftAbsolute,
87
87
  skip: () => skip,
88
- staticExpected: () => staticExpected,
88
+ token: () => token,
89
89
  transform: () => transform,
90
90
  trivia: () => trivia,
91
91
  triviaEntries: () => triviaEntries,
@@ -155,6 +155,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
155
155
  case "transform":
156
156
  case "label":
157
157
  case "trivia":
158
+ case "token":
158
159
  case "expect":
159
160
  case "withCtx":
160
161
  case "node":
@@ -181,6 +182,55 @@ function sequenceFirstSet(parsers) {
181
182
  }
182
183
  return fs;
183
184
  }
185
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
186
+ if (seen.has(p)) return any();
187
+ seen.add(p);
188
+ const fs = (c) => firstSetOf(c, seen);
189
+ const d = p._def;
190
+ switch (d.tag) {
191
+ case "literal":
192
+ case "regex":
193
+ case "keywords":
194
+ return p._meta.firstSet;
195
+ // terminals: no refs, cached set is exact
196
+ case "lazy":
197
+ try {
198
+ return fs(d.thunk());
199
+ } catch {
200
+ return any();
201
+ }
202
+ case "choice": {
203
+ let out = empty();
204
+ for (const arm of d.parsers) out = union(out, fs(arm));
205
+ return out;
206
+ }
207
+ case "sequence": {
208
+ let out = empty();
209
+ for (const term of d.parsers) {
210
+ out = union(out, fs(term));
211
+ if (!matchesEmpty(term)) return out;
212
+ }
213
+ return out;
214
+ }
215
+ case "oneOrMore":
216
+ case "many":
217
+ case "optional":
218
+ case "transform":
219
+ case "label":
220
+ case "trivia":
221
+ case "token":
222
+ case "node":
223
+ case "grammar":
224
+ case "expect":
225
+ return fs(d.parser);
226
+ case "sepBy":
227
+ return fs(d.parser);
228
+ case "skip":
229
+ return fs(d.main);
230
+ default:
231
+ return p._meta.firstSet;
232
+ }
233
+ }
184
234
  function mergeRanges(ranges) {
185
235
  if (ranges.length === 0) return [];
186
236
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -1295,6 +1345,7 @@ function markUnusedValues(root) {
1295
1345
  return;
1296
1346
  case "label":
1297
1347
  case "trivia":
1348
+ case "token":
1298
1349
  case "withCtx":
1299
1350
  case "expect":
1300
1351
  visit(def.parser, consumed);
@@ -1595,8 +1646,60 @@ function parse(combinator, input, opts = {}) {
1595
1646
  return withErrors;
1596
1647
  }
1597
1648
 
1649
+ // src/combinators/token.ts
1650
+ function token(root) {
1651
+ const meta = {
1652
+ firstSet: root._meta.firstSet,
1653
+ canMatchNewline: root._meta.canMatchNewline,
1654
+ isTrivia: false
1655
+ };
1656
+ return {
1657
+ _tag: "token",
1658
+ _meta: meta,
1659
+ _def: { tag: "token", parser: root },
1660
+ parse(input, pos, ctx) {
1661
+ const savedTrivia = ctx.trivia;
1662
+ const savedKinds = ctx.triviaKindLabels;
1663
+ const savedBuf = ctx._cstBuf;
1664
+ const savedChildren = ctx._cstChildren;
1665
+ const savedLeaves = ctx._cstLeaves;
1666
+ const savedRaw = ctx._cstRawChildren;
1667
+ const savedTriviaLog = ctx._cstTriviaLog;
1668
+ const savedOuterTriviaLog = ctx._triviaLog;
1669
+ const wasCapturing = cstCaptureActive(ctx);
1670
+ ctx.trivia = void 0;
1671
+ ctx.triviaKindLabels = void 0;
1672
+ ctx._cstBuf = void 0;
1673
+ ctx._cstChildren = void 0;
1674
+ ctx._cstLeaves = void 0;
1675
+ ctx._cstRawChildren = void 0;
1676
+ ctx._cstTriviaLog = void 0;
1677
+ delete ctx._triviaLog;
1678
+ let result;
1679
+ try {
1680
+ result = root.parse(input, pos, ctx);
1681
+ } finally {
1682
+ ctx.trivia = savedTrivia;
1683
+ ctx.triviaKindLabels = savedKinds;
1684
+ ctx._cstBuf = savedBuf;
1685
+ ctx._cstChildren = savedChildren;
1686
+ ctx._cstLeaves = savedLeaves;
1687
+ ctx._cstRawChildren = savedRaw;
1688
+ ctx._cstTriviaLog = savedTriviaLog;
1689
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
1690
+ else ctx._triviaLog = savedOuterTriviaLog;
1691
+ }
1692
+ if (!result.ok) return result;
1693
+ const span = result.span;
1694
+ const value = input.slice(pos, span.end);
1695
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span });
1696
+ return { ok: true, value, span };
1697
+ }
1698
+ };
1699
+ }
1700
+
1598
1701
  // src/combinators/expect.ts
1599
- function staticExpected(c) {
1702
+ function deriveExpected(c) {
1600
1703
  const def = c._def;
1601
1704
  switch (def.tag) {
1602
1705
  case "literal":
@@ -1608,22 +1711,23 @@ function staticExpected(c) {
1608
1711
  case "label":
1609
1712
  return [def.label];
1610
1713
  case "choice":
1611
- return def.parsers.flatMap(staticExpected);
1714
+ return def.parsers.flatMap(deriveExpected);
1612
1715
  case "sequence":
1613
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1716
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1614
1717
  case "node":
1615
1718
  case "grammar":
1616
1719
  case "trivia":
1720
+ case "token":
1617
1721
  case "optional":
1618
1722
  case "many":
1619
1723
  case "oneOrMore":
1620
1724
  case "transform":
1621
1725
  case "not":
1622
- return staticExpected(def.parser);
1726
+ return deriveExpected(def.parser);
1623
1727
  case "lazy": {
1624
1728
  const name = c._ruleName;
1625
1729
  try {
1626
- return staticExpected(def.thunk());
1730
+ return deriveExpected(def.thunk());
1627
1731
  } catch {
1628
1732
  return name ? [name] : [];
1629
1733
  }
@@ -1633,7 +1737,7 @@ function staticExpected(c) {
1633
1737
  }
1634
1738
  }
1635
1739
  function expect(combinator, label2) {
1636
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1740
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1637
1741
  const meta = {
1638
1742
  firstSet: combinator._meta.firstSet,
1639
1743
  canMatchNewline: combinator._meta.canMatchNewline,
@@ -3121,16 +3225,12 @@ function namedFnPrelude() {
3121
3225
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
3122
3226
  ctx.namedFnDecls.push([
3123
3227
  `function ${fnName}(input, _pos, _ctx) {`,
3124
- ` let _pfok = false, _pfv, _pfe = _pos`,
3125
3228
  ` _pfail: {`,
3126
3229
  ...reindentStmts(bodyStmts, 2),
3127
- ` _pfv = ${valueVar}`,
3128
- ` _pfe = ${endVar}`,
3129
- ` _pfok = true`,
3230
+ ` ${NAMED_FN_END} = ${endVar}`,
3231
+ ` return ${valueVar}`,
3130
3232
  ` }`,
3131
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
3132
- ` ${NAMED_FN_END} = _pfe`,
3133
- ` return _pfv`,
3233
+ ` return ${NAMED_FN_FAIL}`,
3134
3234
  `}`
3135
3235
  ].join("\n"));
3136
3236
  }
@@ -3289,6 +3389,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3289
3389
  case "guard":
3290
3390
  case "not":
3291
3391
  case "trivia":
3392
+ case "token":
3292
3393
  case "scanTo":
3293
3394
  case "unknown":
3294
3395
  return false;
@@ -3350,6 +3451,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3350
3451
  case "regex":
3351
3452
  case "keywords":
3352
3453
  case "node":
3454
+ case "token":
3353
3455
  return true;
3354
3456
  case "not":
3355
3457
  case "guard":
@@ -3628,9 +3730,9 @@ function emitSeq(def, ctx, pos) {
3628
3730
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3629
3731
  return { stmts, valueVar: arrV, endVar };
3630
3732
  }
3631
- function staticExpectedArr(parsers) {
3733
+ function deriveExpectedArr(parsers) {
3632
3734
  return JSON.stringify(parsers.flatMap((p) => {
3633
- const e = staticExpected(p);
3735
+ const e = deriveExpected(p);
3634
3736
  return e.length > 0 ? e : [p._tag];
3635
3737
  }));
3636
3738
  }
@@ -3651,7 +3753,7 @@ function failsAtStart(p) {
3651
3753
  }
3652
3754
  }
3653
3755
  function armStaticExpected(ctx, p) {
3654
- return hoistExpected(ctx, staticExpectedArr([p]));
3756
+ return hoistExpected(ctx, deriveExpectedArr([p]));
3655
3757
  }
3656
3758
  function canMatchEmptyAtStart(p) {
3657
3759
  const d = p._def;
@@ -3689,7 +3791,7 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
3689
3791
  ];
3690
3792
  }
3691
3793
  function emitChoice(def, ctx, pos) {
3692
- const allExpected = staticExpectedArr(def.parsers);
3794
+ const allExpected = deriveExpectedArr(def.parsers);
3693
3795
  if (def.disjoint) {
3694
3796
  const codeV = v(ctx, "_code");
3695
3797
  const valV = v(ctx, "_chv");
@@ -3750,7 +3852,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3750
3852
  const wordV = v(ctx, "_gw");
3751
3853
  const endV = v(ctx, "_ge");
3752
3854
  const valV = v(ctx, "_gcv");
3753
- const regexExpected = JSON.stringify(staticExpected(superParser));
3855
+ const regexExpected = JSON.stringify(deriveExpected(superParser));
3754
3856
  const stmts = [
3755
3857
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
3756
3858
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
@@ -3831,11 +3933,16 @@ function emitFirstMatch(def, ctx, pos) {
3831
3933
  const atStart = failsAtStart(p);
3832
3934
  const staticFx = armStaticExpected(ctx, p);
3833
3935
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3834
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3936
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3937
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3938
+ const deep = firstSetOf(p);
3939
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3940
+ })();
3835
3941
  let gateCond = null;
3836
3942
  if (gate) {
3837
3943
  const gateIdx = ctx.mapFns.length;
3838
3944
  ctx.mapFns.push(gate);
3945
+ ctx.mapFnSrcs.push(null);
3839
3946
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3840
3947
  }
3841
3948
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3894,13 +4001,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3894
4001
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3895
4002
  return emitFirstMatch(def, ctx, pos);
3896
4003
  }
4004
+ function pushMapFn(ctx, fn, src) {
4005
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
4006
+ const hit = ctx.mapFnSrcs.indexOf(src);
4007
+ if (hit !== -1) return hit;
4008
+ }
4009
+ const idx = ctx.mapFns.length;
4010
+ ctx.mapFns.push(fn);
4011
+ ctx.mapFnSrcs.push(src);
4012
+ return idx;
4013
+ }
3897
4014
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3898
4015
  const def = p._def;
3899
4016
  if (def.tag === "transform") {
3900
4017
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3901
- const fnIdx = ctx.mapFns.length;
3902
- ctx.mapFns.push(def.fn);
3903
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4018
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3904
4019
  const vv = v(ctx);
3905
4020
  return {
3906
4021
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -4083,7 +4198,7 @@ function emitScanTo(def, ctx, pos) {
4083
4198
  `${ind(ctx)}while (${curV} < input.length) {`
4084
4199
  ];
4085
4200
  ctx.indent++;
4086
- const probeCtx = { ...ctx, capturing: false };
4201
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
4087
4202
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
4088
4203
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
4089
4204
  if (def.skip.length > 0) {
@@ -4129,6 +4244,116 @@ function emitNot(def, ctx, pos) {
4129
4244
  endVar: pos
4130
4245
  };
4131
4246
  }
4247
+ function escapeTokenLiteral(value) {
4248
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
4249
+ }
4250
+ function regexMatchesEmpty(source, flags) {
4251
+ try {
4252
+ return new RegExp(`^(?:${source})$`, flags).test("");
4253
+ } catch {
4254
+ return true;
4255
+ }
4256
+ }
4257
+ function tokenTerminalSource(p) {
4258
+ const def = p._def;
4259
+ switch (def.tag) {
4260
+ case "literal":
4261
+ if (def.value.length === 0) return null;
4262
+ return {
4263
+ source: escapeTokenLiteral(def.value),
4264
+ caseInsensitive: def.caseInsensitive,
4265
+ rawCaseSensitive: def.caseInsensitive ? "" : def.value
4266
+ };
4267
+ case "regex": {
4268
+ if (def.flags.replace(/i/g, "") !== "") return null;
4269
+ if (regexMatchesEmpty(def.source, def.flags)) return null;
4270
+ return {
4271
+ source: `(?:${def.source})`,
4272
+ caseInsensitive: def.flags.includes("i"),
4273
+ rawCaseSensitive: def.flags.includes("i") ? "" : def.source
4274
+ };
4275
+ }
4276
+ default:
4277
+ return null;
4278
+ }
4279
+ }
4280
+ function tokenSequenceSource(p) {
4281
+ const def = p._def;
4282
+ const parts = def.tag === "sequence" ? def.parsers.map(tokenTerminalSource) : [tokenTerminalSource(p)];
4283
+ if (parts.some((part) => part === null)) return null;
4284
+ const typed = parts;
4285
+ const flags = typed.some((part) => part.caseInsensitive) ? "i" : "";
4286
+ if (flags.includes("i") && typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))) return null;
4287
+ return {
4288
+ source: typed.map((part) => part.source).join(""),
4289
+ flags,
4290
+ hasCaseSensitiveLetters: typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))
4291
+ };
4292
+ }
4293
+ function combineTokenRegexSources(parts) {
4294
+ const flags = parts.some((part) => part.flags.includes("i")) ? "i" : "";
4295
+ if (flags.includes("i") && parts.some((part) => part.flags === "" && part.hasCaseSensitiveLetters)) return null;
4296
+ return { source: parts.map((part) => part.source).join(""), flags };
4297
+ }
4298
+ function tokenNullableRegex(def) {
4299
+ const inner = def.parser._def;
4300
+ if (inner.tag === "many") {
4301
+ const seq = tokenSequenceSource(inner.parser);
4302
+ if (!seq) return null;
4303
+ return { tag: "regex", source: `(?:${seq.source})*`, flags: seq.flags };
4304
+ }
4305
+ if (inner.tag === "optional") {
4306
+ const seq = tokenSequenceSource(inner.parser);
4307
+ if (!seq) return null;
4308
+ return { tag: "regex", source: `(?:${seq.source})?`, flags: seq.flags };
4309
+ }
4310
+ if (inner.tag === "sepBy") {
4311
+ const item = tokenSequenceSource(inner.parser);
4312
+ const sep = tokenSequenceSource(inner.separator);
4313
+ if (!item || !sep) return null;
4314
+ const combined = combineTokenRegexSources([item, sep]);
4315
+ if (!combined) return null;
4316
+ return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
4317
+ }
4318
+ return null;
4319
+ }
4320
+ function emitToken(def, ctx, pos) {
4321
+ const lowered = tokenNullableRegex(def);
4322
+ if (lowered) return emitRegex(lowered, ctx, pos);
4323
+ const savedTrivia = ctx.activeTrivia;
4324
+ const savedKindLabels = ctx.triviaKindLabels;
4325
+ ctx.activeTrivia = void 0;
4326
+ ctx.triviaKindLabels = void 0;
4327
+ const inner = emitFallible(def.parser, ctx, pos);
4328
+ ctx.activeTrivia = savedTrivia;
4329
+ ctx.triviaKindLabels = savedKindLabels;
4330
+ const i = ind(ctx);
4331
+ const sc = v(ctx, "_tokCh");
4332
+ const sl = v(ctx, "_tokLv");
4333
+ const sr = v(ctx, "_tokRaw");
4334
+ const sv = v(ctx, "_tokTr");
4335
+ const sk = v(ctx, "_tokKinds");
4336
+ const stl = v(ctx, "_tokTl");
4337
+ const sol = v(ctx, "_tokLog");
4338
+ const sb = v(ctx, "_tokBuf");
4339
+ const valV = v(ctx, "_tok");
4340
+ return {
4341
+ stmts: [
4342
+ `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren, ${sv} = _ctx.trivia, ${sk} = _ctx.triviaKindLabels, ${stl} = _ctx._cstTriviaLog, ${sol} = _ctx._triviaLog, ${sb} = _ctx._cstBuf`,
4343
+ `${i}_ctx.trivia = undefined; _ctx.triviaKindLabels = undefined; _ctx._cstChildren = undefined; _ctx._cstLeaves = undefined; _ctx._cstRawChildren = undefined; _ctx._cstTriviaLog = undefined; _ctx._triviaLog = undefined; _ctx._cstBuf = undefined`,
4344
+ `${i}try {`,
4345
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
4346
+ `${i}} finally {`,
4347
+ `${i} _ctx.trivia = ${sv}; _ctx.triviaKindLabels = ${sk}; _ctx._cstChildren = ${sc}; _ctx._cstLeaves = ${sl}; _ctx._cstRawChildren = ${sr}; _ctx._cstTriviaLog = ${stl}; _ctx._triviaLog = ${sol}; _ctx._cstBuf = ${sb}`,
4348
+ `${i}}`,
4349
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
4350
+ `${i}const ${valV} = input.slice(${pos}, ${inner.endVar})`,
4351
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
4352
+ ],
4353
+ valueVar: valV,
4354
+ endVar: inner.endVar
4355
+ };
4356
+ }
4132
4357
  function emitNode(def, ctx, pos) {
4133
4358
  const structural = def.build === void 0;
4134
4359
  const mkType = structural ? null : analyzeMkInlineBuild(def);
@@ -4248,7 +4473,7 @@ function emitRecover(def, ctx, pos) {
4248
4473
  `${ind0} if (${sentOk}) break`,
4249
4474
  `${ind0} ${scanV}++`,
4250
4475
  `${ind0} }`,
4251
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
4476
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(deriveExpected(def.parser))} }`,
4252
4477
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
4253
4478
  `${ind0} ${valVar} = ${errV}`,
4254
4479
  `${ind0} ${endVar} = ${scanV}`,
@@ -4274,6 +4499,32 @@ function emitExpect(def, ctx, pos) {
4274
4499
  return { stmts, valueVar: valVar, endVar };
4275
4500
  }
4276
4501
  function emit(p, ctx, pos) {
4502
+ const usage = ctx.lazyUsage;
4503
+ 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
4504
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4505
+ // faster than it calls and barely moves the byte count; the size explosion
4506
+ // is all in LARGE choices/sequences referenced from many positions.
4507
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4508
+ const existing = ctx.namedParsers.get(p);
4509
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4510
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4511
+ ctx.namedParsers.set(p, fnName);
4512
+ const savedIndent = ctx.indent;
4513
+ const savedFailLabel = ctx.failLabel;
4514
+ const savedRecord = ctx.recordFail;
4515
+ ctx.indent = 1;
4516
+ ctx.failLabel = "_pfail";
4517
+ ctx.recordFail = true;
4518
+ const r = emitDispatch(p, ctx, "_pos");
4519
+ ctx.indent = savedIndent;
4520
+ ctx.failLabel = savedFailLabel;
4521
+ ctx.recordFail = savedRecord;
4522
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4523
+ return emitNamedFnCall(ctx, fnName, pos);
4524
+ }
4525
+ return emitDispatch(p, ctx, pos);
4526
+ }
4527
+ function emitDispatch(p, ctx, pos) {
4277
4528
  const def = p._def;
4278
4529
  switch (def.tag) {
4279
4530
  case "literal":
@@ -4319,9 +4570,7 @@ function emit(p, ctx, pos) {
4319
4570
  };
4320
4571
  }
4321
4572
  }
4322
- const fnIdx = ctx.mapFns.length;
4323
- ctx.mapFns.push(def.fn);
4324
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4573
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4325
4574
  const mv = v(ctx, "_mapped");
4326
4575
  return {
4327
4576
  stmts: [
@@ -4354,6 +4603,8 @@ function emit(p, ctx, pos) {
4354
4603
  return emitLazy(p, def, ctx, pos);
4355
4604
  case "trivia":
4356
4605
  return emit(def.parser, ctx, pos);
4606
+ case "token":
4607
+ return emitToken(def, ctx, pos);
4357
4608
  case "label":
4358
4609
  return emit(def.parser, ctx, pos);
4359
4610
  case "grammar": {
@@ -4386,6 +4637,7 @@ function emit(p, ctx, pos) {
4386
4637
  case "guard": {
4387
4638
  const fnIdx = ctx.mapFns.length;
4388
4639
  ctx.mapFns.push(def.predicate);
4640
+ ctx.mapFnSrcs.push(null);
4389
4641
  const vv = v(ctx);
4390
4642
  return {
4391
4643
  stmts: [
@@ -4400,6 +4652,7 @@ function emit(p, ctx, pos) {
4400
4652
  const evIdx = ctx.mapFns.length;
4401
4653
  const extra = def.extra;
4402
4654
  ctx.mapFns.push((() => extra));
4655
+ ctx.mapFnSrcs.push(null);
4403
4656
  const innerParser = def.parser;
4404
4657
  if (!ctx.namedParsers.has(innerParser)) {
4405
4658
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4453,6 +4706,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4453
4706
  }
4454
4707
  case "grammar":
4455
4708
  case "trivia":
4709
+ case "token":
4456
4710
  case "label":
4457
4711
  case "optional":
4458
4712
  case "many":
@@ -4488,6 +4742,7 @@ function childrenOf(def) {
4488
4742
  case "optional":
4489
4743
  case "transform":
4490
4744
  case "trivia":
4745
+ case "token":
4491
4746
  case "label":
4492
4747
  case "grammar":
4493
4748
  case "not":
@@ -4519,9 +4774,58 @@ function childrenOf(def) {
4519
4774
  return [];
4520
4775
  }
4521
4776
  }
4777
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4778
+ "sequence",
4779
+ "choice",
4780
+ "many",
4781
+ "oneOrMore",
4782
+ "optional",
4783
+ "sepBy",
4784
+ "transform",
4785
+ "node",
4786
+ "not"
4787
+ ]);
4788
+ function isHoistableTag(tag) {
4789
+ return HOISTABLE_TAGS.has(tag);
4790
+ }
4791
+ var HOIST_MIN_SUBTREE = 3;
4522
4792
  function analyzeLazyUsage(root) {
4523
4793
  return analyzeLazyUsageMulti([root]);
4524
4794
  }
4795
+ function subtreeSizes(roots) {
4796
+ const sizes = /* @__PURE__ */ new Map();
4797
+ const sz = (p) => {
4798
+ const cached = sizes.get(p);
4799
+ if (cached !== void 0) return cached;
4800
+ const def = p._def;
4801
+ let s = 1;
4802
+ if (def.tag !== "lazy") {
4803
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4804
+ }
4805
+ sizes.set(p, s);
4806
+ return s;
4807
+ };
4808
+ const visitedLazy = /* @__PURE__ */ new Set();
4809
+ const visit = (p) => {
4810
+ const def = p._def;
4811
+ if (def.tag === "lazy") {
4812
+ if (visitedLazy.has(p)) return;
4813
+ visitedLazy.add(p);
4814
+ let resolved;
4815
+ try {
4816
+ resolved = def.thunk();
4817
+ } catch {
4818
+ return;
4819
+ }
4820
+ visit(resolved);
4821
+ return;
4822
+ }
4823
+ sz(p);
4824
+ for (const child of childrenOf(def)) visit(child);
4825
+ };
4826
+ for (const root of roots) visit(root);
4827
+ return sizes;
4828
+ }
4525
4829
  function analyzeLazyUsageMulti(roots) {
4526
4830
  const counts = /* @__PURE__ */ new Map();
4527
4831
  const recursive = /* @__PURE__ */ new Set();
@@ -4548,10 +4852,14 @@ function analyzeLazyUsageMulti(roots) {
4548
4852
  active.delete(p);
4549
4853
  return;
4550
4854
  }
4855
+ if (isHoistableTag(def.tag)) {
4856
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4857
+ }
4551
4858
  for (const child of childrenOf(def)) walk2(child);
4552
4859
  }
4553
- for (const root of roots) walk2(root);
4554
- return { counts, recursive };
4860
+ const rootsArr = [...roots];
4861
+ for (const root of rootsArr) walk2(root);
4862
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4555
4863
  }
4556
4864
  function ruleDependencies(ruleMap) {
4557
4865
  const nameOf = /* @__PURE__ */ new Map();
@@ -4877,6 +5185,174 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4877
5185
  ].join("\n");
4878
5186
  }
4879
5187
 
5188
+ // src/combinators/scanTo.ts
5189
+ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
5190
+ const meta = {
5191
+ firstSet: any(),
5192
+ canMatchNewline: true,
5193
+ isTrivia: false
5194
+ };
5195
+ return {
5196
+ _tag: "scanTo",
5197
+ _meta: meta,
5198
+ _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
5199
+ parse(input, pos, ctx) {
5200
+ let cur = pos;
5201
+ const probeCtx = {
5202
+ trackLines: ctx.trackLines,
5203
+ state: ctx.state,
5204
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
5205
+ };
5206
+ const emit2 = (end) => {
5207
+ if (end > pos && cstCaptureActive(ctx)) {
5208
+ const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
5209
+ pushCstLeaf(ctx, leaf);
5210
+ }
5211
+ };
5212
+ while (cur < input.length) {
5213
+ const s = sentinel.parse(input, cur, probeCtx);
5214
+ if (s.ok) {
5215
+ emit2(cur);
5216
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5217
+ }
5218
+ let advanced = false;
5219
+ for (const skipper of skip2) {
5220
+ const r = skipper.parse(input, cur, probeCtx);
5221
+ if (r.ok && r.span.end > cur) {
5222
+ cur = r.span.end;
5223
+ advanced = true;
5224
+ break;
5225
+ }
5226
+ }
5227
+ if (!advanced) cur++;
5228
+ }
5229
+ if (orEOF) {
5230
+ emit2(cur);
5231
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5232
+ }
5233
+ const sentDef = sentinel._def;
5234
+ const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5235
+ return { ok: false, expected, span: { start: pos, end: cur } };
5236
+ }
5237
+ };
5238
+ }
5239
+ function balanced(open, close, options = {}) {
5240
+ const self = ref();
5241
+ const skips = options.skip ?? [];
5242
+ const stop = /* @__PURE__ */ new Set([open, close]);
5243
+ let bounded = true;
5244
+ for (const sk of skips) {
5245
+ const cs = firstSetClassChars(sk);
5246
+ if (cs === null) {
5247
+ bounded = false;
5248
+ break;
5249
+ }
5250
+ for (const ch of cs) stop.add(ch);
5251
+ }
5252
+ const cls = [...stop].map(escapeClassChar).join("");
5253
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5254
+ const inner = many(choice(self, ...skips, content));
5255
+ const combi = transform(
5256
+ sequence(literal(open), inner, expect(literal(close))),
5257
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5258
+ // ParseError (recovered close). `c` is the close string or a ParseError.
5259
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5260
+ );
5261
+ if (combi._def.tag === "transform") {
5262
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5263
+ }
5264
+ self.define(combi);
5265
+ return combi;
5266
+ }
5267
+ function firstSetClassChars(c) {
5268
+ const fs = c._meta.firstSet;
5269
+ if (fs.kind === "empty") return [];
5270
+ if (fs.kind !== "ranges") return null;
5271
+ const out = [];
5272
+ for (const { lo, hi } of fs.ranges) {
5273
+ if (hi - lo > 8) return null;
5274
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5275
+ }
5276
+ return out;
5277
+ }
5278
+ function escapeClassChar(ch) {
5279
+ return ch.replace(/[\\\]^-]/g, "\\$&");
5280
+ }
5281
+
5282
+ // src/compiler/ir-serialize.ts
5283
+ function evalRuleMapIR(ir) {
5284
+ const _tf = (child, src) => {
5285
+ let fn2;
5286
+ try {
5287
+ fn2 = (0, eval)(`(${src})`);
5288
+ } catch {
5289
+ fn2 = () => {
5290
+ throw new Error("IR transform fn not materialized");
5291
+ };
5292
+ }
5293
+ const t = transform(child, fn2);
5294
+ t._def.fnSrc = src;
5295
+ return t;
5296
+ };
5297
+ const _nd = (type, child, src, opts) => {
5298
+ const n = node(type, child, void 0, opts);
5299
+ n._def.buildSrc = src;
5300
+ return n;
5301
+ };
5302
+ const fn = new Function(
5303
+ "rules",
5304
+ "ref",
5305
+ "regex",
5306
+ "literal",
5307
+ "keywords",
5308
+ "sequence",
5309
+ "choice",
5310
+ "many",
5311
+ "oneOrMore",
5312
+ "optional",
5313
+ "sepBy",
5314
+ "not",
5315
+ "node",
5316
+ "parser",
5317
+ "scanTo",
5318
+ "token",
5319
+ "transform",
5320
+ "skip",
5321
+ "trivia",
5322
+ "label",
5323
+ "expect",
5324
+ "_tf",
5325
+ "_nd",
5326
+ `return (${ir})`
5327
+ );
5328
+ const map = fn(
5329
+ rules,
5330
+ ref,
5331
+ regex,
5332
+ literal,
5333
+ keywords,
5334
+ sequence,
5335
+ choice,
5336
+ many,
5337
+ oneOrMore,
5338
+ optional,
5339
+ sepBy,
5340
+ not,
5341
+ node,
5342
+ parser,
5343
+ scanTo,
5344
+ token,
5345
+ transform,
5346
+ skip,
5347
+ trivia,
5348
+ label,
5349
+ expect,
5350
+ _tf,
5351
+ _nd
5352
+ );
5353
+ return Object.entries(map);
5354
+ }
5355
+
4880
5356
  // src/compiler/linker.ts
4881
5357
  var _nsCounter = 0;
4882
5358
  function linkable(rulesMap, ns) {
@@ -4961,9 +5437,18 @@ function fuseRules(pieces) {
4961
5437
  return new Function("_env", body)(env);
4962
5438
  }
4963
5439
  var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
5440
+ function isIRPiece(p) {
5441
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5442
+ }
5443
+ function materializePiece(p) {
5444
+ if (!isIRPiece(p)) return p;
5445
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5446
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5447
+ return pieces;
5448
+ }
4964
5449
  function itemPieces(item) {
4965
5450
  const carried = item[COMPOSED_PIECES];
4966
- if (Array.isArray(carried)) return carried;
5451
+ if (Array.isArray(carried)) return carried.map(materializePiece);
4967
5452
  if (item.ruleFns instanceof Map) return [item];
4968
5453
  return [linkable(item)];
4969
5454
  }
@@ -5017,7 +5502,7 @@ function recover(combinator, sentinel) {
5017
5502
  canMatchNewline: true,
5018
5503
  isTrivia: false
5019
5504
  };
5020
- const expected = staticExpected(combinator);
5505
+ const expected = deriveExpected(combinator);
5021
5506
  return {
5022
5507
  _tag: "recover",
5023
5508
  _meta: meta,
@@ -5059,100 +5544,6 @@ function deeperFail(a, b) {
5059
5544
  return a.span.start >= b.span.start ? a : b;
5060
5545
  }
5061
5546
 
5062
- // src/combinators/scanTo.ts
5063
- function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
5064
- const meta = {
5065
- firstSet: any(),
5066
- canMatchNewline: true,
5067
- isTrivia: false
5068
- };
5069
- return {
5070
- _tag: "scanTo",
5071
- _meta: meta,
5072
- _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
5073
- parse(input, pos, ctx) {
5074
- let cur = pos;
5075
- const probeCtx = {
5076
- trackLines: ctx.trackLines,
5077
- state: ctx.state,
5078
- ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
5079
- };
5080
- const emit2 = (end) => {
5081
- if (end > pos && cstCaptureActive(ctx)) {
5082
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
5083
- pushCstLeaf(ctx, leaf);
5084
- }
5085
- };
5086
- while (cur < input.length) {
5087
- const s = sentinel.parse(input, cur, probeCtx);
5088
- if (s.ok) {
5089
- emit2(cur);
5090
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5091
- }
5092
- let advanced = false;
5093
- for (const skipper of skip2) {
5094
- const r = skipper.parse(input, cur, probeCtx);
5095
- if (r.ok && r.span.end > cur) {
5096
- cur = r.span.end;
5097
- advanced = true;
5098
- break;
5099
- }
5100
- }
5101
- if (!advanced) cur++;
5102
- }
5103
- if (orEOF) {
5104
- emit2(cur);
5105
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5106
- }
5107
- const sentDef = sentinel._def;
5108
- const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5109
- return { ok: false, expected, span: { start: pos, end: cur } };
5110
- }
5111
- };
5112
- }
5113
- function balanced(open, close, options = {}) {
5114
- const self = ref();
5115
- const skips = options.skip ?? [];
5116
- const stop = /* @__PURE__ */ new Set([open, close]);
5117
- let bounded = true;
5118
- for (const sk of skips) {
5119
- const cs = firstSetClassChars(sk);
5120
- if (cs === null) {
5121
- bounded = false;
5122
- break;
5123
- }
5124
- for (const ch of cs) stop.add(ch);
5125
- }
5126
- const cls = [...stop].map(escapeClassChar).join("");
5127
- const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5128
- const inner = many(choice(self, ...skips, content));
5129
- const combi = transform(
5130
- sequence(literal(open), inner, expect(literal(close), close)),
5131
- // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5132
- // ParseError (recovered close). `c` is the close string or a ParseError.
5133
- ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5134
- );
5135
- if (combi._def.tag === "transform") {
5136
- combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5137
- }
5138
- self.define(combi);
5139
- return combi;
5140
- }
5141
- function firstSetClassChars(c) {
5142
- const fs = c._meta.firstSet;
5143
- if (fs.kind === "empty") return [];
5144
- if (fs.kind !== "ranges") return null;
5145
- const out = [];
5146
- for (const { lo, hi } of fs.ranges) {
5147
- if (hi - lo > 8) return null;
5148
- for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5149
- }
5150
- return out;
5151
- }
5152
- function escapeClassChar(ch) {
5153
- return ch.replace(/[\\\]^-]/g, "\\$&");
5154
- }
5155
-
5156
5547
  // src/combinators/recover-list.ts
5157
5548
  function tolerantElement(item, sentinel, until) {
5158
5549
  const el = transform(