formualizer_eval/builtins/math/
reduction.rs

1use super::super::utils::{ARG_RANGE_NUM_LENIENT_ONE, coerce_num};
2use crate::args::ArgSchema;
3use crate::function::Function;
4use crate::traits::{ArgumentHandle, FunctionContext};
5use formualizer_common::{ExcelError, LiteralValue};
6use formualizer_macros::func_caps;
7
8#[derive(Debug)]
9pub struct MinFn; // MIN(...)
10impl Function for MinFn {
11    func_caps!(PURE, REDUCTION, NUMERIC_ONLY);
12    fn name(&self) -> &'static str {
13        "MIN"
14    }
15    fn min_args(&self) -> usize {
16        1
17    }
18    fn variadic(&self) -> bool {
19        true
20    }
21    fn arg_schema(&self) -> &'static [ArgSchema] {
22        &ARG_RANGE_NUM_LENIENT_ONE[..]
23    }
24    fn eval_scalar<'a, 'b>(
25        &self,
26        args: &'a [ArgumentHandle<'a, 'b>],
27        _ctx: &dyn FunctionContext,
28    ) -> Result<LiteralValue, ExcelError> {
29        let mut mv: Option<f64> = None;
30        for a in args {
31            if let Ok(view) = a.range_view() {
32                view.for_each_cell(&mut |v| {
33                    match v {
34                        LiteralValue::Error(e) => return Err(e.clone()),
35                        other => {
36                            if let Ok(n) = coerce_num(other) {
37                                mv = Some(mv.map(|m| m.min(n)).unwrap_or(n));
38                            }
39                        }
40                    }
41                    Ok(())
42                })?;
43            } else {
44                let v = a.value()?;
45                match v.as_ref() {
46                    LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
47                    other => {
48                        if let Ok(n) = coerce_num(other) {
49                            mv = Some(mv.map(|m| m.min(n)).unwrap_or(n));
50                        }
51                    }
52                }
53            }
54        }
55        Ok(LiteralValue::Number(mv.unwrap_or(0.0)))
56    }
57}
58
59#[derive(Debug)]
60pub struct MaxFn; // MAX(...)
61impl Function for MaxFn {
62    func_caps!(PURE, REDUCTION, NUMERIC_ONLY);
63    fn name(&self) -> &'static str {
64        "MAX"
65    }
66    fn min_args(&self) -> usize {
67        1
68    }
69    fn variadic(&self) -> bool {
70        true
71    }
72    fn arg_schema(&self) -> &'static [ArgSchema] {
73        &ARG_RANGE_NUM_LENIENT_ONE[..]
74    }
75    fn eval_scalar<'a, 'b>(
76        &self,
77        args: &'a [ArgumentHandle<'a, 'b>],
78        _ctx: &dyn FunctionContext,
79    ) -> Result<LiteralValue, ExcelError> {
80        let mut mv: Option<f64> = None;
81        for a in args {
82            if let Ok(view) = a.range_view() {
83                view.for_each_cell(&mut |v| {
84                    match v {
85                        LiteralValue::Error(e) => return Err(e.clone()),
86                        other => {
87                            if let Ok(n) = coerce_num(other) {
88                                mv = Some(mv.map(|m| m.max(n)).unwrap_or(n));
89                            }
90                        }
91                    }
92                    Ok(())
93                })?;
94            } else {
95                let v = a.value()?;
96                match v.as_ref() {
97                    LiteralValue::Error(e) => return Ok(LiteralValue::Error(e.clone())),
98                    other => {
99                        if let Ok(n) = coerce_num(other) {
100                            mv = Some(mv.map(|m| m.max(n)).unwrap_or(n));
101                        }
102                    }
103                }
104            }
105        }
106        Ok(LiteralValue::Number(mv.unwrap_or(0.0)))
107    }
108}
109
110pub fn register_builtins() {
111    use std::sync::Arc;
112    crate::function_registry::register_function(Arc::new(MinFn));
113    crate::function_registry::register_function(Arc::new(MaxFn));
114}
115
116#[cfg(test)]
117mod tests_min_max {
118    use super::*;
119    use crate::test_workbook::TestWorkbook;
120    use crate::traits::ArgumentHandle;
121    use formualizer_common::LiteralValue;
122    use formualizer_parse::parser::{ASTNode, ASTNodeType};
123    fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
124        wb.interpreter()
125    }
126
127    #[test]
128    fn min_basic_array_and_scalar() {
129        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MinFn));
130        let ctx = interp(&wb);
131        let arr = ASTNode::new(
132            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
133                LiteralValue::Int(5),
134                LiteralValue::Int(2),
135                LiteralValue::Int(9),
136            ]])),
137            None,
138        );
139        let extra = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
140        let f = ctx.context.get_function("", "MIN").unwrap();
141        let out = f
142            .dispatch(
143                &[
144                    ArgumentHandle::new(&arr, &ctx),
145                    ArgumentHandle::new(&extra, &ctx),
146                ],
147                &ctx.function_context(None),
148            )
149            .unwrap();
150        assert_eq!(out, LiteralValue::Number(1.0));
151    }
152
153    #[test]
154    fn max_basic_with_text_ignored() {
155        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MaxFn));
156        let ctx = interp(&wb);
157        let arr = ASTNode::new(
158            ASTNodeType::Literal(LiteralValue::Array(vec![vec![
159                LiteralValue::Int(5),
160                LiteralValue::Text("x".into()),
161                LiteralValue::Int(9),
162            ]])),
163            None,
164        );
165        let f = ctx.context.get_function("", "MAX").unwrap();
166        let out = f
167            .dispatch(
168                &[ArgumentHandle::new(&arr, &ctx)],
169                &ctx.function_context(None),
170            )
171            .unwrap();
172        assert_eq!(out, LiteralValue::Number(9.0));
173    }
174
175    #[test]
176    fn min_error_propagates() {
177        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MinFn));
178        let ctx = interp(&wb);
179        let err = ASTNode::new(
180            ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
181            None,
182        );
183        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
184        let f = ctx.context.get_function("", "MIN").unwrap();
185        let out = f
186            .dispatch(
187                &[
188                    ArgumentHandle::new(&err, &ctx),
189                    ArgumentHandle::new(&one, &ctx),
190                ],
191                &ctx.function_context(None),
192            )
193            .unwrap();
194        match out {
195            LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
196            v => panic!("expected error got {v:?}"),
197        }
198    }
199
200    #[test]
201    fn max_error_propagates() {
202        let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MaxFn));
203        let ctx = interp(&wb);
204        let err = ASTNode::new(
205            ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
206                "#DIV/0!",
207            ))),
208            None,
209        );
210        let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
211        let f = ctx.context.get_function("", "MAX").unwrap();
212        let out = f
213            .dispatch(
214                &[
215                    ArgumentHandle::new(&one, &ctx),
216                    ArgumentHandle::new(&err, &ctx),
217                ],
218                &ctx.function_context(None),
219            )
220            .unwrap();
221        match out {
222            LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
223            v => panic!("expected error got {v:?}"),
224        }
225    }
226}