use alloc::collections::BTreeSet;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::api::SatResult;
use crate::cmd_context::Session;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Sort {
Bool,
Int,
Real,
BitVec(u32),
Array(alloc::boxed::Box<Sort>, alloc::boxed::Box<Sort>),
Uninterpreted(String),
Datatype(String),
}
impl Sort {
pub fn smt(&self) -> String {
match self {
Sort::Bool => "Bool".to_string(),
Sort::Int => "Int".to_string(),
Sort::Real => "Real".to_string(),
Sort::BitVec(n) => alloc::format!("(_ BitVec {n})"),
Sort::Array(d, r) => alloc::format!("(Array {} {})", d.smt(), r.smt()),
Sort::Uninterpreted(name) => name.clone(),
Sort::Datatype(name) => name.clone(),
}
}
pub fn bv_width(&self) -> Option<u32> {
match self {
Sort::BitVec(n) => Some(*n),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Ast {
src: String,
sort: Sort,
}
impl Ast {
pub fn sort(&self) -> &Sort {
&self.sort
}
pub fn to_smt(&self) -> &str {
&self.src
}
pub fn new(src: String, sort: Sort) -> Ast {
Ast { src, sort }
}
pub fn pattern(terms: &[&Ast]) -> String {
let mut src = String::from("(");
for (i, t) in terms.iter().enumerate() {
if i > 0 {
src.push(' ');
}
src.push_str(&t.src);
}
src.push(')');
src
}
pub fn distinct(args: &[&Ast]) -> Ast {
let mut src = String::from("(distinct");
for a in args {
src.push(' ');
src.push_str(&a.src);
}
src.push(')');
Ast {
src,
sort: Sort::Bool,
}
}
fn binop(&self, op: &str, rhs: &Ast, sort: Sort) -> Ast {
Ast {
src: alloc::format!("({op} {} {})", self.src, rhs.src),
sort,
}
}
fn unop(&self, op: &str, sort: Sort) -> Ast {
Ast {
src: alloc::format!("({op} {})", self.src),
sort,
}
}
pub fn add(&self, rhs: &Ast) -> Ast {
self.binop("+", rhs, self.sort.clone())
}
pub fn sub(&self, rhs: &Ast) -> Ast {
self.binop("-", rhs, self.sort.clone())
}
pub fn mul(&self, rhs: &Ast) -> Ast {
self.binop("*", rhs, self.sort.clone())
}
pub fn neg(&self) -> Ast {
self.unop("-", self.sort.clone())
}
pub fn lt(&self, rhs: &Ast) -> Ast {
self.binop("<", rhs, Sort::Bool)
}
pub fn le(&self, rhs: &Ast) -> Ast {
self.binop("<=", rhs, Sort::Bool)
}
pub fn gt(&self, rhs: &Ast) -> Ast {
self.binop(">", rhs, Sort::Bool)
}
pub fn ge(&self, rhs: &Ast) -> Ast {
self.binop(">=", rhs, Sort::Bool)
}
pub fn eq(&self, rhs: &Ast) -> Ast {
self.binop("=", rhs, Sort::Bool)
}
pub fn ne(&self, rhs: &Ast) -> Ast {
self.eq(rhs).not()
}
pub fn and(&self, rhs: &Ast) -> Ast {
self.binop("and", rhs, Sort::Bool)
}
pub fn or(&self, rhs: &Ast) -> Ast {
self.binop("or", rhs, Sort::Bool)
}
pub fn implies(&self, rhs: &Ast) -> Ast {
self.binop("=>", rhs, Sort::Bool)
}
pub fn xor(&self, rhs: &Ast) -> Ast {
self.binop("xor", rhs, Sort::Bool)
}
pub fn not(&self) -> Ast {
self.unop("not", Sort::Bool)
}
pub fn iff(&self, rhs: &Ast) -> Ast {
self.binop("=", rhs, Sort::Bool)
}
pub fn ite(&self, then: &Ast, els: &Ast) -> Ast {
Ast {
src: alloc::format!("(ite {} {} {})", self.src, then.src, els.src),
sort: then.sort.clone(),
}
}
pub fn div(&self, rhs: &Ast) -> Ast {
let op = if self.sort == Sort::Int { "div" } else { "/" };
self.binop(op, rhs, self.sort.clone())
}
pub fn rem_(&self, rhs: &Ast) -> Ast {
self.binop("rem", rhs, self.sort.clone())
}
pub fn modulo(&self, rhs: &Ast) -> Ast {
self.binop("mod", rhs, self.sort.clone())
}
pub fn power(&self, rhs: &Ast) -> Ast {
self.binop("^", rhs, self.sort.clone())
}
pub fn int2real(&self) -> Ast {
self.unop("to_real", Sort::Real)
}
pub fn real2int(&self) -> Ast {
self.unop("to_int", Sort::Int)
}
pub fn is_int(&self) -> Ast {
self.unop("is_int", Sort::Bool)
}
pub fn bvadd(&self, rhs: &Ast) -> Ast {
self.binop("bvadd", rhs, self.sort.clone())
}
pub fn bvsub(&self, rhs: &Ast) -> Ast {
self.binop("bvsub", rhs, self.sort.clone())
}
pub fn bvmul(&self, rhs: &Ast) -> Ast {
self.binop("bvmul", rhs, self.sort.clone())
}
pub fn bvudiv(&self, rhs: &Ast) -> Ast {
self.binop("bvudiv", rhs, self.sort.clone())
}
pub fn bvsdiv(&self, rhs: &Ast) -> Ast {
self.binop("bvsdiv", rhs, self.sort.clone())
}
pub fn bvurem(&self, rhs: &Ast) -> Ast {
self.binop("bvurem", rhs, self.sort.clone())
}
pub fn bvsrem(&self, rhs: &Ast) -> Ast {
self.binop("bvsrem", rhs, self.sort.clone())
}
pub fn bvsmod(&self, rhs: &Ast) -> Ast {
self.binop("bvsmod", rhs, self.sort.clone())
}
pub fn bvand(&self, rhs: &Ast) -> Ast {
self.binop("bvand", rhs, self.sort.clone())
}
pub fn bvor(&self, rhs: &Ast) -> Ast {
self.binop("bvor", rhs, self.sort.clone())
}
pub fn bvxor(&self, rhs: &Ast) -> Ast {
self.binop("bvxor", rhs, self.sort.clone())
}
pub fn bvnand(&self, rhs: &Ast) -> Ast {
self.binop("bvnand", rhs, self.sort.clone())
}
pub fn bvnor(&self, rhs: &Ast) -> Ast {
self.binop("bvnor", rhs, self.sort.clone())
}
pub fn bvxnor(&self, rhs: &Ast) -> Ast {
self.binop("bvxnor", rhs, self.sort.clone())
}
pub fn bvshl(&self, rhs: &Ast) -> Ast {
self.binop("bvshl", rhs, self.sort.clone())
}
pub fn bvlshr(&self, rhs: &Ast) -> Ast {
self.binop("bvlshr", rhs, self.sort.clone())
}
pub fn bvashr(&self, rhs: &Ast) -> Ast {
self.binop("bvashr", rhs, self.sort.clone())
}
pub fn bvnot(&self) -> Ast {
self.unop("bvnot", self.sort.clone())
}
pub fn bvneg(&self) -> Ast {
self.unop("bvneg", self.sort.clone())
}
pub fn bvult(&self, rhs: &Ast) -> Ast {
self.binop("bvult", rhs, Sort::Bool)
}
pub fn bvslt(&self, rhs: &Ast) -> Ast {
self.binop("bvslt", rhs, Sort::Bool)
}
pub fn bvule(&self, rhs: &Ast) -> Ast {
self.binop("bvule", rhs, Sort::Bool)
}
pub fn bvsle(&self, rhs: &Ast) -> Ast {
self.binop("bvsle", rhs, Sort::Bool)
}
pub fn bvugt(&self, rhs: &Ast) -> Ast {
self.binop("bvugt", rhs, Sort::Bool)
}
pub fn bvsgt(&self, rhs: &Ast) -> Ast {
self.binop("bvsgt", rhs, Sort::Bool)
}
pub fn bvuge(&self, rhs: &Ast) -> Ast {
self.binop("bvuge", rhs, Sort::Bool)
}
pub fn bvsge(&self, rhs: &Ast) -> Ast {
self.binop("bvsge", rhs, Sort::Bool)
}
pub fn concat(&self, rhs: &Ast) -> Ast {
let w = self.sort.bv_width().unwrap_or(0) + rhs.sort.bv_width().unwrap_or(0);
self.binop("concat", rhs, Sort::BitVec(w))
}
pub fn extract(&self, high: u32, low: u32) -> Ast {
Ast {
src: alloc::format!("((_ extract {high} {low}) {})", self.src),
sort: Sort::BitVec(high.saturating_sub(low) + 1),
}
}
pub fn sign_ext(&self, i: u32) -> Ast {
Ast {
src: alloc::format!("((_ sign_extend {i}) {})", self.src),
sort: Sort::BitVec(self.sort.bv_width().unwrap_or(0) + i),
}
}
pub fn zero_ext(&self, i: u32) -> Ast {
Ast {
src: alloc::format!("((_ zero_extend {i}) {})", self.src),
sort: Sort::BitVec(self.sort.bv_width().unwrap_or(0) + i),
}
}
pub fn repeat(&self, i: u32) -> Ast {
Ast {
src: alloc::format!("((_ repeat {i}) {})", self.src),
sort: Sort::BitVec(self.sort.bv_width().unwrap_or(0) * i),
}
}
pub fn rotate_left(&self, i: u32) -> Ast {
Ast {
src: alloc::format!("((_ rotate_left {i}) {})", self.src),
sort: self.sort.clone(),
}
}
pub fn rotate_right(&self, i: u32) -> Ast {
Ast {
src: alloc::format!("((_ rotate_right {i}) {})", self.src),
sort: self.sort.clone(),
}
}
pub fn int2bv(&self, n: u32) -> Ast {
Ast {
src: alloc::format!("((_ int2bv {n}) {})", self.src),
sort: Sort::BitVec(n),
}
}
pub fn bv2int(&self, _signed: bool) -> Ast {
self.unop("bv2int", Sort::Int)
}
pub fn select(&self, index: &Ast) -> Ast {
let range = match &self.sort {
Sort::Array(_, r) => (**r).clone(),
other => other.clone(),
};
self.binop("select", index, range)
}
pub fn store(&self, index: &Ast, value: &Ast) -> Ast {
Ast {
src: alloc::format!("(store {} {} {})", self.src, index.src, value.src),
sort: self.sort.clone(),
}
}
pub fn is_numeral(&self) -> bool {
parse_numeral(&self.src).is_some()
}
pub fn as_rational(&self) -> Option<(i128, i128)> {
parse_numeral(&self.src)
}
pub fn as_int(&self) -> Option<i128> {
match parse_numeral(&self.src)? {
(n, 1) => Some(n),
_ => None,
}
}
pub fn numeral_string(&self) -> Option<String> {
let (n, d) = parse_numeral(&self.src)?;
Some(if d == 1 {
n.to_string()
} else {
alloc::format!("{n}/{d}")
})
}
}
pub(crate) fn top_level_parts(s: &str) -> Vec<&str> {
let bytes = s.as_bytes();
let mut parts = Vec::new();
let mut depth: i32 = 0;
let mut start: Option<usize> = None;
for (i, &c) in bytes.iter().enumerate() {
match c {
b'(' => {
if depth == 0 && start.is_none() {
start = Some(i);
}
depth += 1;
}
b')' => {
depth -= 1;
if depth == 0
&& let Some(st) = start.take()
{
parts.push(&s[st..=i]);
}
}
c if c.is_ascii_whitespace() => {
if depth == 0
&& let Some(st) = start.take()
{
parts.push(&s[st..i]);
}
}
_ => {
if depth == 0 && start.is_none() {
start = Some(i);
}
}
}
}
if let Some(st) = start {
parts.push(&s[st..]);
}
parts
}
fn gcd(mut a: u128, mut b: u128) -> u128 {
while b != 0 {
let t = a % b;
a = b;
b = t;
}
a
}
fn reduce(mut n: i128, mut d: i128) -> Option<(i128, i128)> {
if d == 0 {
return None;
}
if d < 0 {
n = -n;
d = -d;
}
let g = gcd(n.unsigned_abs(), d.unsigned_abs());
if g > 1 {
n /= g as i128;
d /= g as i128;
}
Some((n, d))
}
fn parse_numeral(s: &str) -> Option<(i128, i128)> {
let s = s.trim();
if s.is_empty() {
return None;
}
if let Some(hex) = s.strip_prefix("#x") {
if !hex.is_empty() && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return i128::from_str_radix(hex, 16).ok().map(|n| (n, 1));
}
return None;
}
if let Some(bin) = s.strip_prefix("#b") {
if !bin.is_empty() && bin.bytes().all(|b| b == b'0' || b == b'1') {
return i128::from_str_radix(bin, 2).ok().map(|n| (n, 1));
}
return None;
}
if let Some(rest) = s.strip_prefix("(_ bv") {
let digits: String = rest.chars().take_while(|c| c.is_ascii_digit()).collect();
if !digits.is_empty() {
return digits.parse::<i128>().ok().map(|n| (n, 1));
}
return None;
}
if let Some(inner) = s.strip_prefix('(').and_then(|r| r.strip_suffix(')')) {
let parts = top_level_parts(inner);
match parts.as_slice() {
["-", x] => {
let (n, d) = parse_numeral(x)?;
return Some((-n, d));
}
["/", p, q] => {
let (pn, pd) = parse_numeral(p)?;
let (qn, qd) = parse_numeral(q)?;
return reduce(pn.checked_mul(qd)?, pd.checked_mul(qn)?);
}
_ => return None,
}
}
if let Some(dot) = s.find('.') {
let (int_part, frac_part) = (&s[..dot], &s[dot + 1..]);
let neg = int_part.starts_with('-');
let int_digits = int_part.strip_prefix('-').unwrap_or(int_part);
if (int_digits.is_empty() || int_digits.bytes().all(|b| b.is_ascii_digit()))
&& frac_part.bytes().all(|b| b.is_ascii_digit())
&& frac_part.len() <= 30
{
let combined = alloc::format!("{int_digits}{frac_part}");
let num = combined.parse::<i128>().ok()?;
let mut den: i128 = 1;
for _ in 0..frac_part.len() {
den = den.checked_mul(10)?;
}
return reduce(if neg { -num } else { num }, den);
}
return None;
}
s.parse::<i128>().ok().map(|n| (n, 1))
}
#[derive(Clone, Debug)]
pub struct FuncDecl {
name: String,
range: Sort,
}
impl FuncDecl {
pub fn new(name: String, range: Sort) -> FuncDecl {
FuncDecl { name, range }
}
pub fn name(&self) -> &str {
&self.name
}
pub fn range(&self) -> &Sort {
&self.range
}
pub fn apply(&self, args: &[&Ast]) -> Ast {
if args.is_empty() {
return Ast {
src: self.name.clone(),
sort: self.range.clone(),
};
}
let mut src = alloc::format!("({}", self.name);
for a in args {
src.push(' ');
src.push_str(&a.src);
}
src.push(')');
Ast {
src,
sort: self.range.clone(),
}
}
}
pub struct Context {
session: Session,
declared: BTreeSet<String>,
decls: Vec<String>,
fresh: u64,
}
impl Default for Context {
fn default() -> Context {
Context::new()
}
}
impl Context {
pub fn new() -> Context {
Context {
session: Session::new(),
declared: BTreeSet::new(),
decls: Vec::new(),
fresh: 0,
}
}
pub fn declarations(&self) -> &[String] {
&self.decls
}
fn declare(&mut self, cmd: String) {
let _ = self.session.eval(&cmd);
self.decls.push(cmd);
}
pub fn const_(&mut self, name: &str, sort: Sort) -> Ast {
if self.declared.insert(name.to_string()) {
self.declare(alloc::format!("(declare-const {name} {})", sort.smt()));
}
Ast {
src: name.to_string(),
sort,
}
}
pub fn declare_func(&mut self, name: &str, domain: Vec<Sort>, range: Sort) -> FuncDecl {
if self.declared.insert(name.to_string()) {
let doms: Vec<String> = domain.iter().map(Sort::smt).collect();
self.declare(alloc::format!(
"(declare-fun {name} ({}) {})",
doms.join(" "),
range.smt()
));
}
FuncDecl {
name: name.to_string(),
range,
}
}
pub fn declare_sort(&mut self, name: &str) -> Sort {
if self.declared.insert(alloc::format!("sort:{name}")) {
self.declare(alloc::format!("(declare-sort {name} 0)"));
}
Sort::Uninterpreted(name.to_string())
}
pub fn declare_datatype(&mut self, name: &str, ctors_body: &str) -> Sort {
if self.declared.insert(alloc::format!("sort:{name}")) {
self.declare(alloc::format!("(declare-datatype {name} ({ctors_body}))"));
}
Sort::Datatype(name.to_string())
}
pub fn fresh_const(&mut self, prefix: &str, sort: Sort) -> Ast {
self.fresh += 1;
let name = alloc::format!("{prefix}!{}", self.fresh);
self.const_(&name, sort)
}
pub fn const_array(domain: Sort, value: &Ast) -> Ast {
let arr = Sort::Array(
alloc::boxed::Box::new(domain),
alloc::boxed::Box::new(value.sort.clone()),
);
Ast {
src: alloc::format!("((as const {}) {})", arr.smt(), value.src),
sort: arr,
}
}
pub fn quantifier(
is_forall: bool,
weight: u32,
bound: &[(&str, &Sort)],
patterns: &[&str],
body: &Ast,
) -> Ast {
let kw = if is_forall { "forall" } else { "exists" };
let mut vars = String::new();
for (name, sort) in bound {
vars.push_str(&alloc::format!("({name} {})", sort.smt()));
}
let inner = if patterns.is_empty() && weight == 0 {
body.src.clone()
} else {
let mut ann = alloc::format!("(! {}", body.src);
for p in patterns {
ann.push_str(" :pattern ");
ann.push_str(p);
}
if weight != 0 {
ann.push_str(&alloc::format!(" :weight {weight}"));
}
ann.push(')');
ann
};
Ast {
src: alloc::format!("({kw} ({vars}) {inner})"),
sort: Sort::Bool,
}
}
pub fn int(v: i64) -> Ast {
let src = if v < 0 {
alloc::format!("(- {})", -(v as i128))
} else {
v.to_string()
};
Ast {
src,
sort: Sort::Int,
}
}
pub fn real(v: i64) -> Ast {
let inner = Context::int(v);
Ast {
src: inner.src,
sort: Sort::Real,
}
}
pub fn bool_val(b: bool) -> Ast {
Ast {
src: if b { "true" } else { "false" }.to_string(),
sort: Sort::Bool,
}
}
pub fn bv_val(v: u64, width: u32) -> Ast {
Ast {
src: alloc::format!("(_ bv{v} {width})"),
sort: Sort::BitVec(width),
}
}
pub fn numeral(s: &str, sort: Sort) -> Ast {
Ast {
src: s.to_string(),
sort,
}
}
pub fn assert(&mut self, a: &Ast) {
let _ = self.session.eval(&alloc::format!("(assert {})", a.src));
}
pub fn session_eval(&mut self, script: &str) -> Result<Vec<String>, String> {
self.session.eval(script)
}
pub fn check(&mut self) -> SatResult {
match self
.session
.eval("(check-sat)")
.ok()
.and_then(|v| v.into_iter().next())
.as_deref()
{
Some("sat") => SatResult::Sat,
Some("unsat") => SatResult::Unsat,
_ => SatResult::Unknown,
}
}
pub fn push(&mut self) {
let _ = self.session.eval("(push)");
}
pub fn pop(&mut self) {
let _ = self.session.eval("(pop)");
}
pub fn eval_value(&mut self, a: &Ast) -> Option<String> {
let out = self
.session
.eval(&alloc::format!("(get-value ({}))", a.src))
.ok()?;
let line = out.first()?;
let inner = line.trim().strip_prefix('(')?.strip_suffix(')')?.trim();
let inner = inner.strip_prefix('(')?.strip_suffix(')')?.trim();
Some(
inner
.strip_prefix(&a.src)
.map(str::trim)
.unwrap_or(inner)
.to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arithmetic_unsat() {
let mut ctx = Context::new();
let x = ctx.const_("x", Sort::Int);
let three = Context::int(3);
let four = Context::int(4);
ctx.assert(&three.lt(&x));
ctx.assert(&x.lt(&four));
assert_eq!(ctx.check(), SatResult::Unsat); }
#[test]
fn boolean_and_model() {
let mut ctx = Context::new();
let x = ctx.const_("x", Sort::Int);
ctx.assert(&x.ge(&Context::int(5)));
ctx.assert(&x.le(&Context::int(5)));
assert_eq!(ctx.check(), SatResult::Sat);
assert_eq!(ctx.eval_value(&x).as_deref(), Some("5"));
}
#[test]
fn push_pop_scopes() {
let mut ctx = Context::new();
let p = ctx.const_("p", Sort::Bool);
ctx.assert(&p.or(&p.not()));
assert_eq!(ctx.check(), SatResult::Sat);
ctx.push();
ctx.assert(&p.and(&p.not()));
assert_eq!(ctx.check(), SatResult::Unsat);
ctx.pop();
assert_eq!(ctx.check(), SatResult::Sat);
}
#[test]
fn bitvector_ops() {
let mut ctx = Context::new();
let b = ctx.const_("b", Sort::BitVec(8));
ctx.assert(&b.bvadd(&Context::bv_val(1, 8)).eq(&Context::bv_val(16, 8)));
assert_eq!(ctx.check(), SatResult::Sat);
assert_eq!(ctx.eval_value(&b).as_deref(), Some("#x0f"));
}
#[test]
fn array_select_store() {
let mut ctx = Context::new();
let idx = Sort::Int;
let arr = Sort::Array(
alloc::boxed::Box::new(idx.clone()),
alloc::boxed::Box::new(Sort::Int),
);
let m = ctx.const_("m", arr);
let stored = m.store(&Context::int(3), &Context::int(7));
let sel = stored.select(&Context::int(3));
ctx.assert(&sel.ne(&Context::int(7)));
assert_eq!(ctx.check(), SatResult::Unsat);
}
#[test]
fn ite_and_distinct() {
let mut ctx = Context::new();
let x = ctx.const_("x", Sort::Int);
let y = ctx.const_("y", Sort::Int);
ctx.assert(&Ast::distinct(&[&x, &y]));
let z = x.lt(&y).ite(&x, &y);
ctx.assert(&z.eq(&x));
ctx.assert(&z.eq(&y));
assert_eq!(ctx.check(), SatResult::Unsat);
}
#[test]
fn uf_apply_congruence() {
let mut ctx = Context::new();
let f = ctx.declare_func("f", alloc::vec![Sort::Int], Sort::Int);
let x = ctx.const_("x", Sort::Int);
let y = ctx.const_("y", Sort::Int);
ctx.assert(&x.eq(&y));
ctx.assert(&f.apply(&[&x]).ne(&f.apply(&[&y])));
assert_eq!(ctx.check(), SatResult::Unsat);
}
#[test]
fn bitvector_extended_ops() {
let mut ctx = Context::new();
let b = ctx.const_("b", Sort::BitVec(8));
ctx.assert(&b.bvsub(&Context::bv_val(1, 8)).eq(&Context::bv_val(14, 8)));
assert_eq!(ctx.check(), SatResult::Sat);
assert_eq!(ctx.eval_value(&b).as_deref(), Some("#x0f"));
}
#[test]
fn numeral_readback() {
let bv = |s: &str| Ast::new(s.to_string(), Sort::BitVec(8));
let int = |s: &str| Ast::new(s.to_string(), Sort::Int);
let real = |s: &str| Ast::new(s.to_string(), Sort::Real);
assert_eq!(int("6").numeral_string().as_deref(), Some("6"));
assert_eq!(int("6").as_int(), Some(6));
assert_eq!(int("(- 6)").numeral_string().as_deref(), Some("-6"));
assert_eq!(int("(- 6)").as_int(), Some(-6));
assert_eq!(bv("#x0f").numeral_string().as_deref(), Some("15"));
assert_eq!(bv("#x0f").as_int(), Some(15));
assert_eq!(bv("#b1010").as_int(), Some(10));
assert_eq!(bv("(_ bv15 8)").as_int(), Some(15));
assert_eq!(real("(/ 1 2)").numeral_string().as_deref(), Some("1/2"));
assert_eq!(real("(/ 1 2)").as_int(), None);
assert_eq!(
real("(/ (- 3) 6)").numeral_string().as_deref(),
Some("-1/2")
);
assert_eq!(real("1.5").numeral_string().as_deref(), Some("3/2"));
assert_eq!(real("2.0").as_int(), Some(2));
assert!(!Ast::new("x".to_string(), Sort::Int).is_numeral());
assert!(!Ast::new("(+ x 1)".to_string(), Sort::Int).is_numeral());
assert!(!Ast::new("(- x 1)".to_string(), Sort::Int).is_numeral());
assert!(!Ast::new("true".to_string(), Sort::Bool).is_numeral());
assert!(int("6").is_numeral());
}
#[test]
fn forall_uf_unsat() {
let mut ctx = Context::new();
let f = ctx.declare_func("f", alloc::vec![Sort::Int], Sort::Int);
let x = Ast::new("x".to_string(), Sort::Int);
let body = f.apply(&[&x]).ge(&Context::int(0));
let q = Context::quantifier(true, 0, &[("x", &Sort::Int)], &[], &body);
ctx.assert(&q);
ctx.assert(&f.apply(&[&Context::int(3)]).lt(&Context::int(0)));
assert_eq!(ctx.check(), SatResult::Unsat);
}
#[test]
fn forall_with_pattern() {
let mut ctx = Context::new();
let f = ctx.declare_func("f", alloc::vec![Sort::Int], Sort::Int);
let x = Ast::new("x".to_string(), Sort::Int);
let fx = f.apply(&[&x]);
let pat = Ast::pattern(&[&fx]);
let body = fx.ge(&Context::int(0));
let q = Context::quantifier(true, 1, &[("x", &Sort::Int)], &[&pat], &body);
assert!(q.to_smt().contains(":pattern"));
assert!(q.to_smt().contains(":weight 1"));
ctx.assert(&q);
ctx.assert(&f.apply(&[&Context::int(3)]).lt(&Context::int(0)));
assert_eq!(ctx.check(), SatResult::Unsat);
}
#[test]
fn datatype_list_sat() {
let mut ctx = Context::new();
let lst = ctx.declare_datatype("Lst", "(nil) (cons (hd Int) (tl Lst))");
assert_eq!(lst, Sort::Datatype("Lst".to_string()));
let l = ctx.const_("l", lst);
let cons1 = Ast::new(
"(cons 1 nil)".to_string(),
Sort::Datatype("Lst".to_string()),
);
ctx.assert(&l.eq(&cons1));
let hd_l = Ast::new("(hd l)".to_string(), Sort::Int);
ctx.assert(&hd_l.eq(&Context::int(1)));
assert_eq!(ctx.check(), SatResult::Sat);
}
#[test]
fn uf_congruence_unsat() {
let mut ctx = Context::new();
let x = ctx.const_("x", Sort::Int);
let y = ctx.const_("y", Sort::Int);
ctx.assert(&x.eq(&y));
ctx.assert(&x.add(&Context::int(1)).ne(&y.add(&Context::int(1))));
assert_eq!(ctx.check(), SatResult::Unsat);
}
}