use super::*;
pub(super) fn eval_arithm(state: &mut ShellState, expr: &ArithmExpr) -> Result<i64, String> {
match expr {
ArithmExpr::Literal(n) => Ok(n.value()),
ArithmExpr::Raw(expr) => crate::parser::arithm::parse_arithm_expr_strict(expr.expr())
.map_err(|err| {
let expr = expr.expr();
format!(
"{}: \"{}\"",
err.message,
expr[err.position.min(expr.len())..].trim()
)
})
.and_then(|parsed| eval_arithm(state, &parsed)),
ArithmExpr::Variable(name) => {
let var = name.name().strip_prefix('$').unwrap_or(name.name());
match state.env_get(var) {
Some("") => Ok(0),
Some(v) => v.parse::<i64>().map_err(|_| "Illegal number".to_string()),
None => Ok(0),
}
}
ArithmExpr::BinOp(binary) => {
let l = eval_arithm(state, binary.left())?;
let r = eval_arithm(state, binary.right())?;
eval_arithm_binop(binary.op(), l, r).map_err(str::to_string)
}
ArithmExpr::UnOp(unary) => {
let v = eval_arithm(state, unary.operand())?;
Ok(match unary.op() {
ArithmUnOp::Plus => v,
ArithmUnOp::Minus => -v,
ArithmUnOp::BitNot => !v,
ArithmUnOp::LogNot => i64::from(v == 0),
})
}
ArithmExpr::Cond(cond) => {
if eval_arithm(state, cond.cond())? != 0 {
eval_arithm(state, cond.then_branch())
} else {
eval_arithm(state, cond.else_branch())
}
}
ArithmExpr::Assign(assign) => {
let rhs = eval_arithm(state, assign.value())?;
let current = match state.env_get(assign.name()) {
Some("") => 0,
Some(v) => v.parse::<i64>().map_err(|_| "Illegal number".to_string())?,
None => 0,
};
let result = eval_assign_op(assign.op(), current, rhs).map_err(str::to_string)?;
state.env_set(assign.name(), result.to_string(), 0);
Ok(result)
}
}
}
fn checked_shift(rhs: i64) -> Result<u32, &'static str> {
if !(0..i64::BITS as i64).contains(&rhs) {
return Err("invalid shift count");
}
Ok(rhs as u32)
}
fn eval_checked_arithm_binop(op: ArithmBinOp, l: i64, r: i64) -> Result<i64, &'static str> {
Ok(match op {
ArithmBinOp::Add => l.wrapping_add(r),
ArithmBinOp::Sub => l.wrapping_sub(r),
ArithmBinOp::Mul => l.wrapping_mul(r),
ArithmBinOp::Div => {
if r == 0 {
return Err("division by zero");
}
l.wrapping_div(r)
}
ArithmBinOp::Mod => {
if r == 0 {
return Err("division by zero");
}
l.wrapping_rem(r)
}
ArithmBinOp::Shl => l.wrapping_shl(checked_shift(r)?),
ArithmBinOp::Shr => l.wrapping_shr(checked_shift(r)?),
ArithmBinOp::LessThan => i64::from(l < r),
ArithmBinOp::LessEq => i64::from(l <= r),
ArithmBinOp::GreaterThan => i64::from(l > r),
ArithmBinOp::GreaterEq => i64::from(l >= r),
ArithmBinOp::Equal => i64::from(l == r),
ArithmBinOp::NotEqual => i64::from(l != r),
ArithmBinOp::BitAnd => l & r,
ArithmBinOp::BitXor => l ^ r,
ArithmBinOp::BitOr => l | r,
ArithmBinOp::LogAnd => i64::from(l != 0 && r != 0),
ArithmBinOp::LogOr => i64::from(l != 0 || r != 0),
})
}
pub(super) fn eval_arithm_binop(op: ArithmBinOp, l: i64, r: i64) -> Result<i64, &'static str> {
eval_checked_arithm_binop(op, l, r)
}
fn assign_op_as_binop(op: ArithmAssignOp) -> Option<ArithmBinOp> {
match op {
ArithmAssignOp::Equal => None,
ArithmAssignOp::MulEq => Some(ArithmBinOp::Mul),
ArithmAssignOp::DivEq => Some(ArithmBinOp::Div),
ArithmAssignOp::ModEq => Some(ArithmBinOp::Mod),
ArithmAssignOp::AddEq => Some(ArithmBinOp::Add),
ArithmAssignOp::SubEq => Some(ArithmBinOp::Sub),
ArithmAssignOp::ShlEq => Some(ArithmBinOp::Shl),
ArithmAssignOp::ShrEq => Some(ArithmBinOp::Shr),
ArithmAssignOp::AndEq => Some(ArithmBinOp::BitAnd),
ArithmAssignOp::XorEq => Some(ArithmBinOp::BitXor),
ArithmAssignOp::OrEq => Some(ArithmBinOp::BitOr),
}
}
pub(super) fn eval_assign_op(
op: ArithmAssignOp,
current: i64,
rhs: i64,
) -> Result<i64, &'static str> {
match assign_op_as_binop(op) {
Some(binop) => eval_checked_arithm_binop(binop, current, rhs),
None => Ok(rhs),
}
}