Skip to main content

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