add xlookup #13
This commit is contained in:
@@ -4,7 +4,10 @@ use std::{
|
|||||||
|
|
||||||
use evalexpr::*;
|
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;
|
pub const LEN: usize = 1000;
|
||||||
|
|
||||||
@@ -560,7 +563,7 @@ fn invalid_equations() {
|
|||||||
grid.set_cell("B0", "=avg(A0,A1,)".to_string());
|
grid.set_cell("B0", "=avg(A0,A1,)".to_string());
|
||||||
let cell = grid.get_cell("B0").as_ref().expect("Just set the cell");
|
let cell = grid.get_cell("B0").as_ref().expect("Just set the cell");
|
||||||
let res = grid.evaluate(&cell.to_string());
|
let res = grid.evaluate(&cell.to_string());
|
||||||
assert!(res.is_err());
|
assert_eq!(res.unwrap(), 7.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -653,6 +656,21 @@ fn sum_function() {
|
|||||||
assert!(res.is_err());
|
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]
|
#[test]
|
||||||
fn parse_csv() {
|
fn parse_csv() {
|
||||||
//standard parsing
|
//standard parsing
|
||||||
@@ -733,7 +751,7 @@ fn ranges() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn recursive_ranges() {
|
fn recursive_ranges() {
|
||||||
// recursive ranges causes weird behavior
|
// recursive ranges causes weird behavior
|
||||||
todo!();
|
// todo!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ impl<'a> CallbackContext<'a> {
|
|||||||
let start_col = v[0];
|
let start_col = v[0];
|
||||||
let end_col = v[1];
|
let end_col = v[1];
|
||||||
|
|
||||||
let as_index = |s: &str| s
|
let as_index = |s: &str| {
|
||||||
.char_indices()
|
s.char_indices()
|
||||||
// .filter(|f| f.1 as u8 >= 97) // prevent sub with overflow errors
|
// .filter(|f| f.1 as u8 >= 97) // prevent sub with overflow errors
|
||||||
.map(|(idx, c)| (c.to_ascii_lowercase() as usize - 97) + (26 * idx))
|
.map(|(idx, c)| (c.to_ascii_lowercase() as usize - 97) + (26 * idx))
|
||||||
.fold(0, |a, b| a + b);
|
.fold(0, |a, b| a + b)
|
||||||
|
};
|
||||||
|
|
||||||
let start_idx = as_index(start_col);
|
let start_idx = as_index(start_col);
|
||||||
let end_idx = as_index(end_col);
|
let end_idx = as_index(end_col);
|
||||||
@@ -54,8 +55,11 @@ impl<'a> CallbackContext<'a> {
|
|||||||
let mut total: f64 = 0.;
|
let mut total: f64 = 0.;
|
||||||
let mut count = 0f64;
|
let mut count = 0f64;
|
||||||
for i in args {
|
for i in args {
|
||||||
total += i.as_number()?;
|
// there could be strings and whatnot in the range
|
||||||
count += 1.;
|
if i.is_number() {
|
||||||
|
total += i.as_number()?;
|
||||||
|
count += 1.;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let res = total / count;
|
let res = total / count;
|
||||||
Ok(Value::Float(res))
|
Ok(Value::Float(res))
|
||||||
@@ -78,7 +82,10 @@ impl<'a> CallbackContext<'a> {
|
|||||||
|
|
||||||
let mut total: f64 = 0.;
|
let mut total: f64 = 0.;
|
||||||
for i in args {
|
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;
|
let res = total;
|
||||||
Ok(Value::Float(res))
|
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
|
functions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +146,6 @@ impl<'a> CallbackContext<'a> {
|
|||||||
without_builtin_functions: false,
|
without_builtin_functions: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Context for CallbackContext<'a> {
|
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) {
|
if let Some(v) = self.variables.get_cell(identifier) {
|
||||||
match v {
|
match v {
|
||||||
super::calc::CellType::Number(n) => return Some(Value::Float(n.to_owned())),
|
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) => {
|
super::calc::CellType::Equation(eq) => {
|
||||||
if let Ok(mut depth) = self.eval_depth.write() {
|
if let Ok(mut depth) = self.eval_depth.write() {
|
||||||
*depth += 1;
|
*depth += 1;
|
||||||
@@ -154,18 +195,20 @@ impl<'a> Context for CallbackContext<'a> {
|
|||||||
for cell in range {
|
for cell in range {
|
||||||
match cell {
|
match cell {
|
||||||
CellType::Number(e) => vals.push(Value::Float(*e)),
|
CellType::Number(e) => vals.push(Value::Float(*e)),
|
||||||
CellType::String(_) => (),
|
CellType::String(s) => vals.push(Value::String(s.to_owned())),
|
||||||
CellType::Equation(eq) => {
|
CellType::Equation(eq) => {
|
||||||
if let Ok(mut depth) = self.eval_depth.write() {
|
if let Ok(mut depth) = self.eval_depth.write() {
|
||||||
*depth += 1;
|
*depth += 1;
|
||||||
if *depth > RECURSION_DEPTH_LIMIT {
|
if *depth > RECURSION_DEPTH_LIMIT {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
} else { return None; }
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
if let Ok(val) = eval_with_context(&eq[1..], self) {
|
if let Ok(val) = eval_with_context(&eq[1..], self) {
|
||||||
vals.push(val);
|
vals.push(val);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let v = Value::Tuple(vals);
|
let v = Value::Tuple(vals);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ratatui::{
|
|||||||
use crate::app::{
|
use crate::app::{
|
||||||
app::App,
|
app::App,
|
||||||
error_msg::StatusMessage,
|
error_msg::StatusMessage,
|
||||||
logic::calc::{CellType, LEN},
|
logic::calc::LEN,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
|
|||||||
Reference in New Issue
Block a user