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 {
69 func_caps!(PURE, REDUCTION, NUMERIC_ONLY);
70 fn name(&self) -> &'static str {
71 "MIN"
72 }
73 fn min_args(&self) -> usize {
74 1
75 }
76 fn variadic(&self) -> bool {
77 true
78 }
79 fn arg_schema(&self) -> &'static [ArgSchema] {
80 &ARG_RANGE_NUM_LENIENT_ONE[..]
81 }
82 fn eval<'a, 'b, 'c>(
83 &self,
84 args: &'c [ArgumentHandle<'a, 'b>],
85 _ctx: &dyn FunctionContext<'b>,
86 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
87 let mut mv: Option<f64> = None;
88 for a in args {
89 if let Ok(view) = a.range_view() {
90 for res in view.errors_slices() {
92 let (_, _, err_cols) = res?;
93 for col in err_cols {
94 if col.null_count() < col.len() {
95 for i in 0..col.len() {
96 if !col.is_null(i) {
97 return Ok(crate::traits::CalcValue::Scalar(
98 LiteralValue::Error(ExcelError::new(
99 crate::arrow_store::unmap_error_code(col.value(i)),
100 )),
101 ));
102 }
103 }
104 }
105 }
106 }
107
108 for res in view.numbers_slices() {
109 let (_, _, num_cols) = res?;
110 for col in num_cols {
111 if let Some(n) = arrow::compute::kernels::aggregate::min(col.as_ref()) {
112 mv = Some(mv.map(|m| m.min(n)).unwrap_or(n));
113 }
114 }
115 }
116 } else {
117 let v = a.value()?.into_literal();
118 match v {
119 LiteralValue::Error(e) => {
120 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
121 }
122 other => {
123 if let Ok(n) = coerce_num(&other) {
124 mv = Some(mv.map(|m| m.min(n)).unwrap_or(n));
125 }
126 }
127 }
128 }
129 }
130 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
131 mv.unwrap_or(0.0),
132 )))
133 }
134}
135
136#[derive(Debug)]
137pub struct MaxFn; impl Function for MaxFn {
196 func_caps!(PURE, REDUCTION, NUMERIC_ONLY);
197 fn name(&self) -> &'static str {
198 "MAX"
199 }
200 fn min_args(&self) -> usize {
201 1
202 }
203 fn variadic(&self) -> bool {
204 true
205 }
206 fn arg_schema(&self) -> &'static [ArgSchema] {
207 &ARG_RANGE_NUM_LENIENT_ONE[..]
208 }
209 fn eval<'a, 'b, 'c>(
210 &self,
211 args: &'c [ArgumentHandle<'a, 'b>],
212 _ctx: &dyn FunctionContext<'b>,
213 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
214 let mut mv: Option<f64> = None;
215 for a in args {
216 if let Ok(view) = a.range_view() {
217 for res in view.errors_slices() {
219 let (_, _, err_cols) = res?;
220 for col in err_cols {
221 if col.null_count() < col.len() {
222 for i in 0..col.len() {
223 if !col.is_null(i) {
224 return Ok(crate::traits::CalcValue::Scalar(
225 LiteralValue::Error(ExcelError::new(
226 crate::arrow_store::unmap_error_code(col.value(i)),
227 )),
228 ));
229 }
230 }
231 }
232 }
233 }
234
235 for res in view.numbers_slices() {
236 let (_, _, num_cols) = res?;
237 for col in num_cols {
238 if let Some(n) = arrow::compute::kernels::aggregate::max(col.as_ref()) {
239 mv = Some(mv.map(|m| m.max(n)).unwrap_or(n));
240 }
241 }
242 }
243 } else {
244 let v = a.value()?.into_literal();
245 match v {
246 LiteralValue::Error(e) => {
247 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
248 }
249 other => {
250 if let Ok(n) = coerce_num(&other) {
251 mv = Some(mv.map(|m| m.max(n)).unwrap_or(n));
252 }
253 }
254 }
255 }
256 }
257 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
258 mv.unwrap_or(0.0),
259 )))
260 }
261}
262
263pub fn register_builtins() {
264 use std::sync::Arc;
265 crate::function_registry::register_function(Arc::new(MinFn));
266 crate::function_registry::register_function(Arc::new(MaxFn));
267}
268
269#[cfg(test)]
270mod tests_min_max {
271 use super::*;
272 use crate::test_workbook::TestWorkbook;
273 use crate::traits::ArgumentHandle;
274 use formualizer_common::LiteralValue;
275 use formualizer_parse::parser::{ASTNode, ASTNodeType};
276 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
277 wb.interpreter()
278 }
279
280 #[test]
281 fn min_basic_array_and_scalar() {
282 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MinFn));
283 let ctx = interp(&wb);
284 let arr = ASTNode::new(
285 ASTNodeType::Literal(LiteralValue::Array(vec![vec![
286 LiteralValue::Int(5),
287 LiteralValue::Int(2),
288 LiteralValue::Int(9),
289 ]])),
290 None,
291 );
292 let extra = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
293 let f = ctx.context.get_function("", "MIN").unwrap();
294 let out = f
295 .dispatch(
296 &[
297 ArgumentHandle::new(&arr, &ctx),
298 ArgumentHandle::new(&extra, &ctx),
299 ],
300 &ctx.function_context(None),
301 )
302 .unwrap()
303 .into_literal();
304 assert_eq!(out, LiteralValue::Number(1.0));
305 }
306
307 #[test]
308 fn max_basic_with_text_ignored() {
309 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MaxFn));
310 let ctx = interp(&wb);
311 let arr = ASTNode::new(
312 ASTNodeType::Literal(LiteralValue::Array(vec![vec![
313 LiteralValue::Int(5),
314 LiteralValue::Text("x".into()),
315 LiteralValue::Int(9),
316 ]])),
317 None,
318 );
319 let f = ctx.context.get_function("", "MAX").unwrap();
320 let out = f
321 .dispatch(
322 &[ArgumentHandle::new(&arr, &ctx)],
323 &ctx.function_context(None),
324 )
325 .unwrap()
326 .into_literal();
327 assert_eq!(out, LiteralValue::Number(9.0));
328 }
329
330 #[test]
331 fn min_error_propagates() {
332 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MinFn));
333 let ctx = interp(&wb);
334 let err = ASTNode::new(
335 ASTNodeType::Literal(LiteralValue::Error(ExcelError::new_na())),
336 None,
337 );
338 let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
339 let f = ctx.context.get_function("", "MIN").unwrap();
340 let out = f
341 .dispatch(
342 &[
343 ArgumentHandle::new(&err, &ctx),
344 ArgumentHandle::new(&one, &ctx),
345 ],
346 &ctx.function_context(None),
347 )
348 .unwrap()
349 .into_literal();
350 match out {
351 LiteralValue::Error(e) => assert_eq!(e, "#N/A"),
352 v => panic!("expected error got {v:?}"),
353 }
354 }
355
356 #[test]
357 fn max_error_propagates() {
358 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(MaxFn));
359 let ctx = interp(&wb);
360 let err = ASTNode::new(
361 ASTNodeType::Literal(LiteralValue::Error(ExcelError::from_error_string(
362 "#DIV/0!",
363 ))),
364 None,
365 );
366 let one = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
367 let f = ctx.context.get_function("", "MAX").unwrap();
368 let out = f
369 .dispatch(
370 &[
371 ArgumentHandle::new(&one, &ctx),
372 ArgumentHandle::new(&err, &ctx),
373 ],
374 &ctx.function_context(None),
375 )
376 .unwrap()
377 .into_literal();
378 match out {
379 LiteralValue::Error(e) => assert_eq!(e, "#DIV/0!"),
380 v => panic!("expected error got {v:?}"),
381 }
382 }
383}