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