// Groups failures from a judge --list tsv by machine state and source context. // Usage: node classify.mjs fails.tsv [kindFilter] [max] import fs from "node:fs"; import { createRequire } from "node:module"; const root = new URL("../..", import.meta.url).pathname.replace(/\/$/, ""); const ts = createRequire(root + "/package.json")("typescript"); process.env.TYPESTRIP_ERASE_WASM ??= "build/erase.wasm"; const { blankSourceFile } = await import(root + "/vendor/ts-blank-space/out/index.js"); const { transformSync, engine } = await import(root + "/src/index.js"); const rows = fs.readFileSync(process.argv[2], "utf8").trim().split("\n").map((l) => l.split("\t")); const filter = process.argv[3] ?? ""; const max = Number(process.argv[4] ?? 400); const d = (i) => engine().dbg?.(i); // only build/downlevel-debug.wasm has the probe function* parts(src) { let seed = src.length; for (let i = 0; i < src.length; ) { seed = (seed * 1103515245 + 12345) & 0x7fffffff; const n = 1 + (seed % 4096); yield src.slice(i, i + n); i += n; } } function chunked(src) { let out = ""; for (const piece of transformSync(parts(src))) out += piece; return out; } // First index where got differs from want other than a `;` where want has a space, or vice versa. function sigDiff(want, got) { const n = Math.min(want.length, got.length); for (let i = 0; i < n; i++) { if (want[i] === got[i]) continue; if ((want[i] === " " && got[i] === ";") || (want[i] === ";" && got[i] === " ")) continue; return i; } return want.length === got.length ? -1 : n; } const groups = new Map(); let shown = 0; for (const [kind, name, problem] of rows) { if (filter && !kind.includes(filter)) continue; if (shown++ >= max) break; let src; try { src = fs.readFileSync(name, "utf8"); } catch { continue; } let key, ctx; if (kind.startsWith("threw")) { const bytes = Buffer.from(src, "utf8"); try { if (kind === "threw whole") transformSync(src); else chunked(src); key = "no longer throws"; ctx = ""; } catch { const tsb = d(4); const at = bytes.subarray(Math.max(0, tsb - 70), tsb).toString() + "⟨⟩" + bytes.subarray(tsb, tsb + Number(process.env.AFTER ?? 30)).toString(); key = `${kind}: state=${d(0)} c=${d(1)} tt=${d(5)}`; ctx = JSON.stringify(at); } } else { const sf = ts.createSourceFile("input.ts", src, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS); const want = blankSourceFile(sf, () => {}); let got; try { got = transformSync(src); } catch { key = kind + " (now throws)"; ctx = ""; } if (got !== undefined) { const i = sigDiff(want, got); key = kind; ctx = i < 0 ? "only ; differences" : `@${i} src=${JSON.stringify(src.slice(Math.max(0, i - 60), i + 40))}\n want=${JSON.stringify(want.slice(Math.max(0, i - 60), i + 40))}\n got =${JSON.stringify(got.slice(Math.max(0, i - 60), i + 40))}`; } } if (!groups.has(key)) groups.set(key, []); groups.get(key).push({ name, ctx, problem }); } for (const [key, items] of [...groups.entries()].sort((a, b) => b[1].length - a[1].length)) { console.log(`\n## ${key} (${items.length})`); for (const it of items.slice(0, Number(process.env.PER ?? 3))) console.log(` ${it.name}\n ${it.ctx}`); }