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