fee 0.2.4

Expression evaluator supporting numeric, logical and bitwise operators
Documentation
use std::borrow::Cow;

use crate::{
    Error, EvalError, IndexedResolver, UContext,
    expr::{ExprCompiler, NotIndexedResolver, Op, ParseableToken},
    parsing,
    prelude::*,
    resolver::{LockedResolver, ResolverState, UnlockedResolver},
};

#[derive(Debug, PartialEq, Copy, Clone)]
pub enum IVRpn<'e>
{
    Num(f64),
    Var(usize, usize),
    Fn(&'e str, usize),
    Op(Op),
}

impl<'a, 'c, S, V, F, LV, LF> ParseableToken<'a, 'c, S, V, F, LV, LF> for IVRpn<'a>
where
    S: ResolverState,
    V: Resolver<S, f64>,
    F: Resolver<S, ExprFn>,
{
    #[inline]
    fn f64(num: f64) -> Self
    {
        IVRpn::Num(num)
    }

    #[inline]
    fn i64(num: i64) -> Self
    {
        IVRpn::Num(num as f64)
    }

    #[inline]
    fn bool(val: bool) -> Self
    {
        IVRpn::Num(if val { 1.0 } else { 0.0 })
    }

    #[inline]
    fn op(op: Op) -> Self
    {
        IVRpn::Op(op)
    }

    #[inline]
    fn var(name: &'a str, _ctx: &'c Context<S, V, F, LV, LF>) -> Self
    {
        let name_bytes = name.as_bytes();
        let letter = name_bytes[0] - b'a';
        let idx = parsing::parse_usize(&name_bytes[1..]);
        IVRpn::Var(letter as usize, idx)
    }

    #[inline]
    fn fun(name: &'a str, argc: usize, _ctx: &'c Context<S, V, F, LV, LF>) -> Self
    {
        IVRpn::Fn(name, argc)
    }
}

impl<'e, 'c, F, LF>
    ExprCompiler<
        'e,
        'c,
        Unlocked,
        IndexedResolver<Unlocked, f64>,
        F,
        IndexedResolver<Locked, f64>,
        LF,
        IVRpn<'e>,
    > for Expr<IVRpn<'e>>
where
    F: NotIndexedResolver + UnlockedResolver<ExprFn, LF>,
    LF: LockedResolver<ExprFn>,
{
    fn compile(
        expr: &'e str,
        ctx: &'c UContext<IndexedResolver<Unlocked, f64>, F, IndexedResolver<Locked, f64>, LF>,
    ) -> Result<Expr<IVRpn<'e>>, Error<'e>>
    {
        Expr::try_from((expr, ctx))
    }
}

impl<'e, F, LF>
    ExprEvaluator<'e, Unlocked, IndexedResolver<Unlocked, f64>, F, IndexedResolver<Locked, f64>, LF>
    for Expr<IVRpn<'e>>
where
    F: NotIndexedResolver + UnlockedResolver<ExprFn, LF>,
    LF: LockedResolver<ExprFn>,
{
    fn eval(
        &self,
        ctx: &UContext<IndexedResolver<Unlocked, f64>, F, IndexedResolver<Locked, f64>, LF>,
        stack: &mut Vec<f64>,
    ) -> Result<f64, Error<'e>>
    {
        if self.tokens.len() == 1 {
            if let IVRpn::Num(num) = &self.tokens[0] {
                return Ok(*num);
            }
        }

        for tok in self.tokens.iter() {
            match tok {
                IVRpn::Num(num) => stack.push(*num),
                IVRpn::Var(id, idx) => {
                    stack.push(*ctx.get_var_by_index(*id, *idx).ok_or_else(|| {
                        Error::UnknownVar(Cow::Owned(format!(
                            "{}{}",
                            (*id as u8 + b'a') as char,
                            idx
                        )))
                    })?)
                }
                IVRpn::Fn(name, argc) => {
                    if *argc > stack.len() {
                        return Err(Error::EvalError(EvalError::RPNStackUnderflow));
                    }

                    let start = stack.len() - argc;
                    let args = unsafe { stack.get_unchecked(start..) };
                    let val = ctx
                        .get_fn(name)
                        .ok_or_else(|| Error::UnknownFn(Cow::Borrowed(name)))?(
                        args
                    );

                    stack.truncate(start);
                    stack.push(val);
                }
                IVRpn::Op(op) => {
                    if op.num_operands() > stack.len() {
                        return Err(Error::EvalError(EvalError::RPNStackUnderflow));
                    }

                    let start = stack.len() - op.num_operands();
                    let args = unsafe { stack.get_unchecked(start..) };
                    let res = op.apply(args);
                    stack.truncate(start);
                    stack.push(res);
                }
            }
        }

        match stack.pop() {
            Some(result) if stack.is_empty() => Ok(result),
            _ => Err(Error::EvalError(EvalError::MalformedExpression)),
        }
    }
}