rust-expression 0.1.1

Calculator and solver for linear equations.
Documentation
use std::collections::HashMap;

use crate::ast::*;

#[derive(Debug, PartialEq, Eq)]
pub enum CalcError {
    UnknownSymbol(String),
}

#[derive(Debug, Default, Clone)]
pub struct Env {
    env: HashMap<String, Number>,
}

impl Env {
    pub fn get(&self, sym: &str) -> Option<&Number> {
        self.env.get(sym)
    }

    pub fn put(&mut self, sym: String, num: Number) {
        self.env.insert(sym, num);
    }
}

pub fn calc_term(term: &Term, env: &Env) -> Result<Number, CalcError> {
    use self::Operation::*;
    let lhs = calc_operand(&term.lhs, env)?;
    let rhs = calc_operand(&term.rhs, env)?;
    Ok(match term.op {
        Add => lhs + rhs,
        Sub => lhs - rhs,
        Mul => lhs * rhs,
        Div => lhs / rhs,
        Rem => lhs % rhs,
        Pow => lhs.powf(rhs),
    })
}

pub fn calc_operand(op: &Operand, env: &Env) -> Result<Number, CalcError> {
    use self::Operand::*;
    match op {
        Number(num) => Ok(*num),
        Term(term) => calc_term(term, env),
        Symbol(sym) => match env.get(sym) {
            Some(num) => Ok(*num),
            None => Err(CalcError::UnknownSymbol(sym.clone())),
        },
    }
}

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

    #[test]
    fn read_env_empty() {
        let env = Env::default();

        assert_eq!(None, env.get("x"));
    }

    #[test]
    fn read_env_var() {
        let mut env = Env::default();
        env.put("x".to_string(), 12.0);

        assert_eq!(Some(&12.0), env.get("x"));
    }

    #[test]
    fn calc_number_atom() {
        assert_eq!(
            Ok(12.0),
            calc_operand(&Operand::Number(12.0), &Env::default())
        );
    }

    #[test]
    fn calc_sym_unknown() {
        assert_eq!(
            Err(CalcError::UnknownSymbol("x".to_string())),
            calc_operand(&Operand::Symbol("x".to_string()), &Env::default())
        );
    }

    #[test]
    fn calc_sym_known() {
        let mut env = Env::default();
        env.put("x".to_string(), 12.0);
        assert_eq!(
            Ok(12.0),
            calc_operand(&Operand::Symbol("x".to_string()), &env)
        );
    }

    #[test]
    fn calc_term_add() {
        let lhs = Operand::Number(3.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Add;
        assert_eq!(Ok(7.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_term_sub() {
        let lhs = Operand::Number(3.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Sub;
        assert_eq!(Ok(-1.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_term_mul() {
        let lhs = Operand::Number(3.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Mul;
        assert_eq!(Ok(12.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_term_div() {
        let lhs = Operand::Number(12.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Div;
        assert_eq!(Ok(3.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_term_rem() {
        let lhs = Operand::Number(14.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Rem;
        assert_eq!(Ok(2.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_term_pow() {
        let lhs = Operand::Number(3.0);
        let rhs = Operand::Number(4.0);
        let op = Operation::Pow;
        assert_eq!(Ok(81.0), calc_term(&Term { op, lhs, rhs }, &Env::default()));
    }

    #[test]
    fn calc_equation_simple() {
        let op = Operand::Number(3.0);
        assert_eq!(Ok(3.0), calc_operand(&op, &Env::default()));
    }
}