Skip to main content

showcase_calculator/core/
mod.rs

1//! Core calculator module with 100% test coverage
2//!
3//! Probar Principles:
4//! - Error prevention: Type-safe operations prevent invalid states
5//! - Anomaly: Automatic anomaly detection
6
7pub mod evaluator;
8pub mod history;
9mod operations;
10pub mod parser;
11
12pub use operations::{Calculator, Operation};
13
14use std::collections::VecDeque;
15
16/// Result type for calculator operations
17pub type CalcResult<T> = Result<T, CalcError>;
18
19/// Calculator error types - exhaustive enum ensures all cases handled
20#[derive(Debug, Clone, PartialEq)]
21pub enum CalcError {
22    /// Division by zero attempted
23    DivisionByZero,
24    /// Result overflowed (infinity)
25    Overflow,
26    /// Invalid expression syntax
27    ParseError(String),
28    /// Empty expression provided
29    EmptyExpression,
30    /// Invalid result (NaN or other)
31    InvalidResult(String),
32    /// Anomaly violation detected
33    AnomalyViolation(AnomalyViolation),
34}
35
36impl std::fmt::Display for CalcError {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            Self::DivisionByZero => write!(f, "Division by zero"),
40            Self::Overflow => write!(f, "Overflow: result exceeds maximum value"),
41            Self::ParseError(msg) => write!(f, "Invalid expression: {msg}"),
42            Self::EmptyExpression => write!(f, "Empty expression"),
43            Self::InvalidResult(msg) => write!(f, "Invalid result: {msg}"),
44            Self::AnomalyViolation(v) => write!(f, "Anomaly violation: {v}"),
45        }
46    }
47}
48
49impl std::error::Error for CalcError {}
50
51/// Anomaly violation types - anomalies detected during calculation
52#[derive(Debug, Clone, PartialEq)]
53pub enum AnomalyViolation {
54    /// NaN detected in result
55    NaN,
56    /// Infinity detected in result
57    Infinite,
58    /// Result exceeds maximum magnitude
59    Overflow(f64),
60}
61
62impl std::fmt::Display for AnomalyViolation {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            Self::NaN => write!(f, "NaN detected"),
66            Self::Infinite => write!(f, "Infinite value detected"),
67            Self::Overflow(v) => write!(f, "Overflow: {v} exceeds maximum magnitude"),
68        }
69    }
70}
71
72/// Anomaly validator - automatic anomaly detection by Probar
73///
74/// Implements the Anomaly principle: "Automation with human touch"
75/// Detects anomalies and stops the line before defects propagate.
76#[derive(Debug, Clone)]
77pub struct AnomalyValidator {
78    /// Maximum allowed result magnitude
79    pub max_magnitude: f64,
80    /// Detect NaN/Infinity
81    pub check_special_values: bool,
82    /// History of recent results for drift detection
83    history: VecDeque<f64>,
84    /// Maximum history size
85    max_history: usize,
86}
87
88impl Default for AnomalyValidator {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl AnomalyValidator {
95    /// Default maximum magnitude (f64::MAX / 2 for safety margin)
96    pub const DEFAULT_MAX_MAGNITUDE: f64 = 1e100;
97
98    /// Creates a new validator with default settings
99    #[must_use]
100    pub fn new() -> Self {
101        Self {
102            max_magnitude: Self::DEFAULT_MAX_MAGNITUDE,
103            check_special_values: true,
104            history: VecDeque::new(),
105            max_history: 100,
106        }
107    }
108
109    /// Creates a validator with custom maximum magnitude
110    #[must_use]
111    pub fn with_max_magnitude(max_magnitude: f64) -> Self {
112        Self {
113            max_magnitude,
114            check_special_values: true,
115            history: VecDeque::new(),
116            max_history: 100,
117        }
118    }
119
120    /// Validates a calculation result (Anomaly check)
121    ///
122    /// Returns the result if valid, or a violation describing the anomaly.
123    pub fn validate(&mut self, result: f64) -> Result<f64, AnomalyViolation> {
124        // Check for NaN
125        if self.check_special_values && result.is_nan() {
126            return Err(AnomalyViolation::NaN);
127        }
128
129        // Check for Infinity
130        if self.check_special_values && result.is_infinite() {
131            return Err(AnomalyViolation::Infinite);
132        }
133
134        // Check magnitude bounds
135        if result.abs() > self.max_magnitude {
136            return Err(AnomalyViolation::Overflow(result));
137        }
138
139        // Record in history for drift detection
140        self.record_result(result);
141
142        Ok(result)
143    }
144
145    /// Records a result in history
146    fn record_result(&mut self, result: f64) {
147        if self.history.len() >= self.max_history {
148            self.history.pop_front();
149        }
150        self.history.push_back(result);
151    }
152
153    /// Returns the history of recent results
154    #[must_use]
155    pub fn history(&self) -> &VecDeque<f64> {
156        &self.history
157    }
158
159    /// Clears the history
160    pub fn clear_history(&mut self) {
161        self.history.clear();
162    }
163
164    /// Returns the number of results in history
165    #[must_use]
166    pub fn history_len(&self) -> usize {
167        self.history.len()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    // ===== CalcError tests =====
176
177    #[test]
178    fn test_calc_error_display_division_by_zero() {
179        let err = CalcError::DivisionByZero;
180        assert_eq!(format!("{err}"), "Division by zero");
181    }
182
183    #[test]
184    fn test_calc_error_display_overflow() {
185        let err = CalcError::Overflow;
186        assert_eq!(format!("{err}"), "Overflow: result exceeds maximum value");
187    }
188
189    #[test]
190    fn test_calc_error_display_parse_error() {
191        let err = CalcError::ParseError("unexpected token".into());
192        assert_eq!(format!("{err}"), "Invalid expression: unexpected token");
193    }
194
195    #[test]
196    fn test_calc_error_display_empty_expression() {
197        let err = CalcError::EmptyExpression;
198        assert_eq!(format!("{err}"), "Empty expression");
199    }
200
201    #[test]
202    fn test_calc_error_display_invalid_result() {
203        let err = CalcError::InvalidResult("NaN".into());
204        assert_eq!(format!("{err}"), "Invalid result: NaN");
205    }
206
207    #[test]
208    fn test_calc_error_display_jidoka_violation() {
209        let err = CalcError::AnomalyViolation(AnomalyViolation::NaN);
210        assert_eq!(format!("{err}"), "Anomaly violation: NaN detected");
211    }
212
213    #[test]
214    fn test_calc_error_is_error_trait() {
215        let err: Box<dyn std::error::Error> = Box::new(CalcError::DivisionByZero);
216        assert!(err.to_string().contains("Division"));
217    }
218
219    // ===== AnomalyViolation tests =====
220
221    #[test]
222    fn test_jidoka_violation_display_nan() {
223        let v = AnomalyViolation::NaN;
224        assert_eq!(format!("{v}"), "NaN detected");
225    }
226
227    #[test]
228    fn test_jidoka_violation_display_infinite() {
229        let v = AnomalyViolation::Infinite;
230        assert_eq!(format!("{v}"), "Infinite value detected");
231    }
232
233    #[test]
234    fn test_jidoka_violation_display_overflow() {
235        let v = AnomalyViolation::Overflow(1e200);
236        assert!(format!("{v}").contains("exceeds maximum magnitude"));
237    }
238
239    // ===== AnomalyValidator tests =====
240
241    #[test]
242    fn test_jidoka_validator_new() {
243        let v = AnomalyValidator::new();
244        assert_eq!(v.max_magnitude, AnomalyValidator::DEFAULT_MAX_MAGNITUDE);
245        assert!(v.check_special_values);
246        assert!(v.history.is_empty());
247    }
248
249    #[test]
250    fn test_jidoka_validator_default() {
251        let v = AnomalyValidator::default();
252        assert_eq!(v.max_magnitude, AnomalyValidator::DEFAULT_MAX_MAGNITUDE);
253    }
254
255    #[test]
256    fn test_jidoka_validator_with_max_magnitude() {
257        let v = AnomalyValidator::with_max_magnitude(100.0);
258        assert_eq!(v.max_magnitude, 100.0);
259    }
260
261    #[test]
262    fn test_jidoka_validate_valid_result() {
263        let mut v = AnomalyValidator::new();
264        assert_eq!(v.validate(42.0), Ok(42.0));
265    }
266
267    #[test]
268    fn test_jidoka_validate_nan() {
269        let mut v = AnomalyValidator::new();
270        assert_eq!(v.validate(f64::NAN), Err(AnomalyViolation::NaN));
271    }
272
273    #[test]
274    fn test_jidoka_validate_positive_infinity() {
275        let mut v = AnomalyValidator::new();
276        assert_eq!(v.validate(f64::INFINITY), Err(AnomalyViolation::Infinite));
277    }
278
279    #[test]
280    fn test_jidoka_validate_negative_infinity() {
281        let mut v = AnomalyValidator::new();
282        assert_eq!(
283            v.validate(f64::NEG_INFINITY),
284            Err(AnomalyViolation::Infinite)
285        );
286    }
287
288    #[test]
289    fn test_jidoka_validate_overflow_positive() {
290        let mut v = AnomalyValidator::with_max_magnitude(100.0);
291        let result = v.validate(150.0);
292        assert!(matches!(result, Err(AnomalyViolation::Overflow(_))));
293    }
294
295    #[test]
296    fn test_jidoka_validate_overflow_negative() {
297        let mut v = AnomalyValidator::with_max_magnitude(100.0);
298        let result = v.validate(-150.0);
299        assert!(matches!(result, Err(AnomalyViolation::Overflow(_))));
300    }
301
302    #[test]
303    fn test_jidoka_validate_at_boundary() {
304        let mut v = AnomalyValidator::with_max_magnitude(100.0);
305        assert_eq!(v.validate(100.0), Ok(100.0));
306        assert_eq!(v.validate(-100.0), Ok(-100.0));
307    }
308
309    #[test]
310    fn test_jidoka_history_recording() {
311        let mut v = AnomalyValidator::new();
312        v.validate(1.0).unwrap();
313        v.validate(2.0).unwrap();
314        v.validate(3.0).unwrap();
315        assert_eq!(v.history_len(), 3);
316        assert_eq!(
317            v.history().iter().copied().collect::<Vec<_>>(),
318            vec![1.0, 2.0, 3.0]
319        );
320    }
321
322    #[test]
323    fn test_jidoka_history_clear() {
324        let mut v = AnomalyValidator::new();
325        v.validate(1.0).unwrap();
326        v.validate(2.0).unwrap();
327        v.clear_history();
328        assert_eq!(v.history_len(), 0);
329    }
330
331    #[test]
332    fn test_jidoka_history_max_size() {
333        let mut v = AnomalyValidator::new();
334        v.max_history = 3;
335        for i in 0..5 {
336            v.validate(i as f64).unwrap();
337        }
338        assert_eq!(v.history_len(), 3);
339        assert_eq!(
340            v.history().iter().copied().collect::<Vec<_>>(),
341            vec![2.0, 3.0, 4.0]
342        );
343    }
344
345    #[test]
346    fn test_jidoka_special_values_disabled() {
347        let mut v = AnomalyValidator::new();
348        v.check_special_values = false;
349        // NaN still recorded but not rejected
350        assert!(v.validate(f64::NAN).is_ok());
351    }
352}