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
14pub fn evaluate_expr(expr: &Expr, ctx: &mut EvalCtx<'_>) -> Value {
21 match expr {
22 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 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 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 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 Expr::FunctionCall { name, args, .. } => {
65 match ctx.registry.get(name) {
66 None => Value::Error(ErrorKind::Name),
67 Some(FunctionKind::Lazy(f)) => {
68 let f: functions::LazyFn = *f;
71 f(args, ctx)
72 }
73 Some(FunctionKind::Eager(f)) => {
74 let f: functions::EagerFn = *f;
75 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
91fn 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 Value::Error(_) | Value::Array(_) => 3,
101 }
102}
103
104fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
105 match op {
106 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 _ => unreachable!(),
123 };
124 if !result.is_finite() {
125 return Value::Error(ErrorKind::Num);
126 }
127 Value::Number(result)
128 }
129
130 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 BinaryOp::Eq | BinaryOp::Ne
139 | BinaryOp::Lt | BinaryOp::Gt
140 | BinaryOp::Le | BinaryOp::Ge => {
141 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
151fn 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 (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 _ => {
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 _ => unreachable!(),
177 }
178 }
179 }
180}
181
182fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
183 match ord {
184 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 _ => unreachable!(),
196 },
197 }
198}
199
200#[cfg(test)]
202mod tests;