pub mod context;
pub mod coercion;
pub mod functions;
pub use context::Context;
pub use functions::{EvalCtx, FunctionMeta, Registry};
use crate::parser::ast::{BinaryOp, Expr, UnaryOp};
use crate::types::{ErrorKind, Value};
use coercion::{to_number, to_string_val};
use functions::FunctionKind;
pub fn evaluate_expr(expr: &Expr, ctx: &mut EvalCtx<'_>) -> Value {
match expr {
Expr::Number(n, _) => {
if n.is_finite() {
Value::Number(*n)
} else {
Value::Error(ErrorKind::Num)
}
}
Expr::Text(s, _) => Value::Text(s.clone()),
Expr::Bool(b, _) => Value::Bool(*b),
Expr::Variable(name, _) => ctx.ctx.get(name),
Expr::UnaryOp { op, operand, .. } => {
let val = evaluate_expr(operand, ctx);
match to_number(val) {
Err(e) => e,
Ok(n) => match op {
UnaryOp::Neg => Value::Number(-n),
UnaryOp::Percent => Value::Number(n / 100.0),
},
}
}
Expr::BinaryOp { op, left, right, .. } => {
let lv = evaluate_expr(left, ctx);
let rv = evaluate_expr(right, ctx);
eval_binary(op, lv, rv)
}
Expr::Array(elems, _) => {
let mut values = Vec::with_capacity(elems.len());
for elem in elems {
let v = evaluate_expr(elem, ctx);
values.push(v);
}
Value::Array(values)
}
Expr::Apply { func, call_args, .. } => {
eval_apply(func, call_args, ctx)
}
Expr::FunctionCall { name, args, .. } => {
match ctx.registry.get(name) {
None => Value::Error(ErrorKind::Name),
Some(FunctionKind::Lazy(f)) => {
let f: functions::LazyFn = *f;
f(args, ctx)
}
Some(FunctionKind::Eager(f)) => {
let f: functions::EagerFn = *f;
let mut evaluated = Vec::with_capacity(args.len());
for arg in args {
let v = evaluate_expr(arg, ctx);
if matches!(v, Value::Error(_)) {
return v;
}
evaluated.push(v);
}
f(&evaluated)
}
}
}
}
}
fn eval_apply(func: &Expr, call_args: &[Expr], ctx: &mut EvalCtx<'_>) -> Value {
let (lambda_params, body) = match func {
Expr::FunctionCall { name, args: lambda_args, .. } if name == "LAMBDA" => {
if lambda_args.is_empty() {
return Value::Error(ErrorKind::NA);
}
let param_count = lambda_args.len() - 1;
let mut params: Vec<String> = Vec::with_capacity(param_count);
for param_expr in &lambda_args[..param_count] {
match param_expr {
Expr::Variable(n, _) => params.push(n.to_uppercase()),
_ => return Value::Error(ErrorKind::Value),
}
}
let body = &lambda_args[lambda_args.len() - 1];
(params, body)
}
_ => return Value::Error(ErrorKind::Value),
};
if call_args.len() != lambda_params.len() {
return Value::Error(ErrorKind::NA);
}
let mut evaluated_args: Vec<Value> = Vec::with_capacity(call_args.len());
for arg in call_args {
let v = evaluate_expr(arg, ctx);
if matches!(v, Value::Error(_)) {
return v;
}
evaluated_args.push(v);
}
let mut saved: Vec<(String, Option<Value>)> = Vec::with_capacity(lambda_params.len());
for (param, val) in lambda_params.iter().zip(evaluated_args) {
let old = ctx.ctx.set(param.clone(), val);
saved.push((param.clone(), old));
}
let result = evaluate_expr(body, ctx);
for (name, old_val) in saved.into_iter().rev() {
match old_val {
Some(v) => { ctx.ctx.set(name, v); }
None => { ctx.ctx.remove(&name); }
}
}
result
}
fn type_rank(v: &Value) -> u8 {
match v {
Value::Number(_) | Value::Date(_) | Value::Empty => 0,
Value::Text(_) => 1,
Value::Bool(_) => 2,
Value::Error(_) | Value::Array(_) => 3,
}
}
fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
match (&lv, &rv) {
(Value::Array(elems), _) => {
let result: Vec<Value> = elems
.iter()
.map(|e| eval_binary(op, e.clone(), rv.clone()))
.collect();
return Value::Array(result);
}
(_, Value::Array(elems)) => {
let result: Vec<Value> = elems
.iter()
.map(|e| eval_binary(op, lv.clone(), e.clone()))
.collect();
return Value::Array(result);
}
_ => {}
}
match op {
BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Pow => {
let ln = match to_number(lv) { Ok(n) => n, Err(e) => return e };
let rn = match to_number(rv) { Ok(n) => n, Err(e) => return e };
let result = match op {
BinaryOp::Add => ln + rn,
BinaryOp::Sub => ln - rn,
BinaryOp::Mul => ln * rn,
BinaryOp::Div => {
if rn == 0.0 {
return Value::Error(ErrorKind::DivByZero);
}
ln / rn
}
BinaryOp::Pow => ln.powf(rn),
_ => unreachable!(),
};
if !result.is_finite() {
return Value::Error(ErrorKind::Num);
}
Value::Number(result)
}
BinaryOp::Concat => {
let ls = match to_string_val(lv) { Ok(s) => s, Err(e) => return e };
let rs = match to_string_val(rv) { Ok(s) => s, Err(e) => return e };
Value::Text(ls + &rs)
}
BinaryOp::Eq | BinaryOp::Ne
| BinaryOp::Lt | BinaryOp::Gt
| BinaryOp::Le | BinaryOp::Ge => {
if let Value::Error(_) = &lv { return lv; }
if let Value::Error(_) = &rv { return rv; }
let result = compare_values(op, &lv, &rv);
Value::Bool(result)
}
}
}
fn compare_values(op: &BinaryOp, lv: &Value, rv: &Value) -> bool {
match (lv, rv) {
(Value::Number(a), Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
(Value::Date(a), Value::Date(b)) => apply_cmp(op, a.partial_cmp(b)),
(Value::Date(a), Value::Number(b)) => apply_cmp(op, a.partial_cmp(b)),
(Value::Number(a), Value::Date(b)) => apply_cmp(op, a.partial_cmp(b)),
(Value::Text(a), Value::Text(b)) => apply_cmp(op, Some(a.cmp(b))),
(Value::Bool(a), Value::Bool(b)) => apply_cmp(op, Some(a.cmp(b))),
(Value::Empty, Value::Empty) => apply_cmp(op, Some(std::cmp::Ordering::Equal)),
(Value::Empty, Value::Number(b)) => apply_cmp(op, 0.0f64.partial_cmp(b)),
(Value::Number(a), Value::Empty) => apply_cmp(op, a.partial_cmp(&0.0f64)),
_ => {
let lr = type_rank(lv);
let rr = type_rank(rv);
match op {
BinaryOp::Eq => false,
BinaryOp::Ne => true,
BinaryOp::Lt => lr < rr,
BinaryOp::Gt => lr > rr,
BinaryOp::Le => lr <= rr,
BinaryOp::Ge => lr >= rr,
_ => unreachable!(),
}
}
}
}
fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
match ord {
None => false,
Some(o) => match op {
BinaryOp::Eq => o.is_eq(),
BinaryOp::Ne => o.is_ne(),
BinaryOp::Lt => o.is_lt(),
BinaryOp::Gt => o.is_gt(),
BinaryOp::Le => o.is_le(),
BinaryOp::Ge => o.is_ge(),
_ => unreachable!(),
},
}
}
#[cfg(test)]
mod tests;