calcrs 2.2.1

A command-line calculator with everything you need
Documentation
use anyhow::{anyhow, Result};
use chumsky::prelude::*;

/// Evaluates the provided `equation`
pub fn run(equation: &str) -> Result<f64> {
    match parser().parse(equation) {
        Ok(ast) => match eval(&ast) {
            Ok(ans) => Ok(ans),
            Err(e) => Err(anyhow!("Evaluation error: {}", e)),
        },
        Err(errs) => {
            errs.into_iter()
                .for_each(|e| eprintln!("Parse error: {}", e));
            return Err(anyhow!(""));
        }
    }
}

enum Expr {
    Num(f64),

    Neg(Box<Self>),

    Pow(Box<Self>, Box<Self>),
    Mod(Box<Self>, Box<Self>),

    Add(Box<Self>, Box<Self>),
    Sub(Box<Self>, Box<Self>),

    Mul(Box<Self>, Box<Self>),
    Div(Box<Self>, Box<Self>),
}

fn eval(expr: &Expr) -> Result<f64, String> {
    use Expr::*;

    match expr {
        Num(n) => Ok(*n),
        Neg(a) => Ok(-eval(a)?),
        Add(a, b) => Ok(eval(a)? + eval(b)?),
        Sub(a, b) => Ok(eval(a)? - eval(b)?),
        Mul(a, b) => Ok(eval(a)? * eval(b)?),
        Div(a, b) => Ok(eval(a)? / eval(b)?),
        Pow(a, b) => Ok(eval(a)?.powf(eval(b)?)),
        Mod(a, b) => Ok(eval(a)? % eval(b)?),
    }
}

fn parser() -> impl Parser<char, Expr, Error = Simple<char>> {
    recursive(|expr| {
        let num = text::int(10)
            .map(|s: String| Expr::Num(s.parse().unwrap()))
            .padded();

        let atom = num.or(expr.delimited_by(just('('), just(')')));

        let op = |c| just(c).padded();

        let neg = op('-')
            .repeated()
            .then(atom)
            .foldr(|_op, rhs| Expr::Neg(Box::new(rhs)));

        let pow = neg
            .clone()
            .then(
                op('^')
                    .to(Expr::Pow as fn(_, _) -> _)
                    .or(op('%').to(Expr::Mod as fn(_, _) -> _))
                    .then(neg)
                    .repeated(),
            )
            .foldl(|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)));

        let product = pow
            .clone()
            .then(
                op('*')
                    .to(Expr::Mul as fn(_, _) -> _)
                    .or(op('/').to(Expr::Div as fn(_, _) -> _))
                    .then(pow)
                    .repeated(),
            )
            .foldl(|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)));

        let sum = product
            .clone()
            .then(
                op('+')
                    .to(Expr::Add as fn(_, _) -> _)
                    .or(op('-').to(Expr::Sub as fn(_, _) -> _))
                    .then(product)
                    .repeated(),
            )
            .foldl(|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)));

        sum.padded()
    })
}