rustbasic_core/template/
evaluator.rs1use super::parser::{Expr, Node};
2use serde_json::Value;
3use std::collections::HashMap;
4use std::sync::Arc;
5
6pub type FilterFn = Arc<dyn Fn(&Value, &[Value]) -> Value + Send + Sync>;
7
8pub struct Evaluator {
9 filters: HashMap<String, FilterFn>,
10}
11
12impl Evaluator {
13 pub fn new(filters: HashMap<String, FilterFn>) -> Self {
14 Self { filters }
15 }
16
17 pub fn evaluate_expr(&self, expr: &Expr, context: &Value) -> Value {
18 match expr {
19 Expr::StringLiteral(s) => Value::String(s.clone()),
20 Expr::NumberLiteral(n) => {
21 if let Some(num) = serde_json::Number::from_f64(*n) {
22 Value::Number(num)
23 } else {
24 Value::Null
25 }
26 }
27 Expr::BooleanLiteral(b) => Value::Bool(*b),
28 Expr::Path(path) => {
29 let mut current = context;
30 for segment in path {
31 if let Value::Object(map) = current {
32 if let Some(next) = map.get(segment) {
33 current = next;
34 } else {
35 return Value::Null;
36 }
37 } else {
38 return Value::Null;
39 }
40 }
41 current.clone()
42 }
43 Expr::Comparison { left, op, right } => {
44 let left_val = self.evaluate_expr(left, context);
45 let right_val = self.evaluate_expr(right, context);
46
47 let res = match op.as_str() {
48 "==" => left_val == right_val,
49 "!=" => left_val != right_val,
50 "<" => match (&left_val, &right_val) {
51 (Value::Number(l), Value::Number(r)) => l.as_f64() < r.as_f64(),
52 (Value::String(l), Value::String(r)) => l < r,
53 _ => false,
54 },
55 ">" => match (&left_val, &right_val) {
56 (Value::Number(l), Value::Number(r)) => l.as_f64() > r.as_f64(),
57 (Value::String(l), Value::String(r)) => l > r,
58 _ => false,
59 },
60 "<=" => match (&left_val, &right_val) {
61 (Value::Number(l), Value::Number(r)) => l.as_f64() <= r.as_f64(),
62 (Value::String(l), Value::String(r)) => l <= r,
63 _ => false,
64 },
65 ">=" => match (&left_val, &right_val) {
66 (Value::Number(l), Value::Number(r)) => l.as_f64() >= r.as_f64(),
67 (Value::String(l), Value::String(r)) => l >= r,
68 _ => false,
69 },
70 _ => false,
71 };
72 Value::Bool(res)
73 }
74 Expr::Filter { expr, filter_name, args } => {
75 let val = self.evaluate_expr(expr, context);
76 let evaluated_args: Vec<Value> = args.iter()
77 .map(|a| self.evaluate_expr(a, context))
78 .collect();
79
80 if let Some(filter_fn) = self.filters.get(filter_name) {
81 filter_fn(&val, &evaluated_args)
82 } else {
83 val
84 }
85 }
86 }
87 }
88
89 pub fn render_nodes(&self, nodes: &[Node], context: &Value) -> Result<String, String> {
90 let mut output = String::new();
91 for node in nodes {
92 match node {
93 Node::Text(t) => {
94 output.push_str(t);
95 }
96 Node::Variable(expr) => {
97 let val = self.evaluate_expr(expr, context);
98 match val {
99 Value::Null => {}
100 Value::String(s) => output.push_str(&s),
101 Value::Number(n) => output.push_str(&n.to_string()),
102 Value::Bool(b) => output.push_str(&b.to_string()),
103 other => {
104 output.push_str(&other.to_string());
105 }
106 }
107 }
108 Node::If { condition, then_branch, else_branch } => {
109 let cond_val = self.evaluate_expr(condition, context);
110 let is_truthy = match cond_val {
111 Value::Null => false,
112 Value::Bool(b) => b,
113 Value::Number(n) => n.as_f64().unwrap_or(0.0) != 0.0,
114 Value::String(s) => !s.is_empty(),
115 Value::Array(a) => !a.is_empty(),
116 Value::Object(o) => !o.is_empty(),
117 };
118
119 if is_truthy {
120 let sub = self.render_nodes(then_branch, context)?;
121 output.push_str(&sub);
122 } else if let Some(else_nodes) = else_branch {
123 let sub = self.render_nodes(else_nodes, context)?;
124 output.push_str(&sub);
125 }
126 }
127 Node::For { item, iterator, body } => {
128 let iter_val = self.evaluate_expr(iterator, context);
129 if let Value::Array(list) = iter_val {
130 for val in list {
131 let mut local_context = context.clone();
132 if let Value::Object(ref mut map) = local_context {
133 map.insert(item.clone(), val.clone());
134 } else {
135 let mut map = serde_json::Map::new();
136 map.insert(item.clone(), val.clone());
137 local_context = Value::Object(map);
138 }
139 let sub = self.render_nodes(body, &local_context)?;
140 output.push_str(&sub);
141 }
142 }
143 }
144 }
145 }
146 Ok(output)
147 }
148}