source_equations.py (9147B)
1 #!/usr/bin/env python3 2 """Opt-in authored equation/AST regression corpus against an explicit self-built Haskell toolchain.""" 3 import argparse 4 import contextlib 5 import json 6 from pathlib import Path 7 import re 8 import subprocess 9 import tempfile 10 from generation_backend import BACKENDS, DEFAULT_BACKEND 11 12 parser = argparse.ArgumentParser(description=__doc__) 13 parser.add_argument("tricu", type=Path) 14 parser.add_argument("--generation", type=Path, required=True, help="completed self-built closure to exercise; no recovery frontend") 15 parser.add_argument("--timeout", type=int, default=60) 16 parser.add_argument("--compile-seconds", type=int, default=180) 17 parser.add_argument("--directory", type=Path, help='retain preparation and verified compile checkpoints') 18 parser.add_argument("--batch-start", type=int, default=0, help='explicit resume after already reported batches; skipped outcomes are not counted') 19 parser.add_argument("--backend", choices=BACKENDS, default=DEFAULT_BACKEND, 20 help='application lowering for wrappers (default: %(default)s); rebuilt unit/parser exports always come from --generation') 21 args = parser.parse_args() 22 tricu = str(args.tricu.resolve()) 23 generation = args.generation.resolve() 24 25 26 from source_equation_cases import cases, quoted, position 27 28 29 manager = contextlib.nullcontext(args.directory.resolve()) if args.directory else tempfile.TemporaryDirectory(prefix="source-equations-") 30 with manager as directory: 31 root = Path(directory) 32 root.mkdir(parents=True, exist_ok=True) 33 (root / "tricu.workspace").write_text("tricu.workspace.v1\n") 34 store = str(root / "store") 35 36 def run(command, timeout=None): 37 reply = subprocess.run(command, capture_output=True, text=True, 38 timeout=timeout or args.timeout) 39 if reply.returncode: 40 raise RuntimeError(f"host/resource failure {reply.returncode}: {command}\n{reply.stderr}") 41 return reply.stdout.strip() 42 43 tools = root / "tools" 44 tool_file = 'build-stable.arboricx' if args.backend == 'stable' else 'build.arboricx' 45 tool_import = run([tricu, "arboricx", "import", "--file", str(generation / tool_file), 46 "--store", str(tools), "--indexed"]) 47 tool_pin = re.search(r"Created module alias .* -> ([0-9a-f]{64})", tool_import)[1] 48 flags = ["--toolchain", tool_pin, "--tool-store", str(tools)] 49 # Selected compiler/parser exports are actual rebuilt executable trees. 50 # Package them natively; source facades are compiled by the selected artifact. 51 for module, alias, selection in [("prelude", "prelude", None), ("parser", "parser", None), 52 ("source_unit", "unit", ["compileGateProgram"]), 53 ("source_program", "program", ["parseGateProgram"])]: 54 cas = generation / "cas" 55 manifest = (cas / "cache/source-build" / module).read_text().split("\t")[2] 56 exports = {row.split("\t")[1]: row.split("\t")[3] 57 for row in (cas / "objects" / manifest[:3] / manifest).read_text().splitlines()[1:]} 58 bundle = root / (alias + "-support.arboricx") 59 command = [tricu, "arboricx", "export", "--store", str(cas), "--output", str(bundle)] 60 for name in selection or exports: 61 command += ["--target", exports[name], "--name", name] 62 run(command, timeout=180) 63 imported = run([tricu, "arboricx", "import", "--store", store, "--file", str(bundle)]) 64 pin = re.search(r"Created module alias .* -> ([0-9a-f]{64})", imported)[1] 65 (root / (alias + ".tri")).write_text("!export * from " + pin + "\n") 66 sources = "" 67 uses = "!import prelude *\n!import parser *\n" 68 header = sources + uses 69 runner = root / "runner.tri" 70 original = Path(__file__).with_name("sourceEquationRunner.tri").read_text() 71 body = "\n".join(line for line in original.splitlines() if not line.startswith("!import")) 72 runner.write_text(sources + "" + uses 73 + "!import unit as U\n!import program as G\n" 74 + "!export matchesBatch\n!export matchesAst\n" + body) 75 76 def compile_bundle(source, name): 77 bundle = root / (name + ".arboricx") 78 run([tricu, "arboricx", "compile", "--store", store, "--file", str(source), 79 "--name", name, "--output", str(bundle), *flags, "--compile-timeout", str(args.compile_seconds), 80 "+RTS", "-N1", "-RTS"], timeout=args.compile_seconds + 30) 81 assert bundle.stat().st_size < 1048576, bundle 82 return bundle 83 84 def import_bundle(bundle, alias): 85 output = run([tricu, "arboricx", "import", "--store", store, 86 "--file", str(bundle), "--module", alias]) 87 pin = re.search(r"Created module alias .* -> ([0-9a-f]{64})", output)[1] 88 term = re.search(r"^ \w+ -> ([0-9a-f]{64})", output, re.MULTILINE)[1] 89 manifest = (Path(store) / "objects" / pin[:3] / pin).read_text() 90 name = manifest.splitlines()[1].split("\t")[1] 91 return pin, pin + ":" + name 92 93 comparator = compile_bundle(runner, "matchesBatch") 94 comparator_pin, _ = import_bundle(comparator, "equation-runner") 95 fixture_source = root / "fixtures.tri" 96 declarations = [f"fixture{name} = (pair {environment} (pair {quoted(source)} {expected}))" 97 for name, source, expected, environment, _ in cases] 98 batch_names = [] 99 for index in range(0, len(cases), 4): 100 chunk = cases[index:index + 4] 101 registry = "[" + " ".join(f"(pair {quoted(name)} fixture{name})" 102 for name, *_ in chunk) + "]" 103 outcomes = [f'(pair {quoted(name)} {"true" if truth else "false"})' 104 for name, _, _, _, truth in chunk] 105 name = "batch" + str(index // 4) 106 declarations.append(name + " = (pair " + registry + " [" + " ".join(outcomes) + "])" ) 107 batch_names.append((name, True)) 108 if index == 0: 109 wrong = [f'(pair {quoted(chunk[0][0])} false)', *outcomes[1:]] 110 declarations.append("wrongBatch = (pair " + registry + " [" + " ".join(wrong) + "])" ) 111 batch_names.append(("wrongBatch", False)) 112 fixture_source.write_text(header + "!export *\n" + "\n".join(declarations) + "\n") 113 # Small batches bound retained fixture/compiled-output residency. 114 for name, truth in batch_names: 115 if name == 'wrongBatch' and args.batch_start > 0: 116 continue 117 if name.startswith('batch') and int(name[5:]) < args.batch_start: 118 continue 119 bundle = compile_bundle(fixture_source, name) 120 _, term = import_bundle(bundle, name) 121 host = run([tricu, "arboricx", "run", "--store", store, 122 "--module", comparator_pin, "--name", "matchesBatch", 123 "--arg-export", term, "--format", "tree"]) 124 assert host == ("t t" if truth else "t"), (name, host) 125 print(f"{name}: expected Haskell outcome", flush=True) 126 tested_cases = len(cases[args.batch_start * 4:]) 127 print(f"{tested_cases} independent named source outcomes passed in this invocation", flush=True) 128 # Independent full parser reply: located equation/pattern spans, original 129 # body names, one coalesced definition, empty remainder and protocol flags. 130 ast_source = "f t = x\nf _ = y" 131 golden_source = root / "ast-golden.tri" 132 golden_source.write_text(header + '!export *\n' 133 'p line column offset = pair line (pair column offset)\n' 134 'n kind payload start end = pair kind (pair payload (pair start end))\n' 135 'name = n 2 "f" (p 1 1 0) (p 1 2 1)\n' 136 'leaf = n 13 (pair 1 t) (p 1 3 2) (p 1 4 3)\n' 137 'wild = n 13 (pair 0 t) (p 2 3 10) (p 2 4 11)\n' 138 'bodyX = n 2 "x" (p 1 7 6) (p 1 8 7)\n' 139 'bodyY = n 2 "y" (p 2 7 14) (p 2 8 15)\n' 140 'group = n 12 [(pair [leaf] bodyX) (pair [wild] bodyY)] (p 1 1 0) (p 2 8 15)\n' 141 'program = pair [(pair name group)] t\n' 142 'astExpected = pair true (pair program (pair t (pair true false)))\n' 143 'badLeaf = n 13 (pair 1 t) (p 1 4 2) (p 1 4 3)\n' 144 'badGroup = n 12 [(pair [badLeaf] bodyX) (pair [wild] bodyY)] (p 1 1 0) (p 2 8 15)\n' 145 'astWrong = pair true (pair (pair [(pair name badGroup)] t) (pair t (pair true false)))\n' 146 f'astSource = {quoted(ast_source)}\n') 147 ast_comparator = compile_bundle(runner, "matchesAst") 148 ast_pin, _ = import_bundle(ast_comparator, "ast-runner") 149 source_bundle = compile_bundle(golden_source, "astSource") 150 _, source_term = import_bundle(source_bundle, "ast-source") 151 for name, truth in [("astExpected", True), ("astWrong", False)]: 152 bundle = compile_bundle(golden_source, name) 153 _, term = import_bundle(bundle, name) 154 host = run([tricu, "arboricx", "run", "--store", store, "--module", ast_pin, 155 "--name", "matchesAst", "--arg-export", source_term, 156 "--arg-export", term, "--format", "tree"]) 157 assert host == ("t t" if truth else "t"), (name, host) 158 print(f"{tested_cases} authored-source and 2 full AST/reply checks passed in this invocation; " 159 f"comparator {comparator.stat().st_size} bytes")