|
| 1 | +use std::collections::HashMap; |
| 2 | + |
| 3 | +use super::parser::{Expr, Parser}; |
| 4 | + |
| 5 | +pub type Vars = HashMap<String, Evalue>; |
| 6 | + |
| 7 | +pub struct Evaluator {} |
| 8 | + |
| 9 | +#[derive(PartialEq, PartialOrd, Debug, Clone)] |
| 10 | +pub enum Evalue { |
| 11 | + String(String), |
| 12 | + Number(f64), |
| 13 | + Bool(bool), |
| 14 | +} |
| 15 | +impl Evalue { |
| 16 | + fn to_bool(&self) -> bool { |
| 17 | + match self { |
| 18 | + Evalue::String(v) => v != "" && v != "0", |
| 19 | + Evalue::Number(n) => *n != 0.0, |
| 20 | + Evalue::Bool(b) => *b, |
| 21 | + } |
| 22 | + } |
| 23 | + |
| 24 | + fn to_string(&self) -> String { |
| 25 | + match self { |
| 26 | + Evalue::String(v) => v.clone(), |
| 27 | + Evalue::Number(n) => format!("{}", *n), |
| 28 | + Evalue::Bool(b) => match b { |
| 29 | + true => "true".to_string(), |
| 30 | + false => "false".to_string(), |
| 31 | + }, |
| 32 | + } |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +impl Evaluator { |
| 37 | + pub fn new() -> Evaluator { |
| 38 | + Evaluator {} |
| 39 | + } |
| 40 | + |
| 41 | + pub fn parse(&mut self, expr: &str) -> Result<Expr, String> { |
| 42 | + Parser::new(expr).parse() |
| 43 | + } |
| 44 | + |
| 45 | + pub fn parse_and_evaluate(&mut self, expr: &str, vars: &Vars) -> Result<bool, String> { |
| 46 | + let expr = Parser::new(expr).parse()?; |
| 47 | + self.evaluate(&expr, vars) |
| 48 | + } |
| 49 | + |
| 50 | + pub fn evaluate(&self, expr: &Expr, vars: &Vars) -> Result<bool, String> { |
| 51 | + match self.evaluate_expr(&expr, vars)? { |
| 52 | + Evalue::String(_) | Evalue::Number(_) => { |
| 53 | + Err(format!("expression must evluate to a boolean, got: {:?}", expr).to_string()) |
| 54 | + } |
| 55 | + Evalue::Bool(b) => Ok(b), |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + fn evaluate_expr(&self, expr: &super::parser::Expr, vars: &Vars) -> Result<Evalue, String> { |
| 60 | + match expr { |
| 61 | + super::parser::Expr::Boolean(b) => Ok(Evalue::Bool(*b)), |
| 62 | + super::parser::Expr::String(s) => Ok(Evalue::String(s.clone())), |
| 63 | + super::parser::Expr::Binary(lexpr, op, rexpr) => { |
| 64 | + let lval = self.evaluate_expr(lexpr, vars)?; |
| 65 | + let rval = self.evaluate_expr(rexpr, vars)?; |
| 66 | + let eval = match op { |
| 67 | + super::lexer::TokenKind::GreaterThan => Ok(lval > rval), |
| 68 | + super::lexer::TokenKind::GreaterThanEqual => Ok(lval >= rval), |
| 69 | + super::lexer::TokenKind::LessThanEqual => Ok(lval <= rval), |
| 70 | + super::lexer::TokenKind::LessThan => Ok(lval < rval), |
| 71 | + super::lexer::TokenKind::Equal => Ok(lval == rval), |
| 72 | + super::lexer::TokenKind::FuzzyEqual => Ok(lval.to_string().contains(rval.to_string().as_str())), |
| 73 | + super::lexer::TokenKind::NotEqual => Ok(lval != rval), |
| 74 | + super::lexer::TokenKind::NotFuzzyEqual => Ok(!lval.to_string().contains(rval.to_string().as_str())), |
| 75 | + super::lexer::TokenKind::Or => Ok(lval.to_bool() || rval.to_bool()), |
| 76 | + super::lexer::TokenKind::And => Ok(lval.to_bool() && rval.to_bool()), |
| 77 | + _ => Err(format!("unknown operator: {:?}", op)), |
| 78 | + }?; |
| 79 | + Ok(Evalue::Bool(eval)) |
| 80 | + } |
| 81 | + super::parser::Expr::Number(n) => Ok(Evalue::Number(*n)), |
| 82 | + super::parser::Expr::Variable(v) => match vars.get(v) { |
| 83 | + Some(v) => Ok(v.clone()), |
| 84 | + None => Err(format!("Unknown variable `{}`", v)), |
| 85 | + }, |
| 86 | + } |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +#[cfg(test)] |
| 91 | +mod test { |
| 92 | + use super::*; |
| 93 | + |
| 94 | + #[test] |
| 95 | + fn test_evaluate() { |
| 96 | + let result = Evaluator::new().parse_and_evaluate("false", &HashMap::new()); |
| 97 | + assert_eq!(false, result.unwrap()); |
| 98 | + let result = Evaluator::new().parse_and_evaluate("20 > 10", &HashMap::new()); |
| 99 | + |
| 100 | + assert_eq!(true, result.unwrap()); |
| 101 | + |
| 102 | + let result = Evaluator::new().parse_and_evaluate("20 > 10 and false", &HashMap::new()); |
| 103 | + |
| 104 | + assert_eq!(false, result.unwrap()); |
| 105 | + } |
| 106 | + |
| 107 | + #[test] |
| 108 | + fn test_evaluate_params() { |
| 109 | + let map = HashMap::from([ |
| 110 | + ("distance".to_string(), Evalue::Number(10.0)), |
| 111 | + ("type".to_string(), Evalue::String("Run".to_string())), |
| 112 | + ]); |
| 113 | + let result = Evaluator::new().parse_and_evaluate("distance > 5", &map); |
| 114 | + assert_eq!(true, result.unwrap()); |
| 115 | + let result = Evaluator::new().parse_and_evaluate("distance < 5", &map); |
| 116 | + assert_eq!(false, result.unwrap()); |
| 117 | + let result = Evaluator::new().parse_and_evaluate("distance = 10", &map); |
| 118 | + assert_eq!(true, result.unwrap()); |
| 119 | + let result = Evaluator::new().parse_and_evaluate("type = 'Run'", &map); |
| 120 | + assert_eq!(true, result.unwrap()); |
| 121 | + let result = Evaluator::new().parse_and_evaluate("type ~ 'Ru'", &map); |
| 122 | + assert_eq!(true, result.unwrap()); |
| 123 | + let result = Evaluator::new().parse_and_evaluate("type !~ 'Rup'", &map); |
| 124 | + assert_eq!(true, result.unwrap()); |
| 125 | + let result = Evaluator::new().parse_and_evaluate("type != 'Run'", &map); |
| 126 | + assert_eq!(false, result.unwrap()); |
| 127 | + } |
| 128 | +} |
0 commit comments