1use crate::token::{Token, TokenType};
8
9pub trait ChUtils {
14 fn is_number(&self) -> bool;
16
17 fn is_point(&self) -> bool;
21
22 fn is_plus_or_minus(&self) -> bool;
27
28 fn is_div_or_prod(&self) -> bool;
33
34 fn is_operation_sign(&self) -> bool;
42
43 fn is_parentheses(&self) -> (bool, bool);
48
49 fn is_abs(&self) -> (bool, bool);
54
55 fn is_percentage(&self) -> bool;
57}
58
59impl ChUtils for String {
60 fn is_number(&self) -> bool {
61 let trimmed = self.trim();
62 if trimmed.is_empty() {
63 return false;
64 }
65 let mut chars = trimmed.chars().peekable();
68
69 if let Some(&c) = chars.peek() {
71 if c == '+' || c == '-' {
72 chars.next();
73 }
74 }
75
76 let mut has_digit = false;
78 let mut has_decimal = false;
79
80 for c in chars {
81 if c.is_ascii_digit() {
82 has_digit = true;
83 } else if c == '.' || c == ',' {
84 if has_decimal {
85 return false; }
87 has_decimal = true;
88 } else if c == ' ' {
89 continue;
91 } else {
92 return false; }
94 }
95
96 has_digit
97 }
98
99 fn is_point(&self) -> bool {
100 self.trim().eq(".") || self.trim().eq(",")
101 }
102
103 fn is_plus_or_minus(&self) -> bool {
104 self.trim().eq("+") || self.trim().eq("-")
105 }
106
107 fn is_div_or_prod(&self) -> bool {
108 let is_div: bool = self.trim().eq(":") || self.trim().eq("/");
109 let is_prod: bool = self.trim().eq("*") || self.trim().eq("•");
110
111 is_div || is_prod
112 }
113
114 fn is_operation_sign(&self) -> bool {
115 self.is_plus_or_minus() || self.is_div_or_prod()
116 }
117
118 fn is_parentheses(&self) -> (bool, bool) {
119 (self.trim().eq("("), self.trim().eq(")"))
120 }
121
122 fn is_abs(&self) -> (bool, bool) {
123 (self.trim().eq("["), self.trim().eq("]"))
124 }
125
126 fn is_percentage(&self) -> bool {
127 self.trim().eq("%")
128 }
129}
130
131impl ChUtils for Token {
132 fn is_number(&self) -> bool {
133 matches!(self.typ, TokenType::NUMBER)
134 }
135
136 fn is_point(&self) -> bool {
137 false
138 }
139
140 fn is_plus_or_minus(&self) -> bool {
141 matches!(self.typ, TokenType::PLUS | TokenType::MINUS)
142 }
143
144 fn is_div_or_prod(&self) -> bool {
145 matches!(self.typ, TokenType::PRODUCT | TokenType::DIVIDE)
146 }
147
148 fn is_operation_sign(&self) -> bool {
149 self.is_plus_or_minus() || self.is_div_or_prod()
150 }
151
152 fn is_parentheses(&self) -> (bool, bool) {
153 match self.typ {
154 TokenType::LPAREN => (true, false),
155 TokenType::RPAREN => (false, true),
156 _ => (false, false),
157 }
158 }
159
160 fn is_abs(&self) -> (bool, bool) {
161 match self.typ {
162 TokenType::LABS => (true, false),
163 TokenType::RABS => (false, true),
164 _ => (false, false),
165 }
166 }
167
168 fn is_percentage(&self) -> bool {
169 matches!(self.typ, TokenType::PERCENTAGE)
170 }
171}
172
173#[cfg(test)]
175mod tests {
176 use super::*;
177 use std::collections::HashMap;
178
179 #[test]
180 fn is_number() {
181 let test_data: HashMap<String, bool> = HashMap::from([
182 (String::from("42"), true),
183 (String::from("-25"), true),
184 (String::from("+50"), true),
185 (String::from("-"), false),
186 (String::from("+"), false),
187 (String::from("/"), false),
188 ]);
189
190 for (target, expected) in test_data {
191 assert_eq!(target.is_number(), expected);
192 assert_eq!(Token::from(target, (0, 0)).is_number(), expected);
193 }
194 }
195
196 #[test]
197 fn is_point() {
198 let test_data: HashMap<String, bool> = HashMap::from([
199 (String::from("."), true),
200 (String::from(","), true),
201 (String::from("-"), false),
202 (String::from("+"), false),
203 (String::from("/"), false),
204 (String::from("5"), false),
205 ]);
206
207 for (target, expected) in test_data {
208 assert_eq!(target.is_point(), expected);
209 }
210 }
211
212 #[test]
213 fn is_plus_or_minus() {
214 let test_data: HashMap<String, bool> = HashMap::from([
215 (String::from("-"), true),
216 (String::from("+"), true),
217 (String::from("/"), false),
218 (String::from(".5"), false),
219 (String::from("/"), false),
220 (String::from("*"), false),
221 ]);
222
223 for (target, expected) in test_data {
224 assert_eq!(target.is_plus_or_minus(), expected);
225 assert_eq!(Token::from(target, (0, 0)).is_plus_or_minus(), expected);
226 }
227 }
228
229 #[test]
230 fn is_div_or_prod() {
231 let test_data: HashMap<String, bool> = HashMap::from([
232 (String::from("/"), true),
233 (String::from("*"), true),
234 (String::from(":"), true),
235 (String::from("•"), true),
236 (String::from("-"), false),
237 (String::from("+"), false),
238 (String::from(".5"), false),
239 ]);
240
241 for (target, expected) in test_data {
242 assert_eq!(target.is_div_or_prod(), expected);
243 assert_eq!(Token::from(target, (0, 0)).is_div_or_prod(), expected);
244 }
245 }
246
247 #[test]
248 fn is_operation_sign() {
249 let test_data: HashMap<String, bool> = HashMap::from([
250 (String::from("/"), true),
251 (String::from("*"), true),
252 (String::from(":"), true),
253 (String::from("•"), true),
254 (String::from("-"), true),
255 (String::from("+"), true),
256 (String::from("5"), false),
257 (String::from("."), false),
258 (String::from(","), false),
259 ]);
260
261 for (target, expected) in test_data {
262 assert_eq!(target.is_operation_sign(), expected);
263 assert_eq!(Token::from(target, (0, 0)).is_operation_sign(), expected);
264 }
265 }
266
267 #[test]
268 fn is_parentheses() {
269 let test_data: HashMap<String, (bool, bool)> = HashMap::from([
270 (String::from("/"), (false, false)),
271 (String::from("*"), (false, false)),
272 (String::from(":"), (false, false)),
273 (String::from("•"), (false, false)),
274 (String::from("-"), (false, false)),
275 (String::from("+"), (false, false)),
276 (String::from("5"), (false, false)),
277 (String::from(")"), (false, true)),
278 (String::from("("), (true, false)),
279 ]);
280
281 for (target, expected) in test_data {
282 assert_eq!(target.is_parentheses(), expected);
283 assert_eq!(Token::from(target, (0, 0)).is_parentheses(), expected);
284 }
285 }
286
287 #[test]
288 fn is_abs() {
289 let test_data: HashMap<String, (bool, bool)> = HashMap::from([
290 (String::from("/"), (false, false)),
291 (String::from("*"), (false, false)),
292 (String::from(":"), (false, false)),
293 (String::from("•"), (false, false)),
294 (String::from("-"), (false, false)),
295 (String::from("+"), (false, false)),
296 (String::from("5"), (false, false)),
297 (String::from("]"), (false, true)),
298 (String::from("["), (true, false)),
299 ]);
300
301 for (target, expected) in test_data {
302 assert_eq!(target.is_abs(), expected);
303 assert_eq!(Token::from(target, (0, 0)).is_abs(), expected);
304 }
305 }
306
307 #[test]
308 fn is_percentage() {
309 let test_data: HashMap<bool, String> = HashMap::from([
310 (false, String::from("-25")),
311 (false, String::from("-")),
312 (false, String::from("(")),
313 (true, String::from("%")),
314 ]);
315
316 for (expected, data) in test_data {
317 assert_eq!(expected, data.is_percentage());
318 assert_eq!(expected, Token::from(data, (0, 0)).is_percentage());
319 }
320 }
321}