start adding ranges
This commit is contained in:
@@ -5,3 +5,4 @@ Based loosely off sc-im, which has dumb keybinds and not many features.
|
|||||||
## Key notes:
|
## Key notes:
|
||||||
|
|
||||||
* Every number is a float (or will end up as a float)
|
* Every number is a float (or will end up as a float)
|
||||||
|
* Ranges skip text cells
|
||||||
@@ -43,10 +43,10 @@ impl Widget for &App {
|
|||||||
x2 += 1;
|
x2 += 1;
|
||||||
y2 += 1;
|
y2 += 1;
|
||||||
|
|
||||||
|
// in-between the Xs
|
||||||
if (x >= x1 && x <= x2) || (x >= x2 && x <= x1) {
|
if (x >= x1 && x <= x2) || (x >= x2 && x <= x1) {
|
||||||
// in-between the Xs
|
// in-between the Ys
|
||||||
if (y >= y1 && y <= y2) || (y >= y2 && y <= y1) {
|
if (y >= y1 && y <= y2) || (y >= y2 && y <= y1) {
|
||||||
// in-between the Ys
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,27 @@ impl Grid {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn max_y_at_x(&self, x: usize) -> usize {
|
||||||
|
let mut max_y = 0;
|
||||||
|
if let Some(col) = self.cells.get(x) {
|
||||||
|
// we could do fancy things like .take_while(not null) but then
|
||||||
|
// we would have to deal with empty cells and stuff, which sounds
|
||||||
|
// boring. This will be fast "enough", considering the grid is
|
||||||
|
// probably only like 1k cells
|
||||||
|
for (yi, cell) in col.iter().enumerate() {
|
||||||
|
if cell.is_some() {
|
||||||
|
if yi > max_y {
|
||||||
|
max_y = yi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max_y
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over the entire grid and see where
|
/// Iterate over the entire grid and see where
|
||||||
/// the farthest modified cell is.
|
/// the farthest modified cell is.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -218,35 +239,84 @@ impl Grid {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Only evaluates equations, such as `=10` or `=A1/C2` not,
|
/// Only evaluates equations, such as `=10` or `=A1/C2`, not
|
||||||
/// strings or numbers.
|
/// strings or numbers.
|
||||||
pub fn evaluate(&self, mut eq: &str) -> Result<f64, String> {
|
pub fn evaluate(&self, mut eq: &str) -> Result<f64, String> {
|
||||||
|
let original_equation = eq;
|
||||||
|
|
||||||
if eq.starts_with('=') {
|
if eq.starts_with('=') {
|
||||||
eq = &eq[1..];
|
eq = &eq[1..];
|
||||||
} else {
|
} else {
|
||||||
// Should be evaluating an equation
|
// Should be evaluating an equation
|
||||||
return Err("not eq".to_string());
|
return Err(format!("\"{eq}\" is not an equation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = ctx::CallbackContext::new(&self);
|
let ctx = ctx::CallbackContext::new(&self);
|
||||||
|
|
||||||
|
let prep_for_return = |v: Value| {
|
||||||
|
if v.is_number() {
|
||||||
|
if v.is_float() {
|
||||||
|
let val = v.as_float().expect("Value lied about being a float");
|
||||||
|
return Ok(val);
|
||||||
|
} else if v.is_int() {
|
||||||
|
let i = v.as_int().expect("Value lied about being an int");
|
||||||
|
return Ok(i as f64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err("Result is NaN".to_string());
|
||||||
|
};
|
||||||
|
|
||||||
match eval_with_context(eq, &ctx) {
|
match eval_with_context(eq, &ctx) {
|
||||||
Ok(e) => {
|
Ok(e) => {
|
||||||
if e.is_number() {
|
return prep_for_return(e);
|
||||||
if e.is_float() {
|
|
||||||
let val = e.as_float().expect("Value lied about being a float");
|
|
||||||
return Ok(val);
|
|
||||||
} else if e.is_int() {
|
|
||||||
let i = e.as_int().expect("Value lied about being an int");
|
|
||||||
return Ok(i as f64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err("Result is NaN".to_string());
|
|
||||||
}
|
}
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
EvalexprError::VariableIdentifierNotFound(e) => {
|
EvalexprError::VariableIdentifierNotFound(var_not_found) => {
|
||||||
|
// the identifier might be a range: A:A
|
||||||
|
let v = var_not_found.split(':').collect::<Vec<&str>>();
|
||||||
|
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 start_idx = as_index(start_col);
|
||||||
|
let end_idx = as_index(end_col);
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
|
||||||
|
for x in start_idx..=end_idx {
|
||||||
|
for y in 0..=self.max_y_at_x(x) {
|
||||||
|
if let Some(s) = self.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()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let start = original_equation.find(&var_not_found).expect("It should be here");
|
||||||
|
let end = start + var_not_found.len();
|
||||||
|
|
||||||
|
let res = buf.iter().enumerate().map(|(i,b )| {
|
||||||
|
if i == buf.len()-1 {
|
||||||
|
// last cell
|
||||||
|
b.to_owned()
|
||||||
|
} else {
|
||||||
|
format!("{b},")
|
||||||
|
}
|
||||||
|
}).collect::<String>();
|
||||||
|
|
||||||
|
let new_eq = format!("{}{}{}", &original_equation[..start], res, &original_equation[end..]);
|
||||||
|
|
||||||
|
// FIXME this might be a dangerous recursion, as I don't think its bounded right now
|
||||||
|
return self.evaluate(&new_eq)
|
||||||
|
}
|
||||||
// panic!("Will not be able to parse this equation, cell {e} not found")
|
// panic!("Will not be able to parse this equation, cell {e} not found")
|
||||||
return Err(format!("{e} is not a variable"));
|
return Err(format!("{var_not_found} is not a variable"));
|
||||||
}
|
}
|
||||||
EvalexprError::TypeError {
|
EvalexprError::TypeError {
|
||||||
expected: e,
|
expected: e,
|
||||||
@@ -260,6 +330,14 @@ 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)> {
|
||||||
@@ -270,10 +348,7 @@ impl Grid {
|
|||||||
let x_idx = chars
|
let x_idx = chars
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(idx, c)| {
|
.map(Self::char_to_idx)
|
||||||
// convert to numbers
|
|
||||||
(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
|
||||||
@@ -403,6 +478,9 @@ fn alphanumeric_indexing() {
|
|||||||
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::char_to_idx((0, &'A')), 0);
|
||||||
|
assert_eq!(Grid::char_to_idx((1, &'A')), 26);
|
||||||
|
|
||||||
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");
|
||||||
assert_eq!(Grid::num_to_char(26), "AA");
|
assert_eq!(Grid::num_to_char(26), "AA");
|
||||||
@@ -500,6 +578,14 @@ fn invalid_equations() {
|
|||||||
let res = grid.evaluate(&cell.to_string());
|
let res = grid.evaluate(&cell.to_string());
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
assert!(res.is_ok_and(|v| v == 10.));
|
assert!(res.is_ok_and(|v| v == 10.));
|
||||||
|
|
||||||
|
// Trailing comma in function call
|
||||||
|
grid.set_cell("A0", 5.);
|
||||||
|
grid.set_cell("A1", 10.);
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user