evar 0.2.0

Modern ergonomic math calculator inspired by eva
use super::{Context, operators::*};
use super::{EvalError, Value};

#[derive(Debug, PartialEq)]
pub enum Expr {
    Int(i32),
    Float(f64),
    Variable(String),
    FnCall {
        name: String,
        args: Vec<Expr>,
    },
    PrefixOp {
        op: PrefixOp,
        arg: Box<Expr>,
    },
    PostfixOp {
        op: PostfixOp,
        arg: Box<Expr>,
    },
    InfixOp {
        op: InfixOp,
        lhs: Box<Expr>,
        rhs: Box<Expr>,
    },
    PrevAnswer,
}

impl std::fmt::Display for Expr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Expr::Int(n) => write!(f, "(int: {})", n),
            Expr::Float(n) => write!(f, "(float: {})", n),
            Expr::Variable(n) => write!(f, "{}", n),
            Expr::FnCall { name, args } => {
                let args_str = args
                    .iter()
                    .map(Expr::to_string)
                    .reduce(|acc, x| acc + ", " + &x)
                    .unwrap_or_else(|| String::from(""));
                write!(f, "{}({})", name, args_str)
            }
            Expr::PrefixOp { op, arg } => write!(f, "({}{})", op, arg),
            Expr::PostfixOp { op, arg } => write!(f, "({}{})", arg, op),
            Expr::InfixOp { op, lhs, rhs } => write!(f, "({} {} {})", lhs, op, rhs),
            Expr::PrevAnswer => write!(f, "_"),
        }
    }
}

impl Expr {
    pub fn eval(&self, context: &mut Context) -> Result<Value, EvalError> {
        match self {
            Expr::Int(f) => Ok(Value::from(*f)),
            Expr::Float(f) => Ok(Value::from(*f)),
            Expr::InfixOp { op, lhs, rhs } => {
                use InfixOp::*;
                match op {
                    Add => Ok(lhs.eval(context)? + rhs.eval(context)?),
                    Sub => Ok(lhs.eval(context)? - rhs.eval(context)?),
                    Mul => Ok(lhs.eval(context)? * rhs.eval(context)?),
                    Div => lhs.eval(context)? / rhs.eval(context)?,
                    IntDiv => lhs.eval(context)?.int_div(rhs.eval(context)?),
                    Rem => lhs.eval(context)?.rem_euclid(rhs.eval(context)?),
                    Pow => Ok(lhs.eval(context)?.pow(rhs.eval(context)?)),
                }
            }
            Expr::PrefixOp { op, arg } => {
                use PrefixOp::*;
                match op {
                    Neg => Ok(-arg.eval(context)?),
                }
            }
            Expr::PostfixOp { op, arg } => {
                use PostfixOp::*;
                match op {
                    Fac => arg.eval(context)?.factorial(),
                }
            }
            Expr::FnCall { name, args } => {
                let mut evaluated_args = Vec::new();
                for arg in args {
                    evaluated_args.push(arg.eval(context)?);
                }

                let function = context
                    .get_function(name)
                    .ok_or(EvalError::FunctionNotFound(name.to_string()))?
                    .clone();

                function.call(evaluated_args, context)
            }
            Expr::Variable(name) => {
                let variable = context
                    .get_variable(name)
                    .ok_or(EvalError::VariableNotFound(name.to_string()))?
                    .get();
                Ok(variable)
            }
            Expr::PrevAnswer => context.get_prev_answer().ok_or(EvalError::NoHistory),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{args::AngleUnit::*, create_context};

    #[test]
    fn test_number() {
        let mut context = create_context(&Radian);
        let expr = Expr::Float(42.0);
        assert_eq!(expr.eval(&mut context,).unwrap(), Value::from(42.0));
    }

    #[test]
    fn test_infix_op_add() {
        let mut context = create_context(&Radian);
        let expr = Expr::InfixOp {
            op: InfixOp::Add,
            lhs: Box::new(Expr::Float(1.0)),
            rhs: Box::new(Expr::Float(2.0)),
        };
        assert_eq!(expr.eval(&mut context,).unwrap(), Value::from(3.0));
    }

    #[test]
    fn test_prefix_op_neg() {
        let mut context = create_context(&Radian);
        let expr = Expr::PrefixOp {
            op: PrefixOp::Neg,
            arg: Box::new(Expr::Float(5.0)),
        };
        assert_eq!(expr.eval(&mut context,).unwrap(), Value::from(-5.0));
    }

    #[test]
    fn test_postfix_op_fac() {
        let mut context = create_context(&Radian);
        let expr = Expr::PostfixOp {
            op: PostfixOp::Fac,
            arg: Box::new(Expr::Int(5)),
        };
        assert_eq!(expr.eval(&mut context,).unwrap(), Value::from(120));
    }

    #[test]
    fn test_fn_call() {
        let mut context = create_context(&Radian);
        let expr = Expr::FnCall {
            name: "mock_fn".to_string(),
            args: vec![Expr::Float(2.0), Expr::Float(3.0)],
        };
        context.set_function(
            "mock_fn",
            vec![String::from("x"), String::from("y")],
            Expr::InfixOp {
                op: InfixOp::Add,
                lhs: Expr::Variable(String::from("x")).into(),
                rhs: Expr::Variable(String::from("y")).into(),
            },
        );
        assert_eq!(expr.eval(&mut context,).unwrap(), Value::from(5.0));
    }
}