Martin Berg Alstad 3dad5754f9 fmt and todo
2024-07-18 17:45:57 +02:00

363 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Expression>),
Binary {
left: Rc<Expression>,
operator: BinaryOperator,
right: Rc<Expression>,
},
Atomic(String),
}
impl PartialEq<Rc<Expression>> for Expression {
fn eq(&self, other: &Rc<Expression>) -> 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<String> {
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<nom::error::Error<&'a str>>;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
parse_expression(value)
}
}
impl TryFrom<String> for Expression {
type Error = nom::Err<nom::error::Error<String>>;
fn try_from(value: String) -> Result<Self, Self::Error> {
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());
}
}