calculatex/
function.rs

1use crate::{error::CalcError, expr::val::Val, parser::fn_call::FnCall};
2use crate::{expr::unit::Unit, expr::Expr, statement::Scope};
3use std::ops::RangeInclusive;
4
5type FunctionArgsRange = (RangeInclusive<usize>, fn(&[f64]) -> f64);
6
7macro_rules! match_unary_fn {
8    ($f:expr, $($name:ident),* $(,)?) => {
9        {
10            let res: Option<FunctionArgsRange> = match $f {
11                $(stringify!($name) => Some((1..=1, |x: &[f64]| f64::$name(x[0]))),)*
12                _ => None,
13            };
14            res
15        }
16    };
17}
18
19pub fn eval_fn_call(fc: &FnCall, scope: &Scope) -> Result<Val, CalcError> {
20    let e = |a: &Expr| a.eval(scope);
21
22    let name = fc.name.as_str();
23    let args_len = fc.args.len();
24
25    let fn_args_range = match_unary_fn!(
26        name, abs, acos, acosh, asin, asinh, atan, atanh, cbrt, ceil, cos, cosh, exp, floor, ln,
27        log10, log2, round, sin, sinh, sqrt, tan, tanh,
28    )
29    .or_else(|| match name {
30        "atan2" => Some((2..=2, |x: &[f64]| f64::atan2(x[0], x[1]))),
31        "min" => Some((1..=usize::MAX, |x: &[f64]| {
32            x.iter().cloned().reduce(f64::min).unwrap()
33        })),
34        "max" => Some((1..=usize::MAX, |x: &[f64]| {
35            x.iter().cloned().reduce(f64::max).unwrap()
36        })),
37        _ => None,
38    });
39
40    // TODO: Handle values with units
41    if let Some((args_range, calc)) = fn_args_range {
42        if args_range.contains(&args_len) {
43            let args: Result<Vec<f64>, CalcError> = fc
44                .args
45                .iter()
46                .map(|a| {
47                    let a = e(&a)?;
48                    if a.unit.desc.is_empty() {
49                        Ok(a.num * a.unit.mult * 10f64.powi(a.unit.exp as i32))
50                    } else {
51                        Err(CalcError::UnitError(format!(
52                            "Can't take {} of unit-ed value",
53                            fc.name
54                        )))
55                    }
56                })
57                .collect();
58            let args = args?;
59
60            let res: Val = (calc(args.as_slice()), Unit::empty()).into();
61            Ok(res.clamp_num())
62        } else {
63            Err(CalcError::Other(format!(
64                "Incorrect number of arguments to function {}, expected {:?} but got {}",
65                name, args_range, args_len
66            )))
67        }
68    } else {
69        Err(CalcError::Other(format!("Unknown function {}", name)))
70    }
71}