rust_rule_engine/
expression.rs

1///! Expression Evaluator
2///!
3///! This module provides runtime evaluation of arithmetic expressions
4///! similar to CLIPS (bind ?total (* ?quantity ?price))
5
6use crate::types::Value;
7use crate::errors::{Result, RuleEngineError};
8use crate::engine::facts::Facts;
9
10/// Evaluate an arithmetic expression with field references
11/// Example: "Order.quantity * Order.price" with facts containing Order.quantity=10, Order.price=100
12/// Returns: Value::Integer(1000) or Value::Number(1000.0)
13pub fn evaluate_expression(expr: &str, facts: &Facts) -> Result<Value> {
14    let expr = expr.trim();
15
16    // Try to evaluate as simple arithmetic expression
17    // Support: +, -, *, /, %
18
19    // Find the operator (right to left for correct precedence)
20    // Precedence: *, /, % (higher) then +, - (lower)
21
22    // First pass: look for + or - (lowest precedence)
23    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    // Second pass: look for *, /, % (higher precedence)
35    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    // No operator found - must be a single value
47    // Could be: field reference (Order.quantity), number (100), or variable
48
49    // Try to parse as number first
50    if let Ok(int_val) = expr.parse::<i64>() {
51        return Ok(Value::Integer(int_val));
52    }
53
54    if let Ok(float_val) = expr.parse::<f64>() {
55        return Ok(Value::Number(float_val));
56    }
57
58    // Must be a field reference - get from facts
59    if let Some(value) = facts.get(expr) {
60        return Ok(value.clone());
61    }
62
63    // Field not found - return error
64    Err(RuleEngineError::EvaluationError {
65        message: format!("Field '{}' not found in facts", expr),
66    })
67}
68
69/// Find position of operator, skipping parentheses
70/// Returns rightmost occurrence for left-to-right evaluation
71fn find_operator(expr: &str, operators: &[char]) -> Option<usize> {
72    let mut paren_depth = 0;
73    let mut last_pos = None;
74
75    for (i, ch) in expr.chars().enumerate() {
76        match ch {
77            '(' => paren_depth += 1,
78            ')' => paren_depth -= 1,
79            _ if paren_depth == 0 && operators.contains(&ch) => {
80                last_pos = Some(i);
81            }
82            _ => {}
83        }
84    }
85
86    last_pos
87}
88
89/// Apply arithmetic operator to two values
90fn apply_operator(left: &Value, op: &str, right: &Value) -> Result<Value> {
91    // Convert to numbers
92    let left_num = value_to_number(left)?;
93    let right_num = value_to_number(right)?;
94
95    let result = match op {
96        "+" => left_num + right_num,
97        "-" => left_num - right_num,
98        "*" => left_num * right_num,
99        "/" => {
100            if right_num == 0.0 {
101                return Err(RuleEngineError::EvaluationError {
102                    message: "Division by zero".to_string(),
103                });
104            }
105            left_num / right_num
106        }
107        "%" => left_num % right_num,
108        _ => {
109            return Err(RuleEngineError::EvaluationError {
110                message: format!("Unknown operator: {}", op),
111            });
112        }
113    };
114
115    // Return integer if both operands were integers and result is whole number
116    if is_integer_value(left) && is_integer_value(right) && result.fract() == 0.0 {
117        Ok(Value::Integer(result as i64))
118    } else {
119        Ok(Value::Number(result))
120    }
121}
122
123/// Convert Value to f64 for arithmetic
124fn value_to_number(value: &Value) -> Result<f64> {
125    match value {
126        Value::Integer(i) => Ok(*i as f64),
127        Value::Number(n) => Ok(*n),
128        Value::String(s) => {
129            s.parse::<f64>().map_err(|_| RuleEngineError::EvaluationError {
130                message: format!("Cannot convert '{}' to number", s),
131            })
132        }
133        _ => Err(RuleEngineError::EvaluationError {
134            message: format!("Cannot convert {:?} to number", value),
135        }),
136    }
137}
138
139/// Check if Value represents an integer
140fn is_integer_value(value: &Value) -> bool {
141    matches!(value, Value::Integer(_))
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_simple_arithmetic() {
150        let facts = Facts::new();
151
152        assert_eq!(
153            evaluate_expression("10 + 20", &facts).unwrap(),
154            Value::Integer(30)
155        );
156
157        assert_eq!(
158            evaluate_expression("100 - 25", &facts).unwrap(),
159            Value::Integer(75)
160        );
161
162        assert_eq!(
163            evaluate_expression("5 * 6", &facts).unwrap(),
164            Value::Integer(30)
165        );
166
167        assert_eq!(
168            evaluate_expression("100 / 4", &facts).unwrap(),
169            Value::Integer(25)
170        );
171    }
172
173    #[test]
174    fn test_field_references() {
175        let mut facts = Facts::new();
176        facts.set("Order.quantity", Value::Integer(10));
177        facts.set("Order.price", Value::Integer(100));
178
179        assert_eq!(
180            evaluate_expression("Order.quantity * Order.price", &facts).unwrap(),
181            Value::Integer(1000)
182        );
183    }
184
185    #[test]
186    fn test_mixed_operations() {
187        let mut facts = Facts::new();
188        facts.set("a", Value::Integer(10));
189        facts.set("b", Value::Integer(5));
190        facts.set("c", Value::Integer(2));
191
192        // 10 + 5 * 2 = 10 + 10 = 20
193        assert_eq!(
194            evaluate_expression("a + b * c", &facts).unwrap(),
195            Value::Integer(20)
196        );
197    }
198}