1use super::super::utils::collapse_if_scalar;
14use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
15use crate::function::Function;
16use crate::traits::{ArgumentHandle, FunctionContext};
17use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
18use formualizer_macros::func_caps;
19
20#[derive(Debug)]
21pub struct HStackFn;
22#[derive(Debug)]
23pub struct VStackFn;
24
25fn materialize_arg<'b>(
26 arg: &ArgumentHandle<'_, 'b>,
27 ctx: &dyn FunctionContext<'b>,
28) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
29 if let Ok(r) = arg.as_reference_or_eval() {
31 let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
32 let sheet = ctx.current_sheet();
33 let rv = ctx.resolve_range_view(&r, sheet)?;
34 rv.for_each_row(&mut |row| {
35 rows.push(row.to_vec());
36 Ok(())
37 })?;
38 Ok(rows)
39 } else {
40 let cv = arg.value()?;
41 match cv.into_literal() {
42 LiteralValue::Array(a) => Ok(a),
43 v => Ok(vec![vec![v]]),
44 }
45 }
46}
47
48impl Function for HStackFn {
49 func_caps!(PURE);
50 fn name(&self) -> &'static str {
51 "HSTACK"
52 }
53 fn min_args(&self) -> usize {
54 1
55 }
56 fn variadic(&self) -> bool {
57 true
58 }
59 fn arg_schema(&self) -> &'static [ArgSchema] {
60 use once_cell::sync::Lazy;
61 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
62 vec![ArgSchema {
63 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
64 required: true,
65 by_ref: false,
66 shape: ShapeKind::Range,
67 coercion: CoercionPolicy::None,
68 max: None,
69 repeating: Some(1),
70 default: None,
71 }]
72 });
73 &SCHEMA
74 }
75 fn eval<'a, 'b, 'c>(
76 &self,
77 args: &'c [ArgumentHandle<'a, 'b>],
78 ctx: &dyn FunctionContext<'b>,
79 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
80 if args.is_empty() {
81 return Ok(crate::traits::CalcValue::Range(
82 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
83 ));
84 }
85
86 let mut entries = Vec::with_capacity(args.len());
87 let mut target_rows: Option<usize> = None;
88 let mut total_cols = 0;
89
90 for a in args {
91 if let Ok(v) = a.range_view() {
92 let (rows, cols) = v.dims();
93 if rows == 0 || cols == 0 {
94 continue;
95 }
96 if let Some(tr) = target_rows {
97 if rows != tr {
98 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
99 ExcelError::new(ExcelErrorKind::Value),
100 )));
101 }
102 } else {
103 target_rows = Some(rows);
104 }
105 total_cols += cols;
106 entries.push(HStackEntry::View(v));
107 } else {
108 let v = a.value()?.into_literal();
109 if let Some(tr) = target_rows {
110 if tr != 1 {
111 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
112 ExcelError::new(ExcelErrorKind::Value),
113 )));
114 }
115 } else {
116 target_rows = Some(1);
117 }
118 total_cols += 1;
119 entries.push(HStackEntry::Scalar(v));
120 }
121 }
122
123 if entries.is_empty() {
124 return Ok(crate::traits::CalcValue::Range(
125 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
126 ));
127 }
128
129 let row_count = target_rows.unwrap();
130 let mut result: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_count);
131 for _ in 0..row_count {
132 result.push(Vec::with_capacity(total_cols));
133 }
134
135 for entry in entries {
136 match entry {
137 HStackEntry::View(v) => {
138 let (v_rows, v_cols) = v.dims();
139 for (r, row) in result.iter_mut().enumerate().take(v_rows) {
140 for c in 0..v_cols {
141 row.push(v.get_cell(r, c));
142 }
143 }
144 }
145 HStackEntry::Scalar(s) => {
146 result[0].push(s);
147 }
148 }
149 }
150
151 Ok(collapse_if_scalar(result, ctx.date_system()))
152 }
153}
154
155enum HStackEntry<'a> {
156 View(crate::engine::range_view::RangeView<'a>),
157 Scalar(LiteralValue),
158}
159
160impl Function for VStackFn {
161 func_caps!(PURE);
162 fn name(&self) -> &'static str {
163 "VSTACK"
164 }
165 fn min_args(&self) -> usize {
166 1
167 }
168 fn variadic(&self) -> bool {
169 true
170 }
171 fn arg_schema(&self) -> &'static [ArgSchema] {
172 use once_cell::sync::Lazy;
173 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
174 vec![ArgSchema {
175 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
176 required: true,
177 by_ref: false,
178 shape: ShapeKind::Range,
179 coercion: CoercionPolicy::None,
180 max: None,
181 repeating: Some(1),
182 default: None,
183 }]
184 });
185 &SCHEMA
186 }
187 fn eval<'a, 'b, 'c>(
188 &self,
189 args: &'c [ArgumentHandle<'a, 'b>],
190 ctx: &dyn FunctionContext<'b>,
191 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
192 if args.is_empty() {
193 return Ok(crate::traits::CalcValue::Range(
194 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
195 ));
196 }
197
198 let mut target_width: Option<usize> = None;
199 let mut total_rows = 0;
200 let mut entries = Vec::with_capacity(args.len());
201
202 for a in args {
203 if let Ok(v) = a.range_view() {
204 let (rows, cols) = v.dims();
205 if rows == 0 || cols == 0 {
206 continue;
207 }
208 if let Some(tw) = target_width {
209 if cols != tw {
210 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
211 ExcelError::new(ExcelErrorKind::Value),
212 )));
213 }
214 } else {
215 target_width = Some(cols);
216 }
217 total_rows += rows;
218 entries.push(VStackEntry::View(v));
219 } else {
220 let v = a.value()?.into_literal();
221 if let Some(tw) = target_width {
222 if tw != 1 {
223 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
224 ExcelError::new(ExcelErrorKind::Value),
225 )));
226 }
227 } else {
228 target_width = Some(1);
229 }
230 total_rows += 1;
231 entries.push(VStackEntry::Scalar(v));
232 }
233 }
234
235 if entries.is_empty() {
236 return Ok(crate::traits::CalcValue::Range(
237 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
238 ));
239 }
240
241 let mut result: Vec<Vec<LiteralValue>> = Vec::with_capacity(total_rows);
242 for entry in entries {
243 match entry {
244 VStackEntry::View(v) => {
245 let _ = v.for_each_row(&mut |row| {
246 result.push(row.to_vec());
247 Ok(())
248 });
249 }
250 VStackEntry::Scalar(s) => {
251 result.push(vec![s]);
252 }
253 }
254 }
255
256 Ok(collapse_if_scalar(result, ctx.date_system()))
257 }
258}
259
260enum VStackEntry<'a> {
261 View(crate::engine::range_view::RangeView<'a>),
262 Scalar(LiteralValue),
263}
264
265pub fn register_builtins() {
266 use crate::function_registry::register_function;
267 use std::sync::Arc;
268 register_function(Arc::new(HStackFn));
269 register_function(Arc::new(VStackFn));
270}
271
272#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::test_workbook::TestWorkbook;
277 use crate::traits::ArgumentHandle;
278 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
279 use std::sync::Arc;
280
281 fn ref_range(r: &str, sr: i32, sc: i32, er: i32, ec: i32) -> ASTNode {
282 ASTNode::new(
283 ASTNodeType::Reference {
284 original: r.into(),
285 reference: ReferenceType::range(
286 None,
287 Some(sr as u32),
288 Some(sc as u32),
289 Some(er as u32),
290 Some(ec as u32),
291 ),
292 },
293 None,
294 )
295 }
296
297 fn lit(v: LiteralValue) -> ASTNode {
298 ASTNode::new(ASTNodeType::Literal(v), None)
299 }
300
301 #[test]
302 fn hstack_basic_and_mismatched_rows() {
303 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
304 let wb = wb
305 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
306 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
307 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
308 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
309 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100)); let ctx = wb.interpreter();
311 let left = ref_range("A1:A2", 1, 1, 2, 1);
312 let right = ref_range("B1:B2", 1, 2, 2, 2);
313 let f = ctx.context.get_function("", "HSTACK").unwrap();
314 let args = vec![
315 ArgumentHandle::new(&left, &ctx),
316 ArgumentHandle::new(&right, &ctx),
317 ];
318 let v = f
319 .dispatch(&args, &ctx.function_context(None))
320 .unwrap()
321 .into_literal();
322 match v {
323 LiteralValue::Array(a) => {
324 assert_eq!(a.len(), 2);
325 assert_eq!(
326 a[0],
327 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
328 );
329 }
330 other => panic!("expected array got {other:?}"),
331 }
332 let mism = ref_range("C1:C1", 1, 3, 1, 3);
334 let args_bad = vec![
335 ArgumentHandle::new(&left, &ctx),
336 ArgumentHandle::new(&mism, &ctx),
337 ];
338 let v_bad = f
339 .dispatch(&args_bad, &ctx.function_context(None))
340 .unwrap()
341 .into_literal();
342 match v_bad {
343 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
344 other => panic!("expected #VALUE! got {other:?}"),
345 }
346 }
347
348 #[test]
349 fn vstack_basic_and_mismatched_cols() {
350 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
351 let wb = wb
352 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
353 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
354 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
355 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
356 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100))
357 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(200));
358 let ctx = wb.interpreter();
359 let top = ref_range("A1:B1", 1, 1, 1, 2);
360 let bottom = ref_range("A2:B2", 2, 1, 2, 2);
361 let f = ctx.context.get_function("", "VSTACK").unwrap();
362 let args = vec![
363 ArgumentHandle::new(&top, &ctx),
364 ArgumentHandle::new(&bottom, &ctx),
365 ];
366 let v = f
367 .dispatch(&args, &ctx.function_context(None))
368 .unwrap()
369 .into_literal();
370 match v {
371 LiteralValue::Array(a) => {
372 assert_eq!(a.len(), 2);
373 assert_eq!(
374 a[0],
375 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
376 );
377 }
378 other => panic!("expected array got {other:?}"),
379 }
380 let extra = ref_range("A1:C1", 1, 1, 1, 3);
382 let args_bad = vec![
383 ArgumentHandle::new(&top, &ctx),
384 ArgumentHandle::new(&extra, &ctx),
385 ];
386 let v_bad = f
387 .dispatch(&args_bad, &ctx.function_context(None))
388 .unwrap()
389 .into_literal();
390 match v_bad {
391 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
392 other => panic!("expected #VALUE! got {other:?}"),
393 }
394 }
395
396 #[test]
397 fn hstack_scalar_and_array_collapse() {
398 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
399 let ctx = wb.interpreter();
400 let f = ctx.context.get_function("", "HSTACK").unwrap();
401 let s1 = lit(LiteralValue::Int(5));
402 let s2 = lit(LiteralValue::Int(6));
403 let args = vec![
404 ArgumentHandle::new(&s1, &ctx),
405 ArgumentHandle::new(&s2, &ctx),
406 ];
407 let v = f
408 .dispatch(&args, &ctx.function_context(None))
409 .unwrap()
410 .into_literal();
411 match v {
413 LiteralValue::Array(a) => {
414 assert_eq!(a.len(), 1);
415 assert_eq!(
416 a[0],
417 vec![LiteralValue::Number(5.0), LiteralValue::Number(6.0)]
418 );
419 }
420 other => panic!("expected array got {other:?}"),
421 }
422 }
423
424 #[test]
425 fn vstack_scalar_collapse_single_result() {
426 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
427 let ctx = wb.interpreter();
428 let f = ctx.context.get_function("", "VSTACK").unwrap();
429 let lone = lit(LiteralValue::Int(9));
430 let args = vec![ArgumentHandle::new(&lone, &ctx)];
431 let v = f
432 .dispatch(&args, &ctx.function_context(None))
433 .unwrap()
434 .into_literal();
435 assert_eq!(v, LiteralValue::Int(9));
436 }
437}