diff --git a/src/app/logic/calc.rs b/src/app/logic/calc.rs index c04d894..1d1077f 100644 --- a/src/app/logic/calc.rs +++ b/src/app/logic/calc.rs @@ -2,7 +2,7 @@ use std::{ fmt::Display, fs, io::{Read, Write}, - path::PathBuf, sync::RwLock, + path::PathBuf, }; use evalexpr::*; @@ -283,25 +283,22 @@ impl Grid { } } - /// Char and Idx making a new index - /// AZ becomes: - /// (0, A) - /// (1, Z) - pub fn char_to_idx((idx, c): (usize, &char)) -> usize { - (c.to_ascii_lowercase() as usize - 97) + (26 * idx) - } - /// Parse values in the format of A0, C10 ZZ99, etc, and /// turn them into an X,Y index. fn parse_to_idx(i: &str) -> Option<(usize, usize)> { let chars = i.chars().take_while(|c| c.is_alphabetic()).collect::>(); let nums = i.chars().skip(chars.len()).take_while(|c| c.is_numeric()).collect::(); + // At least half the arguments are gone + if chars.len() == 0 || nums.len() == 0 { + return None + } + // get the x index from the chars let x_idx = chars .iter() .enumerate() - .map(Self::char_to_idx) + .map(|(idx, c)| (c.to_ascii_lowercase() as usize - 97) + (26 * idx)) .fold(0, |a, b| a + b); // get the y index from the numbers @@ -431,9 +428,10 @@ fn alphanumeric_indexing() { assert_eq!(Grid::parse_to_idx("A10"), Some((0, 10))); assert_eq!(Grid::parse_to_idx("Aa10"), Some((26, 10))); assert_eq!(Grid::parse_to_idx("invalid"), None); - - assert_eq!(Grid::char_to_idx((0, &'A')), 0); - assert_eq!(Grid::char_to_idx((1, &'A')), 26); + assert_eq!(Grid::parse_to_idx("1"), None); + assert_eq!(Grid::parse_to_idx("A"), None); + assert_eq!(Grid::parse_to_idx(":"), None); + assert_eq!(Grid::parse_to_idx("="), None); assert_eq!(Grid::num_to_char(0).trim(), "A"); assert_eq!(Grid::num_to_char(25).trim(), "Z"); @@ -668,15 +666,49 @@ fn ranges() { grid.set_cell("A1", 1.); grid.set_cell("B0", "=sum(A:A)".to_string()); + // range with numbers let cell = grid.get_cell("B0").as_ref().expect("Just set it"); let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); assert_eq!(res, 3.); + // use range output as input for other function grid.set_cell("B1", "=B0*2".to_string()); - - // cell math let cell = grid.get_cell("B1").as_ref().expect("Just set it"); let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); assert_eq!(res, 6.); + // use equation outputs as range input + grid.set_cell("A2", "=C0+1".to_string()); + grid.set_cell("C0", 5.); + + let cell = grid.get_cell("A2").as_ref().expect("Just set it"); + let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); + assert_eq!(res, 6.); + + let cell = grid.get_cell("B0").as_ref().expect("Just set it"); + let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); + assert_eq!(res, 9.); + + // use function outputs as range input + grid.set_cell("B1", 2.); + grid.set_cell("C0", "=sum(B:B)".to_string()); + grid.set_cell("A2", "null".to_string()); + + let cell = grid.get_cell("C0").as_ref().expect("Just set it"); + let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); + assert_eq!(res, 5.); + + // use range outputs as range input + grid.set_cell("D0", "=sum(C:C)".to_string()); + grid.set_cell("C1", 1.); + + let cell = grid.get_cell("D0").as_ref().expect("Just set it"); + let res = grid.evaluate(&cell.to_string()).expect("Should evaluate."); + assert_eq!(res, 6.); +} + +#[test] +fn recursive_ranges() { + // recursive ranges causes weird behavior + todo!(); } \ No newline at end of file diff --git a/src/app/logic/ctx.rs b/src/app/logic/ctx.rs index f061fbb..159b05e 100644 --- a/src/app/logic/ctx.rs +++ b/src/app/logic/ctx.rs @@ -1,8 +1,8 @@ -use std::{collections::HashMap, process::id, sync::RwLock}; +use std::{collections::HashMap, sync::RwLock}; use evalexpr::{error::EvalexprResultValue, *}; -use crate::app::logic::calc::Grid; +use crate::app::logic::calc::{CellType, Grid}; pub struct CallbackContext<'a> { variables: &'a Grid, @@ -14,13 +14,17 @@ pub struct CallbackContext<'a> { } impl<'a> CallbackContext<'a> { - fn expand_range(&self, range: &str) -> Option> { + fn expand_range(&self, range: &str) -> Option> { let v = range.split(':').collect::>(); if v.len() == 2 { let start_col = v[0]; let end_col = v[1]; - let as_index = |s: &str| s.char_indices().map(|(a, b)| Grid::char_to_idx((a, &b))).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); @@ -30,11 +34,7 @@ impl<'a> CallbackContext<'a> { for x in start_idx..=end_idx { for y in 0..=self.variables.max_y_at_x(x) { if let Some(s) = self.variables.get_cell_raw(x, y) { - match s { - super::calc::CellType::Number(n) => buf.push(n.to_string()), - super::calc::CellType::String(_) => (), - super::calc::CellType::Equation(e) => buf.push(e.to_string()), - }; + buf.push(s); } } } @@ -75,6 +75,7 @@ impl<'a> CallbackContext<'a> { Function::new(|arg| { if arg.is_tuple() { let args = arg.as_tuple()?; + let mut total: f64 = 0.; for i in args { total += i.as_number()?; @@ -110,6 +111,8 @@ impl<'a> Context for CallbackContext<'a> { type NumericTypes = DefaultNumericTypes; fn get_value(&self, identifier: &str) -> Option> { + const RECURSION_DEPTH_LIMIT: usize = 20; + if let Some(v) = self.variables.get_cell(identifier) { match v { super::calc::CellType::Number(n) => return Some(Value::Float(n.to_owned())), @@ -117,9 +120,7 @@ impl<'a> Context for CallbackContext<'a> { super::calc::CellType::Equation(eq) => { if let Ok(mut depth) = self.eval_depth.write() { *depth += 1; - } - if let Ok(depth) = self.eval_depth.read() { - if *depth > 10 { + if *depth > RECURSION_DEPTH_LIMIT { return None; } } else { @@ -147,16 +148,28 @@ impl<'a> Context for CallbackContext<'a> { } } } else { - // identifier not found in cells, might be range - if let Some(v) = self.expand_range(identifier) { - dbg!(&v); + // identifier did not locate a cell, might be range + if let Some(range) = self.expand_range(identifier) { let mut vals = Vec::new(); - for v in v { - if let Some(value) = self.get_value(&v) { - vals.push(value); + for cell in range { + match cell { + CellType::Number(e) => vals.push(Value::Float(*e)), + CellType::String(_) => (), + CellType::Equation(eq) => { + if let Ok(mut depth) = self.eval_depth.write() { + *depth += 1; + if *depth > RECURSION_DEPTH_LIMIT { + return None; + } + } else { return None; } + if let Ok(val) = eval_with_context(&eq[1..], self) { + vals.push(val); + } + }, } } - return Some(Value::Tuple(vals)); + let v = Value::Tuple(vals); + return Some(v); } } return None; diff --git a/src/app/mode.rs b/src/app/mode.rs index 593f8e0..1006ef2 100644 --- a/src/app/mode.rs +++ b/src/app/mode.rs @@ -1,6 +1,6 @@ use std::{ cmp::min, - fmt::Display, fs, + fmt::Display, }; use ratatui::{ diff --git a/src/app/screen.rs b/src/app/screen.rs index d2ec126..a0089ce 100644 --- a/src/app/screen.rs +++ b/src/app/screen.rs @@ -28,7 +28,7 @@ impl ScreenSpace { let x_cells = (screen_size.0 / self.get_cell_width(vars) as usize) -2; let x_center = self.scroll_x() + (x_cells/2); - let delta = (cursor_x as isize - x_center as isize); + let delta = cursor_x as isize - x_center as isize; self.scroll.0 = self.scroll.0.saturating_add_signed(delta); } } @@ -37,7 +37,7 @@ impl ScreenSpace { let y_cells = (screen_size.1 / self.get_cell_height(vars) as usize) -2; let y_center = self.scroll_y() + (y_cells/2); - let delta = (cursor_y as isize - y_center as isize); + let delta = cursor_y as isize - y_center as isize; self.scroll.1 = self.scroll.1.saturating_add_signed(delta); } }