Skip to main content

aprender_test_showcase/core/
evaluator.rs

1//! AST evaluator with 100% test coverage
2//!
3//! Probar: Anomaly detection - Automatic anomaly detection during evaluation
4
5use crate::core::parser::AstNode;
6use crate::core::{AnomalyValidator, CalcResult, Calculator, Operation};
7
8/// Evaluator for AST expressions
9#[derive(Debug)]
10pub struct Evaluator {
11    calculator: Calculator,
12}
13
14impl Default for Evaluator {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl Evaluator {
21    /// Creates a new evaluator with default settings
22    #[must_use]
23    pub fn new() -> Self {
24        Self {
25            calculator: Calculator::new(),
26        }
27    }
28
29    /// Creates an evaluator with custom Anomaly validator
30    #[must_use]
31    pub fn with_validator(validator: AnomalyValidator) -> Self {
32        Self {
33            calculator: Calculator::with_validator(validator),
34        }
35    }
36
37    /// Evaluates an AST node and returns the result
38    pub fn evaluate(&mut self, node: &AstNode) -> CalcResult<f64> {
39        match node {
40            AstNode::Number(n) => Ok(*n),
41            AstNode::Negate(inner) => {
42                let value = self.evaluate(inner)?;
43                // Negation is multiplication by -1
44                self.calculator.calculate(value, -1.0, Operation::Multiply)
45            }
46            AstNode::BinaryOp { left, op, right } => {
47                let left_val = self.evaluate(left)?;
48                let right_val = self.evaluate(right)?;
49                self.calculator.calculate(left_val, right_val, *op)
50            }
51        }
52    }
53
54    /// Evaluates a string expression
55    pub fn evaluate_str(&mut self, input: &str) -> CalcResult<f64> {
56        use crate::core::parser::Parser;
57        let ast = Parser::parse_str(input)?;
58        self.evaluate(&ast)
59    }
60
61    /// Returns a reference to the calculator's Anomaly validator history
62    #[must_use]
63    pub fn history(&self) -> &std::collections::VecDeque<f64> {
64        self.calculator.validator.history()
65    }
66
67    /// Clears the Anomaly validator history
68    pub fn clear_history(&mut self) {
69        self.calculator.validator.clear_history();
70    }
71
72    /// Returns a reference to the Anomaly validator
73    #[must_use]
74    pub fn validator(&self) -> Option<&AnomalyValidator> {
75        Some(&self.calculator.validator)
76    }
77}
78
79#[cfg(test)]
80#[allow(clippy::unwrap_used)]
81mod tests {
82    use super::*;
83    use crate::core::CalcError;
84
85    // ===== Basic evaluation tests =====
86
87    #[test]
88    fn test_evaluate_number() {
89        let mut eval = Evaluator::new();
90        let ast = AstNode::number(42.0);
91        assert_eq!(eval.evaluate(&ast), Ok(42.0));
92    }
93
94    #[test]
95    fn test_evaluate_negative_number() {
96        let mut eval = Evaluator::new();
97        let ast = AstNode::negate(AstNode::number(5.0));
98        assert_eq!(eval.evaluate(&ast), Ok(-5.0));
99    }
100
101    #[test]
102    fn test_evaluate_double_negative() {
103        let mut eval = Evaluator::new();
104        let ast = AstNode::negate(AstNode::negate(AstNode::number(5.0)));
105        assert_eq!(eval.evaluate(&ast), Ok(5.0));
106    }
107
108    #[test]
109    fn test_evaluate_addition() {
110        let mut eval = Evaluator::new();
111        let ast = AstNode::binary(AstNode::number(2.0), Operation::Add, AstNode::number(3.0));
112        assert_eq!(eval.evaluate(&ast), Ok(5.0));
113    }
114
115    #[test]
116    fn test_evaluate_subtraction() {
117        let mut eval = Evaluator::new();
118        let ast = AstNode::binary(
119            AstNode::number(5.0),
120            Operation::Subtract,
121            AstNode::number(3.0),
122        );
123        assert_eq!(eval.evaluate(&ast), Ok(2.0));
124    }
125
126    #[test]
127    fn test_evaluate_multiplication() {
128        let mut eval = Evaluator::new();
129        let ast = AstNode::binary(
130            AstNode::number(4.0),
131            Operation::Multiply,
132            AstNode::number(3.0),
133        );
134        assert_eq!(eval.evaluate(&ast), Ok(12.0));
135    }
136
137    #[test]
138    fn test_evaluate_division() {
139        let mut eval = Evaluator::new();
140        let ast = AstNode::binary(
141            AstNode::number(12.0),
142            Operation::Divide,
143            AstNode::number(4.0),
144        );
145        assert_eq!(eval.evaluate(&ast), Ok(3.0));
146    }
147
148    #[test]
149    fn test_evaluate_modulo() {
150        let mut eval = Evaluator::new();
151        let ast = AstNode::binary(
152            AstNode::number(7.0),
153            Operation::Modulo,
154            AstNode::number(3.0),
155        );
156        assert_eq!(eval.evaluate(&ast), Ok(1.0));
157    }
158
159    #[test]
160    fn test_evaluate_power() {
161        let mut eval = Evaluator::new();
162        let ast = AstNode::binary(AstNode::number(2.0), Operation::Power, AstNode::number(3.0));
163        assert_eq!(eval.evaluate(&ast), Ok(8.0));
164    }
165
166    // ===== Complex expression tests =====
167
168    #[test]
169    fn test_evaluate_nested_expression() {
170        let mut eval = Evaluator::new();
171        // (2 + 3) * 4 = 20
172        let ast = AstNode::binary(
173            AstNode::binary(AstNode::number(2.0), Operation::Add, AstNode::number(3.0)),
174            Operation::Multiply,
175            AstNode::number(4.0),
176        );
177        assert_eq!(eval.evaluate(&ast), Ok(20.0));
178    }
179
180    #[test]
181    fn test_evaluate_deeply_nested() {
182        let mut eval = Evaluator::new();
183        // ((1 + 2) * (3 + 4)) = 3 * 7 = 21
184        let ast = AstNode::binary(
185            AstNode::binary(AstNode::number(1.0), Operation::Add, AstNode::number(2.0)),
186            Operation::Multiply,
187            AstNode::binary(AstNode::number(3.0), Operation::Add, AstNode::number(4.0)),
188        );
189        assert_eq!(eval.evaluate(&ast), Ok(21.0));
190    }
191
192    #[test]
193    fn test_evaluate_with_negative_in_expression() {
194        let mut eval = Evaluator::new();
195        // 5 + (-3) = 2
196        let ast = AstNode::binary(
197            AstNode::number(5.0),
198            Operation::Add,
199            AstNode::negate(AstNode::number(3.0)),
200        );
201        assert_eq!(eval.evaluate(&ast), Ok(2.0));
202    }
203
204    // ===== Error handling tests =====
205
206    #[test]
207    fn test_evaluate_division_by_zero() {
208        let mut eval = Evaluator::new();
209        let ast = AstNode::binary(
210            AstNode::number(10.0),
211            Operation::Divide,
212            AstNode::number(0.0),
213        );
214        assert_eq!(eval.evaluate(&ast), Err(CalcError::DivisionByZero));
215    }
216
217    #[test]
218    fn test_evaluate_modulo_by_zero() {
219        let mut eval = Evaluator::new();
220        let ast = AstNode::binary(
221            AstNode::number(10.0),
222            Operation::Modulo,
223            AstNode::number(0.0),
224        );
225        assert_eq!(eval.evaluate(&ast), Err(CalcError::DivisionByZero));
226    }
227
228    #[test]
229    fn test_evaluate_error_propagates_from_left() {
230        let mut eval = Evaluator::new();
231        // (10 / 0) + 5 - error in left operand
232        let ast = AstNode::binary(
233            AstNode::binary(
234                AstNode::number(10.0),
235                Operation::Divide,
236                AstNode::number(0.0),
237            ),
238            Operation::Add,
239            AstNode::number(5.0),
240        );
241        assert!(matches!(
242            eval.evaluate(&ast),
243            Err(CalcError::DivisionByZero)
244        ));
245    }
246
247    #[test]
248    fn test_evaluate_error_propagates_from_right() {
249        let mut eval = Evaluator::new();
250        // 5 + (10 / 0) - error in right operand
251        let ast = AstNode::binary(
252            AstNode::number(5.0),
253            Operation::Add,
254            AstNode::binary(
255                AstNode::number(10.0),
256                Operation::Divide,
257                AstNode::number(0.0),
258            ),
259        );
260        assert!(matches!(
261            eval.evaluate(&ast),
262            Err(CalcError::DivisionByZero)
263        ));
264    }
265
266    // ===== String evaluation tests =====
267
268    #[test]
269    fn test_evaluate_str_simple() {
270        let mut eval = Evaluator::new();
271        assert_eq!(eval.evaluate_str("2 + 3"), Ok(5.0));
272    }
273
274    #[test]
275    fn test_evaluate_str_complex() {
276        let mut eval = Evaluator::new();
277        assert_eq!(eval.evaluate_str("42 * (3 + 7)"), Ok(420.0));
278    }
279
280    #[test]
281    fn test_evaluate_str_power() {
282        let mut eval = Evaluator::new();
283        assert_eq!(eval.evaluate_str("2 ^ 3 ^ 2"), Ok(512.0)); // 2^(3^2) = 2^9
284    }
285
286    #[test]
287    fn test_evaluate_str_precedence() {
288        let mut eval = Evaluator::new();
289        assert_eq!(eval.evaluate_str("2 + 3 * 4"), Ok(14.0)); // 2 + (3*4)
290    }
291
292    #[test]
293    fn test_evaluate_str_unary_minus() {
294        let mut eval = Evaluator::new();
295        assert_eq!(eval.evaluate_str("-5"), Ok(-5.0));
296    }
297
298    #[test]
299    fn test_evaluate_str_empty() {
300        let mut eval = Evaluator::new();
301        assert!(matches!(
302            eval.evaluate_str(""),
303            Err(CalcError::EmptyExpression)
304        ));
305    }
306
307    #[test]
308    fn test_evaluate_str_invalid() {
309        let mut eval = Evaluator::new();
310        assert!(matches!(
311            eval.evaluate_str("2 +"),
312            Err(CalcError::ParseError(_))
313        ));
314    }
315
316    // ===== Anomaly validation tests =====
317
318    #[test]
319    fn test_evaluate_with_jidoka_overflow() {
320        let validator = AnomalyValidator::with_max_magnitude(100.0);
321        let mut eval = Evaluator::with_validator(validator);
322        let result = eval.evaluate_str("50 * 3"); // 150 > 100
323        assert!(matches!(result, Err(CalcError::AnomalyViolation(_))));
324    }
325
326    #[test]
327    fn test_evaluator_history() {
328        let mut eval = Evaluator::new();
329        // Use actual calculations that go through the validator
330        eval.evaluate_str("1 + 1").unwrap();
331        eval.evaluate_str("2 + 2").unwrap();
332        eval.evaluate_str("3 + 3").unwrap();
333        assert_eq!(eval.history().len(), 3);
334    }
335
336    #[test]
337    fn test_evaluator_clear_history() {
338        let mut eval = Evaluator::new();
339        eval.evaluate_str("1").unwrap();
340        eval.evaluate_str("2").unwrap();
341        eval.clear_history();
342        assert!(eval.history().is_empty());
343    }
344
345    // ===== Constructor tests =====
346
347    #[test]
348    fn test_evaluator_new() {
349        let eval = Evaluator::new();
350        assert!(eval.history().is_empty());
351    }
352
353    #[test]
354    fn test_evaluator_default() {
355        let eval = Evaluator::default();
356        assert!(eval.history().is_empty());
357    }
358
359    #[test]
360    fn test_evaluator_with_validator() {
361        let validator = AnomalyValidator::with_max_magnitude(50.0);
362        let mut eval = Evaluator::with_validator(validator);
363        // Should fail because 100 > 50
364        let result = eval.evaluate_str("10 * 10");
365        assert!(matches!(result, Err(CalcError::AnomalyViolation(_))));
366    }
367
368    // ===== Integration tests =====
369
370    #[test]
371    fn test_evaluate_all_operations() {
372        let mut eval = Evaluator::new();
373
374        // Addition
375        assert_eq!(eval.evaluate_str("10 + 5"), Ok(15.0));
376
377        // Subtraction
378        assert_eq!(eval.evaluate_str("10 - 3"), Ok(7.0));
379
380        // Multiplication
381        assert_eq!(eval.evaluate_str("6 * 7"), Ok(42.0));
382
383        // Division
384        assert_eq!(eval.evaluate_str("20 / 4"), Ok(5.0));
385
386        // Modulo
387        assert_eq!(eval.evaluate_str("17 % 5"), Ok(2.0));
388
389        // Power
390        assert_eq!(eval.evaluate_str("3 ^ 4"), Ok(81.0));
391    }
392
393    #[test]
394    fn test_evaluate_complex_real_world() {
395        let mut eval = Evaluator::new();
396
397        // Quadratic: (b^2 - 4*a*c) for a=1, b=5, c=6
398        // discriminant = 25 - 24 = 1
399        assert_eq!(eval.evaluate_str("5 ^ 2 - 4 * 1 * 6"), Ok(1.0));
400    }
401}