ranges work now!!
This commit is contained in:
@@ -2,7 +2,7 @@ use std::{
|
|||||||
fmt::Display,
|
fmt::Display,
|
||||||
fs,
|
fs,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
path::PathBuf, sync::RwLock,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use evalexpr::*;
|
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
|
/// Parse values in the format of A0, C10 ZZ99, etc, and
|
||||||
/// turn them into an X,Y index.
|
/// turn them into an X,Y index.
|
||||||
fn parse_to_idx(i: &str) -> Option<(usize, usize)> {
|
fn parse_to_idx(i: &str) -> Option<(usize, usize)> {
|
||||||
let chars = i.chars().take_while(|c| c.is_alphabetic()).collect::<Vec<char>>();
|
let chars = i.chars().take_while(|c| c.is_alphabetic()).collect::<Vec<char>>();
|
||||||
let nums = i.chars().skip(chars.len()).take_while(|c| c.is_numeric()).collect::<String>();
|
let nums = i.chars().skip(chars.len()).take_while(|c| c.is_numeric()).collect::<String>();
|
||||||
|
|
||||||
|
// At least half the arguments are gone
|
||||||
|
if chars.len() == 0 || nums.len() == 0 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
// get the x index from the chars
|
// get the x index from the chars
|
||||||
let x_idx = chars
|
let x_idx = chars
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(Self::char_to_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);
|
||||||
|
|
||||||
// get the y index from the numbers
|
// 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("A10"), Some((0, 10)));
|
||||||
assert_eq!(Grid::parse_to_idx("Aa10"), Some((26, 10)));
|
assert_eq!(Grid::parse_to_idx("Aa10"), Some((26, 10)));
|
||||||
assert_eq!(Grid::parse_to_idx("invalid"), None);
|
assert_eq!(Grid::parse_to_idx("invalid"), None);
|
||||||
|
assert_eq!(Grid::parse_to_idx("1"), None);
|
||||||
assert_eq!(Grid::char_to_idx((0, &'A')), 0);
|
assert_eq!(Grid::parse_to_idx("A"), None);
|
||||||
assert_eq!(Grid::char_to_idx((1, &'A')), 26);
|
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(0).trim(), "A");
|
||||||
assert_eq!(Grid::num_to_char(25).trim(), "Z");
|
assert_eq!(Grid::num_to_char(25).trim(), "Z");
|
||||||
@@ -668,15 +666,49 @@ fn ranges() {
|
|||||||
grid.set_cell("A1", 1.);
|
grid.set_cell("A1", 1.);
|
||||||
grid.set_cell("B0", "=sum(A:A)".to_string());
|
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 cell = grid.get_cell("B0").as_ref().expect("Just set it");
|
||||||
let res = grid.evaluate(&cell.to_string()).expect("Should evaluate.");
|
let res = grid.evaluate(&cell.to_string()).expect("Should evaluate.");
|
||||||
assert_eq!(res, 3.);
|
assert_eq!(res, 3.);
|
||||||
|
|
||||||
|
// use range output as input for other function
|
||||||
grid.set_cell("B1", "=B0*2".to_string());
|
grid.set_cell("B1", "=B0*2".to_string());
|
||||||
|
|
||||||
// cell math
|
|
||||||
let cell = grid.get_cell("B1").as_ref().expect("Just set it");
|
let cell = grid.get_cell("B1").as_ref().expect("Just set it");
|
||||||
let res = grid.evaluate(&cell.to_string()).expect("Should evaluate.");
|
let res = grid.evaluate(&cell.to_string()).expect("Should evaluate.");
|
||||||
assert_eq!(res, 6.);
|
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!();
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::{collections::HashMap, process::id, sync::RwLock};
|
use std::{collections::HashMap, sync::RwLock};
|
||||||
|
|
||||||
use evalexpr::{error::EvalexprResultValue, *};
|
use evalexpr::{error::EvalexprResultValue, *};
|
||||||
|
|
||||||
use crate::app::logic::calc::Grid;
|
use crate::app::logic::calc::{CellType, Grid};
|
||||||
|
|
||||||
pub struct CallbackContext<'a> {
|
pub struct CallbackContext<'a> {
|
||||||
variables: &'a Grid,
|
variables: &'a Grid,
|
||||||
@@ -14,13 +14,17 @@ pub struct CallbackContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CallbackContext<'a> {
|
impl<'a> CallbackContext<'a> {
|
||||||
fn expand_range(&self, range: &str) -> Option<Vec<String>> {
|
fn expand_range(&self, range: &str) -> Option<Vec<&CellType>> {
|
||||||
let v = range.split(':').collect::<Vec<&str>>();
|
let v = range.split(':').collect::<Vec<&str>>();
|
||||||
if v.len() == 2 {
|
if v.len() == 2 {
|
||||||
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.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 start_idx = as_index(start_col);
|
||||||
let end_idx = as_index(end_col);
|
let end_idx = as_index(end_col);
|
||||||
@@ -30,11 +34,7 @@ impl<'a> CallbackContext<'a> {
|
|||||||
for x in start_idx..=end_idx {
|
for x in start_idx..=end_idx {
|
||||||
for y in 0..=self.variables.max_y_at_x(x) {
|
for y in 0..=self.variables.max_y_at_x(x) {
|
||||||
if let Some(s) = self.variables.get_cell_raw(x, y) {
|
if let Some(s) = self.variables.get_cell_raw(x, y) {
|
||||||
match s {
|
buf.push(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()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ impl<'a> CallbackContext<'a> {
|
|||||||
Function::new(|arg| {
|
Function::new(|arg| {
|
||||||
if arg.is_tuple() {
|
if arg.is_tuple() {
|
||||||
let args = arg.as_tuple()?;
|
let args = arg.as_tuple()?;
|
||||||
|
|
||||||
let mut total: f64 = 0.;
|
let mut total: f64 = 0.;
|
||||||
for i in args {
|
for i in args {
|
||||||
total += i.as_number()?;
|
total += i.as_number()?;
|
||||||
@@ -110,6 +111,8 @@ impl<'a> Context for CallbackContext<'a> {
|
|||||||
type NumericTypes = DefaultNumericTypes;
|
type NumericTypes = DefaultNumericTypes;
|
||||||
|
|
||||||
fn get_value(&self, identifier: &str) -> Option<Value<Self::NumericTypes>> {
|
fn get_value(&self, identifier: &str) -> Option<Value<Self::NumericTypes>> {
|
||||||
|
const RECURSION_DEPTH_LIMIT: usize = 20;
|
||||||
|
|
||||||
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())),
|
||||||
@@ -117,9 +120,7 @@ impl<'a> Context for CallbackContext<'a> {
|
|||||||
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;
|
||||||
}
|
if *depth > RECURSION_DEPTH_LIMIT {
|
||||||
if let Ok(depth) = self.eval_depth.read() {
|
|
||||||
if *depth > 10 {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -147,16 +148,28 @@ impl<'a> Context for CallbackContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// identifier not found in cells, might be range
|
// identifier did not locate a cell, might be range
|
||||||
if let Some(v) = self.expand_range(identifier) {
|
if let Some(range) = self.expand_range(identifier) {
|
||||||
dbg!(&v);
|
|
||||||
let mut vals = Vec::new();
|
let mut vals = Vec::new();
|
||||||
for v in v {
|
for cell in range {
|
||||||
if let Some(value) = self.get_value(&v) {
|
match cell {
|
||||||
vals.push(value);
|
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;
|
return None;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cmp::min,
|
cmp::min,
|
||||||
fmt::Display, fs,
|
fmt::Display,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl ScreenSpace {
|
|||||||
let x_cells = (screen_size.0 / self.get_cell_width(vars) as usize) -2;
|
let x_cells = (screen_size.0 / self.get_cell_width(vars) as usize) -2;
|
||||||
let x_center = self.scroll_x() + (x_cells/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);
|
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_cells = (screen_size.1 / self.get_cell_height(vars) as usize) -2;
|
||||||
let y_center = self.scroll_y() + (y_cells/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);
|
self.scroll.1 = self.scroll.1.saturating_add_signed(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user