prexel 0.1.9

A math expression evaluator
Documentation
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};

use num_traits::{FromPrimitive, Zero};

use crate::error::*;
use crate::function::{
    Associativity, BinaryFunction, Function, Notation, Precedence, UnaryFunction,
};
use crate::Result;

#[cfg(feature = "docs")]
use crate::descriptions::Description;

pub struct AddOperator;
impl<N: Add<N, Output = N>> BinaryFunction<N> for AddOperator {
    fn name(&self) -> &'static str {
        "+"
    }

    fn precedence(&self) -> Precedence {
        Precedence::LOW
    }

    fn associativity(&self) -> Associativity {
        Associativity::Left
    }

    fn call(&self, left: N, right: N) -> Result<N> {
        Ok(left + right)
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Add.into())
    }
}

pub struct SubOperator;
impl<N: Sub<N, Output = N>> BinaryFunction<N> for SubOperator {
    fn name(&self) -> &'static str {
        "-"
    }

    fn precedence(&self) -> Precedence {
        Precedence::LOW
    }

    fn associativity(&self) -> Associativity {
        Associativity::Left
    }

    fn call(&self, left: N, right: N) -> Result<N> {
        Ok(left - right)
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Sub.into())
    }
}

pub struct MulOperator;
impl<N: Mul<N, Output = N>> BinaryFunction<N> for MulOperator {
    fn name(&self) -> &'static str {
        "*"
    }

    fn precedence(&self) -> Precedence {
        Precedence::MEDIUM
    }

    fn associativity(&self) -> Associativity {
        Associativity::Left
    }

    fn call(&self, left: N, right: N) -> Result<N> {
        Ok(left * right)
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Mul.into())
    }
}

pub struct DivOperator;
impl<N: Div<N, Output = N> + Zero> BinaryFunction<N> for DivOperator {
    fn name(&self) -> &'static str {
        "/"
    }

    fn precedence(&self) -> Precedence {
        Precedence::MEDIUM
    }

    fn associativity(&self) -> Associativity {
        Associativity::Left
    }

    fn call(&self, left: N, right: N) -> Result<N> {
        if right.is_zero() {
            return Err(Error::from(ErrorKind::DivisionByZero));
        }

        Ok(left / right)
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Div.into())
    }
}

pub struct ModOperator;
impl<N: Rem<N, Output = N> + Zero> BinaryFunction<N> for ModOperator {
    fn name(&self) -> &'static str {
        "mod"
    }

    fn precedence(&self) -> Precedence {
        Precedence::MEDIUM
    }

    fn associativity(&self) -> Associativity {
        Associativity::Left
    }

    fn call(&self, left: N, right: N) -> Result<N> {
        if right.is_zero() {
            return Err(Error::from(ErrorKind::DivisionByZero));
        }

        Ok(left % right)
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Mod.into())
    }
}

pub struct UnaryMinus;
impl<N: Neg<Output = N>> UnaryFunction<N> for UnaryMinus {
    fn name(&self) -> &str {
        "-"
    }

    fn notation(&self) -> Notation {
        Notation::Prefix
    }

    fn call(&self, value: N) -> Result<N> {
        Ok(value.neg())
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Neg.into())
    }
}

pub struct AbsFunction;
impl<N: Zero + PartialOrd + Neg<Output = N> + Clone> Function<N> for AbsFunction {
    fn name(&self) -> &str {
        "abs"
    }

    fn call(&self, args: &[N]) -> Result<N> {
        if args.len() != 1 {
            Err(Error::from(ErrorKind::InvalidArgumentCount))
        } else if args[0] >= N::zero() {
            Ok(args[0].clone())
        } else {
            Ok(args[0].clone().neg())
        }
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Abs.into())
    }
}

pub struct SumFunction;
impl<N: Add<N, Output = N> + Clone> Function<N> for SumFunction {
    fn name(&self) -> &str {
        "sum"
    }

    fn call(&self, args: &[N]) -> Result<N> {
        let mut result = None;

        for cur in args {
            match result {
                None => result = Some(cur.clone()),
                Some(ref n) => {
                    result = Some(n.clone() + cur.clone());
                }
            }
        }

        result.ok_or_else(|| Error::from(ErrorKind::InvalidArgumentCount))
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Sum.into())
    }
}

