Skip to main content

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