use crate::ast::{Expr, StringPart};
use crate::error::{JsonnetError, Result};
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::value::{JsonnetBuiltin, JsonnetFunction, JsonnetValue};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PureEvaluator {
tla_args: HashMap<String, String>,
ext_vars: HashMap<String, String>,
context: EvaluationContext,
}
#[derive(Debug, Clone)]
struct EvaluationContext {
variables: HashMap<String, JsonnetValue>,
}
impl Default for PureEvaluator {
fn default() -> Self {
Self::new()
}
}
impl PureEvaluator {
pub fn new() -> Self {
let mut context = EvaluationContext {
variables: HashMap::new(),
};
let mut std_obj = HashMap::new();
std_obj.insert("length".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Length));
std_obj.insert("toString".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::ToString));
std_obj.insert("join".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Join));
std_obj.insert("substr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Substr));
std_obj.insert("split".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Split));
std_obj.insert("startsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StartsWith));
std_obj.insert("endsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::EndsWith));
std_obj.insert("stringChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StringChars));
std_obj.insert("asciiLower".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiLower));
std_obj.insert("asciiUpper".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiUpper));
std_obj.insert("flatMap".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FlatMap));
std_obj.insert("mapWithIndex".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::MapWithIndex));
std_obj.insert("lstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::LstripChars));
std_obj.insert("rstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::RstripChars));
std_obj.insert("stripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StripChars));
std_obj.insert("findSubstr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FindSubstr));
std_obj.insert("repeat".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Repeat));
std_obj.insert("set".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Set));
std_obj.insert("setMember".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetMember));
std_obj.insert("setInter".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetInter));
std_obj.insert("setUnion".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetUnion));
std_obj.insert("setDiff".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetDiff));
context.variables.insert("std".to_string(), JsonnetValue::Object(std_obj));
Self {
tla_args: HashMap::new(),
ext_vars: HashMap::new(),
context,
}
}
pub fn with_tla_args(tla_args: HashMap<String, String>) -> Self {
let mut context = EvaluationContext {
variables: HashMap::new(),
};
let mut std_obj = HashMap::new();
std_obj.insert("length".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Length));
std_obj.insert("toString".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::ToString));
std_obj.insert("join".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Join));
std_obj.insert("substr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Substr));
std_obj.insert("split".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Split));
std_obj.insert("startsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StartsWith));
std_obj.insert("endsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::EndsWith));
std_obj.insert("stringChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StringChars));
std_obj.insert("asciiLower".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiLower));
std_obj.insert("asciiUpper".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiUpper));
std_obj.insert("flatMap".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FlatMap));
std_obj.insert("mapWithIndex".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::MapWithIndex));
std_obj.insert("lstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::LstripChars));
std_obj.insert("rstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::RstripChars));
std_obj.insert("stripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StripChars));
std_obj.insert("findSubstr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FindSubstr));
std_obj.insert("repeat".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Repeat));
std_obj.insert("set".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Set));
std_obj.insert("setMember".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetMember));
std_obj.insert("setInter".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetInter));
std_obj.insert("setUnion".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetUnion));
std_obj.insert("setDiff".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetDiff));
context.variables.insert("std".to_string(), JsonnetValue::Object(std_obj));
for (key, value_str) in &tla_args {
if let Ok(value) = Self::parse_and_eval_simple(value_str) {
context.variables.insert(key.clone(), value);
}
}
Self {
tla_args,
ext_vars: HashMap::new(),
context,
}
}
pub fn with_config(tla_args: HashMap<String, String>, ext_vars: HashMap<String, String>) -> Self {
let mut context = EvaluationContext {
variables: HashMap::new(),
};
let mut std_obj = HashMap::new();
std_obj.insert("length".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Length));
std_obj.insert("toString".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::ToString));
std_obj.insert("join".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Join));
std_obj.insert("substr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Substr));
std_obj.insert("split".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Split));
std_obj.insert("startsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StartsWith));
std_obj.insert("endsWith".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::EndsWith));
std_obj.insert("stringChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StringChars));
std_obj.insert("asciiLower".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiLower));
std_obj.insert("asciiUpper".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::AsciiUpper));
std_obj.insert("flatMap".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FlatMap));
std_obj.insert("mapWithIndex".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::MapWithIndex));
std_obj.insert("lstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::LstripChars));
std_obj.insert("rstripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::RstripChars));
std_obj.insert("stripChars".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::StripChars));
std_obj.insert("findSubstr".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::FindSubstr));
std_obj.insert("repeat".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Repeat));
std_obj.insert("set".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::Set));
std_obj.insert("setMember".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetMember));
std_obj.insert("setInter".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetInter));
std_obj.insert("setUnion".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetUnion));
std_obj.insert("setDiff".to_string(), JsonnetValue::Builtin(JsonnetBuiltin::SetDiff));
context.variables.insert("std".to_string(), JsonnetValue::Object(std_obj));
for (key, value_str) in &tla_args {
if let Ok(value) = Self::parse_and_eval_simple(value_str) {
context.variables.insert(key.clone(), value);
}
}
for (key, value_str) in &ext_vars {
if let Ok(value) = Self::parse_and_eval_simple(value_str) {
context.variables.insert(key.clone(), value);
}
}
Self {
tla_args,
ext_vars,
context,
}
}
fn parse_and_eval_simple(source: &str) -> Result<JsonnetValue> {
let tokens = Lexer::new(source.to_string()).tokenize()?;
let mut parser = Parser::new(tokens);
let expr = parser.parse()?;
let mut temp_eval = PureEvaluator::new();
temp_eval.evaluate_expression(expr)
}
pub fn evaluate(&mut self, source: &str) -> Result<JsonnetValue> {
self.evaluate_with_context(source)
}
fn evaluate_with_context(&mut self, source: &str) -> Result<JsonnetValue> {
let tokens = Lexer::new(source.to_string()).tokenize()?;
let mut parser = Parser::new(tokens);
let parsed = parser.parse()?;
self.evaluate_expression(parsed)
}
fn evaluate_expression(&mut self, expr: Expr) -> Result<JsonnetValue> {
match expr {
Expr::String(s) => Ok(JsonnetValue::String(s)),
Expr::Number(n) => Ok(JsonnetValue::Number(n)),
Expr::Boolean(b) => Ok(JsonnetValue::Boolean(b)),
Expr::Null => Ok(JsonnetValue::Null),
Expr::Object(fields) => {
let mut obj = std::collections::HashMap::new();
for (key, value_expr) in fields {
let value = self.evaluate_expression(value_expr)?;
obj.insert(key, value);
}
Ok(JsonnetValue::Object(obj))
}
Expr::Array(elements) => {
let mut arr = Vec::new();
for element_expr in elements {
let element = self.evaluate_expression(element_expr)?;
arr.push(element);
}
Ok(JsonnetValue::Array(arr))
}
Expr::BinaryOp(op, left, right) => {
let left_val = self.evaluate_expression(*left)?;
let right_val = self.evaluate_expression(*right)?;
self.evaluate_binary_op(op, left_val, right_val)
}
Expr::UnaryOp(op, expr) => {
let val = self.evaluate_expression(*expr)?;
self.evaluate_unary_op(op, val)
}
Expr::ArrayAccess(target, index) => {
let target_val = self.evaluate_expression(*target)?;
let index_val = self.evaluate_expression(*index)?;
match (&target_val, &index_val) {
(JsonnetValue::Array(arr), JsonnetValue::Number(idx)) => {
let idx = *idx as i64;
if idx < 0 {
return Err(JsonnetError::runtime_error("Negative array index"));
}
let idx = idx as usize;
if idx >= arr.len() {
return Err(JsonnetError::index_out_of_bounds(idx as i64));
}
Ok(arr[idx].clone())
}
(JsonnetValue::Object(fields), JsonnetValue::String(field)) => {
match fields.get(field) {
Some(value) => Ok(value.clone()),
None => Err(JsonnetError::undefined_field(field)),
}
}
(JsonnetValue::Array(_), _) => Err(JsonnetError::type_error("Array index must be a number")),
(JsonnetValue::Object(_), _) => Err(JsonnetError::type_error("Object index must be a string")),
_ => Err(JsonnetError::type_error("Cannot index into this type")),
}
}
Expr::FieldAccess(obj, field) => {
let obj_val = self.evaluate_expression(*obj)?;
match obj_val {
JsonnetValue::Object(fields) => {
match fields.get(&field) {
Some(value) => Ok(value.clone()),
None => Err(JsonnetError::undefined_field(&field)),
}
}
_ => Err(JsonnetError::type_error("Field access requires object")),
}
}
Expr::Local(bindings, body) => {
let mut local_vars = self.context.variables.clone();
for (name, value_expr) in bindings {
let value = self.evaluate_expression(value_expr)?;
local_vars.insert(name, value);
}
let old_context = std::mem::replace(&mut self.context.variables, local_vars);
let result = self.evaluate_expression(*body);
self.context.variables = old_context;
result
}
Expr::Function(params, body) => {
let environment = self.context.variables.clone();
Ok(JsonnetValue::Function(JsonnetFunction {
parameters: params.clone(),
body: body.clone(),
environment,
}))
}
Expr::Conditional(condition, then_branch, else_branch) => {
let cond_val = self.evaluate_expression(*condition)?;
match cond_val {
JsonnetValue::Boolean(true) => self.evaluate_expression(*then_branch),
JsonnetValue::Boolean(false) => self.evaluate_expression(*else_branch),
_ => Err(JsonnetError::type_error("Condition must evaluate to boolean")),
}
}
Expr::Call(func_expr, args) => {
let func_val = self.evaluate_expression(*func_expr)?;
match func_val {
JsonnetValue::Function(func) => {
let mut arg_vals = Vec::new();
for arg in args {
arg_vals.push(self.evaluate_expression(arg)?);
}
let mut func_scope = func.environment.clone();
if func.parameters.len() != arg_vals.len() {
return Err(JsonnetError::runtime_error(
format!("Expected {} arguments, got {}", func.parameters.len(), arg_vals.len())
));
}
for (param, arg_val) in func.parameters.iter().zip(arg_vals) {
func_scope.insert(param.clone(), arg_val);
}
let old_context = std::mem::replace(&mut self.context.variables, func_scope);
let result = self.evaluate_expression(*func.body.clone());
self.context.variables = old_context;
result
}
JsonnetValue::Builtin(builtin) => {
let mut arg_vals = Vec::new();
for arg in args {
arg_vals.push(self.evaluate_expression(arg)?);
}
self.call_builtin_function(&builtin, arg_vals)
}
_ => Err(JsonnetError::type_error("Cannot call non-function value")),
}
}
Expr::ArrayComprehension { expr, var_name, array_expr, condition } => {
let array_val = self.evaluate_expression(*array_expr)?;
let array = array_val.as_array()?;
let mut result = Vec::new();
for item in array {
let original_value = self.context.variables.insert(var_name.clone(), item.clone());
let expr_result = self.evaluate_expression((*expr).clone());
if let Some(orig) = original_value {
self.context.variables.insert(var_name.clone(), orig);
} else {
self.context.variables.remove(&var_name);
}
match expr_result {
Ok(value) => {
let include = if let Some(ref cond_expr) = condition {
let cond_result = self.evaluate_expression((**cond_expr).clone());
match cond_result {
Ok(JsonnetValue::Boolean(true)) => true,
Ok(JsonnetValue::Boolean(false)) => false,
_ => false, }
} else {
true
};
if include {
result.push(value);
}
}
Err(e) => return Err(e),
}
}
Ok(JsonnetValue::Array(result))
}
Expr::StringInterpolation(parts) => {
let mut result = String::new();
for part in parts {
match part {
crate::ast::StringPart::Literal(text) => {
result.push_str(&text);
}
crate::ast::StringPart::Interpolation(expr) => {
let value = self.evaluate_expression(expr)?;
result.push_str(&self.value_to_string(&value));
}
}
}
Ok(JsonnetValue::String(result))
}
Expr::Identifier(name) => {
match self.context.variables.get(&name) {
Some(value) => Ok(value.clone()),
None => Err(JsonnetError::undefined_variable(&name)),
}
}
}
}
fn value_to_string(&self, value: &JsonnetValue) -> String {
match value {
JsonnetValue::String(s) => s.clone(),
JsonnetValue::Number(n) => n.to_string(),
JsonnetValue::Boolean(b) => b.to_string(),
JsonnetValue::Null => "null".to_string(),
JsonnetValue::Array(_) => "[array]".to_string(), JsonnetValue::Object(_) => "{object}".to_string(), JsonnetValue::Function(_) => "[function]".to_string(),
JsonnetValue::Builtin(_) => "[builtin]".to_string(),
}
}
fn call_builtin_function(&self, builtin: &JsonnetBuiltin, args: Vec<JsonnetValue>) -> Result<JsonnetValue> {
builtin.call(args)
}
fn evaluate_binary_op(&self, op: crate::ast::BinaryOp, left: JsonnetValue, right: JsonnetValue) -> Result<JsonnetValue> {
use crate::ast::BinaryOp::*;
match op {
Add => {
if let JsonnetValue::String(l) = &left {
let right_str = self.value_to_string(&right);
return Ok(JsonnetValue::String(l.clone() + &right_str));
}
if let JsonnetValue::String(r) = &right {
let left_str = self.value_to_string(&left);
return Ok(JsonnetValue::String(left_str + r));
}
match (&left, &right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Number(l + r)),
_ => Err(JsonnetError::type_error("Invalid operands for +")),
}
},
Sub => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Number(l - r)),
_ => Err(JsonnetError::type_error("Invalid operands for -")),
},
Mul => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Number(l * r)),
_ => Err(JsonnetError::type_error("Invalid operands for *")),
},
Div => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => {
if r == 0.0 {
Err(JsonnetError::DivisionByZero)
} else {
Ok(JsonnetValue::Number(l / r))
}
}
_ => Err(JsonnetError::type_error("Invalid operands for /")),
},
Mod => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Number(l % r)),
_ => Err(JsonnetError::type_error("Invalid operands for %")),
},
Eq => Ok(JsonnetValue::Boolean(left == right)),
Ne => Ok(JsonnetValue::Boolean(left != right)),
Lt => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Boolean(l < r)),
_ => Err(JsonnetError::type_error("Invalid operands for <")),
},
Le => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Boolean(l <= r)),
_ => Err(JsonnetError::type_error("Invalid operands for <=")),
},
Gt => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Boolean(l > r)),
_ => Err(JsonnetError::type_error("Invalid operands for >")),
},
Ge => match (left, right) {
(JsonnetValue::Number(l), JsonnetValue::Number(r)) => Ok(JsonnetValue::Boolean(l >= r)),
_ => Err(JsonnetError::type_error("Invalid operands for >=")),
},
And => match (left, right) {
(JsonnetValue::Boolean(l), JsonnetValue::Boolean(r)) => Ok(JsonnetValue::Boolean(l && r)),
_ => Err(JsonnetError::type_error("Invalid operands for &&")),
},
Or => match (left, right) {
(JsonnetValue::Boolean(l), JsonnetValue::Boolean(r)) => Ok(JsonnetValue::Boolean(l || r)),
_ => Err(JsonnetError::type_error("Invalid operands for ||")),
},
}
}
fn evaluate_unary_op(&self, op: crate::ast::UnaryOp, val: JsonnetValue) -> Result<JsonnetValue> {
use crate::ast::UnaryOp::*;
match op {
Neg => match val {
JsonnetValue::Number(n) => Ok(JsonnetValue::Number(-n)),
_ => Err(JsonnetError::type_error("Invalid operand for unary -")),
},
Not => match val {
JsonnetValue::Boolean(b) => Ok(JsonnetValue::Boolean(!b)),
_ => Err(JsonnetError::type_error("Invalid operand for !")),
},
Plus => match val {
JsonnetValue::Number(n) => Ok(JsonnetValue::Number(n)),
_ => Err(JsonnetError::type_error("Invalid operand for unary +")),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pure_evaluation_is_deterministic() {
let mut evaluator = PureEvaluator::new();
let source = r#" "hello" + " world" "#;
let result1 = evaluator.evaluate(source).unwrap();
let result2 = evaluator.evaluate(source).unwrap();
let result3 = evaluator.evaluate(source).unwrap();
assert_eq!(result1, result2);
assert_eq!(result2, result3);
}
#[test]
fn test_pure_evaluation_with_tla() {
let tla_args = HashMap::from([
("name".to_string(), r#""Alice""#.to_string()),
("age".to_string(), "30".to_string()),
]);
let mut evaluator = PureEvaluator::with_tla_args(tla_args);
let source = r#" "Hello, " + name + "!" "#;
let result = evaluator.evaluate(source).unwrap();
assert!(matches!(result, JsonnetValue::String(_)));
}
#[test]
fn test_pure_evaluator_clone() {
let mut evaluator1 = PureEvaluator::new();
let mut evaluator2 = evaluator1.clone();
let source = r#" "test" "#;
let result1 = evaluator1.evaluate(source).unwrap();
let result2 = evaluator2.evaluate(source).unwrap();
assert_eq!(result1, result2);
}
#[test]
fn test_pure_evaluator_immutability() {
let mut evaluator = PureEvaluator::new();
let tla_args = HashMap::from([("greeting".to_string(), r#""Hello""#.to_string())]);
let evaluator_with_tla = PureEvaluator::with_tla_args(tla_args.clone());
assert!(evaluator.tla_args.is_empty());
assert!(evaluator.ext_vars.is_empty());
assert_eq!(evaluator_with_tla.tla_args.len(), 1);
assert_eq!(evaluator_with_tla.tla_args.get("greeting").unwrap(), r#""Hello""#);
let ext_vars = HashMap::from([("env".to_string(), r#""production""#.to_string())]);
let evaluator_with_both = PureEvaluator::with_config(tla_args, ext_vars);
assert_eq!(evaluator_with_both.tla_args.len(), 1);
assert_eq!(evaluator_with_both.ext_vars.len(), 1);
assert_eq!(evaluator_with_both.ext_vars.get("env").unwrap(), r#""production""#);
}
#[test]
fn test_pure_evaluator_deterministic_with_config() {
let tla_args1 = HashMap::from([
("name".to_string(), r#""World""#.to_string()),
("count".to_string(), "42".to_string()),
]);
let tla_args2 = HashMap::from([
("name".to_string(), r#""World""#.to_string()),
("count".to_string(), "42".to_string()),
]);
let mut evaluator1 = PureEvaluator::with_tla_args(tla_args1);
let mut evaluator2 = PureEvaluator::with_tla_args(tla_args2);
let source = r#" "Result: " + name + " - " + count "#;
let result1 = evaluator1.evaluate(source).unwrap();
let result2 = evaluator2.evaluate(source).unwrap();
assert_eq!(result1, result2);
for _ in 0..5 {
let result_n = evaluator1.evaluate(source).unwrap();
assert_eq!(result1, result_n);
}
}
#[test]
fn test_pure_evaluator_external_vars() {
let ext_vars = HashMap::from([
("version".to_string(), r#""1.0.0""#.to_string()),
("debug".to_string(), "false".to_string()),
]);
let tla_args = HashMap::from([
("app".to_string(), r#""myapp""#.to_string()),
]);
let mut evaluator = PureEvaluator::with_config(tla_args, ext_vars);
let source = r#" "App: " + app + " v" + version + " debug=" + debug "#;
let mut evaluator2 = PureEvaluator::with_config(
HashMap::from([("app".to_string(), r#""myapp""#.to_string())]),
HashMap::from([
("version".to_string(), r#""1.0.0""#.to_string()),
("debug".to_string(), "false".to_string()),
])
);
let result1 = evaluator.evaluate(source).unwrap();
let result2 = evaluator2.evaluate(source).unwrap();
assert_eq!(result1, result2);
}
#[test]
fn test_pure_evaluator_no_side_effects() {
let mut evaluator = PureEvaluator::new();
let source = r#" "side effect test" "#;
let tla_before = evaluator.tla_args.len();
let ext_before = evaluator.ext_vars.len();
let _result = evaluator.evaluate(source).unwrap();
assert_eq!(evaluator.tla_args.len(), tla_before);
assert_eq!(evaluator.ext_vars.len(), ext_before);
for _ in 0..10 {
let _result = evaluator.evaluate(source).unwrap();
assert_eq!(evaluator.tla_args.len(), tla_before);
assert_eq!(evaluator.ext_vars.len(), ext_before);
}
}
}