Skip to main content

ganit_core/eval/
mod.rs

1pub mod context;
2pub mod coercion;
3pub mod functions;
4
5pub use context::Context;
6pub use functions::{EvalCtx, FunctionMeta, Registry};
7
8use crate::parser::ast::{BinaryOp, Expr, UnaryOp};
9use crate::types::{ErrorKind, Value};
10
11use coercion::{to_number, to_string_val};
12use functions::FunctionKind;
13
14/// Walk an expression tree and produce a [`Value`].
15///
16/// Variables are resolved from `ctx.ctx`; functions are dispatched through
17/// `ctx.registry`. Eager functions receive pre-evaluated arguments; lazy
18/// functions (e.g. `IF`) receive raw [`Expr`] nodes and control their own
19/// evaluation order.
20pub fn evaluate_expr(expr: &Expr, ctx: &mut EvalCtx<'_>) -> Value {
21    match expr {
22        // ── Leaf nodes ──────────────────────────────────────────────────────
23        Expr::Number(n, _) => {
24            if n.is_finite() {
25                Value::Number(*n)
26            } else {
27                Value::Error(ErrorKind::Num)
28            }
29        }
30        Expr::Text(s, _)   => Value::Text(s.clone()),
31        Expr::Bool(b, _)   => Value::Bool(*b),
32        Expr::Variable(name, _) => ctx.ctx.get(name),
33
34        // ── Unary ops ───────────────────────────────────────────────────────
35        Expr::UnaryOp { op, operand, .. } => {
36            let val = evaluate_expr(operand, ctx);
37            match to_number(val) {
38                Err(e) => e,
39                Ok(n)  => match op {
40                    UnaryOp::Neg     => Value::Number(-n),
41                    UnaryOp::Percent => Value::Number(n / 100.0),
42                },
43            }
44        }
45
46        // ── Binary ops ──────────────────────────────────────────────────────
47        Expr::BinaryOp { op, left, right, .. } => {
48            let lv = evaluate_expr(left, ctx);
49            let rv = evaluate_expr(right, ctx);
50            eval_binary(op, lv, rv)
51        }
52
53        // ── Function calls ──────────────────────────────────────────────────
54        Expr::FunctionCall { name, args, .. } => {
55            match ctx.registry.get(name) {
56                None => Value::Error(ErrorKind::Name),
57                Some(FunctionKind::Lazy(f)) => {
58                    // Copy the fn pointer out to avoid holding a borrow on ctx.registry
59                    // while also mutably borrowing ctx itself.
60                    let f: functions::LazyFn = *f;
61                    f(args, ctx)
62                }
63                Some(FunctionKind::Eager(f)) => {
64                    let f: functions::EagerFn = *f;
65                    // Evaluate all args; return first error encountered.
66                    let mut evaluated = Vec::with_capacity(args.len());
67                    for arg in args {
68                        let v = evaluate_expr(arg, ctx);
69                        if matches!(v, Value::Error(_)) {
70                            return v;
71                        }
72                        evaluated.push(v);
73                    }
74                    f(&evaluated)
75                }
76            }
77        }
78    }
79}
80
81// ── Type ordering for cross-type comparisons (Excel semantics) ───────────────
82// Number < Text < Bool  (Empty counts as Number)
83fn type_rank(v: &Value) -> u8 {
84    match v {
85        Value::Number(_) | Value::Empty => 0,
86        Value::Text(_)                  => 1,
87        Value::Bool(_)                  => 2,
88        // Error and Array cannot reach compare_values through the normal eval path
89        // (eval_binary guards against errors before calling compare_values).
90        Value::Error(_) | Value::Array(_) => 3,
91    }
92}
93
94fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
95    match op {
96        // ── Arithmetic ──────────────────────────────────────────────────────
97        BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Pow => {
98            let ln = match to_number(lv) { Ok(n) => n, Err(e) => return e };
99            let rn = match to_number(rv) { Ok(n) => n, Err(e) => return e };
100            let result = match op {
101                BinaryOp::Add => ln + rn,
102                BinaryOp::Sub => ln - rn,
103                BinaryOp::Mul => ln * rn,
104                BinaryOp::Div => {
105                    if rn == 0.0 {
106                        return Value::Error(ErrorKind::DivByZero);
107                    }
108                    ln / rn
109                }
110                BinaryOp::Pow => ln.powf(rn),
111                // Safety: outer match arm covers exactly Add|Sub|Mul|Div|Pow; Concat and comparison ops are handled separately.
112                _ => unreachable!(),
113            };
114            if !result.is_finite() {
115                return Value::Error(ErrorKind::Num);
116            }
117            Value::Number(result)
118        }
119
120        // ── Concatenation ───────────────────────────────────────────────────
121        BinaryOp::Concat => {
122            let ls = match to_string_val(lv) { Ok(s) => s, Err(e) => return e };
123            let rs = match to_string_val(rv) { Ok(s) => s, Err(e) => return e };
124            Value::Text(ls + &rs)
125        }
126
127        // ── Comparisons ─────────────────────────────────────────────────────
128        BinaryOp::Eq | BinaryOp::Ne
129        | BinaryOp::Lt | BinaryOp::Gt
130        | BinaryOp::Le | BinaryOp::Ge => {
131            // Error propagation: left side first.
132            if let Value::Error(_) = &lv { return lv; }
133            if let Value::Error(_) = &rv { return rv; }
134
135            let result = compare_values(op, &lv, &rv);
136            Value::Bool(result)
137        }
138    }
139}
140
141/// Compare two (non-error) values with Excel ordering semantics.
142fn compare_values(op: &BinaryOp, lv: &Value, rv: &Value) -> bool {
143    match (lv, rv) {
144        (Value::Number(a), Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
145        (Value::Text(a),   Value::Text(b))   => apply_cmp(op, Some(a.cmp(b))),
146        (Value::Bool(a),   Value::Bool(b))   => apply_cmp(op, Some(a.cmp(b))),
147        (Value::Empty,     Value::Empty)     => apply_cmp(op, Some(std::cmp::Ordering::Equal)),
148        // Empty acts as Number(0)
149        (Value::Empty, Value::Number(b))     => apply_cmp(op, 0.0f64.partial_cmp(b)),
150        (Value::Number(a), Value::Empty)     => apply_cmp(op, a.partial_cmp(&0.0f64)),
151        // Cross-type: use type rank
152        _ => {
153            let lr = type_rank(lv);
154            let rr = type_rank(rv);
155            match op {
156                BinaryOp::Eq => false,
157                BinaryOp::Ne => true,
158                BinaryOp::Lt => lr < rr,
159                BinaryOp::Gt => lr > rr,
160                BinaryOp::Le => lr <= rr,
161                BinaryOp::Ge => lr >= rr,
162                // Safety: outer match arm covers exactly Eq|Ne|Lt|Gt|Le|Ge; arithmetic and Concat ops are handled separately.
163                _ => unreachable!(),
164            }
165        }
166    }
167}
168
169fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
170    match ord {
171        // NaN: per Value::Number invariant this should not occur after the is_finite() guard;
172        // returning false matches Excel semantics if it somehow does.
173        None => false,
174        Some(o) => match op {
175            BinaryOp::Eq => o.is_eq(),
176            BinaryOp::Ne => o.is_ne(),
177            BinaryOp::Lt => o.is_lt(),
178            BinaryOp::Gt => o.is_gt(),
179            BinaryOp::Le => o.is_le(),
180            BinaryOp::Ge => o.is_ge(),
181            // Safety: apply_cmp is only called from compare_values which is only called from eval_binary's comparison arm (Eq|Ne|Lt|Gt|Le|Ge).
182            _ => unreachable!(),
183        },
184    }
185}
186
187// ── Tests ────────────────────────────────────────────────────────────────────
188#[cfg(test)]
189mod tests;