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