use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Expr {
Const(crate::value::Ternary),
Var(String),
Not(Box<Expr>),
And(Box<Expr>, Box<Expr>),
Or(Box<Expr>, Box<Expr>),
Xor(Box<Expr>, Box<Expr>),
Implies(Box<Expr>, Box<Expr>),
Equiv(Box<Expr>, Box<Expr>),
}
impl Expr {
pub fn and(a: Expr, b: Expr) -> Self {
Self::And(Box::new(a), Box::new(b))
}
pub fn or(a: Expr, b: Expr) -> Self {
Self::Or(Box::new(a), Box::new(b))
}
pub fn logical_not(a: Expr) -> Self {
Self::Not(Box::new(a))
}
pub fn xor(a: Expr, b: Expr) -> Self {
Self::Xor(Box::new(a), Box::new(b))
}
pub fn implies(a: Expr, b: Expr) -> Self {
Self::Implies(Box::new(a), Box::new(b))
}
pub fn equiv(a: Expr, b: Expr) -> Self {
Self::Equiv(Box::new(a), Box::new(b))
}
pub const fn is_unknown_const(&self) -> bool {
matches!(self, Self::Const(crate::value::Ternary::Unknown))
}
pub fn variables(&self) -> Vec<String> {
let mut out = Vec::new();
let mut seen = std::collections::HashSet::new();
self.collect_vars(&mut out, &mut seen);
out
}
fn collect_vars(&self, out: &mut Vec<String>, seen: &mut std::collections::HashSet<String>) {
match self {
Self::Const(_) => {}
Self::Var(name) => {
if seen.insert(name.clone()) {
out.push(name.clone());
}
}
Self::Not(a) => a.collect_vars(out, seen),
Self::And(a, b) | Self::Or(a, b) | Self::Xor(a, b) => {
a.collect_vars(out, seen);
b.collect_vars(out, seen);
}
Self::Implies(a, b) | Self::Equiv(a, b) => {
a.collect_vars(out, seen);
b.collect_vars(out, seen);
}
}
}
pub fn node_count(&self) -> usize {
match self {
Self::Const(_) | Self::Var(_) => 1,
Self::Not(a) => 1 + a.node_count(),
Self::And(a, b)
| Self::Or(a, b)
| Self::Xor(a, b)
| Self::Implies(a, b)
| Self::Equiv(a, b) => 1 + a.node_count() + b.node_count(),
}
}
pub fn depth(&self) -> usize {
match self {
Self::Const(_) | Self::Var(_) => 1,
Self::Not(a) => 1 + a.depth(),
Self::And(a, b)
| Self::Or(a, b)
| Self::Xor(a, b)
| Self::Implies(a, b)
| Self::Equiv(a, b) => 1 + a.depth().max(b.depth()),
}
}
pub fn to_infix(&self) -> String {
match self {
Self::Const(v) => v.to_string(),
Self::Var(name) => name.clone(),
Self::Not(a) => format!("¬({})", a.to_infix()),
Self::And(a, b) => format!("({} ∧ {})", a.to_infix(), b.to_infix()),
Self::Or(a, b) => format!("({} ∨ {})", a.to_infix(), b.to_infix()),
Self::Xor(a, b) => format!("({} ⊕ {})", a.to_infix(), b.to_infix()),
Self::Implies(a, b) => format!("({} ⇒ {})", a.to_infix(), b.to_infix()),
Self::Equiv(a, b) => format!("({} ≡ {})", a.to_infix(), b.to_infix()),
}
}
}
impl std::fmt::Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_infix())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Ternary;
#[test]
fn collects_variables_in_order() {
let e = Expr::and(
Expr::Var("a".into()),
Expr::or(Expr::Var("b".into()), Expr::Var("a".into())),
);
assert_eq!(e.variables(), vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn node_count_and_depth() {
let e = Expr::and(Expr::Var("a".into()), Expr::Var("b".into()));
assert_eq!(e.node_count(), 3);
assert_eq!(e.depth(), 2);
}
#[test]
fn infix_rendering() {
let e = Expr::logical_not(Expr::Var("a".into()));
assert_eq!(e.to_infix(), "¬(a)");
let e2 = Expr::equiv(Expr::Const(Ternary::True), Expr::Var("x".into()));
assert_eq!(e2.to_infix(), "(1 ≡ x)");
}
}