forked from 1b-agent/opencadstudio-upstream
fix(table): ignore malformed formula delimiters instead of slicing reversed
Bug:
evaluate_table_formula located "(" with find (forward) and ")" with
rfind (backward), then sliced body[open+1..close]. For malformed input
like =)( the delimiters come out reversed and the slice panics ("slice
index starts at 2 but ends at 0") during tessellation - on the render
path, from a single pasted cell value.
Reproduce:
1. Insert a table and double-click a cell.
2. Type =)( (or =a)b(c ) and press Enter.
3. Before: the next repaint panics.
4. After: the formula does not evaluate; the literal text renders.
Fix:
body.get(open + 1..close)? - reversed delimiters yield None and the
caller's existing fallback shows the literal text. Well-formed formulas
are untouched: regression tests keep =SUM(A1:B2), =AVERAGE(A1:B2) and
=A1 evaluating, and reject =)(, =a)b(c and =).
This commit is contained in:
1 parent
ee188a6e3c
commit
d175861fa4
1 file changed
+51
-1
+51
-1
@@ -505,7 +505,10 @@ fn evaluate_table_formula(table: &Table, expression: &str) -> Option<String> {
|
||||
let open = body.find('(')?;
|
||||
let close = body.rfind(')')?;
|
||||
let function = body[..open].trim().to_ascii_uppercase();
|
||||
let range = &body[open + 1..close];
|
||||
// `find('(')` scans forward while `rfind(')')` scans backward, so a
|
||||
// malformed body like `)(` orders them reversed; the safe `get` yields
|
||||
// None (fall back to the literal text) instead of a slice panic.
|
||||
let range = body.get(open + 1..close)?;
|
||||
let (first, last) = range.split_once(':').unwrap_or((range, range));
|
||||
let (r1, c1) = table_cell_reference(first)?;
|
||||
let (r2, c2) = table_cell_reference(last)?;
|
||||
@@ -3367,3 +3370,50 @@ pub(crate) fn normalize_scripted_table(old: Option<&Table>, new: &mut Table) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn sample_table() -> Table {
|
||||
let mut table = Table::new(codec::types::Vector3::new(0.0, 0.0, 0.0), 2, 2);
|
||||
table.set_cell_text(0, 0, "10");
|
||||
table.set_cell_text(0, 1, "20");
|
||||
table.set_cell_text(1, 0, "30");
|
||||
table.set_cell_text(1, 1, "40");
|
||||
table
|
||||
}
|
||||
|
||||
/// Delimiters located independently (`find('(')` forward, `rfind(')')`
|
||||
/// backward) must not produce a reversed slice: `=)(` used to build
|
||||
/// `&body[2..0]` — `slice index starts at 2 but ends at 0` — killing the
|
||||
/// render pass from a single pasted cell value.
|
||||
#[test]
|
||||
fn malformed_formula_is_ignored_instead_of_panicking() {
|
||||
let table = sample_table();
|
||||
for expression in ["=)(", "=a)b(c", "=)"] {
|
||||
assert_eq!(
|
||||
evaluate_table_formula(&table, expression),
|
||||
None,
|
||||
"{expression} should not evaluate"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-formed formulas keep evaluating across the whole file — the
|
||||
/// guard must only reject malformed delimiters.
|
||||
#[test]
|
||||
fn well_formed_formulas_still_evaluate() {
|
||||
let table = sample_table();
|
||||
assert_eq!(
|
||||
evaluate_table_formula(&table, "=SUM(A1:B2)"),
|
||||
Some("100".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
evaluate_table_formula(&table, "=AVERAGE(A1:B2)"),
|
||||
Some("25".to_string())
|
||||
);
|
||||
assert_eq!(evaluate_table_formula(&table, "=A1"), Some("10".to_string()));
|
||||
assert_eq!(evaluate_table_formula(&table, "not a formula"), None);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user