Skip to main content

openpql_pql_parser/
lib.rs

1//! Parser for the Poker Query Language (PQL).
2
3#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
4#![cfg_attr(test, allow(clippy::needless_pass_by_value))]
5#![cfg_attr(test, allow(clippy::wildcard_imports))]
6
7use std::{collections::hash_map::Entry, fmt, string::String};
8
9use lalrpop_util::{ParseError, lalrpop_mod, lexer::Token};
10use rustc_hash::{FxHashMap, FxHashSet};
11
12/// Abstract syntax tree nodes for parsed PQL statements.
13pub mod ast;
14mod error;
15mod spanned;
16
17pub use error::Error;
18use error::{LalrError, ResultE, user_err};
19use parser::{
20    ExprParser, FnCallParser, FromClauseParser, IdentParser, NumParser,
21    PQLParser, SelectorParser, StrParser,
22};
23pub use spanned::Spanned;
24
25/// Parses a full PQL source into a list of statements.
26pub fn parse_pql(src: &str) -> Result<Vec<ast::Stmt<'_>>, Error> {
27    PQLParser::new().parse(src).map_err(Into::into)
28}
29
30// Editor macro is much simpler than rust macro :>
31
32/// Parses a single selector expression (e.g. `avg(equity(hero))`).
33pub fn parse_selector(src: &str) -> Result<ast::Selector<'_>, Error> {
34    SelectorParser::new().parse(src).map_err(Into::into)
35}
36
37/// Parses a `from` clause.
38pub fn parse_from_clause(src: &str) -> Result<ast::FromClause<'_>, Error> {
39    FromClauseParser::new().parse(src).map_err(Into::into)
40}
41
42/// Parses a standalone expression.
43pub fn parse_expr(src: &str) -> Result<ast::Expr<'_>, Error> {
44    ExprParser::new().parse(src).map_err(Into::into)
45}
46
47/// Parses a function call.
48pub fn parse_fn_call(src: &str) -> Result<ast::FnCall<'_>, Error> {
49    FnCallParser::new().parse(src).map_err(Into::into)
50}
51
52/// Parses a quoted string literal.
53pub fn parse_str(src: &str) -> Result<ast::Str<'_>, Error> {
54    StrParser::new().parse(src).map_err(Into::into)
55}
56
57/// Parses a numeric literal.
58pub fn parse_num(src: &str) -> Result<ast::Num, Error> {
59    NumParser::new().parse(src).map_err(Into::into)
60}
61
62/// Parses an identifier.
63pub fn parse_ident(src: &str) -> Result<ast::Ident<'_>, Error> {
64    IdentParser::new().parse(src).map_err(Into::into)
65}
66
67type Expected = Vec<String>;
68
69lalrpop_mod!(
70    #[allow(clippy::empty_line_after_outer_attr)]
71    #[allow(clippy::iter_nth_zero)]
72    #[allow(clippy::nursery)]
73    #[allow(clippy::pedantic)]
74    #[allow(clippy::restriction)]
75    #[allow(clippy::useless_conversion)]
76    pub(crate) parser,
77    "/pql.rs"
78);
79
80/// Byte offset into the source string.
81pub type Loc = usize;
82/// Inclusive start and exclusive end byte offsets in the source.
83pub type LocInfo = (Loc, Loc);
84
85fn strip_str(s: &str) -> &str {
86    &s[1..s.len() - 1]
87}
88
89#[cfg(test)]
90pub use tests::*;
91
92#[cfg(test)]
93#[cfg_attr(coverage_nightly, coverage(off))]
94pub mod tests {
95    use super::*;
96
97    #[allow(clippy::missing_panics_doc)]
98    pub fn loc(full: &str, sub: &str) -> (Loc, Loc) {
99        let start = full
100            .find(sub)
101            .unwrap_or_else(|| panic!("{sub} not in {full}"));
102        let end = start + sub.len();
103        (start, end)
104    }
105
106    #[test]
107    fn test_error_invalid_token() {
108        assert_eq!(Error::InvalidToken((0, 1)), parse_pql("?").unwrap_err());
109    }
110
111    #[test]
112    fn test_error_unrecognized_eof() {
113        let res = parse_pql("select").unwrap_err();
114
115        if let Error::UnrecognizedEof(loc, expected) = res {
116            assert_eq!(loc, (6, 7));
117            assert_eq!(expected.len(), 1);
118        } else {
119            panic!("Expected: UnrecognizedEof. Got: {res:?}")
120        }
121    }
122
123    #[test]
124    fn test_error_unrecognized_token() {
125        let res = parse_pql("select ()").unwrap_err();
126
127        if let Error::UnrecognizedToken(loc, expected) = res {
128            assert_eq!(loc, (7, 8));
129            assert_eq!(expected.len(), 1);
130        } else {
131            panic!("Expected: UnrecognizedToken. Got: {res:?}")
132        }
133    }
134}