1use std::str::FromStr;
2use std::fmt;
3use crate::error::{LibError, LibErrorContext};
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum LibExpr {
7 Const(bool),
8 Var(String),
9 Not(Box<LibExpr>),
10 And(Vec<LibExpr>),
11 Or(Vec<LibExpr>),
12 Xor(Vec<LibExpr>),
13}
14
15impl FromStr for LibExpr {
16 type Err = LibError;
17 fn from_str(s: &str) -> Result<Self, LibError> {
18 let tokens = tokenize(s).context(|| "tokenize expr")?;
19 let mut parser = Parser { tokens, pos: 0 };
20 parser.parse_expr().context(|| "parse expr")
21 }
22}
23
24impl fmt::Display for LibExpr {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 LibExpr::Const(v) => {
28 if *v { write!(f, "1") } else { write!(f, "0") }
29 }
30 LibExpr::Var(name) => write!(f, "{}", name),
31
32 LibExpr::Not(expr) => {
33 write!(f, "!{}", wrap_if_needed(expr))
34 }
35 LibExpr::And(exprs) => {
36 write!(
37 f,
38 "{}",
39 exprs
40 .iter()
41 .map(|e| wrap_if_needed(e))
42 .collect::<Vec<_>>()
43 .join(" & ")
44 )
45 }
46 LibExpr::Or(exprs) => {
47 write!(
48 f,
49 "{}",
50 exprs
51 .iter()
52 .map(|e| wrap_if_needed(e))
53 .collect::<Vec<_>>()
54 .join(" | ")
55 )
56 }
57 LibExpr::Xor(exprs) => {
58 write!(
59 f,
60 "{}",
61 exprs
62 .iter()
63 .map(|e| wrap_if_needed(e))
64 .collect::<Vec<_>>()
65 .join(" ^ ")
66 )
67 }
68 }
69 }
70}
71
72fn wrap_if_needed(expr: &LibExpr) -> String {
73 match expr {
74 LibExpr::Const(_) | LibExpr::Var(_) => expr.to_string(),
75 LibExpr::Not(inner) => format!("!{}", wrap_if_needed(inner)),
76 _ => format!("({})", expr),
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
81enum Token {
82 Ident(String),
83 Not,
84 And,
85 Or,
86 Xor,
87 LParen,
88 RParen,
89}
90
91fn tokenize(s: &str) -> Result<Vec<Token>, LibError> {
92 let mut tokens = vec![];
93 let mut buf = String::new();
94 let mut chars = s.chars().peekable();
95 let mut pos = 0;
96
97 while let Some(c) = chars.next() {
98 pos += 1;
99 match c {
100 ' ' | '\t' | '\r' | '\n' => {
101 if !buf.is_empty() {
102 tokens.push(Token::Ident(buf.clone()));
103 buf.clear();
104 }
105 }
106 '!' => {
107 if !buf.is_empty() {
108 tokens.push(Token::Ident(buf.clone()));
109 buf.clear();
110 }
111 tokens.push(Token::Not);
112 }
113 '&' | '*' => {
114 if !buf.is_empty() {
115 tokens.push(Token::Ident(buf.clone()));
116 buf.clear();
117 }
118 tokens.push(Token::And);
119 }
120 '|' | '+' => {
121 if !buf.is_empty() {
122 tokens.push(Token::Ident(buf.clone()));
123 buf.clear();
124 }
125 tokens.push(Token::Or);
126 }
127 '^' => {
128 if !buf.is_empty() {
129 tokens.push(Token::Ident(buf.clone()));
130 buf.clear();
131 }
132 tokens.push(Token::Xor);
133 }
134 '(' => {
135 if !buf.is_empty() {
136 tokens.push(Token::Ident(buf.clone()));
137 buf.clear();
138 }
139 tokens.push(Token::LParen);
140 }
141 ')' => {
142 if !buf.is_empty() {
143 tokens.push(Token::Ident(buf.clone()));
144 buf.clear();
145 }
146 tokens.push(Token::RParen);
147 }
148 _ if c.is_alphanumeric() || c == '_' => {
149 buf.push(c);
150 }
151 _ => {
152 return Err(LibError::ParseExpr(format!(
154 "invalid character '{}' at position {} in expression '{}'",
155 c, pos, s
156 )));
157 }
158 }
159 }
160
161 if !buf.is_empty() {
162 tokens.push(Token::Ident(buf));
163 }
164
165 Ok(tokens)
166}
167
168struct Parser {
169 tokens: Vec<Token>,
170 pos: usize,
171}
172
173
174impl Parser {
175 fn peek(&self) -> Option<&Token> {
176 self.tokens.get(self.pos)
177 }
178
179 fn next(&mut self) -> Option<Token> {
180 if self.pos < self.tokens.len() {
181 self.pos += 1;
182 Some(self.tokens[self.pos - 1].clone())
183 } else {
184 None
185 }
186 }
187
188 fn parse_expr(&mut self) -> Result<LibExpr, LibError> {
189 self.parse_or()
190 }
191
192 fn parse_or(&mut self) -> Result<LibExpr, LibError> {
193 let mut left = self.parse_xor()?;
194 while let Some(Token::Or) = self.peek() {
195 self.next();
196 let right = self.parse_xor()?;
197 left = LibExpr::Or(vec![left, right]);
198 }
199 Ok(left)
200 }
201
202 fn parse_xor(&mut self) -> Result<LibExpr, LibError> {
203 let mut left = self.parse_and()?;
204 while let Some(Token::Xor) = self.peek() {
205 self.next();
206 let right = self.parse_and()?;
207 left = LibExpr::Xor(vec![left, right]);
208 }
209 Ok(left)
210 }
211
212 fn parse_and(&mut self) -> Result<LibExpr, LibError> {
213 let mut left = self.parse_unary()?;
214 while let Some(Token::And) = self.peek() {
215 self.next();
216 let right = self.parse_unary()?;
217 left = LibExpr::And(vec![left, right]);
218 }
219 Ok(left)
220 }
221
222 fn parse_unary(&mut self) -> Result<LibExpr, LibError> {
223 if let Some(Token::Not) = self.peek() {
224 self.next();
225 let expr = self.parse_unary()?;
226 Ok(LibExpr::Not(Box::new(expr)))
227 } else {
228 self.parse_primary()
229 }
230 }
231
232 fn parse_primary(&mut self) -> Result<LibExpr, LibError> {
233 match self.next() {
234 Some(Token::Ident(name)) => match name.as_str() {
235 "0" | "FALSE" | "false" => Ok(LibExpr::Const(false)),
236 "1" | "TRUE" | "true" => Ok(LibExpr::Const(true)),
237 _ => Ok(LibExpr::Var(name)),
238 },
239 Some(Token::LParen) => {
240 let expr = self.parse_expr()?;
241 match self.next() {
242 Some(Token::RParen) => Ok(expr),
243 _ => Err(LibError::ParseExpr("missing ')'".into())),
244 }
245 }
246 Some(tok) => Err(LibError::ParseExpr(format!("unexpected token: {:?}", tok))),
247 None => Err(LibError::ParseExpr("unexpected end".into())),
248 }
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_tokenize_simple_and() {
258 let expr = "A & B";
259 let tokens = tokenize(expr).unwrap();
260 assert_eq!(
261 tokens,
262 vec![
263 Token::Ident("A".into()),
264 Token::And,
265 Token::Ident("B".into()),
266 ]
267 );
268 }
269
270 #[test]
271 fn test_tokenize_not_and_or() {
272 let expr = "!A & (B | C)";
273 let tokens = tokenize(expr).unwrap();
274 assert_eq!(
275 tokens,
276 vec![
277 Token::Not,
278 Token::Ident("A".into()),
279 Token::And,
280 Token::LParen,
281 Token::Ident("B".into()),
282 Token::Or,
283 Token::Ident("C".into()),
284 Token::RParen,
285 ]
286 );
287 }
288
289 #[test]
290 fn test_tokenize_xor_expression() {
291 let expr = "A ^ B";
292 let tokens = tokenize(expr).unwrap();
293 assert_eq!(
294 tokens,
295 vec![
296 Token::Ident("A".into()),
297 Token::Xor,
298 Token::Ident("B".into()),
299 ]
300 );
301 }
302
303 #[test]
304 fn test_tokenize_with_constants() {
305 let expr = "1";
306 let tokens = tokenize(expr).unwrap();
307 assert_eq!(tokens, vec![Token::Ident("1".into())]);
308
309 let expr = "0";
310 let tokens = tokenize(expr).unwrap();
311 assert_eq!(tokens, vec![Token::Ident("0".into())]);
312 }
313
314 #[test]
315 fn test_tokenize_with_underscore_names() {
316 let expr = "A_1 & B_2";
317 let tokens = tokenize(expr).unwrap();
318 assert_eq!(
319 tokens,
320 vec![
321 Token::Ident("A_1".into()),
322 Token::And,
323 Token::Ident("B_2".into()),
324 ]
325 );
326 }
327
328 #[test]
329 fn test_tokenize_invalid_character() {
330 let expr = "A @ B";
331 let err = tokenize(expr).unwrap_err();
332 assert!(err.to_string().contains("@"));
333 }
334
335 #[test]
336 fn test_tokenize_multiple_spaces() {
337 let expr = " A & B ";
338 let tokens = tokenize(expr).unwrap();
339 assert_eq!(
340 tokens,
341 vec![
342 Token::Ident("A".into()),
343 Token::And,
344 Token::Ident("B".into()),
345 ]
346 );
347 }
348
349
350 fn parse_ok(s: &str) -> LibExpr {
351 LibExpr::from_str(s).unwrap()
352 }
353
354 fn parse_err(s: &str) -> String {
355 LibExpr::from_str(s)
356 .unwrap_err()
357 .to_string()
358 }
359
360 #[test]
361 fn test_const_and_var() {
362 assert_eq!(parse_ok("1"), LibExpr::Const(true));
363 assert_eq!(parse_ok("true"), LibExpr::Const(true));
364 assert_eq!(parse_ok("0"), LibExpr::Const(false));
365 assert_eq!(parse_ok("FALSE"), LibExpr::Const(false));
366 assert_eq!(parse_ok("A"), LibExpr::Var("A".into()));
367 assert_eq!(parse_ok("foo_bar"), LibExpr::Var("foo_bar".into()));
368 }
369
370 #[test]
371 fn test_not_expr() {
372 assert_eq!(
373 parse_ok("!A"),
374 LibExpr::Not(Box::new(LibExpr::Var("A".into())))
375 );
376 assert_eq!(
377 parse_ok("!1"),
378 LibExpr::Not(Box::new(LibExpr::Const(true)))
379 );
380 }
381
382 #[test]
383 fn test_and_expr() {
384 let expr = parse_ok("A & B");
385 assert_eq!(
386 expr,
387 LibExpr::And(vec![
388 LibExpr::Var("A".into()),
389 LibExpr::Var("B".into())
390 ])
391 );
392 }
393
394 #[test]
395 fn test_or_expr() {
396 let expr = parse_ok("A | B");
397 assert_eq!(
398 expr,
399 LibExpr::Or(vec![
400 LibExpr::Var("A".into()),
401 LibExpr::Var("B".into())
402 ])
403 );
404 }
405
406 #[test]
407 fn test_xor_expr() {
408 let expr = parse_ok("A ^ B");
409 assert_eq!(
412 expr,
413 LibExpr::Xor(vec![
414 LibExpr::Var("A".into()),
415 LibExpr::Var("B".into())
416 ])
417 );
418 }
419
420 #[test]
421 fn test_nested_expr() {
422 let expr = parse_ok("A & (B | !C)");
423 assert_eq!(
424 expr,
425 LibExpr::And(vec![
426 LibExpr::Var("A".into()),
427 LibExpr::Or(vec![
428 LibExpr::Var("B".into()),
429 LibExpr::Not(Box::new(LibExpr::Var("C".into())))
430 ])
431 ])
432 );
433 }
434
435 #[test]
436 fn test_precedence() {
437 let expr = parse_ok("A & B | C");
438 assert_eq!(
439 expr,
440 LibExpr::Or(vec![
441 LibExpr::And(vec![
442 LibExpr::Var("A".into()),
443 LibExpr::Var("B".into())
444 ]),
445 LibExpr::Var("C".into())
446 ])
447 );
448 }
449
450 #[test]
451 fn test_invalid_char() {
452 let err = parse_err("A $ B");
453 assert!(
454 err.contains("invalid character"),
455 "unexpected error: {}",
456 err
457 );
458 }
459
460 #[test]
461 fn test_missing_paren() {
462 let err = parse_err("(A & B");
463 assert!(err.contains("missing ')'"));
464 }
465
466 #[test]
467 fn test_empty_input() {
468 let err = parse_err("");
469 assert!(err.contains("unexpected end"));
470 }
471
472 #[test]
473 fn test_to_string() {
474 let expr = LibExpr::And(vec![
475 LibExpr::Var("A".into()),
476 LibExpr::Or(vec![
477 LibExpr::Var("B".into()),
478 LibExpr::Not(Box::new(LibExpr::Var("C".into()))),
479 ]),
480 ]);
481
482 assert_eq!(expr.to_string(), "A & (B | !C)");
483
484 let expr2 = LibExpr::Or(vec![
485 LibExpr::And(vec![
486 LibExpr::Var("A".into()),
487 LibExpr::Var("B".into()),
488 ]),
489 LibExpr::Var("C".into()),
490 ]);
491 assert_eq!(expr2.to_string(), "(A & B) | C");
492 }
493}