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