use crate::{expr::Expr, varint::make_mask};
pub(crate) fn prettify(e: Expr, n: u8) -> Expr {
prettify_masked(e, make_mask(n))
}
fn prettify_masked(e: Expr, mask: u64) -> Expr {
let e = e.map(|child| prettify_masked(child, mask));
as_binary_bitwise(&e, mask)
.or_else(|| as_not(&e, mask))
.unwrap_or(e)
}
fn peel(e: &Expr) -> &Expr {
match e {
Expr::And(inner)
| Expr::Or(inner)
| Expr::Xor(inner)
| Expr::Add(inner)
| Expr::Mul(inner)
if inner.len() == 1 =>
{
peel(&inner[0])
}
_ => e,
}
}
fn as_binary_bitwise(e: &Expr, mask: u64) -> Option<Expr> {
let Expr::Add(terms) = e else { return None };
if terms.len() != 3 {
return None;
}
for (i, term) in terms.iter().enumerate() {
let Expr::Scale(coeff, inner) = term else {
continue;
};
let Expr::And(operands) = inner.as_ref() else {
continue;
};
let [a, b] = &operands[..] else { continue };
let (a, b) = (peel(a), peel(b));
let rest: Vec<&Expr> = terms
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, t)| peel(t))
.collect();
let matches_operands = (rest[0] == a && rest[1] == b) || (rest[0] == b && rest[1] == a);
if !matches_operands {
continue;
}
let minus_one = mask;
let minus_two = mask & mask.wrapping_sub(1);
let operands = vec![a.clone(), b.clone()];
return match coeff & mask {
c if c == minus_one => Some(Expr::Or(operands)),
c if c == minus_two => Some(Expr::Xor(operands)),
_ => None,
};
}
None
}
fn as_not(e: &Expr, mask: u64) -> Option<Expr> {
let Expr::Add(terms) = e else { return None };
if terms.len() != 2 {
return None;
}
for (i, term) in terms.iter().enumerate() {
let Expr::Const(c) = peel(term) else { continue };
if c & mask != mask {
continue;
}
let Expr::Scale(coeff, inner) = peel(&terms[1 - i]) else {
continue;
};
if coeff & mask == mask {
return Some(!peel(inner).clone());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::simplify::simplify_mba;
fn v(i: usize) -> Expr {
Expr::Var(i.into())
}
fn term(e: Expr) -> Expr {
Expr::And(vec![e])
}
#[test]
fn folds_the_three_exact_forms() {
let or = Expr::Add(vec![term(v(0)), term(v(1)), u64::MAX * (v(0) & v(1))]);
assert_eq!(prettify(or, 32), v(0) | v(1));
let xor = Expr::Add(vec![term(v(0)), term(v(1)), (u64::MAX - 1) * (v(0) & v(1))]);
assert_eq!(prettify(xor, 32), v(0) ^ v(1));
let not = Expr::Add(vec![Expr::Const(u64::MAX), u64::MAX * v(0)]);
assert_eq!(prettify(not, 32), !v(0));
}
#[test]
fn folds_on_narrow_widths() {
let or = Expr::Add(vec![term(v(0)), term(v(1)), 0xff * (v(0) & v(1))]);
assert_eq!(prettify(or, 8), v(0) | v(1));
let xor = Expr::Add(vec![term(v(0)), term(v(1)), 0xfe * (v(0) & v(1))]);
assert_eq!(prettify(xor, 8), v(0) ^ v(1));
let unchanged = Expr::Add(vec![term(v(0)), term(v(1)), 0xff * (v(0) & v(1))]);
assert_eq!(prettify(unchanged.clone(), 32), unchanged);
}
#[test]
fn leaves_a_sum_that_only_contains_the_pattern() {
let e = Expr::Add(vec![
term(v(0)),
term(v(1)),
u64::MAX * (v(0) & v(1)),
term(v(2)),
]);
assert_eq!(prettify(e.clone(), 32), e);
}
#[test]
fn folds_a_nested_node() {
let or = Expr::Add(vec![term(v(0)), term(v(1)), u64::MAX * (v(0) & v(1))]);
let e = Expr::Mul(vec![or, v(2)]);
assert_eq!(prettify(e, 32), Expr::Mul(vec![v(0) | v(1), v(2)]));
}
#[test]
fn leaves_other_coefficients_alone() {
let e = Expr::Add(vec![term(v(0)), term(v(1)), (u64::MAX - 2) * (v(0) & v(1))]);
assert_eq!(prettify(e.clone(), 32), e);
}
#[cfg(feature = "parse")]
#[test]
fn preserves_semantics_through_the_solver() {
for src in ["v0 | v1", "v0 ^ v1", "~v0", "~(v0 & v1)"] {
let e = crate::parser::parse_expr(src).unwrap();
let solved = simplify_mba(e.clone(), 32).unwrap();
assert!(
e.sem_equal(&solved, 32, 1000).is_ok(),
"{src} became {solved}"
);
}
}
}