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        // ── Immediately-invoked apply: LAMBDA(x, body)(arg) ────────────────
64        Expr::Apply { func, call_args, .. } => {
65            eval_apply(func, call_args, ctx)
66        }
67
68        // ── Function calls ──────────────────────────────────────────────────
69        Expr::FunctionCall { name, args, .. } => {
70            match ctx.registry.get(name) {
71                None => Value::Error(ErrorKind::Name),
72                Some(FunctionKind::Lazy(f)) => {
73                    // Copy the fn pointer out to avoid holding a borrow on ctx.registry
74                    // while also mutably borrowing ctx itself.
75                    let f: functions::LazyFn = *f;
76                    f(args, ctx)
77                }
78                Some(FunctionKind::Eager(f)) => {
79                    let f: functions::EagerFn = *f;
80                    // Evaluate all args; return first error encountered.
81                    let mut evaluated = Vec::with_capacity(args.len());
82                    for arg in args {
83                        let v = evaluate_expr(arg, ctx);
84                        if matches!(v, Value::Error(_)) {
85                            return v;
86                        }
87                        evaluated.push(v);
88                    }
89                    f(&evaluated)
90                }
91            }
92        }
93
94    }
95}
96
97/// Evaluate an immediately-invoked function application `func(call_args)`.
98fn eval_apply(func: &Expr, call_args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
99    let (lambda_params, body) = match func {
100        Expr::FunctionCall { name, args: lambda_args, .. } if name == "LAMBDA" => {
101            if lambda_args.is_empty() {
102                return Value::Error(ErrorKind::NA);
103            }
104            let param_count = lambda_args.len() - 1;
105            let mut params: Vec<String> = Vec::with_capacity(param_count);
106            for param_expr in &lambda_args[..param_count] {
107                match param_expr {
108                    Expr::Variable(n, _) => params.push(n.to_uppercase()),
109                    _ => return Value::Error(ErrorKind::Value),
110                }
111            }
112            let body = &lambda_args[lambda_args.len() - 1];
113            (params, body)
114        }
115        _ => return Value::Error(ErrorKind::Value),
116    };
117
118    if call_args.len() != lambda_params.len() {
119        return Value::Error(ErrorKind::NA);
120    }
121
122    let mut evaluated_args: Vec<Value> = Vec::with_capacity(call_args.len());
123    for arg in call_args {
124        let v = evaluate_expr(arg, ctx);
125        if matches!(v, Value::Error(_)) {
126            return v;
127        }
128        evaluated_args.push(v);
129    }
130
131    let mut saved: Vec<(String, Option<Value>)> = Vec::with_capacity(lambda_params.len());
132    for (param, val) in lambda_params.iter().zip(evaluated_args) {
133        let old = ctx.ctx.set(param.clone(), val);
134        saved.push((param.clone(), old));
135    }
136
137    let result = evaluate_expr(body, ctx);
138
139    for (name, old_val) in saved.into_iter().rev() {
140        match old_val {
141            Some(v) => { ctx.ctx.set(name, v); }
142            None    => { ctx.ctx.remove(&name); }
143        }
144    }
145
146    result
147}
148
149// ── Type ordering for cross-type comparisons (Excel semantics) ───────────────
150// Number < Text < Bool  (Empty counts as Number)
151fn type_rank(v: &Value) -> u8 {
152    match v {
153        Value::Number(_) | Value::Date(_) | Value::Empty => 0,
154        Value::Text(_)                  => 1,
155        Value::Bool(_)                  => 2,
156        // Error and Array cannot reach compare_values through the normal eval path
157        // (eval_binary guards against errors before calling compare_values).
158        Value::Error(_) | Value::Array(_) => 3,
159    }
160}
161
162fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
163    // ── Array broadcasting ───────────────────────────────────────────────────
164    match (&lv, &rv) {
165        (Value::Array(elems), _) => {
166            let result: Vec<Value> = elems
167                .iter()
168                .map(|e| eval_binary(op, e.clone(), rv.clone()))
169                .collect();
170            return Value::Array(result);
171        }
172        (_, Value::Array(elems)) => {
173            let result: Vec<Value> = elems
174                .iter()
175                .map(|e| eval_binary(op, lv.clone(), e.clone()))
176                .collect();
177            return Value::Array(result);
178        }
179        _ => {}
180    }
181    match op {
182        // ── Arithmetic ──────────────────────────────────────────────────────
183        BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Pow => {
184            let ln = match to_number(lv) { Ok(n) => n, Err(e) => return e };
185            let rn = match to_number(rv) { Ok(n) => n, Err(e) => return e };
186            let result = match op {
187                BinaryOp::Add => ln + rn,
188                BinaryOp::Sub => ln - rn,
189                BinaryOp::Mul => ln * rn,
190                BinaryOp::Div => {
191                    if rn == 0.0 {
192                        return Value::Error(ErrorKind::DivByZero);
193                    }
194                    ln / rn
195                }
196                BinaryOp::Pow => ln.powf(rn),
197                // Safety: outer match arm covers exactly Add|Sub|Mul|Div|Pow; Concat and comparison ops are handled separately.
198                _ => unreachable!(),
199            };
200            if !result.is_finite() {
201                return Value::Error(ErrorKind::Num);
202            }
203            Value::Number(result)
204        }
205
206        // ── Concatenation ───────────────────────────────────────────────────
207        BinaryOp::Concat => {
208            let ls = match to_string_val(lv) { Ok(s) => s, Err(e) => return e };
209            let rs = match to_string_val(rv) { Ok(s) => s, Err(e) => return e };
210            Value::Text(ls + &rs)
211        }
212
213        // ── Comparisons ─────────────────────────────────────────────────────
214        BinaryOp::Eq | BinaryOp::Ne
215        | BinaryOp::Lt | BinaryOp::Gt
216        | BinaryOp::Le | BinaryOp::Ge => {
217            // Error propagation: left side first.
218            if let Value::Error(_) = &lv { return lv; }
219            if let Value::Error(_) = &rv { return rv; }
220
221            let result = compare_values(op, &lv, &rv);
222            Value::Bool(result)
223        }
224    }
225}
226
227/// Compare two (non-error) values with Excel ordering semantics.
228fn compare_values(op: &BinaryOp, lv: &Value, rv: &Value) -> bool {
229    match (lv, rv) {
230        (Value::Number(a), Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
231        (Value::Date(a),   Value::Date(b))   => apply_cmp(op, a.partial_cmp(b)),
232        (Value::Date(a),   Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
233        (Value::Number(a), Value::Date(b))   => apply_cmp(op, a.partial_cmp(b)),
234        (Value::Text(a),   Value::Text(b))   => apply_cmp(op, Some(a.cmp(b))),
235        (Value::Bool(a),   Value::Bool(b))   => apply_cmp(op, Some(a.cmp(b))),
236        (Value::Empty,     Value::Empty)     => apply_cmp(op, Some(std::cmp::Ordering::Equal)),
237        // Empty acts as Number(0)
238        (Value::Empty, Value::Number(b))     => apply_cmp(op, 0.0f64.partial_cmp(b)),
239        (Value::Number(a), Value::Empty)     => apply_cmp(op, a.partial_cmp(&0.0f64)),
240        // Cross-type: use type rank
241        _ => {
242            let lr = type_rank(lv);
243            let rr = type_rank(rv);
244            match op {
245                BinaryOp::Eq => false,
246                BinaryOp::Ne => true,
247                BinaryOp::Lt => lr < rr,
248                BinaryOp::Gt => lr > rr,
249                BinaryOp::Le => lr <= rr,
250                BinaryOp::Ge => lr >= rr,
251                // Safety: outer match arm covers exactly Eq|Ne|Lt|Gt|Le|Ge; arithmetic and Concat ops are handled separately.
252                _ => unreachable!(),
253            }
254        }
255    }
256}
257
258fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
259    match ord {
260        // NaN: per Value::Number invariant this should not occur after the is_finite() guard;
261        // returning false matches Excel semantics if it somehow does.
262        None => false,
263        Some(o) => match op {
264            BinaryOp::Eq => o.is_eq(),
265            BinaryOp::Ne => o.is_ne(),
266            BinaryOp::Lt => o.is_lt(),
267            BinaryOp::Gt => o.is_gt(),
268            BinaryOp::Le => o.is_le(),
269            BinaryOp::Ge => o.is_ge(),
270            // 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).
271            _ => unreachable!(),
272        },
273    }
274}
275
276// ── Tests ────────────────────────────────────────────────────────────────────
277#[cfg(test)]
278mod tests;