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