openpql-range-parser 0.1.0

Poker Range Notation Parser
Documentation
use super::{Array, Card, Error, Expr, Idx, parse_expr};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Checker<
    const N: usize = 2,
    const B: bool = false,
    const SD: bool = false,
> where
    [Idx; N]: Array<Item = Idx>,
{
    expr: Expr<N, B>,
}

impl<const N: usize, const B: bool, const SD: bool> Checker<N, B, SD>
where
    [Idx; N]: Array<Item = Idx>,
{
    pub fn from_src(src: &str) -> Result<Self, Error> {
        Ok(Self {
            expr: parse_expr(SD, src).and_then(|expr| Expr::try_from(*expr))?,
        })
    }

    #[inline]
    pub fn is_satisfied(&self, cs: &[Card]) -> bool {
        self.expr.is_satisfied(cs)
    }
}

impl<const N: usize, const B: bool, const SD: bool> Default
    for Checker<N, B, SD>
where
    [Idx; N]: Array<Item = Idx>,
{
    fn default() -> Self {
        Self::from_src("*").unwrap()
    }
}

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
    use super::*;
    use crate::*;

    fn assert_checker<const N: usize, const B: bool>(
        s: &str,
        hands_in: &[&str],
        hands_not_in: &[&str],
    ) where
        [Idx; N]: Array<Item = Idx>,
    {
        let checker = Checker::<N, B, false>::from_src(s).unwrap();

        for hand in hands_in {
            assert!(
                checker.is_satisfied(&cards![hand]),
                "unexpected: {hand} not in {s}"
            );
        }

        for hand in hands_not_in {
            assert!(
                !checker.is_satisfied(&cards![hand]),
                "unexpected: {hand} in {s}"
            );
        }
    }

    #[test]
    fn test_default() {
        assert_eq!(
            Checker::default(),
            Checker::<4, false, false>::from_src("*").unwrap()
        );
    }

    #[test]
    fn test_card() {
        assert_checker::<4, false>("AwRsOyKd", &["AhJsTcKd"], &[]);
    }

    #[test]
    fn test_rank_const() {
        assert_checker::<2, false>("AK", &["As", "Ks", "As Ks"], &["As 2s"]);
    }

    #[test]
    fn test_rank_var() {
        assert_checker::<2, false>("RR", &["As Ah", "Ks Kh"], &["As Ks"]);
        assert_checker::<2, false>("RO", &["As Ks"], &["As Ah", "Ks Kh"]);
        assert_checker::<4, false>("AKQB", &["As Ks Qs 2s"], &["As Ks Qs Qh"]);
        assert_checker::<4, false>("AKBB", &["As Ks Qs Qh"], &["As Ks Qs Jh"]);
        assert_checker::<4, false>("RRRO", &["As Ah Ad Kc"], &["As Ah Ad Ac"]);
        assert_checker::<4, false>(
            "[A,K][4-]2sB",
            &["As 3s 2s Ah", "As 3s 2s 3h"],
            &[],
        );
    }

    #[test]
    fn test_suit_const() {
        assert_checker::<2, false>("sh", &["As", "Kh", "Ah Ks"], &["As 2s"]);
    }

    #[test]
    fn test_suit_var() {
        assert_checker::<2, false>("sw", &["As Ah"], &["As Ks"]);
        assert_checker::<2, false>("xx", &["As Ks", "Ah Kh"], &["As Kh"]);
        assert_checker::<2, false>("xy", &["As Kh"], &["As Ks", "Ah Kh"]);
        assert_checker::<4, false>("ssww", &["As Ks Qh Jh"], &["As Ks Qh Jd"]);
        assert_checker::<4, false>("xxxy", &["As Ks Qs Jh"], &["As Ks Qs Js"]);
        assert_checker::<4, false>(
            "[h][4s-]2dw",
            &["Ah 3s 2d Ts", "Ah 3s 2d Th", "Ah 3s 2d Tc"],
            &["Ah 3s 2d Td"],
        );
    }

    #[test]
    fn test_var() {
        assert_checker::<2, false>("AxRs", &["Ah Ks"], &["As Kh"]);
        assert_checker::<2, false>("RxOy", &["Ah Ks"], &["As Ah", "As Ks"]);
        assert_checker::<5, true>(
            "2xRsOyzN",
            &["2h 3s 4d 5c 6s"],
            &["2h 3s 4h 5c 6s"],
        );
    }

    #[test]
    fn test_span() {
        assert_checker::<2, false>("AKs-", &["As Ks", "3h"], &["As Kh"]);
        assert_checker::<2, false>("22+", &["As Ah"], &["As Kh"]);
        assert_checker::<4, false>(
            "AKQT-",
            &["As Ks", "Qs Th", "3h"],
            &["2s 3s 4s"],
        );
        assert_checker::<2, false>("AK-JT", &["Qs Jh"], &["Ts 9h"]);
    }

    #[test]
    fn test_list() {
        assert_checker::<2, false>(
            "[2c,A,s]Td",
            &["Td 2c", "Td Ah", "Td Ks"],
            &["Td 2d"],
        );
        assert_checker::<4, false>(
            "[2c,A,s]Td9d8d",
            &["Td9d8d 2c", "Td9d8d Ah", "Td9d8d Ks"],
            &["Td9d8d 2d"],
        );
        assert_checker::<4, false>(
            "[s][h][d][c]",
            &["2s 2h 3d 3c"],
            &["2s 2h 3c 3c"],
        );
    }

    #[test]
    fn test_board() {
        assert_checker::<5, true>(
            "AKQJ[T,3s]",
            &["As Ks Qs Js Ts", "Ks Qs As Js Ts", "As Ks Qs Js 3s"],
            &["As Ks Qs Ts Js", "Ts Ks Qs Js As", "As Ks Qs Js 3h"],
        );
        assert_checker::<5, true>(
            "AA,JJ",
            &["Js Jh 2d 2c 3s", "Js 2h Jd 2c 3s"],
            &["Js 2h 2s Jc Jd"],
        );
        assert_checker::<5, true>(
            "222[2]s",
            &["2s2h2d2c 3s"],
            &["2s2h2d2c 3h"],
        );
    }

    #[quickcheck]
    fn test_any(cards: CardN<5>) -> TestResult {
        fn to_str(cs: &[Card]) -> String {
            cs.iter().map(ToString::to_string).join("")
        }

        assert_checker::<2, false>("*", &[&to_str(&cards[..2])], &[]);
        assert_checker::<4, false>("*", &[&to_str(&cards[..4])], &[]);
        assert_checker::<5, true>("*", &[&to_str(&cards[..])], &[]);

        TestResult::passed()
    }

    #[test]
    fn test_not() {
        assert_checker::<2, false>("A!K", &["As Qs"], &["As Ks"]);
    }

    #[test]
    fn test_or() {
        assert_checker::<2, false>("AA,KK", &["As Ah", "Ks Kh"], &[]);
    }

    #[test]
    fn test_and() {
        assert_checker::<2, false>("A:K", &["As Kh"], &[]);
    }

    fn e<const N: usize>(s: &str) -> Error
    where
        [Idx; N]: Array<Item = Idx>,
    {
        Checker::<N, false, false>::from_src(s).unwrap_err()
    }

    #[test]
    fn test_error() {
        assert_eq!(e::<2>("AK*"), Error::TooManyCardsInRange((0, 3)));
        assert_eq!(e::<2>("*!AAA"), Error::TooManyCardsInRange((2, 5)));
        assert_eq!(e::<2>("*:AAA"), Error::TooManyCardsInRange((2, 5)));
        assert_eq!(e::<2>("*,AAA"), Error::TooManyCardsInRange((2, 5)));
        assert_eq!(e::<2>("AAA!*"), Error::TooManyCardsInRange((0, 3)));
        assert_eq!(e::<2>("AAA:*"), Error::TooManyCardsInRange((0, 3)));
        assert_eq!(e::<2>("AAA,*"), Error::TooManyCardsInRange((0, 3)));
    }
}