strong-kleene 0.1.1

Another implementation of Strong Kleene logic in Rust.
Documentation
use core::{
    error::Error,
    fmt::Display,
    ops::{BitAnd, BitOr, BitXor, Not},
};

/// Three-valued logic value for strong Kleene semantics.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Trit {
    /// Logical false.
    False = 0,
    /// Logical true.
    True = 1,
    /// Logical value is currently unknown.
    Unknown = 2,
}

impl Trit {
    /// Returns `true` if this value is [`Trit::True`].
    pub fn is_true(&self) -> bool {
        matches!(self, Trit::True)
    }

    /// Returns `true` if this value is [`Trit::False`].
    pub fn is_false(&self) -> bool {
        matches!(self, Trit::False)
    }

    /// Returns `true` if this value is [`Trit::Unknown`].
    pub fn is_unknown(&self) -> bool {
        matches!(self, Trit::Unknown)
    }
}

impl Display for Trit {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Trit::True => write!(f, "true"),
            Trit::Unknown => write!(f, "unknown"),
            Trit::False => write!(f, "false"),
        }
    }
}

impl From<bool> for Trit {
    fn from(value: bool) -> Self {
        if value { Trit::True } else { Trit::False }
    }
}

impl TryFrom<Trit> for bool {
    type Error = UnknownToBoolError;

    fn try_from(value: Trit) -> Result<Self, Self::Error> {
        match value {
            Trit::True => Ok(true),
            Trit::False => Ok(false),
            Trit::Unknown => Err(UnknownToBoolError),
        }
    }
}

/// Error returned when converting [`Trit::Unknown`] into `bool`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownToBoolError;

impl Display for UnknownToBoolError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "Cannot convert Trit::Unknown to bool; handle Unknown explicitly!"
        )
    }
}

impl Error for UnknownToBoolError {}

impl Not for Trit {
    type Output = Self;

    fn not(self) -> Self::Output {
        match self {
            Trit::True => Trit::False,
            Trit::Unknown => Trit::Unknown,
            Trit::False => Trit::True,
        }
    }
}

impl BitOr for Trit {
    type Output = Self;

    fn bitor(self, other: Trit) -> Self::Output {
        match (self, other) {
            (Trit::True, _) | (_, Trit::True) => Trit::True,
            (Trit::False, Trit::False) => Trit::False,
            _ => Trit::Unknown,
        }
    }
}

impl BitAnd for Trit {
    type Output = Self;

    fn bitand(self, other: Trit) -> Self::Output {
        match (self, other) {
            (Trit::False, _) | (_, Trit::False) => Trit::False,
            (Trit::True, Trit::True) => Trit::True,
            _ => Trit::Unknown,
        }
    }
}

impl BitXor for Trit {
    type Output = Self;

    fn bitxor(self, other: Trit) -> Self::Output {
        match (self, other) {
            (Trit::Unknown, _) | (_, Trit::Unknown) => Trit::Unknown,
            (Trit::True, Trit::True) | (Trit::False, Trit::False) => Trit::False,
            _ => Trit::True,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    #[rstest]
    #[case(true, Trit::True)]
    #[case(false, Trit::False)]
    fn test_from_bool(#[case] input: bool, #[case] expected: Trit) {
        assert_eq!(Trit::from(input), expected);
    }

    #[rstest]
    #[case(Trit::True, Ok(true))]
    #[case(Trit::False, Ok(false))]
    #[case(Trit::Unknown, Err(UnknownToBoolError))]
    fn from_trit_to_bool(#[case] input: Trit, #[case] expected: Result<bool, UnknownToBoolError>) {
        assert_eq!(bool::try_from(input), expected);
    }

    #[rstest]
    #[case(Trit::True, Trit::False)]
    #[case(Trit::Unknown, Trit::Unknown)]
    #[case(Trit::False, Trit::True)]
    fn test_not(#[case] input: Trit, #[case] expected: Trit) {
        assert_eq!(!input, expected);
    }

    #[rstest]
    #[case(Trit::True, Trit::True, Trit::True)]
    #[case(Trit::True, Trit::Unknown, Trit::True)]
    #[case(Trit::True, Trit::False, Trit::True)]
    #[case(Trit::Unknown, Trit::True, Trit::True)]
    #[case(Trit::Unknown, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::Unknown, Trit::False, Trit::Unknown)]
    #[case(Trit::False, Trit::True, Trit::True)]
    #[case(Trit::False, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::False, Trit::False, Trit::False)]
    fn test_or_truth_table(#[case] a: Trit, #[case] b: Trit, #[case] expected: Trit) {
        assert_eq!(a | b, expected);
    }

    #[rstest]
    fn test_or_commutative(
        #[values(Trit::True, Trit::Unknown, Trit::False)] a: Trit,
        #[values(Trit::True, Trit::Unknown, Trit::False)] b: Trit,
    ) {
        assert_eq!(a | b, b | a);
    }

    #[rstest]
    fn test_or_identity_false(#[values(Trit::True, Trit::Unknown, Trit::False)] a: Trit) {
        assert_eq!(a | Trit::False, a);
    }

    #[rstest]
    fn test_or_domination_true(#[values(Trit::True, Trit::Unknown, Trit::False)] a: Trit) {
        assert_eq!(a | Trit::True, Trit::True);
    }

    #[rstest]
    fn test_or_idempotent(#[values(Trit::True, Trit::Unknown, Trit::False)] a: Trit) {
        assert_eq!(a | a, a);
    }

    #[rstest]
    #[case(Trit::True, Trit::True, Trit::True)]
    #[case(Trit::True, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::True, Trit::False, Trit::False)]
    #[case(Trit::Unknown, Trit::True, Trit::Unknown)]
    #[case(Trit::Unknown, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::Unknown, Trit::False, Trit::False)]
    #[case(Trit::False, Trit::True, Trit::False)]
    #[case(Trit::False, Trit::Unknown, Trit::False)]
    #[case(Trit::False, Trit::False, Trit::False)]
    fn and_truth_table(#[case] a: Trit, #[case] b: Trit, #[case] expected: Trit) {
        assert_eq!(a & b, expected);
    }

    #[rstest]
    #[case(Trit::True, Trit::True, Trit::False)]
    #[case(Trit::True, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::True, Trit::False, Trit::True)]
    #[case(Trit::Unknown, Trit::True, Trit::Unknown)]
    #[case(Trit::Unknown, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::Unknown, Trit::False, Trit::Unknown)]
    #[case(Trit::False, Trit::True, Trit::True)]
    #[case(Trit::False, Trit::Unknown, Trit::Unknown)]
    #[case(Trit::False, Trit::False, Trit::False)]
    fn xor_truth_table(#[case] a: Trit, #[case] b: Trit, #[case] expected: Trit) {
        assert_eq!(a ^ b, expected);
    }
}