package freeboogie.backend; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.math.BigInteger; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import java.util.List; import java.util.logging.Logger; /** * Used to interact with Simplify. * * TODO keep track whether the prover is alive or is known to be dead * TODO Abstract the prompt-based interaction in another class * * @author rgrig * @author reviewed by TODO */ public class SimplifyProver implements Prover { private static final Logger log = Logger.getLogger("freeboogie.backend"); private Process prover; private List<String> cmd; private BufferedReader in; private PrintStream out; // a |marker| is used to mark the beginning of an assumption frame private Deque<Term> assumptions; private Term marker; private SmtTermBuilder builder; /** * Creates a new {@code SimplifyProver}. It also tries to start the prover. * @param cmd the command to use to start the prover * @throws ProverException if the prover cannot be started */ public SimplifyProver(String[] cmd) throws ProverException { this.cmd = Arrays.asList(cmd); assumptions = new ArrayDeque<Term>(); marker = new Term(null); assumptions.add(marker); restartProver(); // TODO some of this stuff is probably common to multiple provers // so move it into the builder builder = new SmtTermBuilder(); builder.def("not", new Sort[]{Sort.PRED}, Sort.PRED); builder.def("and", Sort.PRED, Sort.PRED); builder.def("or", Sort.PRED, Sort.PRED); builder.def("implies", new Sort[]{Sort.PRED, Sort.PRED}, Sort.PRED); builder.def("iff", new Sort[]{Sort.PRED, Sort.PRED}, Sort.PRED); builder.def("var_int", String.class, Sort.VARINT); builder.def("var_bool", String.class, Sort.VARBOOL); builder.def("var_pred", String.class, Sort.PRED); builder.def("const_int", BigInteger.class, Sort.INT); builder.def("const_bool", Boolean.class, Sort.BOOL); builder.def("forall_int", new Sort[]{Sort.VARINT, Sort.PRED}, Sort.PRED); builder.def("<", new Sort[]{Sort.INT, Sort.INT}, Sort.PRED); builder.def("<=", new Sort[]{Sort.INT, Sort.INT}, Sort.PRED); builder.def(">", new Sort[]{Sort.INT, Sort.INT}, Sort.PRED); builder.def(">=", new Sort[]{Sort.INT, Sort.INT}, Sort.PRED); builder.def("eq_int", new Sort[]{Sort.INT, Sort.INT}, Sort.PRED); builder.def("eq_bool", new Sort[]{Sort.BOOL, Sort.BOOL}, Sort.PRED); // TODO register all stuff with the builder // TODO should I leave the user (vcgen) responsible for adding the // excluded middle for boolean variables? i think that's in the // escjava background predicate anyway builder.pushDef(); // mark the end of the prover builtin definitions } // TODO This is quite incomplete now private void printTerm(Term t, StringBuilder sb) { SmtTerm st = (SmtTerm)t; if (st.id.startsWith("var")) { sb.append((String)st.data); } else if (st.id.startsWith("forall")) { sb.append("(FORALL ("); printTerm(st.children[0], sb); sb.append(") "); printTerm(st.children[1], sb); sb.append(")"); } else if (st.id.equals("const_int")) { sb.append(st.data); } else if (st.id.equals("const_bool")) { if ((Boolean)st.data) sb.append("|true|"); else sb.append("|false|"); } else if (st.id.startsWith("eq")) { sb.append("(EQ "); printTerm(st.children[0], sb); sb.append(" "); printTerm(st.children[1], sb); sb.append(")"); } else { sb.append("("); sb.append(st.id.toUpperCase()); for (Term c : st.children) { sb.append(" "); printTerm(c, sb); } sb.append(")"); } } private void sendTerm(Term t) { StringBuilder sb = new StringBuilder(); printTerm(t, sb); log.info("backend> " + sb); out.print(sb); } private void sendAssume(Term t) { out.print("(BG_PUSH "); sendTerm(t); out.print(")\n"); out.flush(); } private void checkIfDead() throws ProverException { try { int ev = prover.exitValue(); throw new ProverException("Prover exit code: " + ev); } catch (IllegalThreadStateException e) { // the prover is still alive } } private void waitPrompt() throws ProverException { try { int c; boolean firstonline = true; while ((c = in.read()) != -1) { if (firstonline && c == '>') return; firstonline = c == '\n' || c == '\r'; } } catch (IOException e) { throw new ProverException("Can't read what Simplify says."); } } /* @see freeboogie.backend.Prover#assume(freeboogie.backend.Term) */ public void assume(Term t) throws ProverException { sendAssume(t); assumptions.add(t); checkIfDead(); } /* @see freeboogie.backend.Prover#getBuilder() */ public TermBuilder getBuilder() { return builder; } /* @see freeboogie.backend.Prover#getDetailedAnswer() */ public ProverAnswer getDetailedAnswer() { // TODO Auto-generated method stub return null; } /* @see freeboogie.backend.Prover#isSat(freeboogie.backend.Term) */ public boolean isSat(Term t) throws ProverException { waitPrompt(); log.fine("Got prompt, sending query."); sendTerm(builder.mk("not", t)); out.println(); out.flush(); // wait for prompt or for Valid, Invalid, Unknown try { StringBuilder sb = new StringBuilder(); while (true) { int c = in.read(); if (c == '\n' || c == '\r') { String line = sb.toString(); log.info("simplify> " + line); if (line.contains("Valid")) return false; if (line.contains("Invalid") || line.contains("Unknown")) return true; sb.setLength(0); continue; } if (c == '>' && sb.length() == 0) throw new ProverException("The prover seems a bit confused."); if (c == -1) throw new ProverException("Prover died."); sb.append((char)c); } } catch (IOException e) { throw new ProverException("Failed to read prover answer."); } } /* @see freeboogie.backend.Prover#pop() */ public void pop() throws ProverException { while (assumptions.getLast() != marker) retract(); assumptions.removeLast(); } /* @see freeboogie.backend.Prover#push() */ public void push() { assumptions.push(marker); } /* @see freeboogie.backend.Prover#restartProver() */ public void restartProver() throws ProverException { log.fine("exec: " + cmd); ProcessBuilder pb = new ProcessBuilder(cmd); try { prover = pb.start(); in = new BufferedReader(new InputStreamReader(prover.getInputStream())); out = new PrintStream(prover.getOutputStream()); out.println("(PROMPT_ON)"); // make sure prompt is ON for (Term t : assumptions) if (t != marker) sendAssume(t); out.flush(); } catch (Exception e) { throw new ProverException("Cannot start prover." + cmd); } } /* @see freeboogie.backend.Prover#retract() */ public void retract() { Term last; do last = assumptions.getLast(); while (last == marker); out.print("(BG_POP)"); out.flush(); } /** * Runs some basic tests. * @param args the command line arguments * @throws Exception thrown if something goes wrong */ public static void main(String[] args) throws Exception { Prover p = new SimplifyProver(args); TermBuilder b = p.getBuilder(); Term x = b.mk("var_pred", "x"); Term y = b.mk("var_pred", "y"); Term q = b.mk("not", b.mk("iff", b.mk("iff", b.mk("and", x, y), b.mk("or", x, y)), b.mk("iff", x, y) )); System.out.println("false = " + p.isSat(q)); } }