evar 0.2.0

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

#[derive(Debug, PartialEq)]
pub enum Stmt {
    DefVar {
        name: String,
        expr: Expr,
    },
    DefFun {
        name: String,
        arg_names: Vec<String>,
        body: Expr,
    },
    Expr(Expr),
}

impl std::fmt::Display for Stmt {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Stmt::DefVar { name, expr } => write!(f, "let {} := {}", name, expr),
            Stmt::DefFun {
                name,
                arg_names,
                body,
            } => write!(f, "let {}({:?}) := {}", name, arg_names, body),
            Stmt::Expr(expr) => write!(f, "{}", expr),
        }
    }
}

impl Stmt {
    pub fn eval(self, context: &mut Context) -> Result<Value, EvalError> {
        match self {
            Stmt::DefVar { name, expr } => {
                let val = expr.eval(context)?;
                let variable = context
                    .set_variable(&name, val)
                    .ok_or(EvalError::InvalidVariableDefinition(name))?;
                Ok(variable)
            }
            Stmt::DefFun {
                name,
                arg_names: args,
                body,
            } => {
                context.set_function(&name, args, body);
                Ok(Value::null())
            }
            Stmt::Expr(expr) => {
                let answer = expr.eval(context)?;
                context.set_prev_answer(&answer);
                Ok(answer)
            }
        }
    }
}

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

    #[test]
    fn test_def_var_eval() {
        let mut context = create_context(&Radian);
        let stmt = Stmt::DefVar {
            name: "x".to_string(),
            expr: Expr::Float(42.0),
        };
        assert_eq!(stmt.eval(&mut context).unwrap(), Value::from(42.0));
        assert_eq!(context.get_variable("x").unwrap().get(), Value::from(42.0));
    }

    #[test]
    fn test_def_fun_eval() {
        let mut context = create_context(&Radian);
        let stmt = Stmt::DefFun {
            name: "add".to_string(),
            arg_names: vec!["a".to_string(), "b".to_string()],
            body: Expr::InfixOp {
                op: crate::models::operators::InfixOp::Add,
                lhs: Box::new(Expr::Variable("a".to_string())),
                rhs: Box::new(Expr::Variable("b".to_string())),
            },
        };
        assert!(stmt.eval(&mut context).is_ok());
        assert!(context.get_function("add").is_some());
    }

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