formualizer_eval/builtins/lookup/
stack.rs1use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
14use crate::function::Function;
15use crate::traits::{ArgumentHandle, FunctionContext};
16use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
17use formualizer_macros::func_caps;
18
19#[derive(Debug)]
20pub struct HStackFn;
21#[derive(Debug)]
22pub struct VStackFn;
23
24fn materialize_arg(
25 arg: &ArgumentHandle,
26 ctx: &dyn FunctionContext,
27) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
28 if let Ok(r) = arg.as_reference_or_eval() {
30 let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
31 let sheet = "Sheet1"; let rv = ctx.resolve_range_view(&r, sheet)?;
33 rv.for_each_row(&mut |row| {
34 rows.push(row.to_vec());
35 Ok(())
36 })?;
37 Ok(rows)
38 } else {
39 match arg.value()?.as_ref() {
40 LiteralValue::Array(a) => Ok(a.clone()),
41 v => Ok(vec![vec![v.clone()]]),
42 }
43 }
44}
45
46fn collapse_if_scalar(mut rows: Vec<Vec<LiteralValue>>) -> LiteralValue {
47 if rows.len() == 1 && rows[0].len() == 1 {
48 return rows.remove(0).remove(0);
49 }
50 LiteralValue::Array(rows)
51}
52
53impl Function for HStackFn {
54 func_caps!(PURE);
55 fn name(&self) -> &'static str {
56 "HSTACK"
57 }
58 fn min_args(&self) -> usize {
59 1
60 }
61 fn variadic(&self) -> bool {
62 true
63 }
64 fn arg_schema(&self) -> &'static [ArgSchema] {
65 use once_cell::sync::Lazy;
66 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
67 vec![ArgSchema {
68 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
69 required: true,
70 by_ref: false,
71 shape: ShapeKind::Range,
72 coercion: CoercionPolicy::None,
73 max: None,
74 repeating: Some(1),
75 default: None,
76 }]
77 });
78 &SCHEMA
79 }
80 fn eval_scalar<'a, 'b>(
81 &self,
82 args: &'a [ArgumentHandle<'a, 'b>],
83 ctx: &dyn FunctionContext,
84 ) -> Result<LiteralValue, ExcelError> {
85 if args.is_empty() {
86 return Ok(LiteralValue::Array(vec![]));
87 }
88 let mut blocks: Vec<Vec<Vec<LiteralValue>>> = Vec::new();
89 let mut target_rows: Option<usize> = None;
90 for a in args {
91 let rows = materialize_arg(a, ctx)?;
92 if rows.is_empty() {
93 continue;
94 }
95 if let Some(tr) = target_rows {
96 if rows.len() != tr {
97 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
98 }
99 } else {
100 target_rows = Some(rows.len());
101 }
102 blocks.push(rows);
103 }
104 if blocks.is_empty() {
105 return Ok(LiteralValue::Array(vec![]));
106 }
107 let row_count = target_rows.unwrap();
108 for b in &blocks {
110 let w = b[0].len();
112 if b.iter().any(|r| r.len() != w) {
113 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
114 }
115 }
116 let mut result: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_count);
117 for r in 0..row_count {
118 result.push(Vec::new());
119 }
120 for b in blocks {
121 for (r, row_vec) in b.into_iter().enumerate() {
122 result[r].extend(row_vec);
123 }
124 }
125 Ok(collapse_if_scalar(result))
126 }
127}
128
129impl Function for VStackFn {
130 func_caps!(PURE);
131 fn name(&self) -> &'static str {
132 "VSTACK"
133 }
134 fn min_args(&self) -> usize {
135 1
136 }
137 fn variadic(&self) -> bool {
138 true
139 }
140 fn arg_schema(&self) -> &'static [ArgSchema] {
141 use once_cell::sync::Lazy;
142 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
143 vec![ArgSchema {
144 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
145 required: true,
146 by_ref: false,
147 shape: ShapeKind::Range,
148 coercion: CoercionPolicy::None,
149 max: None,
150 repeating: Some(1),
151 default: None,
152 }]
153 });
154 &SCHEMA
155 }
156 fn eval_scalar<'a, 'b>(
157 &self,
158 args: &'a [ArgumentHandle<'a, 'b>],
159 ctx: &dyn FunctionContext,
160 ) -> Result<LiteralValue, ExcelError> {
161 if args.is_empty() {
162 return Ok(LiteralValue::Array(vec![]));
163 }
164 let mut blocks: Vec<Vec<Vec<LiteralValue>>> = Vec::new();
165 let mut target_width: Option<usize> = None;
166 for a in args {
167 let rows = materialize_arg(a, ctx)?;
168 if rows.is_empty() {
169 continue;
170 }
171 let width = rows[0].len();
173 if rows.iter().any(|r| r.len() != width) {
174 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
175 }
176 if let Some(tw) = target_width {
177 if width != tw {
178 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
179 }
180 } else {
181 target_width = Some(width);
182 }
183 blocks.push(rows);
184 }
185 if blocks.is_empty() {
186 return Ok(LiteralValue::Array(vec![]));
187 }
188 let mut result: Vec<Vec<LiteralValue>> = Vec::new();
189 for b in blocks {
190 result.extend(b);
191 }
192 Ok(collapse_if_scalar(result))
193 }
194}
195
196pub fn register_builtins() {
197 use crate::function_registry::register_function;
198 use std::sync::Arc;
199 register_function(Arc::new(HStackFn));
200 register_function(Arc::new(VStackFn));
201}
202
203#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::test_workbook::TestWorkbook;
208 use crate::traits::ArgumentHandle;
209 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
210 use std::sync::Arc;
211
212 fn ref_range(r: &str, sr: i32, sc: i32, er: i32, ec: i32) -> ASTNode {
213 ASTNode::new(
214 ASTNodeType::Reference {
215 original: r.into(),
216 reference: ReferenceType::Range {
217 sheet: None,
218 start_row: Some(sr as u32),
219 start_col: Some(sc as u32),
220 end_row: Some(er as u32),
221 end_col: Some(ec as u32),
222 },
223 },
224 None,
225 )
226 }
227
228 fn lit(v: LiteralValue) -> ASTNode {
229 ASTNode::new(ASTNodeType::Literal(v), None)
230 }
231
232 #[test]
233 fn hstack_basic_and_mismatched_rows() {
234 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
235 let wb = wb
236 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
237 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
238 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
239 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
240 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100)); let ctx = wb.interpreter();
242 let left = ref_range("A1:A2", 1, 1, 2, 1);
243 let right = ref_range("B1:B2", 1, 2, 2, 2);
244 let f = ctx.context.get_function("", "HSTACK").unwrap();
245 let args = vec![
246 ArgumentHandle::new(&left, &ctx),
247 ArgumentHandle::new(&right, &ctx),
248 ];
249 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
250 match v {
251 LiteralValue::Array(a) => {
252 assert_eq!(a.len(), 2);
253 assert_eq!(a[0], vec![LiteralValue::Int(1), LiteralValue::Int(10)]);
254 }
255 other => panic!("expected array got {other:?}"),
256 }
257 let mism = ref_range("C1:C1", 1, 3, 1, 3);
259 let args_bad = vec![
260 ArgumentHandle::new(&left, &ctx),
261 ArgumentHandle::new(&mism, &ctx),
262 ];
263 let v_bad = f.dispatch(&args_bad, &ctx.function_context(None)).unwrap();
264 match v_bad {
265 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
266 other => panic!("expected #VALUE! got {other:?}"),
267 }
268 }
269
270 #[test]
271 fn vstack_basic_and_mismatched_cols() {
272 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
273 let wb = wb
274 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
275 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
276 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
277 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
278 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100))
279 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(200));
280 let ctx = wb.interpreter();
281 let top = ref_range("A1:B1", 1, 1, 1, 2);
282 let bottom = ref_range("A2:B2", 2, 1, 2, 2);
283 let f = ctx.context.get_function("", "VSTACK").unwrap();
284 let args = vec![
285 ArgumentHandle::new(&top, &ctx),
286 ArgumentHandle::new(&bottom, &ctx),
287 ];
288 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
289 match v {
290 LiteralValue::Array(a) => {
291 assert_eq!(a.len(), 2);
292 assert_eq!(a[0], vec![LiteralValue::Int(1), LiteralValue::Int(10)]);
293 }
294 other => panic!("expected array got {other:?}"),
295 }
296 let extra = ref_range("A1:C1", 1, 1, 1, 3);
298 let args_bad = vec![
299 ArgumentHandle::new(&top, &ctx),
300 ArgumentHandle::new(&extra, &ctx),
301 ];
302 let v_bad = f.dispatch(&args_bad, &ctx.function_context(None)).unwrap();
303 match v_bad {
304 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
305 other => panic!("expected #VALUE! got {other:?}"),
306 }
307 }
308
309 #[test]
310 fn hstack_scalar_and_array_collapse() {
311 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
312 let ctx = wb.interpreter();
313 let f = ctx.context.get_function("", "HSTACK").unwrap();
314 let s1 = lit(LiteralValue::Int(5));
315 let s2 = lit(LiteralValue::Int(6));
316 let args = vec![
317 ArgumentHandle::new(&s1, &ctx),
318 ArgumentHandle::new(&s2, &ctx),
319 ];
320 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
321 match v {
323 LiteralValue::Array(a) => {
324 assert_eq!(a.len(), 1);
325 assert_eq!(a[0], vec![LiteralValue::Int(5), LiteralValue::Int(6)]);
326 }
327 other => panic!("expected array got {other:?}"),
328 }
329 }
330
331 #[test]
332 fn vstack_scalar_collapse_single_result() {
333 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
334 let ctx = wb.interpreter();
335 let f = ctx.context.get_function("", "VSTACK").unwrap();
336 let lone = lit(LiteralValue::Int(9));
337 let args = vec![ArgumentHandle::new(&lone, &ctx)];
338 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
339 assert_eq!(v, LiteralValue::Int(9));
340 }
341}