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.
@@ -65,6 +65,7 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
65
65
  case "transform":
66
66
  case "label":
67
67
  case "trivia":
68
+ case "token":
68
69
  case "expect":
69
70
  case "withCtx":
70
71
  case "node":
@@ -91,6 +92,55 @@ function sequenceFirstSet(parsers) {
91
92
  }
92
93
  return fs2;
93
94
  }
95
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
96
+ if (seen.has(p)) return any();
97
+ seen.add(p);
98
+ const fs2 = (c) => firstSetOf(c, seen);
99
+ const d = p._def;
100
+ switch (d.tag) {
101
+ case "literal":
102
+ case "regex":
103
+ case "keywords":
104
+ return p._meta.firstSet;
105
+ // terminals: no refs, cached set is exact
106
+ case "lazy":
107
+ try {
108
+ return fs2(d.thunk());
109
+ } catch {
110
+ return any();
111
+ }
112
+ case "choice": {
113
+ let out = empty();
114
+ for (const arm of d.parsers) out = union(out, fs2(arm));
115
+ return out;
116
+ }
117
+ case "sequence": {
118
+ let out = empty();
119
+ for (const term of d.parsers) {
120
+ out = union(out, fs2(term));
121
+ if (!matchesEmpty(term)) return out;
122
+ }
123
+ return out;
124
+ }
125
+ case "oneOrMore":
126
+ case "many":
127
+ case "optional":
128
+ case "transform":
129
+ case "label":
130
+ case "trivia":
131
+ case "token":
132
+ case "node":
133
+ case "grammar":
134
+ case "expect":
135
+ return fs2(d.parser);
136
+ case "sepBy":
137
+ return fs2(d.parser);
138
+ case "skip":
139
+ return fs2(d.main);
140
+ default:
141
+ return p._meta.firstSet;
142
+ }
143
+ }
94
144
  function mergeRanges(ranges) {
95
145
  if (ranges.length === 0) return [];
96
146
  const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
@@ -1202,6 +1252,7 @@ function markUnusedValues(root) {
1202
1252
  return;
1203
1253
  case "label":
1204
1254
  case "trivia":
1255
+ case "token":
1205
1256
  case "withCtx":
1206
1257
  case "expect":
1207
1258
  visit(def.parser, consumed);
@@ -1230,6 +1281,41 @@ function markUnusedValues(root) {
1230
1281
  visit(resolveRoot(root), true);
1231
1282
  }
1232
1283
 
1284
+ // src/combinators/parser.ts
1285
+ function rules(factory) {
1286
+ const cache = {};
1287
+ const proxy = new Proxy(cache, {
1288
+ get(target, key) {
1289
+ if (typeof key !== "string") return void 0;
1290
+ const record = target;
1291
+ if (!(key in record)) {
1292
+ const r = ref();
1293
+ r._ruleName = key;
1294
+ record[key] = r;
1295
+ }
1296
+ return record[key];
1297
+ }
1298
+ });
1299
+ const definitions = factory(proxy);
1300
+ for (const key of Object.keys(definitions)) {
1301
+ const placeholder = cache[key];
1302
+ const parser2 = definitions[key];
1303
+ if (placeholder !== void 0 && typeof placeholder.define === "function") {
1304
+ ;
1305
+ placeholder.define(parser2);
1306
+ placeholder._meta.firstSet = parser2._meta.firstSet;
1307
+ placeholder._meta.canMatchNewline = parser2._meta.canMatchNewline;
1308
+ } else {
1309
+ ;
1310
+ cache[key] = parser2;
1311
+ }
1312
+ }
1313
+ for (const key of Object.keys(definitions)) {
1314
+ markUnusedValues(cache[key]);
1315
+ }
1316
+ return cache;
1317
+ }
1318
+
1233
1319
  // src/combinators/not.ts
1234
1320
  function not(combinator) {
1235
1321
  const meta = {
@@ -1336,6 +1422,20 @@ function transform(combinator, fn) {
1336
1422
  }
1337
1423
  };
1338
1424
  }
1425
+ function skip(main, skipped) {
1426
+ return {
1427
+ _tag: "skip",
1428
+ _meta: main._meta,
1429
+ _def: { tag: "skip", main, skipped },
1430
+ parse(input, pos, ctx) {
1431
+ const result = main.parse(input, pos, ctx);
1432
+ if (!result.ok) return result;
1433
+ const s = skipped.parse(input, result.span.end, ctx);
1434
+ if (!s.ok) return result;
1435
+ return { ...result, span: { start: result.span.start, end: s.span.end } };
1436
+ }
1437
+ };
1438
+ }
1339
1439
  function trivia(combinator) {
1340
1440
  const kindLabels = analyzeLabeledTrivia(combinator)?.labels;
1341
1441
  return {
@@ -1430,8 +1530,60 @@ function noTrivia(root) {
1430
1530
  return parser({ trivia: null }, root);
1431
1531
  }
1432
1532
 
1533
+ // src/combinators/token.ts
1534
+ function token(root) {
1535
+ const meta = {
1536
+ firstSet: root._meta.firstSet,
1537
+ canMatchNewline: root._meta.canMatchNewline,
1538
+ isTrivia: false
1539
+ };
1540
+ return {
1541
+ _tag: "token",
1542
+ _meta: meta,
1543
+ _def: { tag: "token", parser: root },
1544
+ parse(input, pos, ctx) {
1545
+ const savedTrivia = ctx.trivia;
1546
+ const savedKinds = ctx.triviaKindLabels;
1547
+ const savedBuf = ctx._cstBuf;
1548
+ const savedChildren = ctx._cstChildren;
1549
+ const savedLeaves = ctx._cstLeaves;
1550
+ const savedRaw = ctx._cstRawChildren;
1551
+ const savedTriviaLog = ctx._cstTriviaLog;
1552
+ const savedOuterTriviaLog = ctx._triviaLog;
1553
+ const wasCapturing = cstCaptureActive(ctx);
1554
+ ctx.trivia = void 0;
1555
+ ctx.triviaKindLabels = void 0;
1556
+ ctx._cstBuf = void 0;
1557
+ ctx._cstChildren = void 0;
1558
+ ctx._cstLeaves = void 0;
1559
+ ctx._cstRawChildren = void 0;
1560
+ ctx._cstTriviaLog = void 0;
1561
+ delete ctx._triviaLog;
1562
+ let result;
1563
+ try {
1564
+ result = root.parse(input, pos, ctx);
1565
+ } finally {
1566
+ ctx.trivia = savedTrivia;
1567
+ ctx.triviaKindLabels = savedKinds;
1568
+ ctx._cstBuf = savedBuf;
1569
+ ctx._cstChildren = savedChildren;
1570
+ ctx._cstLeaves = savedLeaves;
1571
+ ctx._cstRawChildren = savedRaw;
1572
+ ctx._cstTriviaLog = savedTriviaLog;
1573
+ if (savedOuterTriviaLog === void 0) delete ctx._triviaLog;
1574
+ else ctx._triviaLog = savedOuterTriviaLog;
1575
+ }
1576
+ if (!result.ok) return result;
1577
+ const span = result.span;
1578
+ const value = input.slice(pos, span.end);
1579
+ if (wasCapturing) pushCstLeaf(ctx, { _tag: "leaf", value, span });
1580
+ return { ok: true, value, span };
1581
+ }
1582
+ };
1583
+ }
1584
+
1433
1585
  // src/combinators/expect.ts
1434
- function staticExpected(c) {
1586
+ function deriveExpected(c) {
1435
1587
  const def = c._def;
1436
1588
  switch (def.tag) {
1437
1589
  case "literal":
@@ -1443,22 +1595,23 @@ function staticExpected(c) {
1443
1595
  case "label":
1444
1596
  return [def.label];
1445
1597
  case "choice":
1446
- return def.parsers.flatMap(staticExpected);
1598
+ return def.parsers.flatMap(deriveExpected);
1447
1599
  case "sequence":
1448
- return def.parsers.length > 0 ? staticExpected(def.parsers[0]) : [];
1600
+ return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1449
1601
  case "node":
1450
1602
  case "grammar":
1451
1603
  case "trivia":
1604
+ case "token":
1452
1605
  case "optional":
1453
1606
  case "many":
1454
1607
  case "oneOrMore":
1455
1608
  case "transform":
1456
1609
  case "not":
1457
- return staticExpected(def.parser);
1610
+ return deriveExpected(def.parser);
1458
1611
  case "lazy": {
1459
1612
  const name = c._ruleName;
1460
1613
  try {
1461
- return staticExpected(def.thunk());
1614
+ return deriveExpected(def.thunk());
1462
1615
  } catch {
1463
1616
  return name ? [name] : [];
1464
1617
  }
@@ -1468,7 +1621,7 @@ function staticExpected(c) {
1468
1621
  }
1469
1622
  }
1470
1623
  function expect(combinator, label2) {
1471
- const expected = label2 !== void 0 ? [label2] : staticExpected(combinator);
1624
+ const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
1472
1625
  const meta = {
1473
1626
  firstSet: combinator._meta.firstSet,
1474
1627
  canMatchNewline: combinator._meta.canMatchNewline,
@@ -2964,16 +3117,12 @@ function namedFnPrelude() {
2964
3117
  function pushNamedFnDecl(ctx, fnName, bodyStmts, valueVar, endVar) {
2965
3118
  ctx.namedFnDecls.push([
2966
3119
  `function ${fnName}(input, _pos, _ctx) {`,
2967
- ` let _pfok = false, _pfv, _pfe = _pos`,
2968
3120
  ` _pfail: {`,
2969
3121
  ...reindentStmts(bodyStmts, 2),
2970
- ` _pfv = ${valueVar}`,
2971
- ` _pfe = ${endVar}`,
2972
- ` _pfok = true`,
3122
+ ` ${NAMED_FN_END} = ${endVar}`,
3123
+ ` return ${valueVar}`,
2973
3124
  ` }`,
2974
- ` if (!_pfok) return ${NAMED_FN_FAIL}`,
2975
- ` ${NAMED_FN_END} = _pfe`,
2976
- ` return _pfv`,
3125
+ ` return ${NAMED_FN_FAIL}`,
2977
3126
  `}`
2978
3127
  ].join("\n"));
2979
3128
  }
@@ -3132,6 +3281,7 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
3132
3281
  case "guard":
3133
3282
  case "not":
3134
3283
  case "trivia":
3284
+ case "token":
3135
3285
  case "scanTo":
3136
3286
  case "unknown":
3137
3287
  return false;
@@ -3193,6 +3343,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
3193
3343
  case "regex":
3194
3344
  case "keywords":
3195
3345
  case "node":
3346
+ case "token":
3196
3347
  return true;
3197
3348
  case "not":
3198
3349
  case "guard":
@@ -3471,9 +3622,9 @@ function emitSeq(def, ctx, pos) {
3471
3622
  stmts.push(`${ind(ctx)}const ${arrV} = [${valueVars.join(", ")}]`);
3472
3623
  return { stmts, valueVar: arrV, endVar };
3473
3624
  }
3474
- function staticExpectedArr(parsers) {
3625
+ function deriveExpectedArr(parsers) {
3475
3626
  return JSON.stringify(parsers.flatMap((p) => {
3476
- const e = staticExpected(p);
3627
+ const e = deriveExpected(p);
3477
3628
  return e.length > 0 ? e : [p._tag];
3478
3629
  }));
3479
3630
  }
@@ -3494,7 +3645,7 @@ function failsAtStart(p) {
3494
3645
  }
3495
3646
  }
3496
3647
  function armStaticExpected(ctx, p) {
3497
- return hoistExpected(ctx, staticExpectedArr([p]));
3648
+ return hoistExpected(ctx, deriveExpectedArr([p]));
3498
3649
  }
3499
3650
  function canMatchEmptyAtStart(p) {
3500
3651
  const d = p._def;
@@ -3532,7 +3683,7 @@ function emitDisjointArm(p, ctx, pos, valV, endV) {
3532
3683
  ];
3533
3684
  }
3534
3685
  function emitChoice(def, ctx, pos) {
3535
- const allExpected = staticExpectedArr(def.parsers);
3686
+ const allExpected = deriveExpectedArr(def.parsers);
3536
3687
  if (def.disjoint) {
3537
3688
  const codeV = v(ctx, "_code");
3538
3689
  const valV = v(ctx, "_chv");
@@ -3593,7 +3744,7 @@ function emitGreedyClassify(def, superIndex, ctx, pos) {
3593
3744
  const wordV = v(ctx, "_gw");
3594
3745
  const endV = v(ctx, "_ge");
3595
3746
  const valV = v(ctx, "_gcv");
3596
- const regexExpected = JSON.stringify(staticExpected(superParser));
3747
+ const regexExpected = JSON.stringify(deriveExpected(superParser));
3597
3748
  const stmts = [
3598
3749
  `${ind(ctx)}${reVar}.lastIndex = ${pos}`,
3599
3750
  `${ind(ctx)}const ${matchV} = ${reVar}.exec(input)`,
@@ -3674,11 +3825,16 @@ function emitFirstMatch(def, ctx, pos) {
3674
3825
  const atStart = failsAtStart(p);
3675
3826
  const staticFx = armStaticExpected(ctx, p);
3676
3827
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
3677
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : null;
3828
+ const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
3829
+ if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
3830
+ const deep = firstSetOf(p);
3831
+ return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
3832
+ })();
3678
3833
  let gateCond = null;
3679
3834
  if (gate) {
3680
3835
  const gateIdx = ctx.mapFns.length;
3681
3836
  ctx.mapFns.push(gate);
3837
+ ctx.mapFnSrcs.push(null);
3682
3838
  gateCond = `${mfRef(ctx)}[${gateIdx}](_ctx.state)`;
3683
3839
  }
3684
3840
  const skipCond = gateCond ? `!${resOkV} && ${gateCond}` : `!${resOkV}`;
@@ -3737,13 +3893,21 @@ function emitNonDisjoint(def, strategy, allExpected, ctx, pos) {
3737
3893
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos);
3738
3894
  return emitFirstMatch(def, ctx, pos);
3739
3895
  }
3896
+ function pushMapFn(ctx, fn, src) {
3897
+ if (src !== null && ctx.mapFns.length === ctx.mapFnSrcs.length) {
3898
+ const hit = ctx.mapFnSrcs.indexOf(src);
3899
+ if (hit !== -1) return hit;
3900
+ }
3901
+ const idx = ctx.mapFns.length;
3902
+ ctx.mapFns.push(fn);
3903
+ ctx.mapFnSrcs.push(src);
3904
+ return idx;
3905
+ }
3740
3906
  function emitTransformChain(p, baseValue, endV, startPos, ctx) {
3741
3907
  const def = p._def;
3742
3908
  if (def.tag === "transform") {
3743
3909
  const innerR = emitTransformChain(def.parser, baseValue, endV, startPos, ctx);
3744
- const fnIdx = ctx.mapFns.length;
3745
- ctx.mapFns.push(def.fn);
3746
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
3910
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
3747
3911
  const vv = v(ctx);
3748
3912
  return {
3749
3913
  stmts: [...innerR.stmts, `${ind(ctx)}const ${vv} = ${mfRef(ctx)}[${fnIdx}](${innerR.valueVar}, { start: ${startPos}, end: ${endV} })`],
@@ -3926,7 +4090,7 @@ function emitScanTo(def, ctx, pos) {
3926
4090
  `${ind(ctx)}while (${curV} < input.length) {`
3927
4091
  ];
3928
4092
  ctx.indent++;
3929
- const probeCtx = { ...ctx, capturing: false };
4093
+ const probeCtx = { ...ctx, capturing: false, noHoist: true };
3930
4094
  const { stmts: sentStmts, okVar: sentOk } = emitFallible(def.sentinel, probeCtx, curV);
3931
4095
  stmts.push(...sentStmts, `${ind(ctx)}if (${sentOk}) { ${foundV} = true; break }`);
3932
4096
  if (def.skip.length > 0) {
@@ -3972,6 +4136,116 @@ function emitNot(def, ctx, pos) {
3972
4136
  endVar: pos
3973
4137
  };
3974
4138
  }
4139
+ function escapeTokenLiteral(value) {
4140
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
4141
+ }
4142
+ function regexMatchesEmpty(source, flags) {
4143
+ try {
4144
+ return new RegExp(`^(?:${source})$`, flags).test("");
4145
+ } catch {
4146
+ return true;
4147
+ }
4148
+ }
4149
+ function tokenTerminalSource(p) {
4150
+ const def = p._def;
4151
+ switch (def.tag) {
4152
+ case "literal":
4153
+ if (def.value.length === 0) return null;
4154
+ return {
4155
+ source: escapeTokenLiteral(def.value),
4156
+ caseInsensitive: def.caseInsensitive,
4157
+ rawCaseSensitive: def.caseInsensitive ? "" : def.value
4158
+ };
4159
+ case "regex": {
4160
+ if (def.flags.replace(/i/g, "") !== "") return null;
4161
+ if (regexMatchesEmpty(def.source, def.flags)) return null;
4162
+ return {
4163
+ source: `(?:${def.source})`,
4164
+ caseInsensitive: def.flags.includes("i"),
4165
+ rawCaseSensitive: def.flags.includes("i") ? "" : def.source
4166
+ };
4167
+ }
4168
+ default:
4169
+ return null;
4170
+ }
4171
+ }
4172
+ function tokenSequenceSource(p) {
4173
+ const def = p._def;
4174
+ const parts = def.tag === "sequence" ? def.parsers.map(tokenTerminalSource) : [tokenTerminalSource(p)];
4175
+ if (parts.some((part) => part === null)) return null;
4176
+ const typed = parts;
4177
+ const flags = typed.some((part) => part.caseInsensitive) ? "i" : "";
4178
+ if (flags.includes("i") && typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))) return null;
4179
+ return {
4180
+ source: typed.map((part) => part.source).join(""),
4181
+ flags,
4182
+ hasCaseSensitiveLetters: typed.some((part) => /[A-Za-z]/.test(part.rawCaseSensitive))
4183
+ };
4184
+ }
4185
+ function combineTokenRegexSources(parts) {
4186
+ const flags = parts.some((part) => part.flags.includes("i")) ? "i" : "";
4187
+ if (flags.includes("i") && parts.some((part) => part.flags === "" && part.hasCaseSensitiveLetters)) return null;
4188
+ return { source: parts.map((part) => part.source).join(""), flags };
4189
+ }
4190
+ function tokenNullableRegex(def) {
4191
+ const inner = def.parser._def;
4192
+ if (inner.tag === "many") {
4193
+ const seq = tokenSequenceSource(inner.parser);
4194
+ if (!seq) return null;
4195
+ return { tag: "regex", source: `(?:${seq.source})*`, flags: seq.flags };
4196
+ }
4197
+ if (inner.tag === "optional") {
4198
+ const seq = tokenSequenceSource(inner.parser);
4199
+ if (!seq) return null;
4200
+ return { tag: "regex", source: `(?:${seq.source})?`, flags: seq.flags };
4201
+ }
4202
+ if (inner.tag === "sepBy") {
4203
+ const item = tokenSequenceSource(inner.parser);
4204
+ const sep = tokenSequenceSource(inner.separator);
4205
+ if (!item || !sep) return null;
4206
+ const combined = combineTokenRegexSources([item, sep]);
4207
+ if (!combined) return null;
4208
+ return { tag: "regex", source: `(?:${item.source}(?:${sep.source}${item.source})*)?`, flags: combined.flags };
4209
+ }
4210
+ return null;
4211
+ }
4212
+ function emitToken(def, ctx, pos) {
4213
+ const lowered = tokenNullableRegex(def);
4214
+ if (lowered) return emitRegex(lowered, ctx, pos);
4215
+ const savedTrivia = ctx.activeTrivia;
4216
+ const savedKindLabels = ctx.triviaKindLabels;
4217
+ ctx.activeTrivia = void 0;
4218
+ ctx.triviaKindLabels = void 0;
4219
+ const inner = emitFallible(def.parser, ctx, pos);
4220
+ ctx.activeTrivia = savedTrivia;
4221
+ ctx.triviaKindLabels = savedKindLabels;
4222
+ const i = ind(ctx);
4223
+ const sc = v(ctx, "_tokCh");
4224
+ const sl = v(ctx, "_tokLv");
4225
+ const sr = v(ctx, "_tokRaw");
4226
+ const sv = v(ctx, "_tokTr");
4227
+ const sk = v(ctx, "_tokKinds");
4228
+ const stl = v(ctx, "_tokTl");
4229
+ const sol = v(ctx, "_tokLog");
4230
+ const sb = v(ctx, "_tokBuf");
4231
+ const valV = v(ctx, "_tok");
4232
+ return {
4233
+ stmts: [
4234
+ `${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`,
4235
+ `${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`,
4236
+ `${i}try {`,
4237
+ ...reindentStmts(inner.stmts, ctx.indent + 1).map((stmt) => stmt.replace(/^(\s*)(?:const|let)\s+/, "$1var ")),
4238
+ `${i}} finally {`,
4239
+ `${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}`,
4240
+ `${i}}`,
4241
+ ...emitIfFail(ctx, `!${inner.okVar}`, propagateFailBody(ctx)),
4242
+ `${i}const ${valV} = input.slice(${pos}, ${inner.endVar})`,
4243
+ ...emitLeafCapture(ctx, valV, pos, inner.endVar)
4244
+ ],
4245
+ valueVar: valV,
4246
+ endVar: inner.endVar
4247
+ };
4248
+ }
3975
4249
  function emitNode(def, ctx, pos) {
3976
4250
  const structural = def.build === void 0;
3977
4251
  const mkType = structural ? null : analyzeMkInlineBuild(def);
@@ -4091,7 +4365,7 @@ function emitRecover(def, ctx, pos) {
4091
4365
  `${ind0} if (${sentOk}) break`,
4092
4366
  `${ind0} ${scanV}++`,
4093
4367
  `${ind0} }`,
4094
- `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(staticExpected(def.parser))} }`,
4368
+ `${ind0} const ${errV} = { _tag: 'parseError', span: { start: ${pos}, end: ${scanV} }, expected: ${JSON.stringify(deriveExpected(def.parser))} }`,
4095
4369
  `${ind0} if (_ctx._errors) _ctx._errors.push(${errV})`,
4096
4370
  `${ind0} ${valVar} = ${errV}`,
4097
4371
  `${ind0} ${endVar} = ${scanV}`,
@@ -4117,6 +4391,32 @@ function emitExpect(def, ctx, pos) {
4117
4391
  return { stmts, valueVar: valVar, endVar };
4118
4392
  }
4119
4393
  function emit(p, ctx, pos) {
4394
+ const usage = ctx.lazyUsage;
4395
+ 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
4396
+ // overhead. A tiny shared wrapper (e.g. `optional(literal(';'))`) inlines
4397
+ // faster than it calls and barely moves the byte count; the size explosion
4398
+ // is all in LARGE choices/sequences referenced from many positions.
4399
+ (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE) {
4400
+ const existing = ctx.namedParsers.get(p);
4401
+ if (existing !== void 0) return emitNamedFnCall(ctx, existing, pos);
4402
+ const fnName = `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
4403
+ ctx.namedParsers.set(p, fnName);
4404
+ const savedIndent = ctx.indent;
4405
+ const savedFailLabel = ctx.failLabel;
4406
+ const savedRecord = ctx.recordFail;
4407
+ ctx.indent = 1;
4408
+ ctx.failLabel = "_pfail";
4409
+ ctx.recordFail = true;
4410
+ const r = emitDispatch(p, ctx, "_pos");
4411
+ ctx.indent = savedIndent;
4412
+ ctx.failLabel = savedFailLabel;
4413
+ ctx.recordFail = savedRecord;
4414
+ pushNamedFnDecl(ctx, fnName, r.stmts, r.valueVar, r.endVar);
4415
+ return emitNamedFnCall(ctx, fnName, pos);
4416
+ }
4417
+ return emitDispatch(p, ctx, pos);
4418
+ }
4419
+ function emitDispatch(p, ctx, pos) {
4120
4420
  const def = p._def;
4121
4421
  switch (def.tag) {
4122
4422
  case "literal":
@@ -4162,9 +4462,7 @@ function emit(p, ctx, pos) {
4162
4462
  };
4163
4463
  }
4164
4464
  }
4165
- const fnIdx = ctx.mapFns.length;
4166
- ctx.mapFns.push(def.fn);
4167
- ctx.mapFnSrcs.push(def.fnSrc ?? null);
4465
+ const fnIdx = pushMapFn(ctx, def.fn, def.fnSrc ?? null);
4168
4466
  const mv = v(ctx, "_mapped");
4169
4467
  return {
4170
4468
  stmts: [
@@ -4197,6 +4495,8 @@ function emit(p, ctx, pos) {
4197
4495
  return emitLazy(p, def, ctx, pos);
4198
4496
  case "trivia":
4199
4497
  return emit(def.parser, ctx, pos);
4498
+ case "token":
4499
+ return emitToken(def, ctx, pos);
4200
4500
  case "label":
4201
4501
  return emit(def.parser, ctx, pos);
4202
4502
  case "grammar": {
@@ -4229,6 +4529,7 @@ function emit(p, ctx, pos) {
4229
4529
  case "guard": {
4230
4530
  const fnIdx = ctx.mapFns.length;
4231
4531
  ctx.mapFns.push(def.predicate);
4532
+ ctx.mapFnSrcs.push(null);
4232
4533
  const vv = v(ctx);
4233
4534
  return {
4234
4535
  stmts: [
@@ -4243,6 +4544,7 @@ function emit(p, ctx, pos) {
4243
4544
  const evIdx = ctx.mapFns.length;
4244
4545
  const extra = def.extra;
4245
4546
  ctx.mapFns.push((() => extra));
4547
+ ctx.mapFnSrcs.push(null);
4246
4548
  const innerParser = def.parser;
4247
4549
  if (!ctx.namedParsers.has(innerParser)) {
4248
4550
  const fnName = `_wcf${ctx.namedParsers.size}`;
@@ -4296,6 +4598,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
4296
4598
  }
4297
4599
  case "grammar":
4298
4600
  case "trivia":
4601
+ case "token":
4299
4602
  case "label":
4300
4603
  case "optional":
4301
4604
  case "many":
@@ -4331,6 +4634,7 @@ function childrenOf(def) {
4331
4634
  case "optional":
4332
4635
  case "transform":
4333
4636
  case "trivia":
4637
+ case "token":
4334
4638
  case "label":
4335
4639
  case "grammar":
4336
4640
  case "not":
@@ -4362,9 +4666,58 @@ function childrenOf(def) {
4362
4666
  return [];
4363
4667
  }
4364
4668
  }
4669
+ var HOISTABLE_TAGS = /* @__PURE__ */ new Set([
4670
+ "sequence",
4671
+ "choice",
4672
+ "many",
4673
+ "oneOrMore",
4674
+ "optional",
4675
+ "sepBy",
4676
+ "transform",
4677
+ "node",
4678
+ "not"
4679
+ ]);
4680
+ function isHoistableTag(tag) {
4681
+ return HOISTABLE_TAGS.has(tag);
4682
+ }
4683
+ var HOIST_MIN_SUBTREE = 3;
4365
4684
  function analyzeLazyUsage(root) {
4366
4685
  return analyzeLazyUsageMulti([root]);
4367
4686
  }
4687
+ function subtreeSizes(roots) {
4688
+ const sizes = /* @__PURE__ */ new Map();
4689
+ const sz = (p) => {
4690
+ const cached = sizes.get(p);
4691
+ if (cached !== void 0) return cached;
4692
+ const def = p._def;
4693
+ let s = 1;
4694
+ if (def.tag !== "lazy") {
4695
+ for (const child of childrenOf(def)) s += child._def.tag === "lazy" ? 1 : sz(child);
4696
+ }
4697
+ sizes.set(p, s);
4698
+ return s;
4699
+ };
4700
+ const visitedLazy = /* @__PURE__ */ new Set();
4701
+ const visit = (p) => {
4702
+ const def = p._def;
4703
+ if (def.tag === "lazy") {
4704
+ if (visitedLazy.has(p)) return;
4705
+ visitedLazy.add(p);
4706
+ let resolved;
4707
+ try {
4708
+ resolved = def.thunk();
4709
+ } catch {
4710
+ return;
4711
+ }
4712
+ visit(resolved);
4713
+ return;
4714
+ }
4715
+ sz(p);
4716
+ for (const child of childrenOf(def)) visit(child);
4717
+ };
4718
+ for (const root of roots) visit(root);
4719
+ return sizes;
4720
+ }
4368
4721
  function analyzeLazyUsageMulti(roots) {
4369
4722
  const counts = /* @__PURE__ */ new Map();
4370
4723
  const recursive = /* @__PURE__ */ new Set();
@@ -4391,10 +4744,14 @@ function analyzeLazyUsageMulti(roots) {
4391
4744
  active.delete(p);
4392
4745
  return;
4393
4746
  }
4747
+ if (isHoistableTag(def.tag)) {
4748
+ counts.set(p, (counts.get(p) ?? 0) + 1);
4749
+ }
4394
4750
  for (const child of childrenOf(def)) walk2(child);
4395
4751
  }
4396
- for (const root of roots) walk2(root);
4397
- return { counts, recursive };
4752
+ const rootsArr = [...roots];
4753
+ for (const root of rootsArr) walk2(root);
4754
+ return { counts, recursive, sizes: subtreeSizes(rootsArr) };
4398
4755
  }
4399
4756
  function ruleDependencies(ruleMap) {
4400
4757
  const nameOf = /* @__PURE__ */ new Map();
@@ -4793,64 +5150,6 @@ function buildInlineExpression(ctx, r, mapFnSources, buildSources) {
4793
5150
  ].join("\n");
4794
5151
  }
4795
5152
 
4796
- // src/compiler/linker.ts
4797
- function fusedBody(pieces) {
4798
- const winner = /* @__PURE__ */ new Map();
4799
- for (const p of pieces) for (const k of p.keys) winner.set(k, p);
4800
- for (const [k, p] of winner) {
4801
- for (const d of p.deps.get(k) ?? []) {
4802
- if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
4803
- }
4804
- }
4805
- const contributing = new Set(winner.values());
4806
- const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
4807
- const needsHostReads = [...contributing].some((p) => p.needsHostReads);
4808
- const lines = [
4809
- // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
4810
- "const _pfFail = {}",
4811
- "let _pfEnd",
4812
- ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
4813
- ...needsHostReads ? [HOST_READS_DECL] : [],
4814
- // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
4815
- ...[...contributing].flatMap((p) => p.prelude),
4816
- // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
4817
- ...[...winner].map(([k, p]) => p.ruleFns.get(k))
4818
- ];
4819
- const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
4820
- const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
4821
- const finalFS = /* @__PURE__ */ new Map();
4822
- for (const [k, p] of winner) {
4823
- const fs2 = p.firstSets?.get(k);
4824
- if (fs2) finalFS.set(k, fs2);
4825
- }
4826
- const body = rawBody.replace(
4827
- // Rule name + code-point var are both JS identifiers (rule names are validated
4828
- // at compile time — see assertRuleName in codegen), so an identifier class
4829
- // matches every well-formed placeholder.
4830
- /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
4831
- (_m, name, codevar) => {
4832
- const fs2 = finalFS.get(name);
4833
- if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
4834
- return `(${firstSetCond(codevar, fs2)})`;
4835
- }
4836
- );
4837
- const env = {};
4838
- for (const p of contributing) {
4839
- if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
4840
- if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
4841
- }
4842
- return { body, env };
4843
- }
4844
- function emitFusedSource(pieces) {
4845
- const { body, env } = fusedBody(pieces);
4846
- if (Object.keys(env).length > 0) {
4847
- throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
4848
- }
4849
- return `/* @__PURE__ */ (() => {
4850
- ${body}
4851
- })()`;
4852
- }
4853
-
4854
5153
  // src/combinators/scanTo.ts
4855
5154
  function scanTo(sentinel, { skip: skip2 = [], orEOF = false } = {}) {
4856
5155
  const meta = {
@@ -4919,7 +5218,7 @@ function balanced(open, close, options = {}) {
4919
5218
  const content = bounded ? regex(new RegExp(`[^${cls}]+`)) : regex(new RegExp(`[^${escapeClassChar(open)}${escapeClassChar(close)}]`));
4920
5219
  const inner = many(choice(self, ...skips, content));
4921
5220
  const combi = transform(
4922
- sequence(literal(open), inner, expect(literal(close), close)),
5221
+ sequence(literal(open), inner, expect(literal(close))),
4923
5222
  // parts: strings (content/self) or arrays (a sequence-shaped skip) or a
4924
5223
  // ParseError (recovered close). `c` is the close string or a ParseError.
4925
5224
  ([o, parts, c]) => o + parts.map((p) => typeof p === "string" ? p : Array.isArray(p) ? p.join("") : "").join("") + (typeof c === "string" ? c : "")
@@ -4945,6 +5244,359 @@ function escapeClassChar(ch) {
4945
5244
  return ch.replace(/[\\\]^-]/g, "\\$&");
4946
5245
  }
4947
5246
 
5247
+ // src/compiler/ir-serialize.ts
5248
+ function childrenOf2(def) {
5249
+ switch (def.tag) {
5250
+ case "sequence":
5251
+ case "choice":
5252
+ return def.parsers;
5253
+ case "many":
5254
+ case "oneOrMore":
5255
+ case "optional":
5256
+ case "transform":
5257
+ case "trivia":
5258
+ case "token":
5259
+ case "label":
5260
+ case "not":
5261
+ case "node":
5262
+ case "expect":
5263
+ return [def.parser];
5264
+ case "grammar":
5265
+ return def.triviaParser ? [def.parser, def.triviaParser] : [def.parser];
5266
+ case "sepBy":
5267
+ return [def.parser, def.separator];
5268
+ case "skip":
5269
+ return [def.main, def.skipped];
5270
+ case "scanTo":
5271
+ return [def.sentinel, ...def.skip];
5272
+ case "lazy":
5273
+ case "literal":
5274
+ case "regex":
5275
+ case "keywords":
5276
+ case "guard":
5277
+ case "withCtx":
5278
+ case "recover":
5279
+ case "unknown":
5280
+ return [];
5281
+ }
5282
+ }
5283
+ var ruleNameOf = (c) => c._ruleName;
5284
+ function lazyTarget(c) {
5285
+ if (c._def.tag !== "lazy") return null;
5286
+ try {
5287
+ return c._def.thunk();
5288
+ } catch {
5289
+ return null;
5290
+ }
5291
+ }
5292
+ var Unserializable = class extends Error {
5293
+ };
5294
+ function serializeRuleMap(ruleMap) {
5295
+ try {
5296
+ return new Serializer(ruleMap).run();
5297
+ } catch (e) {
5298
+ if (e instanceof Unserializable) {
5299
+ if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
5300
+ return null;
5301
+ }
5302
+ throw e;
5303
+ }
5304
+ }
5305
+ function evalRuleMapIR(ir) {
5306
+ const _tf = (child, src) => {
5307
+ let fn2;
5308
+ try {
5309
+ fn2 = (0, eval)(`(${src})`);
5310
+ } catch {
5311
+ fn2 = () => {
5312
+ throw new Error("IR transform fn not materialized");
5313
+ };
5314
+ }
5315
+ const t = transform(child, fn2);
5316
+ t._def.fnSrc = src;
5317
+ return t;
5318
+ };
5319
+ const _nd = (type, child, src, opts) => {
5320
+ const n = node(type, child, void 0, opts);
5321
+ n._def.buildSrc = src;
5322
+ return n;
5323
+ };
5324
+ const fn = new Function(
5325
+ "rules",
5326
+ "ref",
5327
+ "regex",
5328
+ "literal",
5329
+ "keywords",
5330
+ "sequence",
5331
+ "choice",
5332
+ "many",
5333
+ "oneOrMore",
5334
+ "optional",
5335
+ "sepBy",
5336
+ "not",
5337
+ "node",
5338
+ "parser",
5339
+ "scanTo",
5340
+ "token",
5341
+ "transform",
5342
+ "skip",
5343
+ "trivia",
5344
+ "label",
5345
+ "expect",
5346
+ "_tf",
5347
+ "_nd",
5348
+ `return (${ir})`
5349
+ );
5350
+ const map = fn(
5351
+ rules,
5352
+ ref,
5353
+ regex,
5354
+ literal,
5355
+ keywords,
5356
+ sequence,
5357
+ choice,
5358
+ many,
5359
+ oneOrMore,
5360
+ optional,
5361
+ sepBy,
5362
+ not,
5363
+ node,
5364
+ parser,
5365
+ scanTo,
5366
+ token,
5367
+ transform,
5368
+ skip,
5369
+ trivia,
5370
+ label,
5371
+ expect,
5372
+ _tf,
5373
+ _nd
5374
+ );
5375
+ return Object.entries(map);
5376
+ }
5377
+ var Serializer = class {
5378
+ counts = /* @__PURE__ */ new Map();
5379
+ selfRef = /* @__PURE__ */ new Set();
5380
+ // combinators reached by an unnamed lazy pointing back into their own subtree
5381
+ constName = /* @__PURE__ */ new Map();
5382
+ emitted = /* @__PURE__ */ new Set();
5383
+ decls = [];
5384
+ ruleValues = /* @__PURE__ */ new Set();
5385
+ ruleMap;
5386
+ constructor(ruleMap) {
5387
+ this.ruleMap = ruleMap;
5388
+ for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
5389
+ }
5390
+ /** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
5391
+ * its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
5392
+ * `g[name]` ref — only the top-level entry is unwrapped. */
5393
+ body(c) {
5394
+ return c._def.tag === "lazy" && ruleNameOf(c) !== void 0 ? lazyTarget(c) ?? c : c;
5395
+ }
5396
+ run() {
5397
+ for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
5398
+ let n = 0;
5399
+ for (const [c, count] of this.counts) {
5400
+ if (this.ruleValues.has(c)) continue;
5401
+ if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
5402
+ }
5403
+ const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
5404
+ if (this.decls.length === 0) return `rules((g) => ({
5405
+ ${body}
5406
+ }))`;
5407
+ return `rules((g) => {
5408
+ ${this.decls.map((d) => " " + d).join("\n")}
5409
+ return ({
5410
+ ${body}
5411
+ })
5412
+ })`;
5413
+ }
5414
+ /** Count identity references and flag self-referential subtrees. `active` is the
5415
+ * set of combinators on the current DFS path (to detect a lazy pointing back). */
5416
+ analyze(c, active) {
5417
+ const def = c._def;
5418
+ if (def.tag === "lazy") {
5419
+ if (ruleNameOf(c) !== void 0) return;
5420
+ const target = lazyTarget(c);
5421
+ if (!target) throw new Unserializable("unresolved unnamed ref");
5422
+ if (active.has(target)) {
5423
+ this.selfRef.add(target);
5424
+ return;
5425
+ }
5426
+ this.analyze(target, active);
5427
+ return;
5428
+ }
5429
+ if (def.tag === "guard" || def.tag === "withCtx" || def.tag === "recover" || def.tag === "unknown") {
5430
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5431
+ }
5432
+ this.counts.set(c, (this.counts.get(c) ?? 0) + 1);
5433
+ if ((this.counts.get(c) ?? 0) > 1) return;
5434
+ active.add(c);
5435
+ for (const child of childrenOf2(def)) this.analyze(child, active);
5436
+ active.delete(c);
5437
+ }
5438
+ /** Reference `c`: a const var if it has one (emitting its decl first), else inline.
5439
+ * `selfOf` is the const currently being wrapped in a ref()/define closure — an
5440
+ * unnamed lazy back into it emits the local ref var instead of recursing. */
5441
+ ref(c, selfOf) {
5442
+ const cn = this.constName.get(c);
5443
+ if (cn) {
5444
+ this.emitDecl(c);
5445
+ return cn;
5446
+ }
5447
+ return this.expr(c, selfOf);
5448
+ }
5449
+ emitDecl(c) {
5450
+ const name = this.constName.get(c);
5451
+ if (this.emitted.has(name)) return;
5452
+ this.emitted.add(name);
5453
+ this.decls.push(`const ${name} = ${this.wrap(c)}`);
5454
+ }
5455
+ selfN = 0;
5456
+ /** Emit `c`'s own constructor call, wrapping it in a `ref()`+`.define()` closure
5457
+ * if it is self-referential (an unnamed lazy in its subtree points back to it) —
5458
+ * the shape `balanced()` and other recursive library combinators build. */
5459
+ wrap(c) {
5460
+ if (!this.selfRef.has(c)) return this.expr(c, null);
5461
+ const rv = `_rr${this.selfN++}`;
5462
+ const bodyExpr = this.expr(c, { comb: c, var: rv });
5463
+ return `(() => { const ${rv} = ref(); const _b = ${bodyExpr}; ${rv}.define(_b); return _b })()`;
5464
+ }
5465
+ /** Emit the constructor-call source for `c` (never a const shortcut for `c` itself). */
5466
+ expr(c, selfOf) {
5467
+ const def = c._def;
5468
+ const kid = (x) => this.ref(x, selfOf);
5469
+ switch (def.tag) {
5470
+ case "lazy": {
5471
+ if (ruleNameOf(c) !== void 0) return `g[${JSON.stringify(ruleNameOf(c))}]`;
5472
+ const target = lazyTarget(c);
5473
+ if (selfOf && target === selfOf.comb) return selfOf.var;
5474
+ return this.ref(target, selfOf);
5475
+ }
5476
+ case "literal":
5477
+ return `literal(${JSON.stringify(def.value)}${def.caseInsensitive ? ", { caseInsensitive: true }" : ""})`;
5478
+ case "regex":
5479
+ return `regex(${JSON.stringify(def.source)}, ${JSON.stringify(def.flags)})`;
5480
+ case "keywords":
5481
+ return `keywords(${JSON.stringify(def.words)}, { caseInsensitive: ${def.caseInsensitive}${def.boundary !== void 0 ? `, boundary: ${JSON.stringify(def.boundary)}` : ""} })`;
5482
+ case "sequence":
5483
+ return `sequence(${def.parsers.map(kid).join(", ")})`;
5484
+ case "choice": {
5485
+ if (def.gates.some((g) => g !== null)) throw new Unserializable("choice with a gate() has no source");
5486
+ return `choice(${def.parsers.map(kid).join(", ")})`;
5487
+ }
5488
+ case "many":
5489
+ return `many(${kid(def.parser)})`;
5490
+ case "oneOrMore":
5491
+ return `oneOrMore(${kid(def.parser)})`;
5492
+ case "optional":
5493
+ return `optional(${kid(def.parser)})`;
5494
+ case "sepBy":
5495
+ return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
5496
+ case "not":
5497
+ return `not(${kid(def.parser)})`;
5498
+ case "trivia":
5499
+ return `trivia(${kid(def.parser)})`;
5500
+ case "token":
5501
+ return `token(${kid(def.parser)})`;
5502
+ case "label":
5503
+ return `label(${JSON.stringify(def.label)}, ${kid(def.parser)})`;
5504
+ case "expect":
5505
+ return `expect(${kid(def.parser)}${def.label !== void 0 ? `, ${JSON.stringify(def.label)}` : ""})`;
5506
+ case "skip":
5507
+ return `skip(${kid(def.main)}, ${kid(def.skipped)})`;
5508
+ case "scanTo":
5509
+ return `scanTo(${kid(def.sentinel)}, { skip: [${def.skip.map(kid).join(", ")}], orEOF: ${def.orEOF} })`;
5510
+ case "transform": {
5511
+ if (def.fnSrc === void 0) throw new Unserializable("transform without fnSrc");
5512
+ return `_tf(${kid(def.parser)}, ${JSON.stringify(def.fnSrc)})`;
5513
+ }
5514
+ case "node": {
5515
+ if (def.build !== void 0 && def.buildSrc === void 0) throw new Unserializable("node build without buildSrc");
5516
+ const opts = def.collapse ? `, { collapse: true }` : "";
5517
+ if (def.buildSrc !== void 0) return `_nd(${JSON.stringify(def.type)}, ${kid(def.parser)}, ${JSON.stringify(def.buildSrc)}${opts})`;
5518
+ return opts ? `node(${JSON.stringify(def.type)}, ${kid(def.parser)}, undefined${opts})` : `node(${JSON.stringify(def.type)}, ${kid(def.parser)})`;
5519
+ }
5520
+ case "grammar": {
5521
+ const trivia2 = def.clearTrivia ? "null" : def.triviaParser ? kid(def.triviaParser) : "undefined";
5522
+ return `parser({ trivia: ${trivia2}${def.trackLines ? ", trackLines: true" : ""} }, ${kid(def.parser)})`;
5523
+ }
5524
+ case "guard":
5525
+ case "withCtx":
5526
+ case "recover":
5527
+ case "unknown":
5528
+ throw new Unserializable(`unsupported tag ${def.tag}`);
5529
+ }
5530
+ }
5531
+ };
5532
+
5533
+ // src/compiler/linker.ts
5534
+ function fusedBody(pieces) {
5535
+ const winner = /* @__PURE__ */ new Map();
5536
+ for (const p of pieces) for (const k of p.keys) winner.set(k, p);
5537
+ for (const [k, p] of winner) {
5538
+ for (const d of p.deps.get(k) ?? []) {
5539
+ if (!winner.has(d)) throw new Error(`compose: rule "${k}" references missing rule "${d}"`);
5540
+ }
5541
+ }
5542
+ const contributing = new Set(winner.values());
5543
+ const needsEmptyTl = [...contributing].some((p) => p.needsEmptyTl);
5544
+ const needsHostReads = [...contributing].some((p) => p.needsHostReads);
5545
+ const lines = [
5546
+ // Shared sentinel protocol (must match NAMED_FN_FAIL / NAMED_FN_END in codegen).
5547
+ "const _pfFail = {}",
5548
+ "let _pfEnd",
5549
+ ...needsEmptyTl ? ["const _EMPTY_TL = Object.freeze([])"] : [],
5550
+ ...needsHostReads ? [HOST_READS_DECL] : [],
5551
+ // Each contributing artifact's namespaced private prelude (regexes, _pf, …).
5552
+ ...[...contributing].flatMap((p) => p.prelude),
5553
+ // The winning `_r_<Name>` function for each rule (one per name → no redeclare).
5554
+ ...[...winner].map(([k, p]) => p.ruleFns.get(k))
5555
+ ];
5556
+ const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
5557
+ const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
5558
+ const finalFS = /* @__PURE__ */ new Map();
5559
+ for (const [k, p] of winner) {
5560
+ const fs2 = p.firstSets?.get(k);
5561
+ if (fs2) finalFS.set(k, fs2);
5562
+ }
5563
+ const body = rawBody.replace(
5564
+ // Rule name + code-point var are both JS identifiers (rule names are validated
5565
+ // at compile time — see assertRuleName in codegen), so an identifier class
5566
+ // matches every well-formed placeholder.
5567
+ /\/\*@FS:([A-Za-z0-9_$]+):([A-Za-z0-9_$]+)@\*\/true/g,
5568
+ (_m, name, codevar) => {
5569
+ const fs2 = finalFS.get(name);
5570
+ if (!fs2 || fs2.kind === "any" || fs2.kind === "empty") return "true";
5571
+ return `(${firstSetCond(codevar, fs2)})`;
5572
+ }
5573
+ );
5574
+ const env = {};
5575
+ for (const p of contributing) {
5576
+ if (p.mfFns.length) env[`${p.ns}mf`] = p.mfFns;
5577
+ if (p.buildFns.length) env[`${p.ns}build`] = p.buildFns;
5578
+ }
5579
+ return { body, env };
5580
+ }
5581
+ function emitFusedSource(pieces) {
5582
+ const { body, env } = fusedBody(pieces);
5583
+ if (Object.keys(env).length > 0) {
5584
+ throw new Error("emitFusedSource: artifact carries non-static callbacks (runtime-only); cannot emit static source");
5585
+ }
5586
+ return `/* @__PURE__ */ (() => {
5587
+ ${body}
5588
+ })()`;
5589
+ }
5590
+ function isIRPiece(p) {
5591
+ return !!p && typeof p === "object" && typeof p.ir === "string" && typeof p.ns === "string" && !("ruleFns" in p);
5592
+ }
5593
+ function materializePiece(p) {
5594
+ if (!isIRPiece(p)) return p;
5595
+ const pieces = compileLinkable(evalRuleMapIR(p.ir), p.ns);
5596
+ if (!pieces) throw new Error(`compose: carried IR for ns "${p.ns}" could not be re-lowered`);
5597
+ return pieces;
5598
+ }
5599
+
4948
5600
  // src/cst/offset-model.ts
4949
5601
  var { isFinite } = Number;
4950
5602
 
@@ -4969,6 +5621,7 @@ var SUPPORTED = {
4969
5621
  trivia: (...a) => trivia(a[0]),
4970
5622
  label: (...a) => label(a[0], a[1]),
4971
5623
  noTrivia: (...a) => noTrivia(a[0]),
5624
+ token: (...a) => token(a[0]),
4972
5625
  expect: (...a) => expect(a[0], a[1])
4973
5626
  };
4974
5627
  function isScopeEntry(v2) {
@@ -5518,6 +6171,14 @@ function findCarriedPiecesLiteral(root) {
5518
6171
  const obj = callee?.object?.name;
5519
6172
  const prop = callee?.property?.name;
5520
6173
  const args = node2.arguments;
6174
+ if (obj === "Object" && prop === "assign" && args && args[1]?.type === "ObjectExpression") {
6175
+ for (const p of args[1].properties ?? []) {
6176
+ if (p.computed && isComposedPiecesSymbol(p.key) && p.value) {
6177
+ found = { start: p.value.start, end: p.value.end };
6178
+ return;
6179
+ }
6180
+ }
6181
+ }
5521
6182
  if (obj === "Object" && prop === "defineProperty" && args && isComposedPiecesSymbol(args[1])) {
5522
6183
  const descriptor = args[2];
5523
6184
  for (const p of descriptor?.properties ?? []) {
@@ -5538,6 +6199,20 @@ function findCarriedPiecesLiteral(root) {
5538
6199
  visit(root);
5539
6200
  return found;
5540
6201
  }
6202
+ function extractImportBindings(body) {
6203
+ const out = /* @__PURE__ */ new Map();
6204
+ for (const stmt of body) {
6205
+ if (stmt.type !== "ImportDeclaration") continue;
6206
+ const s = stmt;
6207
+ for (const spec of s.specifiers) {
6208
+ if (spec.type === "ImportSpecifier") {
6209
+ const imported = spec.imported.name ?? spec.local.name;
6210
+ out.set(spec.local.name, { source: s.source.value, imported });
6211
+ }
6212
+ }
6213
+ }
6214
+ return out;
6215
+ }
5541
6216
  function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSEMAN_MODULE]), warnUnloweredRegex = false) {
5542
6217
  let result;
5543
6218
  try {
@@ -5612,14 +6287,57 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5612
6287
  const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
5613
6288
  const localRuleMaps = /* @__PURE__ */ new Map();
5614
6289
  const nsFor = (label2) => `_${createHash("sha1").update(`${id}#${label2}`).digest("hex").slice(0, 8)}_`;
6290
+ const stripIndent = (s) => s.replace(/\n[ \t]+/g, "\n");
5615
6291
  const serializePieces = (p) => {
5616
- const mapLit = (m) => `new Map([${[...m].map(([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(v2)}]`).join(", ")}])`;
5617
- 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: [] }`;
6292
+ const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
6293
+ ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
6294
+ ).join(", ")}])`;
6295
+ 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: [] }`;
5618
6296
  };
5619
- const serializeList = (list) => `[${list.map(serializePieces).join(", ")}]`;
5620
- const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ (() => { const _g = ${grammarExpr}; Object.defineProperty(_g, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)} }); return _g })()`;
6297
+ const isSpread = (it) => "__spreadLocal" in it;
6298
+ const isIR = (it) => "ir" in it && !("ruleFns" in it);
6299
+ 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);
6300
+ const serializeList = (list) => `[${list.map(serializeItem).join(", ")}]`;
6301
+ const withCarriedPieces = (grammarExpr, list) => `/* @__PURE__ */ Object.defineProperty(${grammarExpr}, Symbol.for('parseman.composedPieces'), { value: ${serializeList(list)}, enumerable: false })`;
5621
6302
  const localComposedPieces = /* @__PURE__ */ new Map();
5622
6303
  const importedPiecesCache = /* @__PURE__ */ new Map();
6304
+ const resolveModulePieces = (file, exportName, seen) => {
6305
+ const mod = parseModuleCached(file);
6306
+ if (!mod) return null;
6307
+ const localFor = exportLocalName(mod.body, exportName);
6308
+ const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
6309
+ const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
6310
+ if (!literalRange) return null;
6311
+ const literal2 = mod.src.slice(literalRange.start, literalRange.end);
6312
+ const imports = extractImportBindings(mod.body);
6313
+ const stubNames = [];
6314
+ const stubVals = [];
6315
+ const spreadRe = /\.\.\.\s*\(?\s*([A-Za-z_$][\w$]*)\s*\[\s*Symbol\s*\.\s*for\s*\(\s*['"]parseman\.composedPieces['"]/g;
6316
+ const done = /* @__PURE__ */ new Set();
6317
+ for (let m; m = spreadRe.exec(literal2); ) {
6318
+ const local = m[1];
6319
+ if (done.has(local)) continue;
6320
+ done.add(local);
6321
+ const b = imports.get(local);
6322
+ let subPieces = [];
6323
+ if (b) {
6324
+ let subFile = null;
6325
+ try {
6326
+ subFile = getCompiledResolver().resolveFileSync(file, b.source).path ?? null;
6327
+ } catch {
6328
+ }
6329
+ if (subFile && !seen.has(subFile)) subPieces = resolveModulePieces(subFile, b.imported, new Set(seen).add(subFile)) ?? [];
6330
+ }
6331
+ stubNames.push(local);
6332
+ stubVals.push({ [/* @__PURE__ */ Symbol.for("parseman.composedPieces")]: subPieces });
6333
+ }
6334
+ try {
6335
+ const list = new Function(...stubNames, `return (${literal2})`)(...stubVals);
6336
+ return list.map(materializePiece);
6337
+ } catch {
6338
+ return null;
6339
+ }
6340
+ };
5623
6341
  const importedPieces = (localName) => {
5624
6342
  if (importedPiecesCache.has(localName)) return importedPiecesCache.get(localName);
5625
6343
  let result2 = null;
@@ -5630,41 +6348,34 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5630
6348
  file = getCompiledResolver().resolveFileSync(id, binding.source).path ?? null;
5631
6349
  } catch {
5632
6350
  }
5633
- const mod = file ? parseModuleCached(file) : null;
5634
- if (mod) {
5635
- const localFor = exportLocalName(mod.body, binding.imported);
5636
- const initNode = localFor ? topLevelInit(mod.body, localFor) : null;
5637
- const literalRange = initNode ? findCarriedPiecesLiteral(initNode) : null;
5638
- if (literalRange) {
5639
- const literal2 = mod.src.slice(literalRange.start, literalRange.end);
5640
- try {
5641
- result2 = new Function(`return (${literal2})`)();
5642
- } catch {
5643
- result2 = null;
5644
- }
5645
- }
5646
- }
6351
+ result2 = file ? resolveModulePieces(file, binding.imported, /* @__PURE__ */ new Set([file])) : null;
5647
6352
  }
5648
6353
  importedPiecesCache.set(localName, result2);
5649
6354
  return result2;
5650
6355
  };
6356
+ const localArg = (rm, label2) => {
6357
+ const entries = [...rm];
6358
+ const ns = nsFor(label2);
6359
+ const p = compileLinkable(entries, ns);
6360
+ if (!p) return null;
6361
+ const ir = serializeRuleMap(entries);
6362
+ return ir ? { pieces: [p], ir: { ns, ir } } : { pieces: [p] };
6363
+ };
5651
6364
  const argPieces = (arg, label2) => {
5652
6365
  if (isRulesCall(arg)) {
5653
6366
  const factory = arg.arguments[0];
5654
6367
  const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
5655
- const p = rm ? compileLinkable([...rm], nsFor(label2)) : null;
5656
- return p ? [p] : null;
6368
+ return rm ? localArg(rm, label2) : null;
5657
6369
  }
5658
6370
  if (arg.type === "Identifier") {
5659
6371
  const name = arg.name;
5660
6372
  const rm = localRuleMaps.get(name);
5661
- if (rm) {
5662
- const p = compileLinkable([...rm], nsFor(label2));
5663
- return p ? [p] : null;
5664
- }
6373
+ if (rm) return localArg(rm, label2);
5665
6374
  const composed = localComposedPieces.get(name);
5666
- if (composed) return composed;
5667
- return importedPieces(name);
6375
+ if (composed) return { pieces: composed };
6376
+ const pieces = importedPieces(name);
6377
+ if (!pieces) return null;
6378
+ return importBindings.has(name) ? { pieces, spreadLocal: name } : { pieces };
5668
6379
  }
5669
6380
  return null;
5670
6381
  };
@@ -5677,16 +6388,20 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5677
6388
  }
5678
6389
  const elements = arr.elements;
5679
6390
  const pieces = [];
6391
+ const carried = [];
5680
6392
  for (let i = 0; i < elements.length; i++) {
5681
- const list = argPieces(elements[i], `compose${init.start}_${i}`);
5682
- if (!list) {
6393
+ const r = argPieces(elements[i], `compose${init.start}_${i}`);
6394
+ if (!r) {
5683
6395
  warn(init.start, `compose(): argument ${i} isn't a build-resolvable grammar; falling back to runtime`);
5684
6396
  return null;
5685
6397
  }
5686
- pieces.push(...list);
6398
+ pieces.push(...r.pieces);
6399
+ if (r.spreadLocal) carried.push({ __spreadLocal: r.spreadLocal });
6400
+ else if (r.ir) carried.push(r.ir);
6401
+ else carried.push(...r.pieces);
5687
6402
  }
5688
6403
  try {
5689
- return { replacement: emitFusedSource(pieces), pieces };
6404
+ return { replacement: emitFusedSource(pieces), pieces, carried };
5690
6405
  } catch (e) {
5691
6406
  warn(init.start, `compose(): ${e.message}; falling back to runtime`);
5692
6407
  return null;
@@ -5773,9 +6488,11 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5773
6488
  localRuleMaps.set(varName, compiledRules.ruleMap);
5774
6489
  let replacement = compiledRules.replacement;
5775
6490
  if (exportPrefix) {
5776
- const pieces = compileLinkable([...compiledRules.ruleMap], nsFor(varName));
6491
+ const ns = nsFor(varName);
6492
+ const pieces = compileLinkable([...compiledRules.ruleMap], ns);
5777
6493
  if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
5778
- replacement = withCarriedPieces(replacement, [pieces]);
6494
+ const ir = serializeRuleMap([...compiledRules.ruleMap]);
6495
+ replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
5779
6496
  }
5780
6497
  }
5781
6498
  replacements.push({ start: init.start, end: init.end, replacement });
@@ -5785,7 +6502,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
5785
6502
  const fused = compileComposeCall(init);
5786
6503
  if (!fused) continue;
5787
6504
  localComposedPieces.set(varName, fused.pieces);
5788
- const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.pieces) : fused.replacement;
6505
+ const replacement = exportPrefix ? withCarriedPieces(fused.replacement, fused.carried) : fused.replacement;
5789
6506
  replacements.push({ start: init.start, end: init.end, replacement });
5790
6507
  continue;
5791
6508
  }