1use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
10use crate::function::Function;
11use crate::traits::{ArgumentHandle, FunctionContext};
12use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
13use formualizer_macros::func_caps;
14
15#[derive(Debug)]
16pub struct ChooseFn;
17#[derive(Debug)]
18pub struct ChooseColsFn;
19#[derive(Debug)]
20pub struct ChooseRowsFn;
21
22impl Function for ChooseFn {
23 fn name(&self) -> &'static str {
24 "CHOOSE"
25 }
26
27 fn min_args(&self) -> usize {
28 2
29 }
30
31 func_caps!(PURE, LOOKUP);
32
33 fn arg_schema(&self) -> &'static [ArgSchema] {
34 use once_cell::sync::Lazy;
35 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
36 vec![
37 ArgSchema {
39 kinds: smallvec::smallvec![ArgKind::Number],
40 required: true,
41 by_ref: false,
42 shape: ShapeKind::Scalar,
43 coercion: CoercionPolicy::NumberStrict,
44 max: None,
45 repeating: None,
46 default: None,
47 },
48 ArgSchema {
50 kinds: smallvec::smallvec![ArgKind::Any],
51 required: true,
52 by_ref: false, shape: ShapeKind::Scalar, coercion: CoercionPolicy::None,
55 max: None,
56 repeating: Some(1), default: None,
58 },
59 ]
60 });
61 &SCHEMA
62 }
63
64 fn eval_scalar<'a, 'b>(
65 &self,
66 args: &'a [ArgumentHandle<'a, 'b>],
67 _ctx: &dyn FunctionContext,
68 ) -> Result<LiteralValue, ExcelError> {
69 if args.len() < 2 {
70 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
71 }
72
73 let index_val = args[0].value()?;
75 if let LiteralValue::Error(e) = index_val.as_ref() {
76 return Ok(LiteralValue::Error(e.clone()));
77 }
78
79 let index = match index_val.as_ref() {
80 LiteralValue::Number(n) => *n as i64,
81 LiteralValue::Int(i) => *i,
82 LiteralValue::Text(s) => s.parse::<f64>().map(|n| n as i64).unwrap_or(-1),
83 _ => return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
84 };
85
86 if index < 1 || index as usize > args.len() - 1 {
88 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
89 }
90
91 let selected_arg = &args[index as usize];
93 match selected_arg.value() {
94 Ok(v) => Ok(v.as_ref().clone()),
95 Err(e) => Ok(LiteralValue::Error(e)),
96 }
97 }
98}
99
100fn materialize_rows_2d(
103 arg: &ArgumentHandle,
104 ctx: &dyn FunctionContext,
105) -> Result<Vec<Vec<formualizer_common::LiteralValue>>, ExcelError> {
106 if let Ok(r) = arg.as_reference_or_eval() {
107 let mut rows: Vec<Vec<LiteralValue>> = Vec::new();
108 let sheet = "Sheet1"; let rv = ctx.resolve_range_view(&r, sheet)?;
110 rv.for_each_row(&mut |row| {
111 rows.push(row.to_vec());
112 Ok(())
113 })?;
114 Ok(rows)
115 } else {
116 match arg.value()?.as_ref() {
117 LiteralValue::Array(a) => Ok(a.clone()),
118 v => Ok(vec![vec![v.clone()]]),
119 }
120 }
121}
122
123impl Function for ChooseColsFn {
124 func_caps!(PURE, LOOKUP);
125 fn name(&self) -> &'static str {
126 "CHOOSECOLS"
127 }
128 fn min_args(&self) -> usize {
129 2
130 }
131 fn variadic(&self) -> bool {
132 true
133 }
134 fn arg_schema(&self) -> &'static [ArgSchema] {
135 use once_cell::sync::Lazy;
136 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
137 vec![
138 ArgSchema {
140 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
141 required: true,
142 by_ref: false,
143 shape: ShapeKind::Range,
144 coercion: CoercionPolicy::None,
145 max: None,
146 repeating: None,
147 default: None,
148 },
149 ArgSchema {
151 kinds: smallvec::smallvec![ArgKind::Number],
152 required: true,
153 by_ref: false,
154 shape: ShapeKind::Scalar,
155 coercion: CoercionPolicy::NumberLenientText,
156 max: None,
157 repeating: Some(1),
158 default: None,
159 },
160 ]
161 });
162 &SCHEMA
163 }
164 fn eval_scalar<'a, 'b>(
165 &self,
166 args: &'a [ArgumentHandle<'a, 'b>],
167 ctx: &dyn FunctionContext,
168 ) -> Result<LiteralValue, ExcelError> {
169 if args.len() < 2 {
170 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
171 }
172 let rows = materialize_rows_2d(&args[0], ctx)?;
173 if rows.is_empty() {
174 return Ok(LiteralValue::Array(vec![]));
175 }
176 let width = rows[0].len();
177 if rows.iter().any(|r| r.len() != width) {
178 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
179 }
180 let mut indices: Vec<usize> = Vec::new();
181 for a in &args[1..] {
182 let v = a.value()?;
183 if let LiteralValue::Error(e) = v.as_ref() {
184 return Ok(LiteralValue::Error(e.clone()));
185 }
186 let raw = match v.as_ref() {
187 LiteralValue::Int(i) => *i,
188 LiteralValue::Number(n) => *n as i64,
189 _ => return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
190 };
191 if raw == 0 {
192 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
193 }
194 let adj = if raw > 0 {
195 raw - 1
196 } else {
197 (width as i64) + raw
198 };
199 if adj < 0 || adj as usize >= width {
200 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
201 }
202 indices.push(adj as usize);
203 }
204 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows.len());
205 for r in &rows {
206 let mut new_row = Vec::with_capacity(indices.len());
207 for &c in &indices {
208 new_row.push(r[c].clone());
209 }
210 out.push(new_row);
211 }
212 if out.len() == 1 && out[0].len() == 1 {
213 return Ok(out[0][0].clone());
214 }
215 Ok(LiteralValue::Array(out))
216 }
217}
218
219impl Function for ChooseRowsFn {
220 func_caps!(PURE, LOOKUP);
221 fn name(&self) -> &'static str {
222 "CHOOSEROWS"
223 }
224 fn min_args(&self) -> usize {
225 2
226 }
227 fn variadic(&self) -> bool {
228 true
229 }
230 fn arg_schema(&self) -> &'static [ArgSchema] {
231 use once_cell::sync::Lazy;
232 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
233 vec![
234 ArgSchema {
236 kinds: smallvec::smallvec![ArgKind::Range, ArgKind::Any],
237 required: true,
238 by_ref: false,
239 shape: ShapeKind::Range,
240 coercion: CoercionPolicy::None,
241 max: None,
242 repeating: None,
243 default: None,
244 },
245 ArgSchema {
247 kinds: smallvec::smallvec![ArgKind::Number],
248 required: true,
249 by_ref: false,
250 shape: ShapeKind::Scalar,
251 coercion: CoercionPolicy::NumberLenientText,
252 max: None,
253 repeating: Some(1),
254 default: None,
255 },
256 ]
257 });
258 &SCHEMA
259 }
260 fn eval_scalar<'a, 'b>(
261 &self,
262 args: &'a [ArgumentHandle<'a, 'b>],
263 ctx: &dyn FunctionContext,
264 ) -> Result<LiteralValue, ExcelError> {
265 if args.len() < 2 {
266 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
267 }
268 let rows = materialize_rows_2d(&args[0], ctx)?;
269 if rows.is_empty() {
270 return Ok(LiteralValue::Array(vec![]));
271 }
272 let height = rows.len();
273 let mut indices: Vec<usize> = Vec::new();
274 for a in &args[1..] {
275 let v = a.value()?;
276 if let LiteralValue::Error(e) = v.as_ref() {
277 return Ok(LiteralValue::Error(e.clone()));
278 }
279 let raw = match v.as_ref() {
280 LiteralValue::Int(i) => *i,
281 LiteralValue::Number(n) => *n as i64,
282 _ => return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value))),
283 };
284 if raw == 0 {
285 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
286 }
287 let adj = if raw > 0 {
288 raw - 1
289 } else {
290 (height as i64) + raw
291 };
292 if adj < 0 || adj as usize >= height {
293 return Ok(LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value)));
294 }
295 indices.push(adj as usize);
296 }
297 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(indices.len());
298 for &r in &indices {
299 out.push(rows[r].clone());
300 }
301 if out.len() == 1 && out[0].len() == 1 {
302 return Ok(out[0][0].clone());
303 }
304 Ok(LiteralValue::Array(out))
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use crate::test_workbook::TestWorkbook;
312 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
313 use std::sync::Arc;
314
315 fn lit(v: LiteralValue) -> ASTNode {
316 ASTNode::new(ASTNodeType::Literal(v), None)
317 }
318
319 #[test]
320 fn choose_basic() {
321 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
322 let ctx = wb.interpreter();
323 let f = ctx.context.get_function("", "CHOOSE").unwrap();
324
325 let two = lit(LiteralValue::Int(2));
327 let a = lit(LiteralValue::Text("A".into()));
328 let b = lit(LiteralValue::Text("B".into()));
329 let c = lit(LiteralValue::Text("C".into()));
330
331 let args = vec![
332 ArgumentHandle::new(&two, &ctx),
333 ArgumentHandle::new(&a, &ctx),
334 ArgumentHandle::new(&b, &ctx),
335 ArgumentHandle::new(&c, &ctx),
336 ];
337
338 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
339 assert_eq!(result, LiteralValue::Text("B".into()));
340 }
341
342 #[test]
343 fn choose_numeric_values() {
344 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
345 let ctx = wb.interpreter();
346 let f = ctx.context.get_function("", "CHOOSE").unwrap();
347
348 let three = lit(LiteralValue::Int(3));
350 let ten = lit(LiteralValue::Int(10));
351 let twenty = lit(LiteralValue::Int(20));
352 let thirty = lit(LiteralValue::Int(30));
353 let forty = lit(LiteralValue::Int(40));
354
355 let args = vec![
356 ArgumentHandle::new(&three, &ctx),
357 ArgumentHandle::new(&ten, &ctx),
358 ArgumentHandle::new(&twenty, &ctx),
359 ArgumentHandle::new(&thirty, &ctx),
360 ArgumentHandle::new(&forty, &ctx),
361 ];
362
363 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
364 assert_eq!(result, LiteralValue::Int(30));
365 }
366
367 #[test]
368 fn choose_out_of_range() {
369 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
370 let ctx = wb.interpreter();
371 let f = ctx.context.get_function("", "CHOOSE").unwrap();
372
373 let five = lit(LiteralValue::Int(5));
375 let a = lit(LiteralValue::Text("A".into()));
376 let b = lit(LiteralValue::Text("B".into()));
377 let c = lit(LiteralValue::Text("C".into()));
378
379 let args = vec![
380 ArgumentHandle::new(&five, &ctx),
381 ArgumentHandle::new(&a, &ctx),
382 ArgumentHandle::new(&b, &ctx),
383 ArgumentHandle::new(&c, &ctx),
384 ];
385
386 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
387 assert!(matches!(result, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
388
389 let zero = lit(LiteralValue::Int(0));
391 let args2 = vec![
392 ArgumentHandle::new(&zero, &ctx),
393 ArgumentHandle::new(&a, &ctx),
394 ArgumentHandle::new(&b, &ctx),
395 ];
396
397 let result2 = f.dispatch(&args2, &ctx.function_context(None)).unwrap();
398 assert!(matches!(result2, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
399 }
400
401 #[test]
402 fn choose_decimal_index() {
403 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
404 let ctx = wb.interpreter();
405 let f = ctx.context.get_function("", "CHOOSE").unwrap();
406
407 let two_seven = lit(LiteralValue::Number(2.7));
409 let a = lit(LiteralValue::Text("A".into()));
410 let b = lit(LiteralValue::Text("B".into()));
411 let c = lit(LiteralValue::Text("C".into()));
412
413 let args = vec![
414 ArgumentHandle::new(&two_seven, &ctx),
415 ArgumentHandle::new(&a, &ctx),
416 ArgumentHandle::new(&b, &ctx),
417 ArgumentHandle::new(&c, &ctx),
418 ];
419
420 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
421 assert_eq!(result, LiteralValue::Text("B".into()));
422 }
423
424 #[test]
425 fn choose_text_index_numeric_string() {
426 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
427 let ctx = wb.interpreter();
428 let f = ctx.context.get_function("", "CHOOSE").unwrap();
429 let two_txt = lit(LiteralValue::Text("2".into()));
430 let a = lit(LiteralValue::Text("A".into()));
431 let b = lit(LiteralValue::Text("B".into()));
432 let c = lit(LiteralValue::Text("C".into()));
433 let args = vec![
434 ArgumentHandle::new(&two_txt, &ctx),
435 ArgumentHandle::new(&a, &ctx),
436 ArgumentHandle::new(&b, &ctx),
437 ArgumentHandle::new(&c, &ctx),
438 ];
439 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
440 assert!(matches!(result, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
442 }
443
444 #[test]
445 fn choose_decimal_less_than_one() {
446 let wb = TestWorkbook::new().with_function(Arc::new(ChooseFn));
447 let ctx = wb.interpreter();
448 let f = ctx.context.get_function("", "CHOOSE").unwrap();
449 let zero_nine = lit(LiteralValue::Number(0.9));
450 let a = lit(LiteralValue::Text("A".into()));
451 let b = lit(LiteralValue::Text("B".into()));
452 let args = vec![
453 ArgumentHandle::new(&zero_nine, &ctx),
454 ArgumentHandle::new(&a, &ctx),
455 ArgumentHandle::new(&b, &ctx),
456 ];
457 let result = f.dispatch(&args, &ctx.function_context(None)).unwrap();
458 assert!(matches!(result, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Value));
459 }
460
461 fn range(r: &str, sr: u32, sc: u32, er: u32, ec: u32) -> ASTNode {
462 ASTNode::new(
463 ASTNodeType::Reference {
464 original: r.into(),
465 reference: ReferenceType::Range {
466 sheet: None,
467 start_row: Some(sr),
468 start_col: Some(sc),
469 end_row: Some(er),
470 end_col: Some(ec),
471 },
472 },
473 None,
474 )
475 }
476
477 #[test]
478 fn choosecols_basic_and_negative_and_duplicates() {
479 let wb = TestWorkbook::new().with_function(Arc::new(ChooseColsFn));
480 let wb = wb
481 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
482 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(2))
483 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(3))
484 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
485 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
486 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(30));
487 let ctx = wb.interpreter();
488 let arr = range("A1:C2", 1, 1, 2, 3);
489 let f = ctx.context.get_function("", "CHOOSECOLS").unwrap();
490 let one = lit(LiteralValue::Int(1));
491 let three = lit(LiteralValue::Int(3));
492 let neg_one = lit(LiteralValue::Int(-1));
493 let args = vec![
495 ArgumentHandle::new(&arr, &ctx),
496 ArgumentHandle::new(&one, &ctx),
497 ArgumentHandle::new(&three, &ctx),
498 ];
499 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
500 match v {
501 LiteralValue::Array(a) => {
502 assert_eq!(a.len(), 2);
503 assert_eq!(a[0], vec![LiteralValue::Int(1), LiteralValue::Int(3)]);
504 }
505 other => panic!("expected array got {other:?}"),
506 }
507 let args_neg = vec![
509 ArgumentHandle::new(&arr, &ctx),
510 ArgumentHandle::new(&neg_one, &ctx),
511 ];
512 let v2 = f.dispatch(&args_neg, &ctx.function_context(None)).unwrap();
513 match v2 {
514 LiteralValue::Array(a) => {
515 assert_eq!(a[0], vec![LiteralValue::Int(3)]);
516 }
517 other => panic!("expected array last col got {other:?}"),
518 }
519 let args_dup = vec![
521 ArgumentHandle::new(&arr, &ctx),
522 ArgumentHandle::new(&one, &ctx),
523 ArgumentHandle::new(&one, &ctx),
524 ];
525 let v3 = f.dispatch(&args_dup, &ctx.function_context(None)).unwrap();
526 match v3 {
527 LiteralValue::Array(a) => {
528 assert_eq!(a[0], vec![LiteralValue::Int(1), LiteralValue::Int(1)]);
529 }
530 other => panic!("expected dup cols got {other:?}"),
531 }
532 }
533
534 #[test]
535 fn choosecols_out_of_range() {
536 let wb = TestWorkbook::new().with_function(Arc::new(ChooseColsFn));
537 let wb = wb
538 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
539 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(2));
540 let ctx = wb.interpreter();
541 let arr = range("A1:B1", 1, 1, 1, 2);
542 let f = ctx.context.get_function("", "CHOOSECOLS").unwrap();
543 let three = lit(LiteralValue::Int(3));
544 let args = vec![
545 ArgumentHandle::new(&arr, &ctx),
546 ArgumentHandle::new(&three, &ctx),
547 ];
548 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
549 match v {
550 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
551 other => panic!("expected #VALUE! got {other:?}"),
552 }
553 }
554
555 #[test]
556 fn chooserows_basic_and_negative() {
557 let wb = TestWorkbook::new().with_function(Arc::new(ChooseRowsFn));
558 let wb = wb
559 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
560 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(2))
561 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(10))
562 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
563 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(100))
564 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(200));
565 let ctx = wb.interpreter();
566 let arr = range("A1:B3", 1, 1, 3, 2);
567 let f = ctx.context.get_function("", "CHOOSEROWS").unwrap();
568 let one = lit(LiteralValue::Int(1));
569 let neg_one = lit(LiteralValue::Int(-1));
570 let args = vec![
571 ArgumentHandle::new(&arr, &ctx),
572 ArgumentHandle::new(&one, &ctx),
573 ArgumentHandle::new(&neg_one, &ctx),
574 ];
575 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
576 match v {
577 LiteralValue::Array(a) => {
578 assert_eq!(a.len(), 2);
579 assert_eq!(a[0][0], LiteralValue::Int(1));
580 assert_eq!(a[1][0], LiteralValue::Int(100));
581 }
582 other => panic!("expected array got {other:?}"),
583 }
584 }
585
586 #[test]
587 fn chooserows_out_of_range() {
588 let wb = TestWorkbook::new().with_function(Arc::new(ChooseRowsFn));
589 let wb = wb.with_cell_a1("Sheet1", "A1", LiteralValue::Int(1));
590 let ctx = wb.interpreter();
591 let arr = range("A1:A1", 1, 1, 1, 1);
592 let f = ctx.context.get_function("", "CHOOSEROWS").unwrap();
593 let two = lit(LiteralValue::Int(2));
594 let args = vec![
595 ArgumentHandle::new(&arr, &ctx),
596 ArgumentHandle::new(&two, &ctx),
597 ];
598 let v = f.dispatch(&args, &ctx.function_context(None)).unwrap();
599 match v {
600 LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Value),
601 other => panic!("expected #VALUE! got {other:?}"),
602 }
603 }
604}