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.
@@ -61,6 +61,15 @@ function intersects(a, b) {
61
61
  function fromChar(code) {
62
62
  return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
63
63
  }
64
+ function startsFirstSet(combinator, input, pos) {
65
+ const fs2 = combinator._meta.firstSet;
66
+ if (fs2.kind === "any") return true;
67
+ if (fs2.kind === "empty") return false;
68
+ const code = input.codePointAt(pos);
69
+ if (code === void 0) return false;
70
+ for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
71
+ return false;
72
+ }
64
73
  function fromRange(lo, hi) {
65
74
  return { kind: "ranges", ranges: [{ lo, hi }] };
66
75
  }
@@ -1020,6 +1029,17 @@ function deriveExpected(c) {
1020
1029
  return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
1021
1030
  case "attempt":
1022
1031
  return deriveExpected(def.parser);
1032
+ // Delegating wrappers whose first-token failure is their inner parser's — must
1033
+ // pass through so a start-failure (and the first-set-miss fast-path in attempt/
1034
+ // node, which reads this) names the real expected token, not the wrapper tag.
1035
+ case "field":
1036
+ case "withCtx":
1037
+ case "recover":
1038
+ return deriveExpected(def.parser);
1039
+ case "skip":
1040
+ return deriveExpected(def.main);
1041
+ case "expect":
1042
+ return def.expected;
1023
1043
  case "node":
1024
1044
  case "grammar":
1025
1045
  case "trivia":
@@ -1080,7 +1100,7 @@ function choice(...args) {
1080
1100
  disjoint
1081
1101
  };
1082
1102
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
1083
- const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
1103
+ const autoNot = !disjoint && !hasGates && (strategy?.tag === "firstMatch" || strategy?.tag === "sharedPrefix") ? computeAutoNot(parsers) : parsers.map(() => null);
1084
1104
  let greedyLitMap = null;
1085
1105
  let sortedParsers = null;
1086
1106
  const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
@@ -1206,8 +1226,47 @@ function detectStrategy(parsers) {
1206
1226
  );
1207
1227
  return { tag: "literalsLongestFirst", sortedIndices };
1208
1228
  }
1229
+ const shared = detectSharedPrefix(parsers);
1230
+ if (shared !== null) return shared;
1209
1231
  return { tag: "firstMatch" };
1210
1232
  }
