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.js CHANGED
@@ -58,6 +58,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
58
58
  case "transform":
59
59
  case "label":
60
60
  case "trivia":
61
+ case "token":
61
62
  case "expect":
62
63
  case "withCtx":
63
64
  case "node":
@@ -84,6 +85,55 @@ function sequenceFirstSet(parsers) {
84
85
  }
85
86
  return fs;
86
87
  }
88
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
89
+ if (seen.has(p)) return any();
90
+ seen.add(p);
91
+ const fs = (c) => firstSetOf(c, seen);
92
+ const d = p._def;
93
+ switch (d.tag) {
94
+ case "literal":
95
+ case "regex":
96
+ case "keywords":
97
+ return p._meta.firstSet;
98
+ // terminals: no refs, cached set is exact
99
+ case "lazy":
100
+ try {
101
+ return fs(d.thunk());
102
+ } catch {
103
+ return any();
104
+ }
105
+ case "choice": {
106
+ let out = empty();
107
+ for (const arm of d.parsers) out = union(out, fs(arm));
108
+ return out;
109
+ }
110
+ case "sequence": {
111
+ let out = empty();
112
+ for (const term of d.parsers) {
113
+ out = union(out, fs(term));
114
+ if (!matchesEmpty(term)) return out;
115
+ }
116
+ return out;
117
+ }
118
+ case "oneOrMore":
119
+ case "many":
120
+ case "optional":
121
+ case "transform":
122
+ case "label":
123
+ case "trivia":
124
+ case "token":
125
+ case "node":
126
+ case "grammar":
127
+ case "expect":
128
+ return fs(d.parser);
129
+ case "sepBy":
130
+ return fs(d.parser);
131
+ case "skip":
132
+ return fs(d.main);
133
+ default:
134
+ return p._meta.firstSet;
135
+ }
136
+ }
87
137
  function mergeRanges(ranges) {
88
138
  if (ranges.length === 0) return [];
89
139
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -1198,6 +1248,7 @@ function markUnusedValues(root) {
1198
1248
  return;
1199
1249
  case "label":
1200
1250
  case "trivia":
1251
+ case "token":
1201
1252
  case "withCtx":
1202
1253
  case "expect":
1203
1254
  visit(def.parser, consumed);
@@ -1498,8 +1549,60 @@ function parse(combinator, input, opts = {}) {
1498
1549
  return withErrors;
1499
1550
  }
1500
1551
 
1552
+ // src/combinators/token.ts
1553
+ function token(root) {
1554
+ const meta = {
1555
+ firstSet: root._meta.firstSet,
1556
+ canMatchNewline: root._meta.canMatchNewline,
1557
+ isTrivia: false
1558
+ };
1559
+ return {
1560
+ _tag: "token",
1561
+ _meta: meta,
1562
+ _def: { tag: "token", parser: root },
1563
+ parse(input, pos, ctx) {
1564
+ const savedTrivia = ctx.trivia;
1565
+ const savedKinds = ctx.triviaKindLabels;
1566
+ const savedBuf = ctx._cstBuf;
1567
+ const savedChildren = ctx._cstChildren;
1568
+ const savedLeaves = ctx._cstLeaves;
1569
+ const savedRaw = ctx._cstRawChildren;
1570
+ const savedTriviaLog = ctx._cstTriviaLog;
1571
+ const savedOuterTriviaLog = ctx._triviaLog;
1572
+ const wasCapturing = cstCaptureActive(ctx);
1573
+ ctx.trivia = void 0;
1574
+ ctx.triviaKindLabels = void 0;
1575
+ ctx._cstBuf = void 0;
1576
+ ctx._cstChildren = void 0;
1577
+ ctx._cstLeaves = void 0;
1578
+ ctx._cstRawChildren = void 0;
1579
+ ctx._cstTriviaLog = void 0;
1580
+ delete ctx._triviaLog;
1581
+ let result;
1582
+ try {
1583
+ result = root.parse(input, pos, ctx);
1584
+ } finally {
1585
+ ctx.trivia = savedTrivia;
1586
+ ctx.triviaKindLabels = savedKinds;
1587
+ ctx._cstBuf = savedBuf;
1588
+ ctx._cstChildren = savedChildren;
1589
+ ctx._cstLeaves = savedLeaves;
1590
+ ctx._cstRawChildren = savedRaw;
1591
+ ctx._cstTriviaLog = savedTriviaLog;
1592
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
1593
+ else ctx._triviaLog = savedOuterTriviaLog;
1594
+ }
1595
+ if (!result.ok) return result;
1596
+ const span = result.span;
1597
+ const value = input.slice(pos, span.end);
1598
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span });
1599
+ return { ok: true, value, span };
1600
+ }
1601
+ };
1602
+ }
1603
+
1501
1604
  // src/combinators/expect.ts
