1use crate::binding::{BindingValue, UiBindable};
7use crate::expr::error::{BindingError, BindingErrorKind};
8use crate::expr::{
9 BinaryOp, BinaryOpExpr, ConditionalExpr, Expr, FieldAccessExpr, LiteralExpr, MethodCallExpr,
10 SharedFieldAccessExpr, UnaryOp, UnaryOpExpr,
11};
12
13pub fn evaluate_expr(expr: &Expr, model: &dyn UiBindable) -> Result<BindingValue, BindingError> {
18 evaluate_expr_with_shared(expr, model, None)
19}
20
21pub fn evaluate_expr_with_shared(
36 expr: &Expr,
37 model: &dyn UiBindable,
38 shared: Option<&dyn UiBindable>,
39) -> Result<BindingValue, BindingError> {
40 match expr {
41 Expr::FieldAccess(field_expr) => evaluate_field_access(field_expr, model),
42 Expr::SharedFieldAccess(shared_expr) => evaluate_shared_field_access(shared_expr, shared),
43 Expr::MethodCall(method_expr) => evaluate_method_call(method_expr, model, shared),
44 Expr::BinaryOp(binary_expr) => evaluate_binary_op(binary_expr, model, shared),
45 Expr::UnaryOp(unary_expr) => evaluate_unary_op(unary_expr, model, shared),
46 Expr::Conditional(conditional_expr) => {
47 evaluate_conditional(conditional_expr, model, shared)
48 }
49 Expr::Literal(literal_expr) => Ok(evaluate_literal(literal_expr)),
50 }
51}
52
53fn evaluate_field_access(
55 field_expr: &FieldAccessExpr,
56 model: &dyn UiBindable,
57) -> Result<BindingValue, BindingError> {
58 let path: Vec<&str> = field_expr.path.iter().map(|s| s.as_str()).collect();
59
60 model.get_field(&path).ok_or_else(|| {
61 let field_name = field_expr.path.join(".");
62 BindingError {
63 kind: BindingErrorKind::UnknownField,
64 message: format!("Field '{}' not found", field_name),
65 span: crate::ir::span::Span::new(0, 0, 0, 0),
66 suggestion: None,
67 }
68 })
69}
70
71fn evaluate_shared_field_access(
73 shared_expr: &SharedFieldAccessExpr,
74 shared: Option<&dyn UiBindable>,
75) -> Result<BindingValue, BindingError> {
76 let Some(shared_ctx) = shared else {
77 return Ok(BindingValue::String(String::new()));
78 };
79
80 let path: Vec<&str> = shared_expr.path.iter().map(|s| s.as_str()).collect();
81
82 shared_ctx.get_field(&path).ok_or_else(|| {
83 let field_name = format!("shared.{}", shared_expr.path.join("."));
84 BindingError {
85 kind: BindingErrorKind::UnknownField,
86 message: format!("Shared field '{}' not found", field_name),
87 span: crate::ir::span::Span::new(0, 0, 0, 0),
88 suggestion: None,
89 }
90 })
91}
92
93fn evaluate_method_call(
95 method_expr: &MethodCallExpr,
96 model: &dyn UiBindable,
97 shared: Option<&dyn UiBindable>,
98) -> Result<BindingValue, BindingError> {
99 let receiver = evaluate_expr_with_shared(&method_expr.receiver, model, shared)?;
100 let method = &method_expr.method;
101
102 let _args: Vec<BindingValue> = method_expr
103 .args
104 .iter()
105 .map(|arg| evaluate_expr_with_shared(arg, model, shared))
106 .collect::<Result<Vec<_>, _>>()?;
107
108 evaluate_method(receiver, method)
109}
110
111fn evaluate_method(receiver: BindingValue, method: &str) -> Result<BindingValue, BindingError> {
113 match (receiver.clone(), method) {
114 (BindingValue::String(s), "len") => Ok(BindingValue::Integer(s.len() as i64)),
115 (BindingValue::String(s), "to_uppercase") => Ok(BindingValue::String(s.to_uppercase())),
116 (BindingValue::String(s), "to_lowercase") => Ok(BindingValue::String(s.to_lowercase())),
117 (BindingValue::String(s), "trim") => Ok(BindingValue::String(s.trim().to_string())),
118 (BindingValue::List(l), "len") => Ok(BindingValue::Integer(l.len() as i64)),
119 (BindingValue::List(l), "is_empty") => Ok(BindingValue::Bool(l.is_empty())),
120 (BindingValue::Integer(i), "to_string") => Ok(BindingValue::String(i.to_string())),
121 (BindingValue::Float(f), "to_string") => Ok(BindingValue::String(f.to_string())),
122 (BindingValue::Float(f), "round") => Ok(BindingValue::Float(f.round())),
123 (BindingValue::Float(f), "floor") => Ok(BindingValue::Float(f.floor())),
124 (BindingValue::Float(f), "ceil") => Ok(BindingValue::Float(f.ceil())),
125 (BindingValue::Bool(b), "to_string") => Ok(BindingValue::String(b.to_string())),
126 _ => Err(BindingError {
127 kind: BindingErrorKind::UnknownMethod,
128 message: format!("Method '{}' not supported on {:?}", method, receiver),
129 span: crate::ir::span::Span::new(0, 0, 0, 0),
130 suggestion: None,
131 }),
132 }
133}
134
135fn evaluate_binary_op(
137 binary_expr: &BinaryOpExpr,
138 model: &dyn UiBindable,
139 shared: Option<&dyn UiBindable>,
140) -> Result<BindingValue, BindingError> {
141 let left = evaluate_expr_with_shared(&binary_expr.left, model, shared)?;
142 let right = evaluate_expr_with_shared(&binary_expr.right, model, shared)?;
143
144 match binary_expr.op {
145 BinaryOp::Add => evaluate_add(left, right),
146 BinaryOp::Sub => evaluate_sub(left, right),
147 BinaryOp::Mul => evaluate_mul(left, right),
148 BinaryOp::Div => evaluate_div(left, right),
149 BinaryOp::Eq => Ok(BindingValue::Bool(left == right)),
150 BinaryOp::Ne => Ok(BindingValue::Bool(left != right)),
151 BinaryOp::Lt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
152 a < b
153 }))),
154 BinaryOp::Le => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
155 a <= b
156 }))),
157 BinaryOp::Gt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
158 a > b
159 }))),
160 BinaryOp::Ge => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
161 a >= b
162 }))),
163 BinaryOp::And => Ok(BindingValue::Bool(left.to_bool() && right.to_bool())),
164 BinaryOp::Or => Ok(BindingValue::Bool(left.to_bool() || right.to_bool())),
165 }
166}
167
168fn evaluate_add(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
169 match (left, right) {
170 (BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a + b)),
171 (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a + b)),
172 (BindingValue::String(a), BindingValue::String(b)) => Ok(BindingValue::String(a + &b)),
173 _ => Err(invalid_operation_error("add")),
174 }
175}
176
177fn evaluate_sub(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
178 match (left, right) {
179 (BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a - b)),
180 (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a - b)),
181 _ => Err(invalid_operation_error("subtract")),
182 }
183}
184
185fn evaluate_mul(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
186 match (left, right) {
187 (BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a * b)),
188 (BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a * b)),
189 _ => Err(invalid_operation_error("multiply")),
190 }
191}
192
193fn evaluate_div(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
194 match (left, right) {
195 (BindingValue::Integer(a), BindingValue::Integer(b)) if b != 0 => {
196 Ok(BindingValue::Integer(a / b))
197 }
198 (BindingValue::Float(a), BindingValue::Float(b)) if b != 0.0 => {
199 Ok(BindingValue::Float(a / b))
200 }
201 (BindingValue::Integer(_), BindingValue::Integer(0)) => Err(BindingError {
202 kind: BindingErrorKind::InvalidOperation,
203 message: "Division by zero".to_string(),
204 span: crate::ir::span::Span::new(0, 0, 0, 0),
205 suggestion: None,
206 }),
207 (BindingValue::Float(_), BindingValue::Float(0.0)) => Err(BindingError {
208 kind: BindingErrorKind::InvalidOperation,
209 message: "Division by zero".to_string(),
210 span: crate::ir::span::Span::new(0, 0, 0, 0),
211 suggestion: None,
212 }),
213 _ => Err(invalid_operation_error("divide")),
214 }
215}
216
217fn invalid_operation_error(operation: &str) -> BindingError {
218 BindingError {
219 kind: BindingErrorKind::InvalidOperation,
220 message: format!("Cannot {} these types", operation),
221 span: crate::ir::span::Span::new(0, 0, 0, 0),
222 suggestion: None,
223 }
224}
225
226fn compare_values<F>(left: &BindingValue, right: &BindingValue, cmp: F) -> bool
232where
233 F: Fn(f64, f64) -> bool,
234{
235 match (left, right) {
236 (BindingValue::Integer(a), BindingValue::Integer(b)) => cmp(*a as f64, *b as f64),
237 (BindingValue::Float(a), BindingValue::Float(b)) => cmp(*a, *b),
238 (BindingValue::String(a), BindingValue::String(b)) => cmp(a.len() as f64, b.len() as f64),
239 (BindingValue::List(a), BindingValue::List(b)) => cmp(a.len() as f64, b.len() as f64),
240 (BindingValue::Object(_), _) | (_, BindingValue::Object(_)) => false,
241 _ => false,
242 }
243}
244
245fn evaluate_unary_op(
247 unary_expr: &UnaryOpExpr,
248 model: &dyn UiBindable,
249 shared: Option<&dyn UiBindable>,
250) -> Result<BindingValue, BindingError> {
251 let operand = evaluate_expr_with_shared(&unary_expr.operand, model, shared)?;
252
253 match unary_expr.op {
254 UnaryOp::Not => Ok(BindingValue::Bool(!operand.to_bool())),
255 UnaryOp::Neg => match operand {
256 BindingValue::Integer(i) => Ok(BindingValue::Integer(-i)),
257 BindingValue::Float(f) => Ok(BindingValue::Float(-f)),
258 _ => Err(BindingError {
259 kind: BindingErrorKind::InvalidOperation,
260 message: "Cannot negate this type".to_string(),
261 span: crate::ir::span::Span::new(0, 0, 0, 0),
262 suggestion: None,
263 }),
264 },
265 }
266}
267
268fn evaluate_conditional(
270 conditional_expr: &ConditionalExpr,
271 model: &dyn UiBindable,
272 shared: Option<&dyn UiBindable>,
273) -> Result<BindingValue, BindingError> {
274 let condition = evaluate_expr_with_shared(&conditional_expr.condition, model, shared)?;
275
276 if condition.to_bool() {
277 evaluate_expr_with_shared(&conditional_expr.then_branch, model, shared)
278 } else {
279 evaluate_expr_with_shared(&conditional_expr.else_branch, model, shared)
280 }
281}
282
283fn evaluate_literal(literal_expr: &LiteralExpr) -> BindingValue {
285 match literal_expr {
286 LiteralExpr::String(s) => BindingValue::String(s.clone()),
287 LiteralExpr::Integer(i) => BindingValue::Integer(*i),
288 LiteralExpr::Float(f) => BindingValue::Float(*f),
289 LiteralExpr::Bool(b) => BindingValue::Bool(*b),
290 }
291}
292
293pub fn evaluate_binding_expr(
295 binding_expr: &crate::expr::BindingExpr,
296 model: &dyn UiBindable,
297) -> Result<BindingValue, BindingError> {
298 evaluate_binding_expr_with_shared(binding_expr, model, None)
299}
300
301pub fn evaluate_binding_expr_with_shared(
303 binding_expr: &crate::expr::BindingExpr,
304 model: &dyn UiBindable,
305 shared: Option<&dyn UiBindable>,
306) -> Result<BindingValue, BindingError> {
307 match evaluate_expr_with_shared(&binding_expr.expr, model, shared) {
308 Ok(result) => Ok(result),
309 Err(mut err) => {
310 err.span = binding_expr.span;
311 Err(err)
312 }
313 }
314}
315
316pub fn evaluate_formatted(
318 parts: &[crate::ir::InterpolatedPart],
319 model: &dyn UiBindable,
320) -> Result<String, BindingError> {
321 evaluate_formatted_with_shared(parts, model, None)
322}
323
324pub fn evaluate_formatted_with_shared(
326 parts: &[crate::ir::InterpolatedPart],
327 model: &dyn UiBindable,
328 shared: Option<&dyn UiBindable>,
329) -> Result<String, BindingError> {
330 let literal_len: usize = parts
331 .iter()
332 .filter_map(|p| match p {
333 crate::ir::InterpolatedPart::Literal(lit) => Some(lit.len()),
334 _ => None,
335 })
336 .sum();
337
338 let mut result = String::with_capacity(literal_len.saturating_mul(2).max(32));
339
340 for part in parts {
341 match part {
342 crate::ir::InterpolatedPart::Literal(literal) => {
343 result.push_str(literal);
344 }
345 crate::ir::InterpolatedPart::Binding(binding_expr) => {
346 let value = evaluate_binding_expr_with_shared(binding_expr, model, shared)?;
347 result.push_str(&value.to_display_string());
348 }
349 }
350 }
351
352 Ok(result)
353}