1233
+ function bareLeadingTermKey(term) {
1234
+ const d = term._def;
1235
+ if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
1236
+ if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
1237
+ return null;
1238
+ }
1239
+ function detectSharedPrefix(parsers) {
1240
+ if (parsers.length < 2) return null;
1241
+ let key = null;
1242
+ let prefix = null;
1243
+ const members = [];
1244
+ for (let i = 0; i < parsers.length; i++) {
1245
+ const term0 = leadingTermOfArm(parsers[i]);
1246
+ if (term0 === null) return null;
1247
+ const k = bareLeadingTermKey(term0);
1248
+ if (k === null) return null;
1249
+ if (key === null) {
1250
+ key = k;
1251
+ prefix = term0;
1252
+ } else if (k !== key) return null;
1253
+ members.push(i);
1254
+ }
1255
+ if (prefix === null || members.length < 2) return null;
1256
+ return { tag: "sharedPrefix", prefix, members };
1257
+ }
1258
+ function leadingTermOfArm(arm) {
1259
+ let d = arm._def;
1260
+ for (; ; ) {
1261
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
1262
+ d = d.parser._def;
1263
+ continue;
1264
+ }
1265
+ break;
1266
+ }
1267
+ if (d.tag !== "sequence" || d.parsers.length < 2) return null;
1268
+ return d.parsers[0];
1269
+ }
1211
1270
  function computeAutoNot(parsers) {
1212
1271
  return parsers.map((p, i) => {
1213
1272
  const litVal = getCoreLiteralValue(p);
@@ -1801,11 +1860,20 @@ function sequence(...parsers) {
1801
1860
 
1802
1861
  // src/combinators/attempt.ts
1803
1862
  function attempt(parser2) {
1863
+ const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
1864
+ let failExpected;
1804
1865
  return {
1805
1866
  _tag: "attempt",
1806
1867
  _meta: parser2._meta,
1807
1868
  _def: { tag: "attempt", parser: parser2 },
1808
1869
  parse(input, pos, ctx) {
1870
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(parser2, input, pos)) {
1871
+ if (failExpected === void 0) {
1872
+ const e = deriveExpected(parser2);
1873
+ failExpected = e.length > 0 ? e : [parser2._tag];
1874
+ }
1875
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
1876
+ }
1809
1877
  const mark = saveCstMark(ctx);
1810
1878
  const log = ctx._triviaLog?.length;
1811
1879
  const result = parser2.parse(input, pos, ctx);
@@ -1818,7 +1886,7 @@ function attempt(parser2) {
1818
1886
  }
1819
1887
 
1820
1888
  // src/combinators/repeat.ts
1821
- function repItem(combinator, input, cur, ctx) {
1889
+ function repItem(combinator, input, cur, ctx, guardable) {
1822
1890
  const mark = saveTriviaMark(ctx);
1823
1891
  let pos = cur;
1824
1892
  if (ctx.trivia) {
@@ -1834,6 +1902,10 @@ function repItem(combinator, input, cur, ctx) {
1834
1902
  rollbackTrivia(ctx, mark);
1835
1903
  return "stop";
1836
1904
  }
1905
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
1906
+ rollbackTrivia(ctx, mark);
1907
+ return "stop";
1908
+ }
1837
1909
  const result = combinator.parse(input, pos, ctx);
1838
1910
  if (!result.ok) {
1839
1911
  rollbackTrivia(ctx, mark);
@@ -1853,6 +1925,7 @@ function many(combinator) {
1853
1925
  };
1854
1926
  const def = { tag: "many", parser: combinator, min: 0 };
1855
1927
  let expected;
1928
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1856
1929
  return {
1857
1930
  _tag: "many",
1858
1931
  _meta: meta,
@@ -1861,7 +1934,7 @@ function many(combinator) {
1861
1934
  const values = def.valueUnused ? void 0 : [];
1862
1935
  let cur = pos;
1863
1936
  while (cur < input.length) {
1864
- const item = repItem(combinator, input, cur, ctx);
1937
+ const item = repItem(combinator, input, cur, ctx, guardable);
1865
1938
  if (item === "stop") break;
1866
1939
  if ("fail" in item) {
1867
1940
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1889,6 +1962,7 @@ function oneOrMore(combinator) {
1889
1962
  };
1890
1963
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1891
1964
  let expected;
1965
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1892
1966
  return {
1893
1967
  _tag: "oneOrMore",
1894
1968
  _meta: meta,
@@ -1899,7 +1973,7 @@ function oneOrMore(combinator) {
1899
1973
  const values = def.valueUnused ? void 0 : [first.value];
1900
1974
  let cur = first.span.end;
1901
1975
  while (cur < input.length) {
1902
- const item = repItem(combinator, input, cur, ctx);
1976
+ const item = repItem(combinator, input, cur, ctx, guardable);
1903
1977
  if (item === "stop") break;
1904
1978
  if ("fail" in item) {
1905
1979
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1942,15 +2016,6 @@ function optional(combinator) {
1942
2016
  }
1943
2017
  };
1944
2018
  }
1945
- function startsFirstSet(combinator, input, pos) {
1946
- const fs2 = combinator._meta.firstSet;
1947
- if (fs2.kind === "any") return true;
1948
- if (fs2.kind === "empty") return false;
1949
- const code = input.codePointAt(pos);
1950
- if (code === void 0) return false;
1951
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
1952
- return false;
1953
- }
1954
2019
  function sepBy(combinator, separator) {
1955
2020
  const meta = {
1956
2021
  firstSet: combinator._meta.firstSet,
@@ -2447,11 +2512,20 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2447
2512
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : true);
2448
2513
  const clonesState = build ? buildReadsState(def) : true;
2449
2514
  const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
2515
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
2516
+ let failExpected;
2450
2517
  return {
2451
2518
  _tag: "node",
2452
2519
  _meta: meta,
2453
2520
  _def: def,
2454
2521
  parse(input, pos, ctx) {
2522
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
2523
+ if (failExpected === void 0) {
2524
+ const e = deriveExpected(combinator);
2525
+ failExpected = e.length > 0 ? e : [combinator._tag];
2526
+ }
2527
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
2528
+ }
2455
2529
  const saved = beginCstNodeCapture(ctx);
2456
2530
  const savedFields = ctx._fields;
2457
2531
  ctx._fields = capturesFields ? [] : void 0;
@@ -4151,6 +4225,61 @@ function tryInlineDestructureTransform(src, valueVars) {
4151
4225
  }
4152
4226
 
4153
4227
  // src/compiler/codegen.ts
4228
+ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
4229
+ if (seen.has(p)) return { concrete: empty(), refs: [] };
4230
+ seen.add(p);
4231
+ const d = p._def;
4232
+ const rec = (c) => leadingFirstSetRecipe(c, seen);
4233
+ const merge = (a, b) => ({ concrete: union(a.concrete, b.concrete), refs: [...a.refs, ...b.refs] });
4234
+ switch (d.tag) {
4235
+ case "lazy": {
4236
+ const name = p._ruleName;
4237
+ if (name !== void 0) return { concrete: empty(), refs: [name] };
4238
+ try {
4239
+ return rec(d.thunk());
4240
+ } catch {
4241
+ return { concrete: any(), refs: [] };
4242
+ }
4243
+ }
4244
+ case "literal":
4245
+ case "regex":
4246
+ case "keywords":
4247
+ return { concrete: p._meta.firstSet, refs: [] };
4248
+ case "choice": {
4249
+ let out = { concrete: empty(), refs: [] };
4250
+ for (const arm of d.parsers) out = merge(out, rec(arm));
4251
+ return out;
4252
+ }
4253
+ case "sequence": {
4254
+ let out = { concrete: empty(), refs: [] };
4255
+ for (const term of d.parsers) {
4256
+ out = merge(out, rec(term));
4257
+ if (!matchesEmpty(term)) return out;
4258
+ }
4259
+ return out;
4260
+ }
4261
+ case "oneOrMore":
4262
+ case "many":
4263
+ case "optional":
4264
+ case "transform":
4265
+ case "label":
4266
+ case "field":
4267
+ case "trivia":
4268
+ case "token":
4269
+ case "leaf":
4270
+ case "node":
4271
+ case "grammar":
4272
+ case "expect":
4273
+ return rec(d.parser);
4274
+ case "sepBy":
4275
+ return rec(d.parser);
4276
+ case "skip":
4277
+ return rec(d.main);
4278
+ // not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
4279
+ default:
4280
+ return { concrete: p._meta.firstSet, refs: [] };
4281
+ }
4282
+ }
4154
4283
  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 }";
4155
4284
  var _loweringSink = null;
4156
4285
  function beginLoweringCapture() {
@@ -4392,6 +4521,7 @@ var SWITCH_RANGE_LIMIT = 4;
4392
4521
  var SWITCH_MAX_CASES = 48;
4393
4522
  var SWITCH_MIN_CASES = 3;
4394
4523
  var _forceDisjointIf = false;
4524
+ var _forceNoSharedPrefix = false;
4395
4525
  function planDisjointDispatch(parsers) {
4396
4526
  const cases = [];
4397
4527
  let total = 0;
@@ -5027,7 +5157,7 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos, cov
5027
5157
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
5028
5158
  return { stmts, valueVar: valV, endVar: endV };
5029
5159
  }
5030
- function emitFirstMatch(def, ctx, pos, coverageBase) {
5160
+ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
5031
5161
  const resValV = v(ctx, "_crv");
5032
5162
  const resEndV = v(ctx, "_cre");
5033
5163
  const resOkV = v(ctx, "_crok");
@@ -5041,6 +5171,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5041
5171
  ...autoRejectedV === void 0 ? [] : [`${ind0}const ${autoRejectedV} = []`],
5042
5172
  `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
5043
5173
  ];
5174
+ if (preStmts) stmts.push(...preStmts);
5044
5175
  for (let i = 0; i < def.parsers.length; i++) {
5045
5176
  const p = def.parsers[i];
5046
5177
  const gate = def.gates[i];
@@ -5048,11 +5179,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5048
5179
  const atStart = failsAtStart(p);
5049
5180
  const staticFx = armStaticExpected(ctx, p);
5050
5181
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
5051
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5182
+ const baseFsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5052
5183
  if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
5053
5184
  const deep = firstSetOf(p);
5054
5185
  return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
5055
5186
  })();
5187
+ const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
5056
5188
  let gateCond = null;
5057
5189
  if (gate) {
5058
5190
  const gateIdx = ctx.mapFns.length;
@@ -5121,11 +5253,64 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5121
5253
  stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
5122
5254
  return { stmts, valueVar: resValV, endVar: resEndV };
5123
5255
  }
5256
+ function emitsAsNamedFn(p, ctx) {
5257
+ if (p._def.tag === "lazy") return true;
5258
+ if (ctx.ruleNames?.has(p)) return true;
5259
+ if (ctx.namedParsers.has(p)) return true;
5260
+ const usage = ctx.lazyUsage;
5261
+ return !!usage && !ctx.noHoist && !ctx.capAsTrivia && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE;
5262
+ }
5263
+ function armReplayInScope(arm, ctx) {
5264
+ let p = arm;
5265
+ for (; ; ) {
5266
+ if (emitsAsNamedFn(p, ctx)) return false;
5267
+ const d = p._def;
5268
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
5269
+ p = d.parser;
5270
+ continue;
5271
+ }
5272
+ return d.tag === "sequence";
5273
+ }
5274
+ }
5275
+ function emitSharedPrefix(def, strategy, ctx, pos) {
5276
+ const { prefix, members } = strategy;
5277
+ const savedCapturing = ctx.capturing;
5278
+ ctx.capturing = false;
5279
+ const pfx = emitFallible(prefix, ctx, pos, false);
5280
+ ctx.capturing = savedCapturing;
5281
+ const map = ctx.replayPrefix ??= /* @__PURE__ */ new Map();
5282
+ const keys = [];
5283
+ for (const i of members) {
5284
+ const term0 = leadingTermOfArm(def.parsers[i]);
5285
+ if (term0 === null) continue;
5286
+ map.set(term0, { valVar: pfx.valVar, endVar: pfx.endVar });
5287
+ keys.push(term0);
5288
+ }
5289
+ const r = emitFirstMatch(def, ctx, pos, void 0, pfx.stmts, pfx.okVar);
5290
+ for (const k of keys) map.delete(k);
5291
+ return r;
5292
+ }
5293
+ function emitReplayPrefixLeaf(ctx, pos, replay) {
5294
+ const vv = v(ctx);
5295
+ return {
5296
+ stmts: [
5297
+ `${ind(ctx)}const ${vv} = ${replay.valVar}`,
5298
+ ...emitLeafCapture(ctx, vv, pos, replay.endVar)
5299
+ ],
5300
+ valueVar: vv,
5301
+ endVar: replay.endVar
5302
+ };
5303
+ }
5124
5304
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos, coverageBase) {
5125
5305
  if (strategy.tag === "greedyClassify")
5126
5306
  return emitGreedyClassify(def, strategy.superIndex, ctx, pos, coverageBase);
5127
5307
  if (strategy.tag === "literalsLongestFirst")
5128
5308
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos, coverageBase);
5309
+ if (strategy.tag === "sharedPrefix") {
5310
+ if (!_forceNoSharedPrefix && coverageBase === void 0 && !ctx.recovery && strategy.members.every((i) => armReplayInScope(def.parsers[i], ctx)))
5311
+ return emitSharedPrefix(def, strategy, ctx, pos);
5312
+ return emitFirstMatch(def, ctx, pos, coverageBase);
5313
+ }
5129
5314
  return emitFirstMatch(def, ctx, pos, coverageBase);
5130
5315
  }
5131
5316
  function pushMapFn(ctx, fn, src) {
@@ -5205,6 +5390,13 @@ function emitMany(def, ctx, pos) {
5205
5390
  itemPos = npV;
5206
5391
  }
5207
5392
  }
5393
+ if (!ctx.recovery && !ctx.coverage && !failsAtStart(def.parser) && needsFirstSetGuard(def.parser)) {
5394
+ const lcV = v(ctx, "_lc");
5395
+ stmts.push(
5396
+ `${ind(ctx)}const ${lcV} = ${itemPos} < input.length ? (input.codePointAt(${itemPos}) ?? -1) : -1`,
5397
+ `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
5398
+ );
5399
+ }
5208
5400
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
5209
5401
  stmts.push(...iterStmts);
5210
5402
  if (ctx.recovery) {
@@ -5255,8 +5447,19 @@ function emitAttempt(p, def, ctx, pos) {
5255
5447
  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}`;
5256
5448
  const traceId = ctx.coverage?.plan.attempts.get(p);
5257
5449
  const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5450
+ const preGuard = [];
5451
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
5452
+ const gcV = v(ctx, "_agc");
5453
+ const gExp = armStaticExpected(ctx, def.parser);
5454
+ const gFail = ctx.failLabel ? `{ _ctx._fe = ${pos};${ctx.recordFail ? ` _ctx._fx = ${gExp};` : ""} break ${ctx.failLabel} }` : `{ _ctx._fe = ${pos}; ${failReturnArr(gExp, pos)} }`;
5455
+ preGuard.push(
5456
+ `${ind(ctx)}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5457
+ `${ind(ctx)}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5458
+ );
5459
+ }
5258
5460
  return {
5259
5461
  stmts: [
5462
+ ...preGuard,
5260
5463
  `${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`,
5261
5464
  ...inner.stmts,
5262
5465
  ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
@@ -5636,7 +5839,18 @@ ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRec
5636
5839
  const saveTrivia = needsTriviaFrame ? `, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${smk ? `, ${smk} = _ctx._triviaCaptureMask` : ""}` : "";
5637
5840
  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}` : ""}` : "";
5638
5841
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
5842
+ const preGuard = [];
5843
+ if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
5844
+ const gcV = v(ctx, "_ngc");
5845
+ const gExp = armStaticExpected(ctx, def.parser);
5846
+ const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(gExp, pos);
5847
+ preGuard.push(
5848
+ `${i}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5849
+ `${i}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5850
+ );
5851
+ }
5639
5852
  const stmts = [
5853
+ ...preGuard,
5640
5854
  profHoist,
5641
5855
  allocStmt,
5642
5856
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
@@ -5810,6 +6024,8 @@ function emitExpect(def, ctx, pos) {
5810
6024
  return { stmts, valueVar: valVar, endVar };
5811
6025
  }
5812
6026
  function emit(p, ctx, pos) {
6027
+ const replay = ctx.replayPrefix?.get(p);
6028
+ if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
5813
6029
  const dispatch = (emissionPos = pos) => {
5814
6030
  const savedRule = ctx.activeCoverageRuleId;
5815
6031
  const rule = ctx.coverage?.plan.rules.get(p);
@@ -6663,6 +6879,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6663
6879
  ].join("\n")));
6664
6880
  }
6665
6881
  const firstSets = /* @__PURE__ */ new Map();
6882
+ const firstSetRecipes = /* @__PURE__ */ new Map();
6666
6883
  for (const [key, rule] of ruleMap) {
6667
6884
  let resolved = rule;
6668
6885
  const d = rule._def;
@@ -6674,6 +6891,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6674
6891
  }
6675
6892
  }
6676
6893
  firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
6894
+ firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { concrete: any(), refs: [] } : leadingFirstSetRecipe(resolved));
6677
6895
  }
6678
6896
  return {
6679
6897
  ns,
@@ -6682,6 +6900,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6682
6900
  ruleFns,
6683
6901
  wrappers,
6684
6902
  firstSets,
6903
+ firstSetRecipes,
6685
6904
  deps: ruleDependencies(ruleMap),
6686
6905
  needsEmptyTl: !!ctx.needsEmptyTl,
6687
6906
  needsHostReads: !!ctx.needsHostReads,
@@ -7328,10 +7547,30 @@ function fusedBody(pieces) {
7328
7547
  ];
7329
7548
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
7330
7549
  const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
7331
- const finalFS = /* @__PURE__ */ new Map();
7550
+ const ANY2 = { kind: "any" };
7551
+ const recipes = /* @__PURE__ */ new Map();
7552
+ const shallow = /* @__PURE__ */ new Map();
7332
7553
  for (const [k, p] of winner) {
7554
+ const r = p.firstSetRecipes?.get(k);
7555
+ if (r) recipes.set(k, r);
7333
7556
  const fs2 = p.firstSets?.get(k);
7334
- if (fs2) finalFS.set(k, fs2);
7557
+ if (fs2) shallow.set(k, fs2);
7558
+ }
7559
+ const finalFS = /* @__PURE__ */ new Map();
7560
+ for (const [k] of winner) finalFS.set(k, recipes.get(k)?.concrete ?? shallow.get(k) ?? ANY2);
7561
+ for (let iter = 0; iter <= recipes.size; iter++) {
7562
+ let changed = false;
7563
+ for (const [k, r] of recipes) {
7564
+ let fs2 = r.concrete;
7565
+ for (const ref2 of r.refs) {
7566
+ fs2 = union(fs2, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
7567
+ }
7568
+ if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
7569
+ finalFS.set(k, fs2);
7570
+ changed = true;
7571
+ }
7572
+ }
7573
+ if (!changed) break;
7335
7574
  }
7336
7575
  const body = rawBody.replace(
7337
7576
  // Rule name + code-point var are both JS identifiers (rule names are validated
@@ -8502,7 +8741,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8502
8741
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
8503
8742
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
8504
8743
  ).join(", ")}])`;
8505
- 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}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
8744
+ 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)}, firstSetRecipes: ${p.firstSetRecipes ? mapLit(p.firstSetRecipes) : "new Map()"}, deps: ${mapLit(p.deps)}, needsEmptyTl: ${p.needsEmptyTl}, needsHostReads: ${p.needsHostReads}, hasDirectBuilders: ${p.hasDirectBuilders === true}, isRecognitionOnly: ${p.isRecognitionOnly === true}, mfFns: [], buildFns: [] }`;
8506
8745
  };
8507
8746
  const isSpread = (it) => "__spreadLocal" in it;
8508
8747
  const isIR = (it) => "ir" in it && !("ruleFns" in it);