1use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
2use crate::function::{FnCaps, Function};
3use crate::traits::{ArgumentHandle, FunctionContext};
4use formualizer_common::{ArgKind, ExcelError, ExcelErrorKind, LiteralValue};
5use formualizer_parse::parser::ReferenceType;
6
7fn number_strict_scalar() -> ArgSchema {
8 ArgSchema {
9 kinds: smallvec::smallvec![ArgKind::Number],
10 required: true,
11 by_ref: false,
12 shape: ShapeKind::Scalar,
13 coercion: CoercionPolicy::NumberStrict,
14 max: None,
15 repeating: None,
16 default: None,
17 }
18}
19
20fn arg_byref_array() -> Vec<ArgSchema> {
21 vec![
22 ArgSchema {
24 kinds: smallvec::smallvec![ArgKind::Any],
25 required: true,
26 by_ref: false,
27 shape: ShapeKind::Range,
28 coercion: CoercionPolicy::None,
29 max: None,
30 repeating: None,
31 default: None,
32 },
33 number_strict_scalar(),
34 ArgSchema {
36 kinds: smallvec::smallvec![ArgKind::Number],
37 required: false,
38 by_ref: false,
39 shape: ShapeKind::Scalar,
40 coercion: CoercionPolicy::NumberStrict,
41 max: None,
42 repeating: None,
43 default: None,
44 },
45 ]
46}
47
48fn arg_byref_reference() -> Vec<ArgSchema> {
49 vec![
50 ArgSchema {
51 kinds: smallvec::smallvec![ArgKind::Range],
52 required: true,
53 by_ref: true,
54 shape: ShapeKind::Range,
55 coercion: CoercionPolicy::None,
56 max: None,
57 repeating: None,
58 default: None,
59 },
60 number_strict_scalar(),
61 number_strict_scalar(),
62 ArgSchema {
63 kinds: smallvec::smallvec![ArgKind::Number],
65 required: false,
66 by_ref: false,
67 shape: ShapeKind::Scalar,
68 coercion: CoercionPolicy::NumberStrict,
69 max: None,
70 repeating: None,
71 default: None,
72 },
73 ArgSchema {
74 kinds: smallvec::smallvec![ArgKind::Number],
76 required: false,
77 by_ref: false,
78 shape: ShapeKind::Scalar,
79 coercion: CoercionPolicy::NumberStrict,
80 max: None,
81 repeating: None,
82 default: None,
83 },
84 ]
85}
86
87fn resolve_reference_bounds<'b>(
95 ctx: &dyn FunctionContext<'b>,
96 base: &ReferenceType,
97) -> Result<(Option<String>, u32, u32, u32, u32), ExcelError> {
98 match base {
99 ReferenceType::Range {
100 sheet,
101 start_row,
102 start_col,
103 end_row,
104 end_col,
105 ..
106 } => {
107 if let (Some(sr), Some(sc), Some(er), Some(ec)) =
108 (start_row, start_col, end_row, end_col)
109 {
110 return Ok((sheet.clone(), *sr, *sc, *er, *ec));
111 }
112 let rv = ctx.resolve_range_view(base, ctx.current_sheet())?;
113 if rv.is_empty() {
114 return Err(ExcelError::new(ExcelErrorKind::Ref));
115 }
116 Ok((
118 sheet.clone(),
119 rv.start_row() as u32 + 1,
120 rv.start_col() as u32 + 1,
121 rv.end_row() as u32 + 1,
122 rv.end_col() as u32 + 1,
123 ))
124 }
125 ReferenceType::Cell {
126 sheet, row, col, ..
127 } => Ok((sheet.clone(), *row, *col, *row, *col)),
128 _ => Err(ExcelError::new(ExcelErrorKind::Ref)),
129 }
130}
131
132#[derive(Debug)]
133pub struct IndexFn;
134
135impl Function for IndexFn {
197 fn caps(&self) -> FnCaps {
198 FnCaps::PURE | FnCaps::RETURNS_REFERENCE
199 }
200 fn name(&self) -> &'static str {
201 "INDEX"
202 }
203 fn min_args(&self) -> usize {
204 2
205 }
206 fn arg_schema(&self) -> &'static [ArgSchema] {
207 use once_cell::sync::Lazy;
208 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_array);
209 &SCHEMA
210 }
211
212 fn eval_reference<'a, 'b, 'c>(
213 &self,
214 args: &'c [ArgumentHandle<'a, 'b>],
215 ctx: &dyn FunctionContext<'b>,
216 ) -> Option<Result<ReferenceType, ExcelError>> {
217 if args.len() < 2 {
219 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
220 }
221 let base = match args[0].as_reference_or_eval() {
223 Ok(r) => r,
224 Err(_) => return None,
225 };
226 let position = match args[1].value() {
227 Ok(cv) => match cv.into_literal() {
228 LiteralValue::Number(n) => n as i64,
229 LiteralValue::Int(i) => i,
230 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
231 },
232 Err(e) => return Some(Err(e)),
233 };
234 let explicit_col = if args.len() >= 3 {
235 Some(match args[2].value() {
236 Ok(cv) => match cv.into_literal() {
237 LiteralValue::Number(n) => n as i64,
238 LiteralValue::Int(i) => i,
239 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
240 },
241 Err(e) => return Some(Err(e)),
242 })
243 } else {
244 None
245 };
246
247 let (sheet, sr, sc, er, ec) = match resolve_reference_bounds(ctx, &base) {
250 Ok(bounds) => bounds,
251 Err(e) => return Some(Err(e)),
252 };
253
254 let (row, col) = match explicit_col {
255 Some(col) => (position, col),
256 None if sr == er => {
257 (1, position)
259 }
260 None => {
261 (position, 1)
264 }
265 };
266
267 if row < 0 || col < 0 {
271 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
272 }
273 let range_ref = |sheet, sr, sc, er, ec| ReferenceType::Range {
274 sheet,
275 start_row: Some(sr),
276 start_col: Some(sc),
277 end_row: Some(er),
278 end_col: Some(ec),
279 start_row_abs: false,
280 start_col_abs: false,
281 end_row_abs: false,
282 end_col_abs: false,
283 };
284 if col == 0 {
285 if row == 0 {
286 return Some(Ok(range_ref(sheet, sr, sc, er, ec)));
288 }
289 let r = sr + (row as u32) - 1;
292 if r > er {
293 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
294 }
295 return Some(Ok(if sc == ec {
296 ReferenceType::cell(sheet, r, sc)
297 } else {
298 range_ref(sheet, r, sc, r, ec)
299 }));
300 }
301 if row == 0 {
302 let c = sc + (col as u32) - 1;
305 if c > ec {
306 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
307 }
308 return Some(Ok(if sr == er {
309 ReferenceType::cell(sheet, sr, c)
310 } else {
311 range_ref(sheet, sr, c, er, c)
312 }));
313 }
314 let r = sr + (row as u32) - 1;
315 let c = sc + (col as u32) - 1;
316 if r > er || c > ec {
317 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
318 }
319
320 Some(Ok(ReferenceType::cell(sheet, r, c)))
321 }
322
323 fn eval<'a, 'b, 'c>(
324 &self,
325 args: &'c [ArgumentHandle<'a, 'b>],
326 ctx: &dyn FunctionContext<'b>,
327 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
328 if let Some(result) = self.eval_reference(args, ctx) {
330 match result {
331 Ok(r) => {
332 let current_sheet = ctx.current_sheet();
334 match ctx.resolve_range_view(&r, current_sheet) {
335 Ok(rv) => {
336 let (rows, cols) = rv.dims();
337 if rows == 1 && cols == 1 {
338 Ok(crate::traits::CalcValue::Scalar(
339 rv.as_1x1().unwrap_or(LiteralValue::Empty),
340 ))
341 } else {
342 Ok(crate::traits::CalcValue::Range(rv))
343 }
344 }
345 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
346 }
347 }
348 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
349 }
350 } else {
351 if args.len() < 2 {
353 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
354 ExcelError::new(ExcelErrorKind::Value),
355 )));
356 }
357 let v = args[0].value()?.into_literal();
358 let table: Vec<Vec<LiteralValue>> = match v {
359 LiteralValue::Array(rows) => rows,
360 other => vec![vec![other]],
361 };
362 let index = match args[1].value()?.into_literal() {
363 LiteralValue::Number(n) => n as i64,
364 LiteralValue::Int(i) => i,
365 _ => {
366 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
367 ExcelError::new(ExcelErrorKind::Value),
368 )));
369 }
370 };
371
372 let explicit_col = if args.len() >= 3 {
374 Some(match args[2].value()?.into_literal() {
375 LiteralValue::Number(n) => n as i64,
376 LiteralValue::Int(i) => i,
377 _ => {
378 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
379 ExcelError::new(ExcelErrorKind::Value),
380 )));
381 }
382 })
383 } else {
384 None
385 };
386
387 let nrows = table.len();
388 let ncols = table.iter().map(|r| r.len()).max().unwrap_or(0);
389 let single_row = nrows == 1;
390
391 let (row, col) = match explicit_col {
395 Some(c) => (index, c),
396 None if single_row => (1, index),
397 None => (index, 1),
398 };
399
400 let ref_err = || {
404 crate::traits::CalcValue::Scalar(LiteralValue::Error(ExcelError::new(
405 ExcelErrorKind::Ref,
406 )))
407 };
408 if row < 0 || col < 0 {
409 return Ok(ref_err());
410 }
411
412 let as_range = |rows: Vec<Vec<LiteralValue>>| {
417 crate::traits::CalcValue::Range(
418 crate::engine::range_view::RangeView::from_owned_rows(rows, ctx.date_system()),
419 )
420 };
421
422 if col == 0 {
423 if row == 0 {
424 return Ok(as_range(table));
426 }
427 if row as usize > nrows {
429 return Ok(ref_err());
430 }
431 let r = &table[row as usize - 1];
432 if ncols == 1 {
433 return Ok(crate::traits::CalcValue::Scalar(
434 r.first().cloned().unwrap_or(LiteralValue::Empty),
435 ));
436 }
437 return Ok(as_range(vec![r.clone()]));
438 }
439 if row == 0 {
440 if col as usize > ncols {
442 return Ok(ref_err());
443 }
444 let cidx = col as usize - 1;
445 if single_row {
446 return Ok(crate::traits::CalcValue::Scalar(
447 table[0].get(cidx).cloned().unwrap_or(LiteralValue::Empty),
448 ));
449 }
450 let column: Vec<Vec<LiteralValue>> = table
451 .iter()
452 .map(|r| vec![r.get(cidx).cloned().unwrap_or(LiteralValue::Empty)])
453 .collect();
454 return Ok(as_range(column));
455 }
456
457 if row as usize > nrows || col as usize > ncols {
459 return Ok(ref_err());
460 }
461 let val = table
462 .get(row as usize - 1)
463 .and_then(|r| r.get(col as usize - 1))
464 .cloned()
465 .unwrap_or_else(|| LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref)));
466 Ok(crate::traits::CalcValue::Scalar(val))
467 }
468 }
469}
470
471#[derive(Debug)]
472pub struct OffsetFn;
473
474impl Function for OffsetFn {
531 fn caps(&self) -> FnCaps {
532 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE | FnCaps::DYNAMIC_DEPENDENCY
534 }
535 fn name(&self) -> &'static str {
536 "OFFSET"
537 }
538 fn min_args(&self) -> usize {
539 3
540 }
541 fn arg_schema(&self) -> &'static [ArgSchema] {
542 use once_cell::sync::Lazy;
543 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_byref_reference);
544 &SCHEMA
545 }
546
547 fn eval_reference<'a, 'b, 'c>(
548 &self,
549 args: &'c [ArgumentHandle<'a, 'b>],
550 ctx: &dyn FunctionContext<'b>,
551 ) -> Option<Result<ReferenceType, ExcelError>> {
552 if args.len() < 3 {
553 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
554 }
555 let base = match args[0].as_reference_or_eval() {
556 Ok(r) => r,
557 Err(e) => return Some(Err(e)),
558 };
559 let dr = match args[1].value() {
560 Ok(cv) => match cv.into_literal() {
561 LiteralValue::Number(n) => n as i64,
562 LiteralValue::Int(i) => i,
563 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
564 },
565 Err(e) => return Some(Err(e)),
566 };
567 let dc = match args[2].value() {
568 Ok(cv) => match cv.into_literal() {
569 LiteralValue::Number(n) => n as i64,
570 LiteralValue::Int(i) => i,
571 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
572 },
573 Err(e) => return Some(Err(e)),
574 };
575
576 let (sheet, sr, sc, er, ec) = match resolve_reference_bounds(ctx, &base) {
579 Ok(bounds) => bounds,
580 Err(e) => return Some(Err(e)),
581 };
582
583 let nsr = (sr as i64) + dr;
584 let nsc = (sc as i64) + dc;
585 let height = if args.len() >= 4 {
586 match args[3].value() {
587 Ok(cv) => match cv.into_literal() {
588 LiteralValue::Number(n) => n as i64,
589 LiteralValue::Int(i) => i,
590 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
591 },
592 Err(e) => return Some(Err(e)),
593 }
594 } else {
595 (er as i64) - (sr as i64) + 1
596 };
597 let width = if args.len() >= 5 {
598 match args[4].value() {
599 Ok(cv) => match cv.into_literal() {
600 LiteralValue::Number(n) => n as i64,
601 LiteralValue::Int(i) => i,
602 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
603 },
604 Err(e) => return Some(Err(e)),
605 }
606 } else {
607 (ec as i64) - (sc as i64) + 1
608 };
609
610 if nsr <= 0 || nsc <= 0 || height <= 0 || width <= 0 {
611 return Some(Err(ExcelError::new(ExcelErrorKind::Ref)));
612 }
613 let ner = nsr + height - 1;
614 let nec = nsc + width - 1;
615
616 if height == 1 && width == 1 {
617 Some(Ok(ReferenceType::cell(sheet, nsr as u32, nsc as u32)))
618 } else {
619 Some(Ok(ReferenceType::range(
620 sheet,
621 Some(nsr as u32),
622 Some(nsc as u32),
623 Some(ner as u32),
624 Some(nec as u32),
625 )))
626 }
627 }
628
629 fn eval<'a, 'b, 'c>(
630 &self,
631 args: &'c [ArgumentHandle<'a, 'b>],
632 ctx: &dyn FunctionContext<'b>,
633 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
634 if let Some(Ok(r)) = self.eval_reference(args, ctx) {
635 let current_sheet = ctx.current_sheet();
636 match ctx.resolve_range_view(&r, current_sheet) {
637 Ok(rv) => {
638 let (rows, cols) = rv.dims();
639 if rows == 1 && cols == 1 {
640 Ok(crate::traits::CalcValue::Scalar(
641 rv.as_1x1().unwrap_or(LiteralValue::Empty),
642 ))
643 } else {
644 Ok(crate::traits::CalcValue::Range(rv))
645 }
646 }
647 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
648 }
649 } else {
650 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
651 ExcelError::new(ExcelErrorKind::Ref),
652 )))
653 }
654 }
655}
656
657fn arg_indirect() -> Vec<ArgSchema> {
658 vec![
659 ArgSchema {
660 kinds: smallvec::smallvec![ArgKind::Text],
661 required: true,
662 by_ref: false,
663 shape: ShapeKind::Scalar,
664 coercion: CoercionPolicy::None,
665 max: None,
666 repeating: None,
667 default: None,
668 },
669 ArgSchema {
670 kinds: smallvec::smallvec![ArgKind::Logical, ArgKind::Number],
671 required: false,
672 by_ref: false,
673 shape: ShapeKind::Scalar,
674 coercion: CoercionPolicy::Logical,
675 max: None,
676 repeating: None,
677 default: Some(LiteralValue::Boolean(true)),
678 },
679 ]
680}
681
682#[derive(Debug)]
683pub struct IndirectFn;
684
685impl Function for IndirectFn {
737 fn caps(&self) -> FnCaps {
738 FnCaps::PURE | FnCaps::RETURNS_REFERENCE | FnCaps::VOLATILE | FnCaps::DYNAMIC_DEPENDENCY
739 }
740 fn name(&self) -> &'static str {
741 "INDIRECT"
742 }
743 fn min_args(&self) -> usize {
744 1
745 }
746 fn arg_schema(&self) -> &'static [ArgSchema] {
747 use once_cell::sync::Lazy;
748 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(arg_indirect);
749 &SCHEMA
750 }
751
752 fn eval_reference<'a, 'b, 'c>(
753 &self,
754 args: &'c [ArgumentHandle<'a, 'b>],
755 _ctx: &dyn FunctionContext<'b>,
756 ) -> Option<Result<ReferenceType, ExcelError>> {
757 if args.is_empty() {
758 return Some(Err(ExcelError::new(ExcelErrorKind::Value)));
759 }
760
761 let ref_text = match args[0].value() {
762 Ok(cv) => match cv.into_literal() {
763 LiteralValue::Text(s) => s.to_string(),
764 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
765 },
766 Err(e) => return Some(Err(e)),
767 };
768
769 let a1_style = if args.len() >= 2 {
770 match args[1].value() {
771 Ok(cv) => match cv.into_literal() {
772 LiteralValue::Boolean(b) => b,
773 LiteralValue::Int(i) => i != 0,
774 LiteralValue::Number(n) => n != 0.0,
775 _ => return Some(Err(ExcelError::new(ExcelErrorKind::Value))),
776 },
777 Err(e) => return Some(Err(e)),
778 }
779 } else {
780 true
781 };
782
783 if !a1_style {
784 return match formualizer_parse::parser::ReferenceType::from_string(&ref_text) {
789 Ok(ReferenceType::NamedRange(name)) => Some(Ok(ReferenceType::NamedRange(name))),
790 Ok(ReferenceType::Table(tref)) => Some(Ok(ReferenceType::Table(tref))),
791 _ => Some(Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
792 "INDIRECT with R1C1 style (second argument FALSE) is not yet supported",
793 ))),
794 };
795 }
796
797 let parsed = formualizer_parse::parser::ReferenceType::parse_sheet_ref(&ref_text);
798
799 match parsed {
800 Ok(formualizer_common::SheetRef::Cell(cell)) => {
801 let sheet = match cell.sheet {
802 formualizer_common::SheetLocator::Current => None,
803 formualizer_common::SheetLocator::Name(name) => Some(name.to_string()),
804 formualizer_common::SheetLocator::Id(_) => None,
805 };
806 Some(Ok(ReferenceType::Cell {
807 sheet,
808 row: cell.coord.row() + 1,
809 col: cell.coord.col() + 1,
810 row_abs: cell.coord.row_abs(),
811 col_abs: cell.coord.col_abs(),
812 }))
813 }
814 Ok(formualizer_common::SheetRef::Range(range)) => {
815 let sheet = match range.sheet {
816 formualizer_common::SheetLocator::Current => None,
817 formualizer_common::SheetLocator::Name(name) => Some(name.to_string()),
818 formualizer_common::SheetLocator::Id(_) => None,
819 };
820 Some(Ok(ReferenceType::Range {
821 sheet,
822 start_row: range.start_row.map(|b| b.index + 1),
823 start_col: range.start_col.map(|b| b.index + 1),
824 end_row: range.end_row.map(|b| b.index + 1),
825 end_col: range.end_col.map(|b| b.index + 1),
826 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
827 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
828 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
829 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
830 }))
831 }
832 Err(_) => match formualizer_parse::parser::ReferenceType::from_string(&ref_text) {
833 Ok(ReferenceType::NamedRange(name)) => Some(Ok(ReferenceType::NamedRange(name))),
834 Ok(ReferenceType::Table(tref)) => Some(Ok(ReferenceType::Table(tref))),
835 _ => Some(Err(ExcelError::new(ExcelErrorKind::Ref))),
836 },
837 }
838 }
839
840 fn eval<'a, 'b, 'c>(
841 &self,
842 args: &'c [ArgumentHandle<'a, 'b>],
843 ctx: &dyn FunctionContext<'b>,
844 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
845 match self.eval_reference(args, ctx) {
846 Some(Ok(r)) => {
847 let current_sheet = ctx.current_sheet();
848 match ctx.resolve_range_view(&r, current_sheet) {
849 Ok(rv) => {
850 let (rows, cols) = rv.dims();
851 if rows == 1 && cols == 1 {
852 Ok(crate::traits::CalcValue::Scalar(
853 rv.as_1x1().unwrap_or(LiteralValue::Empty),
854 ))
855 } else {
856 Ok(crate::traits::CalcValue::Range(rv))
857 }
858 }
859 Err(e) => {
860 let mapped = if e.kind == ExcelErrorKind::Name {
861 ExcelError::new(ExcelErrorKind::Ref)
862 } else {
863 e
864 };
865 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
866 mapped,
867 )))
868 }
869 }
870 }
871 Some(Err(e)) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
872 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
873 ExcelError::new(ExcelErrorKind::Ref),
874 ))),
875 }
876 }
877}
878
879pub fn register_builtins() {
880 crate::function_registry::register_function(std::sync::Arc::new(IndexFn));
881 crate::function_registry::register_function(std::sync::Arc::new(OffsetFn));
882 crate::function_registry::register_function(std::sync::Arc::new(IndirectFn));
883}
884
885#[cfg(test)]
886mod tests {
887 use super::*;
888 use crate::builtins::lookup::MatchFn;
889 use crate::test_workbook::TestWorkbook;
890 use crate::traits::ArgumentHandle;
891 use formualizer_common::error::{ExcelError, ExcelErrorKind};
892 use formualizer_parse::parser::{ASTNode, ASTNodeType, Parser};
893
894 fn interp(wb: &TestWorkbook) -> crate::interpreter::Interpreter<'_> {
895 wb.interpreter()
896 }
897
898 fn evaluate_formula(formula: &str, wb: &TestWorkbook) -> Result<LiteralValue, ExcelError> {
899 let mut parser = Parser::new(formula).unwrap();
900 let ast = parser
901 .parse()
902 .map_err(|e| ExcelError::new(ExcelErrorKind::Error).with_message(e.message.clone()))?;
903 Ok(interp(wb).evaluate_ast(&ast)?.into_literal())
904 }
905
906 #[test]
907 fn index_returns_reference_and_materializes_in_value_context() {
908 let wb = TestWorkbook::new()
909 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(42))
910 .with_function(std::sync::Arc::new(IndexFn));
911 let ctx = interp(&wb);
912
913 let array_ref = ASTNode::new(
915 ASTNodeType::Reference {
916 original: "A1:C3".into(),
917 reference: ReferenceType::Range {
918 sheet: None,
919 start_row: Some(1),
920 start_col: Some(1),
921 end_row: Some(3),
922 end_col: Some(3),
923 start_row_abs: false,
924 start_col_abs: false,
925 end_row_abs: false,
926 end_col_abs: false,
927 },
928 },
929 None,
930 );
931 let row = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
932 let col = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
933 let call = ASTNode::new(
934 ASTNodeType::Function {
935 name: "INDEX".into(),
936 args: vec![array_ref.clone(), row.clone(), col.clone()],
937 },
938 None,
939 );
940
941 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
943 match r {
944 ReferenceType::Cell { row, col, .. } => {
945 assert_eq!((row, col), (2, 2));
946 }
947 _ => panic!(),
948 }
949
950 let args = vec![
952 ArgumentHandle::new(&array_ref, &ctx),
953 ArgumentHandle::new(&row, &ctx),
954 ArgumentHandle::new(&col, &ctx),
955 ];
956 let f = ctx.context.get_function("", "INDEX").unwrap();
957 let v = f
958 .dispatch(&args, &ctx.function_context(None))
959 .unwrap()
960 .into_literal();
961 assert_eq!(v, LiteralValue::Number(42.0));
962 }
963
964 #[test]
965 fn index_single_row_reference_uses_omitted_col_as_horizontal_position() {
966 let wb = TestWorkbook::new()
967 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
968 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
969 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
970 .with_function(std::sync::Arc::new(IndexFn));
971 let ctx = interp(&wb);
972
973 let array_ref = ASTNode::new(
974 ASTNodeType::Reference {
975 original: "A1:C1".into(),
976 reference: ReferenceType::Range {
977 sheet: None,
978 start_row: Some(1),
979 start_col: Some(1),
980 end_row: Some(1),
981 end_col: Some(3),
982 start_row_abs: false,
983 start_col_abs: false,
984 end_row_abs: false,
985 end_col_abs: false,
986 },
987 },
988 None,
989 );
990 let index = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
991 let call = ASTNode::new(
992 ASTNodeType::Function {
993 name: "INDEX".into(),
994 args: vec![array_ref.clone(), index.clone()],
995 },
996 None,
997 );
998
999 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
1000 match r {
1001 ReferenceType::Cell { row, col, .. } => assert_eq!((row, col), (1, 2)),
1002 _ => panic!(),
1003 }
1004
1005 let args = vec![
1006 ArgumentHandle::new(&array_ref, &ctx),
1007 ArgumentHandle::new(&index, &ctx),
1008 ];
1009 let f = ctx.context.get_function("", "INDEX").unwrap();
1010 let v = f
1011 .dispatch(&args, &ctx.function_context(None))
1012 .unwrap()
1013 .into_literal();
1014 assert_eq!(v, LiteralValue::Number(20.0));
1015 }
1016
1017 #[test]
1018 fn index_single_column_reference_keeps_omitted_col_as_vertical_position() {
1019 let wb = TestWorkbook::new()
1020 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1021 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
1022 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
1023 .with_function(std::sync::Arc::new(IndexFn));
1024 let ctx = interp(&wb);
1025
1026 let array_ref = ASTNode::new(
1027 ASTNodeType::Reference {
1028 original: "A1:A3".into(),
1029 reference: ReferenceType::Range {
1030 sheet: None,
1031 start_row: Some(1),
1032 start_col: Some(1),
1033 end_row: Some(3),
1034 end_col: Some(1),
1035 start_row_abs: false,
1036 start_col_abs: false,
1037 end_row_abs: false,
1038 end_col_abs: false,
1039 },
1040 },
1041 None,
1042 );
1043 let index = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(2)), None);
1044 let args = vec![
1045 ArgumentHandle::new(&array_ref, &ctx),
1046 ArgumentHandle::new(&index, &ctx),
1047 ];
1048 let f = ctx.context.get_function("", "INDEX").unwrap();
1049 let v = f
1050 .dispatch(&args, &ctx.function_context(None))
1051 .unwrap()
1052 .into_literal();
1053 assert_eq!(v, LiteralValue::Number(20.0));
1054 }
1055
1056 #[test]
1057 fn index_rectangular_reference_defaults_omitted_col_to_first_column() {
1058 let wb = TestWorkbook::new()
1059 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1060 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
1061 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200))
1062 .with_function(std::sync::Arc::new(IndexFn));
1063
1064 let value = evaluate_formula("=INDEX(A1:B2,2)", &wb).unwrap();
1065 assert_eq!(value, LiteralValue::Number(20.0));
1066 }
1067
1068 #[test]
1069 fn index_single_row_reference_match_position_materializes_value() {
1070 let wb = TestWorkbook::new()
1071 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1072 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
1073 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
1074 .with_function(std::sync::Arc::new(IndexFn))
1075 .with_function(std::sync::Arc::new(MatchFn));
1076
1077 let value = evaluate_formula("=INDEX(A1:C1,MATCH(20,A1:C1,0))", &wb).unwrap();
1078 assert_eq!(value, LiteralValue::Number(20.0));
1079 }
1080
1081 #[test]
1082 fn index_single_row_reference_out_of_bounds_is_ref() {
1083 let wb = TestWorkbook::new()
1084 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1085 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
1086 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
1087 .with_function(std::sync::Arc::new(IndexFn));
1088
1089 let value = evaluate_formula("=INDEX(A1:C1,4)", &wb).unwrap();
1090 match value {
1091 LiteralValue::Error(err) => assert_eq!(err.kind, ExcelErrorKind::Ref),
1092 other => panic!("expected #REF!, got {other:?}"),
1093 }
1094 }
1095
1096 #[test]
1097 fn index_zero_column_degenerates_to_cell_in_single_column_range() {
1098 let wb = TestWorkbook::new()
1100 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(10))
1101 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(20))
1102 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(30))
1103 .with_function(std::sync::Arc::new(IndexFn));
1104
1105 let value = evaluate_formula("=INDEX(B1:B5,2,0)", &wb).unwrap();
1106 assert_eq!(value, LiteralValue::Number(20.0));
1107 }
1108
1109 #[test]
1110 fn index_zero_row_degenerates_to_cell_in_single_row_range() {
1111 let wb = TestWorkbook::new()
1113 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1114 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(20))
1115 .with_cell_a1("Sheet1", "C1", LiteralValue::Int(30))
1116 .with_function(std::sync::Arc::new(IndexFn));
1117
1118 let value = evaluate_formula("=INDEX(A1:C1,0,2)", &wb).unwrap();
1119 assert_eq!(value, LiteralValue::Number(20.0));
1120 }
1121
1122 #[test]
1123 fn index_zero_column_returns_entire_row_range() {
1124 let wb = TestWorkbook::new()
1126 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(1))
1127 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(2))
1128 .with_cell_a1("Sheet1", "C2", LiteralValue::Int(3))
1129 .with_function(std::sync::Arc::new(IndexFn))
1130 .with_function(std::sync::Arc::new(crate::builtins::math::aggregate::SumFn));
1131
1132 let value = evaluate_formula("=SUM(INDEX(A1:C3,2,0))", &wb).unwrap();
1133 assert_eq!(value, LiteralValue::Number(6.0));
1134 }
1135
1136 #[test]
1137 fn index_zero_row_returns_entire_column_range() {
1138 let wb = TestWorkbook::new()
1140 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(4))
1141 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(5))
1142 .with_cell_a1("Sheet1", "B3", LiteralValue::Int(6))
1143 .with_function(std::sync::Arc::new(IndexFn))
1144 .with_function(std::sync::Arc::new(crate::builtins::math::aggregate::SumFn));
1145
1146 let value = evaluate_formula("=SUM(INDEX(A1:C3,0,2))", &wb).unwrap();
1147 assert_eq!(value, LiteralValue::Number(15.0));
1148 }
1149
1150 #[test]
1151 fn index_negative_index_is_ref() {
1152 let wb = TestWorkbook::new()
1153 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
1154 .with_function(std::sync::Arc::new(IndexFn));
1155
1156 let value = evaluate_formula("=INDEX(A1:C3,-1,2)", &wb).unwrap();
1157 match value {
1158 LiteralValue::Error(err) => assert_eq!(err.kind, ExcelErrorKind::Ref),
1159 other => panic!("expected #REF!, got {other:?}"),
1160 }
1161 }
1162
1163 fn as_number(v: &LiteralValue) -> f64 {
1164 match v {
1165 LiteralValue::Number(n) => *n,
1166 LiteralValue::Int(i) => *i as f64,
1167 other => panic!("expected number, got {other:?}"),
1168 }
1169 }
1170
1171 #[test]
1172 fn index_array_constant_zero_column_returns_entire_row() {
1173 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IndexFn));
1175
1176 let raw = evaluate_formula("=INDEX({1,2,3},0)", &wb).unwrap();
1177 let LiteralValue::Array(rows) = raw else {
1178 panic!("expected a 1x3 array, got {raw:?}");
1179 };
1180 assert_eq!(rows.len(), 1);
1181 let flat: Vec<f64> = rows[0].iter().map(as_number).collect();
1182 assert_eq!(flat, vec![1.0, 2.0, 3.0]);
1183 }
1184
1185 #[test]
1186 fn index_array_constant_zero_row_returns_entire_column() {
1187 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IndexFn));
1189
1190 let raw = evaluate_formula("=INDEX({1,2;3,4},0,2)", &wb).unwrap();
1191 let LiteralValue::Array(rows) = raw else {
1192 panic!("expected a 2x1 array, got {raw:?}");
1193 };
1194 let flat: Vec<f64> = rows.iter().map(|r| as_number(&r[0])).collect();
1195 assert_eq!(flat, vec![2.0, 4.0]);
1196 }
1197
1198 #[test]
1199 fn index_array_constant_zero_zero_returns_whole_array() {
1200 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IndexFn));
1201
1202 let raw = evaluate_formula("=INDEX({1,2;3,4},0,0)", &wb).unwrap();
1203 let LiteralValue::Array(rows) = raw else {
1204 panic!("expected the whole 2x2 array, got {raw:?}");
1205 };
1206 let flat: Vec<f64> = rows.iter().flatten().map(as_number).collect();
1207 assert_eq!(flat, vec![1.0, 2.0, 3.0, 4.0]);
1208 }
1209
1210 #[test]
1211 fn index_array_constant_row_zero_for_single_row_degenerates_to_scalar() {
1212 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IndexFn));
1214
1215 let value = evaluate_formula("=INDEX({1,2,3},0,2)", &wb).unwrap();
1216 assert_eq!(as_number(&value), 2.0);
1217 }
1218
1219 #[test]
1220 fn index_array_constant_negative_is_ref() {
1221 let wb = TestWorkbook::new().with_function(std::sync::Arc::new(IndexFn));
1222
1223 let value = evaluate_formula("=INDEX({1,2,3},-1)", &wb).unwrap();
1224 match value {
1225 LiteralValue::Error(err) => assert_eq!(err.kind, ExcelErrorKind::Ref),
1226 other => panic!("expected #REF!, got {other:?}"),
1227 }
1228 }
1229
1230 #[test]
1231 fn offset_returns_reference_and_materializes() {
1232 let wb = TestWorkbook::new()
1233 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1))
1234 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(5))
1235 .with_function(std::sync::Arc::new(OffsetFn));
1236 let ctx = interp(&wb);
1237
1238 let base = ASTNode::new(
1239 ASTNodeType::Reference {
1240 original: "A1".into(),
1241 reference: ReferenceType::Cell {
1242 sheet: None,
1243 row: 1,
1244 col: 1,
1245 row_abs: false,
1246 col_abs: false,
1247 },
1248 },
1249 None,
1250 );
1251 let dr = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
1252 let dc = ASTNode::new(ASTNodeType::Literal(LiteralValue::Int(1)), None);
1253 let call = ASTNode::new(
1254 ASTNodeType::Function {
1255 name: "OFFSET".into(),
1256 args: vec![base.clone(), dr.clone(), dc.clone()],
1257 },
1258 None,
1259 );
1260
1261 let r = ctx.evaluate_ast_as_reference(&call).expect("ref ok");
1262 match r {
1263 ReferenceType::Cell { row, col, .. } => assert_eq!((row, col), (2, 2)),
1264 _ => panic!(),
1265 }
1266
1267 let args = vec![
1268 ArgumentHandle::new(&base, &ctx),
1269 ArgumentHandle::new(&dr, &ctx),
1270 ArgumentHandle::new(&dc, &ctx),
1271 ];
1272 let f = ctx.context.get_function("", "OFFSET").unwrap();
1273 let v = f
1274 .dispatch(&args, &ctx.function_context(None))
1275 .unwrap()
1276 .into_literal();
1277 assert_eq!(v, LiteralValue::Number(5.0));
1278 }
1279}