use crate::binding::{BindingValue, UiBindable};
use crate::expr::error::{BindingError, BindingErrorKind};
use crate::expr::{
BinaryOp, BinaryOpExpr, ConditionalExpr, Expr, FieldAccessExpr, LiteralExpr, MethodCallExpr,
SharedFieldAccessExpr, UnaryOp, UnaryOpExpr,
};
pub fn evaluate_expr(expr: &Expr, model: &dyn UiBindable) -> Result<BindingValue, BindingError> {
evaluate_expr_with_shared(expr, model, None)
}
pub fn evaluate_expr_with_shared(
expr: &Expr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
match expr {
Expr::FieldAccess(field_expr) => evaluate_field_access(field_expr, model),
Expr::SharedFieldAccess(shared_expr) => evaluate_shared_field_access(shared_expr, shared),
Expr::MethodCall(method_expr) => evaluate_method_call(method_expr, model, shared),
Expr::BinaryOp(binary_expr) => evaluate_binary_op(binary_expr, model, shared),
Expr::UnaryOp(unary_expr) => evaluate_unary_op(unary_expr, model, shared),
Expr::Conditional(conditional_expr) => {
evaluate_conditional(conditional_expr, model, shared)
}
Expr::Literal(literal_expr) => Ok(evaluate_literal(literal_expr)),
}
}
fn evaluate_field_access(
field_expr: &FieldAccessExpr,
model: &dyn UiBindable,
) -> Result<BindingValue, BindingError> {
let path: Vec<&str> = field_expr.path.iter().map(|s| s.as_str()).collect();
model.get_field(&path).ok_or_else(|| {
let field_name = field_expr.path.join(".");
BindingError {
kind: BindingErrorKind::UnknownField,
message: format!("Field '{}' not found", field_name),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}
})
}
fn evaluate_shared_field_access(
shared_expr: &SharedFieldAccessExpr,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
let Some(shared_ctx) = shared else {
return Ok(BindingValue::String(String::new()));
};
let path: Vec<&str> = shared_expr.path.iter().map(|s| s.as_str()).collect();
shared_ctx.get_field(&path).ok_or_else(|| {
let field_name = format!("shared.{}", shared_expr.path.join("."));
BindingError {
kind: BindingErrorKind::UnknownField,
message: format!("Shared field '{}' not found", field_name),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}
})
}
fn evaluate_method_call(
method_expr: &MethodCallExpr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
let receiver = evaluate_expr_with_shared(&method_expr.receiver, model, shared)?;
let method = &method_expr.method;
let _args: Vec<BindingValue> = method_expr
.args
.iter()
.map(|arg| evaluate_expr_with_shared(arg, model, shared))
.collect::<Result<Vec<_>, _>>()?;
evaluate_method(receiver, method)
}
fn evaluate_method(receiver: BindingValue, method: &str) -> Result<BindingValue, BindingError> {
match (receiver.clone(), method) {
(BindingValue::String(s), "len") => Ok(BindingValue::Integer(s.len() as i64)),
(BindingValue::String(s), "to_uppercase") => Ok(BindingValue::String(s.to_uppercase())),
(BindingValue::String(s), "to_lowercase") => Ok(BindingValue::String(s.to_lowercase())),
(BindingValue::String(s), "trim") => Ok(BindingValue::String(s.trim().to_string())),
(BindingValue::List(l), "len") => Ok(BindingValue::Integer(l.len() as i64)),
(BindingValue::List(l), "is_empty") => Ok(BindingValue::Bool(l.is_empty())),
(BindingValue::Integer(i), "to_string") => Ok(BindingValue::String(i.to_string())),
(BindingValue::Float(f), "to_string") => Ok(BindingValue::String(f.to_string())),
(BindingValue::Float(f), "round") => Ok(BindingValue::Float(f.round())),
(BindingValue::Float(f), "floor") => Ok(BindingValue::Float(f.floor())),
(BindingValue::Float(f), "ceil") => Ok(BindingValue::Float(f.ceil())),
(BindingValue::Bool(b), "to_string") => Ok(BindingValue::String(b.to_string())),
_ => Err(BindingError {
kind: BindingErrorKind::UnknownMethod,
message: format!("Method '{}' not supported on {:?}", method, receiver),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}),
}
}
fn evaluate_binary_op(
binary_expr: &BinaryOpExpr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
let left = evaluate_expr_with_shared(&binary_expr.left, model, shared)?;
let right = evaluate_expr_with_shared(&binary_expr.right, model, shared)?;
match binary_expr.op {
BinaryOp::Add => evaluate_add(left, right),
BinaryOp::Sub => evaluate_sub(left, right),
BinaryOp::Mul => evaluate_mul(left, right),
BinaryOp::Div => evaluate_div(left, right),
BinaryOp::Eq => Ok(BindingValue::Bool(left == right)),
BinaryOp::Ne => Ok(BindingValue::Bool(left != right)),
BinaryOp::Lt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
a < b
}))),
BinaryOp::Le => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
a <= b
}))),
BinaryOp::Gt => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
a > b
}))),
BinaryOp::Ge => Ok(BindingValue::Bool(compare_values(&left, &right, |a, b| {
a >= b
}))),
BinaryOp::And => Ok(BindingValue::Bool(left.to_bool() && right.to_bool())),
BinaryOp::Or => Ok(BindingValue::Bool(left.to_bool() || right.to_bool())),
}
}
fn evaluate_add(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
match (left, right) {
(BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a + b)),
(BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a + b)),
(BindingValue::String(a), BindingValue::String(b)) => Ok(BindingValue::String(a + &b)),
_ => Err(invalid_operation_error("add")),
}
}
fn evaluate_sub(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
match (left, right) {
(BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a - b)),
(BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a - b)),
_ => Err(invalid_operation_error("subtract")),
}
}
fn evaluate_mul(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
match (left, right) {
(BindingValue::Integer(a), BindingValue::Integer(b)) => Ok(BindingValue::Integer(a * b)),
(BindingValue::Float(a), BindingValue::Float(b)) => Ok(BindingValue::Float(a * b)),
_ => Err(invalid_operation_error("multiply")),
}
}
fn evaluate_div(left: BindingValue, right: BindingValue) -> Result<BindingValue, BindingError> {
match (left, right) {
(BindingValue::Integer(a), BindingValue::Integer(b)) if b != 0 => {
Ok(BindingValue::Integer(a / b))
}
(BindingValue::Float(a), BindingValue::Float(b)) if b != 0.0 => {
Ok(BindingValue::Float(a / b))
}
(BindingValue::Integer(_), BindingValue::Integer(0)) => Err(BindingError {
kind: BindingErrorKind::InvalidOperation,
message: "Division by zero".to_string(),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}),
(BindingValue::Float(_), BindingValue::Float(0.0)) => Err(BindingError {
kind: BindingErrorKind::InvalidOperation,
message: "Division by zero".to_string(),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}),
_ => Err(invalid_operation_error("divide")),
}
}
fn invalid_operation_error(operation: &str) -> BindingError {
BindingError {
kind: BindingErrorKind::InvalidOperation,
message: format!("Cannot {} these types", operation),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}
}
fn compare_values<F>(left: &BindingValue, right: &BindingValue, cmp: F) -> bool
where
F: Fn(f64, f64) -> bool,
{
match (left, right) {
(BindingValue::Integer(a), BindingValue::Integer(b)) => cmp(*a as f64, *b as f64),
(BindingValue::Float(a), BindingValue::Float(b)) => cmp(*a, *b),
(BindingValue::String(a), BindingValue::String(b)) => cmp(a.len() as f64, b.len() as f64),
(BindingValue::List(a), BindingValue::List(b)) => cmp(a.len() as f64, b.len() as f64),
(BindingValue::Object(_), _) | (_, BindingValue::Object(_)) => false,
_ => false,
}
}
fn evaluate_unary_op(
unary_expr: &UnaryOpExpr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
let operand = evaluate_expr_with_shared(&unary_expr.operand, model, shared)?;
match unary_expr.op {
UnaryOp::Not => Ok(BindingValue::Bool(!operand.to_bool())),
UnaryOp::Neg => match operand {
BindingValue::Integer(i) => Ok(BindingValue::Integer(-i)),
BindingValue::Float(f) => Ok(BindingValue::Float(-f)),
_ => Err(BindingError {
kind: BindingErrorKind::InvalidOperation,
message: "Cannot negate this type".to_string(),
span: crate::ir::span::Span::new(0, 0, 0, 0),
suggestion: None,
}),
},
}
}
fn evaluate_conditional(
conditional_expr: &ConditionalExpr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
let condition = evaluate_expr_with_shared(&conditional_expr.condition, model, shared)?;
if condition.to_bool() {
evaluate_expr_with_shared(&conditional_expr.then_branch, model, shared)
} else {
evaluate_expr_with_shared(&conditional_expr.else_branch, model, shared)
}
}
fn evaluate_literal(literal_expr: &LiteralExpr) -> BindingValue {
match literal_expr {
LiteralExpr::String(s) => BindingValue::String(s.clone()),
LiteralExpr::Integer(i) => BindingValue::Integer(*i),
LiteralExpr::Float(f) => BindingValue::Float(*f),
LiteralExpr::Bool(b) => BindingValue::Bool(*b),
}
}
pub fn evaluate_binding_expr(
binding_expr: &crate::expr::BindingExpr,
model: &dyn UiBindable,
) -> Result<BindingValue, BindingError> {
evaluate_binding_expr_with_shared(binding_expr, model, None)
}
pub fn evaluate_binding_expr_with_shared(
binding_expr: &crate::expr::BindingExpr,
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<BindingValue, BindingError> {
match evaluate_expr_with_shared(&binding_expr.expr, model, shared) {
Ok(result) => Ok(result),
Err(mut err) => {
err.span = binding_expr.span;
Err(err)
}
}
}
pub fn evaluate_formatted(
parts: &[crate::ir::InterpolatedPart],
model: &dyn UiBindable,
) -> Result<String, BindingError> {
evaluate_formatted_with_shared(parts, model, None)
}
pub fn evaluate_formatted_with_shared(
parts: &[crate::ir::InterpolatedPart],
model: &dyn UiBindable,
shared: Option<&dyn UiBindable>,
) -> Result<String, BindingError> {
let literal_len: usize = parts
.iter()
.filter_map(|p| match p {
crate::ir::InterpolatedPart::Literal(lit) => Some(lit.len()),
_ => None,
})
.sum();
let mut result = String::with_capacity(literal_len.saturating_mul(2).max(32));
for part in parts {
match part {
crate::ir::InterpolatedPart::Literal(literal) => {
result.push_str(literal);
}
crate::ir::InterpolatedPart::Binding(binding_expr) => {
let value = evaluate_binding_expr_with_shared(binding_expr, model, shared)?;
result.push_str(&value.to_display_string());
}
}
}
Ok(result)
}