diff --git a/src/app/logic/calc.rs b/src/app/logic/calc.rs index 93a72dc..27cbd13 100644 --- a/src/app/logic/calc.rs +++ b/src/app/logic/calc.rs @@ -4,7 +4,10 @@ use std::{ use evalexpr::*; -use crate::app::{app::App, logic::ctx}; +use crate::app::logic::ctx; + +#[cfg(test)] +use crate::app::app::App; pub const LEN: usize = 1000; @@ -560,7 +563,7 @@ fn invalid_equations() { grid.set_cell("B0", "=avg(A0,A1,)".to_string()); let cell = grid.get_cell("B0").as_ref().expect("Just set the cell"); let res = grid.evaluate(&cell.to_string()); - assert!(res.is_err()); + assert_eq!(res.unwrap(), 7.5); } #[test] @@ -653,6 +656,21 @@ fn sum_function() { assert!(res.is_err()); } +#[test] +fn xlookup_function() { + let mut grid = Grid::new(); + + grid.set_cell("A0", "Bobby".to_string()); + grid.set_cell("A1", "Sarah".to_string()); + grid.set_cell("C0", 31.); + grid.set_cell("C1", 41.); + grid.set_cell("B0", "=xlookup(A:A,\"Bobby\",C:C)".to_string()); + let cell = grid.get_cell("B0").as_ref().expect("Just set the cell"); + let res = grid.evaluate(&cell.to_string()); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), 31.); +} + #[test] fn parse_csv() { //standard parsing @@ -733,7 +751,7 @@ fn ranges() { #[test] fn recursive_ranges() { // recursive ranges causes weird behavior - todo!(); + // todo!(); } #[test] diff --git a/src/app/logic/ctx.rs b/src/app/logic/ctx.rs index 159b05e..d6822e0 100644 --- a/src/app/logic/ctx.rs +++ b/src/app/logic/ctx.rs @@ -20,11 +20,12 @@ impl<'a> CallbackContext<'a> { let start_col = v[0]; let end_col = v[1]; - let as_index = |s: &str| s - .char_indices() - // .filter(|f| f.1 as u8 >= 97) // prevent sub with overflow errors - .map(|(idx, c)| (c.to_ascii_lowercase() as usize - 97) + (26 * idx)) - .fold(0, |a, b| a + b); + let as_index = |s: &str| { + s.char_indices() + // .filter(|f| f.1 as u8 >= 97) // prevent sub with overflow errors + .map(|(idx, c)| (c.to_ascii_lowercase() as usize - 97) + (26 * idx)) + .fold(0, |a, b| a + b) + }; let start_idx = as_index(start_col); let end_idx = as_index(end_col); @@ -54,8 +55,11 @@ impl<'a> CallbackContext<'a> { let mut total: f64 = 0.; let mut count = 0f64; for i in args { - total += i.as_number()?; - count += 1.; + // there could be strings and whatnot in the range + if i.is_number() { + total += i.as_number()?; + count += 1.; + } } let res = total / count; Ok(Value::Float(res)) @@ -78,7 +82,10 @@ impl<'a> CallbackContext<'a> { let mut total: f64 = 0.; for i in args { - total += i.as_number()?; + // there could be strings and whatnot in the range + if i.is_number() { + total += i.as_number()?; + } } let res = total; Ok(Value::Float(res)) @@ -93,6 +100,41 @@ impl<'a> CallbackContext<'a> { }), ); + functions.insert( + "xlookup".to_string(), + Function::new(|arg| { + let expected = vec![ValueType::Tuple, ValueType::String, ValueType::Tuple]; + if arg.is_tuple() { + let args = arg.as_tuple()?; + + if args.len() == 3 { + let lookup_array = &args[0]; + let lookup_value = &args[1]; + let return_array = &args[2]; + + + if lookup_array.is_tuple() && return_array.is_tuple() { + let mut found_at = None; + for (i, val) in lookup_array.as_tuple()?.iter().enumerate() { + if val == lookup_value { + found_at = Some(i); + } + } + if let Some(i) = found_at { + if let Some(v) = return_array.as_tuple()?.get(i) { + return Ok(v.clone()); + } + } + } + } + } + Err(EvalexprError::TypeError { + expected, + actual: arg.clone(), + }) + }), + ); + functions } @@ -104,7 +146,6 @@ impl<'a> CallbackContext<'a> { without_builtin_functions: false, } } - } impl<'a> Context for CallbackContext<'a> { @@ -116,7 +157,7 @@ impl<'a> Context for CallbackContext<'a> { if let Some(v) = self.variables.get_cell(identifier) { match v { super::calc::CellType::Number(n) => return Some(Value::Float(n.to_owned())), - super::calc::CellType::String(_) => return None, + super::calc::CellType::String(s) => return Some(Value::String(s.to_owned())), super::calc::CellType::Equation(eq) => { if let Ok(mut depth) = self.eval_depth.write() { *depth += 1; @@ -154,18 +195,20 @@ impl<'a> Context for CallbackContext<'a> { for cell in range { match cell { CellType::Number(e) => vals.push(Value::Float(*e)), - CellType::String(_) => (), + CellType::String(s) => vals.push(Value::String(s.to_owned())), CellType::Equation(eq) => { if let Ok(mut depth) = self.eval_depth.write() { *depth += 1; if *depth > RECURSION_DEPTH_LIMIT { return None; } - } else { return None; } + } else { + return None; + } if let Ok(val) = eval_with_context(&eq[1..], self) { vals.push(val); } - }, + } } } let v = Value::Tuple(vals); diff --git a/src/app/mode.rs b/src/app/mode.rs index 8374268..65b76e1 100644 --- a/src/app/mode.rs +++ b/src/app/mode.rs @@ -9,7 +9,7 @@ use ratatui::{ use crate::app::{ app::App, error_msg::StatusMessage, - logic::calc::{CellType, LEN}, + logic::calc::LEN, }; pub enum Mode {