use std::collections::HashSet; use std::fmt::Display; use std::rc::Rc; use serde::{Deserialize, Serialize}; use crate::expressions::operator::BinaryOperator; use crate::parsing::expression_parser::parse_expression; #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum Expression { Not(Rc), Binary { left: Rc, operator: BinaryOperator, right: Rc, }, Atomic(String), } impl PartialEq> for Expression { fn eq(&self, other: &Rc) -> bool { self == other.as_ref() } } impl Expression { pub fn is_atomic(&self) -> bool { match self { Expression::Not(expr) => expr.is_atomic(), Expression::Binary { .. } => false, Expression::Atomic(_) => true, } } pub fn get_atomic_values(&self) -> HashSet { match self { Expression::Not(expr) => expr.get_atomic_values(), Expression::Binary { left, right, .. } => { let mut values = left.get_atomic_values(); values.extend(right.get_atomic_values()); values } Expression::Atomic(value) => HashSet::from([value.clone()]), } } //TODO replace with eq_ignore_case and use a macro to select correct eq function pub fn eq(&self, other: &Self, ignore_case: bool) -> bool { match (self, other) { (Expression::Not(left), Expression::Not(right)) => { Expression::eq(left, right, ignore_case) } ( Expression::Binary { left: left_left, operator: left_operator, right: left_right, }, Expression::Binary { left: right_left, operator: right_operator, right: right_right, }, ) => { Expression::eq(left_left, right_left, ignore_case) && left_operator == right_operator && Expression::eq(left_right, right_right, ignore_case) } (Expression::Atomic(left), Expression::Atomic(right)) => { if ignore_case { left.eq_ignore_ascii_case(right) } else { left == right } } _ => false, } } pub fn opposite_eq(&self, other: &Self, ignore_case: bool) -> bool { match (self, other) { (Expression::Not(_), Expression::Not(_)) => false, (Expression::Not(left), right) => left.as_ref().eq(right, ignore_case), (left, Expression::Not(right)) => left.eq(right.as_ref(), ignore_case), _ => false, } } pub fn is_in(&self, other: &Self) -> bool { if let Expression::Binary { left, right, .. } = other { self == left || self == right } else { false } } pub fn is_or(&self) -> bool { matches!( self, Expression::Binary { operator: BinaryOperator::Or, .. } ) } pub fn is_and(&self) -> bool { matches!( self, Expression::Binary { operator: BinaryOperator::And, .. } ) } } impl<'a> TryFrom<&'a str> for Expression { type Error = nom::Err>; fn try_from(value: &'a str) -> Result { parse_expression(value) } } impl TryFrom for Expression { type Error = nom::Err>; fn try_from(value: String) -> Result { Self::try_from(value.as_str()) .map_err(|err| err.map(|err| nom::error::Error::new(err.input.into(), err.code))) } } impl Display for Expression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { return write!(f, "{}", fmt_helper(self, None)); fn fmt_helper(expression: &Expression, parent: Option<&Expression>) -> String { match expression { Expression::Not(expr) if expr.is_atomic() => { format!("¬{}", fmt_helper(expr, Some(expression))) } Expression::Not(expr) => format!("¬({})", fmt_helper(expr, Some(expression))), Expression::Binary { left, operator: BinaryOperator::And, right, } => { format!( "{} ⋀ {}", fmt_helper(left, Some(expression)), fmt_helper(right, Some(expression)) ) } Expression::Binary { left, operator: BinaryOperator::Or, right, } => { if parent.is_none() || matches!( parent, Some( Expression::Not(_) | Expression::Binary { operator: BinaryOperator::Or | BinaryOperator::Implication, .. } ) ) { format!( "{} ⋁ {}", fmt_helper(left, Some(expression)), fmt_helper(right, Some(expression)) ) } else { format!( "({} ⋁ {})", fmt_helper(left, Some(expression)), fmt_helper(right, Some(expression)) ) } } Expression::Binary { left, operator: BinaryOperator::Implication, right, } => { format!( "{} ➔ {}", fmt_helper(left, Some(expression)), fmt_helper(right, Some(expression)) ) } Expression::Atomic(value) => value.clone(), } } } } #[cfg(test)] mod tests { use crate::expressions::expression::Expression; use crate::expressions::helpers::{and, atomic, implies, not, or}; #[test] fn test_eq_ignore_case_atomics() { let expression_lower = atomic("a"); let expression_upper = atomic("A"); assert!(expression_lower.eq(&expression_upper, true)); } #[test] fn test_eq_ignore_case_not() { let expression_lower = not(atomic("a")); let expression_upper = not(atomic("A")); assert!(expression_lower.eq(&expression_upper, true)); } #[test] fn test_eq_ignore_case_and() { let expression_lower = and(atomic("a"), atomic("b")); let expression_upper = and(atomic("A"), atomic("B")); assert!(expression_lower.eq(&expression_upper, true)); } #[test] fn test_eq_ignore_case_equal() { let expression_lower = or(atomic("a"), atomic("b")); let expression_upper = or(atomic("a"), atomic("b")); assert!(expression_lower.eq(&expression_upper, true)); } #[test] fn test_eq_ignore_case_unequal() { let expression_lower = or(atomic("a"), atomic("b")); let expression_upper = or(atomic("a"), atomic("c")); assert!(!expression_lower.eq(&expression_upper, true)); } #[test] fn test_eq_dont_ignore_case() { let expression_lower = or(atomic("a"), atomic("b")); let expression_upper = or(atomic("a"), atomic("B")); assert!(!expression_lower.eq(&expression_upper, false)); } #[test] fn test_expression_a_and_not_b_display() { let expression = and(atomic("a"), not(atomic("b"))); assert_eq!(expression.to_string(), "a ⋀ ¬b"); } #[test] fn test_expression_a_or_b_and_c_display() { let expression = or(atomic("a"), and(atomic("b"), atomic("c"))); assert_eq!(expression.to_string(), "a ⋁ b ⋀ c"); } #[test] fn test_expression_a_or_b() { let expression = or(atomic("a"), atomic("b")); assert_eq!(expression.to_string(), "a ⋁ b"); } #[test] fn test_expression_double_or() { let expression = or(atomic("a"), or(atomic("b"), atomic("c"))); assert_eq!(expression.to_string(), "a ⋁ b ⋁ c"); } #[test] fn test_expression_triple_or() { let expression = or(atomic("a"), or(atomic("b"), or(atomic("c"), atomic("d")))); assert_eq!(expression.to_string(), "a ⋁ b ⋁ c ⋁ d"); } #[test] fn test_expression_nested_parenthesized_or() { let expression = or(atomic("a"), and(atomic("b"), or(atomic("b"), atomic("c")))); assert_eq!(expression.to_string(), "a ⋁ b ⋀ (b ⋁ c)"); } #[test] fn test_expression_c_and_a_or_b_display() { let expression = and(or(atomic("a"), atomic("b")), atomic("c")); assert_eq!(expression.to_string(), "(a ⋁ b) ⋀ c"); } #[test] fn test_expression_a_implies_b_display() { let expression = implies(atomic("a"), atomic("b")); assert_eq!(expression.to_string(), "a ➔ b"); } #[test] fn test_expression_not_a_and_b_display() { let expression = not(and(atomic("a"), atomic("b"))); assert_eq!(expression.to_string(), "¬(a ⋀ b)"); } #[test] fn test_from_str_into_expression_atomic() { let expression: Expression = "a".try_into().unwrap(); assert_eq!(expression, atomic("a")); } #[test] fn test_from_str_into_expression_not() { let expression: Expression = "!a".try_into().unwrap(); assert_eq!(expression, not(atomic("a"))); } #[test] fn test_from_str_into_expression_and() { let expression: Expression = "a & b".try_into().unwrap(); assert_eq!(expression, and(atomic("a"), atomic("b"))); } #[test] fn test_from_str_into_expression_or() { let expression: Expression = "a | b".try_into().unwrap(); assert_eq!(expression, or(atomic("a"), atomic("b"))); } #[test] fn test_from_str_into_expression_implies() { let expression: Expression = "a => b".try_into().unwrap(); assert_eq!(expression, implies(atomic("a"), atomic("b"))); } #[test] fn test_from_str_into_expression_complex() { let expression: Expression = "a & b | c".try_into().unwrap(); assert_eq!(expression, or(and(atomic("a"), atomic("b")), atomic("c"))); } #[test] fn test_from_str_into_expression_complex_parentheses() { let expression: Expression = "a & (b | c)".try_into().unwrap(); assert_eq!(expression, and(atomic("a"), or(atomic("b"), atomic("c")))); } #[test] fn test_from_str_into_expression_very_complex_parentheses() { let expression: Expression = "(a & b) | c => (d & e)".try_into().unwrap(); assert_eq!( expression, implies( or(and(atomic("a"), atomic("b")), atomic("c")), and(atomic("d"), atomic("e")) ) ); } #[test] fn test_from_str_into_expression_empty() { assert!(Expression::try_from("").is_err()); } }