use ordeal::{BoolTerm, BvTerm, Sort};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::fmt;
fn mask(width: u32) -> u128 {
if width >= 128 {
u128::MAX
} else {
(1u128 << width) - 1
}
}
#[derive(Clone, Debug)]
pub struct BV {
term: BvTerm,
width: u32,
}
#[derive(Clone, Debug)]
pub struct Bool {
term: BoolTerm,
}
fn bv_rank(t: &BvTerm) -> u8 {
match t {
BvTerm::Const { .. } => 0,
BvTerm::Var { .. } => 1,
BvTerm::Add(..) => 2,
BvTerm::Sub(..) => 3,
BvTerm::Mul(..) => 4,
BvTerm::Udiv(..) => 5,
BvTerm::And(..) => 6,
BvTerm::Or(..) => 7,
BvTerm::Xor(..) => 8,
BvTerm::Shl(..) => 9,
BvTerm::Lshr(..) => 10,
BvTerm::Ashr(..) => 11,
BvTerm::Rotr(..) => 12,
BvTerm::Extract { .. } => 13,
BvTerm::Concat(..) => 14,
BvTerm::ZeroExt { .. } => 15,
BvTerm::SignExt { .. } => 16,
BvTerm::Ite { .. } => 17,
}
}
fn bool_rank(t: &BoolTerm) -> u8 {
match t {
BoolTerm::Eq(..) => 0,
BoolTerm::Ne(..) => 1,
BoolTerm::Ult(..) => 2,
BoolTerm::Ule(..) => 3,
BoolTerm::Ugt(..) => 4,
BoolTerm::Uge(..) => 5,
BoolTerm::Slt(..) => 6,
BoolTerm::Sle(..) => 7,
BoolTerm::Sgt(..) => 8,
BoolTerm::Sge(..) => 9,
BoolTerm::Not(..) => 10,
BoolTerm::And(..) => 11,
BoolTerm::Or(..) => 12,
}
}
fn ord_bv(a: &BvTerm, b: &BvTerm) -> Ordering {
bv_rank(a).cmp(&bv_rank(b)).then_with(|| match (a, b) {
(
BvTerm::Const {
value: va,
sort: sa,
},
BvTerm::Const {
value: vb,
sort: sb,
},
) => sa.width.cmp(&sb.width).then(va.cmp(vb)),
(BvTerm::Var { name: na, sort: sa }, BvTerm::Var { name: nb, sort: sb }) => {
sa.width.cmp(&sb.width).then_with(|| na.cmp(nb))
}
(BvTerm::Add(a1, a2), BvTerm::Add(b1, b2))
| (BvTerm::Sub(a1, a2), BvTerm::Sub(b1, b2))
| (BvTerm::Mul(a1, a2), BvTerm::Mul(b1, b2))
| (BvTerm::Udiv(a1, a2), BvTerm::Udiv(b1, b2))
| (BvTerm::And(a1, a2), BvTerm::And(b1, b2))
| (BvTerm::Or(a1, a2), BvTerm::Or(b1, b2))
| (BvTerm::Xor(a1, a2), BvTerm::Xor(b1, b2))
| (BvTerm::Shl(a1, a2), BvTerm::Shl(b1, b2))
| (BvTerm::Lshr(a1, a2), BvTerm::Lshr(b1, b2))
| (BvTerm::Ashr(a1, a2), BvTerm::Ashr(b1, b2))
| (BvTerm::Rotr(a1, a2), BvTerm::Rotr(b1, b2))
| (BvTerm::Concat(a1, a2), BvTerm::Concat(b1, b2)) => {
ord_bv(a1, b1).then_with(|| ord_bv(a2, b2))
}
(
BvTerm::Extract {
hi: ha,
lo: la,
arg: aa,
},
BvTerm::Extract {
hi: hb,
lo: lb,
arg: ab,
},
) => ha.cmp(hb).then(la.cmp(lb)).then_with(|| ord_bv(aa, ab)),
(BvTerm::ZeroExt { by: ba, arg: aa }, BvTerm::ZeroExt { by: bb, arg: ab })
| (BvTerm::SignExt { by: ba, arg: aa }, BvTerm::SignExt { by: bb, arg: ab }) => {
ba.cmp(bb).then_with(|| ord_bv(aa, ab))
}
(
BvTerm::Ite {
cond: ca,
then_: ta,
else_: ea,
},
BvTerm::Ite {
cond: cb,
then_: tb,
else_: eb,
},
) => ord_bool(ca, cb)
.then_with(|| ord_bv(ta, tb))
.then_with(|| ord_bv(ea, eb)),
_ => unreachable!("ord_bv: rank-equal variants are exhaustively matched"),
})
}
fn ord_bool(a: &BoolTerm, b: &BoolTerm) -> Ordering {
bool_rank(a).cmp(&bool_rank(b)).then_with(|| match (a, b) {
(BoolTerm::Eq(a1, a2), BoolTerm::Eq(b1, b2))
| (BoolTerm::Ne(a1, a2), BoolTerm::Ne(b1, b2))
| (BoolTerm::Ult(a1, a2), BoolTerm::Ult(b1, b2))
| (BoolTerm::Ule(a1, a2), BoolTerm::Ule(b1, b2))
| (BoolTerm::Ugt(a1, a2), BoolTerm::Ugt(b1, b2))
| (BoolTerm::Uge(a1, a2), BoolTerm::Uge(b1, b2))
| (BoolTerm::Slt(a1, a2), BoolTerm::Slt(b1, b2))
| (BoolTerm::Sle(a1, a2), BoolTerm::Sle(b1, b2))
| (BoolTerm::Sgt(a1, a2), BoolTerm::Sgt(b1, b2))
| (BoolTerm::Sge(a1, a2), BoolTerm::Sge(b1, b2)) => {
ord_bv(a1, b1).then_with(|| ord_bv(a2, b2))
}
(BoolTerm::Not(a1), BoolTerm::Not(b1)) => ord_bool(a1, b1),
(BoolTerm::And(a1, a2), BoolTerm::And(b1, b2))
| (BoolTerm::Or(a1, a2), BoolTerm::Or(b1, b2)) => {
ord_bool(a1, b1).then_with(|| ord_bool(a2, b2))
}
_ => unreachable!("ord_bool: rank-equal variants are exhaustively matched"),
})
}
fn canonical_pair(a: BvTerm, b: BvTerm) -> (Box<BvTerm>, Box<BvTerm>) {
if ord_bv(&a, &b) == Ordering::Greater {
(Box::new(b), Box::new(a))
} else {
(Box::new(a), Box::new(b))
}
}
fn canonicalize_bv(t: &BvTerm) -> BvTerm {
let bin = |a: &BvTerm, b: &BvTerm| (Box::new(canonicalize_bv(a)), Box::new(canonicalize_bv(b)));
let comm = |a: &BvTerm, b: &BvTerm| canonical_pair(canonicalize_bv(a), canonicalize_bv(b));
match t {
BvTerm::Const { .. } | BvTerm::Var { .. } => t.clone(),
BvTerm::Add(a, b) => {
let (a, b) = comm(a, b);
BvTerm::Add(a, b)
}
BvTerm::Mul(a, b) => {
let (a, b) = comm(a, b);
BvTerm::Mul(a, b)
}
BvTerm::And(a, b) => {
let (a, b) = comm(a, b);
BvTerm::And(a, b)
}
BvTerm::Or(a, b) => {
let (a, b) = comm(a, b);
BvTerm::Or(a, b)
}
BvTerm::Xor(a, b) => {
let (a, b) = comm(a, b);
BvTerm::Xor(a, b)
}
BvTerm::Sub(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Sub(a, b)
}
BvTerm::Udiv(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Udiv(a, b)
}
BvTerm::Shl(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Shl(a, b)
}
BvTerm::Lshr(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Lshr(a, b)
}
BvTerm::Ashr(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Ashr(a, b)
}
BvTerm::Rotr(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Rotr(a, b)
}
BvTerm::Concat(a, b) => {
let (a, b) = bin(a, b);
BvTerm::Concat(a, b)
}
BvTerm::Extract { hi, lo, arg } => BvTerm::Extract {
hi: *hi,
lo: *lo,
arg: Box::new(canonicalize_bv(arg)),
},
BvTerm::ZeroExt { by, arg } => BvTerm::ZeroExt {
by: *by,
arg: Box::new(canonicalize_bv(arg)),
},
BvTerm::SignExt { by, arg } => BvTerm::SignExt {
by: *by,
arg: Box::new(canonicalize_bv(arg)),
},
BvTerm::Ite { cond, then_, else_ } => BvTerm::Ite {
cond: Box::new(canonicalize_bool(cond)),
then_: Box::new(canonicalize_bv(then_)),
else_: Box::new(canonicalize_bv(else_)),
},
}
}
fn canonicalize_bool(t: &BoolTerm) -> BoolTerm {
let bin = |a: &BvTerm, b: &BvTerm| (Box::new(canonicalize_bv(a)), Box::new(canonicalize_bv(b)));
let comm = |a: &BvTerm, b: &BvTerm| canonical_pair(canonicalize_bv(a), canonicalize_bv(b));
match t {
BoolTerm::Eq(a, b) => {
let (a, b) = comm(a, b);
BoolTerm::Eq(a, b)
}
BoolTerm::Ne(a, b) => {
let (a, b) = comm(a, b);
BoolTerm::Ne(a, b)
}
BoolTerm::Ult(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Ult(a, b)
}
BoolTerm::Ule(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Ule(a, b)
}
BoolTerm::Ugt(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Ugt(a, b)
}
BoolTerm::Uge(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Uge(a, b)
}
BoolTerm::Slt(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Slt(a, b)
}
BoolTerm::Sle(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Sle(a, b)
}
BoolTerm::Sgt(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Sgt(a, b)
}
BoolTerm::Sge(a, b) => {
let (a, b) = bin(a, b);
BoolTerm::Sge(a, b)
}
BoolTerm::Not(a) => BoolTerm::Not(Box::new(canonicalize_bool(a))),
BoolTerm::And(a, b) => BoolTerm::And(
Box::new(canonicalize_bool(a)),
Box::new(canonicalize_bool(b)),
),
BoolTerm::Or(a, b) => BoolTerm::Or(
Box::new(canonicalize_bool(a)),
Box::new(canonicalize_bool(b)),
),
}
}
impl BV {
#[cfg_attr(not(feature = "z3-solver"), allow(dead_code))]
pub(crate) fn term(&self) -> &BvTerm {
&self.term
}
pub(crate) fn var_name(&self) -> Option<&str> {
match &self.term {
BvTerm::Var { name, .. } => Some(name),
_ => None,
}
}
pub fn new_const(name: impl Into<String>, width: u32) -> Self {
Self {
term: BvTerm::Var {
name: name.into(),
sort: Sort::new(width),
},
width,
}
}
pub fn from_i64(value: i64, width: u32) -> Self {
Self {
term: BvTerm::Const {
value: (value as u64 as u128) & mask(width),
sort: Sort::new(width),
},
width,
}
}
pub fn from_u64(value: u64, width: u32) -> Self {
Self {
term: BvTerm::Const {
value: (value as u128) & mask(width),
sort: Sort::new(width),
},
width,
}
}
pub fn get_size(&self) -> u32 {
self.width
}
fn binop(
&self,
other: impl Borrow<BV>,
f: impl FnOnce(Box<BvTerm>, Box<BvTerm>) -> BvTerm,
) -> BV {
let other = other.borrow();
BV {
term: f(Box::new(self.term.clone()), Box::new(other.term.clone())),
width: self.width,
}
}
fn commutative(
&self,
other: impl Borrow<BV>,
f: impl FnOnce(Box<BvTerm>, Box<BvTerm>) -> BvTerm,
) -> BV {
let other = other.borrow();
let (a, b) = canonical_pair(self.term.clone(), other.term.clone());
BV {
term: f(a, b),
width: self.width,
}
}
pub fn bvadd(&self, other: impl Borrow<BV>) -> BV {
self.commutative(other, BvTerm::Add)
}
pub fn bvsub(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Sub)
}
pub fn bvmul(&self, other: impl Borrow<BV>) -> BV {
self.commutative(other, BvTerm::Mul)
}
pub fn bvudiv(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Udiv)
}
pub fn bvsdiv(&self, other: impl Borrow<BV>) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvsdiv(
self.term.clone(),
other.borrow().term.clone(),
self.width,
)),
width: self.width,
}
}
pub fn bvurem(&self, other: impl Borrow<BV>) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvurem(
self.term.clone(),
other.borrow().term.clone(),
self.width,
)),
width: self.width,
}
}
pub fn bvsrem(&self, other: impl Borrow<BV>) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvsrem(
self.term.clone(),
other.borrow().term.clone(),
self.width,
)),
width: self.width,
}
}
pub fn bvneg(&self) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvneg(self.term.clone(), self.width)),
width: self.width,
}
}
pub fn bvand(&self, other: impl Borrow<BV>) -> BV {
self.commutative(other, BvTerm::And)
}
pub fn bvor(&self, other: impl Borrow<BV>) -> BV {
self.commutative(other, BvTerm::Or)
}
pub fn bvxor(&self, other: impl Borrow<BV>) -> BV {
self.commutative(other, BvTerm::Xor)
}
pub fn bvnot(&self) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvnot(self.term.clone(), self.width)),
width: self.width,
}
}
pub fn bvshl(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Shl)
}
pub fn bvlshr(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Lshr)
}
pub fn bvashr(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Ashr)
}
pub fn bvrotl(&self, other: impl Borrow<BV>) -> BV {
BV {
term: canonicalize_bv(&ordeal::lowering::bvrotl(
self.term.clone(),
other.borrow().term.clone(),
self.width,
)),
width: self.width,
}
}
pub fn bvrotr(&self, other: impl Borrow<BV>) -> BV {
self.binop(other, BvTerm::Rotr)
}
pub fn extract(&self, hi: u32, lo: u32) -> BV {
BV {
term: BvTerm::Extract {
hi,
lo,
arg: Box::new(self.term.clone()),
},
width: hi - lo + 1,
}
}
pub fn concat(&self, other: impl Borrow<BV>) -> BV {
let other = other.borrow();
BV {
term: BvTerm::Concat(Box::new(self.term.clone()), Box::new(other.term.clone())),
width: self.width + other.width,
}
}
pub fn zero_ext(&self, by: u32) -> BV {
BV {
term: BvTerm::ZeroExt {
by,
arg: Box::new(self.term.clone()),
},
width: self.width + by,
}
}
pub fn sign_ext(&self, by: u32) -> BV {
BV {
term: BvTerm::SignExt {
by,
arg: Box::new(self.term.clone()),
},
width: self.width + by,
}
}
fn cmp_op(
&self,
other: impl Borrow<BV>,
f: impl FnOnce(Box<BvTerm>, Box<BvTerm>) -> BoolTerm,
) -> Bool {
Bool {
term: f(
Box::new(self.term.clone()),
Box::new(other.borrow().term.clone()),
),
}
}
pub fn eq(&self, other: impl Borrow<BV>) -> Bool {
let (a, b) = canonical_pair(self.term.clone(), other.borrow().term.clone());
Bool {
term: BoolTerm::Eq(a, b),
}
}
pub fn ne(&self, other: impl Borrow<BV>) -> Bool {
let (a, b) = canonical_pair(self.term.clone(), other.borrow().term.clone());
Bool {
term: BoolTerm::Ne(a, b),
}
}
pub fn bvult(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Ult)
}
pub fn bvule(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Ule)
}
pub fn bvugt(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Ugt)
}
pub fn bvuge(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Uge)
}
pub fn bvslt(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Slt)
}
pub fn bvsle(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Sle)
}
pub fn bvsgt(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Sgt)
}
pub fn bvsge(&self, other: impl Borrow<BV>) -> Bool {
self.cmp_op(other, BoolTerm::Sge)
}
pub fn simplify(&self) -> BV {
self.clone()
}
pub fn as_i64(&self) -> Option<i64> {
self.eval_closed().and_then(|v| i64::try_from(v).ok())
}
pub fn as_u64(&self) -> Option<u64> {
self.eval_closed().and_then(|v| u64::try_from(v).ok())
}
fn eval_closed(&self) -> Option<u128> {
ordeal::eval::eval_bv(&self.term, &ordeal::eval::Env::new()).ok()
}
}
impl Bool {
pub(crate) fn term(&self) -> &BoolTerm {
&self.term
}
pub fn new_const(name: impl Into<String>) -> Self {
let var = BvTerm::Var {
name: name.into(),
sort: Sort::new(1),
};
Self {
term: BoolTerm::Ne(
Box::new(var),
Box::new(BvTerm::Const {
value: 0,
sort: Sort::new(1),
}),
),
}
}
pub fn from_bool(value: bool) -> Self {
Self::literal(value)
}
pub fn eq(&self, other: impl Borrow<Bool>) -> Bool {
let a = self.term.clone();
let b = other.borrow().term.clone();
Bool {
term: BoolTerm::Or(
Box::new(BoolTerm::And(Box::new(a.clone()), Box::new(b.clone()))),
Box::new(BoolTerm::And(
Box::new(BoolTerm::Not(Box::new(a))),
Box::new(BoolTerm::Not(Box::new(b))),
)),
),
}
}
pub fn not(&self) -> Bool {
Bool {
term: BoolTerm::Not(Box::new(self.term.clone())),
}
}
pub fn and(values: &[&Bool]) -> Bool {
Self::fold(values, BoolTerm::And, true)
}
pub fn or(values: &[&Bool]) -> Bool {
Self::fold(values, BoolTerm::Or, false)
}
fn fold(
values: &[&Bool],
f: impl Fn(Box<BoolTerm>, Box<BoolTerm>) -> BoolTerm,
empty: bool,
) -> Bool {
let mut iter = values.iter();
let Some(first) = iter.next() else {
return Self::literal(empty);
};
let mut acc = first.term.clone();
for v in iter {
acc = f(Box::new(acc), Box::new(v.term.clone()));
}
Bool { term: acc }
}
fn literal(value: bool) -> Bool {
let zero = || {
Box::new(BvTerm::Const {
value: 0,
sort: Sort::new(1),
})
};
Bool {
term: if value {
BoolTerm::Eq(zero(), zero())
} else {
BoolTerm::Ne(zero(), zero())
},
}
}
pub fn ite(&self, then_: impl Borrow<BV>, else_: impl Borrow<BV>) -> BV {
let then_ = then_.borrow();
let else_ = else_.borrow();
BV {
term: BvTerm::Ite {
cond: Box::new(self.term.clone()),
then_: Box::new(then_.term.clone()),
else_: Box::new(else_.term.clone()),
},
width: then_.width,
}
}
pub fn simplify(&self) -> Bool {
self.clone()
}
pub fn as_bool(&self) -> Option<bool> {
ordeal::eval::eval_bool(&self.term, &ordeal::eval::Env::new()).ok()
}
}
fn fmt_bv(t: &BvTerm, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match t {
BvTerm::Const { value, sort } => write!(f, "(_ bv{} {})", value, sort.width),
BvTerm::Var { name, .. } => write!(f, "{}", name),
BvTerm::Add(a, b) => fmt_bin(f, "bvadd", a, b),
BvTerm::Sub(a, b) => fmt_bin(f, "bvsub", a, b),
BvTerm::Mul(a, b) => fmt_bin(f, "bvmul", a, b),
BvTerm::Udiv(a, b) => fmt_bin(f, "bvudiv", a, b),
BvTerm::And(a, b) => fmt_bin(f, "bvand", a, b),
BvTerm::Or(a, b) => fmt_bin(f, "bvor", a, b),
BvTerm::Xor(a, b) => fmt_bin(f, "bvxor", a, b),
BvTerm::Shl(a, b) => fmt_bin(f, "bvshl", a, b),
BvTerm::Lshr(a, b) => fmt_bin(f, "bvlshr", a, b),
BvTerm::Ashr(a, b) => fmt_bin(f, "bvashr", a, b),
BvTerm::Rotr(a, b) => fmt_bin(f, "bvrotr", a, b),
BvTerm::Extract { hi, lo, arg } => {
write!(f, "((_ extract {} {}) ", hi, lo)?;
fmt_bv(arg, f)?;
write!(f, ")")
}
BvTerm::Concat(a, b) => fmt_bin(f, "concat", a, b),
BvTerm::ZeroExt { by, arg } => {
write!(f, "((_ zero_extend {}) ", by)?;
fmt_bv(arg, f)?;
write!(f, ")")
}
BvTerm::SignExt { by, arg } => {
write!(f, "((_ sign_extend {}) ", by)?;
fmt_bv(arg, f)?;
write!(f, ")")
}
BvTerm::Ite { cond, then_, else_ } => {
write!(f, "(ite ")?;
fmt_bool(cond, f)?;
write!(f, " ")?;
fmt_bv(then_, f)?;
write!(f, " ")?;
fmt_bv(else_, f)?;
write!(f, ")")
}
}
}
fn fmt_bin(f: &mut fmt::Formatter<'_>, op: &str, a: &BvTerm, b: &BvTerm) -> fmt::Result {
write!(f, "({} ", op)?;
fmt_bv(a, f)?;
write!(f, " ")?;
fmt_bv(b, f)?;
write!(f, ")")
}
fn fmt_bool(t: &BoolTerm, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bin = |f: &mut fmt::Formatter<'_>, op: &str, a: &BvTerm, b: &BvTerm| -> fmt::Result {
write!(f, "({} ", op)?;
fmt_bv(a, f)?;
write!(f, " ")?;
fmt_bv(b, f)?;
write!(f, ")")
};
match t {
BoolTerm::Eq(a, b) => bin(f, "=", a, b),
BoolTerm::Ne(a, b) => bin(f, "distinct", a, b),
BoolTerm::Ult(a, b) => bin(f, "bvult", a, b),
BoolTerm::Ule(a, b) => bin(f, "bvule", a, b),
BoolTerm::Ugt(a, b) => bin(f, "bvugt", a, b),
BoolTerm::Uge(a, b) => bin(f, "bvuge", a, b),
BoolTerm::Slt(a, b) => bin(f, "bvslt", a, b),
BoolTerm::Sle(a, b) => bin(f, "bvsle", a, b),
BoolTerm::Sgt(a, b) => bin(f, "bvsgt", a, b),
BoolTerm::Sge(a, b) => bin(f, "bvsge", a, b),
BoolTerm::Not(a) => {
write!(f, "(not ")?;
fmt_bool(a, f)?;
write!(f, ")")
}
BoolTerm::And(a, b) => {
write!(f, "(and ")?;
fmt_bool(a, f)?;
write!(f, " ")?;
fmt_bool(b, f)?;
write!(f, ")")
}
BoolTerm::Or(a, b) => {
write!(f, "(or ")?;
fmt_bool(a, f)?;
write!(f, " ")?;
fmt_bool(b, f)?;
write!(f, ")")
}
}
}
impl fmt::Display for BV {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_bv(&self.term, f)
}
}
impl fmt::Display for Bool {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_bool(&self.term, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn const_folding_matches_z3_idiom() {
let a = BV::from_i64(40, 32);
let b = BV::from_i64(2, 32);
assert_eq!(a.bvadd(&b).simplify().as_i64(), Some(42));
assert_eq!(a.bvsub(&b).simplify().as_i64(), Some(38));
assert_eq!(BV::from_i64(-1, 32).as_i64(), Some(0xFFFF_FFFF));
}
#[test]
fn commutative_construction_is_canonical() {
let x = BV::new_const("x", 32);
let y = BV::new_const("y", 32);
assert_eq!(x.bvmul(&y).to_string(), y.bvmul(&x).to_string());
assert_eq!(x.bvadd(&y).to_string(), y.bvadd(&x).to_string());
assert_eq!(x.eq(&y).to_string(), y.eq(&x).to_string());
assert_ne!(x.bvsub(&y).to_string(), y.bvsub(&x).to_string());
}
#[test]
fn display_uses_smtlib_mnemonics() {
let x = BV::new_const("x", 32);
let s = x.bvadd(BV::from_i64(1, 32)).to_string();
assert!(s.contains("bvadd"), "{s}");
}
}