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 formualizer_common::{ExcelError, LiteralValue};
6use formualizer_macros::func_caps;
7
8#[derive(Debug)]
9pub struct MinFn; impl 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; impl 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}