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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAwBA,OAAO,WAAW,MAAM,cAAc,CAAA;AAkBtC,MAAM,MAAM,uBAAuB,GAAG;IACpC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;8CAC0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;;;AAgSD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;IAC3C,kFAAkF;IAClF,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,CAAA;AA0CD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,aAAa,cAA6B,EAC1C,kBAAkB,UAAQ,EAC1B,QAAQ,UAAQ,EAChB,eAAe,UAAQ,GACtB,oBAAoB,GAAG,IAAI,CA27B7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAwBA,OAAO,WAAW,MAAM,cAAc,CAAA;AAkBtC,MAAM,MAAM,uBAAuB,GAAG;IACpC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;8CAC0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;;;AAgSD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAA;IAC3C,kFAAkF;IAClF,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB,CAAA;AA0CD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,aAAa,cAA6B,EAC1C,kBAAkB,UAAQ,EAC1B,QAAQ,UAAQ,EAChB,eAAe,UAAQ,GACtB,oBAAoB,GAAG,IAAI,CAi8B7B"}
@@ -26,6 +26,15 @@ function intersects(a, b) {
26
26
  function fromChar(code) {
27
27
  return { kind: "ranges", ranges: [{ lo: code, hi: code }] };
28
28
  }
29
+ function startsFirstSet(combinator, input, pos) {
30
+ const fs2 = combinator._meta.firstSet;
31
+ if (fs2.kind === "any") return true;
32
+ if (fs2.kind === "empty") return false;
33
+ const code = input.codePointAt(pos);
34
+ if (code === void 0) return false;
35
+ for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
36
+ return false;
37
+ }
29
38
  function fromRange(lo, hi) {
30
39
  return { kind: "ranges", ranges: [{ lo, hi }] };
31
40
  }
@@ -985,6 +994,17 @@ function deriveExpected(c) {
985
994
  return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
986
995
  case "attempt":
987
996
  return deriveExpected(def.parser);
997
+ // Delegating wrappers whose first-token failure is their inner parser's — must
998
+ // pass through so a start-failure (and the first-set-miss fast-path in attempt/
999
+ // node, which reads this) names the real expected token, not the wrapper tag.
1000
+ case "field":
1001
+ case "withCtx":
1002
+ case "recover":
1003
+ return deriveExpected(def.parser);
1004
+ case "skip":
1005
+ return deriveExpected(def.main);
1006
+ case "expect":
1007
+ return def.expected;
988
1008
  case "node":
989
1009
  case "grammar":
990
1010
  case "trivia":
@@ -1045,7 +1065,7 @@ function choice(...args) {
1045
1065
  disjoint
1046
1066
  };
1047
1067
  const strategy = disjoint || hasGates ? null : detectStrategy(parsers);
1048
- const autoNot = !disjoint && !hasGates && strategy?.tag === "firstMatch" ? computeAutoNot(parsers) : parsers.map(() => null);
1068
+ const autoNot = !disjoint && !hasGates && (strategy?.tag === "firstMatch" || strategy?.tag === "sharedPrefix") ? computeAutoNot(parsers) : parsers.map(() => null);
1049
1069
  let greedyLitMap = null;
1050
1070
  let sortedParsers = null;
1051
1071
  const asciiDispatch = disjoint ? buildAsciiDispatch(parsers) : null;
@@ -1171,8 +1191,47 @@ function detectStrategy(parsers) {
1171
1191
  );
1172
1192
  return { tag: "literalsLongestFirst", sortedIndices };
1173
1193
  }
1194
+ const shared = detectSharedPrefix(parsers);
1195
+ if (shared !== null) return shared;
1174
1196
  return { tag: "firstMatch" };
1175
1197
  }
