Initial commit

This commit is contained in:
Martin Berg Alstad
2024-06-05 20:41:00 +02:00
commit 096e2105dd
17 changed files with 1212 additions and 0 deletions

View File

@ -0,0 +1,194 @@
use std::fmt::Display;
use crate::expressions::operator::BinaryOperator;
use crate::parsing::expression_parser::parse_expression;
pub trait OppositeEq {
fn opposite_eq(&self, other: &Self) -> bool;
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
Not(Box<Expression>),
Binary(Box<Expression>, BinaryOperator, Box<Expression>),
Atomic(String),
}
impl Expression {
pub fn is_atomic(&self) -> bool {
match self {
Expression::Not(expr) => expr.is_atomic(),
Expression::Binary(_, _, _) => false,
Expression::Atomic(_) => true
}
}
pub fn is_not(&self) -> bool {
matches!(self, Expression::Not(_))
}
pub fn exists(&self, atomic_value: &str) -> bool {
match self {
Expression::Not(expr) => expr.exists(atomic_value),
Expression::Binary(left, _, right) => left.exists(atomic_value) || right.exists(atomic_value),
Expression::Atomic(value) => value == atomic_value,
}
}
}
impl OppositeEq for Expression {
fn opposite_eq(&self, other: &Self) -> bool {
match (self, other) {
(Expression::Not(_), Expression::Not(_)) => false,
(Expression::Not(_), _) => true,
(_, Expression::Not(_)) => true,
_ => false,
}
}
}
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 {
match self {
Expression::Not(expr) if expr.is_atomic() => write!(f, "¬{expr}"),
Expression::Not(expr) => write!(f, "¬({expr})"),
Expression::Binary(left, BinaryOperator::And, right) => {
write!(f, "{left} ⋀ {right}")
}
// TODO do not use parentheses on root level or if several operators are on the same level
Expression::Binary(left, BinaryOperator::Or, right) => {
write!(f, "({left} {right})")
}
Expression::Binary(left, BinaryOperator::Implication, right) => {
write!(f, "{left} ➔ {right}")
}
Expression::Atomic(value) => write!(f, "{value}"),
}
}
}
#[cfg(test)]
mod tests {
use crate::{and, atomic, implies, not, or};
use crate::expressions::expression::Expression;
#[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]
#[ignore]
fn test_expression_a_or_b_and_c_display() {
// TODO
let expression = or!(
atomic!("a"),
and!(
atomic!("b"),
atomic!("c")
));
assert_eq!(expression.to_string(), "a 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());
}
}

View File

@ -0,0 +1,93 @@
#[macro_export]
macro_rules! and {
($left:expr, $right:expr) => {
$crate::binary!($left, $crate::expressions::operator::BinaryOperator::And, $right)
};
}
#[macro_export]
macro_rules! or {
($left:expr, $right:expr) => {
$crate::binary!($left, $crate::expressions::operator::BinaryOperator::Or, $right)
};
}
#[macro_export]
macro_rules! implies {
($left:expr, $right:expr) => {
$crate::binary!($left, $crate::expressions::operator::BinaryOperator::Implication, $right)
};
}
#[macro_export]
macro_rules! binary {
($left:expr, $op:expr, $right:expr) => {
$crate::expressions::expression::Expression::Binary(Box::new($left), $op, Box::new($right))
};
}
#[macro_export]
macro_rules! not {
($value:expr) => {
$crate::expressions::expression::Expression::Not(Box::new($value))
};
}
#[macro_export]
macro_rules! atomic {
($value:expr) => {
$crate::expressions::expression::Expression::Atomic($value.to_string())
};
}
// TODO
#[macro_export]
macro_rules! eval {
($a:literal && $b:literal) => {
$crate::and!($crate::eval!($a), $crate::eval!($b))
};
($a:literal || $b:literal) => {
$crate::or!($crate::eval!($a), $crate::eval!($b))
};
($a:literal => $b:literal) => {
$crate::implies!($crate::eval!($a), $crate::eval!($b))
};
(!$a:expr) => {
$crate::not!($crate::eval!($a))
};
($value:expr) => {
$crate::atomic!($value)
};
}
#[cfg(test)]
mod tests {
use crate::eval;
use crate::expressions::expression::Expression::{Atomic, Binary, Not};
use crate::expressions::operator::BinaryOperator::{And, Implication, Or};
#[test]
fn eval_atomic() {
assert_eq!(eval!("a"), Atomic("a".to_string()));
}
#[test]
fn eval_not() {
assert_eq!(eval!(!"a"), Not(Box::new(Atomic("a".to_string()))));
}
#[test]
fn eval_and() {
assert_eq!(eval!("a" && "b"), Binary(Box::new(Atomic("a".to_string())), And, Box::new(Atomic("b".to_string()))));
}
#[test]
fn eval_or() {
assert_eq!(eval!("a" || "b"), Binary(Box::new(Atomic("a".to_string())), Or, Box::new(Atomic("b".to_string()))));
}
#[test]
fn eval_implies() {
assert_eq!(eval!("a" => "b"), Binary(Box::new(Atomic("a".to_string())), Implication, Box::new(Atomic("b".to_string()))));
}
}

5
src/expressions/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod expression;
pub mod operator;
#[macro_use]
pub mod helpers;
pub mod simplify;

View File

@ -0,0 +1,17 @@
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum BinaryOperator {
Implication,
Or,
And,
}
impl From<BinaryOperator> for &str {
fn from(op: BinaryOperator) -> Self {
match op {
BinaryOperator::Implication => "=>",
BinaryOperator::Or => "|",
BinaryOperator::And => "&",
}
}
}

352
src/expressions/simplify.rs Normal file
View File

@ -0,0 +1,352 @@
use crate::expressions::expression::{Expression, OppositeEq};
use crate::expressions::operator::BinaryOperator;
pub trait Simplify {
fn elimination_of_implication(&self) -> Self;
fn double_negation_elimination(&self) -> Self;
fn de_morgans_laws(&self) -> Self;
fn absorption_law(&self) -> Self;
fn associative_law(&self) -> Self;
fn distribution_law(&self) -> Self;
fn commutative_law(&self) -> Self;
}
impl Simplify for Expression {
/// Eliminate the implication operator from the expression.
/// This is done by replacing `a ➔ b` with `¬a b`.
fn elimination_of_implication(&self) -> Self {
match self {
Expression::Not(expr) => not!(expr.elimination_of_implication()),
Expression::Binary(left, BinaryOperator::Implication, right) => {
let left = left.elimination_of_implication();
let right = right.elimination_of_implication();
or!(not!(left), right)
}
Expression::Binary(left, operator, right) => {
let left = left.elimination_of_implication();
let right = right.elimination_of_implication();
binary!(left, *operator, right)
}
atomic @ Expression::Atomic(_) => atomic.clone(),
}
}
/// Eliminate double negations from the expression.
/// This is done by replacing `¬¬a` with `a`.
/// This function is recursive and will continue to eliminate double negations until none are left.
fn double_negation_elimination(&self) -> Self {
match self {
Expression::Not(expr) => {
if let Expression::Not(inner) = *expr.clone() {
inner.double_negation_elimination()
} else {
not!(expr.double_negation_elimination())
}
}
Expression::Binary(left, operator, right) => {
let left = left.double_negation_elimination();
let right = right.double_negation_elimination();
binary!(left, *operator, right)
}
atomic @ Expression::Atomic(_) => atomic.clone(),
}
}
fn de_morgans_laws(&self) -> Self {
match self {
Expression::Not(expr) => {
match *expr.clone() {
Expression::Binary(left, BinaryOperator::And, right) => {
// TODO unnecessary cloning calls to de_morgans_laws?
let left = not!(left.de_morgans_laws());
let right = not!(right.de_morgans_laws());
or!(left, right).de_morgans_laws()
}
Expression::Binary(left, BinaryOperator::Or, right) => {
let left = not!(left.de_morgans_laws());
let right = not!(right.de_morgans_laws());
and!(left, right).de_morgans_laws()
}
_ => not!(expr.de_morgans_laws()),
}
}
Expression::Binary(left, operator, right) => {
let left = left.de_morgans_laws();
let right = right.de_morgans_laws();
binary!(left, *operator, right)
}
atomic @ Expression::Atomic(_) => atomic.clone(),
}
}
// TODO deduplicate code
fn absorption_law(&self) -> Self {
match self {
Expression::Binary(left, BinaryOperator::And, right) => {
let (left_ref, right_ref) = (left.as_ref(), right.as_ref());
match (left_ref, right_ref) {
(_, Expression::Binary(right_left, BinaryOperator::Or, right_right)) => {
if left_ref == right_left.as_ref() || left_ref == right_right.as_ref() {
return left.absorption_law();
} else if right_left.is_atomic() && right_right.is_atomic() && left.opposite_eq(right_left) {
if left.opposite_eq(right_left) {
return and!(left.absorption_law(), right_left.absorption_law());
} else if left.opposite_eq(right_right) {
return and!(left.absorption_law(), right_right.absorption_law());
}
}
and!(left.absorption_law(), right.absorption_law())
}
(Expression::Binary(left_left, BinaryOperator::Or, left_right), _) => {
if right_ref == left_left.as_ref() || right_ref == left_right.as_ref() {
return right.absorption_law();
} else if left_left.is_atomic() && left_right.is_atomic() && right.opposite_eq(left_left) {
if right.opposite_eq(left_left) {
return and!(left_right.absorption_law(), right.absorption_law());
} else if right.opposite_eq(left_right) {
return and!(left_left.absorption_law(), right.absorption_law());
}
}
and!(left.absorption_law(), right.absorption_law())
}
(left, right) => and!(left.absorption_law(), right.absorption_law())
}
}
Expression::Binary(left, BinaryOperator::Or, right) => {
let (left_ref, right_ref) = (left.as_ref(), right.as_ref());
match (left_ref, right_ref) {
(_, Expression::Binary(right_left, BinaryOperator::And, right_right)) => {
if left_ref == right_left.as_ref() || left_ref == right_right.as_ref() {
return left.absorption_law();
} else if right_left.is_atomic() && right_right.is_atomic() && left.opposite_eq(right_left) {
if left.opposite_eq(right_left) {
return or!(left.absorption_law(), right_left.absorption_law());
} else if left.opposite_eq(right_right) {
return or!(left.absorption_law(), right_right.absorption_law());
}
}
or!(left.absorption_law(), right.absorption_law())
}
(Expression::Binary(left_left, BinaryOperator::And, left_right), _) => {
if right_ref == left_left.as_ref() || right_ref == left_right.as_ref() {
return right.absorption_law();
} else if left_left.is_atomic() && left_right.is_atomic() && right.opposite_eq(left_left) {
if right.opposite_eq(left_left) {
return or!(left_right.absorption_law(), right.absorption_law());
} else if right.opposite_eq(left_right) {
return or!(left_left.absorption_law(), right.absorption_law());
}
}
or!(left.absorption_law(), right.absorption_law())
}
(left, right) => or!(left.absorption_law(), right.absorption_law())
}
}
Expression::Binary(left, operator, right) => {
let left = left.absorption_law();
let right = right.absorption_law();
binary!(left, *operator, right)
}
Expression::Not(expr) => not!(expr.absorption_law()),
atomic => atomic.clone(),
}
}
fn associative_law(&self) -> Self {
todo!("? | Associative law: (a ⋀ b) ⋀ c == a ⋀ (b ⋀ c) and (a b) c == a (b c)")
}
// TODO deduplicate code
fn distribution_law(&self) -> Self {
match self {
Expression::Binary(left, BinaryOperator::And, right) => {
match (left.as_ref(), right.as_ref()) {
(Expression::Atomic(_), Expression::Binary(right_left, BinaryOperator::Or, right_right)) => {
let right_left = right_left.distribution_law();
let right_right = right_right.distribution_law();
or!(and!(*left.clone(), right_left), and!(*left.clone(), right_right))
}
(Expression::Binary(left_left, BinaryOperator::Or, left_right), Expression::Atomic(_)) => {
let left_left = left_left.distribution_law();
let left_right = left_right.distribution_law();
or!(and!(left_left, *right.clone()), and!(left_right, *right.clone()))
}
(left, right) => and!(left.distribution_law(), right.distribution_law())
}
}
Expression::Binary(left, BinaryOperator::Or, right) => {
match (left.as_ref(), right.as_ref()) {
(Expression::Atomic(_), Expression::Binary(right_left, BinaryOperator::And, right_right)) => {
let right_left = right_left.distribution_law();
let right_right = right_right.distribution_law();
and!(or!(*left.clone(), right_left), or!(*left.clone(), right_right))
}
(Expression::Binary(left_left, BinaryOperator::And, left_right), Expression::Atomic(_)) => {
let left_left = left_left.distribution_law();
let left_right = left_right.distribution_law();
and!(or!(left_left, *right.clone()), or!(left_right, *right.clone()))
}
(left, right) => or!(left.distribution_law(), right.distribution_law())
}
}
Expression::Binary(left, operator, right) => {
let left = left.distribution_law();
let right = right.distribution_law();
binary!(left, *operator, right)
}
Expression::Not(expr) => expr.distribution_law(),
atomic => atomic.clone(),
}
}
fn commutative_law(&self) -> Self {
todo!("? | Order of operands does not matter in AND and OR operations.")
}
}
#[cfg(test)]
mod tests {
use crate::expressions::simplify::Simplify;
#[test]
fn test_elimination_of_implication() {
let expression = eval!("a" => "b").elimination_of_implication();
assert_eq!(expression, or!(not!(atomic!("a")), atomic!("b")));
}
#[test]
fn test_elimination_of_implication_nested() {
let expression = implies!(atomic!("a"), implies!(atomic!("b"), atomic!("c"))).elimination_of_implication();
assert_eq!(expression, or!(not!(atomic!("a")), or!(not!(atomic!("b")), atomic!("c"))));
}
#[test]
fn test_elimination_of_implication_none() {
let expression = eval!("a" && "b").elimination_of_implication();
assert_eq!(expression, eval!("a" && "b"));
}
#[test]
fn test_elimination_of_implication_nested_none() {
let expression = or!(atomic!("a"), and!(atomic!("b"), atomic!("c"))).elimination_of_implication();
assert_eq!(expression, or!(atomic!("a"), and!(atomic!("b"), atomic!("c"))));
}
#[test]
fn test_double_negation_elimination() {
let expression = not!(not!(atomic!("a"))).double_negation_elimination();
assert_eq!(expression, atomic!("a"));
}
#[test]
fn test_triple_negation_elimination() {
let expression = not!(not!(not!(atomic!("a")))).double_negation_elimination();
assert_eq!(expression, not!(atomic!("a")));
}
#[test]
fn test_five_negation_elimination() {
let expression = not!(not!(not!(not!(not!(atomic!("a")))))).double_negation_elimination();
assert_eq!(expression, not!(atomic!("a")));
}
#[test]
fn test_no_negation_elimination() {
let expression = atomic!("a").double_negation_elimination();
assert_eq!(expression, atomic!("a"));
}
#[test]
fn test_double_negation_nested_elimination() {
let expression = and!(or!(not!(eval!(!"a")), eval!("b")), not!(eval!(!"c"))).double_negation_elimination();
assert_eq!(expression, and!(or!(atomic!("a"), atomic!("b")), atomic!("c")));
}
#[test]
fn test_de_morgans_laws_and() {
let expression = not!(eval!("a" && "b")).de_morgans_laws();
assert_eq!(expression, or!(not!(atomic!("a")), not!(atomic!("b"))));
}
#[test]
fn test_de_morgans_laws_or() {
let expression = not!(eval!("a" || "b")).de_morgans_laws();
assert_eq!(expression, and!(not!(atomic!("a")), not!(atomic!("b"))));
}
#[test]
fn test_de_morgans_laws_nested_or() {
let expression = not!(or!(eval!("a" && "b"), atomic!("c"))).de_morgans_laws(); // ¬(a ⋀ b c)
assert_eq!(expression, and!(or!(eval!(!"a"), eval!(!"b")), eval!(!"c"))); // ¬(a ⋀ b) ⋀ ¬c == (¬a ¬b) ⋀ ¬c
}
#[test]
fn test_de_morgans_laws_nested_and() {
let expression = not!(and!(eval!("a" || "b"), atomic!("c"))).de_morgans_laws(); // ¬(a b ⋀ c)
assert_eq!(expression, or!(and!(eval!(!"a"), eval!(!"b")), eval!(!"c"))); // ¬(a b) ⋀ ¬c == (¬a ⋀ ¬b) ¬c
}
#[test]
fn test_de_morgans_laws_nested_and_or() {
let expression = not!(and!(eval!("a" || "b"), or!(atomic!("c"), atomic!("d")))).de_morgans_laws(); // ¬(a b ⋀ c d)
assert_eq!(expression, or!(and!(eval!(!"a"), eval!(!"b")), and!(eval!(!"c"), eval!(!"d")))); // ¬(a b) ⋀ ¬(c d) == (¬a ⋀ ¬b) (¬c ⋀ ¬d)
}
#[test]
fn test_absorption_law_and() {
let expression = and!(atomic!("a"), eval!("a" || "b")).absorption_law();
assert_eq!(expression, atomic!("a"));
}
#[test]
fn test_absorption_law_or() {
let expression = or!(atomic!("a"), eval!("a" && "b")).absorption_law();
assert_eq!(expression, atomic!("a"));
}
#[test]
fn test_absorption_law_nested_and() {
let expression = and!(atomic!("a"), or!(atomic!("a"), atomic!("b"))).absorption_law();
assert_eq!(expression, atomic!("a"));
}
// !A & B | A <=> B | A
#[test]
fn test_absorption_law_not() {
let expression = or!(and!(not!(atomic!("a")), atomic!("b")), atomic!("a")).absorption_law();
assert_eq!(expression, or!(atomic!("b"), atomic!("a")));
}
// A & B | !A <=> B | !A
#[test]
fn test_absorption_law_not_reversed() {
let expression = or!(and!(atomic!("a"), atomic!("b")), not!(atomic!("a"))).absorption_law();
assert_eq!(expression, or!(atomic!("b"), not!(atomic!("a"))));
}
// !A & B | !A <=> !A
#[test]
fn test_absorption_law_double_not() {
let expression = or!(and!(not!(atomic!("a")), atomic!("b")), not!(atomic!("a"))).absorption_law();
assert_eq!(expression, not!(atomic!("a")));
}
// (A | B) & !A <=> B & !A
#[test]
fn test_in_parenthesis() {
let expression = and!(or!(atomic!("a"), atomic!("b")), not!(atomic!("a"))).absorption_law();
assert_eq!(expression, and!(atomic!("b"), not!(atomic!("a"))));
}
#[test]
fn test_distributive_law_and() {
let expression = and!(atomic!("a"), or!(atomic!("b"), atomic!("c"))).distribution_law();
assert_eq!(expression, or!(and!(atomic!("a"), atomic!("b")), and!(atomic!("a"), atomic!("c"))));
}
#[test]
fn test_distributive_law_or() {
let expression = or!(atomic!("a"), and!(atomic!("b"), atomic!("c"))).distribution_law();
assert_eq!(expression, and!(or!(atomic!("a"), atomic!("b")), or!(atomic!("a"), atomic!("c"))));
}
}