parseman 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,6 +18,15 @@ function intersects(a, b) {
18
18
  function fromChar(code) {
19
19
  return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
20
20
  }
21
+ function startsFirstSet(combinator, input, pos) {
22
+ const fs = combinator._meta.firstSet;
23
+ if (fs.kind === "any") return true;
24
+ if (fs.kind === "empty") return false;
25
+ const code = input.codePointAt(pos);
26
+ if (code === void 0) return false;
27
+ for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
28
+ return false;
29
+ }
21
30
  function fromRange(lo, hi) {
22
31
  return { kind: "ranges", ranges: [{ lo, hi }] };
23
32
  }
@@ -944,6 +953,17 @@ function deriveExpected(c) {
944
953
  return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
945
954
  case "attempt":
946
955
  return deriveExpected(def.parser);
956
+ // Delegating wrappers whose first-token failure is their inner parser's — must
957
+ // pass through so a start-failure (and the first-set-miss fast-path in attempt/
958
+ // node, which reads this) names the real expected token, not the wrapper tag.
959
+ case "field":
960
+ case "withCtx":
961
+ case "recover":
962
+ return deriveExpected(def.parser);
963
+ case "skip":
964
+ return deriveExpected(def.main);
965
+ case "expect":
966
+ return def.expected;
947
967
  case "node":
948
968
  case "grammar":
949
969
  case "trivia":
@@ -1007,7 +1027,7 @@ function choice(...args) {
1007
1027
  disjoint
1008
1028
  };
1009
1029
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
1010
- const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
1030
+ const autoNot = !disjoint && !hasGates && (strategy?.tag === "firstMatch" || strategy?.tag === "sharedPrefix") ? computeAutoNot(parsers) : parsers.map(() => null);
1011
1031
  let greedyLitMap = null;
1012
1032
  let sortedParsers = null;
1013
1033
  const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
@@ -1133,8 +1153,47 @@ function detectStrategy(parsers) {
1133
1153
  );
1134
1154
  return { tag: "literalsLongestFirst", sortedIndices };
1135
1155
  }
1156
+ const shared = detectSharedPrefix(parsers);
1157
+ if (shared !== null) return shared;
1136
1158
  return { tag: "firstMatch" };
1137
1159
  }
