use std::io::Write;
use std::process::{Command, Stdio};
#[derive(Debug, Clone)]
pub struct Solver {
program: String,
seconds: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Answer {
Unsat,
Sat(String),
Unknown,
}
impl Solver {
#[must_use]
pub fn find() -> Option<Solver> {
for program in ["z3", "cvc5"] {
let found = Command::new(program).arg("--version").output();
if found.is_ok_and(|out| out.status.success()) {
return Some(Solver { program: program.to_owned(), seconds: 10 });
}
}
None
}
#[must_use]
pub fn within(self, seconds: u32) -> Solver {
Solver { seconds, ..self }
}
#[must_use]
pub fn name(&self) -> &str {
&self.program
}
pub fn ask(&self, query: &str) -> std::io::Result<Answer> {
let timeout = match self.program.as_str() {
"cvc5" => format!("--tlimit={}", self.seconds * 1000),
_ => format!("-T:{}", self.seconds),
};
let stdin = if self.program == "cvc5" { "-" } else { "-in" };
let mut child = Command::new(&self.program)
.arg(stdin)
.arg(timeout)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let Some(mut pipe) = child.stdin.take() else {
return Err(std::io::Error::other("the solver has no standard input"));
};
pipe.write_all(query.as_bytes())?;
drop(pipe);
let out = child.wait_with_output()?;
let said = String::from_utf8_lossy(&out.stdout);
let mut lines = said.lines();
Ok(match lines.next().map(str::trim) {
Some("unsat") => Answer::Unsat,
Some("sat") => Answer::Sat(lines.collect::<Vec<_>>().join("\n")),
_ => Answer::Unknown,
})
}
}