rust_rule_engine/
expression.rs1use crate::engine::facts::Facts;
7use crate::errors::{Result, RuleEngineError};
8use crate::types::Value;
9
10pub fn evaluate_expression(expr: &str, facts: &Facts) -> Result<Value> {
14 let expr = expr.trim();
15
16 if let Some(pos) = find_operator(expr, &['+', '-']) {
24 let left = &expr[..pos].trim();
25 let op = &expr[pos..pos + 1];
26 let right = &expr[pos + 1..].trim();
27
28 let left_val = evaluate_expression(left, facts)?;
29 let right_val = evaluate_expression(right, facts)?;
30
31 return apply_operator(&left_val, op, &right_val);
32 }
33
34 if let Some(pos) = find_operator(expr, &['*', '/', '%']) {
36 let left = &expr[..pos].trim();
37 let op = &expr[pos..pos + 1];
38 let right = &expr[pos + 1..].trim();
39
40 let left_val = evaluate_expression(left, facts)?;
41 let right_val = evaluate_expression(right, facts)?;
42
43 return apply_operator(&left_val, op, &right_val);
44 }
45
46 if expr.len() >= 2 {
51 let unquoted = &expr[1..expr.len() - 1];
52 if (expr.starts_with('"') && expr.ends_with('"') && !unquoted.contains('"'))
53 || (expr.starts_with('\'') && expr.ends_with('\'') && !unquoted.contains('\''))
54 {
55 let unquoted = &expr[1..expr.len() - 1];
56 return Ok(Value::String(unquoted.to_string()));
57 }
58 }
59
60 if let Ok(int_val) = expr.parse::<i64>() {
62 return Ok(Value::Integer(int_val));
63 }
64
65 if let Ok(float_val) = expr.parse::<f64>() {
66 return Ok(Value::Number(float_val));
67 }
68
69 if let Some(value) = facts.get(expr) {
71 return Ok(value.clone());
72 }
73
74 Err(RuleEngineError::EvaluationError {
76 message: format!("Field '{}' not found in facts", expr),
77 })
78}
79
80fn find_operator(expr: &str, operators: &[char]) -> Option<usize> {
83 let mut paren_depth = 0;
84 let mut last_pos = None;
85
86 for (i, ch) in expr.chars().enumerate() {
87 match ch {
88 '(' => paren_depth += 1,
89 ')' => paren_depth -= 1,
90 _ if paren_depth == 0 && operators.contains(&ch) => {
91 last_pos = Some(i);
92 }
93 _ => {}
94 }
95 }
96
97 last_pos
98}
99
100fn apply_operator(left: &Value, op: &str, right: &Value) -> Result<Value> {
102 let left_num = value_to_number(left);
104 let right_num = value_to_number(right);
105
106 if op == "+" && (left_num.is_err() || right_num.is_err()) {
108 let concatenated = match (left, right) {
110 (Value::String(s1), Value::String(s2)) => format!("{}{}", s1, s2),
112 _ => {
114 return Err(RuleEngineError::EvaluationError {
115 message: "Only strings can be concatenated".to_string(),
116 })
117 }
118 };
119 return Ok(Value::String(concatenated));
120 }
121
122 let left_num = left_num.unwrap();
123 let right_num = right_num.unwrap();
124
125 let result = match op {
126 "+" => left_num + right_num,
127 "-" => left_num - right_num,
128 "*" => left_num * right_num,
129 "/" => {
130 if right_num == 0.0 {
131 return Err(RuleEngineError::EvaluationError {
132 message: "Division by zero".to_string(),
133 });
134 }
135 left_num / right_num
136 }
137 "%" => left_num % right_num,
138 _ => {
139 return Err(RuleEngineError::EvaluationError {
140 message: format!("Unknown operator: {}", op),
141 });
142 }
143 };
144
145 if is_integer_value(left) && is_integer_value(right) && result.fract() == 0.0 {
147 Ok(Value::Integer(result as i64))
148 } else {
149 Ok(Value::Number(result))
150 }
151}
152
153fn value_to_number(value: &Value) -> Result<f64> {
155 match value {
156 Value::Integer(i) => Ok(*i as f64),
157 Value::Number(n) => Ok(*n),
158 Value::String(s) => s
159 .parse::<f64>()
160 .map_err(|_| RuleEngineError::EvaluationError {
161 message: format!("Cannot convert '{}' to number", s),
162 }),
163 _ => Err(RuleEngineError::EvaluationError {
164 message: format!("Cannot convert {:?} to number", value),
165 }),
166 }
167}
168
169fn is_integer_value(value: &Value) -> bool {
171 matches!(value, Value::Integer(_))
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_simple_arithmetic() {
180 let facts = Facts::new();
181
182 assert_eq!(
183 evaluate_expression("10 + 20", &facts).unwrap(),
184 Value::Integer(30)
185 );
186
187 assert_eq!(
188 evaluate_expression("100 - 25", &facts).unwrap(),
189 Value::Integer(75)
190 );
191
192 assert_eq!(
193 evaluate_expression("5 * 6", &facts).unwrap(),
194 Value::Integer(30)
195 );
196
197 assert_eq!(
198 evaluate_expression("100 / 4", &facts).unwrap(),
199 Value::Integer(25)
200 );
201 }
202
203 #[test]
204 fn test_field_references() {
205 let facts = Facts::new();
206 facts.set("Order.quantity", Value::Integer(10));
207 facts.set("Order.price", Value::Integer(100));
208
209 assert_eq!(
210 evaluate_expression("Order.quantity * Order.price", &facts).unwrap(),
211 Value::Integer(1000)
212 );
213 }
214
215 #[test]
216 fn test_mixed_operations() {
217 let facts = Facts::new();
218 facts.set("a", Value::Integer(10));
219 facts.set("b", Value::Integer(5));
220 facts.set("c", Value::Integer(2));
221
222 assert_eq!(
224 evaluate_expression("a + b * c", &facts).unwrap(),
225 Value::Integer(20)
226 );
227 }
228}