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 {
100 func_caps!(PURE);
101 fn name(&self) -> &'static str {
102 "HSTACK"
103 }
104 fn min_args(&self) -> usize {
105 1
106 }
107 fn variadic(&self) -> bool {
108 true
109 }
110 fn arg_schema(&self) -> &'static [ArgSchema] {
111 use once_cell::sync::Lazy;
112 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
113 vec![ArgSchema {
114 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
115 required: true,
116 by_ref: false,
117 shape: ShapeKind::Range,
118 coercion: CoercionPolicy::None,
119 max: None,
120 repeating: Some(1),
121 default: None,
122 }]
123 });
124 &SCHEMA
125 }
126 fn eval<'a, 'b, 'c>(
127 &self,
128 args: &'c [ArgumentHandle<'a, 'b>],
129 ctx: &dyn FunctionContext<'b>,
130 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
131 if args.is_empty() {
132 return Ok(crate::traits::CalcValue::Range(
133 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
134 ));
135 }
136
137 let mut entries = Vec::with_capacity(args.len());
138 let mut target_rows: Option<usize> = None;
139 let mut total_cols = 0;
140
141 for a in args {
142 if let Ok(v) = a.range_view() {
143 let (rows, cols) = v.dims();
144 if rows == 0 || cols == 0 {
145 continue;
146 }
147 if let Some(tr) = target_rows {
148 if rows != tr {
149 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
150 ExcelError::new(ExcelErrorKind::Value),
151 )));
152 }
153 } else {
154 target_rows = Some(rows);
155 }
156 total_cols += cols;
157 entries.push(HStackEntry::View(v));
158 } else {
159 let v = a.value()?.into_literal();
160 if let Some(tr) = target_rows {
161 if tr != 1 {
162 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
163 ExcelError::new(ExcelErrorKind::Value),
164 )));
165 }
166 } else {
167 target_rows = Some(1);
168 }
169 total_cols += 1;
170 entries.push(HStackEntry::Scalar(v));
171 }
172 }
173
174 if entries.is_empty() {
175 return Ok(crate::traits::CalcValue::Range(
176 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
177 ));
178 }
179
180 let row_count = target_rows.unwrap();
181 let mut result: Vec<Vec<LiteralValue>> = Vec::with_capacity(row_count);
182 for _ in 0..row_count {
183 result.push(Vec::with_capacity(total_cols));
184 }
185
186 for entry in entries {
187 match entry {
188 HStackEntry::View(v) => {
189 let (v_rows, v_cols) = v.dims();
190 for (r, row) in result.iter_mut().enumerate().take(v_rows) {
191 for c in 0..v_cols {
192 row.push(v.get_cell(r, c));
193 }
194 }
195 }
196 HStackEntry::Scalar(s) => {
197 result[0].push(s);
198 }
199 }
200 }
201
202 Ok(collapse_if_scalar(result, ctx.date_system()))
203 }
204}
205
206enum HStackEntry<'a> {
207 View(crate::engine::range_view::RangeView<'a>),
208 Scalar(LiteralValue),
209}
210
211impl Function for VStackFn {
261 func_caps!(PURE);
262 fn name(&self) -> &'static str {
263 "VSTACK"
264 }
265 fn min_args(&self) -> usize {
266 1
267 }
268 fn variadic(&self) -> bool {
269 true
270 }
271 fn arg_schema(&self) -> &'static [ArgSchema] {
272 use once_cell::sync::Lazy;
273 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
274 vec![ArgSchema {
275 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
276 required: true,
277 by_ref: false,
278 shape: ShapeKind::Range,
279 coercion: CoercionPolicy::None,
280 max: None,
281 repeating: Some(1),
282 default: None,
283 }]
284 });
285 &SCHEMA
286 }
287 fn eval<'a, 'b, 'c>(
288 &self,
289 args: &'c [ArgumentHandle<'a, 'b>],
290 ctx: &dyn FunctionContext<'b>,
291 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
292 if args.is_empty() {
293 return Ok(crate::traits::CalcValue::Range(
294 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
295 ));
296 }
297
298 let mut target_width: Option<usize> = None;
299 let mut total_rows = 0;
300 let mut entries = Vec::with_capacity(args.len());
301
302 for a in args {
303 if let Ok(v) = a.range_view() {
304 let (rows, cols) = v.dims();
305 if rows == 0 || cols == 0 {
306 continue;
307 }
308 if let Some(tw) = target_width {
309 if cols != tw {
310 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
311 ExcelError::new(ExcelErrorKind::Value),
312 )));
313 }
314 } else {
315 target_width = Some(cols);
316 }
317 total_rows += rows;
318 entries.push(VStackEntry::View(v));
319 } else {
320 let v = a.value()?.into_literal();
321 if let Some(tw) = target_width {
322 if tw != 1 {
323 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
324 ExcelError::new(ExcelErrorKind::Value),
325 )));
326 }
327 } else {
328 target_width = Some(1);
329 }
330 total_rows += 1;
331 entries.push(VStackEntry::Scalar(v));
332 }
333 }
334
335 if entries.is_empty() {
336 return Ok(crate::traits::CalcValue::Range(
337 crate::engine::range_view::RangeView::from_owned_rows(vec![], ctx.date_system()),
338 ));
339 }
340
341 let mut result: Vec<Vec<LiteralValue>> = Vec::with_capacity(total_rows);
342 for entry in entries {
343 match entry {
344 VStackEntry::View(v) => {
345 let _ = v.for_each_row(&mut |row| {
346 result.push(row.to_vec());
347 Ok(())
348 });
349 }
350 VStackEntry::Scalar(s) => {
351 result.push(vec![s]);
352 }
353 }
354 }
355
356 Ok(collapse_if_scalar(result, ctx.date_system()))
357 }
358}
359
360enum VStackEntry<'a> {
361 View(crate::engine::range_view::RangeView<'a>),
362 Scalar(LiteralValue),
363}
364
365pub fn register_builtins() {
366 use crate::function_registry::register_function;
367 use std::sync::Arc;
368 register_function(Arc::new(HStackFn));
369 register_function(Arc::new(VStackFn));
370}
371
372#[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::test_workbook::TestWorkbook;
377 use crate::traits::ArgumentHandle;
378 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
379 use std::sync::Arc;
380
381 fn ref_range(r: &str, sr: i32, sc: i32, er: i32, ec: i32) -> ASTNode {
382 ASTNode::new(
383 ASTNodeType::Reference {
384 original: r.into(),
385 reference: ReferenceType::range(
386 None,
387 Some(sr as u32),
388 Some(sc as u32),
389 Some(er as u32),
390 Some(ec as u32),
391 ),
392 },
393 None,
394 )
395 }
396
397 fn lit(v: LiteralValue) -> ASTNode {
398 ASTNode::new(ASTNodeType::Literal(v), None)
399 }
400
401 #[test]
402 fn hstack_basic_and_mismatched_rows() {
403 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
404 let wb = wb
405 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
406 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
407 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
408 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
409 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100)); let ctx = wb.interpreter();
411 let left = ref_range("A1:A2", 1, 1, 2, 1);
412 let right = ref_range("B1:B2", 1, 2, 2, 2);
413 let f = ctx.context.get_function("", "HSTACK").unwrap();
414 let args = vec![
415 ArgumentHandle::new(&left, &ctx),
416 ArgumentHandle::new(&right, &ctx),
417 ];
418 let v = f
419 .dispatch(&args, &ctx.function_context(None))
420 .unwrap()
421 .into_literal();
422 match v {
423 LiteralValue::Array(a) => {
424 assert_eq!(a.len(), 2);
425 assert_eq!(
426 a[0],
427 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
428 );
429 }
430 other => panic!("expected array got {other:?}"),
431 }
432 let mism = ref_range("C1:C1", 1, 3, 1, 3);
434 let args_bad = vec![
435 ArgumentHandle::new(&left, &ctx),
436 ArgumentHandle::new(&mism, &ctx),
437 ];
438 let v_bad = f
439 .dispatch(&args_bad, &ctx.function_context(None))
440 .unwrap()
441 .into_literal();
442 match v_bad {
443 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
444 other => panic!("expected #VALUE! got {other:?}"),
445 }
446 }
447
448 #[test]
449 fn vstack_basic_and_mismatched_cols() {
450 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
451 let wb = wb
452 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
453 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
454 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(2))
455 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
456 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(100))
457 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(200));
458 let ctx = wb.interpreter();
459 let top = ref_range("A1:B1", 1, 1, 1, 2);
460 let bottom = ref_range("A2:B2", 2, 1, 2, 2);
461 let f = ctx.context.get_function("", "VSTACK").unwrap();
462 let args = vec![
463 ArgumentHandle::new(&top, &ctx),
464 ArgumentHandle::new(&bottom, &ctx),
465 ];
466 let v = f
467 .dispatch(&args, &ctx.function_context(None))
468 .unwrap()
469 .into_literal();
470 match v {
471 LiteralValue::Array(a) => {
472 assert_eq!(a.len(), 2);
473 assert_eq!(
474 a[0],
475 vec![LiteralValue::Number(1.0), LiteralValue::Number(10.0)]
476 );
477 }
478 other => panic!("expected array got {other:?}"),
479 }
480 let extra = ref_range("A1:C1", 1, 1, 1, 3);
482 let args_bad = vec![
483 ArgumentHandle::new(&top, &ctx),
484 ArgumentHandle::new(&extra, &ctx),
485 ];
486 let v_bad = f
487 .dispatch(&args_bad, &ctx.function_context(None))
488 .unwrap()
489 .into_literal();
490 match v_bad {
491 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
492 other => panic!("expected #VALUE! got {other:?}"),
493 }
494 }
495
496 #[test]
497 fn hstack_scalar_and_array_collapse() {
498 let wb = TestWorkbook::new().with_function(Arc::new(HStackFn));
499 let ctx = wb.interpreter();
500 let f = ctx.context.get_function("", "HSTACK").unwrap();
501 let s1 = lit(LiteralValue::Int(5));
502 let s2 = lit(LiteralValue::Int(6));
503 let args = vec![
504 ArgumentHandle::new(&s1, &ctx),
505 ArgumentHandle::new(&s2, &ctx),
506 ];
507 let v = f
508 .dispatch(&args, &ctx.function_context(None))
509 .unwrap()
510 .into_literal();
511 match v {
513 LiteralValue::Array(a) => {
514 assert_eq!(a.len(), 1);
515 assert_eq!(
516 a[0],
517 vec![LiteralValue::Number(5.0), LiteralValue::Number(6.0)]
518 );
519 }
520 other => panic!("expected array got {other:?}"),
521 }
522 }
523
524 #[test]
525 fn vstack_scalar_collapse_single_result() {
526 let wb = TestWorkbook::new().with_function(Arc::new(VStackFn));
527 let ctx = wb.interpreter();
528 let f = ctx.context.get_function("", "VSTACK").unwrap();
529 let lone = lit(LiteralValue::Int(9));
530 let args = vec![ArgumentHandle::new(&lone, &ctx)];
531 let v = f
532 .dispatch(&args, &ctx.function_context(None))
533 .unwrap()
534 .into_literal();
535 assert_eq!(v, LiteralValue::Int(9));
536 }
537}