#!/usr/bin/env python3 """Re-process the raw responses from a finished run with fixes: - Per-call temp dir so file stem matches module name - Tag-disjoint counting (only count tags unique to a cohort, ignore overlapping ones like `lambda` shared by apply+call) """ from __future__ import annotations import json, re, subprocess, sys from pathlib import Path RUN_DIR = Path(sys.argv[1]) RESULTS = json.loads((RUN_DIR / "results.json").read_text()) REVERSE_APPLY = [("tail-apply","tail-app"),("term-constructor","term-ctor"), ("pat-constructor","pat-ctor"),("constructor","ctor"), ("apply","app"),("lambda","lam"),("typecon","con")] REVERSE_CALL = [("tail-call","tail-app"),("term-constructor","term-ctor"), ("pat-constructor","pat-ctor"),("constructor","ctor"), ("call","app"),("lambda","lam"),("typecon","con")] def reverse_sub(src, pairs): out = src for n,o in pairs: out = re.sub(rf"\({re.escape(n)}(?=[\s)])", f"({o}", out) return out # Disjoint discriminator tags only — the head of function application # (which IS the test variable). Ignore lambda/constructor/typecon since # apply and call share those. DISCRIM = { "classic": ["app","tail-app"], "apply": ["apply","tail-apply"], "call": ["call","tail-call"], } def count_tags(src, tags): return {t: len(re.findall(rf"\({re.escape(t)}(?=[\s)])", src)) for t in tags} def extract_module_name(code): m = re.search(r"\(module\s+([A-Za-z_][A-Za-z0-9_]*)", code) return m.group(1) if m else None def run_pipeline_fixed(code, module_name, expected_stdout, base): work = base / module_name work.mkdir(parents=True, exist_ok=True) src = work / f"{module_name}.ail" src.write_text(code) out = {"check_ok": False, "check_stderr": "", "build_ok": False, "run_ok": False, "run_stdout": "", "matches_expected": False} c = subprocess.run(["ail","check",str(src)], capture_output=True, text=True, timeout=30) out["check_stderr"] = (c.stderr or c.stdout)[:2000] if c.returncode != 0: return out out["check_ok"] = True b = subprocess.run(["ail","build",str(src),"-o",str(work/"bin")], capture_output=True, text=True, timeout=60) if b.returncode != 0: out["check_stderr"] = b.stderr[:2000] return out out["build_ok"] = True r = subprocess.run([str(work/"bin")], capture_output=True, text=True, timeout=10) if r.returncode == 0: out["run_ok"] = True out["run_stdout"] = r.stdout out["matches_expected"] = (r.stdout == expected_stdout) return out # need expected stdout per task TASKS = {} CMA_TASKS = Path("/home/brummel/dev/ailang/experiments/2026-05-12-cross-model-authoring/master/tasks") for f in CMA_TASKS.glob("*.task.json"): t = json.loads(f.read_text()) TASKS[t["id"]] = t["expected_stdout"] base = RUN_DIR / "work2" base.mkdir(exist_ok=True) new_rows = [] for r in RESULTS: code = r["extracted_code"] cohort = r["cohort"] pairs = {"classic":None,"apply":REVERSE_APPLY,"call":REVERSE_CALL}[cohort] pipeline_input = reverse_sub(code, pairs) if pairs else code mod_name = extract_module_name(pipeline_input) or r["task_id"] pipe = run_pipeline_fixed(pipeline_input, mod_name, TASKS[r["task_id"]], base) own_disc = count_tags(code, DISCRIM[cohort]) foreign_disc = {} for c, tags in DISCRIM.items(): if c == cohort: continue foreign_disc[c] = count_tags(code, tags) new_rows.append({ "cohort": cohort, "task_id": r["task_id"], "trial": r["trial"], "module_name_in_output": mod_name, "module_name_expected": r["task_id"], "module_match": (mod_name == r["task_id"]), "own_disc": own_disc, "foreign_disc": foreign_disc, "check_ok": pipe["check_ok"], "build_ok": pipe["build_ok"], "run_ok": pipe["run_ok"], "matches_expected": pipe["matches_expected"], "run_stdout": pipe["run_stdout"], "first_err_line": pipe["check_stderr"].split("\n")[0][:140], }) (RUN_DIR / "results2.json").write_text(json.dumps(new_rows, indent=2)) print("\n=== final pipeline pass per cohort ===") by_cohort = {} for r in new_rows: c = by_cohort.setdefault(r["cohort"], {"n":0,"mod":0,"check":0,"build":0,"run":0,"correct":0}) c["n"] += 1 if r["module_match"]: c["mod"] += 1 if r["check_ok"]: c["check"] += 1 if r["build_ok"]: c["build"] += 1 if r["run_ok"]: c["run"] += 1 if r["matches_expected"]: c["correct"] += 1 for cohort, c in by_cohort.items(): print(f"{cohort:8s} n={c['n']} mod-name-OK={c['mod']} check={c['check']} build={c['build']} run={c['run']} stdout-correct={c['correct']}") print("\n=== discriminator tag totals (head of function application only) ===") disc_totals = {} for r in new_rows: c = r["cohort"] own_sum = sum(r["own_disc"].values()) foreign_sum = sum(sum(v.values()) for v in r["foreign_disc"].values()) t = disc_totals.setdefault(c, {"own":0,"foreign":0,"by_foreign":{}}) t["own"] += own_sum for fc, fv in r["foreign_disc"].items(): t["by_foreign"][fc] = t["by_foreign"].get(fc,0) + sum(fv.values()) t["foreign"] += foreign_sum for cohort, t in disc_totals.items(): print(f"{cohort:8s} own={t['own']:3d} foreign={t['foreign']:3d} breakdown={t['by_foreign']}") print("\n=== per-row failure mode ===") for r in new_rows: flag = "OK" if r["matches_expected"] else ("CORRECT-NOT" if r["run_ok"] else ("RUN" if r["build_ok"] else ("BUILD" if r["check_ok"] else "CHECK"))) print(f"[{r['cohort']:8s}] {r['task_id']:18s} t{r['trial']} {flag:12s} {r['first_err_line']}")