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