mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 05:06:51 -06:00
Merge PR #1066: Engine: formulas.evaluate refuses what cell entry refuses, saying why
ROADMAP.md: both progress-log rows kept.
This commit is contained in:
@@ -138,18 +138,7 @@ pub(crate) fn input_to_cell(input: &str, sheet: usize, at: CellRef, wb: &mut gri
|
||||
&& input.chars().nth(1).is_some_and(|c| c.is_ascii_alphabetic() || c == '('));
|
||||
if is_formula {
|
||||
let body = input.strip_prefix('=').unwrap_or(input);
|
||||
// Excel closes missing parentheses for you.
|
||||
let mut text = body.to_string();
|
||||
let mut parsed = gridcraft_formula::parse(&text);
|
||||
for _ in 0..8 {
|
||||
if parsed.is_ok() {
|
||||
break;
|
||||
}
|
||||
text.push(')');
|
||||
parsed = gridcraft_formula::parse(&text);
|
||||
}
|
||||
let expr = parsed.map_err(|e| EngineError::Other(format!("There's a problem with this formula: {e}")))?;
|
||||
let expr = wb.name_call_case(expr);
|
||||
let expr = wb.name_call_case(parse_typed_formula(body)?);
|
||||
check_arg_counts(&expr)?;
|
||||
let mut cell = Cell::formula(Formula::from_expr(expr));
|
||||
cell.style = style;
|
||||
@@ -191,11 +180,26 @@ pub(crate) fn input_to_cell(input: &str, sheet: usize, at: CellRef, wb: &mut gri
|
||||
Ok(Some(Cell { value: parsed.value, formula: None, style }))
|
||||
}
|
||||
|
||||
/// Parses a formula as it is typed (without the `=`). Excel closes missing parentheses for you,
|
||||
/// and refuses a formula that still doesn't parse.
|
||||
pub(crate) fn parse_typed_formula(body: &str) -> Result<gridcraft_formula::Expr> {
|
||||
let mut text = body.to_string();
|
||||
let mut parsed = gridcraft_formula::parse(&text);
|
||||
for _ in 0..8 {
|
||||
if parsed.is_ok() {
|
||||
break;
|
||||
}
|
||||
text.push(')');
|
||||
parsed = gridcraft_formula::parse(&text);
|
||||
}
|
||||
parsed.map_err(|e| EngineError::Other(format!("There's a problem with this formula: {e}")))
|
||||
}
|
||||
|
||||
/// Like Excel, a formula that calls a built-in function with too few or too many arguments is
|
||||
/// refused when it's entered, rather than stored to show `#VALUE!`. Functions the registry doesn't
|
||||
/// hold (special forms like LET, LAMBDA, OFFSET, INDIRECT, SUBTOTAL), defined LAMBDA names and
|
||||
/// unknown functions are skipped.
|
||||
fn check_arg_counts(expr: &gridcraft_formula::Expr) -> Result<()> {
|
||||
pub(crate) fn check_arg_counts(expr: &gridcraft_formula::Expr) -> Result<()> {
|
||||
let mut bad = None;
|
||||
expr.walk(&mut |e| {
|
||||
if bad.is_none()
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
cmd!("formulas.showFormulas", "Show Formulas", ["Formulas", "Formula Auditing"], Some("Ctrl+`"), "{on?}", has_doc, show_formulas),
|
||||
cmd!(query "formulas.errorChecking", "Error Checking", ["Formulas", "Formula Auditing"], None, "{} → cells with errors", has_doc, error_checking),
|
||||
cmd!(query "formulas.evaluateFormula", "Evaluate Formula", ["Formulas", "Formula Auditing"], None, "{cell?, formula?} → steps", has_doc, evaluate_formula),
|
||||
cmd!(query "formulas.evaluate", "Evaluate", [], None, "{formula: \"=SUM(A1:A3)\", cell?} → value without changing the sheet", has_doc, evaluate),
|
||||
cmd!(query "formulas.evaluate", "Evaluate", [], None, "{formula: \"=SUM(A1:A3)\", cell?} → value without changing the sheet; a formula that couldn't be entered in a cell is an error", has_doc, evaluate),
|
||||
cmd!(noundo "formulas.calculateNow", "Calculate Now", ["Formulas", "Calculation"], Some("F9"), "{}", has_doc, calc_now),
|
||||
cmd!(noundo "formulas.calculateSheet", "Calculate Sheet", ["Formulas", "Calculation"], Some("Shift+F9"), "{}", has_doc, calc_now),
|
||||
cmd!(
|
||||
@@ -542,11 +542,16 @@ fn evaluate_formula(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
Ok(json!({"formula": format!("={text}"), "steps": steps, "result": crate::cmd::inspect::value_json(&result)}))
|
||||
}
|
||||
|
||||
/// Evaluates what typing `formula` into a cell would show. A formula `cell.set` would refuse (it
|
||||
/// doesn't parse, or calls a function with the wrong number of arguments) is an error that says
|
||||
/// why, not `#NAME?`; an unknown function name still evaluates to `#NAME?`, as in Excel.
|
||||
fn evaluate(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let d = s.doc()?;
|
||||
let f = str_param(p, "formula").ok_or_else(|| bad("formulas.evaluate", "missing `formula`"))?;
|
||||
let at = cell_param(s, p, "cell")?.unwrap_or(d.selection.active);
|
||||
let v = gridcraft_calc::evaluate(&d.wb, d.wb.active_sheet, at, f);
|
||||
let expr = crate::cmd::edit::parse_typed_formula(f.strip_prefix('=').unwrap_or(f))?;
|
||||
crate::cmd::edit::check_arg_counts(&expr)?;
|
||||
let v = gridcraft_calc::recalc::evaluate_expr(&d.wb, d.wb.active_sheet, at, &expr);
|
||||
Ok(crate::cmd::inspect::value_json(&v))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user