openpql_pql_parser/ast/
from_clause.rs1use super::{
2 Entry, Error, FxHashMap, Ident, Loc, LocInfo, ResultE, Spanned, Str,
3 String, fmt, user_err,
4};
5
6#[derive(PartialEq, Eq, Default)]
8pub struct FromClause<'i> {
9 pub inner: FxHashMap<String, FromItem<'i>>,
11 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 pub fn get_board_range(&self) -> Option<&Str<'_>> {
49 self.get_val(Self::BOARD_KEY)
50 }
51
52 pub fn get_game(&self) -> Option<&Str<'_>> {
54 self.get_val(Self::GAME_KEY)
55 }
56
57 pub fn get_dead(&self) -> Option<&Str<'_>> {
59 self.get_val(Self::DEADCARD_KEY)
60 }
61
62 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#[derive(PartialEq, Eq, Debug)]
93pub struct FromItem<'i> {
94 pub key: Ident<'i>,
96 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 }
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}