tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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")