parseman 0.61.5 → 0.61.7

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.
@@ -36,6 +36,21 @@ export type EntrySupercompileResult = EmitResult & {
36
36
  * externally-addressable scan roots passed to the canonical emitter.
37
37
  */
38
38
  export declare function supercompileEntryAssembly(prog: TableProgram, cfg: EntrySupercompileCfg, extraIps?: readonly number[]): EntrySupercompileResult;
39
+ /**
40
+ * Functions that read the same are one function: `optional(x)` written in 25 places
41
+ * is 25 rows and one body, and a trivia skip can be pooled under two labels that
42
+ * lower alike. This runs after the join, so a site with one caller has
43
+ * already gone into it, the selection is untouched and every call a parse makes is
44
+ * still made. Each pass can make the callers of a merged function read alike, so it
45
+ * repeats until nothing merges.
46
+ *
47
+ * Only identifiers are compared or renamed: a grammar's own text reaches this source
48
+ * as strings and regexes, and they count as written. A temporary is local by the
49
+ * emitter's contract and is numbered per assembly, so one this function binds is
50
+ * compared in order of use. A site name that's also bound somewhere, or abbreviated
51
+ * in `{_pf1}`, can't be renamed by name, so that source stays as it is.
52
+ */
53
+ export declare function mergeSameBodies(source: string): string;
39
54
  /**
40
55
  * Plugin-only final splice for an ordinary macro replacement. The table
41
56
  * compiler emits its canonical callback-free public artifact first; this pass
@@ -1 +1 @@
1
- {"version":3,"file":"entry-supercompiler.d.ts","sourceRoot":"","sources":["../../src/plugin/entry-supercompiler.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAE/E,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAKvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAA;IACpE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACpD,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG;IAAE,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAAA;CAAE,CAAA;AAyKpG;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,oBAAoB,EACzB,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,uBAAuB,CAGzB;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,MAAM,CA0CR;AAED,8EAA8E;AAC9E,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,oBAAoB,EAC1B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,UAAU,GAClB,uBAAuB,CAwRzB"}
1
+ {"version":3,"file":"entry-supercompiler.d.ts","sourceRoot":"","sources":["../../src/plugin/entry-supercompiler.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAE/E,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAKvF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IACpC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAA;IACpE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACvF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACpD,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG;IAAE,QAAQ,CAAC,YAAY,EAAE,sBAAsB,CAAA;CAAE,CAAA;AA2KpG;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,oBAAoB,EACzB,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,uBAAuB,CAGzB;AAgCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA6DtD;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,MAAM,CA0CR;AAED,8EAA8E;AAC9E,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,oBAAoB,EAC1B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,UAAU,GAClB,uBAAuB,CAwRzB"}
@@ -206,8 +206,9 @@ function matchesEmptyBody(p, seen, resolve3) {
206
206
  case "optional":
207
207
  case "not":
208
208
  case "peek":
209
+ case "expect":
209
210
  return true;
210
- // zero repetitions / absent / lookahead
211
+ // zero repetitions / absent / lookahead / recovered
211
212
  // Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
212
213
  // Any `min >= 1` requires that many ITEMS, so it is nullable only when the
213
214
  // item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
@@ -228,7 +229,6 @@ function matchesEmptyBody(p, seen, resolve3) {
228
229
  case "trivia":
229
230
  case "token":
230
231
  case "leaf":
231
- case "expect":
232
232
  case "withCtx":
233
233
  case "node":
234
234
  case "grammar":
@@ -298,17 +298,17 @@ function sequenceSet(parsers, fs3, empties, trivia2, consuming = true) {
298
298
  function sequenceFirstSet(parsers) {
299
299
  return sequenceSet(parsers, (p) => p._meta.firstSet, matchesEmpty, void 0, false);
300
300
  }
301
- function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve3, activeTrivia, consuming = true) {
301
+ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve3, activeTrivia, consuming = true, follow = false) {
302
302
  if (seen.has(p)) return any();
303
303
  seen.add(p);
304
304
  try {
305
- return firstSetBody(p, seen, resolve3, activeTrivia, consuming);
305
+ return firstSetBody(p, seen, resolve3, activeTrivia, consuming, follow);
306
306
  } finally {
307
307
  seen.delete(p);
308
308
  }
309
309
  }
310
- function firstSetBody(p, seen, resolve3, activeTrivia, consuming) {
311
- const fs3 = (c) => firstSetOf(c, seen, resolve3, activeTrivia, consuming);
310
+ function firstSetBody(p, seen, resolve3, activeTrivia, consuming, follow) {
311
+ const fs3 = (c) => firstSetOf(c, seen, resolve3, activeTrivia, consuming, follow);
312
312
  const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve3);
313
313
  const d = p._def;
314
314
  switch (d.tag) {
@@ -329,7 +329,7 @@ function firstSetBody(p, seen, resolve3, activeTrivia, consuming) {
329
329
  }
330
330
  }
331
331
  const through = activeTrivia === null && resolve3 !== void 0;
332
- return firstSetOf(target, seen, resolve3, through ? triviaThroughRef(p, target, null) : activeTrivia, consuming);
332
+ return firstSetOf(target, seen, resolve3, through ? triviaThroughRef(p, target, null) : activeTrivia, consuming, follow);
333
333
  }
334
334
  case "choice": {
335
335
  let out = empty();
@@ -339,6 +339,10 @@ function firstSetBody(p, seen, resolve3, activeTrivia, consuming) {
339
339
  case "sequence": {
340
340
  return sequenceSet(d.parsers, fs3, empties, activeTrivia, consuming);
341
341
  }
342
+ // Runs on any character: on one its child can't start, it reports the error. A
343
+ // resync sentinel still stops where the child could start.
344
+ case "expect":
345
+ return follow ? fs3(d.parser) : any();
342
346
  case "peek": {
343
347
  const inner = d.parser;
344
348
  return empties(inner) ? any() : fs3(inner);
@@ -352,7 +356,6 @@ function firstSetBody(p, seen, resolve3, activeTrivia, consuming) {
352
356
  case "leaf":
353
357
  case "node":
354
358
  case "sepBy":
355
- case "expect":
356
359
  // A transaction accepts exactly what its inner accepts. Its own `_meta` is
357
360
  // the inner's construction-time set, which reads `any` when the inner opens
358
361
  // with a rule reference — resolve through it like any other wrapper.
@@ -360,14 +363,15 @@ function firstSetBody(p, seen, resolve3, activeTrivia, consuming) {
360
363
  return fs3(d.parser);
361
364
  // A token runs its body with trivia cleared (`token.ts`).
362
365
  case "token":
363
- return firstSetOf(d.parser, seen, resolve3, activeTrivia === void 0 ? void 0 : null, consuming);
366
+ return firstSetOf(d.parser, seen, resolve3, activeTrivia === void 0 ? void 0 : null, consuming, follow);
364
367
  case "grammar":
365
368
  return firstSetOf(
366
369
  d.parser,
367
370
  seen,
368
371
  resolve3,
369
372
  d.clearTrivia ? null : d.triviaParser ?? activeTrivia,
370
- consuming
373
+ consuming,
374
+ follow
371
375
  );
372
376
  // A run starts with its first line's prefix; a nullable prefix starts with anything.
373
377
  case "lines": {
@@ -3650,7 +3654,7 @@ function sequence(...parsers) {
3650
3654
  if (tolerant) {
3651
3655
  followSentinels ??= parsers.map((_, i) => firstSetSentinel(
3652
3656
  parsers.slice(i + 1).reduce(
3653
- (set, parser2) => union(set, firstSetOf(parser2, /* @__PURE__ */ new Set(), void 0, void 0, false)),
3657
+ (set, parser2) => union(set, firstSetOf(parser2, /* @__PURE__ */ new Set(), void 0, void 0, false, true)),
3654
3658
  { kind: "empty" }