pub struct ProdFunction;
impl<N: Mul<N, Output = N> + Clone> Function<N> for ProdFunction {
    fn name(&self) -> &str {
        "product"
    }

    fn call(&self, args: &[N]) -> Result<N> {
        let mut result = None;

        for cur in args {
            match result {
                None => result = Some(cur.clone()),
                Some(ref n) => {
                    result = Some(n.clone() * cur.clone());
                }
            }
        }

        result.ok_or_else(|| Error::from(ErrorKind::InvalidArgumentCount))
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Prod.into())
    }
}

pub struct AvgFunction;
impl<N: Add<N, Output = N> + Div<N, Output = N> + FromPrimitive + Clone> Function<N>
    for AvgFunction
{
    fn name(&self) -> &str {
        "avg"
    }

    fn call(&self, args: &[N]) -> Result<N> {
        let mut sum = None;

        for cur in args {
            match sum {
                None => sum = Some(cur.clone()),
                Some(ref n) => {
                    sum = Some(n.clone() + cur.clone());
                }
            }
        }

        match sum {
            Some(n) => Ok(n / N::from_usize(args.len()).unwrap()),
            None => Err(Error::from(ErrorKind::InvalidArgumentCount)),
        }
    }

    #[cfg(feature="docs")]
    fn description(&self) -> Option<&str> {
        Some(Description::Avg.into())
    }
}

#[cfg(test)]
mod tests{
    use super::*;

    fn empty_array<T>() -> Box<[T]>{
        vec![].into_boxed_slice()
    }

    #[test]
    fn add_test(){
        let instance = AddOperator;

        assert_eq!(instance.call(10_f64, 4_f64), Ok(14_f64));
        assert_eq!(instance.call(3, 7), Ok(10));
    }

    #[test]
    fn sub_test(){
        let instance = SubOperator;

        assert_eq!(instance.call(10_f64, 4_f64), Ok(6_f64));
        assert_eq!(instance.call(3, 7), Ok(-4));
    }

    #[test]
    fn mul_test(){
        let instance = MulOperator;

        assert_eq!(instance.call(10_f64, 4_f64), Ok(40_f64));
        assert_eq!(instance.call(3, 7), Ok(21));
    }

    #[test]
    fn div_test(){
        let instance = DivOperator;

        assert_eq!(instance.call(10_f64, 4_f64), Ok(2.5_f64));
        assert_eq!(instance.call(20, 4), Ok(5));
        assert!(instance.call(5, 0).is_err());
    }

    #[test]
    fn mod_test(){
        let instance = ModOperator;

        assert_eq!(instance.call(10_f64, 4_f64), Ok(2_f64));
        assert_eq!(instance.call(20, 4), Ok(0));
        assert!(instance.call(5, 0).is_err());
    }

    #[test]
    fn unary_minus_test(){
        let instance = UnaryMinus;

        assert_eq!(instance.call(10_f64), Ok(-10_f64));
        assert_eq!(instance.call(-5), Ok(5));
    }

    #[test]
    fn abs_test(){
        let instance = AbsFunction;

        assert_eq!(instance.call(&[-5_f64]), Ok(5_f64));
        assert_eq!(instance.call(&[3]), Ok(3));
    }

    #[test]
    fn sum_test(){
        let instance = SumFunction;

        assert_eq!(instance.call(&[1_f64, 2_f64, 3_f64]), Ok(6_f64));
        assert_eq!(instance.call(&[2, 4, 6]), Ok(12));

        assert!(instance.call(&[2]).is_ok());
        assert!(instance.call(empty_array::<i64>().as_ref()).is_err());
    }

    #[test]
    fn prod_test(){
        let instance = ProdFunction;

        assert_eq!(instance.call(&[2_f64, 3_f64, 4_f64]), Ok(24_f64));
        assert_eq!(instance.call(&[2, 4, 6]), Ok(48));

        assert!(instance.call(&[2]).is_ok());
        assert!(instance.call(empty_array::<i64>().as_ref()).is_err());
    }

    #[test]
    fn avg_test(){
        let instance = AvgFunction;

        assert_eq!(instance.call(&[1_f64, 2_f64, 3_f64, 4_f64]), Ok(2.5_f64));
        assert_eq!(instance.call(&[2, 4, 6]), Ok(4));

        assert!(instance.call(empty_array::<i64>().as_ref()).is_err());
    }
}