1160
+ function bareLeadingTermKey(term) {
1161
+ const d = term._def;
1162
+ if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
1163
+ if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
1164
+ return null;
1165
+ }
1166
+ function detectSharedPrefix(parsers) {
1167
+ if (parsers.length < 2) return null;
1168
+ let key = null;
1169
+ let prefix = null;
1170
+ const members = [];
1171
+ for (let i = 0; i < parsers.length; i++) {
1172
+ const term0 = leadingTermOfArm(parsers[i]);
1173
+ if (term0 === null) return null;
1174
+ const k = bareLeadingTermKey(term0);
1175
+ if (k === null) return null;
1176
+ if (key === null) {
1177
+ key = k;
1178
+ prefix = term0;
1179
+ } else if (k !== key) return null;
1180
+ members.push(i);
1181
+ }
1182
+ if (prefix === null || members.length < 2) return null;
1183
+ return { tag: "sharedPrefix", prefix, members };
1184
+ }
1185
+ function leadingTermOfArm(arm) {
1186
+ let d = arm._def;
1187
+ for (; ; ) {
1188
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
1189
+ d = d.parser._def;
1190
+ continue;
1191
+ }
1192
+ break;
1193
+ }
1194
+ if (d.tag !== "sequence" || d.parsers.length < 2) return null;
1195
+ return d.parsers[0];
1196
+ }
1138
1197
  function computeAutoNot(parsers) {
1139
1198
  return parsers.map((p, i) => {
1140
1199
  const litVal = getCoreLiteralValue(p);
@@ -1728,11 +1787,20 @@ function sequence(...parsers) {
1728
1787
 
1729
1788
  // src/combinators/attempt.ts
1730
1789
  function attempt(parser2) {
1790
+ const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
1791
+ let failExpected;
1731
1792
  return {
1732
1793
  _tag: "attempt",
1733
1794
  _meta: parser2._meta,
1734
1795
  _def: { tag: "attempt", parser: parser2 },
1735
1796
  parse(input, pos, ctx) {
1797
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(parser2, input, pos)) {
1798
+ if (failExpected === void 0) {
1799
+ const e = deriveExpected(parser2);
1800
+ failExpected = e.length > 0 ? e : [parser2._tag];
1801
+ }
1802
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
1803
+ }
1736
1804
  const mark = saveCstMark(ctx);
1737
1805
  const log = ctx._triviaLog?.length;
1738
1806
  const result = parser2.parse(input, pos, ctx);
@@ -1745,7 +1813,7 @@ function attempt(parser2) {
1745
1813
  }
1746
1814
 
1747
1815
  // src/combinators/repeat.ts
1748
- function repItem(combinator, input, cur, ctx) {
1816
+ function repItem(combinator, input, cur, ctx, guardable) {
1749
1817
  const mark = saveTriviaMark(ctx);
1750
1818
  let pos = cur;
1751
1819
  if (ctx.trivia) {
@@ -1761,6 +1829,10 @@ function repItem(combinator, input, cur, ctx) {
1761
1829
  rollbackTrivia(ctx, mark);
1762
1830
  return "stop";
1763
1831
  }
1832
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
1833
+ rollbackTrivia(ctx, mark);
1834
+ return "stop";
1835
+ }
1764
1836
  const result = combinator.parse(input, pos, ctx);
1765
1837
  if (!result.ok) {
1766
1838
  rollbackTrivia(ctx, mark);
@@ -1780,6 +1852,7 @@ function many(combinator) {
1780
1852
  };
1781
1853
  const def = { tag: "many", parser: combinator, min: 0 };
1782
1854
  let expected;
1855
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1783
1856
  return {
1784
1857
  _tag: "many",
1785
1858
  _meta: meta,
@@ -1788,7 +1861,7 @@ function many(combinator) {
1788
1861
  const values = def.valueUnused ? void 0 : [];
1789
1862
  let cur = pos;
1790
1863
  while (cur < input.length) {
1791
- const item = repItem(combinator, input, cur, ctx);
1864
+ const item = repItem(combinator, input, cur, ctx, guardable);
1792
1865
  if (item === "stop") break;
1793
1866
  if ("fail" in item) {
1794
1867
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1816,6 +1889,7 @@ function oneOrMore(combinator) {
1816
1889
  };
1817
1890
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1818
1891
  let expected;
1892
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1819
1893
  return {
1820
1894
  _tag: "oneOrMore",
1821
1895
  _meta: meta,
@@ -1826,7 +1900,7 @@ function oneOrMore(combinator) {
1826
1900
  const values = def.valueUnused ? void 0 : [first.value];
1827
1901
  let cur = first.span.end;
1828
1902
  while (cur < input.length) {
1829
- const item = repItem(combinator, input, cur, ctx);
1903
+ const item = repItem(combinator, input, cur, ctx, guardable);
1830
1904
  if (item === "stop") break;
1831
1905
  if ("fail" in item) {
1832
1906
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1869,15 +1943,6 @@ function optional(combinator) {
1869
1943
  }
1870
1944
  };
1871
1945
  }
1872
- function startsFirstSet(combinator, input, pos) {
1873
- const fs = combinator._meta.firstSet;
1874
- if (fs.kind === "any") return true;
1875
- if (fs.kind === "empty") return false;
1876
- const code = input.codePointAt(pos);
1877
- if (code === void 0) return false;
1878
- for (const r of fs.ranges) if (code >= r.lo && code <= r.hi) return true;
1879
- return false;
1880
- }
1881
1946
  function sepBy(combinator, separator) {
1882
1947
  const meta = {
1883
1948
  firstSet: combinator._meta.firstSet,
@@ -2411,11 +2476,20 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2411
2476
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : true);
2412
2477
  const clonesState = build ? buildReadsState(def) : true;
2413
2478
  const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
2479
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
2480
+ let failExpected;
2414
2481
  return {
2415
2482
  _tag: "node",
2416
2483
  _meta: meta,
2417
2484
  _def: def,
2418
2485
  parse(input, pos, ctx) {
2486
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
2487
+ if (failExpected === void 0) {
2488
+ const e = deriveExpected(combinator);
2489
+ failExpected = e.length > 0 ? e : [combinator._tag];
2490
+ }
2491
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
2492
+ }
2419
2493
  const saved = beginCstNodeCapture(ctx);
2420
2494
  const savedFields = ctx._fields;
2421
2495
  ctx._fields = capturesFields ? [] : void 0;
@@ -4140,6 +4214,61 @@ function tryInlineDestructureTransform(src, valueVars) {
4140
4214
  }
4141
4215
 
4142
4216
  // src/compiler/codegen.ts
4217
+ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
4218
+ if (seen.has(p)) return { concrete: empty(), refs: [] };
4219
+ seen.add(p);
4220
+ const d = p._def;
4221
+ const rec = (c) => leadingFirstSetRecipe(c, seen);
4222
+ const merge2 = (a, b) => ({ concrete: union(a.concrete, b.concrete), refs: [...a.refs, ...b.refs] });
4223
+ switch (d.tag) {
4224
+ case "lazy": {
4225
+ const name = p._ruleName;
4226
+ if (name !== void 0) return { concrete: empty(), refs: [name] };
4227
+ try {
4228
+ return rec(d.thunk());
4229
+ } catch {
4230
+ return { concrete: any(), refs: [] };
4231
+ }
4232
+ }
4233
+ case "literal":
4234
+ case "regex":
4235
+ case "keywords":
4236
+ return { concrete: p._meta.firstSet, refs: [] };
4237
+ case "choice": {
4238
+ let out = { concrete: empty(), refs: [] };
4239
+ for (const arm of d.parsers) out = merge2(out, rec(arm));
4240
+ return out;
4241
+ }
4242
+ case "sequence": {
4243
+ let out = { concrete: empty(), refs: [] };
4244
+ for (const term of d.parsers) {
4245
+ out = merge2(out, rec(term));
4246
+ if (!matchesEmpty(term)) return out;
4247
+ }
4248
+ return out;
4249
+ }
4250
+ case "oneOrMore":
4251
+ case "many":
4252
+ case "optional":
4253
+ case "transform":
4254
+ case "label":
4255
+ case "field":
4256
+ case "trivia":
4257
+ case "token":
4258
+ case "leaf":
4259
+ case "node":
4260
+ case "grammar":
4261
+ case "expect":
4262
+ return rec(d.parser);
4263
+ case "sepBy":
4264
+ return rec(d.parser);
4265
+ case "skip":
4266
+ return rec(d.main);
4267
+ // not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
4268
+ default:
4269
+ return { concrete: p._meta.firstSet, refs: [] };
4270
+ }
4271
+ }
4143
4272
  var HOST_READS_DECL = "const _hostReads = (b, n) => { if (b === undefined) return false; let s; try { s = Function.prototype.toString.call(b) } catch (e) { return true } if (/\\barguments\\b/.test(s)) return true; const m = /^[^(]*\\(([\\s\\S]*?)\\)/.exec(s); if (m && /\\.\\.\\.|=/.test(m[1])) return true; return b.length > n }";
4144
4273
  var _loweringSink = null;
4145
4274
  function v(ctx, prefix = "_v") {
@@ -4373,6 +4502,7 @@ var SWITCH_RANGE_LIMIT = 4;
4373
4502
  var SWITCH_MAX_CASES = 48;
4374
4503
  var SWITCH_MIN_CASES = 3;
4375
4504
  var _forceDisjointIf = false;
4505
+ var _forceNoSharedPrefix = false;
4376
4506
  function planDisjointDispatch(parsers) {
4377
4507
  const cases = [];
4378
4508
  let total = 0;
@@ -5008,7 +5138,7 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos, cov
5008
5138
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
5009
5139
  return { stmts, valueVar: valV, endVar: endV };
5010
5140
  }
5011
- function emitFirstMatch(def, ctx, pos, coverageBase) {
5141
+ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
5012
5142
  const resValV = v(ctx, "_crv");
5013
5143
  const resEndV = v(ctx, "_cre");
5014
5144
  const resOkV = v(ctx, "_crok");
@@ -5022,6 +5152,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5022
5152
  ...autoRejectedV === void 0 ? [] : [`${ind0}const ${autoRejectedV} = []`],
5023
5153
  `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
5024
5154
  ];
5155
+ if (preStmts) stmts.push(...preStmts);
5025
5156
  for (let i = 0; i < def.parsers.length; i++) {
5026
5157
  const p = def.parsers[i];
5027
5158
  const gate = def.gates[i];
@@ -5029,11 +5160,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5029
5160
  const atStart = failsAtStart(p);
5030
5161
  const staticFx = armStaticExpected(ctx, p);
5031
5162
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
5032
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5163
+ const baseFsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5033
5164
  if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
5034
5165
  const deep = firstSetOf(p);
5035
5166
  return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
5036
5167
  })();
5168
+ const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
5037
5169
  let gateCond = null;
5038
5170
  if (gate) {
5039
5171
  const gateIdx = ctx.mapFns.length;
@@ -5102,11 +5234,64 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5102
5234
  stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
5103
5235
  return { stmts, valueVar: resValV, endVar: resEndV };
5104
5236
  }
5237
+ function emitsAsNamedFn(p, ctx) {
5238
+ if (p._def.tag === "lazy") return true;
5239
+ if (ctx.ruleNames?.has(p)) return true;
5240
+ if (ctx.namedParsers.has(p)) return true;
5241
+ const usage = ctx.lazyUsage;
5242
+ return !!usage && !ctx.noHoist && !ctx.capAsTrivia && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE;
5243
+ }
5244
+ function armReplayInScope(arm, ctx) {
5245
+ let p = arm;
5246
+ for (; ; ) {
5247
+ if (emitsAsNamedFn(p, ctx)) return false;
5248
+ const d = p._def;
5249
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
5250
+ p = d.parser;
5251
+ continue;
5252
+ }
5253
+ return d.tag === "sequence";
5254
+ }
5255
+ }
5256
+ function emitSharedPrefix(def, strategy, ctx, pos) {
5257
+ const { prefix, members } = strategy;
5258
+ const savedCapturing = ctx.capturing;
5259
+ ctx.capturing = false;
5260
+ const pfx = emitFallible(prefix, ctx, pos, false);
5261
+ ctx.capturing = savedCapturing;
5262
+ const map = ctx.replayPrefix ??= /* @__PURE__ */ new Map();
5263
+ const keys = [];
5264
+ for (const i of members) {
5265
+ const term0 = leadingTermOfArm(def.parsers[i]);
5266
+ if (term0 === null) continue;
5267
+ map.set(term0, { valVar: pfx.valVar, endVar: pfx.endVar });
5268
+ keys.push(term0);
5269
+ }
5270
+ const r = emitFirstMatch(def, ctx, pos, void 0, pfx.stmts, pfx.okVar);
5271
+ for (const k of keys) map.delete(k);
5272
+ return r;
5273
+ }
5274
+ function emitReplayPrefixLeaf(ctx, pos, replay) {
5275
+ const vv = v(ctx);
5276
+ return {
5277
+ stmts: [
5278
+ `${ind(ctx)}const ${vv} = ${replay.valVar}`,
5279
+ ...emitLeafCapture(ctx, vv, pos, replay.endVar)
5280
+ ],
5281
+ valueVar: vv,
5282
+ endVar: replay.endVar
5283
+ };
5284
+ }
5105
5285
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos, coverageBase) {
5106
5286
  if (strategy.tag === "greedyClassify")
5107
5287
  return emitGreedyClassify(def, strategy.superIndex, ctx, pos, coverageBase);
5108
5288
  if (strategy.tag === "literalsLongestFirst")
5109
5289
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos, coverageBase);
5290
+ if (strategy.tag === "sharedPrefix") {
5291
+ if (!_forceNoSharedPrefix && coverageBase === void 0 && !ctx.recovery && strategy.members.every((i) => armReplayInScope(def.parsers[i], ctx)))
5292
+ return emitSharedPrefix(def, strategy, ctx, pos);
5293
+ return emitFirstMatch(def, ctx, pos, coverageBase);
5294
+ }
5110
5295
  return emitFirstMatch(def, ctx, pos, coverageBase);
5111
5296
  }
5112
5297
  function pushMapFn(ctx, fn, src) {
@@ -5186,6 +5371,13 @@ function emitMany(def, ctx, pos) {
5186
5371
  itemPos = npV;
5187
5372
  }
5188
5373
  }
5374
+ if (!ctx.recovery && !ctx.coverage && !failsAtStart(def.parser) && needsFirstSetGuard(def.parser)) {
5375
+ const lcV = v(ctx, "_lc");
5376
+ stmts.push(
5377
+ `${ind(ctx)}const ${lcV} = ${itemPos} < input.length ? (input.codePointAt(${itemPos}) ?? -1) : -1`,
5378
+ `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
5379
+ );
5380
+ }
5189
5381
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
5190
5382
  stmts.push(...iterStmts);
5191
5383
  if (ctx.recovery) {
@@ -5236,8 +5428,19 @@ function emitAttempt(p, def, ctx, pos) {
5236
5428
  const rollback = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${trivia2}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`;
5237
5429
  const traceId = ctx.coverage?.plan.attempts.get(p);
5238
5430
  const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5431
+ const preGuard = [];
5432
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
5433
+ const gcV = v(ctx, "_agc");
5434
+ const gExp = armStaticExpected(ctx, def.parser);
5435
+ const gFail = ctx.failLabel ? `{ _ctx._fe = ${pos};${ctx.recordFail ? ` _ctx._fx = ${gExp};` : ""} break ${ctx.failLabel} }` : `{ _ctx._fe = ${pos}; ${failReturnArr(gExp, pos)} }`;
5436
+ preGuard.push(
5437
+ `${ind(ctx)}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5438
+ `${ind(ctx)}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5439
+ );
5440
+ }
5239
5441
  return {
5240
5442
  stmts: [
5443
+ ...preGuard,
5241
5444
  `${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${trivia2} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`,
5242
5445
  ...inner.stmts,
5243
5446
  ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
@@ -5617,7 +5820,18 @@ ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRec
5617
5820
  const saveTrivia = needsTriviaFrame ? `, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${smk ? `, ${smk} = _ctx._triviaCaptureMask` : ""}` : "";
5618
5821
  const installTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${capTLv ?? "false"}; _ctx._cstTriviaLog = ${innerTl}${smk ? `; _ctx._triviaCaptureMask = ${capTLv} && _ctx.build !== undefined && _ctx.build._parsemanTriviaKinds !== undefined ? _ctx.build._parsemanTriviaKinds(${JSON.stringify(def.type)}) : ${smk}` : ""}` : "";
5619
5822
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
5823
+ const preGuard = [];
5824
+ if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
5825
+ const gcV = v(ctx, "_ngc");
5826
+ const gExp = armStaticExpected(ctx, def.parser);
5827
+ const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(gExp, pos);
5828
+ preGuard.push(
5829
+ `${i}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5830
+ `${i}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5831
+ );
5832
+ }
5620
5833
  const stmts = [
5834
+ ...preGuard,
5621
5835
  profHoist,
5622
5836
  allocStmt,
5623
5837
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
@@ -5791,6 +6005,8 @@ function emitExpect(def, ctx, pos) {
5791
6005
  return { stmts, valueVar: valVar, endVar };
5792
6006
  }
5793
6007
  function emit(p, ctx, pos) {
6008
+ const replay = ctx.replayPrefix?.get(p);
6009
+ if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
5794
6010
  const dispatch = (emissionPos = pos) => {
5795
6011
  const savedRule = ctx.activeCoverageRuleId;
5796
6012
  const rule = ctx.coverage?.plan.rules.get(p);
@@ -6534,6 +6750,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6534
6750
  ].join("\n")));
6535
6751
  }
6536
6752
  const firstSets = /* @__PURE__ */ new Map();
6753
+ const firstSetRecipes = /* @__PURE__ */ new Map();
6537
6754
  for (const [key, rule] of ruleMap) {
6538
6755
  let resolved = rule;
6539
6756
  const d = rule._def;
@@ -6545,6 +6762,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6545
6762
  }
6546
6763
  }
6547
6764
  firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
6765
+ firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { concrete: any(), refs: [] } : leadingFirstSetRecipe(resolved));
6548
6766
  }
6549
6767
  return {
6550
6768
  ns,
@@ -6553,6 +6771,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6553
6771
  ruleFns,
6554
6772
  wrappers,
6555
6773
  firstSets,
6774
+ firstSetRecipes,
6556
6775
  deps: ruleDependencies(ruleMap),
6557
6776
  needsEmptyTl: !!ctx.needsEmptyTl,
6558
6777
  needsHostReads: !!ctx.needsHostReads,
@@ -7188,10 +7407,30 @@ function fusedBody(pieces) {
7188
7407
  ];
7189
7408
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
7190
7409
  const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
7191
- const finalFS = /* @__PURE__ */ new Map();
7410
+ const ANY2 = { kind: "any" };
7411
+ const recipes = /* @__PURE__ */ new Map();
7412
+ const shallow = /* @__PURE__ */ new Map();
7192
7413
  for (const [k, p] of winner) {
7414
+ const r = p.firstSetRecipes?.get(k);
7415
+ if (r) recipes.set(k, r);
7193
7416
  const fs = p.firstSets?.get(k);
7194
- if (fs) finalFS.set(k, fs);
7417
+ if (fs) shallow.set(k, fs);
7418
+ }
7419
+ const finalFS = /* @__PURE__ */ new Map();
7420
+ for (const [k] of winner) finalFS.set(k, recipes.get(k)?.concrete ?? shallow.get(k) ?? ANY2);
7421
+ for (let iter = 0; iter <= recipes.size; iter++) {
7422
+ let changed = false;
7423
+ for (const [k, r] of recipes) {
7424
+ let fs = r.concrete;
7425
+ for (const ref2 of r.refs) {
7426
+ fs = union(fs, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
7427
+ }
7428
+ if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs)) {
7429
+ finalFS.set(k, fs);
7430
+ changed = true;
7431
+ }
7432
+ }
7433
+ if (!changed) break;
7195
7434
  }
7196
7435
  const body = rawBody.replace(
7197
7436
  // Rule name + code-point var are both JS identifiers (rule names are validated