use crate::ast::*;
use std::fmt::Write;
pub fn to_smtlib(spec: &Spec) -> String {
let mut out = String::new();
writeln!(out, "; spec: {}", spec.name).unwrap();
writeln!(out, "(set-logic ALL)").unwrap();
let mut targets = std::collections::BTreeSet::new();
collect_calls(&spec.body, &mut targets);
for q in &spec.quantifiers {
if let Some(c) = &q.constraint { collect_calls(c, &mut targets); }
}
for (name, arity) in targets {
let args: Vec<&str> = std::iter::repeat_n("Int", arity).collect();
writeln!(out, "(declare-fun {name} ({}) Int)", args.join(" ")).unwrap();
}
writeln!(out).unwrap();
write!(out, "(assert (not (forall (").unwrap();
for (i, q) in spec.quantifiers.iter().enumerate() {
if i > 0 { write!(out, " ").unwrap(); }
write!(out, "({} {})", q.name, smt_type(&q.ty)).unwrap();
}
write!(out, ") ").unwrap();
let antecedent = build_antecedent(spec);
write!(out, "(=> {antecedent} ").unwrap();
write!(out, "{}", expr_to_smt(&spec.body)).unwrap();
writeln!(out, "))))").unwrap();
writeln!(out, "(check-sat)").unwrap();
writeln!(out, "(get-model)").unwrap();
out
}
fn smt_type(t: &SpecType) -> &'static str {
match t {
SpecType::Int => "Int",
SpecType::Float => "Real",
SpecType::Bool => "Bool",
SpecType::Str => "String",
SpecType::Record { .. } => "<unsupported-Record>",
SpecType::List { .. } => "<unsupported-List>",
SpecType::Named { .. } => "<unsupported-Named>",
}
}
fn build_antecedent(spec: &Spec) -> String {
let parts: Vec<String> = spec.quantifiers.iter()
.filter_map(|q| q.constraint.as_ref().map(expr_to_smt))
.collect();
if parts.is_empty() { "true".into() }
else if parts.len() == 1 { parts.into_iter().next().unwrap() }
else { format!("(and {})", parts.join(" ")) }
}
fn expr_to_smt(e: &SpecExpr) -> String {
match e {
SpecExpr::Var { name } => name.clone(),
SpecExpr::IntLit { value } => value.to_string(),
SpecExpr::FloatLit { value } => format!("{value}"),
SpecExpr::BoolLit { value } => value.to_string(),
SpecExpr::StrLit { value } => format!("\"{value}\""),
SpecExpr::Not { expr } => format!("(not {})", expr_to_smt(expr)),
SpecExpr::BinOp { op, lhs, rhs } => {
let sop = match op {
SpecOp::Add => "+", SpecOp::Sub => "-", SpecOp::Mul => "*",
SpecOp::Div => "div", SpecOp::Mod => "mod",
SpecOp::Eq => "=", SpecOp::Neq => "distinct",
SpecOp::Lt => "<", SpecOp::Le => "<=",
SpecOp::Gt => ">", SpecOp::Ge => ">=",
SpecOp::And => "and", SpecOp::Or => "or",
};
format!("({sop} {} {})", expr_to_smt(lhs), expr_to_smt(rhs))
}
SpecExpr::Call { func, args } => {
if args.is_empty() { func.clone() }
else {
let parts: Vec<String> = args.iter().map(expr_to_smt).collect();
format!("({func} {})", parts.join(" "))
}
}
SpecExpr::Let { name, value, body } => {
format!("(let (({name} {})) {})", expr_to_smt(value), expr_to_smt(body))
}
SpecExpr::FieldAccess { value, field } => {
format!("(<unsupported-FieldAccess> {} {})", expr_to_smt(value), field)
}
SpecExpr::Index { list, index } => {
format!("(<unsupported-Index> {} {})",
expr_to_smt(list), expr_to_smt(index))
}
SpecExpr::Match { scrutinee, arms } => {
let _ = arms;
format!("(<unsupported-Match> {})", expr_to_smt(scrutinee))
}
}
}
fn collect_calls(e: &SpecExpr, out: &mut std::collections::BTreeSet<(String, usize)>) {
match e {
SpecExpr::Call { func, args } => {
out.insert((func.clone(), args.len()));
for a in args { collect_calls(a, out); }
}
SpecExpr::BinOp { lhs, rhs, .. } => {
collect_calls(lhs, out);
collect_calls(rhs, out);
}
SpecExpr::Not { expr } => collect_calls(expr, out),
SpecExpr::Let { value, body, .. } => {
collect_calls(value, out);
collect_calls(body, out);
}
_ => {}
}
}