kotoba_jsonnet/
evaluator.rs

1//! Jsonnet expression evaluator
2
3use crate::ast::{self, BinaryOp, Expr, Program, Stmt, UnaryOp};
4use crate::error::{JsonnetError, Result};
5use crate::value::{JsonnetBuiltin, JsonnetFunction, JsonnetValue};
6use std::collections::HashMap;
7
8/// Jsonnet evaluator
9pub struct Evaluator {
10    /// Global environment
11    globals: HashMap<String, JsonnetValue>,
12}
13
14impl Evaluator {
15    /// Create a new evaluator
16    pub fn new() -> Self {
17        let mut evaluator = Evaluator {
18            globals: HashMap::new(),
19        };
20        evaluator.init_stdlib();
21        evaluator
22    }
23
24    /// Initialize the standard library
25    fn init_stdlib(&mut self) {
26
27        // Add std object with functions
28        let mut std_object = HashMap::new();
29
30        // std.length function
31        std_object.insert("length".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Length));
32
33        // For now, add other std functions as built-ins that will delegate to StdLib
34        // TODO: Create a proper std function wrapper
35        let std_functions = vec![
36            "type", "makeArray", "filter", "map", "foldl", "foldr", "range", "join", "split",
37            "contains", "startsWith", "endsWith", "substr", "char", "codepoint", "toString",
38            "parseInt", "parseJson", "encodeUTF8", "decodeUTF8", "md5", "base64", "base64Decode",
39            "manifestJson", "manifestJsonEx", "manifestYaml", "escapeStringJson", "escapeStringYaml",
40            "escapeStringPython", "escapeStringBash", "escapeStringDollars", "stringChars", "stringBytes",
41            "format", "isArray", "isBoolean", "isFunction", "isNumber", "isObject", "isString",
42            "count", "find", "member", "modulo", "pow", "exp", "log", "sqrt", "sin", "cos", "tan",
43            "asin", "acos", "atan", "floor", "ceil", "round", "abs", "max", "min", "clamp",
44            "assertEqual", "sort", "uniq", "reverse", "mergePatch", "get", "objectFields",
45            "objectFieldsAll", "objectHas", "objectHasAll", "objectValues", "objectValuesAll",
46            "prune", "mapWithKey", "toLower", "toUpper", "trim", "trace", "all", "any",
47            "id", "equals", "lines", "strReplace", "sha1", "sha256", "sha3", "sha512",
48            "asciiLower", "asciiUpper", "set", "setMember", "setUnion", "setInter", "setDiff",
49            "flatMap", "mapWithIndex", "lstripChars", "rstripChars", "stripChars", "findSubstr", "repeat",
50            "manifestIni", "manifestPython", "manifestCpp", "manifestXmlJsonml",
51            "log2", "log10", "log1p", "expm1"
52        ];
53
54        for func_name in std_functions {
55            std_object.insert(func_name.to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StdLibFunction(func_name.to_string())));
56        }
57
58        self.globals.insert("std".to_string(), JsonnetValue::Object(std_object));
59    }
60
61    /// Evaluate a Jsonnet file
62    pub fn evaluate_file(&mut self, source: &str, _filename: &str) -> Result<JsonnetValue> {
63        use crate::parser::Parser;
64
65        // Try to parse as a single expression first
66        let mut parser = Parser::new();
67        match parser.parse_expression(source) {
68            Ok(expr) => self.evaluate_expression(&expr),
69            Err(_) => {
70                // If that fails, try parsing as a program
71                let mut program_parser = Parser::new();
72                let program = program_parser.parse(source)?;
73                self.evaluate_program(program)
74            }
75        }
76    }
77
78
79
80    /// Evaluate a parsed program
81    fn evaluate_program(&mut self, program: Program) -> Result<JsonnetValue> {
82        let mut result = JsonnetValue::Null;
83
84        for stmt in program.statements {
85            result = self.evaluate_statement(&stmt)?;
86        }
87
88        Ok(result)
89    }
90
91    /// Evaluate a statement
92    fn evaluate_statement(&mut self, stmt: &Stmt) -> Result<JsonnetValue> {
93        match stmt {
94            Stmt::Expr(expr) => self.evaluate_expression(expr),
95            Stmt::Local(bindings) => {
96                let mut scope = HashMap::new();
97
98                // Evaluate bindings in order
99                for (name, expr) in bindings {
100                    let value = self.evaluate_expression(expr)?;
101                    scope.insert(name.clone(), value);
102                }
103
104                // For now, just return the last binding value
105                // TODO: Handle proper scoping
106                if let Some((_, expr)) = bindings.last() {
107                    self.evaluate_expression(expr)
108                } else {
109                    Ok(JsonnetValue::Null)
110                }
111            }
112            Stmt::Assert { cond, message } => {
113                let cond_value = self.evaluate_expression(cond)?;
114                if !cond_value.is_truthy() {
115                    let msg = if let Some(msg_expr) = message {
116                        match self.evaluate_expression(msg_expr)? {
117                            JsonnetValue::String(s) => s,
118                            _ => "Assertion failed".to_string(),
119                        }
120                    } else {
121                        "Assertion failed".to_string()
122                    };
123                    return Err(JsonnetError::runtime_error(&msg));
124                }
125                Ok(JsonnetValue::Null) // Assert statements don't return a value
126            }
127        }
128    }
129
130    /// Evaluate an expression
131    fn evaluate_expression(&mut self, expr: &Expr) -> Result<JsonnetValue> {
132        match expr {
133            Expr::Literal(value) => Ok(value.clone()),
134            Expr::StringInterpolation(parts) => {
135                let mut result = String::new();
136                for part in parts {
137                    match part {
138                        ast::StringInterpolationPart::Literal(s) => result.push_str(s),
139                        ast::StringInterpolationPart::Interpolation(expr) => {
140                            let value = self.evaluate_expression(expr)?;
141                            match value {
142                                JsonnetValue::String(s) => result.push_str(&s),
143                                JsonnetValue::Number(n) => result.push_str(&n.to_string()),
144                                JsonnetValue::Boolean(b) => result.push_str(&b.to_string()),
145                                JsonnetValue::Null => result.push_str("null"),
146                                _ => result.push_str(&value.to_string()),
147                            }
148                        }
149                    }
150                }
151                Ok(JsonnetValue::string(result))
152            }
153            Expr::Var(name) => {
154                if let Some(value) = self.globals.get(name) {
155                    Ok(value.clone())
156                } else {
157                    Err(JsonnetError::runtime_error(format!("Undefined variable: {}", name)))
158                }
159            }
160            Expr::BinaryOp { left, op, right } => {
161                let left_val = self.evaluate_expression(left)?;
162                let right_val = self.evaluate_expression(right)?;
163                self.evaluate_binary_op(*op, left_val, right_val)
164            }
165            Expr::UnaryOp { op, expr } => {
166                let val = self.evaluate_expression(expr)?;
167                self.evaluate_unary_op(*op, val)
168            }
169            Expr::Array(elements) => {
170                let mut values = Vec::new();
171                for elem in elements {
172                    values.push(self.evaluate_expression(elem)?);
173                }
174                Ok(JsonnetValue::Array(values))
175            }
176            Expr::ArrayComp { expr, var, array, cond } => {
177                let array_val = self.evaluate_expression(array)?;
178                let mut result = Vec::new();
179
180                if let JsonnetValue::Array(elements) = array_val {
181                    for element in elements {
182                        // Bind the variable in a local scope
183                        let old_globals = self.globals.clone();
184                        self.globals.insert(var.clone(), element.clone());
185
186                        // Check condition if present
187                        let condition_met = if let Some(cond_expr) = cond {
188                            let cond_val = self.evaluate_expression(cond_expr)?;
189                            cond_val.is_truthy()
190                        } else {
191                            true
192                        };
193
194                        if condition_met {
195                            let value = self.evaluate_expression(expr)?;
196                            result.push(value);
197                        }
198
199                        // Restore globals
200                        self.globals = old_globals;
201                    }
202                } else {
203                    return Err(JsonnetError::RuntimeError {
204                        message: "Array comprehension requires an array".to_string(),
205                    });
206                }
207
208                Ok(JsonnetValue::Array(result))
209            }
210            Expr::Object(fields) => {
211                let mut object = HashMap::new();
212                for field in fields {
213                    let key = self.evaluate_object_field_key(&field.name)?;
214                    let value = self.evaluate_expression(&field.expr)?;
215                    object.insert(key, value);
216                }
217                Ok(JsonnetValue::Object(object))
218            }
219            Expr::Call { func, args } => {
220                let func_val = self.evaluate_expression(func)?;
221                let mut arg_vals = Vec::new();
222                for arg in args {
223                    arg_vals.push(self.evaluate_expression(arg)?);
224                }
225                self.call_function(func_val, arg_vals)
226            }
227            Expr::Index { target, index } => {
228                let target_val = self.evaluate_expression(target)?;
229                let index_val = self.evaluate_expression(index)?;
230                self.index_access(target_val, index_val)
231            }
232            Expr::Local { bindings, body } => {
233                // Create a new scope for local variables
234                let mut local_scope = HashMap::new();
235
236                // Evaluate bindings
237                for (name, expr) in bindings {
238                    let value = self.evaluate_expression(expr)?;
239                    local_scope.insert(name.clone(), value);
240                }
241
242                // Temporarily add to globals (simple implementation)
243                let old_globals = self.globals.clone();
244                for (name, value) in &local_scope {
245                    self.globals.insert(name.clone(), value.clone());
246                }
247
248                let result = self.evaluate_expression(body);
249
250                // Restore globals
251                self.globals = old_globals;
252
253                result
254            }
255            Expr::Function { parameters, body } => {
256                Ok(JsonnetValue::Function(JsonnetFunction::new(parameters.clone(), Box::new((**body).clone()), HashMap::new())))
257            }
258            Expr::If { cond, then_branch, else_branch } => {
259                let cond_val = self.evaluate_expression(cond)?;
260                if cond_val.is_truthy() {
261                    self.evaluate_expression(then_branch)
262                } else if let Some(else_expr) = else_branch {
263                    self.evaluate_expression(else_expr)
264                } else {
265                    Ok(JsonnetValue::Null)
266                }
267            }
268            _ => Err(JsonnetError::runtime_error("Expression type not implemented yet")),
269        }
270    }
271
272    /// Evaluate binary operation
273    fn evaluate_binary_op(&self, op: BinaryOp, left: JsonnetValue, right: JsonnetValue) -> Result<JsonnetValue> {
274        match op {
275            BinaryOp::Add => left.add(&right),
276            BinaryOp::Sub => left.sub(&right),
277            BinaryOp::Mul => left.mul(&right),
278            BinaryOp::Div => left.div(&right),
279            BinaryOp::Mod => left.modulo(&right),
280            BinaryOp::Lt => left.lt(&right),
281            BinaryOp::Le => left.le(&right),
282            BinaryOp::Gt => left.gt(&right),
283            BinaryOp::Ge => left.ge(&right),
284            BinaryOp::Eq => left.eq(&right),
285            BinaryOp::Ne => left.ne(&right),
286            BinaryOp::And => left.and(&right),
287            BinaryOp::Or => left.or(&right),
288            _ => Err(JsonnetError::runtime_error("Binary operator not implemented")),
289        }
290    }
291
292    /// Evaluate unary operation
293    fn evaluate_unary_op(&self, op: UnaryOp, value: JsonnetValue) -> Result<JsonnetValue> {
294        match op {
295            UnaryOp::Not => value.not(),
296            UnaryOp::Neg => value.neg(),
297            UnaryOp::Pos => Ok(value),
298            UnaryOp::BitNot => Err(JsonnetError::runtime_error("Bitwise NOT not implemented")),
299        }
300    }
301
302    /// Evaluate object field key
303    fn evaluate_object_field_key(&mut self, field_name: &ast::FieldName) -> Result<String> {
304        match field_name {
305            ast::FieldName::Fixed(name) => Ok(name.clone()),
306            ast::FieldName::Computed(expr) => {
307                match self.evaluate_expression(expr)? {
308                    JsonnetValue::String(s) => Ok(s),
309                    _ => Err(JsonnetError::runtime_error("Computed field name must evaluate to a string")),
310                }
311            }
312        }
313    }
314
315    /// Call a function
316    fn call_function(&mut self, func: JsonnetValue, args: Vec<JsonnetValue>) -> Result<JsonnetValue> {
317        match func {
318            JsonnetValue::Function(f) => {
319                if args.len() != f.parameters.len() {
320                    return Err(JsonnetError::runtime_error(
321                        format!("Expected {} arguments, got {}", f.parameters.len(), args.len())
322                    ));
323                }
324
325                // Create parameter bindings
326                let old_globals = self.globals.clone();
327                for (param, arg) in f.parameters.iter().zip(args) {
328                    self.globals.insert(param.clone(), arg);
329                }
330
331                let result = self.evaluate_expression(&f.body);
332
333                // Restore globals
334                self.globals = old_globals;
335
336                result
337            }
338            JsonnetValue::Builtin(builtin) => {
339                builtin.call(args)
340            }
341            _ => Err(JsonnetError::runtime_error("Cannot call non-function value")),
342        }
343    }
344
345    /// Index access
346    fn index_access(&self, target: JsonnetValue, index: JsonnetValue) -> Result<JsonnetValue> {
347        match (&target, &index) {
348            (JsonnetValue::Array(arr), JsonnetValue::Number(idx)) => {
349                let idx = *idx as usize;
350                if idx >= arr.len() {
351                    return Err(JsonnetError::runtime_error("Array index out of bounds"));
352                }
353                Ok(arr[idx].clone())
354            }
355            (JsonnetValue::Object(obj), JsonnetValue::String(key)) => {
356                if let Some(value) = obj.get(key) {
357                    Ok(value.clone())
358                } else {
359                    Ok(JsonnetValue::Null) // Jsonnet returns null for missing keys
360                }
361            }
362            _ => Err(JsonnetError::runtime_error("Invalid index operation")),
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_evaluate_null() {
373        let mut evaluator = Evaluator::new();
374        let result = evaluator.evaluate_file("null", "");
375        assert!(result.is_ok());
376        assert_eq!(result.unwrap(), JsonnetValue::Null);
377    }
378
379    #[test]
380    fn test_evaluate_boolean() {
381        let mut evaluator = Evaluator::new();
382        let result = evaluator.evaluate_file("true", "");
383        assert!(result.is_ok());
384        assert_eq!(result.unwrap(), JsonnetValue::boolean(true));
385    }
386
387    #[test]
388    fn test_evaluate_number() {
389        let mut evaluator = Evaluator::new();
390        let result = evaluator.evaluate_file("42", "");
391        assert!(result.is_ok());
392        assert_eq!(result.unwrap(), JsonnetValue::number(42.0));
393    }
394
395    #[test]
396    fn test_evaluate_string() {
397        let mut evaluator = Evaluator::new();
398        let result = evaluator.evaluate_file(r#""hello""#, "");
399        assert!(result.is_ok());
400        assert_eq!(result.unwrap(), JsonnetValue::string("hello"));
401    }
402
403    #[test]
404    fn test_evaluate_arithmetic() {
405        let mut evaluator = Evaluator::new();
406
407        let result = evaluator.evaluate_file("2 + 3", "");
408        assert!(result.is_ok());
409        assert_eq!(result.unwrap(), JsonnetValue::number(5.0));
410
411        let result = evaluator.evaluate_file("10 - 4", "");
412        assert!(result.is_ok());
413        assert_eq!(result.unwrap(), JsonnetValue::number(6.0));
414
415        let result = evaluator.evaluate_file("3 * 4", "");
416        assert!(result.is_ok());
417        assert_eq!(result.unwrap(), JsonnetValue::number(12.0));
418
419        let result = evaluator.evaluate_file("8 / 2", "");
420        assert!(result.is_ok());
421        assert_eq!(result.unwrap(), JsonnetValue::number(4.0));
422    }
423
424    #[test]
425    fn test_evaluate_string_concatenation() {
426        let mut evaluator = Evaluator::new();
427        let result = evaluator.evaluate_file(r#""hello" + " " + "world""#, "");
428        assert!(result.is_ok());
429        assert_eq!(result.unwrap(), JsonnetValue::string("hello world"));
430    }
431
432    #[test]
433    fn test_evaluate_std_length() {
434        let mut evaluator = Evaluator::new();
435
436        let result = evaluator.evaluate_file("std.length([1, 2, 3])", "");
437        assert!(result.is_ok());
438        assert_eq!(result.unwrap(), JsonnetValue::number(3.0));
439
440        let result = evaluator.evaluate_file(r#"std.length("hello")"#, "");
441        assert!(result.is_ok());
442        assert_eq!(result.unwrap(), JsonnetValue::number(5.0));
443    }
444
445    #[test]
446    fn test_evaluate_division_by_zero() {
447        let mut evaluator = Evaluator::new();
448        let result = evaluator.evaluate_file("1 / 0", "");
449        assert!(result.is_err());
450        match result.err().unwrap() {
451            JsonnetError::RuntimeError { message: _ } => {}, // Division by zero is now a runtime error
452            _ => panic!("Expected runtime error"),
453        }
454    }
455
456    #[test]
457    fn test_evaluate_undefined_variable() {
458        let mut evaluator = Evaluator::new();
459        let result = evaluator.evaluate_file("undefined_var", "");
460        assert!(result.is_err());
461        match result.err().unwrap() {
462            JsonnetError::RuntimeError { message: _ } => {}, // Undefined variable is now a runtime error
463            _ => panic!("Expected runtime error"),
464        }
465    }
466}
467
468impl Default for Evaluator {
469    fn default() -> Self {
470        Self::new()
471    }
472}