1198
+ function bareLeadingTermKey(term) {
1199
+ const d = term._def;
1200
+ if (d.tag === "literal" && !d.caseInsensitive) return `L:${d.value}`;
1201
+ if (d.tag === "regex") return `R:${d.source}\0${d.flags}`;
1202
+ return null;
1203
+ }
1204
+ function detectSharedPrefix(parsers) {
1205
+ if (parsers.length < 2) return null;
1206
+ let key = null;
1207
+ let prefix = null;
1208
+ const members = [];
1209
+ for (let i = 0; i < parsers.length; i++) {
1210
+ const term0 = leadingTermOfArm(parsers[i]);
1211
+ if (term0 === null) return null;
1212
+ const k = bareLeadingTermKey(term0);
1213
+ if (k === null) return null;
1214
+ if (key === null) {
1215
+ key = k;
1216
+ prefix = term0;
1217
+ } else if (k !== key) return null;
1218
+ members.push(i);
1219
+ }
1220
+ if (prefix === null || members.length < 2) return null;
1221
+ return { tag: "sharedPrefix", prefix, members };
1222
+ }
1223
+ function leadingTermOfArm(arm) {
1224
+ let d = arm._def;
1225
+ for (; ; ) {
1226
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
1227
+ d = d.parser._def;
1228
+ continue;
1229
+ }
1230
+ break;
1231
+ }
1232
+ if (d.tag !== "sequence" || d.parsers.length < 2) return null;
1233
+ return d.parsers[0];
1234
+ }
1176
1235
  function computeAutoNot(parsers) {
1177
1236
  return parsers.map((p, i) => {
1178
1237
  const litVal = getCoreLiteralValue(p);
@@ -1766,11 +1825,20 @@ function sequence(...parsers) {
1766
1825
 
1767
1826
  // src/combinators/attempt.ts
1768
1827
  function attempt(parser2) {
1828
+ const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
1829
+ let failExpected;
1769
1830
  return {
1770
1831
  _tag: "attempt",
1771
1832
  _meta: parser2._meta,
1772
1833
  _def: { tag: "attempt", parser: parser2 },
1773
1834
  parse(input, pos, ctx) {
1835
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(parser2, input, pos)) {
1836
+ if (failExpected === void 0) {
1837
+ const e = deriveExpected(parser2);
1838
+ failExpected = e.length > 0 ? e : [parser2._tag];
1839
+ }
1840
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
1841
+ }
1774
1842
  const mark = saveCstMark(ctx);
1775
1843
  const log = ctx._triviaLog?.length;
1776
1844
  const result = parser2.parse(input, pos, ctx);
@@ -1783,7 +1851,7 @@ function attempt(parser2) {
1783
1851
  }
1784
1852
 
1785
1853
  // src/combinators/repeat.ts
1786
- function repItem(combinator, input, cur, ctx) {
1854
+ function repItem(combinator, input, cur, ctx, guardable) {
1787
1855
  const mark = saveTriviaMark(ctx);
1788
1856
  let pos = cur;
1789
1857
  if (ctx.trivia) {
@@ -1799,6 +1867,10 @@ function repItem(combinator, input, cur, ctx) {
1799
1867
  rollbackTrivia(ctx, mark);
1800
1868
  return "stop";
1801
1869
  }
1870
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
1871
+ rollbackTrivia(ctx, mark);
1872
+ return "stop";
1873
+ }
1802
1874
  const result = combinator.parse(input, pos, ctx);
1803
1875
  if (!result.ok) {
1804
1876
  rollbackTrivia(ctx, mark);
@@ -1818,6 +1890,7 @@ function many(combinator) {
1818
1890
  };
1819
1891
  const def = { tag: "many", parser: combinator, min: 0 };
1820
1892
  let expected;
1893
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1821
1894
  return {
1822
1895
  _tag: "many",
1823
1896
  _meta: meta,
@@ -1826,7 +1899,7 @@ function many(combinator) {
1826
1899
  const values = def.valueUnused ? void 0 : [];
1827
1900
  let cur = pos;
1828
1901
  while (cur < input.length) {
1829
- const item = repItem(combinator, input, cur, ctx);
1902
+ const item = repItem(combinator, input, cur, ctx, guardable);
1830
1903
  if (item === "stop") break;
1831
1904
  if ("fail" in item) {
1832
1905
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1854,6 +1927,7 @@ function oneOrMore(combinator) {
1854
1927
  };
1855
1928
  const def = { tag: "oneOrMore", parser: combinator, min: 1 };
1856
1929
  let expected;
1930
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
1857
1931
  return {
1858
1932
  _tag: "oneOrMore",
1859
1933
  _meta: meta,
@@ -1864,7 +1938,7 @@ function oneOrMore(combinator) {
1864
1938
  const values = def.valueUnused ? void 0 : [first.value];
1865
1939
  let cur = first.span.end;
1866
1940
  while (cur < input.length) {
1867
- const item = repItem(combinator, input, cur, ctx);
1941
+ const item = repItem(combinator, input, cur, ctx, guardable);
1868
1942
  if (item === "stop") break;
1869
1943
  if ("fail" in item) {
1870
1944
  const sync = ctx._tolerant ? ctx._sync : void 0;
@@ -1907,15 +1981,6 @@ function optional(combinator) {
1907
1981
  }
1908
1982
  };
1909
1983
  }
1910
- function startsFirstSet(combinator, input, pos) {
1911
- const fs2 = combinator._meta.firstSet;
1912
- if (fs2.kind === "any") return true;
1913
- if (fs2.kind === "empty") return false;
1914
- const code = input.codePointAt(pos);
1915
- if (code === void 0) return false;
1916
- for (const r of fs2.ranges) if (code >= r.lo && code <= r.hi) return true;
1917
- return false;
1918
- }
1919
1984
  function sepBy(combinator, separator) {
1920
1985
  const meta = {
1921
1986
  firstSet: combinator._meta.firstSet,
@@ -2412,11 +2477,20 @@ function node(typeOrCombinator, combinatorOrBuild, buildOrOpts, maybeOpts) {
2412
2477
  const capturesTrivia = captureTrivia || trailingTrivia || (build ? buildReadsTrivia(def) : true);
2413
2478
  const clonesState = build ? buildReadsState(def) : true;
2414
2479
  const capturesFields = parserHasOwnFields(combinator) && (build ? buildReadsFields(def) : true);
2480
+ const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
2481
+ let failExpected;
2415
2482
  return {
2416
2483
  _tag: "node",
2417
2484
  _meta: meta,
2418
2485
  _def: def,
2419
2486
  parse(input, pos, ctx) {
2487
+ if (guardable && ctx._probe === void 0 && !ctx._tolerant && !startsFirstSet(combinator, input, pos)) {
2488
+ if (failExpected === void 0) {
2489
+ const e = deriveExpected(combinator);
2490
+ failExpected = e.length > 0 ? e : [combinator._tag];
2491
+ }
2492
+ return { ok: false, expected: failExpected, span: { start: pos, end: pos } };
2493
+ }
2420
2494
  const saved = beginCstNodeCapture(ctx);
2421
2495
  const savedFields = ctx._fields;
2422
2496
  ctx._fields = capturesFields ? [] : void 0;
@@ -4116,6 +4190,61 @@ function tryInlineDestructureTransform(src, valueVars) {
4116
4190
  }
4117
4191
 
4118
4192
  // src/compiler/codegen.ts
4193
+ function leadingFirstSetRecipe(p, seen = /* @__PURE__ */ new Set()) {
4194
+ if (seen.has(p)) return { concrete: empty(), refs: [] };
4195
+ seen.add(p);
4196
+ const d = p._def;
4197
+ const rec = (c) => leadingFirstSetRecipe(c, seen);
4198
+ const merge = (a, b) => ({ concrete: union(a.concrete, b.concrete), refs: [...a.refs, ...b.refs] });
4199
+ switch (d.tag) {
4200
+ case "lazy": {
4201
+ const name = p._ruleName;
4202
+ if (name !== void 0) return { concrete: empty(), refs: [name] };
4203
+ try {
4204
+ return rec(d.thunk());
4205
+ } catch {
4206
+ return { concrete: any(), refs: [] };
4207
+ }
4208
+ }
4209
+ case "literal":
4210
+ case "regex":
4211
+ case "keywords":
4212
+ return { concrete: p._meta.firstSet, refs: [] };
4213
+ case "choice": {
4214
+ let out = { concrete: empty(), refs: [] };
4215
+ for (const arm of d.parsers) out = merge(out, rec(arm));
4216
+ return out;
4217
+ }
4218
+ case "sequence": {
4219
+ let out = { concrete: empty(), refs: [] };
4220
+ for (const term of d.parsers) {
4221
+ out = merge(out, rec(term));
4222
+ if (!matchesEmpty(term)) return out;
4223
+ }
4224
+ return out;
4225
+ }
4226
+ case "oneOrMore":
4227
+ case "many":
4228
+ case "optional":
4229
+ case "transform":
4230
+ case "label":
4231
+ case "field":
4232
+ case "trivia":
4233
+ case "token":
4234
+ case "leaf":
4235
+ case "node":
4236
+ case "grammar":
4237
+ case "expect":
4238
+ return rec(d.parser);
4239
+ case "sepBy":
4240
+ return rec(d.parser);
4241
+ case "skip":
4242
+ return rec(d.main);
4243
+ // not / scanTo / guard / withCtx / recover / unknown → shallow set (safe).
4244
+ default:
4245
+ return { concrete: p._meta.firstSet, refs: [] };
4246
+ }
4247
+ }
4119
4248
  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 }";
4120
4249
  var _loweringSink = null;
4121
4250
  function beginLoweringCapture() {
@@ -4357,6 +4486,7 @@ var SWITCH_RANGE_LIMIT = 4;
4357
4486
  var SWITCH_MAX_CASES = 48;
4358
4487
  var SWITCH_MIN_CASES = 3;
4359
4488
  var _forceDisjointIf = false;
4489
+ var _forceNoSharedPrefix = false;
4360
4490
  function planDisjointDispatch(parsers) {
4361
4491
  const cases = [];
4362
4492
  let total = 0;
@@ -4992,7 +5122,7 @@ function emitLiteralsLongestFirst(def, sortedIndices, allExpected, ctx, pos, cov
4992
5122
  stmts.push(...emitElseFail(ctx, failArrBody(ctx, allExpected, pos)));
4993
5123
  return { stmts, valueVar: valV, endVar: endV };
4994
5124
  }
4995
- function emitFirstMatch(def, ctx, pos, coverageBase) {
5125
+ function emitFirstMatch(def, ctx, pos, coverageBase, preStmts, armGuardPrefix) {
4996
5126
  const resValV = v(ctx, "_crv");
4997
5127
  const resEndV = v(ctx, "_cre");
4998
5128
  const resOkV = v(ctx, "_crok");
@@ -5006,6 +5136,7 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5006
5136
  ...autoRejectedV === void 0 ? [] : [`${ind0}const ${autoRejectedV} = []`],
5007
5137
  `${ind0}const ${codeV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`
5008
5138
  ];
5139
+ if (preStmts) stmts.push(...preStmts);
5009
5140
  for (let i = 0; i < def.parsers.length; i++) {
5010
5141
  const p = def.parsers[i];
5011
5142
  const gate = def.gates[i];
@@ -5013,11 +5144,12 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5013
5144
  const atStart = failsAtStart(p);
5014
5145
  const staticFx = armStaticExpected(ctx, p);
5015
5146
  const deferRuleName = ctx.deferFirstSetRefs ? p._ruleName : void 0;
5016
- const fsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5147
+ const baseFsGuard = deferRuleName !== void 0 ? `/*@FS:${deferRuleName}:${codeV}@*/true` : needsFirstSetGuard(p) ? firstSetCond(codeV, p._meta.firstSet) : (() => {
5017
5148
  if (p._meta.firstSet.kind !== "any" || matchesEmpty(p)) return null;
5018
5149
  const deep = firstSetOf(p);
5019
5150
  return deep.kind === "ranges" ? firstSetCond(codeV, deep) : null;
5020
5151
  })();
5152
+ const fsGuard = armGuardPrefix === void 0 ? baseFsGuard : baseFsGuard === null ? armGuardPrefix : `${armGuardPrefix} && (${baseFsGuard})`;
5021
5153
  let gateCond = null;
5022
5154
  if (gate) {
5023
5155
  const gateIdx = ctx.mapFns.length;
@@ -5086,11 +5218,64 @@ function emitFirstMatch(def, ctx, pos, coverageBase) {
5086
5218
  stmts.push(...emitIfFail(ctx, `!${resOkV}`, failArrBody(ctx, concatExpr, pos, false)));
5087
5219
  return { stmts, valueVar: resValV, endVar: resEndV };
5088
5220
  }
5221
+ function emitsAsNamedFn(p, ctx) {
5222
+ if (p._def.tag === "lazy") return true;
5223
+ if (ctx.ruleNames?.has(p)) return true;
5224
+ if (ctx.namedParsers.has(p)) return true;
5225
+ const usage = ctx.lazyUsage;
5226
+ return !!usage && !ctx.noHoist && !ctx.capAsTrivia && isHoistableTag(p._def.tag) && (usage.counts.get(p) ?? 0) > 1 && (usage.sizes.get(p) ?? 0) >= HOIST_MIN_SUBTREE;
5227
+ }
5228
+ function armReplayInScope(arm, ctx) {
5229
+ let p = arm;
5230
+ for (; ; ) {
5231
+ if (emitsAsNamedFn(p, ctx)) return false;
5232
+ const d = p._def;
5233
+ if (d.tag === "node" || d.tag === "grammar" || d.tag === "transform" || d.tag === "label") {
5234
+ p = d.parser;
5235
+ continue;
5236
+ }
5237
+ return d.tag === "sequence";
5238
+ }
5239
+ }
5240
+ function emitSharedPrefix(def, strategy, ctx, pos) {
5241
+ const { prefix, members } = strategy;
5242
+ const savedCapturing = ctx.capturing;
5243
+ ctx.capturing = false;
5244
+ const pfx = emitFallible(prefix, ctx, pos, false);
5245
+ ctx.capturing = savedCapturing;
5246
+ const map = ctx.replayPrefix ??= /* @__PURE__ */ new Map();
5247
+ const keys = [];
5248
+ for (const i of members) {
5249
+ const term0 = leadingTermOfArm(def.parsers[i]);
5250
+ if (term0 === null) continue;
5251
+ map.set(term0, { valVar: pfx.valVar, endVar: pfx.endVar });
5252
+ keys.push(term0);
5253
+ }
5254
+ const r = emitFirstMatch(def, ctx, pos, void 0, pfx.stmts, pfx.okVar);
5255
+ for (const k of keys) map.delete(k);
5256
+ return r;
5257
+ }
5258
+ function emitReplayPrefixLeaf(ctx, pos, replay) {
5259
+ const vv = v(ctx);
5260
+ return {
5261
+ stmts: [
5262
+ `${ind(ctx)}const ${vv} = ${replay.valVar}`,
5263
+ ...emitLeafCapture(ctx, vv, pos, replay.endVar)
5264
+ ],
5265
+ valueVar: vv,
5266
+ endVar: replay.endVar
5267
+ };
5268
+ }
5089
5269
  function emitNonDisjoint(def, strategy, allExpected, ctx, pos, coverageBase) {
5090
5270
  if (strategy.tag === "greedyClassify")
5091
5271
  return emitGreedyClassify(def, strategy.superIndex, ctx, pos, coverageBase);
5092
5272
  if (strategy.tag === "literalsLongestFirst")
5093
5273
  return emitLiteralsLongestFirst(def, strategy.sortedIndices, allExpected, ctx, pos, coverageBase);
5274
+ if (strategy.tag === "sharedPrefix") {
5275
+ if (!_forceNoSharedPrefix && coverageBase === void 0 && !ctx.recovery && strategy.members.every((i) => armReplayInScope(def.parsers[i], ctx)))
5276
+ return emitSharedPrefix(def, strategy, ctx, pos);
5277
+ return emitFirstMatch(def, ctx, pos, coverageBase);
5278
+ }
5094
5279
  return emitFirstMatch(def, ctx, pos, coverageBase);
5095
5280
  }
5096
5281
  function pushMapFn(ctx, fn, src) {
@@ -5170,6 +5355,13 @@ function emitMany(def, ctx, pos) {
5170
5355
  itemPos = npV;
5171
5356
  }
5172
5357
  }
5358
+ if (!ctx.recovery && !ctx.coverage && !failsAtStart(def.parser) && needsFirstSetGuard(def.parser)) {
5359
+ const lcV = v(ctx, "_lc");
5360
+ stmts.push(
5361
+ `${ind(ctx)}const ${lcV} = ${itemPos} < input.length ? (input.codePointAt(${itemPos}) ?? -1) : -1`,
5362
+ `${ind(ctx)}if (!(${firstSetCond(lcV, def.parser._meta.firstSet)})) { ${rollback}break }`
5363
+ );
5364
+ }
5173
5365
  const { stmts: iterStmts, okVar: iterOk, valVar: iterVal, endVar: iterEnd } = emitFallible(def.parser, ctx, itemPos, true);
5174
5366
  stmts.push(...iterStmts);
5175
5367
  if (ctx.recovery) {
@@ -5220,8 +5412,19 @@ function emitAttempt(p, def, ctx, pos) {
5220
5412
  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}`;
5221
5413
  const traceId = ctx.coverage?.plan.attempts.get(p);
5222
5414
  const traceRollback = traceId === void 0 ? "" : ` _ctx._grammarTrace?.write({ id: ${JSON.stringify(traceId)}, phase: 'rollback', offset: ${pos} });`;
5415
+ const preGuard = [];
5416
+ if (!ctx.recovery && !ctx.coverage && needsFirstSetGuard(def.parser)) {
5417
+ const gcV = v(ctx, "_agc");
5418
+ const gExp = armStaticExpected(ctx, def.parser);
5419
+ const gFail = ctx.failLabel ? `{ _ctx._fe = ${pos};${ctx.recordFail ? ` _ctx._fx = ${gExp};` : ""} break ${ctx.failLabel} }` : `{ _ctx._fe = ${pos}; ${failReturnArr(gExp, pos)} }`;
5420
+ preGuard.push(
5421
+ `${ind(ctx)}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5422
+ `${ind(ctx)}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5423
+ );
5424
+ }
5223
5425
  return {
5224
5426
  stmts: [
5427
+ ...preGuard,
5225
5428
  `${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`,
5226
5429
  ...inner.stmts,
5227
5430
  ...emitIfFail(ctx, `!${inner.okVar}`, `{ ${rollback};${traceRollback} _ctx._fe = ${pos}; ${propagateFailBody(ctx)} }`)
@@ -5601,7 +5804,18 @@ ${i}const ${chV} = ${profileRecognizer} ? undefined : [], ${rawV} = ${profileRec
5601
5804
  const saveTrivia = needsTriviaFrame ? `, ${st} = _ctx.captureTrivia, ${stl} = _ctx._cstTriviaLog${smk ? `, ${smk} = _ctx._triviaCaptureMask` : ""}` : "";
5602
5805
  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}` : ""}` : "";
5603
5806
  const restoreTrivia = needsTriviaFrame ? `; _ctx.captureTrivia = ${st}; _ctx._cstTriviaLog = ${stl}${smk ? `; _ctx._triviaCaptureMask = ${smk}` : ""}` : "";
5807
+ const preGuard = [];
5808
+ if (!ctx.recovery && !ctx.coverage && (capturesChildren || structural) && needsFirstSetGuard(def.parser)) {
5809
+ const gcV = v(ctx, "_ngc");
5810
+ const gExp = armStaticExpected(ctx, def.parser);
5811
+ const gFail = ctx.failLabel ? ctx.recordFail ? `{ _ctx._fe = ${pos}; _ctx._fx = ${gExp}; break ${ctx.failLabel} }` : `break ${ctx.failLabel}` : failReturnArr(gExp, pos);
5812
+ preGuard.push(
5813
+ `${i}const ${gcV} = ${pos} < input.length ? (input.codePointAt(${pos}) ?? -1) : -1`,
5814
+ `${i}if (!(${firstSetCond(gcV, def.parser._meta.firstSet)})) ${gFail}`
5815
+ );
5816
+ }
5604
5817
  const stmts = [
5818
+ ...preGuard,
5605
5819
  profHoist,
5606
5820
  allocStmt,
5607
5821
  `${i}const ${sc} = _ctx._cstChildren, ${sl} = _ctx._cstLeaves, ${sr} = _ctx._cstRawChildren${saveTrivia}${sf ? `, ${sf} = _ctx._fields` : ""}`,
@@ -5775,6 +5989,8 @@ function emitExpect(def, ctx, pos) {
5775
5989
  return { stmts, valueVar: valVar, endVar };
5776
5990
  }
5777
5991
  function emit(p, ctx, pos) {
5992
+ const replay = ctx.replayPrefix?.get(p);
5993
+ if (replay !== void 0) return emitReplayPrefixLeaf(ctx, pos, replay);
5778
5994
  const dispatch = (emissionPos = pos) => {
5779
5995
  const savedRule = ctx.activeCoverageRuleId;
5780
5996
  const rule = ctx.coverage?.plan.rules.get(p);
@@ -6628,6 +6844,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6628
6844
  ].join("\n")));
6629
6845
  }
6630
6846
  const firstSets = /* @__PURE__ */ new Map();
6847
+ const firstSetRecipes = /* @__PURE__ */ new Map();
6631
6848
  for (const [key, rule] of ruleMap) {
6632
6849
  let resolved = rule;
6633
6850
  const d = rule._def;
@@ -6639,6 +6856,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6639
6856
  }
6640
6857
  }
6641
6858
  firstSets.set(key, canMatchEmptyAtStart(resolved) ? { kind: "any" } : resolved._meta.firstSet);
6859
+ firstSetRecipes.set(key, canMatchEmptyAtStart(resolved) ? { concrete: any(), refs: [] } : leadingFirstSetRecipe(resolved));
6642
6860
  }
6643
6861
  return {
6644
6862
  ns,
@@ -6647,6 +6865,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
6647
6865
  ruleFns,
6648
6866
  wrappers,
6649
6867
  firstSets,
6868
+ firstSetRecipes,
6650
6869
  deps: ruleDependencies(ruleMap),
6651
6870
  needsEmptyTl: !!ctx.needsEmptyTl,
6652
6871
  needsHostReads: !!ctx.needsHostReads,
@@ -7293,10 +7512,30 @@ function fusedBody(pieces) {
7293
7512
  ];
7294
7513
  const wrapperEntries = [...winner].map(([k, p]) => `${JSON.stringify(k)}: ${p.wrappers.get(k)}`);
7295
7514
  const rawBody = [...lines, "return {", wrapperEntries.join(",\n"), "}"].join("\n");
7296
- const finalFS = /* @__PURE__ */ new Map();
7515
+ const ANY2 = { kind: "any" };
7516
+ const recipes = /* @__PURE__ */ new Map();
7517
+ const shallow = /* @__PURE__ */ new Map();
7297
7518
  for (const [k, p] of winner) {
7519
+ const r = p.firstSetRecipes?.get(k);
7520
+ if (r) recipes.set(k, r);
7298
7521
  const fs2 = p.firstSets?.get(k);
7299
- if (fs2) finalFS.set(k, fs2);
7522
+ if (fs2) shallow.set(k, fs2);
7523
+ }
7524
+ const finalFS = /* @__PURE__ */ new Map();
7525
+ for (const [k] of winner) finalFS.set(k, recipes.get(k)?.concrete ?? shallow.get(k) ?? ANY2);
7526
+ for (let iter = 0; iter <= recipes.size; iter++) {
7527
+ let changed = false;
7528
+ for (const [k, r] of recipes) {
7529
+ let fs2 = r.concrete;
7530
+ for (const ref2 of r.refs) {
7531
+ fs2 = union(fs2, recipes.has(ref2) || shallow.has(ref2) ? finalFS.get(ref2) ?? ANY2 : ANY2);
7532
+ }
7533
+ if (JSON.stringify(finalFS.get(k)) !== JSON.stringify(fs2)) {
7534
+ finalFS.set(k, fs2);
7535
+ changed = true;
7536
+ }
7537
+ }
7538
+ if (!changed) break;
7300
7539
  }
7301
7540
  const body = rawBody.replace(
7302
7541
  // Rule name + code-point var are both JS identifiers (rule names are validated
@@ -8467,7 +8706,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
8467
8706
  const mapLit = (m, stripVals = false) => `new Map([${[...m].map(
8468
8707
  ([k, v2]) => `[${JSON.stringify(k)}, ${JSON.stringify(stripVals && typeof v2 === "string" ? stripIndent(v2) : v2)}]`
8469
8708
  ).join(", ")}])`;
8470
- 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: [] }`;
8709
+ 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: [] }`;
8471
8710
  };
8472
8711
  const isSpread = (it) => "__spreadLocal" in it;
8473
8712
  const isIR = (it) => "ir" in it && !("ruleFns" in it);