bitcalc 0.3.0

A calculator with bit operations
use std::fmt::Display;

use crate::ast::{Expr, Op, Val};
use num_traits::{CheckedRem, CheckedShl, CheckedShr, PrimInt};

pub fn eval<
    I: PrimInt + TryFrom<u64> + TryInto<u32> + TryInto<usize> + CheckedRem + CheckedShl + CheckedShr,
>(
    answers: &[u64],
    expr: &Expr,
) -> Result<I, EvalError> {
    let eval = |x| eval::<I>(answers, x);

    match expr {
        Expr::Op(op) => match op {
            Op::Mul(a, b) => eval(a)?.checked_mul(&eval(b)?).ok_or(EvalError::Overflow),
            Op::Div(a, b) => eval(a)?.checked_div(&eval(b)?).ok_or(EvalError::Overflow),
            Op::Mod(a, b) => eval(a)?.checked_rem(&eval(b)?).ok_or(EvalError::Overflow),
            Op::Add(a, b) => eval(a)?.checked_add(&eval(b)?).ok_or(EvalError::Overflow),
            Op::Sub(a, b) => eval(a)?.checked_sub(&eval(b)?).ok_or(EvalError::Overflow),
            Op::Pow(a, b) => {
                let a = eval(a)?;
                let b = eval(b)?.try_into().map_err(|_| EvalError::ShiftTooLarge)?;
                num_traits::pow::checked_pow(a, b).ok_or(EvalError::Overflow)
            }
            Op::ShiftLeft(a, b) => {
                let a = eval(a)?;
                let b = eval(b)?.try_into().map_err(|_| EvalError::ShiftTooLarge)?;
                a.checked_shl(b).ok_or(EvalError::ShiftTooLarge)
            }
            Op::ShiftRight(a, b) => {
                let a = eval(a)?;
                let b = eval(b)?.try_into().map_err(|_| EvalError::ShiftTooLarge)?;
                a.checked_shr(b).ok_or(EvalError::ShiftTooLarge)
            }
            Op::RotLeft(a, b) => {
                let a = eval(a)?;
                let b = eval(b)?.try_into().map_err(|_| EvalError::ShiftTooLarge)?;
                Ok(a.rotate_left(b))
            }
            Op::RotRight(a, b) => {
                let a = eval(a)?;
                let b = eval(b)?.try_into().map_err(|_| EvalError::ShiftTooLarge)?;
                Ok(a.rotate_right(b))
            }
            Op::BitXor(a, b) => Ok(eval(a)? ^ eval(b)?),
            Op::BitOr(a, b) => Ok(eval(a)? | eval(b)?),
            Op::BitAnd(a, b) => Ok(eval(a)? & eval(b)?),
        },
        Expr::Val(Val::Int(x) | Val::Hex(x) | Val::Bin(x)) => {
            I::try_from(*x).map_err(|_| EvalError::LiteralTooLarge)
        }
        Expr::Val(Val::LastAnswer) => {
            let x = answers.last().ok_or(EvalError::HistoryValueDoesNotExist)?;
            I::try_from(*x).map_err(|_| EvalError::HistoryValueTooLarge)
        }
        Expr::Val(Val::History(idx)) => {
            let x = answers
                .get(*idx)
                .ok_or(EvalError::HistoryValueDoesNotExist)?;
            I::try_from(*x).map_err(|_| EvalError::HistoryValueTooLarge)
        }
    }
}

pub enum EvalError {
    Overflow,
    ShiftTooLarge,
    LiteralTooLarge,
    HistoryValueTooLarge,
    HistoryValueDoesNotExist,
}

impl Display for EvalError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match self {
            EvalError::Overflow => "an overflow happened",
            EvalError::ShiftTooLarge => "the amount to shift by exceeded the number of bits",
            EvalError::LiteralTooLarge => "the value of a literal was too big to fit",
            EvalError::HistoryValueTooLarge => "the value of a history value was too big to fit",
            EvalError::HistoryValueDoesNotExist => "requested history value does not exist",
        };
        f.write_str(s)
    }
}