tricu

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

scp_format_check.py (5553B)


      1 #!/usr/bin/env python3
      2 """Opt-in formatter integration checks; never part of the fast host gate."""
      3 import argparse
      4 from pathlib import Path
      5 import re
      6 import subprocess
      7 import tempfile
      8 
      9 ROOT = Path(__file__).resolve().parent.parent
     10 # Independent lexical preservation oracle, including comments and raw strings.
     11 TOKENS = re.compile(r'"(?:\\.|[^"\\])*"|--[^\r\n]*|=>|[A-Za-z_][A-Za-z0-9_.?\-]*|[0-9]+|[^\s]', re.S)
     12 
     13 
     14 def main():
     15     parser = argparse.ArgumentParser(description=__doc__)
     16     parser.add_argument("tricu", nargs="?", default="result/bin/tricu")
     17     parser.add_argument("--bootstrap", type=Path, required=True)
     18     args = parser.parse_args()
     19     executable = str((ROOT / args.tricu).resolve())
     20     with tempfile.TemporaryDirectory(prefix="scp-format-") as scratch:
     21         store = Path(scratch) / "store"
     22         source_file = Path(scratch) / "input.scp"
     23         def command(*flags):
     24             return subprocess.run([executable, "scp", "format", str(source_file),
     25                                    "--store", str(store), "--bootstrap", str(args.bootstrap.resolve()), *flags], cwd=ROOT,
     26                                   capture_output=True, timeout=120)
     27         def format_source(source):
     28             source_file.write_bytes(source.encode("utf-8"))
     29             result = command()
     30             if result.returncode:
     31                 if result.stderr.startswith(b"SCP syntax error:"):
     32                     return "__FORMAT_ERROR__"
     33                 raise AssertionError(result.stderr.decode())
     34             return result.stdout.decode("utf-8")
     35         fixtures = sorted((ROOT / "lib/consumer").rglob("*.scp"))
     36         fixtures += [ROOT / "demos/consumers/fizzbuzz/fizzbuzz.scp", ROOT / "demos/consumers/registry/registry.scp",
     37                      ROOT / "demos/consumers/configurations/application.scp"]
     38         cases = [path.read_text() for path in fixtures]
     39         cases += [
     40             'consumer x\n leaf @ _ => call worker "as @ => \\" --" as a in recur a @ leaf -- @ => as\n (leaf, tail) @ state => call worker tail as b in recur b @ state\n',
     41             'consumer x\n leaf @ _ => call a leaf as x in x\n (leaf, a) @ _ => call a call a a as inner in inner as outer in outer\n',
     42             'consumer x\n leaf @ _ => leaf\n (leaf, x) @ _ => x\n\n leaf @ _ => leaf\nconsumer y\n leaf @ _ => leaf\n',
     43             'consumer x\n leaf @ _ => match leaf of (leaf => call a leaf as x in x | stem leaf => call a leaf as y in y)\n',
     44             'consumer x\r\n  leaf @ _ => "literal  spaces" -- comment  spaces\r\n',
     45             'consumer x\n  leaf @ _ => leaf',
     46             'consumer x\n \"first\n    second\" @ _ => leaf\n',
     47             'consumer x\n  leaf @ _ => \"first\n!not a header\"\n',
     48         ]
     49         for i, source in enumerate(cases):
     50             formatted = format_source(source)
     51             assert formatted != "__FORMAT_ERROR__", (i, source)
     52             assert TOKENS.findall(formatted) == TOKENS.findall(source), (i, "tokens changed")
     53             assert format_source(formatted) == formatted, (i, "not idempotent")
     54         add = format_source((ROOT / "lib/consumer/add.scp").read_text())
     55         rules = [line for line in add.splitlines() if " => " in line]
     56         assert len({line.index("@") for line in rules}) == 1
     57         assert len({line.index("=>") for line in rules}) == 1
     58         assert len({line.index(" as ") for line in rules if "call " in line}) == 1
     59         assert len({line.rindex("@") for line in rules if "recur " in line}) == 1
     60         nested = format_source('consumer x\n leaf @ _ => call a leaf as x in x\n (leaf, a) @ _ => call a call a a as inner in inner as outer in outer\n')
     61         nested_rules = nested.splitlines()[1:]
     62         assert len({line.rindex(" as ") for line in nested_rules}) == 1
     63         scoped = format_source('consumer x\n leaf @ _ => leaf\n (leaf, x) @ _ => x\n\n leaf @ _ => leaf\nconsumer y\n leaf @ _ => leaf\n')
     64         scoped_lines = scoped.splitlines()
     65         assert scoped_lines[1].index("@") == scoped_lines[2].index("@")
     66         assert scoped_lines[4].index("@") == scoped_lines[6].index("@") == 7
     67         assert scoped_lines[1].index("@") > scoped_lines[4].index("@")
     68         assert format_source('consumer x\n leaf @ _ => call a leaf as\n') == "__FORMAT_ERROR__"
     69         source_file.write_bytes(cases[0].encode("utf-8"))
     70         expected = command().stdout
     71         original = source_file.read_bytes()
     72         assert command("--check").returncode == int(expected != original)
     73         assert source_file.read_bytes() == original
     74         source_file.chmod(0o640)
     75         assert command("--write").returncode == 0
     76         assert source_file.read_bytes() == expected
     77         assert source_file.stat().st_mode & 0o777 == 0o640
     78         assert command("--check").returncode == 0
     79         assert command("--check", "--write").returncode != 0
     80         source_file.write_bytes(b"consumer x\n leaf @ _ => call\n")
     81         broken = source_file.read_bytes()
     82         assert command("--write").returncode != 0
     83         assert source_file.read_bytes() == broken
     84         target = Path(scratch) / "target.scp"
     85         source_file.rename(target)
     86         source_file.symlink_to(target)
     87         assert command("--write").returncode != 0
     88         assert source_file.is_symlink()
     89         source_file.unlink()
     90         source_file.write_bytes(b"\xff")
     91         assert command("--write").returncode != 0
     92         assert source_file.read_bytes() == b"\xff"
     93         print(f"SCP formatter: {len(cases)} preservation/idempotence cases, columns, CLI modes/error safety OK")
     94 
     95 
     96 if __name__ == "__main__":
     97     main()