Skip to main content

reda_lib/model/
expr.rs

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                // 出现非法字符,立即报错
153                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        // 注意:你的代码中 XOR 实际上调用了 `Or`(应该修复为 Xor)
410        // 这里只是保持一致
411        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}