refactor: replace BinOp string with typed enum in Expr AST

Previously Expr::BinOp(String, ...) accepted any string as an operator.
Invalid operators (e.g. "diagonal") would compile fine and silently
return CellValue::Empty at eval time.

Now BinOp is an enum with variants Add/Sub/Mul/Div/Pow/Eq/Ne/Lt/Gt/Le/Ge.
The parser produces enum variants directly; the evaluator pattern-matches
exhaustively with no fallback branch. An invalid operator is now a
compile error at the call site, and the compiler ensures every variant
is handled in both eval_expr and eval_bool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ed L
2026-03-24 00:20:08 -07:00
parent 5434a60cc4
commit 599f1adcbd
4 changed files with 60 additions and 34 deletions

View File

@ -193,15 +193,19 @@ impl Model {
model.evaluate(&new_key).as_f64()
}
Expr::BinOp(op, l, r) => {
use crate::formula::BinOp;
let lv = eval_expr(l, context, model, target_category)?;
let rv = eval_expr(r, context, model, target_category)?;
Some(match op.as_str() {
"+" => lv + rv,
"-" => lv - rv,
"*" => lv * rv,
"/" => { if rv == 0.0 { return None; } lv / rv }
"^" => lv.powf(rv),
_ => return None,
Some(match op {
BinOp::Add => lv + rv,
BinOp::Sub => lv - rv,
BinOp::Mul => lv * rv,
BinOp::Div => { if rv == 0.0 { return None; } lv / rv }
BinOp::Pow => lv.powf(rv),
// Comparison operators are handled by eval_bool; reaching
// here means a comparison was used where a number is expected.
BinOp::Eq | BinOp::Ne | BinOp::Lt |
BinOp::Gt | BinOp::Le | BinOp::Ge => return None,
})
}
Expr::UnaryMinus(e) => Some(-eval_expr(e, context, model, target_category)?),
@ -247,18 +251,21 @@ impl Model {
model: &Model,
target_category: &str,
) -> Option<bool> {
use crate::formula::BinOp;
match expr {
Expr::BinOp(op, l, r) => {
let lv = eval_expr(l, context, model, target_category)?;
let rv = eval_expr(r, context, model, target_category)?;
Some(match op.as_str() {
"=" | "==" => (lv - rv).abs() < 1e-10,
"!=" => (lv - rv).abs() >= 1e-10,
"<" => lv < rv,
">" => lv > rv,
"<=" => lv <= rv,
">=" => lv >= rv,
_ => return None,
Some(match op {
BinOp::Eq => (lv - rv).abs() < 1e-10,
BinOp::Ne => (lv - rv).abs() >= 1e-10,
BinOp::Lt => lv < rv,
BinOp::Gt => lv > rv,
BinOp::Le => lv <= rv,
BinOp::Ge => lv >= rv,
// Arithmetic operators are not comparisons
BinOp::Add | BinOp::Sub | BinOp::Mul |
BinOp::Div | BinOp::Pow => return None,
})
}
_ => None,