openpql-range-parser 0.1.0

Poker Range Notation Parser
Documentation
#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
#![cfg_attr(test, allow(clippy::needless_pass_by_value))]
#![cfg_attr(test, allow(clippy::wildcard_imports))]

use std::{
    convert::From, marker::PhantomData, ops, string::ToString, sync::LazyLock,
};

use derive_more::Display;
use itertools::Itertools;
use lalrpop_util::{ParseError, lalrpop_mod, lexer::Token};
use openpql_prelude::*;
use smallvec::{Array, SmallVec};

pub mod ast;
mod checker;
mod error;

pub use checker::{BoardRangeChecker, RangeChecker};
pub use error::Error;
use error::{LalrError, ResultE};

lalrpop_mod!(
    #[allow(clippy::empty_line_after_outer_attr)]
    #[allow(clippy::iter_nth_zero)]
    #[allow(clippy::nursery)]
    #[allow(clippy::pedantic)]
    #[allow(clippy::restriction)]
    #[allow(clippy::useless_conversion)]
    parser,
    "/range.rs"
);

type Idx = u8;
pub type Loc = usize;
pub type LocInfo = (Loc, Loc);
type Expected = Vec<String>;

pub fn parse_expr(
    is_shortdeck: bool,
    src: &str,
) -> Result<Box<ast::Expr>, Error> {
    Ok(parser::ExprParser::new().parse(is_shortdeck, src)?)
}

#[cfg(test)]
#[macro_use(quickcheck)]
extern crate quickcheck_macros;

#[cfg(test)]
pub use tests::*;

#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub mod tests {
    pub use std::{cmp, fmt};

    pub use openpql_prelude::s4;
    pub use quickcheck::{Arbitrary, TestResult};

    pub use super::*;

    pub fn parse_card(src: &str) -> ResultE<'_, ast::RangeCard> {
        parser::RangeCardParser::new().parse(false, src)
    }

    pub fn parse_list(src: &str) -> ResultE<'_, ast::List> {
        parser::ListParser::new().parse(false, src)
    }

    pub fn parse_span(src: &str) -> ResultE<'_, ast::Span> {
        parser::SpanParser::new().parse(false, src)
    }

    pub fn parse_term(src: &str) -> ResultE<'_, ast::Term> {
        parser::TermParser::new().parse(false, src)
    }

    pub fn parse_card_sd(src: &str) -> ResultE<'_, ast::RangeCard> {
        parser::RangeCardParser::new().parse(true, src)
    }

    pub(crate) fn assert_range_card(src: &str, expected: &str) {
        let range_card = parse_card(src).unwrap();

        assert_eq!(range_card.to_string(), expected);
    }

    pub(crate) fn assert_err<T: fmt::Debug + cmp::PartialEq>(
        res: ResultE<'_, T>,
        expected: Error,
    ) {
        assert_eq!(res, Err(expected.into()));
    }

    fn assert_str_in(vec: &[String], val: &str) {
        assert!(vec.contains(&format!("\"{val}\"")), "{val} not in {vec:?}");
    }

    #[test]
    fn test_error_invalid_token() {
        assert_eq!(
            Error::InvalidToken((0, 1)),
            parse_expr(false, "?").unwrap_err()
        );
    }

    #[test]
    fn test_error_unrecognized_eof() {
        let res = parse_expr(false, "[").unwrap_err();

        if let Error::UnrecognizedEof(loc, expected) = res {
            assert_eq!(loc, (1, 2));
            assert_eq!(expected.len(), 3);

            assert_str_in(&expected, "Suit");
            assert_str_in(&expected, "Rank");
            assert_str_in(&expected, "RankSuit");
        } else {
            panic!("Expected: UnrecognizedEof. Got: {res:?}")
        }
    }

    #[test]
    fn test_error_unrecognized_token() {
        let res = parse_expr(false, "[,").unwrap_err();

        if let Error::UnrecognizedToken(loc, expected) = res {
            assert_eq!(loc, (1, 2));
            assert_eq!(expected.len(), 3);

            assert_str_in(&expected, "Suit");
            assert_str_in(&expected, "Rank");
            assert_str_in(&expected, "RankSuit");
        } else {
            panic!("Expected: UnrecognizedToken. Got: {res:?}")
        }
    }

    #[test]
    fn test_error() {
        let err = LalrError::ExtraToken {
            token: (0, Token(0, "a"), 1),
        };

        assert_eq!(Error::ExtraToken((0, 1)), err.into());

        let err = LalrError::User {
            error: Error::InvalidRank((0, 1)),
        };

        assert_eq!(Error::InvalidRank((0, 1)), err.into());
    }

    #[test]
    fn test_from_error_to_loc() {
        let err = Error::InvalidToken((5, 10));
        assert_eq!(LocInfo::from(&err), (5, 10));

        let err = Error::UnrecognizedEof((3, 7), vec![]);
        assert_eq!(LocInfo::from(&err), (3, 7));

        let err = Error::UnrecognizedToken((1, 4), vec![]);
        assert_eq!(LocInfo::from(&err), (1, 4));

        let err = Error::ExtraToken((2, 6));
        assert_eq!(LocInfo::from(&err), (2, 6));

        let err = Error::TooManyCardsInRange((10, 15));
        assert_eq!(LocInfo::from(&err), (10, 15));

        let err = Error::NumberOfRanksMismatchInSpan((8, 12));
        assert_eq!(LocInfo::from(&err), (8, 12));

        let err = Error::RankDistanceMismatchInSpan((4, 9));
        assert_eq!(LocInfo::from(&err), (4, 9));

        let err = Error::SuitMismatchInSpan((6, 11));
        assert_eq!(LocInfo::from(&err), (6, 11));

        let err = Error::InvalidSpan((7, 13));
        assert_eq!(LocInfo::from(&err), (7, 13));

        let err = Error::InvalidList((9, 14));
        assert_eq!(LocInfo::from(&err), (9, 14));

        let err = Error::InvalidRank((0, 1));
        assert_eq!(LocInfo::from(&err), (0, 1));

        let err = Error::InvalidSuit((11, 16));
        assert_eq!(LocInfo::from(&err), (11, 16));
    }
}