Skip to main content

openpql_pql_parser/ast/
from_clause.rs

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