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:
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user