dampen_core/expr/
eval.rs

1//! Expression evaluator for binding expressions
2
3use crate::binding::{BindingValue, UiBindable};
4use crate::expr::error::{BindingError, BindingErrorKind};
5use crate::expr::{
6    BinaryOp, BinaryOpExpr, ConditionalExpr, Expr, FieldAccessExpr, LiteralExpr, MethodCallExpr,
7    UnaryOp, UnaryOpExpr,
8};
9
10/// Evaluate an expression against a model
11pub fn evaluate_expr(expr: &Expr, model: &dyn UiBindable) -> Result<BindingValue, BindingError> {
12    match expr {
13        Expr::FieldAccess(field_expr) => evaluate_field_access(field_expr, model),
14        Expr::MethodCall(method_expr) => evaluate_method_call(method_expr, model),
15        Expr::BinaryOp(binary_expr) => evaluate_binary_op(binary_expr, model),
16        Expr::UnaryOp(unary_expr) => evaluate_unary_op(unary_expr, model),
17        Expr::Conditional(conditional_expr) => evaluate_conditional(conditional_expr, model),
18        Expr::Literal(literal_expr) => Ok(evaluate_literal(literal_expr)),
19    }
20}
21
22/// Evaluate field access: `counter` or `user.name`
23fn evaluate_field_access(
24    field_expr: &FieldAccessExpr,
25    model: &dyn UiBindable,
26) -> Result<BindingValue, BindingError> {
27    let path: Vec<&str> = field_expr.path.iter().map(|s| s.as_str()).collect();
28
29    model.get_field(&path).ok_or_else(|| {
30        let field_name = field_expr.path.join(".");
31        BindingError {
32            kind: BindingErrorKind::UnknownField,
33            message: format!("Field '{}' not found", field_name),
34            span: crate::ir::span::Span::new(0, 0, 0, 0), // Will be set by caller
35            suggestion: None,
36        }
37    })
38}
39
40/// Evaluate method call: `items.len()` or `name.to_uppercase()`
41fn evaluate_method_call(
42    method_expr: &MethodCallExpr,
43    model: &dyn UiBindable,
44) -> Result<BindingValue, BindingError> {
45    let receiver = evaluate_expr(&method_expr.receiver, model)?;
46    let method = &method_expr.method;
47
48    // Evaluate arguments (currently unused but evaluated for future compatibility)
49    let _args: Vec<BindingValue> = method_expr
50        .args
51        .iter()
52        .map(|arg| evaluate_expr(arg, model))
53        .collect::<Result<Vec<_>, _>>()?;
54
55    match (receiver.clone(), method.as_str()) {
56        // String methods
57        (BindingValue::String(s), "len") => Ok(BindingValue::Integer(s.len() as i64)),
58        (BindingValue::String(s), "to_uppercase") => Ok(BindingValue::String(s.to_uppercase())),
59        (BindingValue::String(s), "to_lowercase") => Ok(BindingValue::String(s.to_lowercase())),
60        (BindingValue::String(s), "trim") => Ok(BindingValue::String(s.trim().to_string())),
61
62        // List methods
63        (BindingValue::List(l), "len") => Ok(BindingValue::Integer(l.len() as i64)),
64        (BindingValue::List(l), "is_empty") => Ok(BindingValue::Bool(l.is_empty())),
65
66        // Integer methods
67        (BindingValue::Integer(i), "to_string") => Ok(BindingValue::String(i.to_string())),
68
69        // Float methods
70        (BindingValue::Float(f), "to_string") => Ok(BindingValue::String(f.to_string())),
71        (BindingValue::Float(f), "round") => Ok(BindingValue::Float(f.round())),
72        (BindingValue::Float(f), "floor") => Ok(BindingValue::Float(f.floor())),
73        (BindingValue::Float(f), "ceil") => Ok(BindingValue::Float(f.ceil())),
74
75        // Bool methods
76        (BindingValue::Bool(b), "to_string") => Ok(BindingValue::String(b.to_string())),
77
78        _ => Err(BindingError {
79            kind: BindingErrorKind::UnknownMethod,
80            message: format!("Method '{}' not supported on {:?}", method, receiver),
81            span: crate::ir::span::Span::new(0, 0, 0, 0),
82            suggestion: None,
83        }),
84    }
85}
86
87/// Evaluate binary operation: `a + b`, `x > 0`, etc.
88fn evaluate_binary_op(
89    binary_expr: &BinaryOpExpr,
90    model: &dyn UiBindable,
91) -> Result<BindingValue, BindingError> {
92    let left = evaluate_expr(&binary_expr.left, model)?;
93    let right = evaluate_expr(&binary_expr.right, model)?;
94
95    match binary_expr.op {
96        // Arithmetic
97        BinaryOp::Add => match (left, right) {
98            (BindingValue::Integer(a), BindingValue::Integer(b)) => {
99                Ok(BindingValue::Integer(a + b))
100            }
101            (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a + b)),
102            (BindingValue::String(a), BindingValue::String(b)) => Ok(BindingValue::String(a + &b)),
103            _ => Err(BindingError {
104                kind: BindingErrorKind::InvalidOperation,
105                message: "Cannot add these types".to_string(),
106                span: crate::ir::span::Span::new(0, 0, 0, 0),
107                suggestion: None,
108            }),
109        },
110        BinaryOp::Sub => match (left, right) {
111            (BindingValue::Integer(a), BindingValue::Integer(b)) => {
112                Ok(BindingValue::Integer(a - b))
113            }
114            (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a - b)),
115            _ => Err(BindingError {
116                kind: BindingErrorKind::InvalidOperation,
117                message: "Cannot subtract these types".to_string(),
118                span: crate::ir::span::Span::new(0, 0, 0, 0),
119                suggestion: None,
120            }),
121        },
122        BinaryOp::Mul => match (left, right) {
123            (BindingValue::Integer(a), BindingValue::Integer(b)) => {
124                Ok(BindingValue::Integer(a * b))
125            }
126            (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a * b)),
127            _ => Err(BindingError {
128                kind: BindingErrorKind::InvalidOperation,
129                message: "Cannot multiply these types".to_string(),
130                span: crate::ir::span::Span::new(0, 0, 0, 0),
131                suggestion: None,
132            }),
133        },
134        BinaryOp::Div => match (left, right) {
135            (BindingValue::Integer(a), BindingValue::Integer(b)) if b != 0 => {
136                Ok(BindingValue::Integer(a / b))
137            }
138            (BindingValue::Float(a), BindingValue::Float(b)) if b != 0.0 => {
139                Ok(BindingValue::Float(a / b))
140            }
141            _ => Err(BindingError {
142                kind: BindingErrorKind::InvalidOperation,
143                message: "Cannot divide these types or division by zero".to_string(),
144                span: crate::ir::span::Span::new(0, 0, 0, 0),
145                suggestion: None,
146            }),
147        },
148
149        // Comparison
150        BinaryOp::Eq => Ok(BindingValue::Bool(left == right)),
151        BinaryOp::Ne => Ok(BindingValue::Bool(left != right)),
152        BinaryOp::Lt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
153            a < b
154        }))),
155        BinaryOp::Le => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
156            a <= b
157        }))),
158        BinaryOp::Gt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
159            a > b
160        }))),
161        BinaryOp::Ge => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
162            a >= b
163        }))),
164
165        // Logical
166        BinaryOp::And => {
167            let left_bool = left.to_bool();
168            let right_bool = right.to_bool();
169            Ok(BindingValue::Bool(left_bool && right_bool))
170        }
171        BinaryOp::Or => {
172            let left_bool = left.to_bool();
173            let right_bool = right.to_bool();
174            Ok(BindingValue::Bool(left_bool || right_bool))
175        }
176    }
177}
178
179/// Helper for comparison operations
180fn compare_values<F>(left: &BindingValue, right: &BindingValue, cmp: F) -> bool
181where
182    F: Fn(f64, f64) -> bool,
183{
184    match (left, right) {
185        (BindingValue::Integer(a), BindingValue::Integer(b)) => cmp(*a as f64, *b as f64),
186        (BindingValue::Float(a), BindingValue::Float(b)) => cmp(*a, *b),
187        (BindingValue::String(a), BindingValue::String(b)) => cmp(a.len() as f64, b.len() as f64),
188        (BindingValue::List(a), BindingValue::List(b)) => cmp(a.len() as f64, b.len() as f64),
189        _ => false,
190    }
191}
192
193/// Evaluate unary operation: `!valid` or `-offset`
194fn evaluate_unary_op(
195    unary_expr: &UnaryOpExpr,
196    model: &dyn UiBindable,
197) -> Result<BindingValue, BindingError> {
198    let operand = evaluate_expr(&unary_expr.operand, model)?;
199
200    match unary_expr.op {
201        UnaryOp::Not => Ok(BindingValue::Bool(!operand.to_bool())),
202        UnaryOp::Neg => match operand {
203            BindingValue::Integer(i) => Ok(BindingValue::Integer(-i)),
204            BindingValue::Float(f) => Ok(BindingValue::Float(-f)),
205            _ => Err(BindingError {
206                kind: BindingErrorKind::InvalidOperation,
207                message: "Cannot negate this type".to_string(),
208                span: crate::ir::span::Span::new(0, 0, 0, 0),
209                suggestion: None,
210            }),
211        },
212    }
213}
214
215/// Evaluate conditional: `if condition then a else b`
216fn evaluate_conditional(
217    conditional_expr: &ConditionalExpr,
218    model: &dyn UiBindable,
219) -> Result<BindingValue, BindingError> {
220    let condition = evaluate_expr(&conditional_expr.condition, model)?;
221
222    if condition.to_bool() {
223        evaluate_expr(&conditional_expr.then_branch, model)
224    } else {
225        evaluate_expr(&conditional_expr.else_branch, model)
226    }
227}
228
229/// Evaluate literal value
230fn evaluate_literal(literal_expr: &LiteralExpr) -> BindingValue {
231    match literal_expr {
232        LiteralExpr::String(s) => BindingValue::String(s.clone()),
233        LiteralExpr::Integer(i) => BindingValue::Integer(*i),
234        LiteralExpr::Float(f) => BindingValue::Float(*f),
235        LiteralExpr::Bool(b) => BindingValue::Bool(*b),
236    }
237}
238
239/// Evaluate a binding expression with span information
240pub fn evaluate_binding_expr(
241    binding_expr: &crate::expr::BindingExpr,
242    model: &dyn UiBindable,
243) -> Result<BindingValue, BindingError> {
244    match evaluate_expr(&binding_expr.expr, model) {
245        Ok(result) => Ok(result),
246        Err(mut err) => {
247            err.span = binding_expr.span;
248            Err(err)
249        }
250    }
251}
252
253/// Evaluate formatted string with interpolation: `"Total: {count}"`
254pub fn evaluate_formatted(
255    parts: &[crate::ir::InterpolatedPart],
256    model: &dyn UiBindable,
257) -> Result<String, BindingError> {
258    let mut result = String::new();
259
260    for part in parts {
261        match part {
262            crate::ir::InterpolatedPart::Literal(literal) => {
263                result.push_str(literal);
264            }
265            crate::ir::InterpolatedPart::Binding(binding_expr) => {
266                let value = evaluate_binding_expr(binding_expr, model)?;
267                result.push_str(&value.to_display_string());
268            }
269        }
270    }
271
272    Ok(result)
273}