start adding ranges

This commit is contained in:
2025-11-12 15:00:01 -07:00
parent 052e1436cb
commit 90168a0999
3 changed files with 108 additions and 21 deletions

View File

@@ -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

View File

@@ -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;
} }
} }

View File

@@ -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]