1use super::lookup_utils::{cmp_for_lookup, find_exact_index, is_sorted_ascending};
13use crate::args::{ArgSchema, CoercionPolicy, ShapeKind};
14use crate::function::Function;
15use crate::traits::{ArgumentHandle, FunctionContext};
16use formualizer_common::ArgKind;
17use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
18use formualizer_macros::func_caps;
19use formualizer_parse::parser::ReferenceType;
20
21fn binary_search_match(slice: &[LiteralValue], needle: &LiteralValue, mode: i32) -> Option<usize> {
22 if mode == 0 || slice.is_empty() {
23 return None;
24 }
25 if mode == 1 {
27 let mut lo = 0usize;
29 let mut hi = slice.len();
30 while lo < hi {
31 let mid = (lo + hi) / 2;
32 match cmp_for_lookup(&slice[mid], needle) {
33 Some(c) => {
34 if c > 0 {
35 hi = mid;
36 } else {
37 lo = mid + 1;
38 }
39 }
40 None => {
41 hi = mid;
42 }
43 }
44 }
45 if lo == 0 { None } else { Some(lo - 1) }
46 } else {
47 let mut best: Option<usize> = None;
49 for (i, v) in slice.iter().enumerate() {
50 if let Some(c) = cmp_for_lookup(v, needle) {
51 if c == 0 {
52 return Some(i);
53 }
54 if c >= 0 && best.is_none_or(|b| i < b) {
55 best = Some(i);
56 }
57 }
58 }
59 best
60 }
61}
62
63#[derive(Debug)]
64pub struct MatchFn;
65impl Function for MatchFn {
66 fn name(&self) -> &'static str {
67 "MATCH"
68 }
69 fn min_args(&self) -> usize {
70 2
71 }
72 func_caps!(PURE, LOOKUP);
73 fn arg_schema(&self) -> &'static [ArgSchema] {
74 use once_cell::sync::Lazy;
75 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
76 vec![
77 ArgSchema {
79 kinds: smallvec::smallvec![ArgKind::Any],
80 required: true,
81 by_ref: false,
82 shape: ShapeKind::Scalar,
83 coercion: CoercionPolicy::None,
84 max: None,
85 repeating: None,
86 default: None,
87 },
88 ArgSchema {
90 kinds: smallvec::smallvec![ArgKind::Any],
91 required: true,
92 by_ref: false,
93 shape: ShapeKind::Range,
94 coercion: CoercionPolicy::None,
95 max: None,
96 repeating: None,
97 default: None,
98 },
99 ArgSchema {
101 kinds: smallvec::smallvec![ArgKind::Number],
102 required: false,
103 by_ref: false,
104 shape: ShapeKind::Scalar,
105 coercion: CoercionPolicy::NumberLenientText,
106 max: None,
107 repeating: None,
108 default: Some(LiteralValue::Number(1.0)),
109 },
110 ]
111 });
112 &SCHEMA
113 }
114 fn eval<'a, 'b, 'c>(
115 &self,
116 args: &'c [ArgumentHandle<'a, 'b>],
117 ctx: &dyn FunctionContext<'b>,
118 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
119 if args.len() < 2 {
120 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
121 ExcelError::new(ExcelErrorKind::Na),
122 )));
123 }
124 let cv = args[0].value()?;
125 let lookup_value = cv.into_literal();
126 if let LiteralValue::Error(e) = lookup_value {
127 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
128 }
129 let mut match_type = 1.0; if args.len() >= 3 {
131 let mt_val = args[2].value()?.into_literal();
132 if let LiteralValue::Error(e) = mt_val {
133 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
134 }
135 match mt_val {
136 LiteralValue::Number(n) => match_type = n,
137 LiteralValue::Int(i) => match_type = i as f64,
138 LiteralValue::Text(s) => {
139 if let Ok(n) = s.parse::<f64>() {
140 match_type = n;
141 }
142 }
143 _ => {}
144 }
145 }
146 let mt = if match_type > 0.0 {
147 1
148 } else if match_type < 0.0 {
149 -1
150 } else {
151 0
152 };
153 let arr_ref = args[1].as_reference_or_eval().ok();
154 if let Some(r) = arr_ref {
155 let current_sheet = ctx.current_sheet();
156 match ctx.resolve_range_view(&r, current_sheet) {
157 Ok(rv) => {
158 if mt == 0 {
159 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
160 if let Some(idx) = super::lookup_utils::find_exact_index_in_view(
161 &rv,
162 &lookup_value,
163 wildcard_mode,
164 )? {
165 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
166 (idx + 1) as i64,
167 )));
168 }
169 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
170 ExcelError::new(ExcelErrorKind::Na),
171 )));
172 }
173
174 let mut values: Vec<LiteralValue> = Vec::new();
176 if let Err(e) = rv.for_each_cell(&mut |v| {
177 values.push(v.clone());
178 Ok(())
179 }) {
180 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
181 }
182
183 let is_sorted = if mt == 1 {
185 is_sorted_ascending(&values)
186 } else if mt == -1 {
187 values
188 .windows(2)
189 .all(|w| cmp_for_lookup(&w[0], &w[1]).is_some_and(|c| c >= 0))
190 } else {
191 true
192 };
193 if !is_sorted {
194 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
195 ExcelError::new(ExcelErrorKind::Na),
196 )));
197 }
198 let idx = if values.len() < 8 {
199 let mut best: Option<(usize, &LiteralValue)> = None;
201 for (i, v) in values.iter().enumerate() {
202 if let Some(c) = cmp_for_lookup(v, &lookup_value) {
203 if mt == 1 {
205 if (c == 0 || c == -1)
207 && (best.is_none() || i > best.unwrap().0)
208 {
209 best = Some((i, v));
210 }
211 } else {
212 if (c == 0 || c == 1) && (best.is_none() || i > best.unwrap().0)
214 {
215 best = Some((i, v));
216 }
217 }
218 }
219 }
220 best.map(|(i, _)| i)
221 } else {
222 binary_search_match(&values, &lookup_value, mt)
223 };
224 match idx {
225 Some(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
226 (i + 1) as i64,
227 ))),
228 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
229 ExcelError::new(ExcelErrorKind::Na),
230 ))),
231 }
232 }
233 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
234 }
235 } else {
236 let v = args[1].value()?.into_literal();
238 let values: Vec<LiteralValue> = match v {
239 LiteralValue::Array(rows) => {
240 if rows.len() == 1 {
242 rows.into_iter().next().unwrap_or_default()
244 } else if rows.iter().all(|r| r.len() == 1) {
245 rows.into_iter()
247 .filter_map(|r| r.into_iter().next())
248 .collect()
249 } else {
250 rows.into_iter().flatten().collect()
252 }
253 }
254 other => vec![other],
255 };
256 let idx = if mt == 0 {
257 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
258 find_exact_index(&values, &lookup_value, wildcard_mode)
259 } else {
260 binary_search_match(&values, &lookup_value, mt)
261 };
262 match idx {
263 Some(i) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(
264 (i + 1) as i64,
265 ))),
266 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
267 ExcelError::new(ExcelErrorKind::Na),
268 ))),
269 }
270 }
271 }
272}
273
274#[derive(Debug)]
275pub struct VLookupFn;
276impl Function for VLookupFn {
277 fn name(&self) -> &'static str {
278 "VLOOKUP"
279 }
280 fn min_args(&self) -> usize {
281 3
282 }
283 func_caps!(PURE, LOOKUP);
284 fn arg_schema(&self) -> &'static [ArgSchema] {
285 use once_cell::sync::Lazy;
286 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
287 vec![
288 ArgSchema {
290 kinds: smallvec::smallvec![ArgKind::Any],
291 required: true,
292 by_ref: false,
293 shape: ShapeKind::Scalar,
294 coercion: CoercionPolicy::None,
295 max: None,
296 repeating: None,
297 default: None,
298 },
299 ArgSchema {
301 kinds: smallvec::smallvec![ArgKind::Any],
302 required: true,
303 by_ref: false,
304 shape: ShapeKind::Range,
305 coercion: CoercionPolicy::None,
306 max: None,
307 repeating: None,
308 default: None,
309 },
310 ArgSchema {
312 kinds: smallvec::smallvec![ArgKind::Number],
313 required: true,
314 by_ref: false,
315 shape: ShapeKind::Scalar,
316 coercion: CoercionPolicy::NumberStrict,
317 max: None,
318 repeating: None,
319 default: None,
320 },
321 ArgSchema {
323 kinds: smallvec::smallvec![ArgKind::Logical],
324 required: false,
325 by_ref: false,
326 shape: ShapeKind::Scalar,
327 coercion: CoercionPolicy::Logical,
328 max: None,
329 repeating: None,
330 default: Some(LiteralValue::Boolean(false)),
331 },
332 ]
333 });
334 &SCHEMA
335 }
336 fn eval<'a, 'b, 'c>(
337 &self,
338 args: &'c [ArgumentHandle<'a, 'b>],
339 ctx: &dyn FunctionContext<'b>,
340 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
341 if args.len() < 3 {
342 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
343 ExcelError::new(ExcelErrorKind::Na),
344 )));
345 }
346 let lookup_value = args[0].value()?.into_literal();
347
348 let table_ref_opt = args[1].as_reference_or_eval().ok();
350 let col_index = match args[2].value()?.into_literal() {
351 LiteralValue::Int(i) => i,
352 LiteralValue::Number(n) => n as i64,
353 _ => {
354 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
355 ExcelError::new(ExcelErrorKind::Value),
356 )));
357 }
358 };
359 if col_index < 1 {
360 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
361 ExcelError::new(ExcelErrorKind::Value),
362 )));
363 }
364 let approximate = if args.len() >= 4 {
365 match args[3].value()?.into_literal() {
366 LiteralValue::Boolean(b) => b,
367 _ => true,
368 }
369 } else {
370 false };
372 if let Some(table_ref) = table_ref_opt {
374 let (sheet, sr, sc, er, ec) = match &table_ref {
375 ReferenceType::Range {
376 sheet,
377 start_row: Some(sr),
378 start_col: Some(sc),
379 end_row: Some(er),
380 end_col: Some(ec),
381 ..
382 } => (sheet.clone(), *sr, *sc, *er, *ec),
383 _ => {
384 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
385 ExcelError::new(ExcelErrorKind::Ref),
386 )));
387 }
388 };
389 let current_sheet = ctx.current_sheet();
390 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
391 let width = ec - sc + 1;
392 if col_index as u32 > width {
393 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
394 ExcelError::new(ExcelErrorKind::Ref),
395 )));
396 }
397
398 let rv = ctx.resolve_range_view(&table_ref, sheet_name)?;
399 let rows = rv.dims().0;
400 let first_col_view = rv.sub_view(0, 0, rows, 1);
401 let row_idx_opt = if !approximate {
402 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
403 super::lookup_utils::find_exact_index_in_view(
404 &first_col_view,
405 &lookup_value,
406 wildcard_mode,
407 )?
408 } else {
409 let mut first_col: Vec<LiteralValue> = Vec::new();
411 first_col_view.for_each_row(&mut |row| {
412 first_col.push(row[0].clone());
413 Ok(())
414 })?;
415 if first_col.is_empty() {
416 None
417 } else {
418 binary_search_match(&first_col, &lookup_value, 1)
419 }
420 };
421
422 match row_idx_opt {
423 Some(i) => {
424 let target_col_idx = (col_index - 1) as usize;
425 let v = rv.get_cell(i, target_col_idx);
426 let v = match v {
430 LiteralValue::Empty => LiteralValue::Number(0.0),
431 other => other,
432 };
433 Ok(crate::traits::CalcValue::Scalar(v))
434 }
435 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
436 ExcelError::new(ExcelErrorKind::Na),
437 ))),
438 }
439 } else {
440 let v = args[1].value()?.into_literal();
442 let table: Vec<Vec<LiteralValue>> = match v {
443 LiteralValue::Array(rows) => rows,
444 other => vec![vec![other]],
445 };
446 if table.is_empty() {
447 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
448 ExcelError::new(ExcelErrorKind::Na),
449 )));
450 }
451 let width = table.first().map(|r| r.len()).unwrap_or(0);
452 if col_index as usize > width {
453 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
454 ExcelError::new(ExcelErrorKind::Ref),
455 )));
456 }
457
458 let first_col: Vec<LiteralValue> =
460 table.iter().filter_map(|r| r.first().cloned()).collect();
461 let row_idx_opt = if !approximate {
462 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
463 find_exact_index(&first_col, &lookup_value, wildcard_mode)
464 } else {
465 binary_search_match(&first_col, &lookup_value, 1)
466 };
467
468 match row_idx_opt {
469 Some(i) => {
470 let target_col_idx = (col_index - 1) as usize;
471 let val = table
472 .get(i)
473 .and_then(|r| r.get(target_col_idx))
474 .cloned()
475 .unwrap_or(LiteralValue::Empty);
476 let val = match val {
477 LiteralValue::Empty => LiteralValue::Number(0.0),
478 other => other,
479 };
480 Ok(crate::traits::CalcValue::Scalar(val))
481 }
482 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
483 ExcelError::new(ExcelErrorKind::Na),
484 ))),
485 }
486 }
487 }
488}
489
490#[derive(Debug)]
491pub struct HLookupFn;
492impl Function for HLookupFn {
493 fn name(&self) -> &'static str {
494 "HLOOKUP"
495 }
496 fn min_args(&self) -> usize {
497 3
498 }
499 func_caps!(PURE, LOOKUP);
500 fn arg_schema(&self) -> &'static [ArgSchema] {
501 use once_cell::sync::Lazy;
502 static SCHEMA: Lazy<Vec<ArgSchema>> = Lazy::new(|| {
503 vec![
504 ArgSchema {
506 kinds: smallvec::smallvec![ArgKind::Any],
507 required: true,
508 by_ref: false,
509 shape: ShapeKind::Scalar,
510 coercion: CoercionPolicy::None,
511 max: None,
512 repeating: None,
513 default: None,
514 },
515 ArgSchema {
517 kinds: smallvec::smallvec![ArgKind::Any],
518 required: true,
519 by_ref: false,
520 shape: ShapeKind::Range,
521 coercion: CoercionPolicy::None,
522 max: None,
523 repeating: None,
524 default: None,
525 },
526 ArgSchema {
528 kinds: smallvec::smallvec![ArgKind::Number],
529 required: true,
530 by_ref: false,
531 shape: ShapeKind::Scalar,
532 coercion: CoercionPolicy::NumberStrict,
533 max: None,
534 repeating: None,
535 default: None,
536 },
537 ArgSchema {
539 kinds: smallvec::smallvec![ArgKind::Logical],
540 required: false,
541 by_ref: false,
542 shape: ShapeKind::Scalar,
543 coercion: CoercionPolicy::Logical,
544 max: None,
545 repeating: None,
546 default: Some(LiteralValue::Boolean(false)),
547 },
548 ]
549 });
550 &SCHEMA
551 }
552 fn eval<'a, 'b, 'c>(
553 &self,
554 args: &'c [ArgumentHandle<'a, 'b>],
555 ctx: &dyn FunctionContext<'b>,
556 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
557 if args.len() < 3 {
558 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
559 ExcelError::new(ExcelErrorKind::Na),
560 )));
561 }
562 let lookup_value = args[0].value()?.into_literal();
563
564 let table_ref_opt = args[1].as_reference_or_eval().ok();
566 let row_index = match args[2].value()?.into_literal() {
567 LiteralValue::Int(i) => i,
568 LiteralValue::Number(n) => n as i64,
569 _ => {
570 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
571 ExcelError::new(ExcelErrorKind::Value),
572 )));
573 }
574 };
575 if row_index < 1 {
576 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
577 ExcelError::new(ExcelErrorKind::Value),
578 )));
579 }
580 let approximate = if args.len() >= 4 {
581 match args[3].value()?.into_literal() {
582 LiteralValue::Boolean(b) => b,
583 _ => true,
584 }
585 } else {
586 false
587 };
588 if let Some(table_ref) = table_ref_opt {
590 let (sheet, sr, sc, er, ec) = match &table_ref {
591 ReferenceType::Range {
592 sheet,
593 start_row: Some(sr),
594 start_col: Some(sc),
595 end_row: Some(er),
596 end_col: Some(ec),
597 ..
598 } => (sheet.clone(), *sr, *sc, *er, *ec),
599 _ => {
600 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
601 ExcelError::new(ExcelErrorKind::Ref),
602 )));
603 }
604 };
605 let current_sheet = ctx.current_sheet();
606 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
607 let height = er - sr + 1;
608 let width = ec - sc + 1;
609 if row_index as u32 > height {
610 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
611 ExcelError::new(ExcelErrorKind::Ref),
612 )));
613 }
614 let rv = ctx.resolve_range_view(&table_ref, sheet_name)?;
615 let cols = rv.dims().1;
616 let first_row_view = rv.sub_view(0, 0, 1, cols);
617 let col_idx_opt = if approximate {
618 let mut first_row: Vec<LiteralValue> = Vec::with_capacity(width as usize);
619 first_row_view.for_each_row(&mut |row| {
620 if first_row.is_empty() {
621 first_row.extend_from_slice(row);
622 }
623 Ok(())
624 })?;
625 binary_search_match(&first_row, &lookup_value, 1)
626 } else {
627 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
628 super::lookup_utils::find_exact_index_in_view(
629 &first_row_view,
630 &lookup_value,
631 wildcard_mode,
632 )?
633 };
634
635 match col_idx_opt {
636 Some(i) => {
637 let target_row_idx = (row_index - 1) as usize;
638 let v = rv.get_cell(target_row_idx, i);
639 let v = match v {
640 LiteralValue::Empty => LiteralValue::Number(0.0),
641 other => other,
642 };
643 Ok(crate::traits::CalcValue::Scalar(v))
644 }
645 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
646 ExcelError::new(ExcelErrorKind::Na),
647 ))),
648 }
649 } else {
650 let v = args[1].value()?.into_literal();
652 let table: Vec<Vec<LiteralValue>> = match v {
653 LiteralValue::Array(rows) => rows,
654 other => vec![vec![other]],
655 };
656 if table.is_empty() {
657 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
658 ExcelError::new(ExcelErrorKind::Na),
659 )));
660 }
661 let height = table.len();
662 if row_index as usize > height {
663 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
664 ExcelError::new(ExcelErrorKind::Ref),
665 )));
666 }
667
668 let first_row: Vec<LiteralValue> = table.first().cloned().unwrap_or_default();
670 let col_idx_opt = if approximate {
671 binary_search_match(&first_row, &lookup_value, 1)
672 } else {
673 let wildcard_mode = matches!(lookup_value, LiteralValue::Text(ref s) if s.contains('*') || s.contains('?') || s.contains('~'));
674 find_exact_index(&first_row, &lookup_value, wildcard_mode)
675 };
676
677 match col_idx_opt {
678 Some(i) => {
679 let target_row_idx = (row_index - 1) as usize;
680 let val = table
681 .get(target_row_idx)
682 .and_then(|r| r.get(i))
683 .cloned()
684 .unwrap_or(LiteralValue::Empty);
685 let val = match val {
686 LiteralValue::Empty => LiteralValue::Number(0.0),
687 other => other,
688 };
689 Ok(crate::traits::CalcValue::Scalar(val))
690 }
691 None => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
692 ExcelError::new(ExcelErrorKind::Na),
693 ))),
694 }
695 }
696 }
697}
698
699pub fn register_builtins() {
700 use crate::function_registry::register_function;
701 use std::sync::Arc;
702 register_function(Arc::new(MatchFn));
703 register_function(Arc::new(VLookupFn));
704 register_function(Arc::new(HLookupFn));
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710 use crate::test_workbook::TestWorkbook;
711 use crate::traits::ArgumentHandle;
712 use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
713 use std::sync::Arc;
714 fn lit(v: LiteralValue) -> ASTNode {
715 ASTNode::new(ASTNodeType::Literal(v), None)
716 }
717
718 #[test]
719 fn match_wildcard_and_descending_and_unsorted() {
720 let wb = TestWorkbook::new().with_function(Arc::new(MatchFn));
722 let wb = wb
723 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("foo".into()))
724 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("fob".into()))
725 .with_cell_a1("Sheet1", "A3", LiteralValue::Text("bar".into()))
726 .with_cell_a1("Sheet1", "A4", LiteralValue::Text("baz".into()));
727 let ctx = wb.interpreter();
728 let range = ASTNode::new(
729 ASTNodeType::Reference {
730 original: "A1:A4".into(),
731 reference: ReferenceType::range(None, Some(1), Some(1), Some(4), Some(1)),
732 },
733 None,
734 );
735 let f = ctx.context.get_function("", "MATCH").unwrap();
736 let pat = lit(LiteralValue::Text("*o*".into()));
738 let zero = lit(LiteralValue::Int(0));
739 let args = vec![
740 ArgumentHandle::new(&pat, &ctx),
741 ArgumentHandle::new(&range, &ctx),
742 ArgumentHandle::new(&zero, &ctx),
743 ];
744 let v = f
745 .dispatch(&args, &ctx.function_context(None))
746 .unwrap()
747 .into_literal();
748 assert_eq!(v, LiteralValue::Int(1));
749 let pat2 = lit(LiteralValue::Text("b?z".into()));
751 let args2 = vec![
752 ArgumentHandle::new(&pat2, &ctx),
753 ArgumentHandle::new(&range, &ctx),
754 ArgumentHandle::new(&zero, &ctx),
755 ];
756 let v2 = f
757 .dispatch(&args2, &ctx.function_context(None))
758 .unwrap()
759 .into_literal();
760 assert_eq!(v2, LiteralValue::Int(4));
761 let pat3 = lit(LiteralValue::Text("z*".into()));
763 let args3 = vec![
764 ArgumentHandle::new(&pat3, &ctx),
765 ArgumentHandle::new(&range, &ctx),
766 ArgumentHandle::new(&zero, &ctx),
767 ];
768 let v3 = f
769 .dispatch(&args3, &ctx.function_context(None))
770 .unwrap()
771 .into_literal();
772 assert!(matches!(v3, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
773
774 let wb2 = TestWorkbook::new()
776 .with_function(Arc::new(MatchFn))
777 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(50))
778 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(40))
779 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
780 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(20))
781 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(10));
782 let ctx2 = wb2.interpreter();
783 let range2 = ASTNode::new(
784 ASTNodeType::Reference {
785 original: "A1:A5".into(),
786 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
787 },
788 None,
789 );
790 let minus1 = lit(LiteralValue::Int(-1));
791 let thirty = lit(LiteralValue::Int(30));
792 let args_desc = vec![
793 ArgumentHandle::new(&thirty, &ctx2),
794 ArgumentHandle::new(&range2, &ctx2),
795 ArgumentHandle::new(&minus1, &ctx2),
796 ];
797 let v_desc = f
798 .dispatch(&args_desc, &ctx2.function_context(None))
799 .unwrap()
800 .into_literal();
801 assert_eq!(v_desc, LiteralValue::Int(3));
802 let sixty = lit(LiteralValue::Int(60));
804 let args_desc2 = vec![
805 ArgumentHandle::new(&sixty, &ctx2),
806 ArgumentHandle::new(&range2, &ctx2),
807 ArgumentHandle::new(&minus1, &ctx2),
808 ];
809 let v_desc2 = f
810 .dispatch(&args_desc2, &ctx2.function_context(None))
811 .unwrap()
812 .into_literal();
813 assert!(matches!(v_desc2, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
814
815 let wb3 = TestWorkbook::new()
817 .with_function(Arc::new(MatchFn))
818 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
819 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(30))
820 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(20))
821 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(40))
822 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(50));
823 let ctx3 = wb3.interpreter();
824 let range3 = ASTNode::new(
825 ASTNodeType::Reference {
826 original: "A1:A5".into(),
827 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
828 },
829 None,
830 );
831 let args_unsorted = vec![
832 ArgumentHandle::new(&thirty, &ctx3),
833 ArgumentHandle::new(&range3, &ctx3),
834 ];
835 let v_unsorted = f
836 .dispatch(&args_unsorted, &ctx3.function_context(None))
837 .unwrap()
838 .into_literal();
839 assert!(matches!(v_unsorted, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
840 let wb4 = TestWorkbook::new()
842 .with_function(Arc::new(MatchFn))
843 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(50))
844 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(30))
845 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(40))
846 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(20))
847 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(10));
848 let ctx4 = wb4.interpreter();
849 let range4 = ASTNode::new(
850 ASTNodeType::Reference {
851 original: "A1:A5".into(),
852 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
853 },
854 None,
855 );
856 let args_unsorted_desc = vec![
857 ArgumentHandle::new(&thirty, &ctx4),
858 ArgumentHandle::new(&range4, &ctx4),
859 ArgumentHandle::new(&minus1, &ctx4),
860 ];
861 let v_unsorted_desc = f
862 .dispatch(&args_unsorted_desc, &ctx4.function_context(None))
863 .unwrap()
864 .into_literal();
865 assert!(matches!(v_unsorted_desc, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Na));
866 }
867
868 #[test]
869 fn match_exact_and_approx() {
870 let wb = TestWorkbook::new().with_function(Arc::new(MatchFn));
871 let wb = wb
872 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(10))
873 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(20))
874 .with_cell_a1("Sheet1", "A3", LiteralValue::Int(30))
875 .with_cell_a1("Sheet1", "A4", LiteralValue::Int(40))
876 .with_cell_a1("Sheet1", "A5", LiteralValue::Int(50));
877 let ctx = wb.interpreter();
878 let range = ASTNode::new(
879 ASTNodeType::Reference {
880 original: "A1:A5".into(),
881 reference: ReferenceType::range(None, Some(1), Some(1), Some(5), Some(1)),
882 },
883 None,
884 );
885 let f = ctx.context.get_function("", "MATCH").unwrap();
886 let thirty = lit(LiteralValue::Int(30));
887 let zero = lit(LiteralValue::Int(0));
888 let args = vec![
889 ArgumentHandle::new(&thirty, &ctx),
890 ArgumentHandle::new(&range, &ctx),
891 ArgumentHandle::new(&zero, &ctx),
892 ];
893 let v = f
894 .dispatch(&args, &ctx.function_context(None))
895 .unwrap()
896 .into_literal();
897 assert_eq!(v, LiteralValue::Int(3));
898 let thirty_seven = lit(LiteralValue::Int(37));
899 let args = vec![
900 ArgumentHandle::new(&thirty_seven, &ctx),
901 ArgumentHandle::new(&range, &ctx),
902 ];
903 let v = f
904 .dispatch(&args, &ctx.function_context(None))
905 .unwrap()
906 .into_literal();
907 assert_eq!(v, LiteralValue::Int(3));
908 }
909
910 #[test]
911 fn vlookup_basic() {
912 let wb = TestWorkbook::new()
913 .with_function(Arc::new(VLookupFn))
914 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Key1".into()))
915 .with_cell_a1("Sheet1", "A2", LiteralValue::Text("Key2".into()))
916 .with_cell_a1("Sheet1", "B1", LiteralValue::Int(100))
917 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200));
918 let ctx = wb.interpreter();
919 let table = ASTNode::new(
920 ASTNodeType::Reference {
921 original: "A1:B2".into(),
922 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
923 },
924 None,
925 );
926 let f = ctx.context.get_function("", "VLOOKUP").unwrap();
927 let key2 = lit(LiteralValue::Text("Key2".into()));
928 let two = lit(LiteralValue::Int(2));
929 let false_lit = lit(LiteralValue::Boolean(false));
930 let args = vec![
931 ArgumentHandle::new(&key2, &ctx),
932 ArgumentHandle::new(&table, &ctx),
933 ArgumentHandle::new(&two, &ctx),
934 ArgumentHandle::new(&false_lit, &ctx),
935 ];
936 let v = f
937 .dispatch(&args, &ctx.function_context(None))
938 .unwrap()
939 .into_literal();
940 assert_eq!(v, LiteralValue::Number(200.0));
941 }
942
943 #[test]
944 fn vlookup_blank_target_cell_returns_zero() {
945 let wb = TestWorkbook::new()
948 .with_function(Arc::new(VLookupFn))
949 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1));
950
951 let ctx = wb.interpreter();
952 let table = ASTNode::new(
953 ASTNodeType::Reference {
954 original: "A1:B1".into(),
955 reference: ReferenceType::range(None, Some(1), Some(1), Some(1), Some(2)),
956 },
957 None,
958 );
959 let f = ctx.context.get_function("", "VLOOKUP").unwrap();
960 let key1 = lit(LiteralValue::Int(1));
961 let two = lit(LiteralValue::Int(2));
962 let false_lit = lit(LiteralValue::Boolean(false));
963 let args = vec![
964 ArgumentHandle::new(&key1, &ctx),
965 ArgumentHandle::new(&table, &ctx),
966 ArgumentHandle::new(&two, &ctx),
967 ArgumentHandle::new(&false_lit, &ctx),
968 ];
969 let v = f
970 .dispatch(&args, &ctx.function_context(None))
971 .unwrap()
972 .into_literal();
973 assert_eq!(v, LiteralValue::Number(0.0));
974 }
975
976 #[test]
977 fn hlookup_basic() {
978 let wb = TestWorkbook::new()
979 .with_function(Arc::new(HLookupFn))
980 .with_cell_a1("Sheet1", "A1", LiteralValue::Text("Key1".into()))
981 .with_cell_a1("Sheet1", "B1", LiteralValue::Text("Key2".into()))
982 .with_cell_a1("Sheet1", "A2", LiteralValue::Int(100))
983 .with_cell_a1("Sheet1", "B2", LiteralValue::Int(200));
984 let ctx = wb.interpreter();
985 let table = ASTNode::new(
986 ASTNodeType::Reference {
987 original: "A1:B2".into(),
988 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
989 },
990 None,
991 );
992 let f = ctx.context.get_function("", "HLOOKUP").unwrap();
993 let key1 = lit(LiteralValue::Text("Key1".into()));
994 let two = lit(LiteralValue::Int(2));
995 let false_lit = lit(LiteralValue::Boolean(false));
996 let args = vec![
997 ArgumentHandle::new(&key1, &ctx),
998 ArgumentHandle::new(&table, &ctx),
999 ArgumentHandle::new(&two, &ctx),
1000 ArgumentHandle::new(&false_lit, &ctx),
1001 ];
1002 let v = f
1003 .dispatch(&args, &ctx.function_context(None))
1004 .unwrap()
1005 .into_literal();
1006 assert_eq!(v, LiteralValue::Number(100.0));
1007 }
1008
1009 #[test]
1010 fn hlookup_blank_target_cell_returns_zero() {
1011 let wb = TestWorkbook::new()
1012 .with_function(Arc::new(HLookupFn))
1013 .with_cell_a1("Sheet1", "A1", LiteralValue::Int(1));
1014
1015 let ctx = wb.interpreter();
1016 let table = ASTNode::new(
1017 ASTNodeType::Reference {
1018 original: "A1:B2".into(),
1019 reference: ReferenceType::range(None, Some(1), Some(1), Some(2), Some(2)),
1020 },
1021 None,
1022 );
1023 let f = ctx.context.get_function("", "HLOOKUP").unwrap();
1024 let key1 = lit(LiteralValue::Int(1));
1025 let two = lit(LiteralValue::Int(2));
1026 let false_lit = lit(LiteralValue::Boolean(false));
1027 let args = vec![
1028 ArgumentHandle::new(&key1, &ctx),
1029 ArgumentHandle::new(&table, &ctx),
1030 ArgumentHandle::new(&two, &ctx),
1031 ArgumentHandle::new(&false_lit, &ctx),
1032 ];
1033 let v = f
1034 .dispatch(&args, &ctx.function_context(None))
1035 .unwrap()
1036 .into_literal();
1037 assert_eq!(v, LiteralValue::Number(0.0));
1038 }
1039}