pub mod evaluator;
pub mod history;
mod operations;
pub mod parser;
pub use operations::{Calculator, Operation};
use std::collections::VecDeque;
pub type CalcResult<T> = Result<T, CalcError>;
#[derive(Debug, Clone, PartialEq)]
pub enum CalcError {
DivisionByZero,
Overflow,
ParseError(String),
EmptyExpression,
InvalidResult(String),
AnomalyViolation(AnomalyViolation),
}
impl std::fmt::Display for CalcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DivisionByZero => write!(f, "Division by zero"),
Self::Overflow => write!(f, "Overflow: result exceeds maximum value"),
Self::ParseError(msg) => write!(f, "Invalid expression: {msg}"),
Self::EmptyExpression => write!(f, "Empty expression"),
Self::InvalidResult(msg) => write!(f, "Invalid result: {msg}"),
Self::AnomalyViolation(v) => write!(f, "Anomaly violation: {v}"),
}
}
}
impl std::error::Error for CalcError {}
#[derive(Debug, Clone, PartialEq)]
pub enum AnomalyViolation {
NaN,
Infinite,
Overflow(f64),
}
impl std::fmt::Display for AnomalyViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NaN => write!(f, "NaN detected"),
Self::Infinite => write!(f, "Infinite value detected"),
Self::Overflow(v) => write!(f, "Overflow: {v} exceeds maximum magnitude"),
}
}
}
#[derive(Debug, Clone)]
pub struct AnomalyValidator {
pub max_magnitude: f64,
pub check_special_values: bool,
history: VecDeque<f64>,
max_history: usize,
}
impl Default for AnomalyValidator {
fn default() -> Self {
Self::new()
}
}
impl AnomalyValidator {
pub const DEFAULT_MAX_MAGNITUDE: f64 = 1e100;
#[must_use]
pub fn new() -> Self {
Self {
max_magnitude: Self::DEFAULT_MAX_MAGNITUDE,
check_special_values: true,
history: VecDeque::new(),
max_history: 100,
}
}
#[must_use]
pub fn with_max_magnitude(max_magnitude: f64) -> Self {
Self {
max_magnitude,
check_special_values: true,
history: VecDeque::new(),
max_history: 100,
}
}
pub fn validate(&mut self, result: f64) -> Result<f64, AnomalyViolation> {
if self.check_special_values && result.is_nan() {
return Err(AnomalyViolation::NaN);
}
if self.check_special_values && result.is_infinite() {
return Err(AnomalyViolation::Infinite);
}
if result.abs() > self.max_magnitude {
return Err(AnomalyViolation::Overflow(result));
}
self.record_result(result);
Ok(result)
}
fn record_result(&mut self, result: f64) {
if self.history.len() >= self.max_history {
self.history.pop_front();
}
self.history.push_back(result);
}
#[must_use]
pub fn history(&self) -> &VecDeque<f64> {
&self.history
}
pub fn clear_history(&mut self) {
self.history.clear();
}
#[must_use]
pub fn history_len(&self) -> usize {
self.history.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calc_error_display_division_by_zero() {
let err = CalcError::DivisionByZero;
assert_eq!(format!("{err}"), "Division by zero");
}
#[test]
fn test_calc_error_display_overflow() {
let err = CalcError::Overflow;
assert_eq!(format!("{err}"), "Overflow: result exceeds maximum value");
}
#[test]
fn test_calc_error_display_parse_error() {
let err = CalcError::ParseError("unexpected token".into());
assert_eq!(format!("{err}"), "Invalid expression: unexpected token");
}
#[test]
fn test_calc_error_display_empty_expression() {
let err = CalcError::EmptyExpression;
assert_eq!(format!("{err}"), "Empty expression");
}
#[test]
fn test_calc_error_display_invalid_result() {
let err = CalcError::InvalidResult("NaN".into());
assert_eq!(format!("{err}"), "Invalid result: NaN");
}
#[test]
fn test_calc_error_display_jidoka_violation() {
let err = CalcError::AnomalyViolation(AnomalyViolation::NaN);
assert_eq!(format!("{err}"), "Anomaly violation: NaN detected");
}
#[test]
fn test_calc_error_is_error_trait() {
let err: Box<dyn std::error::Error> = Box::new(CalcError::DivisionByZero);
assert!(err.to_string().contains("Division"));
}
#[test]
fn test_jidoka_violation_display_nan() {
let v = AnomalyViolation::NaN;
assert_eq!(format!("{v}"), "NaN detected");
}
#[test]
fn test_jidoka_violation_display_infinite() {
let v = AnomalyViolation::Infinite;
assert_eq!(format!("{v}"), "Infinite value detected");
}
#[test]
fn test_jidoka_violation_display_overflow() {
let v = AnomalyViolation::Overflow(1e200);
assert!(format!("{v}").contains("exceeds maximum magnitude"));
}
#[test]
fn test_jidoka_validator_new() {
let v = AnomalyValidator::new();
assert_eq!(v.max_magnitude, AnomalyValidator::DEFAULT_MAX_MAGNITUDE);
assert!(v.check_special_values);
assert!(v.history.is_empty());
}
#[test]
fn test_jidoka_validator_default() {
let v = AnomalyValidator::default();
assert_eq!(v.max_magnitude, AnomalyValidator::DEFAULT_MAX_MAGNITUDE);
}
#[test]
fn test_jidoka_validator_with_max_magnitude() {
let v = AnomalyValidator::with_max_magnitude(100.0);
assert_eq!(v.max_magnitude, 100.0);
}
#[test]
fn test_jidoka_validate_valid_result() {
let mut v = AnomalyValidator::new();
assert_eq!(v.validate(42.0), Ok(42.0));
}
#[test]
fn test_jidoka_validate_nan() {
let mut v = AnomalyValidator::new();
assert_eq!(v.validate(f64::NAN), Err(AnomalyViolation::NaN));
}
#[test]
fn test_jidoka_validate_positive_infinity() {
let mut v = AnomalyValidator::new();
assert_eq!(v.validate(f64::INFINITY), Err(AnomalyViolation::Infinite));
}
#[test]
fn test_jidoka_validate_negative_infinity() {
let mut v = AnomalyValidator::new();
assert_eq!(
v.validate(f64::NEG_INFINITY),
Err(AnomalyViolation::Infinite)
);
}
#[test]
fn test_jidoka_validate_overflow_positive() {
let mut v = AnomalyValidator::with_max_magnitude(100.0);
let result = v.validate(150.0);
assert!(matches!(result, Err(AnomalyViolation::Overflow(_))));
}
#[test]
fn test_jidoka_validate_overflow_negative() {
let mut v = AnomalyValidator::with_max_magnitude(100.0);
let result = v.validate(-150.0);
assert!(matches!(result, Err(AnomalyViolation::Overflow(_))));
}
#[test]
fn test_jidoka_validate_at_boundary() {
let mut v = AnomalyValidator::with_max_magnitude(100.0);
assert_eq!(v.validate(100.0), Ok(100.0));
assert_eq!(v.validate(-100.0), Ok(-100.0));
}
#[test]
fn test_jidoka_history_recording() {
let mut v = AnomalyValidator::new();
v.validate(1.0).unwrap();
v.validate(2.0).unwrap();
v.validate(3.0).unwrap();
assert_eq!(v.history_len(), 3);
assert_eq!(
v.history().iter().copied().collect::<Vec<_>>(),
vec![1.0, 2.0, 3.0]
);
}
#[test]
fn test_jidoka_history_clear() {
let mut v = AnomalyValidator::new();
v.validate(1.0).unwrap();
v.validate(2.0).unwrap();
v.clear_history();
assert_eq!(v.history_len(), 0);
}
#[test]
fn test_jidoka_history_max_size() {
let mut v = AnomalyValidator::new();
v.max_history = 3;
for i in 0..5 {
v.validate(i as f64).unwrap();
}
assert_eq!(v.history_len(), 3);
assert_eq!(
v.history().iter().copied().collect::<Vec<_>>(),
vec![2.0, 3.0, 4.0]
);
}
#[test]
fn test_jidoka_special_values_disabled() {
let mut v = AnomalyValidator::new();
v.check_special_values = false;
assert!(v.validate(f64::NAN).is_ok());
}
}