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