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).or_else(|| facts.get_nested(expr)) {
72 return Ok(value);
73 }
74
75 Err(RuleEngineError::EvaluationError {
77 message: format!("Field '{}' not found in facts", expr),
78 })
79}
80
81fn find_operator(expr: &str, operators: &[char]) -> Option<usize> {
84 let mut paren_depth = 0;
85 let mut last_pos = None;
86
87 for (i, ch) in expr.chars().enumerate() {
88 match ch {
89 '(' => paren_depth += 1,
90 ')' => paren_depth -= 1,
91 _ if paren_depth == 0 && operators.contains(&ch) => {
92 last_pos = Some(i);
93 }
94 _ => {}
95 }
96 }
97
98 last_pos
99}
100
101fn apply_operator(left: &Value, op: &str, right: &Value) -> Result<Value> {
103 let left_num = value_to_number(left);
105 let right_num = value_to_number(right);
106
107 if op == "+" && (left_num.is_err() || right_num.is_err()) {
109 let concatenated = match (left, right) {
111 (Value::String(s1), Value::String(s2)) => format!("{}{}", s1, s2),
113 _ => {
115 return Err(RuleEngineError::EvaluationError {
116 message: "Only strings can be concatenated".to_string(),
117 })
118 }
119 };
120 return Ok(Value::String(concatenated));
121 }
122
123 let left_num = left_num.unwrap();
124 let right_num = right_num.unwrap();
125
126 let result = match op {
127 "+" => left_num + right_num,
128 "-" => left_num - right_num,
129 "*" => left_num * right_num,
130 "/" => {
131 if right_num == 0.0 {
132 return Err(RuleEngineError::EvaluationError {
133 message: "Division by zero".to_string(),
134 });
135 }
136 left_num / right_num
137 }
138 "%" => left_num % right_num,
139 _ => {
140 return Err(RuleEngineError::EvaluationError {
141 message: format!("Unknown operator: {}", op),
142 });
143 }
144 };
145
146 if is_integer_value(left) && is_integer_value(right) && result.fract() == 0.0 {
148 Ok(Value::Integer(result as i64))
149 } else {
150 Ok(Value::Number(result))
151 }
152}
153
154fn value_to_number(value: &Value) -> Result<f64> {
156 match value {
157 Value::Integer(i) => Ok(*i as f64),
158 Value::Number(n) => Ok(*n),
159 Value::String(s) => s
160 .parse::<f64>()
161 .map_err(|_| RuleEngineError::EvaluationError {
162 message: format!("Cannot convert '{}' to number", s),
163 }),
164 _ => Err(RuleEngineError::EvaluationError {
165 message: format!("Cannot convert {:?} to number", value),
166 }),
167 }
168}
169
170fn is_integer_value(value: &Value) -> bool {
172 matches!(value, Value::Integer(_))
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_simple_arithmetic() {
181 let facts = Facts::new();
182
183 assert_eq!(
184 evaluate_expression("10 + 20", &facts).unwrap(),
185 Value::Integer(30)
186 );
187
188 assert_eq!(
189 evaluate_expression("100 - 25", &facts).unwrap(),
190 Value::Integer(75)
191 );
192
193 assert_eq!(
194 evaluate_expression("5 * 6", &facts).unwrap(),
195 Value::Integer(30)
196 );
197
198 assert_eq!(
199 evaluate_expression("100 / 4", &facts).unwrap(),
200 Value::Integer(25)
201 );
202 }
203
204 #[test]
205 fn test_field_references() {
206 let facts = Facts::new();
207 facts.set("Order.quantity", Value::Integer(10));
208 facts.set("Order.price", Value::Integer(100));
209
210 assert_eq!(
211 evaluate_expression("Order.quantity * Order.price", &facts).unwrap(),
212 Value::Integer(1000)
213 );
214 }
215
216 #[test]
217 fn test_mixed_operations() {
218 let facts = Facts::new();
219 facts.set("a", Value::Integer(10));
220 facts.set("b", Value::Integer(5));
221 facts.set("c", Value::Integer(2));
222
223 assert_eq!(
225 evaluate_expression("a + b * c", &facts).unwrap(),
226 Value::Integer(20)
227 );
228 }
229}