Files
SPARKlib/generate_session.py
2024-05-22 18:06:28 +09:00

196 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python
import difflib
import glob
import os
import shutil
import sys
def run(cmd):
print("")
print("run: " + cmd)
os.system(cmd)
projectfile = "sparklib_internal.gpr"
def run_manual(check_to_prove, option=""):
cmd = f"gnatprove -j0 -P {projectfile} -U --prover=coq --report=provers"
if ":" not in check_to_prove:
run(cmd + " " + option + check_to_prove)
else:
run(cmd + " " + option + "--limit-line=" + check_to_prove)
def run_automatic(prover, level=4, timeout=None):
cmd = (
f"gnatprove -P {projectfile} --counterexamples=off -j0"
+ f" --prover={prover} --level={level}"
)
if timeout is not None:
cmd += f" --timeout={timeout}"
run(cmd)
def run_options(opt):
cmd = f"gnatprove -P {projectfile} --counterexamples=off -j0 {opt}"
run(cmd)
def copy_file(f_ctx, f_v):
# This creates a new file which is the new .ctx file.
# Behavior: Before the intros tactic we append only f_v; after the intros
# or '#' character, we append only f_ctx.
temp = f_ctx + "___tmp.tmp"
b = False
with open(temp, "w") as new_temp:
with open(f_v) as file_v:
for line in file_v:
if line[:6] == "intros":
break
else:
new_temp.write(line)
with open(f_ctx) as file_ctx:
for line in file_ctx:
if line[:6] == "intros" or line[:1] == "#":
b = True
if b:
new_temp.write(line)
# Replace context file with the temp file
os.remove(f_ctx)
shutil.move(temp, f_ctx)
def diff_file(f_ctx, g_v):
# This function makes a diff without spaces between files f and g. If there
# is a line which is in the first file and not in the second one, create a
# temp diff file of same name in folder temp, otherwise do not create temp
# file.
# We assume f ends with .ctx and g ends with .v. Basically, don't use this
# function anywhere else.
diff_seen = False
with open(f_ctx, "r") as file1:
with open(g_v, "r") as file2:
# Removing spaces because cpp introduce extra spaces as diff.
lines1 = list(
map(
lambda y: str(filter(lambda x: x not in " \t", y)),
file1.readlines(),
)
)
lines2 = list(
map(
lambda y: str(filter(lambda x: x not in " \t", y)),
file2.readlines(),
)
)
diff = difflib.unified_diff(lines1, lines2, fromfile=f_ctx, tofile=g_v, n=1)
for line in diff:
if not line[0] == "-":
diff_seen = True
break
if diff_seen:
temp_file = os.path.basename(f_ctx)[:-4] + ".diff"
temp_path = os.path.join("./temp", temp_file)
with open(temp_path, "a") as new:
for line in diff:
new.write(line)
def diff_all(gen_ctx):
# If files have changed due to changes in spark/why3 generates files,
# diff will be printed in temp.
# For technical reasons, diff are printed without spaces
# Do not use this function in an other context. It is used only once
# with gen_ctx to False meaning files are not erased and replaced with
# new ones. To replace them, pass True.
for root, _dirs, files in os.walk("./proof"):
for name in files:
if name.endswith(".v"):
ctx_file = os.path.join(root, name[:-2] + ".ctx")
v_file = os.path.join(root, name)
diff_file(ctx_file, v_file)
if gen_ctx:
copy_file(ctx_file, v_file)
def kill_and_regenerate(check):
list_of_check = []
if check:
list_of_check.append(check)
else:
with open("manual_proof.in") as f:
for i in f:
list_of_check.append(i)
print("")
print("--------------------------")
print("Cleanup previous artifacts")
print("--------------------------")
for d in ["./proof/sessions", "./temp"]:
if os.path.isdir(d):
print(f"deleting {d}")
shutil.rmtree(d)
os.makedirs("./temp")
os.system("make clean")
for envvar in ["SPARKLIB_INSTALLED", "SPARKLIB_BODY_MODE"]:
if envvar not in os.environ:
print(f"{envvar} not set; make sure to run 'source setup.sh'")
exit(1)
print("")
print("----------------------------")
print("Generate the Coq proof files")
print("----------------------------")
# Force regeneration of coq files where necessary.
# This step is used to generate the fake coq files and put the names of
# coq files inside the session. This cannot be done in one step because
# if coq files are already present, it will create new ones (not
# check the present coq files).
for i in list_of_check:
run_manual(i)
# Make the diff between generated .v and .ctx files. If there are differences
# between them not in the proof, you are sure to fail
diff_all(False)
print("")
print("-----------------------------")
print("Check and register Coq proofs")
print("-----------------------------")
# cleaning and regeneration of *.v
os.system("make clean")
os.system("make generate")
run_automatic("cvc5", level=1)
# Do *not* remove this call as it is used to check that coq proofs are
# correct after regeneration. And ability to generate session is *necessary*
# as there is no way to extend a session in gnatprove.
for i in list_of_check:
run_manual(i)
print("")
print("---------------------------------------------")
print("Prove remaining checks with automatic provers")
print("---------------------------------------------")
print("")
run_automatic("cvc5,z3,alt-ergo,colibri", timeout=100)
print("")
print("---------------------------")
print("Summarize all proved checks")
print("---------------------------")
run_options(opt="--output-msg-only --report=provers")
for shape_file in glob.glob("proof/sessions/*/why3shapes*"):
print("deleting shapes file ", shape_file)
os.remove(shape_file)
for bak_file in glob.glob("proof/sessions/*/*.bak"):
print("deleting temp file ", bak_file)
os.remove(bak_file)
def choose_mode():
if len(sys.argv) == 1:
kill_and_regenerate(None)
else:
if len(sys.argv) == 2:
kill_and_regenerate(sys.argv[1])
choose_mode()