use std::f64::consts::PI;
use meval::{Context, Expr};
use crate::domain::error::{Error, Result};
use crate::domain::format::AngleMode;
pub trait Evaluator {
fn eval(&self, expr: &str, angle: AngleMode) -> Result<f64>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct MevalEvaluator;
impl MevalEvaluator {
pub fn new() -> Self {
MevalEvaluator
}
}
impl Evaluator for MevalEvaluator {
fn eval(&self, expr: &str, angle: AngleMode) -> Result<f64> {
let parsed: Expr = expr
.parse()
.map_err(|e| Error::Calculator(format!("{e}")))?;
let result = match angle {
AngleMode::Rad => parsed.eval(),
AngleMode::Deg => parsed.eval_with_context(degree_context()),
};
result.map_err(|e| Error::Calculator(format!("{e}")))
}
}
fn degree_context() -> Context<'static> {
let mut ctx = Context::new();
ctx.func("sin", |x| (x * PI / 180.0).sin());
ctx.func("cos", |x| (x * PI / 180.0).cos());
ctx.func("tan", |x| (x * PI / 180.0).tan());
ctx.func("asin", |x| x.asin() * 180.0 / PI);
ctx.func("acos", |x| x.acos() * 180.0 / PI);
ctx.func("atan", |x| x.atan() * 180.0 / PI);
ctx
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-9;
#[test]
fn evaluates_basic_arithmetic_and_power() {
let evaluator = MevalEvaluator::new();
let value = evaluator.eval("2^8+1", AngleMode::Rad).unwrap();
assert!((value - 257.0).abs() < EPSILON);
}
#[test]
fn radian_mode_matches_the_mathematical_definition() {
let evaluator = MevalEvaluator::new();
let value = evaluator.eval("sin(pi/2)", AngleMode::Rad).unwrap();
assert!((value - 1.0).abs() < EPSILON);
}
#[test]
fn degree_mode_reads_arguments_as_degrees() {
let evaluator = MevalEvaluator::new();
let value = evaluator.eval("sin(90)", AngleMode::Deg).unwrap();
assert!((value - 1.0).abs() < EPSILON);
let inverse = evaluator.eval("asin(1)", AngleMode::Deg).unwrap();
assert!((inverse - 90.0).abs() < EPSILON);
}
#[test]
fn non_trigonometric_builtins_survive_degree_mode() {
let evaluator = MevalEvaluator::new();
let value = evaluator.eval("sqrt(16)", AngleMode::Deg).unwrap();
assert!((value - 4.0).abs() < EPSILON);
}
#[test]
fn an_invalid_expression_is_a_calculator_error() {
let evaluator = MevalEvaluator::new();
let error = evaluator.eval("2+", AngleMode::Rad).unwrap_err();
assert!(matches!(error, Error::Calculator(_)));
}
}