openpql_pql_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::{collections::hash_map::Entry, fmt, string::String};
6
7use derive_more::Display;
8use lalrpop_util::{ParseError, lalrpop_mod, lexer::Token};
9use rustc_hash::{FxHashMap, FxHashSet};
10
11pub mod ast;
12mod error;
13
14pub use error::Error;
15use error::{LalrError, ResultE, user_err};
16use parser::{
17    ExprParser, FnCallParser, FromClauseParser, IdentParser, NumParser,
18    PQLParser, SelectorParser, StrParser,
19};
20
21pub fn parse_pql(src: &str) -> Result<Vec<ast::Stmt<'_>>, Error> {
22    PQLParser::new().parse(src).map_err(Into::into)
23}
24
25// Editor macro is much simpler than rust macro :>
26
27pub fn parse_selector(src: &str) -> Result<ast::Selector<'_>, Error> {
28    SelectorParser::new().parse(src).map_err(Into::into)
29}
30
31pub fn parse_from_clause(src: &str) -> Result<ast::FromClause<'_>, Error> {
32    FromClauseParser::new().parse(src).map_err(Into::into)
33}
34
35pub fn parse_expr(src: &str) -> Result<ast::Expr<'_>, Error> {
36    ExprParser::new().parse(src).map_err(Into::into)
37}
38
39pub fn parse_fn_call(src: &str) -> Result<ast::FnCall<'_>, Error> {
40    FnCallParser::new().parse(src).map_err(Into::into)
41}
42
43pub fn parse_str(src: &str) -> Result<ast::Str<'_>, Error> {
44    StrParser::new().parse(src).map_err(Into::into)
45}
46
47pub fn parse_num(src: &str) -> Result<ast::Num, Error> {
48    NumParser::new().parse(src).map_err(Into::into)
49}
50
51pub fn parse_ident(src: &str) -> Result<ast::Ident<'_>, Error> {
52    IdentParser::new().parse(src).map_err(Into::into)
53}
54
55type Expected = Vec<String>;
56
57lalrpop_mod!(
58    #[allow(clippy::empty_line_after_outer_attr)]
59    #[allow(clippy::iter_nth_zero)]
60    #[allow(clippy::nursery)]
61    #[allow(clippy::pedantic)]
62    #[allow(clippy::restriction)]
63    #[allow(clippy::useless_conversion)]
64    pub(crate) parser,
65    "/pql.rs"
66);
67
68pub type Loc = usize;
69pub type LocInfo = (Loc, Loc);
70
71fn strip_str(s: &str) -> &str {
72    &s[1..s.len() - 1]
73}
74
75#[cfg(test)]
76pub use tests::*;
77
78#[cfg(test)]
79#[cfg_attr(coverage_nightly, coverage(off))]
80pub mod tests {
81    use super::*;
82
83    #[allow(clippy::missing_panics_doc)]
84    pub fn loc(full: &str, sub: &str) -> (Loc, Loc) {
85        let start = full
86            .find(sub)
87            .unwrap_or_else(|| panic!("{sub} not in {full}"));
88        let end = start + sub.len();
89        (start, end)
90    }
91
92    #[test]
93    fn test_error_invalid_token() {
94        assert_eq!(Error::InvalidToken((0, 1)), parse_pql("?").unwrap_err());
95    }
96
97    #[test]
98    fn test_error_unrecognized_eof() {
99        let res = parse_pql("select").unwrap_err();
100
101        if let Error::UnrecognizedEof(loc, expected) = res {
102            assert_eq!(loc, (6, 7));
103            assert_eq!(expected.len(), 1);
104        } else {
105            panic!("Expected: UnrecognizedEof. Got: {res:?}")
106        }
107    }
108
109    #[test]
110    fn test_error_unrecognized_token() {
111        let res = parse_pql("select ()").unwrap_err();
112
113        if let Error::UnrecognizedToken(loc, expected) = res {
114            assert_eq!(loc, (7, 8));
115            assert_eq!(expected.len(), 1);
116        } else {
117            panic!("Expected: UnrecognizedToken. Got: {res:?}")
118        }
119    }
120}