openpql_range_parser/
lib.rs

1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
2#![cfg_attr(test, allow(clippy::needless_pass_by_value))]
3#![cfg_attr(test, allow(clippy::wildcard_imports))]
4
5use std::{
6    convert::From, marker::PhantomData, ops, string::ToString, sync::LazyLock,
7};
8
9use derive_more::Display;
10use itertools::Itertools;
11use lalrpop_util::{ParseError, lalrpop_mod, lexer::Token};
12use openpql_prelude::*;
13use smallvec::{Array, SmallVec};
14
15pub mod ast;
16mod checker;
17mod error;
18
19pub use checker::{BoardRangeChecker, RangeChecker};
20pub use error::Error;
21use error::{LalrError, ResultE};
22
23lalrpop_mod!(
24    #[allow(clippy::empty_line_after_outer_attr)]
25    #[allow(clippy::iter_nth_zero)]
26    #[allow(clippy::nursery)]
27    #[allow(clippy::pedantic)]
28    #[allow(clippy::restriction)]
29    #[allow(clippy::useless_conversion)]
30    parser,
31    "/range.rs"
32);
33
34type Idx = u8;
35pub type Loc = usize;
36pub type LocInfo = (Loc, Loc);
37type Expected = Vec<String>;
38
39pub fn parse_expr(
40    is_shortdeck: bool,
41    src: &str,
42) -> Result<Box<ast::Expr>, Error> {
43    Ok(parser::ExprParser::new().parse(is_shortdeck, src)?)
44}
45
46#[cfg(test)]
47#[macro_use(quickcheck)]
48extern crate quickcheck_macros;
49
50#[cfg(test)]
51pub use tests::*;
52
53#[cfg(test)]
54#[cfg_attr(coverage_nightly, coverage(off))]
55pub mod tests {
56    pub use std::{cmp, fmt};
57
58    pub use openpql_prelude::s4;
59    pub use quickcheck::{Arbitrary, TestResult};
60
61    pub use super::*;
62
63    pub fn parse_card(src: &str) -> ResultE<'_, ast::RangeCard> {
64        parser::RangeCardParser::new().parse(false, src)
65    }
66
67    pub fn parse_list(src: &str) -> ResultE<'_, ast::List> {
68        parser::ListParser::new().parse(false, src)
69    }
70
71    pub fn parse_span(src: &str) -> ResultE<'_, ast::Span> {
72        parser::SpanParser::new().parse(false, src)
73    }
74
75    pub fn parse_term(src: &str) -> ResultE<'_, ast::Term> {
76        parser::TermParser::new().parse(false, src)
77    }
78
79    pub fn parse_card_sd(src: &str) -> ResultE<'_, ast::RangeCard> {
80        parser::RangeCardParser::new().parse(true, src)
81    }
82
83    pub(crate) fn assert_range_card(src: &str, expected: &str) {
84        let range_card = parse_card(src).unwrap();
85
86        assert_eq!(range_card.to_string(), expected);
87    }
88
89    pub(crate) fn assert_err<T: fmt::Debug + cmp::PartialEq>(
90        res: ResultE<'_, T>,
91        expected: Error,
92    ) {
93        assert_eq!(res, Err(expected.into()));
94    }
95
96    fn assert_str_in(vec: &[String], val: &str) {
97        assert!(vec.contains(&format!("\"{val}\"")), "{val} not in {vec:?}");
98    }
99
100    #[test]
101    fn test_error_invalid_token() {
102        assert_eq!(
103            Error::InvalidToken((0, 1)),
104            parse_expr(false, "?").unwrap_err()
105        );
106    }
107
108    #[test]
109    fn test_error_unrecognized_eof() {
110        let res = parse_expr(false, "[").unwrap_err();
111
112        if let Error::UnrecognizedEof(loc, expected) = res {
113            assert_eq!(loc, (1, 2));
114            assert_eq!(expected.len(), 3);
115
116            assert_str_in(&expected, "Suit");
117            assert_str_in(&expected, "Rank");
118            assert_str_in(&expected, "RankSuit");
119        } else {
120            panic!("Expected: UnrecognizedEof. Got: {res:?}")
121        }
122    }
123
124    #[test]
125    fn test_error_unrecognized_token() {
126        let res = parse_expr(false, "[,").unwrap_err();
127
128        if let Error::UnrecognizedToken(loc, expected) = res {
129            assert_eq!(loc, (1, 2));
130            assert_eq!(expected.len(), 3);
131
132            assert_str_in(&expected, "Suit");
133            assert_str_in(&expected, "Rank");
134            assert_str_in(&expected, "RankSuit");
135        } else {
136            panic!("Expected: UnrecognizedToken. Got: {res:?}")
137        }
138    }
139
140    #[test]
141    fn test_error() {
142        let err = LalrError::ExtraToken {
143            token: (0, Token(0, "a"), 1),
144        };
145
146        assert_eq!(Error::ExtraToken((0, 1)), err.into());
147
148        let err = LalrError::User {
149            error: Error::InvalidRank((0, 1)),
150        };
151
152        assert_eq!(Error::InvalidRank((0, 1)), err.into());
153    }
154
155    #[test]
156    fn test_from_error_to_loc() {
157        let err = Error::InvalidToken((5, 10));
158        assert_eq!(LocInfo::from(&err), (5, 10));
159
160        let err = Error::UnrecognizedEof((3, 7), vec![]);
161        assert_eq!(LocInfo::from(&err), (3, 7));
162
163        let err = Error::UnrecognizedToken((1, 4), vec![]);
164        assert_eq!(LocInfo::from(&err), (1, 4));
165
166        let err = Error::ExtraToken((2, 6));
167        assert_eq!(LocInfo::from(&err), (2, 6));
168
169        let err = Error::TooManyCardsInRange((10, 15));
170        assert_eq!(LocInfo::from(&err), (10, 15));
171
172        let err = Error::NumberOfRanksMismatchInSpan((8, 12));
173        assert_eq!(LocInfo::from(&err), (8, 12));
174
175        let err = Error::RankDistanceMismatchInSpan((4, 9));
176        assert_eq!(LocInfo::from(&err), (4, 9));
177
178        let err = Error::SuitMismatchInSpan((6, 11));
179        assert_eq!(LocInfo::from(&err), (6, 11));
180
181        let err = Error::InvalidSpan((7, 13));
182        assert_eq!(LocInfo::from(&err), (7, 13));
183
184        let err = Error::InvalidList((9, 14));
185        assert_eq!(LocInfo::from(&err), (9, 14));
186
187        let err = Error::InvalidRank((0, 1));
188        assert_eq!(LocInfo::from(&err), (0, 1));
189
190        let err = Error::InvalidSuit((11, 16));
191        assert_eq!(LocInfo::from(&err), (11, 16));
192    }
193}