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::FunctionCall { name, args, .. } => {
55 match ctx.registry.get(name) {
56 None => Value::Error(ErrorKind::Name),
57 Some(FunctionKind::Lazy(f)) => {
58 let f: functions::LazyFn = *f;
61 f(args, ctx)
62 }
63 Some(FunctionKind::Eager(f)) => {
64 let f: functions::EagerFn = *f;
65 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
81fn type_rank(v: &Value) -> u8 {
84 match v {
85 Value::Number(_) | Value::Empty => 0,
86 Value::Text(_) => 1,
87 Value::Bool(_) => 2,
88 Value::Error(_) | Value::Array(_) => 3,
91 }
92}
93
94fn eval_binary(op: &BinaryOp, lv: Value, rv: Value) -> Value {
95 match op {
96 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 _ => unreachable!(),
113 };
114 if !result.is_finite() {
115 return Value::Error(ErrorKind::Num);
116 }
117 Value::Number(result)
118 }
119
120 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 BinaryOp::Eq | BinaryOp::Ne
129 | BinaryOp::Lt | BinaryOp::Gt
130 | BinaryOp::Le | BinaryOp::Ge => {
131 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
141fn 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 (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 _ => {
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 _ => unreachable!(),
164 }
165 }
166 }
167}
168
169fn apply_cmp(op: &BinaryOp, ord: Option<std::cmp::Ordering>) -> bool {
170 match ord {
171 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 _ => unreachable!(),
183 },
184 }
185}
186
187#[cfg(test)]
189mod tests;