use crate::{
ast::{ArithExpr, BoolExpr},
rational::Rational,
solver::Solver,
};
use num_traits::ToPrimitive;
use smt2parser::{CommandStream, concrete};
use std::{
collections::HashMap,
fs::File,
io::{BufReader, Write},
};
pub struct SmtParser<'a> {
pub solver: Solver,
bool_vars: HashMap<String, BoolExpr>,
real_vars: HashMap<String, ArithExpr>,
is_unsat: bool,
writer: &'a mut dyn Write,
}
impl<'a> SmtParser<'a> {
pub fn new(writer: &'a mut dyn Write) -> Self {
Self {
solver: Solver::new(),
bool_vars: HashMap::new(),
real_vars: HashMap::new(),
is_unsat: false,
writer,
}
}
pub fn run_str(&mut self, script: &str) {
use std::io::Cursor;
let cursor = Cursor::new(script);
let reader = std::io::BufReader::new(cursor);
let stream = smt2parser::CommandStream::new(reader, smt2parser::concrete::SyntaxBuilder, None);
for command in stream {
match command {
Ok(cmd) => self.execute_command(cmd),
Err(e) => eprintln!("Parser error: {:?}", e),
}
}
}
pub fn run_file(&mut self, path: &str) {
let file = File::open(path).expect("Failed to open SMT-LIB file");
let reader = BufReader::new(file);
let stream = CommandStream::new(reader, concrete::SyntaxBuilder, None);
for command in stream {
match command {
Ok(cmd) => self.execute_command(cmd),
Err(e) => eprintln!("Parser error: {:?}", e),
}
}
}
fn execute_command(&mut self, cmd: concrete::Command) {
match cmd {
concrete::Command::SetLogic { symbol } => {
if symbol.0 != "QF_LRA" && symbol.0 != "QF_LIA" && symbol.0 != "QF_NRA" {
println!("Warning: Logic '{}' might not be fully supported by SeMiTONE yet.", symbol.0);
}
}
concrete::Command::DeclareFun { symbol, sort, .. } => {
let name = symbol.0;
let sort_name = match sort {
concrete::Sort::Simple { identifier } => Self::symbol_of_identifier(&identifier).to_string(),
_ => panic!("Complex sorts are not supported yet"),
};
match sort_name.as_str() {
"Bool" => {
let v = self.solver.smt.new_bool();
self.bool_vars.insert(name, v);
}
"Real" => {
let v = self.solver.smt.new_real();
self.real_vars.insert(name, v);
}
"Int" => {
let v = self.solver.smt.new_int();
self.real_vars.insert(name, v);
}
_ => panic!("Unsupported sort: {}", sort_name),
}
}
concrete::Command::Assert { term } => {
if self.is_unsat {
return; }
let bool_expr = self.translate_bool_term(&term);
if !self.solver.smt.assert(&bool_expr) {
self.is_unsat = true;
}
}
concrete::Command::CheckSat => {
if self.is_unsat {
writeln!(self.writer, "unsat").unwrap();
} else {
match self.solver.check_sat() {
Some(true) => writeln!(self.writer, "sat").unwrap(),
Some(false) => writeln!(self.writer, "unsat").unwrap(),
None => writeln!(self.writer, "unknown").unwrap(),
}
}
}
concrete::Command::GetModel => {
if self.is_unsat {
writeln!(self.writer, "(error \"get-model is only available after a successful check-sat\")").unwrap();
return;
}
writeln!(self.writer, "(").unwrap();
for (name, var) in &self.bool_vars {
if let Some(val) = self.solver.smt.get_bool_val(var) {
writeln!(self.writer, " (define-fun {} () Bool {})", name, if val { "true" } else { "false" }).unwrap();
}
}
for (name, var) in &self.real_vars {
if let Some(val) = self.solver.smt.get_arith_val(var) {
let Rational::Finite(rat) = val.rational_part() else {
writeln!(self.writer, " (define-fun {} () Real <non-finite>)", name).unwrap();
continue;
};
let num = rat.numer();
let den = rat.denom();
if den == &rug::Integer::from(1) {
writeln!(self.writer, " (define-fun {} () Real {}.0)", name, num).unwrap();
} else {
writeln!(self.writer, " (define-fun {} () Real (/ {} {}))", name, num, den).unwrap();
}
}
}
writeln!(self.writer, ")").unwrap();
}
concrete::Command::Push { level } => {
let n = level.to_usize().unwrap_or_else(|| panic!("push level too large for usize: {}", level));
for _ in 0..n {
self.solver.smt.push();
}
}
concrete::Command::Pop { level } => {
let n = level.to_usize().unwrap_or_else(|| panic!("pop level too large for usize: {}", level));
for _ in 0..n {
self.solver.smt.pop();
}
self.is_unsat = false;
}
_ => {} }
}
fn symbol_of_identifier(id: &concrete::Identifier) -> &str {
match id {
concrete::Identifier::Simple { symbol } => symbol.0.as_str(),
concrete::Identifier::Indexed { symbol, .. } => symbol.0.as_str(),
}
}
fn symbol_of_qual_identifier(qid: &concrete::QualIdentifier) -> &str {
match qid {
concrete::QualIdentifier::Simple { identifier } => Self::symbol_of_identifier(identifier),
concrete::QualIdentifier::Sorted { identifier, .. } => Self::symbol_of_identifier(identifier),
}
}
fn translate_bool_term(&self, term: &concrete::Term) -> BoolExpr {
match term {
concrete::Term::QualIdentifier(id) => {
let name = Self::symbol_of_qual_identifier(id);
if name == "true" {
return BoolExpr::True;
}
if name == "false" {
return BoolExpr::False;
}
self.bool_vars.get(name).cloned().unwrap_or_else(|| panic!("Undeclared boolean variable: {}", name))
}
concrete::Term::Application { qual_identifier, arguments } => {
let op = Self::symbol_of_qual_identifier(qual_identifier);
match op {
"and" => BoolExpr::And(arguments.iter().map(|a| self.translate_bool_term(a)).collect()),
"or" => BoolExpr::Or(arguments.iter().map(|a| self.translate_bool_term(a)).collect()),
"not" => !(self.translate_bool_term(&arguments[0])),
"=>" => !self.translate_bool_term(&arguments[0]) | self.translate_bool_term(&arguments[1]),
"<=" => self.translate_arith_term(&arguments[0]).le(self.translate_arith_term(&arguments[1])),
">=" => self.translate_arith_term(&arguments[0]).ge(self.translate_arith_term(&arguments[1])),
"<" => self.translate_arith_term(&arguments[0]).lt(self.translate_arith_term(&arguments[1])),
">" => self.translate_arith_term(&arguments[0]).gt(self.translate_arith_term(&arguments[1])),
"=" => {
if self.is_bool_term(&arguments[0]) {
let left = self.translate_bool_term(&arguments[0]);
let right = self.translate_bool_term(&arguments[1]);
left.eq(&right)
} else {
self.translate_arith_term(&arguments[0]).eq(self.translate_arith_term(&arguments[1]))
}
}
_ => panic!("Unsupported boolean operator: {}", op),
}
}
_ => panic!("Unsupported boolean term structure"),
}
}
fn translate_arith_term(&self, term: &concrete::Term) -> ArithExpr {
match term {
concrete::Term::Constant(c) => match c {
concrete::Constant::Numeral(num) => {
let s = num.to_string();
let rat = rug::Rational::from_str_radix(&s, 10).unwrap();
ArithExpr::Const(rat)
}
concrete::Constant::Decimal(dec) => {
let num_str = dec.numer().to_string();
let den_str = dec.denom().to_string();
let num = rug::Integer::from_str_radix(&num_str, 10).unwrap();
let den = rug::Integer::from_str_radix(&den_str, 10).unwrap();
ArithExpr::Const(rug::Rational::from((num, den)))
}
_ => panic!("Unsupported constant type"),
},
concrete::Term::QualIdentifier(id) => {
let name = Self::symbol_of_qual_identifier(id);
self.real_vars.get(name).cloned().unwrap_or_else(|| panic!("Undeclared arithmetic variable: {}", name))
}
concrete::Term::Application { qual_identifier, arguments } => {
let op = Self::symbol_of_qual_identifier(qual_identifier);
let args: Vec<_> = arguments.iter().map(|a| self.translate_arith_term(a)).collect();
match op {
"+" => ArithExpr::Add(args),
"*" => ArithExpr::Mul(args),
"-" => {
if args.len() == 1 {
ArithExpr::Neg(Box::new(args.into_iter().next().unwrap()))
} else {
let mut iter = args.into_iter();
let first = iter.next().unwrap();
let mut sum_args = vec![first];
for arg in iter {
sum_args.push(ArithExpr::Neg(Box::new(arg)));
}
ArithExpr::Add(sum_args)
}
}
"/" => {
assert_eq!(args.len(), 2, "Division expects exactly two arguments");
let mut iter = args.into_iter();
let num = iter.next().unwrap();
let den = iter.next().unwrap();
ArithExpr::Div(Box::new(num), Box::new(den))
}
_ => panic!("Unsupported arithmetic operator: {}", op),
}
}
_ => panic!("Unsupported arithmetic term structure"),
}
}
fn is_bool_term(&self, term: &concrete::Term) -> bool {
match term {
concrete::Term::QualIdentifier(id) => {
let name = Self::symbol_of_qual_identifier(id);
name == "true" || name == "false" || self.bool_vars.contains_key(name)
}
concrete::Term::Application { qual_identifier, .. } => {
let op = Self::symbol_of_qual_identifier(qual_identifier);
matches!(op, "and" | "or" | "not" | "=>" | "<=" | ">=" | "<" | ">" | "=")
}
_ => false,
}
}
}