3655
3659
  )
3656
3660
  ));
@@ -3800,7 +3804,8 @@ function derive(c, seen) {
3800
3804
  function expect(combinator, label2) {
3801
3805
  const expected = label2 !== void 0 ? [label2] : deriveExpected(combinator);
3802
3806
  const meta = {
3803
- firstSet: combinator._meta.firstSet,
3807
+ // It runs on any character: on one its child can't start, it reports the error.
3808
+ firstSet: any(),
3804
3809
  canMatchNewline: combinator._meta.canMatchNewline,
3805
3810
  isTrivia: false
3806
3811
  };
@@ -5630,7 +5635,7 @@ function sourceLeaf(root, fn) {
5630
5635
  }
5631
5636
 
5632
5637
  // src/version.ts
5633
- var PARSEMAN_VERSION = "0.61.5";
5638
+ var PARSEMAN_VERSION = "0.61.7";
5634
5639
 
5635
5640
  // src/table/lex-body.ts
5636
5641
  function buildLexBodyRecognizer(spec, constants) {
@@ -8562,7 +8567,7 @@ var Encoder = class {
8562
8567
  let fs3 = { kind: "empty" };
8563
8568
  const rr = this.refResolver();
8564
8569
  for (let j = i + 1; j < parsers.length; j++) {
8565
- fs3 = union(fs3, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr, void 0, false));
8570
+ fs3 = union(fs3, firstSetOf(parsers[j], /* @__PURE__ */ new Set(), rr, void 0, false, true));
8566
8571
  }
8567
8572
  return this.charClass(fs3);
8568
8573
  }
@@ -11057,17 +11062,17 @@ var WRAPPERS = /* @__PURE__ */ new Set([
11057
11062
  OP_SCOPE_PLAIN,
11058
11063
  OP_FIELD,
11059
11064
  OP_LABEL,
11060
- OP_EXPECT,
11061
11065
  OP_WITHCTX,
11062
11066
  OP_RULE,
11063
11067
  OP_ATTEMPT,
11064
11068
  OP_COV,
11065
11069
  OP_GAPGATE
11066
11070
  ]);
11067
- function rowNullable(code, k, ip, memo) {
11068
- const hit = memo.get(ip);
11071
+ function rowNullable(code, k, ip, memo, recovers = true) {
11072
+ const key = recovers ? ip : ~ip;
11073
+ const hit = memo.get(key);
11069
11074
  if (hit !== void 0) return hit;
11070
- memo.set(ip, true);
11075
+ memo.set(key, true);
11071
11076
  const op = code[ip];
11072
11077
  const kids = [];
11073
11078
  let out = true;
@@ -11078,20 +11083,20 @@ function rowNullable(code, k, ip, memo) {
11078
11083
  out = !(re instanceof RegExp) || regexCanMatchEmpty(re.source);
11079
11084
  } else if (op === OP_SEQ || op === OP_SEQV || op === OP_SEQX) {
11080
11085
  childSlots(code, ip, kids);
11081
- out = kids.every((c) => rowNullable(code, k, c, memo));
11086
+ out = kids.every((c) => rowNullable(code, k, c, memo, recovers));
11082
11087
  } else if (op === OP_CHOICE) {
11083
11088
  childSlots(code, ip, kids);
11084
- out = kids.length === 0 || kids.some((c) => rowNullable(code, k, c, memo));
11089
+ out = kids.length === 0 || kids.some((c) => rowNullable(code, k, c, memo, recovers));
11085
11090
  } else if (op === OP_REP || op === OP_REPV) {
11086
- out = code[ip + 2] === 0 || rowNullable(code, k, code[ip + 1], memo);
11091
+ out = code[ip + 2] === 0 || rowNullable(code, k, code[ip + 1], memo, recovers);
11087
11092
  } else if (op === OP_SEED) {
11088
- out = rowNullable(code, k, code[ip + 1], memo);
11093
+ out = rowNullable(code, k, code[ip + 1], memo, recovers);
11089
11094
  } else if (op === OP_CONT) {
11090
- out = rowNullable(code, k, code[ip + 1], memo) && rowNullable(code, k, code[ip + 3], memo);
11091
- } else if (WRAPPERS.has(op)) {
11092
- if (childSlots(code, ip, kids) && kids.length === 1) out = rowNullable(code, k, kids[0], memo);
11095
+ out = rowNullable(code, k, code[ip + 1], memo, recovers) && rowNullable(code, k, code[ip + 3], memo, recovers);
11096
+ } else if (WRAPPERS.has(op) || op === OP_EXPECT && !recovers) {
11097
+ if (childSlots(code, ip, kids) && kids.length === 1) out = rowNullable(code, k, kids[0], memo, recovers);
11093
11098
  }
11094
- memo.set(ip, out);
11099
+ memo.set(key, out);
11095
11100
  return out;
11096
11101
  }
11097
11102
  function holdsGap(code, k, base, n, i, memo) {
@@ -11100,7 +11105,7 @@ function holdsGap(code, k, base, n, i, memo) {
11100
11105
  if (code[term] !== OP_NOT && code[term] !== OP_PEEK) return false;
11101
11106
  const next = code[base + i + 1];
11102
11107
  if (code[next] === OP_ADJ || leadingTriviaFieldAt(code, k, next)) return false;
11103
- return !rowNullable(code, k, next, memo);
11108
+ return !rowNullable(code, k, next, memo, false);
11104
11109
  }
11105
11110
 
11106
11111
  // src/table/gap-gate.ts
@@ -11422,11 +11427,35 @@ ${fast}
11422
11427
  ${scanned}
11423
11428
  }`;
11424
11429
  }
11425
- function emitHoldTerm(callee, dst, t, l, skip) {
11430
+ var TRIVIA_LOG_LENGTH = "ctx._cstBuf!==undefined?(ctx._cstBuf.tl!==undefined?ctx._cstBuf.tl.length:0):(ctx._cstTriviaLog!==undefined?ctx._cstTriviaLog.length:0)";
11431
+ function emitHoldTerm(callee, dst, t, l, skip, recovers) {
11426
11432
  const fast = `const ${t}v=${callee}(input,cur,ctx)
11427
11433
  if(${t}v===FAIL)return FAIL
11428
11434
  ${dst}=${t}v
11429
11435
  cur=EC.e`;
11436
+ if (recovers) {
11437
+ const marked = `let ${t}c=cur,${t}s=cur,${t}tl=0,${t}stl=0,${t}lg=0,${t}slg=0,${t}rt=0,${t}srt=0`;
11438
+ if (l.tri === TRI_NONE) return `${marked}
11439
+ ${fast}`;
11440
+ const held2 = `${t}tl=${TRIVIA_LOG_LENGTH}
11441
+ ${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
11442
+ ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0
11443
+ ${t}s=${skip}(input,cur,ctx)
11444
+ ${t}stl=${TRIVIA_LOG_LENGTH}
11445
+ ${t}slg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
11446
+ ${t}srt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0
11447
+ const ${t}v=${callee}(input,${t}s,ctx)
11448
+ if(${t}v===FAIL)return FAIL
11449
+ ${dst}=${t}v
11450
+ cur=${t}s`;
11451
+ return l.tri !== TRI_UNKNOWN ? `${marked}
11452
+ ${held2}` : `${marked}
11453
+ if(ctx.trivia===undefined){
11454
+ ${fast}
11455
+ }else{
11456
+ ${held2}
11457
+ }`;
11458
+ }
11430
11459
  if (l.tri === TRI_NONE) return fast;
11431
11460
  const held = `const ${t}s=${skip}(input,cur,ctx)
11432
11461
  const ${t}v=${callee}(input,${t}s,ctx)
@@ -11440,6 +11469,9 @@ ${fast}
11440
11469
  ${held}
11441
11470
  }`;
11442
11471
  }
11472
+ function emitHeldGapUndo(t) {
11473
+ return `if(cur===${t}s&&${t}s!==${t}c){rollbackScannedTriviaAt(ctx,${t}tl,${t}stl,${t}lg,${t}slg,${t}rt,${t}srt);cur=${t}c}`;
11474
+ }
11443
11475
  function leadSkip(hasTrivia, leadTrivia, skip) {
11444
11476
  if (hasTrivia === "false") return "{}";
11445
11477
  const call = `itemStart=${skip}(input,itemStart,ctx)`;
@@ -12448,6 +12480,7 @@ return v
12448
12480
  }
12449
12481
  parts.push("let cur=EC.e");
12450
12482
  const names = ["v0"];
12483
+ let heldUndo;
12451
12484
  for (let i = 1; i < n; i++) {
12452
12485
  const vn = `v${i}`;
12453
12486
  parts.push(`let ${vn}`);
@@ -12464,7 +12497,16 @@ return v
12464
12497
  );
12465
12498
  } else {
12466
12499
  const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]})` : kids[i];
12467
- parts.push(holdsGap(code, k, base, n, i, nullable2) ? emitHoldTerm(callee, vn, tmp(), L, skipFor(L)) : emitTerm(callee, vn, tmp(), L, skipFor(L), L.tri >= 0 ? triviaMiss(L.tri) : void 0));
12500
+ if (holdsGap(code, k, base, n, i, nullable2)) {
12501
+ const t2 = tmp();
12502
+ const recovers = rowNullable(code, k, code[base + i + 1], nullable2);
12503
+ parts.push(emitHoldTerm(callee, vn, t2, L, skipFor(L), recovers));
12504
+ heldUndo = recovers ? t2 : void 0;
12505
+ } else {
12506
+ parts.push(emitTerm(callee, vn, tmp(), L, skipFor(L), L.tri >= 0 ? triviaMiss(L.tri) : void 0));
12507
+ if (heldUndo !== void 0) parts.push(emitHeldGapUndo(heldUndo));
12508
+ heldUndo = void 0;
12509
+ }
12468
12510
  }
12469
12511
  names.push(vn);
12470
12512
  }
@@ -15965,8 +16007,46 @@ function assemble(t, prog, cfg) {
15965
16007
  }
15966
16008
  if (hasAdj) {
15967
16009
  const first2 = linkFirst();
16010
+ let heldFrom = 0, heldTl = 0, heldScanTl = 0, heldLog = 0, heldScanLog = 0, heldRoot = 0, heldScanRoot = 0;
15968
16011
  const runnerAt = (i) => {
15969
16012
  const kidIp = code[base + i];
16013
+ if (i > 1 && holdsGap(code, k, base, n, i - 1, NULLABLE) && rowNullable(code, k, kidIp, NULLABLE)) {
16014
+ const kid = link(kidIp);
16015
+ return (input, cur, ctx) => {
16016
+ const from = heldFrom, tl = heldTl, scanTl = heldScanTl, log = heldLog;
16017
+ const scanLog = heldScanLog, root = heldRoot, scanRoot = heldScanRoot;
16018
+ const next = step(kid, input, cur, ctx);
16019
+ if (next !== cur || from === cur) return next;
16020
+ rollbackScannedTriviaAt(ctx, tl, scanTl, log, scanLog, root, scanRoot);
16021
+ return from;
16022
+ };
16023
+ }
16024
+ if (holdsGap(code, k, base, n, i, NULLABLE) && rowNullable(code, k, code[base + i + 1], NULLABLE)) {
16025
+ const kid = link(kidIp);
16026
+ return (input, cur, ctx) => {
16027
+ if (ctx.trivia === void 0) {
16028
+ const v2 = kid(input, cur, ctx);
16029
+ if (v2 === FAIL) return -1;
16030
+ TERMV = v2;
16031
+ heldFrom = cur;
16032
+ return EC.e;
16033
+ }
16034
+ const tl = cstTlLen(ctx), log = ctx._triviaLog?.length ?? 0, root = ctx._rootTriviaLog?.length ?? 0;
16035
+ const scanEnd = skipTrivia(input, cur, ctx);
16036
+ const scanTl = cstTlLen(ctx), scanLog = ctx._triviaLog?.length ?? 0, scanRoot = ctx._rootTriviaLog?.length ?? 0;
16037
+ const v = kid(input, scanEnd, ctx);
16038
+ if (v === FAIL) return -1;
16039
+ TERMV = v;
16040
+ heldFrom = cur;
16041
+ heldTl = tl;
16042
+ heldScanTl = scanTl;
16043
+ heldLog = log;
16044
+ heldScanLog = scanLog;
16045
+ heldRoot = root;
16046
+ heldScanRoot = scanRoot;
16047
+ return scanEnd;
16048
+ };
16049
+ }
15970
16050
  if (holdsGap(code, k, base, n, i, NULLABLE)) {
15971
16051
  const kid = link(kidIp);
15972
16052
  return (input, cur, ctx) => {
@@ -19464,21 +19544,70 @@ function runDuplicationDiagnosticRules(ruleMap, opt) {
19464
19544
  var LOOP = 8;
19465
19545
  var ROUNDS = 60;
19466
19546
  var CAP = 1e12;
19547
+ var CROSSING_WEIGHT = 4;
19548
+ var TAKEN = -1;
19467
19549
  function selectHotSites(code, entries, siteBytes, budget, profile) {
19468
19550
  const selected = /* @__PURE__ */ new Set();
19469
19551
  if (budget <= 0) return selected;
19470
- const weight = profile === void 0 ? staticWeights(code, entries) : void 0;
19471
- const w = (ip) => (weight !== void 0 ? weight.get(ip) : profile[ip]) ?? 0;
19472
- const ranked = [...siteBytes.keys()].sort((a, b) => w(b) / siteBytes.get(b) - w(a) / siteBytes.get(a) || a - b);
19473
19552
  let spent = 0;
19474
- for (const ip of ranked) {
19475
- if (w(ip) === 0) break;
19553
+ for (const ip of profile === void 0 ? staticRanking(code, entries, siteBytes) : connectedRanking(code, siteBytes, profile)) {
19476
19554
  spent += siteBytes.get(ip);
19477
19555
  if (spent > budget) break;
19478
19556
  selected.add(ip);
19479
19557
  }
19480
19558
  return selected;
19481
19559
  }
19560
+ function edgesAsLowered(code, siteBytes, ip, out) {
19561
+ out.length = 0;
19562
+ if (code[ip] !== OP_CONT) childSlots(code, ip, out);
19563
+ else if (siteBytes.has(ip)) out.push(code[ip + 1], code[ip + 3]);
19564
+ else out.push(code[ip + 2]);
19565
+ for (let i = 0; i < out.length; i++) {
19566
+ let row = out[i];
19567
+ for (; ; ) {
19568
+ const op = code[row];
19569
+ if (op === OP_GAPGATE || op === OP_SEED) row = code[row + 1];
19570
+ else if (op === OP_CONT && !siteBytes.has(row)) row = code[row + 2];
19571
+ else break;
19572
+ }
19573
+ out[i] = row;
19574
+ }
19575
+ }
19576
+ function staticRanking(code, entries, siteBytes) {
19577
+ const weight = staticWeights(code, entries, siteBytes);
19578
+ const w = (ip) => weight.get(ip) ?? 0;
19579
+ return [...siteBytes.keys()].filter((ip) => w(ip) > 0).sort((a, b) => w(b) / siteBytes.get(b) - w(a) / siteBytes.get(a) || a - b);
19580
+ }
19581
+ function* connectedRanking(code, siteBytes, calls) {
19582
+ const ran = [...siteBytes.keys()].filter((ip) => calls[ip] > 0).sort((a, b) => a - b);
19583
+ const slot = new Map(ran.map((ip, i) => [ip, i]));
19584
+ const bytes = ran.map((ip) => siteBytes.get(ip));
19585
+ const worth = ran.map((ip) => calls[ip]);
19586
+ const score = worth.map((value, i) => value / bytes[i]);
19587
+ const crossings = ran.map(() => []);
19588
+ const kids = [];
19589
+ ran.forEach((ip, from) => {
19590
+ edgesAsLowered(code, siteBytes, ip, kids);
19591
+ for (const callee of kids) {
19592
+ const to = slot.get(callee);
19593
+ if (to === void 0) continue;
19594
+ const gain = CROSSING_WEIGHT * calls[callee];
19595
+ crossings[from].push([to, gain]);
19596
+ crossings[to].push([from, gain]);
19597
+ }
19598
+ });
19599
+ for (let left = ran.length; left > 0; left--) {
19600
+ let best = 0;
19601
+ for (let i = 1; i < score.length; i++) if (score[i] > score[best]) best = i;
19602
+ score[best] = TAKEN;
19603
+ yield ran[best];
19604
+ for (const [neighbour, gain] of crossings[best]) {
19605
+ if (score[neighbour] === TAKEN) continue;
19606
+ worth[neighbour] = worth[neighbour] + gain;
19607
+ score[neighbour] = worth[neighbour] / bytes[neighbour];
19608
+ }
19609
+ }
19610
+ }
19482
19611
  function fitHotSites(code, entries, siteBytes, fixed, ceiling, sourceBytes, profile) {
19483
19612
  const ranked = [...selectHotSites(code, entries, siteBytes, ceiling - fixed, profile)];
19484
19613
  for (; ranked.length > 0; ranked.pop()) {
@@ -19488,25 +19617,17 @@ function fitHotSites(code, entries, siteBytes, fixed, ceiling, sourceBytes, prof
19488
19617
  }
19489
19618
  return void 0;
19490
19619
  }
19491
- function staticWeights(code, entries) {
19620
+ function staticWeights(code, entries, siteBytes) {
19492
19621
  let weight = /* @__PURE__ */ new Map();
19493
19622
  const kids = [];
19494
19623
  for (let round = 0; round < ROUNDS; round++) {
19495
19624
  const next = /* @__PURE__ */ new Map();
19496
19625
  for (const ip of entries) next.set(ip, 1);
19497
19626
  for (const [ip, w] of weight) {
19498
- kids.length = 0;
19499
- childSlots(code, ip, kids);
19627
+ edgesAsLowered(code, siteBytes, ip, kids);
19500
19628
  const op = code[ip];
19501
- if (op === OP_CONT && continuationGate(code, code[ip + 3]) !== void 0) {
19502
- kids.length = 0;
19503
- kids.push(code[ip + 1], code[ip + 3]);
19504
- }
19505
19629
  const scale = op === OP_REP || op === OP_REPV ? LOOP : op === OP_CHOICE || op === OP_DISPATCH ? 1 / Math.max(1, kids.length) : 1;
19506
- for (const k of kids) {
19507
- const kid = code[k] === OP_GAPGATE ? code[k + 1] : k;
19508
- next.set(kid, Math.min(CAP, (next.get(kid) ?? 0) + w * scale));
19509
- }
19630
+ for (const kid of kids) next.set(kid, Math.min(CAP, (next.get(kid) ?? 0) + w * scale));
19510
19631
  }
19511
19632
  weight = next;
19512
19633
  }
@@ -22732,6 +22853,8 @@ function findFreeIdentifiers(code, filename) {
22732
22853
  // src/plugin/entry-supercompiler.ts
22733
22854
  import { parseSync as parseSync5 } from "oxc-parser";
22734
22855
  var PIECE = /^_pf(\d+)$/;
22856
+ var MERGEABLE = /^_(?:pf|sk)\d+$/;
22857
+ var TEMP = /^_t\d+_/;
22735
22858
  var WRAP = "function __parseman_supercompile__(){\n";
22736
22859
  var SUFFIX = "\n}";
22737
22860
  function ast(value) {
@@ -22865,6 +22988,93 @@ function fallback(emitted, reason, before) {
22865
22988
  }
22866
22989
  };
22867
22990
  }
22991
+ function namesUnder(root, uses, bound) {
22992
+ let plain = true;
22993
+ const bind = (id) => {
22994
+ const name = nameOf2(id);
22995
+ if (name !== void 0) bound.add(name);
22996
+ };
22997
+ const walk = (node2, parent) => {
22998
+ if (node2.type === "VariableDeclarator") bind(ast(node2.id));
22999
+ else if (node2.type === "LabeledStatement") bind(ast(node2.label));
23000
+ else if (isFunction(node2)) {
23001
+ if (node2 !== root) bind(ast(node2.id));
23002
+ nodes(node2.params).forEach(bind);
23003
+ }
23004
+ const name = nameOf2(node2);
23005
+ if (name !== void 0 && (MERGEABLE.test(name) || TEMP.test(name))) {
23006
+ const isKey = parent?.type === "Property" && parent.key === node2 && parent.computed !== true;
23007
+ const isProperty = parent?.type === "MemberExpression" && parent.property === node2 && parent.computed !== true;
23008
+ if (parent?.type === "Property" && parent.shorthand === true) plain = false;
23009
+ if (!isKey && !isProperty) uses.push({ ...range(node2), name });
23010
+ }
23011
+ for (const child of children2(node2)) walk(child, node2);
23012
+ };
23013
+ walk(root, void 0);
23014
+ return plain;
23015
+ }
23016
+ function mergeSameBodies(source) {
23017
+ const parsed = parseSync5("parseman-merge-same-bodies.js", WRAP + source + SUFFIX);
23018
+ if (parsed.errors.length > 0) return source;
23019
+ const statements = nodes(ast(nodes(parsed.program.body)[0]?.body)?.body);
23020
+ const bodies = [];
23021
+ const others = [];
23022
+ const declared = /* @__PURE__ */ new Set();
23023
+ for (const statement of statements) {
23024
+ const name = statement.type === "FunctionDeclaration" ? nameOf2(ast(statement.id)) : void 0;
23025
+ const uses = [];
23026
+ const bound = /* @__PURE__ */ new Set();
23027
+ if (!namesUnder(statement, uses, bound)) return source;
23028
+ for (const local of bound) declared.add(local);
23029
+ if (name === void 0 || !MERGEABLE.test(name)) others.push(...uses);
23030
+ else bodies.push({ ...range(statement), name, uses: uses.sort((a, b) => a.start - b.start), local: bound });
23031
+ }
23032
+ for (const local of declared) if (MERGEABLE.test(local)) return source;
23033
+ const merged = /* @__PURE__ */ new Map();
23034
+ const current = (name) => {
23035
+ let at = name;
23036
+ for (let next = merged.get(at); next !== void 0; next = merged.get(at)) at = next;
23037
+ return at;
23038
+ };
23039
+ let live = bodies;
23040
+ for (let more = true; more; ) {
23041
+ more = false;
23042
+ const first = /* @__PURE__ */ new Map();
23043
+ live = live.filter((body) => {
23044
+ const temps = /* @__PURE__ */ new Map();
23045
+ const edits2 = body.uses.map((use) => {
23046
+ if (MERGEABLE.test(use.name)) {
23047
+ const site = current(use.name);
23048
+ return { ...use, text: site === body.name ? "#self" : site };
23049
+ }
23050
+ if (!body.local.has(use.name)) return { ...use, text: use.name };
23051
+ const seen = temps.get(use.name) ?? temps.size;
23052
+ temps.set(use.name, seen);
23053
+ return { ...use, text: `#t${seen}` };
23054
+ });
23055
+ const key = render2(source, body.start, body.end, edits2);
23056
+ const kept = first.get(key);
23057
+ if (kept === void 0) {
23058
+ first.set(key, body.name);
23059
+ return true;
23060
+ }
23061
+ merged.set(body.name, kept);
23062
+ more = true;
23063
+ return false;
23064
+ });
23065
+ }
23066
+ if (merged.size === 0) return source;
23067
+ const edits = [];
23068
+ const rename = (use) => {
23069
+ if (MERGEABLE.test(use.name) && merged.has(use.name)) edits.push({ ...use, text: current(use.name) });
23070
+ };
23071
+ others.forEach(rename);
23072
+ for (const body of bodies) {
23073
+ if (merged.has(body.name)) edits.push({ start: body.start, end: body.end, text: "" });
23074
+ else body.uses.forEach(rename);
23075
+ }
23076
+ return render2(source, 0, source.length, edits);
23077
+ }
22868
23078
  function supercompileRuleMapReplacement(prog, replacement, select) {
22869
23079
  const cfg = defaultAssemblyCfgs(prog)[0];
22870
23080
  if (cfg === void 0) return replacement;
@@ -22899,7 +23109,7 @@ function supercompileRuleMapReplacement(prog, replacement, select) {
22899
23109
  const start = body.start - prefix.length + 1;
22900
23110
  const end = body.end - prefix.length - 1;
22901
23111
  if (replacement.slice(start, end) !== FACTORY_ARITY_GUARD + canonical2.source + "\n") return replacement;
22902
- const transformed = replacement.slice(0, start) + FACTORY_ARITY_GUARD + candidate.source + "\n" + replacement.slice(end);
23112
+ const transformed = replacement.slice(0, start) + FACTORY_ARITY_GUARD + mergeSameBodies(candidate.source) + "\n" + replacement.slice(end);
22903
23113
  const checked = parseSync5("parseman-entry-supercompile-replacement-result.js", prefix + transformed);
22904
23114
  return checked.errors.length === 0 ? transformed : replacement;
22905
23115
  }