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.
@@ -100,6 +100,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
100
100
  case "transform":
101
101
  case "label":
102
102
  case "trivia":
103
+ case "token":
103
104
  case "expect":
104
105
  case "withCtx":
105
106
  case "node":
@@ -126,6 +127,55 @@ function sequenceFirstSet(parsers) {
126
127
  }
127
128
  return fs2;
128
129
  }
130
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
131
+ if (seen.has(p)) return any();
132
+ seen.add(p);
133
+ const fs2 = (c) => firstSetOf(c, seen);
134
+ const d = p._def;
135
+ switch (d.tag) {
136
+ case "literal":
137
+ case "regex":
138
+ case "keywords":
139
+ return p._meta.firstSet;
140
+ // terminals: no refs, cached set is exact
141
+ case "lazy":
142
+ try {
143
+ return fs2(d.thunk());
144
+ } catch {
145
+ return any();
146
+ }
147
+ case "choice": {
148
+ let out = empty();
149
+ for (const arm of d.parsers) out = union(out, fs2(arm));
150
+ return out;
151
+ }
152
+ case "sequence": {
153
+ let out = empty();
154
+ for (const term of d.parsers) {
155
+ out = union(out, fs2(term));
156
+ if (!matchesEmpty(term)) return out;
157
+ }
158
+ return out;
159
+ }
160
+ case "oneOrMore":
161
+ case "many":
162
+ case "optional":
163
+ case "transform":
164
+ case "label":
165
+ case "trivia":
166
+ case "token":
167
+ case "node":
168
+ case "grammar":
169
+ case "expect":
170
+ return fs2(d.parser);
171
+ case "sepBy":
172
+ return fs2(d.parser);
173
+ case "skip":
174
+ return fs2(d.main);
175
+ default:
176
+ return p._meta.firstSet;
177
+ }
178
+ }
129
179
  function mergeRanges(ranges) {
130
180
  if (ranges.length === 0) return [];
131
181
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -1237,6 +1287,7 @@ function markUnusedValues(root) {
1237
1287
  return;
1238
1288
  case "label":
1239
1289
  case "trivia":
1290
+ case "token":
1240
1291
  case "withCtx":
1241
1292
  case "expect":
1242
1293
  visit(def.parser, consumed);
@@ -1265,6 +1316,41 @@ function markUnusedValues(root) {
1265
1316
  visit(resolveRoot(root), true);
1266
1317
  }
1267
1318
 
1319
+ // src/combinators/parser.ts
1320
+ function rules(factory) {
1321
+ const cache = {};
1322
+ const proxy = new Proxy(cache, {
1323
+ get(target, key) {
1324
+ if (typeof key !== "string") return void 0;
1325
+ const record = target;
1326
+ if (!(key in record)) {
1327
+ const r = ref();
1328
+ r._ruleName = key;
1329
+ record[key] = r;
1330
+ }
1331
+ return record[key];
1332
+ }
1333
+ });
1334
+ const definitions = factory(proxy);
1335
+ for (const key of Object.keys(definitions)) {
1336
+ const placeholder = cache[key];
1337
+ const parser2 = definitions[key];
1338
+ if (placeholder !== void 0 && typeof placeholder.define === "function") {
1339
+ ;
1340
+ placeholder.define(parser2);
1341
+ placeholder._meta.firstSet = parser2._meta.firstSet;
1342
+ placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1343
+ } else {
1344
+ ;
1345
+ cache[key] = parser2;
1346
+ }
1347
+ }
1348
+ for (const key of Object.keys(definitions)) {
1349
+ markUnusedValues(cache[key]);
1350
+ }
1351
+ return cache;
1352
+ }
1353
+
1268
1354
  // src/combinators/not.ts
1269
1355
  function not(combinator) {
1270
1356
  const meta = {
@@ -1371,6 +1457,20 @@ function transform(combinator, fn) {
1371
1457
  }
1372
1458
  };
1373
1459
  }
1460
+ function skip(main, skipped) {
1461
+ return {
1462
+ _tag: "skip",
1463
+ _meta: main._meta,
1464
+ _def: { tag: "skip", main, skipped },
1465
+ parse(input, pos, ctx) {
1466
+ const result = main.parse(input, pos, ctx);
1467
+ if (!result.ok) return result;
1468
+ const s = skipped.parse(input, result.span.end, ctx);
1469
+ if (!s.ok) return result;
1470
+ return { ...result, span: { start: result.span.start, end: s.span.end } };
1471
+ }
1472
+ };
1473
+ }
1374
1474
  function trivia(combinator) {
1375
1475
  const kindLabels = analyzeLabeledTrivia(combinator)?.labels;
1376
1476
  return {
@@ -1465,8 +1565,60 @@ function noTrivia(root) {
1465
1565
  return parser({ trivia: null }, root);
1466
1566
  }
1467
1567
 
1568
+ // src/combinators/token.ts
1569
+ function token(root) {
1570
+ const meta = {
1571
+ firstSet: root._meta.firstSet,
1572
+ canMatchNewline: root._meta.canMatchNewline,
1573
+ isTrivia: false
1574
+ };
1575
+ return {
1576
+ _tag: "token",
1577
+ _meta: meta,
1578
+ _def: { tag: "token", parser: root },
1579
+ parse(input, pos, ctx) {
1580
+ const savedTrivia = ctx.trivia;
1581
+ const savedKinds = ctx.triviaKindLabels;
1582
+ const savedBuf = ctx._cstBuf;
1583
+ const savedChildren = ctx._cstChildren;
1584
+ const savedLeaves = ctx._cstLeaves;
1585
+ const savedRaw = ctx._cstRawChildren;
1586
+ const savedTriviaLog = ctx._cstTriviaLog;
1587
+ const savedOuterTriviaLog = ctx._triviaLog;
1588
+ const wasCapturing = cstCaptureActive(ctx);
1589
+ ctx.trivia = void 0;
1590
+ ctx.triviaKindLabels = void 0;
1591
+ ctx._cstBuf = void 0;
1592
+ ctx._cstChildren = void 0;
1593
+ ctx._cstLeaves = void 0;
1594
+ ctx._cstRawChildren = void 0;
1595
+ ctx._cstTriviaLog = void 0;
1596
+ delete ctx._triviaLog;
1597
+ let result;
1598
+ try {
1599
+ result = root.parse(input, pos, ctx);
1600
+ } finally {
1601
+ ctx.trivia = savedTrivia;
1602
+ ctx.triviaKindLabels = savedKinds;
1603
+ ctx._cstBuf = savedBuf;
1604
+ ctx._cstChildren = savedChildren;
1605
+ ctx._cstLeaves = savedLeaves;
1606
+ ctx._cstRawChildren = savedRaw;
1607
+ ctx._cstTriviaLog = savedTriviaLog;
1608
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
1609
+ else ctx._triviaLog = savedOuterTriviaLog;
1610
+ }
1611
+ if (!result.ok) return result;
1612
+ const span = result.span;
1613
+ const value = input.slice(pos, span.end);
1614
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span });
1615
+ return { ok: true, value, span };
1616
+ }
1617
+ };
1618
+ }
1619
+
1468
1620
  // src/combinators/expect.ts
1469
- function staticExpected(c) {
1621
+ function deriveExpected(c) {
1470
1622
  const def = c._def;
1471
1623
  switch (def.tag) {
1472
1624
  case "literal":
@@ -1478,22 +1630,23 @@ function staticExpected(c) {
1478
1630
  case "label":
1479
1631
  return [def.label];
1480
1632
  case "choice":
1481
- return def.parsers.flatMap(staticExpected);
1633
+ return def.parsers.flatMap(deriveExpected);
1482
1634
  case "sequence":
1483
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1635
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1484
1636
  case "node":
1485
1637
  case "grammar":
1486
1638
  case "trivia":
1639
+ case "token":
1487
1640
  case "optional":
1488
1641
  case "many":
1489
1642
  case "oneOrMore":
1490
1643
  case "transform":
1491
1644
  case "not":
1492
- return staticExpected(def.parser);
1645
+ return deriveExpected(def.parser);
1493
1646
  case "lazy": {
1494
1647
  const name = c._ruleName;
1495
1648
  try {
1496
- return staticExpected(def.thunk());
1649
+ return deriveExpected(def.thunk());
1497
1650
  } catch {
1498
1651
  return name ? [name] : [];
1499
1652
  }
@@ -1503,7 +1656,7 @@ function staticExpected(c) {
1503
1656
  }
1504
1657
  }
1505
1658
  function expect(combinator, label2) {
1506
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1659
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1507
1660
  const meta = {
1508
1661
  firstSet: combinator._meta.firstSet,
1509
1662
  canMatchNewline: combinator._meta.canMatchNewline,
@@ -2999,16 +3152,12 @@ function namedFnPrelude() {
2999
3152
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
3000
3153
  ctx.namedFnDecls.push([
3001
3154
  `function ${fnName}(input, _pos, _ctx) {`,
3002
- ` let _pfok = false, _pfv, _pfe = _pos`,
3003
3155
  ` _pfail: {`,
3004
3156
  ...reindentStmts(bodyStmts, 2),
3005
- ` _pfv = ${valueVar}`,
3006
- ` _pfe = ${endVar}`,
3007
- ` _pfok = true`,
3157
+ ` ${NAMED_FN_END} = ${endVar}`,
3158
+ ` return ${valueVar}`,
3008
3159
  ` }`,
3009
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
3010
- ` ${NAMED_FN_END} = _pfe`,
3011
- ` return _pfv`,
3160
+ ` return ${NAMED_FN_FAIL}`,
3012
3161
  `}`
3013
3162
  ].join("\n"));
3014
3163
  }
@@ -3167,6 +3316,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3167
3316
  case "guard":
3168
3317
  case "not":
3169
3318
  case "trivia":
3319
+ case "token":
3170
3320
  case "scanTo":
3171
3321
  case "unknown":
3172
3322
  return false;
@@ -3228,6 +3378,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3228
3378
  case "regex":
3229
3379
  case "keywords":
3230
3380
  case "node":
3381
+ case "token":
3231
3382
  return true;
3232
3383
  case "not":
3233
3384
  case "guard":
@@ -3506,9 +3657,9 @@ function emitSeq(def, ctx, pos) {
3506
3657
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3507
3658
  return { stmts, valueVar: arrV, endVar };
3508
3659
  }
3509
- function staticExpectedArr(parsers) {
3660
+ function deriveExpectedArr(parsers) {
3510
3661
  return JSON.stringify(parsers.flatMap((p) => {
3511
- const e = staticExpected(p);
3662
+ const e = deriveExpected(p);
3512
3663
  return e.length > 0 ? e : [p._tag];
3513
3664
  }));
3514
3665
  }
@@ -3529,7 +3680,7 @@ function failsAtStart(p) {
3529
3680
  }
3530
3681
  }
3531
3682
  function armStaticExpected(ctx, p) {
3532
- return hoistExpected(ctx, staticExpectedArr([p]));
3683
+ return hoistExpected(ctx, deriveExpectedArr([p]));
3533
3684
  }
3534
3685
  function canMatchEmptyAtStart(p) {
3535
3686
  const d = p._def;
@@ -3567,7 +3718,7 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
3567
3718
  ];
3568
3719
  }
3569
3720
  function emitChoice(def, ctx, pos) {
3570
- const allExpected = staticExpectedArr(def.parsers);
3721
+ const allExpected = deriveExpectedArr(def.parsers);
3571
3722
  if (def.disjoint) {
3572
3723
  const codeV = v(ctx, "_code");
3573
3724
  const valV = v(ctx, "_chv");
@@ -3628,7 +3779,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3628
3779
  const wordV = v(ctx, "_gw");
3629
3780
  const endV = v(ctx, "_ge");
3630
3781
  const valV = v(ctx, "_gcv");
3631
- const regexExpected = JSON.stringify(staticExpected(superParser));
3782
+ const regexExpected = JSON.stringify(deriveExpected(superParser));
3632
3783
  const stmts = [
3633
3784
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
3634
3785
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
@@ -3709,11 +3860,16 @@ function emitFirstMatch(def, ctx, pos) {
3709
3860
  const atStart = failsAtStart(p);
3710
3861
  const staticFx = armStaticExpected(ctx, p);
3711
3862
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3712
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3863
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3864
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3865
+ const deep = firstSetOf(p);
3866
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3867
+ })();
3713
3868
  let gateCond = null;
3714
3869
  if (gate) {
3715
3870
  const gateIdx = ctx.mapFns.length;
3716
3871
  ctx.mapFns.push(gate);
3872
+ ctx.mapFnSrcs.push(null);
3717
3873
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3718
3874
  }
3719
3875
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3772,13 +3928,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3772
3928
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3773
3929
  return emitFirstMatch(def, ctx, pos);
3774
3930
  }
3931
+ function pushMapFn(ctx, fn, src) {
3932
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3933
+ const hit = ctx.mapFnSrcs.indexOf(src);
3934
+ if (hit !== -1) return hit;
3935
+ }
3936
+ const idx = ctx.mapFns.length;
3937
+ ctx.mapFns.push(fn);
3938
+ ctx.mapFnSrcs.push(src);
3939
+ return idx;
3940
+ }
3775
3941
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3776
3942
  const def = p._def;
3777
3943
  if (def.tag === "transform") {
3778
3944
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3779
- const fnIdx = ctx.mapFns.length;
3780
- ctx.mapFns.push(def.fn);
3781
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3945
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3782
3946
  const vv = v(ctx);
3783
3947
  return {
3784
3948
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3961,7 +4125,7 @@ function emitScanTo(def, ctx, pos) {
3961
4125
  `${ind(ctx)}while (${curV} < input.length) {`
3962
4126
  ];
3963
4127
  ctx.indent++;
3964
- const probeCtx = { ...ctx, capturing: false };
4128
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3965
4129
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3966
4130
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3967
4131
  if (def.skip.length > 0) {
@@ -4007,6 +4171,116 @@ function emitNot(def, ctx, pos) {
4007
4171
  endVar: pos
4008
4172
  };
4009
4173
  }
4174
+ function escapeTokenLiteral(value) {
4175
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
4176
+ }
4177
+ function regexMatchesEmpty(source, flags) {
4178
+ try {
4179
+ return new RegExp(`^(?:${source})$`, flags).test("");
4180
+ } catch {
4181
+ return true;
4182
+ }
4183
+ }
4184
+ function tokenTerminalSource(p) {
4185
+ const def = p._def;
4186
+ switch (def.tag) {
4187
+ case "literal":
4188
+ if (def.value.length === 0) return null;
4189
+ return {
4190
+ source: escapeTokenLiteral(def.value),
4191
+ caseInsensitive: def.caseInsensitive,
4192
+ rawCaseSensitive: def.caseInsensitive ? "" : def.value
4193
+ };
4194
+ case "regex": {
4195
+ if (def.flags.replace(/i/g, "") !== "") return null;
4196
+ if (regexMatchesEmpty(def.source, def.flags)) return null;
4197
+ return {
4198
+ source: `(?:${def.source})`,
4199
+ caseInsensitive: def.flags.includes("i"),
4200
+ rawCaseSensitive: def.flags.includes("i") ? "" : def.source
4201
+ };
4202
+ }
4203
+ default:
4204
+ return null;
4205
+ }
4206
+ }
4207
+ function tokenSequenceSource(p) {
4208
+ const def = p._def;
4209
+ const parts = def.tag === "sequence" ? def.parsers.map(tokenTerminalSource) : [tokenTerminalSource(p)];
4210
+ if (parts.some((part) => part === null)) return null;
4211
+ const typed = parts;
4212
+ const flags = typed.some((part) => part.caseInsensitive) ? "i" : "";
4213
+ if (flags.includes("i") && typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))) return null;
4214
+ return {
4215
+ source: typed.map((part) => part.source).join(""),
4216
+ flags,
4217
+ hasCaseSensitiveLetters: typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))
4218
+ };
4219
+ }
4220
+ function combineTokenRegexSources(parts) {
4221
+ const flags = parts.some((part) => part.flags.includes("i")) ? "i" : "";
4222
+ if (flags.includes("i") && parts.some((part) => part.flags === "" && part.hasCaseSensitiveLetters)) return null;
4223
+ return { source: parts.map((part) => part.source).join(""), flags };
4224
+ }
4225
+ function tokenNullableRegex(def) {
4226
+ const inner = def.parser._def;
4227
+ if (inner.tag === "many") {
4228
+ const seq = tokenSequenceSource(inner.parser);
4229
+ if (!seq) return null;
4230
+ return { tag: "regex", source: `(?:${seq.source})*`, flags: seq.flags };
4231
+ }
4232
+ if (inner.tag === "optional") {
4233
+ const seq = tokenSequenceSource(inner.parser);
4234
+ if (!seq) return null;
4235
+ return { tag: "regex", source: `(?:${seq.source})?`, flags: seq.flags };
4236
+ }
4237
+ if (inner.tag === "sepBy") {
4238
+ const item = tokenSequenceSource(inner.parser);
4239
+ const sep = tokenSequenceSource(inner.separator);
4240
+ if (!item || !sep) return null;
4241
+ const combined = combineTokenRegexSources([item, sep]);
4242
+ if (!combined) return null;
4243
+ return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
4244
+ }
4245
+ return null;
4246
+ }
4247
+ function emitToken(def, ctx, pos) {
4248
+ const lowered = tokenNullableRegex(def);
4249
+ if (lowered) return emitRegex(lowered, ctx, pos);
4250
+ const savedTrivia = ctx.activeTrivia;
4251
+ const savedKindLabels = ctx.triviaKindLabels;
4252
+ ctx.activeTrivia = void 0;
4253
+ ctx.triviaKindLabels = void 0;
4254
+ const inner = emitFallible(def.parser, ctx, pos);
4255
+ ctx.activeTrivia = savedTrivia;
4256
+ ctx.triviaKindLabels = savedKindLabels;
4257
+ const i = ind(ctx);
4258
+ const sc = v(ctx, "_tokCh");
4259
+ const sl = v(ctx, "_tokLv");
4260
+ const sr = v(ctx, "_tokRaw");
4261
+ const sv = v(ctx, "_tokTr");
4262
+ const sk = v(ctx, "_tokKinds");
4263
+ const stl = v(ctx, "_tokTl");
4264
+ const sol = v(ctx, "_tokLog");
4265
+ const sb = v(ctx, "_tokBuf");
4266
+ const valV = v(ctx, "_tok");
4267
+ return {
4268
+ stmts: [
4269
+ `${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`,
4270
+ `${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`,
4271
+ `${i}try {`,
4272
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
4273
+ `${i}} finally {`,
4274
+ `${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}`,
4275
+ `${i}}`,
4276
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
4277
+ `${i}const ${valV} = input.slice(${pos}, ${inner.endVar})`,
4278
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
4279
+ ],
4280
+ valueVar: valV,
4281
+ endVar: inner.endVar
4282
+ };
4283
+ }
4010
4284
  function emitNode(def, ctx, pos) {
4011
4285
  const structural = def.build === void 0;
4012
4286
  const mkType = structural ? null : analyzeMkInlineBuild(def);
@@ -4126,7 +4400,7 @@ function emitRecover(def, ctx, pos) {
4126
4400
  `${ind0} if (${sentOk}) break`,
4127
4401
  `${ind0} ${scanV}++`,
4128
4402
  `${ind0} }`,
4129
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
4403
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(deriveExpected(def.parser))} }`,
4130
4404
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
4131
4405
  `${ind0} ${valVar} = ${errV}`,
4132
4406
  `${ind0} ${endVar} = ${scanV}`,
@@ -4152,6 +4426,32 @@ function emitExpect(def, ctx, pos) {
4152
4426
  return { stmts, valueVar: valVar, endVar };
4153
4427
  }
4154
4428
  function emit(p, ctx, pos) {
4429
+ const usage = ctx.lazyUsage;
4430
+ 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
4431
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4432
+ // faster than it calls and barely moves the byte count; the size explosion
4433
+ // is all in LARGE choices/sequences referenced from many positions.
4434
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4435
+ const existing = ctx.namedParsers.get(p);
4436
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4437
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4438
+ ctx.namedParsers.set(p, fnName);
4439
+ const savedIndent = ctx.indent;
4440
+ const savedFailLabel = ctx.failLabel;
4441
+ const savedRecord = ctx.recordFail;
4442
+ ctx.indent = 1;
4443
+ ctx.failLabel = "_pfail";
4444
+ ctx.recordFail = true;
4445
+ const r = emitDispatch(p, ctx, "_pos");
4446
+ ctx.indent = savedIndent;
4447
+ ctx.failLabel = savedFailLabel;
4448
+ ctx.recordFail = savedRecord;
4449
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4450
+ return emitNamedFnCall(ctx, fnName, pos);
4451
+ }
4452
+ return emitDispatch(p, ctx, pos);
4453
+ }
4454
+ function emitDispatch(p, ctx, pos) {
4155
4455
  const def = p._def;
4156
4456
  switch (def.tag) {
4157
4457
  case "literal":
@@ -4197,9 +4497,7 @@ function emit(p, ctx, pos) {
4197
4497
  };
4198
4498
  }
4199
4499
  }
4200
- const fnIdx = ctx.mapFns.length;
4201
- ctx.mapFns.push(def.fn);
4202
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4500
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4203
4501
  const mv = v(ctx, "_mapped");
4204
4502
  return {
4205
4503
  stmts: [
@@ -4232,6 +4530,8 @@ function emit(p, ctx, pos) {
4232
4530
  return emitLazy(p, def, ctx, pos);
4233
4531
  case "trivia":
4234
4532
  return emit(def.parser, ctx, pos);
4533
+ case "token":
4534
+ return emitToken(def, ctx, pos);
4235
4535
  case "label":
4236
4536
  return emit(def.parser, ctx, pos);
4237
4537
  case "grammar": {
@@ -4264,6 +4564,7 @@ function emit(p, ctx, pos) {
4264
4564
  case "guard": {
4265
4565
  const fnIdx = ctx.mapFns.length;
4266
4566
  ctx.mapFns.push(def.predicate);
4567
+ ctx.mapFnSrcs.push(null);
4267
4568
  const vv = v(ctx);
4268
4569
  return {
4269
4570
  stmts: [
@@ -4278,6 +4579,7 @@ function emit(p, ctx, pos) {
4278
4579
  const evIdx = ctx.mapFns.length;
4279
4580
  const extra = def.extra;
4280
4581
  ctx.mapFns.push((() => extra));
4582
+ ctx.mapFnSrcs.push(null);
4281
4583
  const innerParser = def.parser;
4282
4584
  if (!ctx.namedParsers.has(innerParser)) {
4283
4585
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4331,6 +4633,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4331
4633
  }
4332
4634
  case "grammar":
4333
4635
  case "trivia":
4636
+ case "token":
4334
4637
  case "label":
4335
4638
  case "optional":
4336
4639
  case "many":
@@ -4366,6 +4669,7 @@ function childrenOf(def) {
4366
4669
  case "optional":
4367
4670
  case "transform":
4368
4671
  case "trivia":
4672
+ case "token":
4369
4673
  case "label":
4370
4674
  case "grammar":
4371
4675
  case "not":
@@ -4397,9 +4701,58 @@ function childrenOf(def) {
4397
4701
  return [];
4398
4702
  }
4399
4703
  }
4704
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4705
+ "sequence",
4706
+ "choice",
4707
+ "many",
4708
+ "oneOrMore",
4709
+ "optional",
4710
+ "sepBy",
4711
+ "transform",
4712
+ "node",
4713
+ "not"
4714
+ ]);
4715
+ function isHoistableTag(tag) {
4716
+ return HOISTABLE_TAGS.has(tag);
4717
+ }
4718
+ var HOIST_MIN_SUBTREE = 3;
4400
4719
  function analyzeLazyUsage(root) {
4401
4720
  return analyzeLazyUsageMulti([root]);
4402
4721
  }
4722
+ function subtreeSizes(roots) {
4723
+ const sizes = /* @__PURE__ */ new Map();
4724
+ const sz = (p) => {
4725
+ const cached = sizes.get(p);
4726
+ if (cached !== void 0) return cached;
4727
+ const def = p._def;
4728
+ let s = 1;
4729
+ if (def.tag !== "lazy") {
4730
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4731
+ }
4732
+ sizes.set(p, s);
4733
+ return s;
4734
+ };
4735
+ const visitedLazy = /* @__PURE__ */ new Set();
4736
+ const visit = (p) => {
4737
+ const def = p._def;
4738
+ if (def.tag === "lazy") {
4739
+ if (visitedLazy.has(p)) return;
4740
+ visitedLazy.add(p);
4741
+ let resolved;
4742
+ try {
4743
+ resolved = def.thunk();
4744
+ } catch {
4745
+ return;
4746
+ }
4747
+ visit(resolved);
4748
+ return;
4749
+ }
4750
+ sz(p);
4751
+ for (const child of childrenOf(def)) visit(child);
4752
+ };
4753
+ for (const root of roots) visit(root);
4754
+ return sizes;
4755
+ }
4403
4756
  function analyzeLazyUsageMulti(roots) {
4404
4757
  const counts = /* @__PURE__ */ new Map();
4405
4758
  const recursive = /* @__PURE__ */ new Set();
@@ -4426,10 +4779,14 @@ function analyzeLazyUsageMulti(roots) {
4426
4779
  active.delete(p);
4427
4780
  return;
4428
4781
  }
4782
+ if (isHoistableTag(def.tag)) {
4783
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4784
+ }
4429
4785
  for (const child of childrenOf(def)) walk2(child);
4430
4786
  }
4431
- for (const root of roots) walk2(root);
4432
- return { counts, recursive };
4787
+ const rootsArr = [...roots];
4788
+ for (const root of rootsArr) walk2(root);
4789
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4433
4790
  }
4434
4791
  function ruleDependencies(ruleMap) {
4435
4792
  const nameOf = /* @__PURE__ */ new Map();
@@ -4828,64 +5185,6 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4828
5185
  ].join("\n");
4829
5186
  }
4830
5187
 
4831
- // src/compiler/linker.ts
4832
- function fusedBody(pieces) {
4833
- const winner = /* @__PURE__ */ new Map();
4834
- for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4835
- for (const [k, p] of winner) {
4836
- for (const d of p.deps.get(k) ?? []) {
4837
- if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4838
- }
4839
- }
4840
- const contributing = new Set(winner.values());
4841
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4842
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4843
- const lines = [
4844
- // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4845
- "const _pfFail = {}",
4846
- "let _pfEnd",
4847
- ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4848
- ...needsHostReads ? [HOST_READS_DECL] : [],
4849
- // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4850
- ...[...contributing].flatMap((p) => p.prelude),
4851
- // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4852
- ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4853
- ];
4854
- const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4855
- const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4856
- const finalFS = /* @__PURE__ */ new Map();
4857
- for (const [k, p] of winner) {
4858
- const fs2 = p.firstSets?.get(k);
4859
- if (fs2) finalFS.set(k, fs2);
4860
- }
4861
- const body = rawBody.replace(
4862
- // Rule name + code-point var are both JS identifiers (rule names are validated
4863
- // at compile time — see assertRuleName in codegen), so an identifier class
4864
- // matches every well-formed placeholder.
4865
- /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4866
- (_m, name, codevar) => {
4867
- const fs2 = finalFS.get(name);
4868
- if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4869
- return `(${firstSetCond(codevar, fs2)})`;
4870
- }
4871
- );
4872
- const env = {};
4873
- for (const p of contributing) {
4874
- if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4875
- if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4876
- }
4877
- return { body, env };
4878
- }
4879
- function emitFusedSource(pieces) {
4880
- const { body, env } = fusedBody(pieces);
4881
- if (Object.keys(env).length > 0) {
4882
- throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4883
- }
4884
- return `/* @__PURE__ */ (() => {
4885
- ${body}
4886
- })()`;
4887
- }
4888
-
4889
5188
  // src/combinators/scanTo.ts
4890
5189
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4891
5190
  const meta = {
@@ -4954,7 +5253,7 @@ function balanced(open, close, options = {}) {
4954
5253
  const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
4955
5254
  const inner = many(choice(self, ...skips, content));
4956
5255
  const combi = transform(
4957
- sequence(literal(open), inner, expect(literal(close), close)),
5256
+ sequence(literal(open), inner, expect(literal(close))),
4958
5257
  // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
4959
5258
  // ParseError (recovered close). `c` is the close string or a ParseError.
4960
5259
  ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
@@ -4980,6 +5279,359 @@ function escapeClassChar(ch) {
4980
5279
  return ch.replace(/[\\\]^-]/g, "\\$&");
4981
5280
  }
4982
5281
 
5282
+ // src/compiler/ir-serialize.ts
5283
+ function childrenOf2(def) {
5284
+ switch (def.tag) {
5285
+ case "sequence":
5286
+ case "choice":
5287
+ return def.parsers;
5288
+ case "many":
5289
+ case "oneOrMore":
5290
+ case "optional":
5291
+ case "transform":
5292
+ case "trivia":
5293
+ case "token":
5294
+ case "label":
5295
+ case "not":
5296
+ case "node":
5297
+ case "expect":
5298
+ return [def.parser];
5299
+ case "grammar":
5300
+ return def.triviaParser ? [def.parser, def.triviaParser] : [def.parser];
5301
+ case "sepBy":
5302
+ return [def.parser, def.separator];
5303
+ case "skip":
5304
+ return [def.main, def.skipped];
5305
+ case "scanTo":
5306
+ return [def.sentinel, ...def.skip];
5307
+ case "lazy":
5308
+ case "literal":
5309
+ case "regex":
5310
+ case "keywords":
5311
+ case "guard":
5312
+ case "withCtx":
5313
+ case "recover":
5314
+ case "unknown":
5315
+ return [];
5316
+ }
5317
+ }
5318
+ var ruleNameOf = (c) => c._ruleName;
5319
+ function lazyTarget(c) {
5320
+ if (c._def.tag !== "lazy") return null;
5321
+ try {
5322
+ return c._def.thunk();
5323
+ } catch {
5324
+ return null;
5325
+ }
5326
+ }
5327
+ var Unserializable = class extends Error {
5328
+ };
5329
+ function serializeRuleMap(ruleMap) {
5330
+ try {
5331
+ return new Serializer(ruleMap).run();
5332
+ } catch (e) {
5333
+ if (e instanceof Unserializable) {
5334
+ if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
5335
+ return null;
5336
+ }
5337
+ throw e;
5338
+ }
5339
+ }
5340
+ function evalRuleMapIR(ir) {
5341
+ const _tf = (child, src) => {
5342
+ let fn2;
5343
+ try {
5344
+ fn2 = (0, eval)(`(${src})`);
5345
+ } catch {
5346
+ fn2 = () => {
5347
+ throw new Error("IR transform fn not materialized");
5348
+ };
5349
+ }
5350
+ const t = transform(child, fn2);
5351
+ t._def.fnSrc = src;
5352
+ return t;
5353
+ };
5354
+ const _nd = (type, child, src, opts) => {
5355
+ const n = node(type, child, void 0, opts);
5356
+ n._def.buildSrc = src;
5357
+ return n;
5358
+ };
5359
+ const fn = new Function(
5360
+ "rules",
5361
+ "ref",
5362
+ "regex",
5363
+ "literal",
5364
+ "keywords",
5365
+ "sequence",
5366
+ "choice",
5367
+ "many",
5368
+ "oneOrMore",
5369
+ "optional",
5370
+ "sepBy",
5371
+ "not",
5372
+ "node",
5373
+ "parser",
5374
+ "scanTo",
5375
+ "token",
5376
+ "transform",
5377
+ "skip",
5378
+ "trivia",
5379
+ "label",
5380
+ "expect",
5381
+ "_tf",
5382
+ "_nd",
5383
+ `return (${ir})`
5384
+ );
5385
+ const map = fn(
5386
+ rules,
5387
+ ref,
5388
+ regex,
5389
+ literal,
5390
+ keywords,
5391
+ sequence,
5392
+ choice,
5393
+ many,
5394
+ oneOrMore,
5395
+ optional,
5396
+ sepBy,
5397
+ not,
5398
+ node,
5399
+ parser,
5400
+ scanTo,
5401
+ token,
5402
+ transform,
5403
+ skip,
5404
+ trivia,
5405
+ label,
5406
+ expect,
5407
+ _tf,
5408
+ _nd
5409
+ );
5410
+ return Object.entries(map);
5411
+ }
5412
+ var Serializer = class {
5413
+ counts = /* @__PURE__ */ new Map();
5414
+ selfRef = /* @__PURE__ */ new Set();
5415
+ // combinators reached by an unnamed lazy pointing back into their own subtree
5416
+ constName = /* @__PURE__ */ new Map();
5417
+ emitted = /* @__PURE__ */ new Set();
5418
+ decls = [];
5419
+ ruleValues = /* @__PURE__ */ new Set();
5420
+ ruleMap;
5421
+ constructor(ruleMap) {
5422
+ this.ruleMap = ruleMap;
5423
+ for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
5424
+ }
5425
+ /** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
5426
+ * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
5427
+ * `g[name]` ref — only the top-level entry is unwrapped. */
5428
+ body(c) {
5429
+ return c._def.tag === "lazy" && ruleNameOf(c) !== void 0 ? lazyTarget(c) ?? c : c;
5430
+ }
5431
+ run() {
5432
+ for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
5433
+ let n = 0;
5434
+ for (const [c, count] of this.counts) {
5435
+ if (this.ruleValues.has(c)) continue;
5436
+ if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
5437
+ }
5438
+ const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
5439
+ if (this.decls.length === 0) return `rules((g) => ({
5440
+ ${body}
5441
+ }))`;
5442
+ return `rules((g) => {
5443
+ ${this.decls.map((d) => " " + d).join("\n")}
5444
+ return ({
5445
+ ${body}
5446
+ })
5447
+ })`;
5448
+ }
5449
+ /** Count identity references and flag self-referential subtrees. `active` is the
5450
+ * set of combinators on the current DFS path (to detect a lazy pointing back). */
5451
+ analyze(c, active) {
5452
+ const def = c._def;
5453
+ if (def.tag === "lazy") {
5454
+ if (ruleNameOf(c) !== void 0) return;
5455
+ const target = lazyTarget(c);
5456
+ if (!target) throw new Unserializable("unresolved unnamed ref");
5457
+ if (active.has(target)) {
5458
+ this.selfRef.add(target);
5459
+ return;
5460
+ }
5461
+ this.analyze(target, active);
5462
+ return;
5463
+ }
5464
+ if (def.tag === "guard" || def.tag === "withCtx" || def.tag === "recover" || def.tag === "unknown") {
5465
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5466
+ }
5467
+ this.counts.set(c, (this.counts.get(c) ?? 0) + 1);
5468
+ if ((this.counts.get(c) ?? 0) > 1) return;
5469
+ active.add(c);
5470
+ for (const child of childrenOf2(def)) this.analyze(child, active);
5471
+ active.delete(c);
5472
+ }
5473
+ /** Reference `c`: a const var if it has one (emitting its decl first), else inline.
5474
+ * `selfOf` is the const currently being wrapped in a ref()/define closure — an
5475
+ * unnamed lazy back into it emits the local ref var instead of recursing. */
5476
+ ref(c, selfOf) {
5477
+ const cn = this.constName.get(c);
5478
+ if (cn) {
5479
+ this.emitDecl(c);
5480
+ return cn;
5481
+ }
5482
+ return this.expr(c, selfOf);
5483
+ }
5484
+ emitDecl(c) {
5485
+ const name = this.constName.get(c);
5486
+ if (this.emitted.has(name)) return;
5487
+ this.emitted.add(name);
5488
+ this.decls.push(`const ${name} = ${this.wrap(c)}`);
5489
+ }
5490
+ selfN = 0;
5491
+ /** Emit `c`'s own constructor call, wrapping it in a `ref()`+`.define()` closure
5492
+ * if it is self-referential (an unnamed lazy in its subtree points back to it) —
5493
+ * the shape `balanced()` and other recursive library combinators build. */
5494
+ wrap(c) {
5495
+ if (!this.selfRef.has(c)) return this.expr(c, null);
5496
+ const rv = `_rr${this.selfN++}`;
5497
+ const bodyExpr = this.expr(c, { comb: c, var: rv });
5498
+ return `(() => { const ${rv} = ref(); const _b = ${bodyExpr}; ${rv}.define(_b); return _b })()`;
5499
+ }
5500
+ /** Emit the constructor-call source for `c` (never a const shortcut for `c` itself). */
5501
+ expr(c, selfOf) {
5502
+ const def = c._def;
5503
+ const kid = (x) => this.ref(x, selfOf);
5504
+ switch (def.tag) {
5505
+ case "lazy": {
5506
+ if (ruleNameOf(c) !== void 0) return `g[${JSON.stringify(ruleNameOf(c))}]`;
5507
+ const target = lazyTarget(c);
5508
+ if (selfOf && target === selfOf.comb) return selfOf.var;
5509
+ return this.ref(target, selfOf);
5510
+ }
5511
+ case "literal":
5512
+ return `literal(${JSON.stringify(def.value)}${def.caseInsensitive ? ", { caseInsensitive: true }" : ""})`;
5513
+ case "regex":
5514
+ return `regex(${JSON.stringify(def.source)}, ${JSON.stringify(def.flags)})`;
5515
+ case "keywords":
5516
+ return `keywords(${JSON.stringify(def.words)}, { caseInsensitive: ${def.caseInsensitive}${def.boundary !== void 0 ? `, boundary: ${JSON.stringify(def.boundary)}` : ""} })`;
5517
+ case "sequence":
5518
+ return `sequence(${def.parsers.map(kid).join(", ")})`;
5519
+ case "choice": {
5520
+ if (def.gates.some((g) => g !== null)) throw new Unserializable("choice with a gate() has no source");
5521
+ return `choice(${def.parsers.map(kid).join(", ")})`;
5522
+ }
5523
+ case "many":
5524
+ return `many(${kid(def.parser)})`;
5525
+ case "oneOrMore":
5526
+ return `oneOrMore(${kid(def.parser)})`;
5527
+ case "optional":
5528
+ return `optional(${kid(def.parser)})`;
5529
+ case "sepBy":
5530
+ return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
5531
+ case "not":
5532
+ return `not(${kid(def.parser)})`;
5533
+ case "trivia":
5534
+ return `trivia(${kid(def.parser)})`;
5535
+ case "token":
5536
+ return `token(${kid(def.parser)})`;
5537
+ case "label":
5538
+ return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
5539
+ case "expect":
5540
+ return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
5541
+ case "skip":
5542
+ return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
5543
+ case "scanTo":
5544
+ return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
5545
+ case "transform": {
5546
+ if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
5547
+ return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
5548
+ }
5549
+ case "node": {
5550
+ if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
5551
+ const opts = def.collapse ? `, { collapse: true }` : "";
5552
+ if (def.buildSrc !== void 0) return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts})`;
5553
+ return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
5554
+ }
5555
+ case "grammar": {
5556
+ const trivia2 = def.clearTrivia ? "null" : def.triviaParser ? kid(def.triviaParser) : "undefined";
5557
+ return `parser({ trivia: ${trivia2}${def.trackLines ? ", trackLines: true" : ""} }, ${kid(def.parser)})`;
5558
+ }
5559
+ case "guard":
5560
+ case "withCtx":
5561
+ case "recover":
5562
+ case "unknown":
5563
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5564
+ }
5565
+ }
5566
+ };
5567
+
5568
+ // src/compiler/linker.ts
5569
+ function fusedBody(pieces) {
5570
+ const winner = /* @__PURE__ */ new Map();
5571
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
5572
+ for (const [k, p] of winner) {
5573
+ for (const d of p.deps.get(k) ?? []) {
5574
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
5575
+ }
5576
+ }
5577
+ const contributing = new Set(winner.values());
5578
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5579
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5580
+ const lines = [
5581
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5582
+ "const _pfFail = {}",
5583
+ "let _pfEnd",
5584
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5585
+ ...needsHostReads ? [HOST_READS_DECL] : [],
5586
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5587
+ ...[...contributing].flatMap((p) => p.prelude),
5588
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5589
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5590
+ ];
5591
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
5592
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
5593
+ const finalFS = /* @__PURE__ */ new Map();
5594
+ for (const [k, p] of winner) {
5595
+ const fs2 = p.firstSets?.get(k);
5596
+ if (fs2) finalFS.set(k, fs2);
5597
+ }
5598
+ const body = rawBody.replace(
5599
+ // Rule name + code-point var are both JS identifiers (rule names are validated
5600
+ // at compile time — see assertRuleName in codegen), so an identifier class
5601
+ // matches every well-formed placeholder.
5602
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
5603
+ (_m, name, codevar) => {
5604
+ const fs2 = finalFS.get(name);
5605
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
5606
+ return `(${firstSetCond(codevar, fs2)})`;
5607
+ }
5608
+ );
5609
+ const env = {};
5610
+ for (const p of contributing) {
5611
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5612
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5613
+ }
5614
+ return { body, env };
5615
+ }
5616
+ function emitFusedSource(pieces) {
5617
+ const { body, env } = fusedBody(pieces);
5618
+ if (Object.keys(env).length > 0) {
5619
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
5620
+ }
5621
+ return `/* @__PURE__ */ (() => {
5622
+ ${body}
5623
+ })()`;
5624
+ }
5625
+ function isIRPiece(p) {
5626
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5627
+ }
5628
+ function materializePiece(p) {
5629
+ if (!isIRPiece(p)) return p;
5630
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5631
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5632
+ return pieces;
5633
+ }
5634
+
4983
5635
  // src/cst/offset-model.ts
4984
5636
  var { isFinite } = Number;
4985
5637
 
@@ -5004,6 +5656,7 @@ var SUPPORTED = {
5004
5656
  trivia: (...a) => trivia(a[0]),
5005
5657
  label: (...a) => label(a[0], a[1]),
5006
5658
  noTrivia: (...a) => noTrivia(a[0]),
5659
+ token: (...a) => token(a[0]),
5007
5660
  expect: (...a) => expect(a[0], a[1])
5008
5661
  };
5009
5662
  function isScopeEntry(v2) {
@@ -5553,6 +6206,14 @@ function findCarriedPiecesLiteral(root) {
5553
6206
  const obj = callee?.object?.name;
5554
6207
  const prop = callee?.property?.name;
5555
6208
  const args = node2.arguments;
6209
+ if (obj === "Object" && prop === "assign" && args && args[1]?.type === "ObjectExpression") {
6210
+ for (const p of args[1].properties ?? []) {
6211
+ if (p.computed && isComposedPiecesSymbol(p.key) && p.value) {
6212
+ found = { start: p.value.start, end: p.value.end };
6213
+ return;
6214
+ }
6215
+ }
6216
+ }
5556
6217
  if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5557
6218
  const descriptor = args[2];
5558
6219
  for (const p of descriptor?.properties ?? []) {
@@ -5573,6 +6234,20 @@ function findCarriedPiecesLiteral(root) {
5573
6234
  visit(root);
5574
6235
  return found;
5575
6236
  }
6237
+ function extractImportBindings(body) {
6238
+ const out = /* @__PURE__ */ new Map();
6239
+ for (const stmt of body) {
6240
+ if (stmt.type !== "ImportDeclaration") continue;
6241
+ const s = stmt;
6242
+ for (const spec of s.specifiers) {
6243
+ if (spec.type === "ImportSpecifier") {
6244
+ const imported = spec.imported.name ?? spec.local.name;
6245
+ out.set(spec.local.name, { source: s.source.value, imported });
6246
+ }
6247
+ }
6248
+ }
6249
+ return out;
6250
+ }
5576
6251
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5577
6252
  let result;
5578
6253
  try {
@@ -5647,14 +6322,57 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5647
6322
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5648
6323
  const localRuleMaps = /* @__PURE__ */ new Map();
5649
6324
  const nsFor = (label2) => `_${(0, import_node_crypto.createHash)("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
6325
+ const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
5650
6326
  const serializePieces = (p) => {
5651
- const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5652
- return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude)}, ruleFns: ${mapLit(p.ruleFns)}, wrappers: ${mapLit(p.wrappers)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, mfFns: [], buildFns: [] }`;
6327
+ const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
6328
+ ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
6329
+ ).join(", ")}])`;
6330
+ return `{ ns: ${JSON.stringify(p.ns)}, keys: ${JSON.stringify(p.keys)}, prelude: ${JSON.stringify(p.prelude.map(stripIndent))}, ruleFns: ${mapLit(p.ruleFns, true)}, wrappers: ${mapLit(p.wrappers, true)}, firstSets: ${mapLit(p.firstSets)}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, mfFns: [], buildFns: [] }`;
5653
6331
  };
5654
- const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5655
- const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
6332
+ const isSpread = (it) => "__spreadLocal" in it;
6333
+ const isIR = (it) => "ir" in it && !("ruleFns" in it);
6334
+ const serializeItem = (it) => isSpread(it) ? `...(${it.__spreadLocal}[Symbol.for('parseman.composedPieces')] ?? [])` : isIR(it) ? `{ ns: ${JSON.stringify(it.ns)}, ir: ${JSON.stringify(it.ir)} }` : serializePieces(it);
6335
+ const serializeList = (list) => `[${list.map(serializeItem).join(", ")}]`;
6336
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
5656
6337
  const localComposedPieces = /* @__PURE__ */ new Map();
5657
6338
  const importedPiecesCache = /* @__PURE__ */ new Map();
6339
+ const resolveModulePieces = (file, exportName, seen) => {
6340
+ const mod = parseModuleCached(file);
6341
+ if (!mod) return null;
6342
+ const localFor = exportLocalName(mod.body, exportName);
6343
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
6344
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
6345
+ if (!literalRange) return null;
6346
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
6347
+ const imports = extractImportBindings(mod.body);
6348
+ const stubNames = [];
6349
+ const stubVals = [];
6350
+ const spreadRe = /\.\.\.\s*\(?\s*([A-Za-z_$][\w$]*)\s*\[\s*Symbol\s*\.\s*for\s*\(\s*['"]parseman\.composedPieces['"]/g;
6351
+ const done = /* @__PURE__ */ new Set();
6352
+ for (let m; m = spreadRe.exec(literal2); ) {
6353
+ const local = m[1];
6354
+ if (done.has(local)) continue;
6355
+ done.add(local);
6356
+ const b = imports.get(local);
6357
+ let subPieces = [];
6358
+ if (b) {
6359
+ let subFile = null;
6360
+ try {
6361
+ subFile = getCompiledResolver().resolveFileSync(file, b.source).path ?? null;
6362
+ } catch {
6363
+ }
6364
+ if (subFile && !seen.has(subFile)) subPieces = resolveModulePieces(subFile, b.imported, new Set(seen).add(subFile)) ?? [];
6365
+ }
6366
+ stubNames.push(local);
6367
+ stubVals.push({ [/* @__PURE__ */ Symbol.for("parseman.composedPieces")]: subPieces });
6368
+ }
6369
+ try {
6370
+ const list = new Function(...stubNames, `return (${literal2})`)(...stubVals);
6371
+ return list.map(materializePiece);
6372
+ } catch {
6373
+ return null;
6374
+ }
6375
+ };
5658
6376
  const importedPieces = (localName) => {
5659
6377
  if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5660
6378
  let result2 = null;
@@ -5665,41 +6383,34 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5665
6383
  file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5666
6384
  } catch {
5667
6385
  }
5668
- const mod = file ? parseModuleCached(file) : null;
5669
- if (mod) {
5670
- const localFor = exportLocalName(mod.body, binding.imported);
5671
- const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5672
- const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5673
- if (literalRange) {
5674
- const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5675
- try {
5676
- result2 = new Function(`return (${literal2})`)();
5677
- } catch {
5678
- result2 = null;
5679
- }
5680
- }
5681
- }
6386
+ result2 = file ? resolveModulePieces(file, binding.imported, /* @__PURE__ */ new Set([file])) : null;
5682
6387
  }
5683
6388
  importedPiecesCache.set(localName, result2);
5684
6389
  return result2;
5685
6390
  };
6391
+ const localArg = (rm, label2) => {
6392
+ const entries = [...rm];
6393
+ const ns = nsFor(label2);
6394
+ const p = compileLinkable(entries, ns);
6395
+ if (!p) return null;
6396
+ const ir = serializeRuleMap(entries);
6397
+ return ir ? { pieces: [p], ir: { ns, ir } } : { pieces: [p] };
6398
+ };
5686
6399
  const argPieces = (arg, label2) => {
5687
6400
  if (isRulesCall(arg)) {
5688
6401
  const factory = arg.arguments[0];
5689
6402
  const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5690
- const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5691
- return p ? [p] : null;
6403
+ return rm ? localArg(rm, label2) : null;
5692
6404
  }
5693
6405
  if (arg.type === "Identifier") {
5694
6406
  const name = arg.name;
5695
6407
  const rm = localRuleMaps.get(name);
5696
- if (rm) {
5697
- const p = compileLinkable([...rm], nsFor(label2));
5698
- return p ? [p] : null;
5699
- }
6408
+ if (rm) return localArg(rm, label2);
5700
6409
  const composed = localComposedPieces.get(name);
5701
- if (composed) return composed;
5702
- return importedPieces(name);
6410
+ if (composed) return { pieces: composed };
6411
+ const pieces = importedPieces(name);
6412
+ if (!pieces) return null;
6413
+ return importBindings.has(name) ? { pieces, spreadLocal: name } : { pieces };
5703
6414
  }
5704
6415
  return null;
5705
6416
  };
@@ -5712,16 +6423,20 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5712
6423
  }
5713
6424
  const elements = arr.elements;
5714
6425
  const pieces = [];
6426
+ const carried = [];
5715
6427
  for (let i = 0; i < elements.length; i++) {
5716
- const list = argPieces(elements[i], `compose${init.start}_${i}`);
5717
- if (!list) {
6428
+ const r = argPieces(elements[i], `compose${init.start}_${i}`);
6429
+ if (!r) {
5718
6430
  warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5719
6431
  return null;
5720
6432
  }
5721
- pieces.push(...list);
6433
+ pieces.push(...r.pieces);
6434
+ if (r.spreadLocal) carried.push({ __spreadLocal: r.spreadLocal });
6435
+ else if (r.ir) carried.push(r.ir);
6436
+ else carried.push(...r.pieces);
5722
6437
  }
5723
6438
  try {
5724
- return { replacement: emitFusedSource(pieces), pieces };
6439
+ return { replacement: emitFusedSource(pieces), pieces, carried };
5725
6440
  } catch (e) {
5726
6441
  warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5727
6442
  return null;
@@ -5808,9 +6523,11 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5808
6523
  localRuleMaps.set(varName, compiledRules.ruleMap);
5809
6524
  let replacement = compiledRules.replacement;
5810
6525
  if (exportPrefix) {
5811
- const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
6526
+ const ns = nsFor(varName);
6527
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns);
5812
6528
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5813
- replacement = withCarriedPieces(replacement, [pieces]);
6529
+ const ir = serializeRuleMap([...compiledRules.ruleMap]);
6530
+ replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
5814
6531
  }
5815
6532
  }
5816
6533
  replacements.push({ start: init.start, end: init.end, replacement });
@@ -5820,7 +6537,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5820
6537
  const fused = compileComposeCall(init);
5821
6538
  if (!fused) continue;
5822
6539
  localComposedPieces.set(varName, fused.pieces);
5823
- const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
6540
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
5824
6541
  replacements.push({ start: init.start, end: init.end, replacement });
5825
6542
  continue;
5826
6543
  }