mxsh 0.1.0

Embeddable POSIX-style shell parser and runtime
Documentation
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),
    }
}