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