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)
}
}