Skip to main content

openpql_pql_parser/ast/
from_clause.rs

1use super::{
2    Entry, Error, FxHashMap, Ident, Loc, LocInfo, ResultE, Spanned, Str, String, fmt, user_err,
3};
4
5/// Parsed `from` clause, indexed by lowercased key.
6#[derive(PartialEq, Eq, Default)]
7pub struct FromClause<'i> {
8    /// Items keyed by their lowercased name.
9    pub inner: FxHashMap<String, FromItem<'i>>,
10    /// Source span covering the whole clause.
11    pub loc: (Loc, Loc),
12}
13
14impl<'i> FromClause<'i> {
15    const BOARD_KEY: &'static str = "board";
16    const GAME_KEY: &'static str = "game";
17    const DEADCARD_KEY: &'static str = "dead";
18    const NON_PLAYER_KEYS: [&'static str; 3] =
19        [Self::BOARD_KEY, Self::GAME_KEY, Self::DEADCARD_KEY];
20
21    pub(crate) fn new<T: IntoIterator<Item = FromItem<'i>>>(
22        items: T,
23        loc: (Loc, Loc),
24    ) -> ResultE<'i, Self> {
25        let mut res = Self::default();
26
27        for item in items {
28            let key = item.key.inner.to_ascii_lowercase();
29
30            if let Entry::Vacant(e) = res.inner.entry(key) {
31                e.insert(item);
32            } else {
33                return Err(user_err(Error::DuplicatedKeyInFrom(item.key.loc)));
34            }
35        }
36
37        res.loc = loc;
38
39        Ok(res)
40    }
41
42    fn get_val(&self, key: &str) -> Option<&Str<'_>> {
43        self.inner.get(key).as_ref().map(|item| &item.value)
44    }
45
46    /// Returns the `board` value, if provided.
47    pub fn get_board_range(&self) -> Option<&Str<'_>> {
48        self.get_val(Self::BOARD_KEY)
49    }
50
51    /// Returns the `game` value, if provided.
52    pub fn get_game(&self) -> Option<&Str<'_>> {
53        self.get_val(Self::GAME_KEY)
54    }
55
56    /// Returns the `dead` (dead-cards) value, if provided.
57    pub fn get_dead(&self) -> Option<&Str<'_>> {
58        self.get_val(Self::DEADCARD_KEY)
59    }
60
61    /// Returns all player entries, i.e. items that are not reserved keys.
62    pub fn get_players(&self) -> Vec<(&Ident<'_>, &Str<'_>)> {
63        self.inner
64            .keys()
65            .filter(|k| !Self::NON_PLAYER_KEYS.contains(&k.as_str()))
66            .map(|k| &self.inner[k])
67            .map(|item| (&item.key, &item.value))
68            .collect()
69    }
70}
71
72impl fmt::Debug for FromClause<'_> {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.debug_map()
75            .entries(
76                self.inner
77                    .values()
78                    .map(|item| (item.key.inner, item.value.inner)),
79            )
80            .finish()
81    }
82}
83
84impl Spanned for FromClause<'_> {
85    fn loc(&self) -> LocInfo {
86        self.loc
87    }
88}
89
90/// Single `key = 'value'` entry in a `from` clause.
91#[derive(PartialEq, Eq, Debug)]
92pub struct FromItem<'i> {
93    /// Entry key.
94    pub key: Ident<'i>,
95    /// Entry string value.
96    pub value: Str<'i>,
97}
98
99impl Spanned for FromItem<'_> {
100    fn loc(&self) -> LocInfo {
101        (self.key.loc.0, self.value.loc.1)
102    }
103}
104
105impl<'i, U, V> From<(U, V)> for FromItem<'i>
106where
107    U: Into<Ident<'i>>,
108    V: Into<Str<'i>>,
109{
110    fn from(t: (U, V)) -> Self {
111        Self {
112            key: t.0.into(),
113            value: t.1.into(),
114        }
115    }
116}
117
118#[cfg(test)]
119#[cfg_attr(coverage_nightly, coverage(off))]
120mod tests {
121    use super::*;
122    use crate::*;
123
124    fn mk_inner<'s>(
125        src: &'s str,
126        kvs: &[(&'static str, &'static str)],
127    ) -> FxHashMap<String, FromItem<'s>> {
128        let mut res = FxHashMap::default();
129        for (key, val) in kvs {
130            let id = Ident::from((*key, loc(src, key)));
131            let s = Str::from((strip_str(val), loc(src, val)));
132            res.insert((*key).to_string(), FromItem::from((id, s)));
133        }
134
135        res
136    }
137
138    fn assert_from_clause(src: &str, kvs: &[(&'static str, &'static str)]) {
139        assert_eq!(parse_from_clause(src).unwrap().inner, mk_inner(src, kvs));
140    }
141
142    #[test]
143    fn test_from_clause() {
144        let src = "from game='holdem', hero='AA'";
145
146        assert_from_clause(src, &[("game", "'holdem'"), ("hero", "'AA'")]);
147    }
148
149    #[test]
150    fn test_from_clause_norm_key() {
151        let obj = parse_from_clause("from GAME=''").unwrap().inner;
152        assert!(obj.contains_key("game"), "should use lowercase for keys");
153        assert!(!obj.contains_key("GAME"));
154    }
155
156    #[test]
157    fn test_values() {
158        let obj = parse_from_clause("from hero='AA'").unwrap();
159        assert_eq!(obj.get_game(), None);
160        assert_eq!(obj.get_board_range(), None);
161        assert_eq!(obj.get_dead(), None);
162        //assert_eq!(obj.get_players(), &[("hero", "AA")]);
163    }
164
165    fn assert_err(src: &str, expected: Error) {
166        assert_eq!(parse_from_clause(src).unwrap_err(), expected);
167    }
168
169    #[test]
170    fn test_from_clause_dup_key() {
171        let src = "from GAME='', game=''";
172        assert_err(src, Error::DuplicatedKeyInFrom(loc(src, "game")));
173    }
174
175    #[test]
176    fn test_debug() {
177        let obj = parse_from_clause("from game='holdem', hero='AA'").unwrap();
178
179        assert!(format!("{obj:?}").find(r#"hero": "AA"#).is_some());
180    }
181
182    #[test]
183    fn test_loc() {
184        let src = "from game='holdem', hero='AA', dead='2c'";
185        let obj = parse_from_clause(src).unwrap();
186
187        assert_eq!(obj.loc().1, src.len());
188    }
189
190    #[test]
191    fn test_from_item_loc() {
192        let src = "from key='val'";
193        let obj = parse_from_clause(src).unwrap();
194        let item = obj.inner.get("key").unwrap();
195        assert_eq!(item.loc(), (item.key.loc.0, item.value.loc.1));
196    }
197}