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        // ── Array literals ──────────────────────────────────────────────────
54        Expr::Array(elems, _) => {
55            let mut values = Vec::with_capacity(elems.len());
56            for elem in elems {
57                let v = evaluate_expr(elem, ctx);
58                values.push(v);
59            }
60            Value::Array(values)
61        }
62
63        // ── Function calls ──────────────────────────────────────────────────
64        Expr::FunctionCall { name, args, .. } => {
65            match ctx.registry.get(name) {
66                None => Value::Error(ErrorKind::Name),
67                Some(FunctionKind::Lazy(f)) => {
68                    // Copy the fn pointer out to avoid holding a borrow on ctx.registry
69                    // while also mutably borrowing ctx itself.
70                    let f: functions::LazyFn = *f;
71                    f(args, ctx)
72                }
73                Some(FunctionKind::Eager(f)) => {
74                    let f: functions::EagerFn = *f;
75                    // Evaluate all args; return first error encountered.
76                    let mut evaluated = Vec::with_capacity(args.len());
77                    for arg in args {
78                        let v = evaluate_expr(arg, ctx);
79                        if matches!(v, Value::Error(_)) {
80                            return v;
81                        }
82                        evaluated.push(v);
83                    }
84                    f(&evaluated)
85                }
86            }
87        }
88    }
89}
90
91// ── Type ordering for cross-type comparisons (Excel semantics) ───────────────
92// Number < Text < Bool  (Empty counts as Number)
93fn type_rank(v: &Value) -> u8 {
94    match v {
95        Value::Number(_) | Value::Date(_) | Value::Empty => 0,
96        Value::Text(_)                  => 1,
97        Value::Bool(_)                  => 2,
98        // Error and Array cannot reach compare_values through the normal eval path
99        // (eval_binary guards against errors before calling compare_values).
100        Value::Error(_) | Value::Array(_) => 3,
101    }
102}
103
104fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
105    match op {
106        // ── Arithmetic ──────────────────────────────────────────────────────
107        BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Pow => {
108            let ln = match to_number(lv) { Ok(n) => n, Err(e) => return e };
109            let rn = match to_number(rv) { Ok(n) => n, Err(e) => return e };
110            let result = match op {
111                BinaryOp::Add => ln + rn,
112                BinaryOp::Sub => ln - rn,
113                BinaryOp::Mul => ln * rn,
114                BinaryOp::Div => {
115                    if rn == 0.0 {
116                        return Value::Error(ErrorKind::DivByZero);
117                    }
118                    ln / rn
119                }
120                BinaryOp::Pow => ln.powf(rn),
121                // Safety: outer match arm covers exactly Add|Sub|Mul|Div|Pow; Concat and comparison ops are handled separately.
122                _ => unreachable!(),
123            };
124            if !result.is_finite() {
125                return Value::Error(ErrorKind::Num);
126            }
127            Value::Number(result)
128        }
129
130        // ── Concatenation ───────────────────────────────────────────────────
131        BinaryOp::Concat => {
132            let ls = match to_string_val(lv) { Ok(s) => s, Err(e) => return e };
133            let rs = match to_string_val(rv) { Ok(s) => s, Err(e) => return e };
134            Value::Text(ls + &rs)
135        }
136
137        // ── Comparisons ─────────────────────────────────────────────────────
138        BinaryOp::Eq | BinaryOp::Ne
139        | BinaryOp::Lt | BinaryOp::Gt
140        | BinaryOp::Le | BinaryOp::Ge => {
141            // Error propagation: left side first.
142            if let Value::Error(_) = &lv { return lv; }
143            if let Value::Error(_) = &rv { return rv; }
144
145            let result = compare_values(op, &lv, &rv);
146            Value::Bool(result)
147        }
148    }
149}
150
151/// Compare two (non-error) values with Excel ordering semantics.
152fn compare_values(op: &BinaryOp, lv: &Value, rv: &Value) -> bool {
153    match (lv, rv) {
154        (Value::Number(a), Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
155        (Value::Date(a),   Value::Date(b))   => apply_cmp(op, a.partial_cmp(b)),
156        (Value::Date(a),   Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
157        (Value::Number(a), Value::Date(b))   => apply_cmp(op, a.partial_cmp(b)),
158        (Value::Text(a),   Value::Text(b))   => apply_cmp(op, Some(a.cmp(b))),
159        (Value::Bool(a),   Value::Bool(b))   => apply_cmp(op, Some(a.cmp(b))),
160        (Value::Empty,     Value::Empty)     => apply_cmp(op, Some(std::cmp::Ordering::Equal)),
161        // Empty acts as Number(0)
162        (Value::Empty, Value::Number(b))     => apply_cmp(op, 0.0f64.partial_cmp(b)),
163        (Value::Number(a), Value::Empty)     => apply_cmp(op, a.partial_cmp(&0.0f64)),
164        // Cross-type: use type rank
165        _ => {
166            let lr = type_rank(lv);
167            let rr = type_rank(rv);
168            match op {
169                BinaryOp::Eq => false,
170                BinaryOp::Ne => true,
171                BinaryOp::Lt => lr < rr,
172                BinaryOp::Gt => lr > rr,
173                BinaryOp::Le => lr <= rr,
174                BinaryOp::Ge => lr >= rr,
175                // Safety: outer match arm covers exactly Eq|Ne|Lt|Gt|Le|Ge; arithmetic and Concat ops are handled separately.
176                _ => unreachable!(),
177            }
178        }
179    }
180}
181
182fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
183    match ord {
184        // NaN: per Value::Number invariant this should not occur after the is_finite() guard;
185        // returning false matches Excel semantics if it somehow does.
186        None => false,
187        Some(o) => match op {
188            BinaryOp::Eq => o.is_eq(),
189            BinaryOp::Ne => o.is_ne(),
190            BinaryOp::Lt => o.is_lt(),
191            BinaryOp::Gt => o.is_gt(),
192            BinaryOp::Le => o.is_le(),
193            BinaryOp::Ge => o.is_ge(),
194            // 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).
195            _ => unreachable!(),
196        },
197    }
198}
199
200// ── Tests ────────────────────────────────────────────────────────────────────
201#[cfg(test)]
202mod tests;