1502
- function staticExpected(c) {
1605
+ function deriveExpected(c) {
1503
1606
  const def = c._def;
1504
1607
  switch (def.tag) {
1505
1608
  case "literal":
@@ -1511,22 +1614,23 @@ function staticExpected(c) {
1511
1614
  case "label":
1512
1615
  return [def.label];
1513
1616
  case "choice":
1514
- return def.parsers.flatMap(staticExpected);
1617
+ return def.parsers.flatMap(deriveExpected);
1515
1618
  case "sequence":
1516
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1619
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1517
1620
  case "node":
1518
1621
  case "grammar":
1519
1622
  case "trivia":
1623
+ case "token":
1520
1624
  case "optional":
1521
1625
  case "many":
1522
1626
  case "oneOrMore":
1523
1627
  case "transform":
1524
1628
  case "not":
1525
- return staticExpected(def.parser);
1629
+ return deriveExpected(def.parser);
1526
1630
  case "lazy": {
1527
1631
  const name = c._ruleName;
1528
1632
  try {
1529
- return staticExpected(def.thunk());
1633
+ return deriveExpected(def.thunk());
1530
1634
  } catch {
1531
1635
  return name ? [name] : [];
1532
1636
  }
@@ -1536,7 +1640,7 @@ function staticExpected(c) {
1536
1640
  }
1537
1641
  }
1538
1642
  function expect(combinator, label2) {
1539
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1643
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1540
1644
  const meta = {
1541
1645
  firstSet: combinator._meta.firstSet,
1542
1646
  canMatchNewline: combinator._meta.canMatchNewline,
@@ -3024,16 +3128,12 @@ function namedFnPrelude() {
3024
3128
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
3025
3129
  ctx.namedFnDecls.push([
3026
3130
  `function ${fnName}(input, _pos, _ctx) {`,
3027
- ` let _pfok = false, _pfv, _pfe = _pos`,
3028
3131
  ` _pfail: {`,
3029
3132
  ...reindentStmts(bodyStmts, 2),
3030
- ` _pfv = ${valueVar}`,
3031
- ` _pfe = ${endVar}`,
3032
- ` _pfok = true`,
3133
+ ` ${NAMED_FN_END} = ${endVar}`,
3134
+ ` return ${valueVar}`,
3033
3135
  ` }`,
3034
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
3035
- ` ${NAMED_FN_END} = _pfe`,
3036
- ` return _pfv`,
3136
+ ` return ${NAMED_FN_FAIL}`,
3037
3137
  `}`
3038
3138
  ].join("\n"));
3039
3139
  }
@@ -3192,6 +3292,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3192
3292
  case "guard":
3193
3293
  case "not":
3194
3294
  case "trivia":
3295
+ case "token":
3195
3296
  case "scanTo":
3196
3297
  case "unknown":
3197
3298
  return false;
@@ -3253,6 +3354,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3253
3354
  case "regex":
3254
3355
  case "keywords":
3255
3356
  case "node":
3357
+ case "token":
3256
3358
  return true;
3257
3359
  case "not":
3258
3360
  case "guard":
@@ -3531,9 +3633,9 @@ function emitSeq(def, ctx, pos) {
3531
3633
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3532
3634
  return { stmts, valueVar: arrV, endVar };
3533
3635
  }
3534
- function staticExpectedArr(parsers) {
3636
+ function deriveExpectedArr(parsers) {
3535
3637
  return JSON.stringify(parsers.flatMap((p) => {
3536
- const e = staticExpected(p);
3638
+ const e = deriveExpected(p);
3537
3639
  return e.length > 0 ? e : [p._tag];
3538
3640
  }));
3539
3641
  }
@@ -3554,7 +3656,7 @@ function failsAtStart(p) {
3554
3656
  }
3555
3657
  }
3556
3658
  function armStaticExpected(ctx, p) {
3557
- return hoistExpected(ctx, staticExpectedArr([p]));
3659
+ return hoistExpected(ctx, deriveExpectedArr([p]));
3558
3660
  }
3559
3661
  function canMatchEmptyAtStart(p) {
3560
3662
  const d = p._def;
@@ -3592,7 +3694,7 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
3592
3694
  ];
3593
3695
  }
3594
3696
  function emitChoice(def, ctx, pos) {
3595
- const allExpected = staticExpectedArr(def.parsers);
3697
+ const allExpected = deriveExpectedArr(def.parsers);
3596
3698
  if (def.disjoint) {
3597
3699
  const codeV = v(ctx, "_code");
3598
3700
  const valV = v(ctx, "_chv");
@@ -3653,7 +3755,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3653
3755
  const wordV = v(ctx, "_gw");
3654
3756
  const endV = v(ctx, "_ge");
3655
3757
  const valV = v(ctx, "_gcv");
3656
- const regexExpected = JSON.stringify(staticExpected(superParser));
3758
+ const regexExpected = JSON.stringify(deriveExpected(superParser));
3657
3759
  const stmts = [
3658
3760
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
3659
3761
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
@@ -3734,11 +3836,16 @@ function emitFirstMatch(def, ctx, pos) {
3734
3836
  const atStart = failsAtStart(p);
3735
3837
  const staticFx = armStaticExpected(ctx, p);
3736
3838
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3737
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3839
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3840
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3841
+ const deep = firstSetOf(p);
3842
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3843
+ })();
3738
3844
  let gateCond = null;
3739
3845
  if (gate) {
3740
3846
  const gateIdx = ctx.mapFns.length;
3741
3847
  ctx.mapFns.push(gate);
3848
+ ctx.mapFnSrcs.push(null);
3742
3849
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3743
3850
  }
3744
3851
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3797,13 +3904,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3797
3904
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3798
3905
  return emitFirstMatch(def, ctx, pos);
3799
3906
  }
3907
+ function pushMapFn(ctx, fn, src) {
3908
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3909
+ const hit = ctx.mapFnSrcs.indexOf(src);
3910
+ if (hit !== -1) return hit;
3911
+ }
3912
+ const idx = ctx.mapFns.length;
3913
+ ctx.mapFns.push(fn);
3914
+ ctx.mapFnSrcs.push(src);
3915
+ return idx;
3916
+ }
3800
3917
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3801
3918
  const def = p._def;
3802
3919
  if (def.tag === "transform") {
3803
3920
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3804
- const fnIdx = ctx.mapFns.length;
3805
- ctx.mapFns.push(def.fn);
3806
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3921
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3807
3922
  const vv = v(ctx);
3808
3923
  return {
3809
3924
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3986,7 +4101,7 @@ function emitScanTo(def, ctx, pos) {
3986
4101
  `${ind(ctx)}while (${curV} < input.length) {`
3987
4102
  ];
3988
4103
  ctx.indent++;
3989
- const probeCtx = { ...ctx, capturing: false };
4104
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3990
4105
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3991
4106
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3992
4107
  if (def.skip.length > 0) {
@@ -4032,6 +4147,116 @@ function emitNot(def, ctx, pos) {
4032
4147
  endVar: pos
4033
4148
  };
4034
4149
  }
4150
+ function escapeTokenLiteral(value) {
4151
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
4152
+ }
4153
+ function regexMatchesEmpty(source, flags) {
4154
+ try {
4155
+ return new RegExp(`^(?:${source})$`, flags).test("");
4156
+ } catch {
4157
+ return true;
4158
+ }
4159
+ }
4160
+ function tokenTerminalSource(p) {
4161
+ const def = p._def;
4162
+ switch (def.tag) {
4163
+ case "literal":
4164
+ if (def.value.length === 0) return null;
4165
+ return {
4166
+ source: escapeTokenLiteral(def.value),
4167
+ caseInsensitive: def.caseInsensitive,
4168
+ rawCaseSensitive: def.caseInsensitive ? "" : def.value
4169
+ };
4170
+ case "regex": {
4171
+ if (def.flags.replace(/i/g, "") !== "") return null;
4172
+ if (regexMatchesEmpty(def.source, def.flags)) return null;
4173
+ return {
4174
+ source: `(?:${def.source})`,
4175
+ caseInsensitive: def.flags.includes("i"),
4176
+ rawCaseSensitive: def.flags.includes("i") ? "" : def.source
4177
+ };
4178
+ }
4179
+ default:
4180
+ return null;
4181
+ }
4182
+ }
4183
+ function tokenSequenceSource(p) {
4184
+ const def = p._def;
4185
+ const parts = def.tag === "sequence" ? def.parsers.map(tokenTerminalSource) : [tokenTerminalSource(p)];
4186
+ if (parts.some((part) => part === null)) return null;
4187
+ const typed = parts;
4188
+ const flags = typed.some((part) => part.caseInsensitive) ? "i" : "";
4189
+ if (flags.includes("i") && typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))) return null;
4190
+ return {
4191
+ source: typed.map((part) => part.source).join(""),
4192
+ flags,
4193
+ hasCaseSensitiveLetters: typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))
4194
+ };
4195
+ }
4196
+ function combineTokenRegexSources(parts) {
4197
+ const flags = parts.some((part) => part.flags.includes("i")) ? "i" : "";
4198
+ if (flags.includes("i") && parts.some((part) => part.flags === "" && part.hasCaseSensitiveLetters)) return null;
4199
+ return { source: parts.map((part) => part.source).join(""), flags };
4200
+ }
4201
+ function tokenNullableRegex(def) {
4202
+ const inner = def.parser._def;
4203
+ if (inner.tag === "many") {
4204
+ const seq = tokenSequenceSource(inner.parser);
4205
+ if (!seq) return null;
4206
+ return { tag: "regex", source: `(?:${seq.source})*`, flags: seq.flags };
4207
+ }
4208
+ if (inner.tag === "optional") {
4209
+ const seq = tokenSequenceSource(inner.parser);
4210
+ if (!seq) return null;
4211
+ return { tag: "regex", source: `(?:${seq.source})?`, flags: seq.flags };
4212
+ }
4213
+ if (inner.tag === "sepBy") {
4214
+ const item = tokenSequenceSource(inner.parser);
4215
+ const sep = tokenSequenceSource(inner.separator);
4216
+ if (!item || !sep) return null;
4217
+ const combined = combineTokenRegexSources([item, sep]);
4218
+ if (!combined) return null;
4219
+ return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
4220
+ }
4221
+ return null;
4222
+ }
4223
+ function emitToken(def, ctx, pos) {
4224
+ const lowered = tokenNullableRegex(def);
4225
+ if (lowered) return emitRegex(lowered, ctx, pos);
4226
+ const savedTrivia = ctx.activeTrivia;
4227
+ const savedKindLabels = ctx.triviaKindLabels;
4228
+ ctx.activeTrivia = void 0;
4229
+ ctx.triviaKindLabels = void 0;
4230
+ const inner = emitFallible(def.parser, ctx, pos);
4231
+ ctx.activeTrivia = savedTrivia;
4232
+ ctx.triviaKindLabels = savedKindLabels;
4233
+ const i = ind(ctx);
4234
+ const sc = v(ctx, "_tokCh");
4235
+ const sl = v(ctx, "_tokLv");
4236
+ const sr = v(ctx, "_tokRaw");
4237
+ const sv = v(ctx, "_tokTr");
4238
+ const sk = v(ctx, "_tokKinds");
4239
+ const stl = v(ctx, "_tokTl");
4240
+ const sol = v(ctx, "_tokLog");
4241
+ const sb = v(ctx, "_tokBuf");
4242
+ const valV = v(ctx, "_tok");
4243
+ return {
4244
+ stmts: [
4245
+ `${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`,
4246
+ `${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`,
4247
+ `${i}try {`,
4248
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
4249
+ `${i}} finally {`,
4250
+ `${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}`,
4251
+ `${i}}`,
4252
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
4253
+ `${i}const ${valV} = input.slice(${pos}, ${inner.endVar})`,
4254
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
4255
+ ],
4256
+ valueVar: valV,
4257
+ endVar: inner.endVar
4258
+ };
4259
+ }
4035
4260
  function emitNode(def, ctx, pos) {
4036
4261
  const structural = def.build === void 0;
4037
4262
  const mkType = structural ? null : analyzeMkInlineBuild(def);
@@ -4151,7 +4376,7 @@ function emitRecover(def, ctx, pos) {
4151
4376
  `${ind0} if (${sentOk}) break`,
4152
4377
  `${ind0} ${scanV}++`,
4153
4378
  `${ind0} }`,
4154
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
4379
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(deriveExpected(def.parser))} }`,
4155
4380
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
4156
4381
  `${ind0} ${valVar} = ${errV}`,
4157
4382
  `${ind0} ${endVar} = ${scanV}`,
@@ -4177,6 +4402,32 @@ function emitExpect(def, ctx, pos) {
4177
4402
  return { stmts, valueVar: valVar, endVar };
4178
4403
  }
4179
4404
  function emit(p, ctx, pos) {
4405
+ const usage = ctx.lazyUsage;
4406
+ 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
4407
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4408
+ // faster than it calls and barely moves the byte count; the size explosion
4409
+ // is all in LARGE choices/sequences referenced from many positions.
4410
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4411
+ const existing = ctx.namedParsers.get(p);
4412
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4413
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4414
+ ctx.namedParsers.set(p, fnName);
4415
+ const savedIndent = ctx.indent;
4416
+ const savedFailLabel = ctx.failLabel;
4417
+ const savedRecord = ctx.recordFail;
4418
+ ctx.indent = 1;
4419
+ ctx.failLabel = "_pfail";
4420
+ ctx.recordFail = true;
4421
+ const r = emitDispatch(p, ctx, "_pos");
4422
+ ctx.indent = savedIndent;
4423
+ ctx.failLabel = savedFailLabel;
4424
+ ctx.recordFail = savedRecord;
4425
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4426
+ return emitNamedFnCall(ctx, fnName, pos);
4427
+ }
4428
+ return emitDispatch(p, ctx, pos);
4429
+ }
4430
+ function emitDispatch(p, ctx, pos) {
4180
4431
  const def = p._def;
4181
4432
  switch (def.tag) {
4182
4433
  case "literal":
@@ -4222,9 +4473,7 @@ function emit(p, ctx, pos) {
4222
4473
  };
4223
4474
  }
4224
4475
  }
4225
- const fnIdx = ctx.mapFns.length;
4226
- ctx.mapFns.push(def.fn);
4227
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4476
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4228
4477
  const mv = v(ctx, "_mapped");
4229
4478
  return {
4230
4479
  stmts: [
@@ -4257,6 +4506,8 @@ function emit(p, ctx, pos) {
4257
4506
  return emitLazy(p, def, ctx, pos);
4258
4507
  case "trivia":
4259
4508
  return emit(def.parser, ctx, pos);
4509
+ case "token":
4510
+ return emitToken(def, ctx, pos);
4260
4511
  case "label":
4261
4512
  return emit(def.parser, ctx, pos);
4262
4513
  case "grammar": {
@@ -4289,6 +4540,7 @@ function emit(p, ctx, pos) {
4289
4540
  case "guard": {
4290
4541
  const fnIdx = ctx.mapFns.length;
4291
4542
  ctx.mapFns.push(def.predicate);
4543
+ ctx.mapFnSrcs.push(null);
4292
4544
  const vv = v(ctx);
4293
4545
  return {
4294
4546
  stmts: [
@@ -4303,6 +4555,7 @@ function emit(p, ctx, pos) {
4303
4555
  const evIdx = ctx.mapFns.length;
4304
4556
  const extra = def.extra;
4305
4557
  ctx.mapFns.push((() => extra));
4558
+ ctx.mapFnSrcs.push(null);
4306
4559
  const innerParser = def.parser;
4307
4560
  if (!ctx.namedParsers.has(innerParser)) {
4308
4561
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4356,6 +4609,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4356
4609
  }
4357
4610
  case "grammar":
4358
4611
  case "trivia":
4612
+ case "token":
4359
4613
  case "label":
4360
4614
  case "optional":
4361
4615
  case "many":
@@ -4391,6 +4645,7 @@ function childrenOf(def) {
4391
4645
  case "optional":
4392
4646
  case "transform":
4393
4647
  case "trivia":
4648
+ case "token":
4394
4649
  case "label":
4395
4650
  case "grammar":
4396
4651
  case "not":
@@ -4422,9 +4677,58 @@ function childrenOf(def) {
4422
4677
  return [];
4423
4678
  }
4424
4679
  }
4680
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4681
+ "sequence",
4682
+ "choice",
4683
+ "many",
4684
+ "oneOrMore",
4685
+ "optional",
4686
+ "sepBy",
4687
+ "transform",
4688
+ "node",
4689
+ "not"
4690
+ ]);
4691
+ function isHoistableTag(tag) {
4692
+ return HOISTABLE_TAGS.has(tag);
4693
+ }
4694
+ var HOIST_MIN_SUBTREE = 3;
4425
4695
  function analyzeLazyUsage(root) {
4426
4696
  return analyzeLazyUsageMulti([root]);
4427
4697
  }
4698
+ function subtreeSizes(roots) {
4699
+ const sizes = /* @__PURE__ */ new Map();
4700
+ const sz = (p) => {
4701
+ const cached = sizes.get(p);
4702
+ if (cached !== void 0) return cached;
4703
+ const def = p._def;
4704
+ let s = 1;
4705
+ if (def.tag !== "lazy") {
4706
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4707
+ }
4708
+ sizes.set(p, s);
4709
+ return s;
4710
+ };
4711
+ const visitedLazy = /* @__PURE__ */ new Set();
4712
+ const visit = (p) => {
4713
+ const def = p._def;
4714
+ if (def.tag === "lazy") {
4715
+ if (visitedLazy.has(p)) return;
4716
+ visitedLazy.add(p);
4717
+ let resolved;
4718
+ try {
4719
+ resolved = def.thunk();
4720
+ } catch {
4721
+ return;
4722
+ }
4723
+ visit(resolved);
4724
+ return;
4725
+ }
4726
+ sz(p);
4727
+ for (const child of childrenOf(def)) visit(child);
4728
+ };
4729
+ for (const root of roots) visit(root);
4730
+ return sizes;
4731
+ }
4428
4732
  function analyzeLazyUsageMulti(roots) {
4429
4733
  const counts = /* @__PURE__ */ new Map();
4430
4734
  const recursive = /* @__PURE__ */ new Set();
@@ -4451,10 +4755,14 @@ function analyzeLazyUsageMulti(roots) {
4451
4755
  active.delete(p);
4452
4756
  return;
4453
4757
  }
4758
+ if (isHoistableTag(def.tag)) {
4759
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4760
+ }
4454
4761
  for (const child of childrenOf(def)) walk2(child);
4455
4762
  }
4456
- for (const root of roots) walk2(root);
4457
- return { counts, recursive };
4763
+ const rootsArr = [...roots];
4764
+ for (const root of rootsArr) walk2(root);
4765
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4458
4766
  }
4459
4767
  function ruleDependencies(ruleMap) {
4460
4768
  const nameOf = /* @__PURE__ */ new Map();
@@ -4780,6 +5088,174 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4780
5088
  ].join("\n");
4781
5089
  }
4782
5090
 
5091
+ // src/combinators/scanTo.ts
5092
+ function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
5093
+ const meta = {
5094
+ firstSet: any(),
5095
+ canMatchNewline: true,
5096
+ isTrivia: false
5097
+ };
5098
+ return {
5099
+ _tag: "scanTo",
5100
+ _meta: meta,
5101
+ _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
5102
+ parse(input, pos, ctx) {
5103
+ let cur = pos;
5104
+ const probeCtx = {
5105
+ trackLines: ctx.trackLines,
5106
+ state: ctx.state,
5107
+ ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
5108
+ };
5109
+ const emit2 = (end) => {
5110
+ if (end > pos && cstCaptureActive(ctx)) {
5111
+ const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
5112
+ pushCstLeaf(ctx, leaf);
5113
+ }
5114
+ };
5115
+ while (cur < input.length) {
5116
+ const s = sentinel.parse(input, cur, probeCtx);
5117
+ if (s.ok) {
5118
+ emit2(cur);
5119
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5120
+ }
5121
+ let advanced = false;
5122
+ for (const skipper of skip2) {
5123
+ const r = skipper.parse(input, cur, probeCtx);
5124
+ if (r.ok && r.span.end > cur) {
5125
+ cur = r.span.end;
5126
+ advanced = true;
5127
+ break;
5128
+ }
5129
+ }
5130
+ if (!advanced) cur++;
5131
+ }
5132
+ if (orEOF) {
5133
+ emit2(cur);
5134
+ return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5135
+ }
5136
+ const sentDef = sentinel._def;
5137
+ const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5138
+ return { ok: false, expected, span: { start: pos, end: cur } };
5139
+ }
5140
+ };
5141
+ }
5142
+ function balanced(open, close, options = {}) {
5143
+ const self = ref();
5144
+ const skips = options.skip ?? [];
5145
+ const stop = /* @__PURE__ */ new Set([open, close]);
5146
+ let bounded = true;
5147
+ for (const sk of skips) {
5148
+ const cs = firstSetClassChars(sk);
5149
+ if (cs === null) {
5150
+ bounded = false;
5151
+ break;
5152
+ }
5153
+ for (const ch of cs) stop.add(ch);
5154
+ }
5155
+ const cls = [...stop].map(escapeClassChar).join("");
5156
+ const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5157
+ const inner = many(choice(self, ...skips, content));
5158
+ const combi = transform(
5159
+ sequence(literal(open), inner, expect(literal(close))),
5160
+ // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5161
+ // ParseError (recovered close). `c` is the close string or a ParseError.
5162
+ ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5163
+ );
5164
+ if (combi._def.tag === "transform") {
5165
+ combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5166
+ }
5167
+ self.define(combi);
5168
+ return combi;
5169
+ }
5170
+ function firstSetClassChars(c) {
5171
+ const fs = c._meta.firstSet;
5172
+ if (fs.kind === "empty") return [];
5173
+ if (fs.kind !== "ranges") return null;
5174
+ const out = [];
5175
+ for (const { lo, hi } of fs.ranges) {
5176
+ if (hi - lo > 8) return null;
5177
+ for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5178
+ }
5179
+ return out;
5180
+ }
5181
+ function escapeClassChar(ch) {
5182
+ return ch.replace(/[\\\]^-]/g, "\\$&");
5183
+ }
5184
+
5185
+ // src/compiler/ir-serialize.ts
5186
+ function evalRuleMapIR(ir) {
5187
+ const _tf = (child, src) => {
5188
+ let fn2;
5189
+ try {
5190
+ fn2 = (0, eval)(`(${src})`);
5191
+ } catch {
5192
+ fn2 = () => {
5193
+ throw new Error("IR transform fn not materialized");
5194
+ };
5195
+ }
5196
+ const t = transform(child, fn2);
5197
+ t._def.fnSrc = src;
5198
+ return t;
5199
+ };
5200
+ const _nd = (type, child, src, opts) => {
5201
+ const n = node(type, child, void 0, opts);
5202
+ n._def.buildSrc = src;
5203
+ return n;
5204
+ };
5205
+ const fn = new Function(
5206
+ "rules",
5207
+ "ref",
5208
+ "regex",
5209
+ "literal",
5210
+ "keywords",
5211
+ "sequence",
5212
+ "choice",
5213
+ "many",
5214
+ "oneOrMore",
5215
+ "optional",
5216
+ "sepBy",
5217
+ "not",
5218
+ "node",
5219
+ "parser",
5220
+ "scanTo",
5221
+ "token",
5222
+ "transform",
5223
+ "skip",
5224
+ "trivia",
5225
+ "label",
5226
+ "expect",
5227
+ "_tf",
5228
+ "_nd",
5229
+ `return (${ir})`
5230
+ );
5231
+ const map = fn(
5232
+ rules,
5233
+ ref,
5234
+ regex,
5235
+ literal,
5236
+ keywords,
5237
+ sequence,
5238
+ choice,
5239
+ many,
5240
+ oneOrMore,
5241
+ optional,
5242
+ sepBy,
5243
+ not,
5244
+ node,
5245
+ parser,
5246
+ scanTo,
5247
+ token,
5248
+ transform,
5249
+ skip,
5250
+ trivia,
5251
+ label,
5252
+ expect,
5253
+ _tf,
5254
+ _nd
5255
+ );
5256
+ return Object.entries(map);
5257
+ }
5258
+
4783
5259
  // src/compiler/linker.ts
4784
5260
  var _nsCounter = 0;
4785
5261
  function linkable(rulesMap, ns) {
@@ -4864,9 +5340,18 @@ function fuseRules(pieces) {
4864
5340
  return new Function("_env", body)(env);
4865
5341
  }
4866
5342
  var COMPOSED_PIECES = /* @__PURE__ */ Symbol.for("parseman.composedPieces");
5343
+ function isIRPiece(p) {
5344
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5345
+ }
5346
+ function materializePiece(p) {
5347
+ if (!isIRPiece(p)) return p;
5348
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5349
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5350
+ return pieces;
5351
+ }
4867
5352
  function itemPieces(item) {
4868
5353
  const carried = item[COMPOSED_PIECES];
4869
- if (Array.isArray(carried)) return carried;
5354
+ if (Array.isArray(carried)) return carried.map(materializePiece);
4870
5355
  if (item.ruleFns instanceof Map) return [item];
4871
5356
  return [linkable(item)];
4872
5357
  }
@@ -4920,7 +5405,7 @@ function recover(combinator, sentinel) {
4920
5405
  canMatchNewline: true,
4921
5406
  isTrivia: false
4922
5407
  };
4923
- const expected = staticExpected(combinator);
5408
+ const expected = deriveExpected(combinator);
4924
5409
  return {
4925
5410
  _tag: "recover",
4926
5411
  _meta: meta,
@@ -4962,100 +5447,6 @@ function deeperFail(a, b) {
4962
5447
  return a.span.start >= b.span.start ? a : b;
4963
5448
  }
4964
5449
 
4965
- // src/combinators/scanTo.ts
4966
- function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4967
- const meta = {
4968
- firstSet: any(),
4969
- canMatchNewline: true,
4970
- isTrivia: false
4971
- };
4972
- return {
4973
- _tag: "scanTo",
4974
- _meta: meta,
4975
- _def: { tag: "scanTo", sentinel, skip: skip2, orEOF },
4976
- parse(input, pos, ctx) {
4977
- let cur = pos;
4978
- const probeCtx = {
4979
- trackLines: ctx.trackLines,
4980
- state: ctx.state,
4981
- ...ctx._errors !== void 0 ? { _errors: ctx._errors } : {}
4982
- };
4983
- const emit2 = (end) => {
4984
- if (end > pos && cstCaptureActive(ctx)) {
4985
- const leaf = { _tag: "leaf", value: input.slice(pos, end), span: { start: pos, end } };
4986
- pushCstLeaf(ctx, leaf);
4987
- }
4988
- };
4989
- while (cur < input.length) {
4990
- const s = sentinel.parse(input, cur, probeCtx);
4991
- if (s.ok) {
4992
- emit2(cur);
4993
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
4994
- }
4995
- let advanced = false;
4996
- for (const skipper of skip2) {
4997
- const r = skipper.parse(input, cur, probeCtx);
4998
- if (r.ok && r.span.end > cur) {
4999
- cur = r.span.end;
5000
- advanced = true;
5001
- break;
5002
- }
5003
- }
5004
- if (!advanced) cur++;
5005
- }
5006
- if (orEOF) {
5007
- emit2(cur);
5008
- return { ok: true, value: input.slice(pos, cur), span: { start: pos, end: cur } };
5009
- }
5010
- const sentDef = sentinel._def;
5011
- const expected = sentDef.tag === "literal" ? [JSON.stringify(sentDef.value)] : ["sentinel"];
5012
- return { ok: false, expected, span: { start: pos, end: cur } };
5013
- }
5014
- };
5015
- }
5016
- function balanced(open, close, options = {}) {
5017
- const self = ref();
5018
- const skips = options.skip ?? [];
5019
- const stop = /* @__PURE__ */ new Set([open, close]);
5020
- let bounded = true;
5021
- for (const sk of skips) {
5022
- const cs = firstSetClassChars(sk);
5023
- if (cs === null) {
5024
- bounded = false;
5025
- break;
5026
- }
5027
- for (const ch of cs) stop.add(ch);
5028
- }
5029
- const cls = [...stop].map(escapeClassChar).join("");
5030
- const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
5031
- const inner = many(choice(self, ...skips, content));
5032
- const combi = transform(
5033
- sequence(literal(open), inner, expect(literal(close), close)),
5034
- // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
5035
- // ParseError (recovered close). `c` is the close string or a ParseError.
5036
- ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
5037
- );
5038
- if (combi._def.tag === "transform") {
5039
- combi._def.fnSrc = '([o, parts, c]) => o + parts.map(p => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")';
5040
- }
5041
- self.define(combi);
5042
- return combi;
5043
- }
5044
- function firstSetClassChars(c) {
5045
- const fs = c._meta.firstSet;
5046
- if (fs.kind === "empty") return [];
5047
- if (fs.kind !== "ranges") return null;
5048
- const out = [];
5049
- for (const { lo, hi } of fs.ranges) {
5050
- if (hi - lo > 8) return null;
5051
- for (let cp = lo; cp <= hi; cp++) out.push(String.fromCodePoint(cp));
5052
- }
5053
- return out;
5054
- }
5055
- function escapeClassChar(ch) {
5056
- return ch.replace(/[\\\]^-]/g, "\\$&");
5057
- }
5058
-
5059
5450
  // src/combinators/recover-list.ts
5060
5451
  function tolerantElement(item, sentinel, until) {
5061
5452
  const el = transform(
@@ -5727,7 +6118,7 @@ export {
5727
6118
  sequence,
5728
6119
  shiftAbsolute,
5729
6120
  skip,
5730
- staticExpected,
6121
+ token,
5731
6122
  transform,
5732
6123
  trivia,
5733
6124
  triviaEntries,