1use crate::types::ExecutionError;
22use serde_json::Value as JsonValue;
23use std::collections::HashMap;
24
25use crate::executor::{
26 ArrayMethodCall, BinaryOperator, BuiltinFunction, NumberMethodCall, ObjectField, UnaryOperator,
27 ValueExpr,
28};
29
30pub trait VariableProvider {
35 fn get_variable(&self, name: &str) -> Option<&JsonValue>;
37}
38
39impl VariableProvider for HashMap<String, JsonValue> {
41 fn get_variable(&self, name: &str) -> Option<&JsonValue> {
42 self.get(name)
43 }
44}
45
46pub fn evaluate_with_scope<V: VariableProvider>(
60 expr: &ValueExpr,
61 global_vars: &V,
62 local_vars: &HashMap<String, JsonValue>,
63) -> Result<JsonValue, ExecutionError> {
64 match expr {
65 ValueExpr::Variable(name) => evaluate_variable_lookup(name, global_vars, local_vars),
66 ValueExpr::Literal(value) => Ok(value.clone()),
67 ValueExpr::PropertyAccess { object, property } => {
68 evaluate_property_access(object, property, global_vars, local_vars)
69 },
70 ValueExpr::ArrayIndex { array, index } => {
71 evaluate_array_index(array, index, global_vars, local_vars)
72 },
73 ValueExpr::ObjectLiteral { fields } => {
74 evaluate_object_literal(fields, global_vars, local_vars)
75 },
76 ValueExpr::ArrayLiteral { items } => evaluate_array_literal(items, global_vars, local_vars),
77 ValueExpr::BinaryOp { left, op, right } => {
78 let l = evaluate_with_scope(left, global_vars, local_vars)?;
79 let r = evaluate_with_scope(right, global_vars, local_vars)?;
80 evaluate_binary_op(&l, *op, &r)
81 },
82 ValueExpr::UnaryOp { op, operand } => {
83 let v = evaluate_with_scope(operand, global_vars, local_vars)?;
84 evaluate_unary_op(*op, &v)
85 },
86 ValueExpr::Ternary {
87 condition,
88 consequent,
89 alternate,
90 } => evaluate_ternary(condition, consequent, alternate, global_vars, local_vars),
91 ValueExpr::OptionalChain { object, property } => {
92 evaluate_optional_chain(object, property, global_vars, local_vars)
93 },
94 ValueExpr::NullishCoalesce { left, right } => {
95 evaluate_nullish_coalesce(left, right, global_vars, local_vars)
96 },
97 ValueExpr::ArrayMethod { array, method } => {
98 evaluate_array_method_dispatch(array, method, global_vars, local_vars)
99 },
100 ValueExpr::NumberMethod { number, method } => {
101 let num_value = evaluate_with_scope(number, global_vars, local_vars)?;
102 evaluate_number_method(&num_value, method)
103 },
104 ValueExpr::Block { bindings, result } => {
105 evaluate_block(bindings, result, global_vars, local_vars)
106 },
107 ValueExpr::BuiltinCall { func, args } => {
108 evaluate_builtin_call(func, args, global_vars, local_vars)
109 },
110 ValueExpr::ApiCall { .. } => Err(executor_only_error(
111 "API calls should be handled by executor, not evaluator",
112 )),
113 ValueExpr::Await { .. } => Err(executor_only_error(
114 "Await expressions should be handled by executor",
115 )),
116 ValueExpr::PromiseAll { .. } => Err(executor_only_error(
117 "Promise.all should be handled by executor",
118 )),
119 #[cfg(feature = "mcp-code-mode")]
120 ValueExpr::McpCall { .. } => Err(executor_only_error(
121 "MCP calls should be handled by executor, not evaluator",
122 )),
123 ValueExpr::SdkCall { .. } => Err(executor_only_error(
124 "SDK calls should be handled by executor, not evaluator",
125 )),
126 }
127}
128
129#[inline]
131fn executor_only_error(message: &str) -> ExecutionError {
132 ExecutionError::RuntimeError {
133 message: message.into(),
134 }
135}
136
137fn evaluate_variable_lookup<V: VariableProvider>(
139 name: &str,
140 global_vars: &V,
141 local_vars: &HashMap<String, JsonValue>,
142) -> Result<JsonValue, ExecutionError> {
143 if let Some(value) = local_vars.get(name) {
144 return Ok(value.clone());
145 }
146 if let Some(value) = global_vars.get_variable(name) {
147 return Ok(value.clone());
148 }
149 if name == "undefined" {
151 return Ok(JsonValue::Null);
152 }
153 Err(ExecutionError::RuntimeError {
154 message: format!("Undefined variable: {}", name),
155 })
156}
157
158fn evaluate_property_access<V: VariableProvider>(
160 object: &ValueExpr,
161 property: &str,
162 global_vars: &V,
163 local_vars: &HashMap<String, JsonValue>,
164) -> Result<JsonValue, ExecutionError> {
165 let obj = evaluate_with_scope(object, global_vars, local_vars)?;
166 match obj {
167 JsonValue::Object(map) => Ok(map.get(property).cloned().unwrap_or(JsonValue::Null)),
168 _ => Ok(JsonValue::Null),
169 }
170}
171
172fn evaluate_array_index<V: VariableProvider>(
174 array: &ValueExpr,
175 index: &ValueExpr,
176 global_vars: &V,
177 local_vars: &HashMap<String, JsonValue>,
178) -> Result<JsonValue, ExecutionError> {
179 let arr = evaluate_with_scope(array, global_vars, local_vars)?;
180 let idx = evaluate_with_scope(index, global_vars, local_vars)?;
181 match (arr, idx) {
182 (JsonValue::Array(arr), JsonValue::Number(n)) => {
183 let i = n.as_i64().unwrap_or(0) as usize;
184 Ok(arr.get(i).cloned().unwrap_or(JsonValue::Null))
185 },
186 _ => Ok(JsonValue::Null),
187 }
188}
189
190fn evaluate_object_literal<V: VariableProvider>(
192 fields: &[ObjectField],
193 global_vars: &V,
194 local_vars: &HashMap<String, JsonValue>,
195) -> Result<JsonValue, ExecutionError> {
196 let mut map = serde_json::Map::new();
197 for field in fields {
198 evaluate_object_field_into(field, &mut map, global_vars, local_vars)?;
199 }
200 Ok(JsonValue::Object(map))
201}
202
203fn evaluate_object_field_into<V: VariableProvider>(
205 field: &ObjectField,
206 map: &mut serde_json::Map<String, JsonValue>,
207 global_vars: &V,
208 local_vars: &HashMap<String, JsonValue>,
209) -> Result<(), ExecutionError> {
210 match field {
211 ObjectField::KeyValue {
212 key,
213 value: value_expr,
214 } => {
215 let value = evaluate_with_scope(value_expr, global_vars, local_vars)?;
216 map.insert(key.clone(), value);
217 },
218 ObjectField::Spread { expr } => {
219 let spread_val = evaluate_with_scope(expr, global_vars, local_vars)?;
220 if let JsonValue::Object(spread_map) = spread_val {
221 for (k, v) in spread_map {
222 map.insert(k, v);
223 }
224 }
225 },
227 }
228 Ok(())
229}
230
231fn evaluate_array_literal<V: VariableProvider>(
233 items: &[ValueExpr],
234 global_vars: &V,
235 local_vars: &HashMap<String, JsonValue>,
236) -> Result<JsonValue, ExecutionError> {
237 let mut arr = Vec::with_capacity(items.len());
238 for item in items {
239 arr.push(evaluate_with_scope(item, global_vars, local_vars)?);
240 }
241 Ok(JsonValue::Array(arr))
242}
243
244fn evaluate_ternary<V: VariableProvider>(
246 condition: &ValueExpr,
247 consequent: &ValueExpr,
248 alternate: &ValueExpr,
249 global_vars: &V,
250 local_vars: &HashMap<String, JsonValue>,
251) -> Result<JsonValue, ExecutionError> {
252 let cond = evaluate_with_scope(condition, global_vars, local_vars)?;
253 if is_truthy(&cond) {
254 evaluate_with_scope(consequent, global_vars, local_vars)
255 } else {
256 evaluate_with_scope(alternate, global_vars, local_vars)
257 }
258}
259
260fn evaluate_optional_chain<V: VariableProvider>(
262 object: &ValueExpr,
263 property: &str,
264 global_vars: &V,
265 local_vars: &HashMap<String, JsonValue>,
266) -> Result<JsonValue, ExecutionError> {
267 let obj = evaluate_with_scope(object, global_vars, local_vars)?;
268 match obj {
269 JsonValue::Null => Ok(JsonValue::Null),
270 JsonValue::Object(map) => Ok(map.get(property).cloned().unwrap_or(JsonValue::Null)),
271 _ => Ok(JsonValue::Null),
272 }
273}
274
275fn evaluate_nullish_coalesce<V: VariableProvider>(
277 left: &ValueExpr,
278 right: &ValueExpr,
279 global_vars: &V,
280 local_vars: &HashMap<String, JsonValue>,
281) -> Result<JsonValue, ExecutionError> {
282 let l = evaluate_with_scope(left, global_vars, local_vars)?;
283 if is_nullish(&l) {
284 evaluate_with_scope(right, global_vars, local_vars)
285 } else {
286 Ok(l)
287 }
288}
289
290fn evaluate_array_method_dispatch<V: VariableProvider>(
293 array: &ValueExpr,
294 method: &ArrayMethodCall,
295 global_vars: &V,
296 local_vars: &HashMap<String, JsonValue>,
297) -> Result<JsonValue, ExecutionError> {
298 let arr_value = evaluate_with_scope(array, global_vars, local_vars)?;
299 let mut scope = local_vars.clone();
300 evaluate_array_method_with_scope(&arr_value, method, global_vars, &mut scope)
301}
302
303fn evaluate_block<V: VariableProvider>(
306 bindings: &[(String, ValueExpr)],
307 result: &ValueExpr,
308 global_vars: &V,
309 local_vars: &HashMap<String, JsonValue>,
310) -> Result<JsonValue, ExecutionError> {
311 let mut merged_vars = local_vars.clone();
312 for (name, binding_expr) in bindings {
313 let value = evaluate_with_scope(binding_expr, global_vars, &merged_vars)?;
314 merged_vars.insert(name.clone(), value);
315 }
316 evaluate_with_scope(result, global_vars, &merged_vars)
317}
318
319fn evaluate_builtin_call<V: VariableProvider>(
321 func: &BuiltinFunction,
322 args: &[ValueExpr],
323 global_vars: &V,
324 local_vars: &HashMap<String, JsonValue>,
325) -> Result<JsonValue, ExecutionError> {
326 let evaluated_args: Vec<JsonValue> = args
327 .iter()
328 .map(|a| evaluate_with_scope(a, global_vars, local_vars))
329 .collect::<Result<Vec<_>, _>>()?;
330 evaluate_builtin(func, &evaluated_args)
331}
332
333pub fn evaluate_binary_op(
335 left: &JsonValue,
336 op: BinaryOperator,
337 right: &JsonValue,
338) -> Result<JsonValue, ExecutionError> {
339 match op {
340 BinaryOperator::Add => add_values(left, right),
341 BinaryOperator::Sub => numeric_op(left, right, |a, b| a - b),
342 BinaryOperator::Mul => numeric_op(left, right, |a, b| a * b),
343 BinaryOperator::Div => {
344 numeric_op(left, right, |a, b| if b != 0.0 { a / b } else { f64::NAN })
345 },
346 BinaryOperator::Mod => {
347 numeric_op(left, right, |a, b| if b != 0.0 { a % b } else { f64::NAN })
348 },
349 BinaryOperator::BitwiseOr => {
350 numeric_op(left, right, |a, b| ((a as i32) | (b as i32)) as f64)
351 },
352 BinaryOperator::Eq => Ok(JsonValue::Bool(json_equals(left, right))),
353 BinaryOperator::NotEq => Ok(JsonValue::Bool(!json_equals(left, right))),
354 BinaryOperator::StrictEq => Ok(JsonValue::Bool(json_strict_equals(left, right))),
355 BinaryOperator::StrictNotEq => Ok(JsonValue::Bool(!json_strict_equals(left, right))),
356 BinaryOperator::Lt => compare_values(left, right, |a, b| a < b),
357 BinaryOperator::Lte => compare_values(left, right, |a, b| a <= b),
358 BinaryOperator::Gt => compare_values(left, right, |a, b| a > b),
359 BinaryOperator::Gte => compare_values(left, right, |a, b| a >= b),
360 BinaryOperator::And => Ok(if is_truthy(left) {
361 right.clone()
362 } else {
363 left.clone()
364 }),
365 BinaryOperator::Or => Ok(if is_truthy(left) {
366 left.clone()
367 } else {
368 right.clone()
369 }),
370 BinaryOperator::Concat => {
371 let l_str = json_to_string(left);
372 let r_str = json_to_string(right);
373 Ok(JsonValue::String(format!("{}{}", l_str, r_str)))
374 },
375 }
376}
377
378pub fn evaluate_unary_op(
380 op: UnaryOperator,
381 value: &JsonValue,
382) -> Result<JsonValue, ExecutionError> {
383 match op {
384 UnaryOperator::Not => Ok(JsonValue::Bool(!is_truthy(value))),
385 UnaryOperator::Plus => {
386 let n = to_number(value);
387 Ok(serde_json::Number::from_f64(n)
388 .map(JsonValue::Number)
389 .unwrap_or(JsonValue::Null))
390 },
391 UnaryOperator::Neg => {
392 let n = to_number(value);
393 Ok(JsonValue::Number(
394 serde_json::Number::from_f64(-n).unwrap_or_else(|| serde_json::Number::from(0)),
395 ))
396 },
397 UnaryOperator::TypeOf => {
398 let type_str = match value {
399 JsonValue::Null => "object", JsonValue::Bool(_) => "boolean",
401 JsonValue::Number(_) => "number",
402 JsonValue::String(_) => "string",
403 JsonValue::Array(_) => "object",
404 JsonValue::Object(_) => "object",
405 };
406 Ok(JsonValue::String(type_str.to_string()))
407 },
408 }
409}
410
411pub fn is_truthy(value: &JsonValue) -> bool {
413 match value {
414 JsonValue::Null => false,
415 JsonValue::Bool(b) => *b,
416 JsonValue::Number(n) => n.as_f64().map(|f| f != 0.0 && !f.is_nan()).unwrap_or(false),
417 JsonValue::String(s) => !s.is_empty(),
418 JsonValue::Array(_) => true,
419 JsonValue::Object(_) => true,
420 }
421}
422
423pub fn is_nullish(value: &JsonValue) -> bool {
425 matches!(value, JsonValue::Null)
426}
427
428pub fn to_number(value: &JsonValue) -> f64 {
430 match value {
431 JsonValue::Null => 0.0,
432 JsonValue::Bool(b) => {
433 if *b {
434 1.0
435 } else {
436 0.0
437 }
438 },
439 JsonValue::Number(n) => n.as_f64().unwrap_or(f64::NAN),
440 JsonValue::String(s) => s.parse().unwrap_or(f64::NAN),
441 JsonValue::Array(_) | JsonValue::Object(_) => f64::NAN,
442 }
443}
444
445fn add_values(left: &JsonValue, right: &JsonValue) -> Result<JsonValue, ExecutionError> {
447 if matches!(left, JsonValue::String(_)) || matches!(right, JsonValue::String(_)) {
449 let l_str = json_to_string(left);
450 let r_str = json_to_string(right);
451 return Ok(JsonValue::String(format!("{}{}", l_str, r_str)));
452 }
453
454 let l = to_number(left);
456 let r = to_number(right);
457 Ok(JsonValue::Number(
458 serde_json::Number::from_f64(l + r).unwrap_or_else(|| serde_json::Number::from(0)),
459 ))
460}
461
462fn numeric_op<F>(left: &JsonValue, right: &JsonValue, op: F) -> Result<JsonValue, ExecutionError>
464where
465 F: Fn(f64, f64) -> f64,
466{
467 let l = to_number(left);
468 let r = to_number(right);
469 let result = op(l, r);
470 Ok(JsonValue::Number(
471 serde_json::Number::from_f64(result).unwrap_or_else(|| serde_json::Number::from(0)),
472 ))
473}
474
475fn compare_values<F>(
477 left: &JsonValue,
478 right: &JsonValue,
479 op: F,
480) -> Result<JsonValue, ExecutionError>
481where
482 F: Fn(f64, f64) -> bool,
483{
484 let l = to_number(left);
485 let r = to_number(right);
486 Ok(JsonValue::Bool(op(l, r)))
487}
488
489fn json_equals(left: &JsonValue, right: &JsonValue) -> bool {
491 match (left, right) {
492 (JsonValue::Null, JsonValue::Null) => true,
493 (JsonValue::Bool(a), JsonValue::Bool(b)) => a == b,
494 (JsonValue::Number(a), JsonValue::Number(b)) => {
495 a.as_f64().unwrap_or(f64::NAN) == b.as_f64().unwrap_or(f64::NAN)
496 },
497 (JsonValue::String(a), JsonValue::String(b)) => a == b,
498 (JsonValue::Number(n), JsonValue::String(s))
500 | (JsonValue::String(s), JsonValue::Number(n)) => {
501 if let Ok(parsed) = s.parse::<f64>() {
502 n.as_f64().unwrap_or(f64::NAN) == parsed
503 } else {
504 false
505 }
506 },
507 _ => false,
508 }
509}
510
511fn json_strict_equals(left: &JsonValue, right: &JsonValue) -> bool {
513 match (left, right) {
514 (JsonValue::Null, JsonValue::Null) => true,
515 (JsonValue::Bool(a), JsonValue::Bool(b)) => a == b,
516 (JsonValue::Number(a), JsonValue::Number(b)) => {
517 a.as_f64().unwrap_or(f64::NAN) == b.as_f64().unwrap_or(f64::NAN)
518 },
519 (JsonValue::String(a), JsonValue::String(b)) => a == b,
520 (JsonValue::Array(a), JsonValue::Array(b)) => std::ptr::eq(a, b), (JsonValue::Object(a), JsonValue::Object(b)) => std::ptr::eq(a, b), _ => false,
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub enum JsonStringMode {
529 JavaScript,
531 Json,
533}
534
535pub fn json_to_string_with_mode(value: &JsonValue, mode: JsonStringMode) -> String {
537 match value {
538 JsonValue::Null => "null".to_string(),
539 JsonValue::Bool(b) => b.to_string(),
540 JsonValue::Number(n) => n.to_string(),
541 JsonValue::String(s) => s.clone(),
542 JsonValue::Array(_) => match mode {
543 JsonStringMode::JavaScript => value.to_string(),
544 JsonStringMode::Json => serde_json::to_string(value).unwrap_or_default(),
545 },
546 JsonValue::Object(_) => match mode {
547 JsonStringMode::JavaScript => "[object Object]".to_string(),
548 JsonStringMode::Json => serde_json::to_string(value).unwrap_or_default(),
549 },
550 }
551}
552
553pub fn json_to_string(value: &JsonValue) -> String {
557 json_to_string_with_mode(value, JsonStringMode::JavaScript)
558}
559
560#[inline]
562fn restore_scope_var(
563 scope: &mut HashMap<String, JsonValue>,
564 key: &str,
565 previous: Option<JsonValue>,
566) {
567 match previous {
568 Some(prev) => {
569 scope.insert(key.to_string(), prev);
570 },
571 None => {
572 scope.remove(key);
573 },
574 }
575}
576
577pub fn evaluate_array_method_with_scope<V: VariableProvider>(
583 arr_value: &JsonValue,
584 method: &ArrayMethodCall,
585 global_vars: &V,
586 local_vars: &mut HashMap<String, JsonValue>,
587) -> Result<JsonValue, ExecutionError> {
588 if let JsonValue::String(s) = arr_value {
590 return evaluate_string_method(s, method, global_vars, local_vars);
591 }
592
593 let arr = match arr_value {
594 JsonValue::Array(a) => a.clone(),
595 _ => {
596 return Err(ExecutionError::RuntimeError {
597 message: "Method called on non-array and non-string".into(),
598 })
599 },
600 };
601
602 match method {
603 ArrayMethodCall::Length => Ok(JsonValue::Number((arr.len() as i64).into())),
604 ArrayMethodCall::Map { item_var, body } => {
605 eval_array_map(arr, item_var, body, global_vars, local_vars)
606 },
607 ArrayMethodCall::Filter {
608 item_var,
609 predicate,
610 } => eval_array_filter(arr, item_var, predicate, global_vars, local_vars),
611 ArrayMethodCall::Find {
612 item_var,
613 predicate,
614 } => eval_array_find(arr, item_var, predicate, global_vars, local_vars),
615 ArrayMethodCall::Some {
616 item_var,
617 predicate,
618 } => eval_array_some(arr, item_var, predicate, global_vars, local_vars),
619 ArrayMethodCall::Every {
620 item_var,
621 predicate,
622 } => eval_array_every(arr, item_var, predicate, global_vars, local_vars),
623 ArrayMethodCall::FlatMap { item_var, body } => {
624 eval_array_flat_map(arr, item_var, body, global_vars, local_vars)
625 },
626 ArrayMethodCall::Reduce {
627 acc_var,
628 item_var,
629 body,
630 initial,
631 } => eval_array_reduce(
632 arr,
633 acc_var,
634 item_var,
635 body,
636 initial,
637 global_vars,
638 local_vars,
639 ),
640 ArrayMethodCall::Slice { start, end } => Ok(eval_array_slice(arr, *start, *end)),
641 ArrayMethodCall::Concat { other } => eval_array_concat(arr, other, global_vars, local_vars),
642 ArrayMethodCall::Push { item } => eval_array_push(arr, item, global_vars, local_vars),
643 ArrayMethodCall::Join { separator } => Ok(eval_array_join(&arr, separator.as_deref())),
644 ArrayMethodCall::Reverse => {
645 let mut reversed = arr;
646 reversed.reverse();
647 Ok(JsonValue::Array(reversed))
648 },
649 ArrayMethodCall::Sort { comparator } => {
650 eval_array_sort(arr, comparator.as_ref(), global_vars, local_vars)
651 },
652 ArrayMethodCall::Flat => Ok(JsonValue::Array(flatten_array(arr, 1))),
653 ArrayMethodCall::Includes { item } => {
654 eval_array_includes(&arr, item, global_vars, local_vars)
655 },
656 ArrayMethodCall::IndexOf { item } => {
657 eval_array_index_of(&arr, item, global_vars, local_vars)
658 },
659 ArrayMethodCall::First => Ok(arr.into_iter().next().unwrap_or(JsonValue::Null)),
660 ArrayMethodCall::Last => Ok(arr.into_iter().last().unwrap_or(JsonValue::Null)),
661 ArrayMethodCall::ToString => Ok(JsonValue::String(
662 serde_json::to_string(&JsonValue::Array(arr)).unwrap_or_default(),
663 )),
664
665 ArrayMethodCall::ToLowerCase
667 | ArrayMethodCall::ToUpperCase
668 | ArrayMethodCall::StartsWith { .. }
669 | ArrayMethodCall::EndsWith { .. }
670 | ArrayMethodCall::Trim
671 | ArrayMethodCall::Replace { .. }
672 | ArrayMethodCall::Split { .. }
673 | ArrayMethodCall::Substring { .. } => Err(ExecutionError::RuntimeError {
674 message: "This method is only available on strings, not arrays".into(),
675 }),
676 }
677}
678
679fn eval_array_map<V: VariableProvider>(
681 arr: Vec<JsonValue>,
682 item_var: &str,
683 body: &ValueExpr,
684 global_vars: &V,
685 local_vars: &mut HashMap<String, JsonValue>,
686) -> Result<JsonValue, ExecutionError> {
687 let mut results = Vec::with_capacity(arr.len());
688 for item in arr {
689 let old = local_vars.insert(item_var.to_string(), item);
690 let value = evaluate_with_scope(body, global_vars, local_vars);
691 restore_scope_var(local_vars, item_var, old);
692 results.push(value?);
693 }
694 Ok(JsonValue::Array(results))
695}
696
697fn eval_array_filter<V: VariableProvider>(
699 arr: Vec<JsonValue>,
700 item_var: &str,
701 predicate: &ValueExpr,
702 global_vars: &V,
703 local_vars: &mut HashMap<String, JsonValue>,
704) -> Result<JsonValue, ExecutionError> {
705 let mut results = Vec::new();
706 for item in arr {
707 let old = local_vars.insert(item_var.to_string(), item.clone());
708 let keep = evaluate_with_scope(predicate, global_vars, local_vars);
709 restore_scope_var(local_vars, item_var, old);
710 if is_truthy(&keep?) {
711 results.push(item);
712 }
713 }
714 Ok(JsonValue::Array(results))
715}
716
717fn eval_array_find<V: VariableProvider>(
719 arr: Vec<JsonValue>,
720 item_var: &str,
721 predicate: &ValueExpr,
722 global_vars: &V,
723 local_vars: &mut HashMap<String, JsonValue>,
724) -> Result<JsonValue, ExecutionError> {
725 for item in arr {
726 let old = local_vars.insert(item_var.to_string(), item.clone());
727 let found = evaluate_with_scope(predicate, global_vars, local_vars);
728 restore_scope_var(local_vars, item_var, old);
729 if is_truthy(&found?) {
730 return Ok(item);
731 }
732 }
733 Ok(JsonValue::Null)
734}
735
736fn eval_array_some<V: VariableProvider>(
738 arr: Vec<JsonValue>,
739 item_var: &str,
740 predicate: &ValueExpr,
741 global_vars: &V,
742 local_vars: &mut HashMap<String, JsonValue>,
743) -> Result<JsonValue, ExecutionError> {
744 for item in arr {
745 let old = local_vars.insert(item_var.to_string(), item);
746 let found = evaluate_with_scope(predicate, global_vars, local_vars);
747 restore_scope_var(local_vars, item_var, old);
748 if is_truthy(&found?) {
749 return Ok(JsonValue::Bool(true));
750 }
751 }
752 Ok(JsonValue::Bool(false))
753}
754
755fn eval_array_every<V: VariableProvider>(
757 arr: Vec<JsonValue>,
758 item_var: &str,
759 predicate: &ValueExpr,
760 global_vars: &V,
761 local_vars: &mut HashMap<String, JsonValue>,
762) -> Result<JsonValue, ExecutionError> {
763 for item in arr {
764 let old = local_vars.insert(item_var.to_string(), item);
765 let found = evaluate_with_scope(predicate, global_vars, local_vars);
766 restore_scope_var(local_vars, item_var, old);
767 if !is_truthy(&found?) {
768 return Ok(JsonValue::Bool(false));
769 }
770 }
771 Ok(JsonValue::Bool(true))
772}
773
774fn eval_array_flat_map<V: VariableProvider>(
776 arr: Vec<JsonValue>,
777 item_var: &str,
778 body: &ValueExpr,
779 global_vars: &V,
780 local_vars: &mut HashMap<String, JsonValue>,
781) -> Result<JsonValue, ExecutionError> {
782 let mut results = Vec::new();
783 for item in arr {
784 let old = local_vars.insert(item_var.to_string(), item);
785 let value = evaluate_with_scope(body, global_vars, local_vars);
786 restore_scope_var(local_vars, item_var, old);
787 match value? {
788 JsonValue::Array(items) => results.extend(items),
789 other => results.push(other),
790 }
791 }
792 Ok(JsonValue::Array(results))
793}
794
795fn eval_array_reduce<V: VariableProvider>(
797 arr: Vec<JsonValue>,
798 acc_var: &str,
799 item_var: &str,
800 body: &ValueExpr,
801 initial: &ValueExpr,
802 global_vars: &V,
803 local_vars: &mut HashMap<String, JsonValue>,
804) -> Result<JsonValue, ExecutionError> {
805 let mut acc = evaluate_with_scope(initial, global_vars, local_vars)?;
806 for item in arr {
807 let old_acc = local_vars.insert(acc_var.to_string(), acc.clone());
808 let old_item = local_vars.insert(item_var.to_string(), item);
809 let result = evaluate_with_scope(body, global_vars, local_vars);
810 restore_scope_var(local_vars, acc_var, old_acc);
811 restore_scope_var(local_vars, item_var, old_item);
812 acc = result?;
813 }
814 Ok(acc)
815}
816
817fn clamp_slice_bounds(len: usize, start: usize, end: Option<usize>) -> (usize, usize) {
820 let start = start.min(len);
821 let end = end.unwrap_or(len).min(len);
822 (start, end.saturating_sub(start))
823}
824
825fn eval_array_slice(arr: Vec<JsonValue>, start: usize, end: Option<usize>) -> JsonValue {
827 let (skip, take) = clamp_slice_bounds(arr.len(), start, end);
828 let sliced: Vec<JsonValue> = arr.into_iter().skip(skip).take(take).collect();
829 JsonValue::Array(sliced)
830}
831
832fn eval_array_concat<V: VariableProvider>(
834 arr: Vec<JsonValue>,
835 other: &ValueExpr,
836 global_vars: &V,
837 local_vars: &mut HashMap<String, JsonValue>,
838) -> Result<JsonValue, ExecutionError> {
839 let mut result = arr;
840 let other_val = evaluate_with_scope(other, global_vars, local_vars)?;
841 if let JsonValue::Array(other_arr) = other_val {
842 result.extend(other_arr);
843 } else {
844 result.push(other_val);
845 }
846 Ok(JsonValue::Array(result))
847}
848
849fn eval_array_push<V: VariableProvider>(
851 arr: Vec<JsonValue>,
852 item: &ValueExpr,
853 global_vars: &V,
854 local_vars: &mut HashMap<String, JsonValue>,
855) -> Result<JsonValue, ExecutionError> {
856 let mut result = arr;
857 let item_val = evaluate_with_scope(item, global_vars, local_vars)?;
858 result.push(item_val);
859 Ok(JsonValue::Array(result))
860}
861
862fn eval_array_join(arr: &[JsonValue], separator: Option<&str>) -> JsonValue {
864 let sep = separator.unwrap_or(",");
865 let joined: String = arr.iter().map(json_to_string).collect::<Vec<_>>().join(sep);
866 JsonValue::String(joined)
867}
868
869fn eval_array_sort<V: VariableProvider>(
872 arr: Vec<JsonValue>,
873 comparator: Option<&(String, String, Box<ValueExpr>)>,
874 global_vars: &V,
875 local_vars: &HashMap<String, JsonValue>,
876) -> Result<JsonValue, ExecutionError> {
877 let mut sorted = arr;
878 match comparator {
879 None => sorted.sort_by_key(json_to_string),
880 Some((a_var, b_var, body)) => {
881 sort_with_comparator(&mut sorted, a_var, b_var, body, global_vars, local_vars)?;
882 },
883 }
884 Ok(JsonValue::Array(sorted))
885}
886
887fn sort_with_comparator<V: VariableProvider>(
890 sorted: &mut [JsonValue],
891 a_var: &str,
892 b_var: &str,
893 body: &ValueExpr,
894 global_vars: &V,
895 local_vars: &HashMap<String, JsonValue>,
896) -> Result<(), ExecutionError> {
897 let mut sort_error: Option<ExecutionError> = None;
898 sorted.sort_by(|a, b| {
899 if sort_error.is_some() {
900 return std::cmp::Ordering::Equal;
901 }
902 let mut merged = local_vars.clone();
903 merged.insert(a_var.to_string(), a.clone());
904 merged.insert(b_var.to_string(), b.clone());
905 match evaluate_with_scope(body, global_vars, &merged) {
906 Ok(result) => comparator_result_to_ordering(&result),
907 Err(e) => {
908 sort_error = Some(e);
909 std::cmp::Ordering::Equal
910 },
911 }
912 });
913 match sort_error {
914 Some(e) => Err(e),
915 None => Ok(()),
916 }
917}
918
919#[inline]
921fn comparator_result_to_ordering(result: &JsonValue) -> std::cmp::Ordering {
922 let n = to_number(result);
923 if n < 0.0 {
924 std::cmp::Ordering::Less
925 } else if n > 0.0 {
926 std::cmp::Ordering::Greater
927 } else {
928 std::cmp::Ordering::Equal
929 }
930}
931
932fn eval_array_includes<V: VariableProvider>(
934 arr: &[JsonValue],
935 item: &ValueExpr,
936 global_vars: &V,
937 local_vars: &mut HashMap<String, JsonValue>,
938) -> Result<JsonValue, ExecutionError> {
939 let search_val = evaluate_with_scope(item, global_vars, local_vars)?;
940 let found = arr.iter().any(|elem| json_equals(elem, &search_val));
941 Ok(JsonValue::Bool(found))
942}
943
944fn eval_array_index_of<V: VariableProvider>(
946 arr: &[JsonValue],
947 item: &ValueExpr,
948 global_vars: &V,
949 local_vars: &mut HashMap<String, JsonValue>,
950) -> Result<JsonValue, ExecutionError> {
951 let search_val = evaluate_with_scope(item, global_vars, local_vars)?;
952 for (i, arr_item) in arr.iter().enumerate() {
953 if json_equals(arr_item, &search_val) {
954 return Ok(JsonValue::Number((i as i64).into()));
955 }
956 }
957 Ok(JsonValue::Number((-1_i64).into()))
958}
959
960fn evaluate_string_method<V: VariableProvider>(
965 s: &str,
966 method: &ArrayMethodCall,
967 global_vars: &V,
968 local_vars: &HashMap<String, JsonValue>,
969) -> Result<JsonValue, ExecutionError> {
970 match method {
971 ArrayMethodCall::Length => Ok(JsonValue::Number((s.chars().count() as i64).into())),
972 ArrayMethodCall::Includes { item } => {
973 eval_string_bool_predicate(s, item, global_vars, local_vars, |s, sub| s.contains(sub))
974 },
975 ArrayMethodCall::IndexOf { item } => eval_string_index_of(s, item, global_vars, local_vars),
976 ArrayMethodCall::Slice { start, end } => Ok(eval_string_slice(s, *start, *end)),
977 ArrayMethodCall::Concat { other } => eval_string_concat(s, other, global_vars, local_vars),
978 ArrayMethodCall::ToLowerCase => Ok(JsonValue::String(s.to_lowercase())),
979 ArrayMethodCall::ToUpperCase => Ok(JsonValue::String(s.to_uppercase())),
980 ArrayMethodCall::StartsWith { search } => {
981 eval_string_bool_predicate(s, search, global_vars, local_vars, |s, sub| {
982 s.starts_with(sub)
983 })
984 },
985 ArrayMethodCall::EndsWith { search } => {
986 eval_string_bool_predicate(s, search, global_vars, local_vars, |s, sub| {
987 s.ends_with(sub)
988 })
989 },
990 ArrayMethodCall::Trim => Ok(JsonValue::String(s.trim().to_string())),
991 ArrayMethodCall::Replace {
992 search,
993 replacement,
994 } => eval_string_replace(s, search, replacement, global_vars, local_vars),
995 ArrayMethodCall::Split { separator } => {
996 eval_string_split(s, separator, global_vars, local_vars)
997 },
998 ArrayMethodCall::Substring { start, end } => {
999 eval_string_substring(s, start, end.as_deref(), global_vars, local_vars)
1000 },
1001 ArrayMethodCall::ToString => Ok(JsonValue::String(s.to_string())),
1002
1003 _ => Err(ExecutionError::RuntimeError {
1005 message: format!(
1006 "String does not support {} — use it only on arrays",
1007 array_only_method_label(method)
1008 ),
1009 }),
1010 }
1011}
1012
1013fn array_only_method_label(method: &ArrayMethodCall) -> &'static str {
1015 match method {
1016 ArrayMethodCall::Map { .. } => ".map()",
1017 ArrayMethodCall::Filter { .. } => ".filter()",
1018 ArrayMethodCall::Find { .. } => ".find()",
1019 ArrayMethodCall::Some { .. } => ".some()",
1020 ArrayMethodCall::Every { .. } => ".every()",
1021 ArrayMethodCall::Reduce { .. } => ".reduce()",
1022 ArrayMethodCall::FlatMap { .. } => ".flatMap()",
1023 ArrayMethodCall::Push { .. } => ".push()",
1024 ArrayMethodCall::Join { .. } => ".join()",
1025 ArrayMethodCall::Reverse => ".reverse()",
1026 ArrayMethodCall::Sort { .. } => ".sort()",
1027 ArrayMethodCall::Flat => ".flat()",
1028 ArrayMethodCall::First => ".first()",
1029 ArrayMethodCall::Last => ".last()",
1030 _ => "this method",
1031 }
1032}
1033
1034fn eval_string_bool_predicate<V, P>(
1037 s: &str,
1038 needle: &ValueExpr,
1039 global_vars: &V,
1040 local_vars: &HashMap<String, JsonValue>,
1041 predicate: P,
1042) -> Result<JsonValue, ExecutionError>
1043where
1044 V: VariableProvider,
1045 P: FnOnce(&str, &str) -> bool,
1046{
1047 let search_val = evaluate_with_scope(needle, global_vars, local_vars)?;
1048 match search_val {
1049 JsonValue::String(sub) => Ok(JsonValue::Bool(predicate(s, sub.as_str()))),
1050 _ => Ok(JsonValue::Bool(false)),
1051 }
1052}
1053
1054fn eval_string_index_of<V: VariableProvider>(
1056 s: &str,
1057 item: &ValueExpr,
1058 global_vars: &V,
1059 local_vars: &HashMap<String, JsonValue>,
1060) -> Result<JsonValue, ExecutionError> {
1061 let search_val = evaluate_with_scope(item, global_vars, local_vars)?;
1062 match search_val {
1063 JsonValue::String(sub) => {
1064 let idx = char_index_of(s, sub.as_str()).unwrap_or(-1);
1065 Ok(JsonValue::Number(idx.into()))
1066 },
1067 _ => Ok(JsonValue::Number((-1_i64).into())),
1068 }
1069}
1070
1071fn char_index_of(haystack: &str, needle: &str) -> Option<i64> {
1073 haystack
1074 .char_indices()
1075 .zip(0i64..)
1076 .find_map(|((byte_pos, _), char_idx)| {
1077 if haystack[byte_pos..].starts_with(needle) {
1078 Some(char_idx)
1079 } else {
1080 None
1081 }
1082 })
1083}
1084
1085fn eval_string_slice(s: &str, start: usize, end: Option<usize>) -> JsonValue {
1087 let (skip, take) = clamp_slice_bounds(s.chars().count(), start, end);
1088 let sliced: String = s.chars().skip(skip).take(take).collect();
1089 JsonValue::String(sliced)
1090}
1091
1092fn eval_string_concat<V: VariableProvider>(
1094 s: &str,
1095 other: &ValueExpr,
1096 global_vars: &V,
1097 local_vars: &HashMap<String, JsonValue>,
1098) -> Result<JsonValue, ExecutionError> {
1099 let other_val = evaluate_with_scope(other, global_vars, local_vars)?;
1100 Ok(JsonValue::String(format!(
1101 "{}{}",
1102 s,
1103 json_to_string(&other_val)
1104 )))
1105}
1106
1107fn eval_string_replace<V: VariableProvider>(
1110 s: &str,
1111 search: &ValueExpr,
1112 replacement: &ValueExpr,
1113 global_vars: &V,
1114 local_vars: &HashMap<String, JsonValue>,
1115) -> Result<JsonValue, ExecutionError> {
1116 let search_val = evaluate_with_scope(search, global_vars, local_vars)?;
1117 let repl_val = evaluate_with_scope(replacement, global_vars, local_vars)?;
1118 match (search_val, repl_val) {
1119 (JsonValue::String(needle), JsonValue::String(repl)) => Ok(JsonValue::String(s.replacen(
1120 needle.as_str(),
1121 repl.as_str(),
1122 1,
1123 ))),
1124 _ => Ok(JsonValue::String(s.to_string())),
1125 }
1126}
1127
1128fn eval_string_split<V: VariableProvider>(
1131 s: &str,
1132 separator: &ValueExpr,
1133 global_vars: &V,
1134 local_vars: &HashMap<String, JsonValue>,
1135) -> Result<JsonValue, ExecutionError> {
1136 let sep_val = evaluate_with_scope(separator, global_vars, local_vars)?;
1137 match sep_val {
1138 JsonValue::String(sep) if sep.is_empty() => Ok(JsonValue::Array(split_chars_capped(s))),
1139 JsonValue::String(sep) => Ok(JsonValue::Array(split_by(s, sep.as_str()))),
1140 _ => Ok(JsonValue::Array(vec![JsonValue::String(s.to_string())])),
1141 }
1142}
1143
1144fn split_chars_capped(s: &str) -> Vec<JsonValue> {
1146 s.chars()
1147 .take(10_000)
1148 .map(|c| JsonValue::String(c.to_string()))
1149 .collect()
1150}
1151
1152fn split_by(s: &str, sep: &str) -> Vec<JsonValue> {
1154 s.split(sep)
1155 .map(|p| JsonValue::String(p.to_string()))
1156 .collect()
1157}
1158
1159fn eval_string_substring<V: VariableProvider>(
1163 s: &str,
1164 start: &ValueExpr,
1165 end: Option<&ValueExpr>,
1166 global_vars: &V,
1167 local_vars: &HashMap<String, JsonValue>,
1168) -> Result<JsonValue, ExecutionError> {
1169 let start_idx = eval_substring_index(start, global_vars, local_vars, 0)?;
1170 let end_idx = match end {
1171 Some(end_expr) => eval_substring_index(end_expr, global_vars, local_vars, usize::MAX)?,
1172 None => usize::MAX,
1173 };
1174 let (lo, hi) = if start_idx > end_idx {
1175 (end_idx, start_idx)
1176 } else {
1177 (start_idx, end_idx)
1178 };
1179 let result: String = s.chars().skip(lo).take(hi.saturating_sub(lo)).collect();
1180 Ok(JsonValue::String(result))
1181}
1182
1183fn eval_substring_index<V: VariableProvider>(
1185 expr: &ValueExpr,
1186 global_vars: &V,
1187 local_vars: &HashMap<String, JsonValue>,
1188 default: usize,
1189) -> Result<usize, ExecutionError> {
1190 let val = evaluate_with_scope(expr, global_vars, local_vars)?;
1191 Ok(match val {
1192 JsonValue::Number(n) => n.as_u64().unwrap_or(0) as usize,
1193 _ => default,
1194 })
1195}
1196
1197fn evaluate_builtin(
1199 func: &BuiltinFunction,
1200 args: &[JsonValue],
1201) -> Result<JsonValue, ExecutionError> {
1202 fn number_or_null(n: f64) -> JsonValue {
1204 serde_json::Number::from_f64(n)
1205 .map(JsonValue::Number)
1206 .unwrap_or(JsonValue::Null)
1207 }
1208
1209 match func {
1210 BuiltinFunction::ParseFloat | BuiltinFunction::NumberCast => {
1211 let val = args.first().unwrap_or(&JsonValue::Null);
1212 Ok(number_or_null(to_number(val)))
1213 },
1214 BuiltinFunction::ParseInt => {
1215 let val = args.first().unwrap_or(&JsonValue::Null);
1216 let n = to_number(val);
1217 if n.is_finite() {
1218 Ok(JsonValue::Number((n as i64).into()))
1219 } else {
1220 Ok(JsonValue::Null) }
1222 },
1223 BuiltinFunction::MathAbs => {
1224 let val = args.first().unwrap_or(&JsonValue::Null);
1225 Ok(number_or_null(to_number(val).abs()))
1226 },
1227 BuiltinFunction::MathRound => {
1228 let val = args.first().unwrap_or(&JsonValue::Null);
1229 Ok(number_or_null(to_number(val).round()))
1230 },
1231 BuiltinFunction::MathFloor => {
1232 let val = args.first().unwrap_or(&JsonValue::Null);
1233 Ok(number_or_null(to_number(val).floor()))
1234 },
1235 BuiltinFunction::MathCeil => {
1236 let val = args.first().unwrap_or(&JsonValue::Null);
1237 Ok(number_or_null(to_number(val).ceil()))
1238 },
1239 BuiltinFunction::MathMax => {
1240 if args.is_empty() {
1241 return Ok(number_or_null(f64::NEG_INFINITY));
1242 }
1243 let mut result = f64::NEG_INFINITY;
1244 for arg in args {
1245 result = result.max(to_number(arg));
1246 }
1247 Ok(number_or_null(result))
1248 },
1249 BuiltinFunction::MathMin => {
1250 if args.is_empty() {
1251 return Ok(number_or_null(f64::INFINITY));
1252 }
1253 let mut result = f64::INFINITY;
1254 for arg in args {
1255 result = result.min(to_number(arg));
1256 }
1257 Ok(number_or_null(result))
1258 },
1259 BuiltinFunction::ObjectKeys => {
1260 let val = args.first().unwrap_or(&JsonValue::Null);
1261 match val {
1262 JsonValue::Object(map) => {
1263 let keys: Vec<JsonValue> =
1264 map.keys().map(|k| JsonValue::String(k.clone())).collect();
1265 Ok(JsonValue::Array(keys))
1266 },
1267 _ => Ok(JsonValue::Array(vec![])),
1268 }
1269 },
1270 BuiltinFunction::ObjectValues => {
1271 let val = args.first().unwrap_or(&JsonValue::Null);
1272 match val {
1273 JsonValue::Object(map) => {
1274 let values: Vec<JsonValue> = map.values().cloned().collect();
1275 Ok(JsonValue::Array(values))
1276 },
1277 _ => Ok(JsonValue::Array(vec![])),
1278 }
1279 },
1280 BuiltinFunction::ObjectEntries => {
1281 let val = args.first().unwrap_or(&JsonValue::Null);
1282 match val {
1283 JsonValue::Object(map) => {
1284 let entries: Vec<JsonValue> = map
1285 .iter()
1286 .map(|(k, v)| {
1287 JsonValue::Array(vec![JsonValue::String(k.clone()), v.clone()])
1288 })
1289 .collect();
1290 Ok(JsonValue::Array(entries))
1291 },
1292 _ => Ok(JsonValue::Array(vec![])),
1293 }
1294 },
1295 }
1296}
1297
1298fn flatten_array(arr: Vec<JsonValue>, depth: usize) -> Vec<JsonValue> {
1300 if depth == 0 {
1301 return arr;
1302 }
1303
1304 let mut result = Vec::new();
1305 for item in arr {
1306 if let JsonValue::Array(inner) = item {
1307 result.extend(flatten_array(inner, depth - 1));
1308 } else {
1309 result.push(item);
1310 }
1311 }
1312 result
1313}
1314
1315pub fn evaluate_number_method(
1317 num_value: &JsonValue,
1318 method: &NumberMethodCall,
1319) -> Result<JsonValue, ExecutionError> {
1320 let num = match num_value {
1321 JsonValue::Number(n) => n.as_f64().unwrap_or(0.0),
1322 JsonValue::String(s) => s.parse::<f64>().unwrap_or(0.0),
1323 _ => {
1324 return Err(ExecutionError::RuntimeError {
1325 message: format!("Number method called on non-number: {:?}", num_value),
1326 })
1327 },
1328 };
1329
1330 match method {
1331 NumberMethodCall::ToFixed { digits } => {
1332 let formatted = format!("{:.prec$}", num, prec = *digits);
1333 Ok(JsonValue::String(formatted))
1334 },
1335 NumberMethodCall::ToString => Ok(JsonValue::String(num.to_string())),
1336 }
1337}
1338
1339pub fn evaluate(
1342 expr: &ValueExpr,
1343 variables: &HashMap<String, JsonValue>,
1344) -> Result<JsonValue, ExecutionError> {
1345 evaluate_with_scope(expr, variables, &HashMap::new())
1346}
1347
1348pub fn evaluate_with_binding(
1351 expr: &ValueExpr,
1352 variables: &HashMap<String, JsonValue>,
1353 var: &str,
1354 value: &JsonValue,
1355) -> Result<JsonValue, ExecutionError> {
1356 let mut local_vars = HashMap::new();
1357 local_vars.insert(var.to_string(), value.clone());
1358 evaluate_with_scope(expr, variables, &local_vars)
1359}
1360
1361pub fn evaluate_with_two_bindings(
1364 expr: &ValueExpr,
1365 variables: &HashMap<String, JsonValue>,
1366 var1: &str,
1367 value1: &JsonValue,
1368 var2: &str,
1369 value2: &JsonValue,
1370) -> Result<JsonValue, ExecutionError> {
1371 let mut local_vars = HashMap::new();
1372 local_vars.insert(var1.to_string(), value1.clone());
1373 local_vars.insert(var2.to_string(), value2.clone());
1374 evaluate_with_scope(expr, variables, &local_vars)
1375}
1376
1377#[cfg(test)]
1378mod tests {
1379 use super::*;
1380
1381 #[test]
1382 fn test_undefined_global() {
1383 let vars: HashMap<String, JsonValue> = HashMap::new();
1384 let expr = ValueExpr::Variable("undefined".to_string());
1385 let result = evaluate(&expr, &vars).unwrap();
1386 assert_eq!(result, JsonValue::Null);
1387 }
1388
1389 #[test]
1390 fn test_undefined_variable_error() {
1391 let vars: HashMap<String, JsonValue> = HashMap::new();
1392 let expr = ValueExpr::Variable("nonexistent".to_string());
1393 let result = evaluate(&expr, &vars);
1394 assert!(result.is_err());
1395 match result {
1396 Err(ExecutionError::RuntimeError { message }) => {
1397 assert!(message.contains("Undefined variable"));
1398 },
1399 _ => panic!("Expected RuntimeError"),
1400 }
1401 }
1402
1403 #[test]
1404 fn test_comparison_with_undefined() {
1405 let mut vars: HashMap<String, JsonValue> = HashMap::new();
1406 vars.insert("x".to_string(), JsonValue::Null);
1407
1408 let expr = ValueExpr::BinaryOp {
1410 left: Box::new(ValueExpr::Variable("x".to_string())),
1411 op: BinaryOperator::StrictNotEq,
1412 right: Box::new(ValueExpr::Variable("undefined".to_string())),
1413 };
1414 let result = evaluate(&expr, &vars).unwrap();
1415 assert_eq!(result, JsonValue::Bool(false));
1417 }
1418
1419 fn eval_string_method(s: &str, method: ArrayMethodCall) -> Result<JsonValue, ExecutionError> {
1425 let mut vars = HashMap::new();
1426 vars.insert("s".to_string(), JsonValue::String(s.to_string()));
1427 let expr = ValueExpr::ArrayMethod {
1428 array: Box::new(ValueExpr::Variable("s".to_string())),
1429 method,
1430 };
1431 evaluate(&expr, &vars)
1432 }
1433
1434 #[test]
1435 fn test_string_length() {
1436 let result = eval_string_method("hello", ArrayMethodCall::Length).unwrap();
1437 assert_eq!(result, JsonValue::Number(5.into()));
1438 }
1439
1440 #[test]
1441 fn test_string_length_empty() {
1442 let result = eval_string_method("", ArrayMethodCall::Length).unwrap();
1443 assert_eq!(result, JsonValue::Number(0.into()));
1444 }
1445
1446 #[test]
1447 fn test_string_length_multibyte() {
1448 let result = eval_string_method("hi\u{1F600}", ArrayMethodCall::Length).unwrap();
1450 assert_eq!(result, JsonValue::Number(3.into()));
1451 }
1452
1453 #[test]
1454 fn test_string_includes_hit() {
1455 let result = eval_string_method(
1456 "hello world",
1457 ArrayMethodCall::Includes {
1458 item: Box::new(ValueExpr::Literal(JsonValue::String("world".into()))),
1459 },
1460 )
1461 .unwrap();
1462 assert_eq!(result, JsonValue::Bool(true));
1463 }
1464
1465 #[test]
1466 fn test_string_includes_miss() {
1467 let result = eval_string_method(
1468 "hello",
1469 ArrayMethodCall::Includes {
1470 item: Box::new(ValueExpr::Literal(JsonValue::String("xyz".into()))),
1471 },
1472 )
1473 .unwrap();
1474 assert_eq!(result, JsonValue::Bool(false));
1475 }
1476
1477 #[test]
1478 fn test_string_includes_non_string_arg() {
1479 let result = eval_string_method(
1481 "abc 42 def",
1482 ArrayMethodCall::Includes {
1483 item: Box::new(ValueExpr::Literal(JsonValue::Number(42.into()))),
1484 },
1485 )
1486 .unwrap();
1487 assert_eq!(result, JsonValue::Bool(false));
1488 }
1489
1490 #[test]
1491 fn test_string_index_of_found() {
1492 let result = eval_string_method(
1493 "abcdef",
1494 ArrayMethodCall::IndexOf {
1495 item: Box::new(ValueExpr::Literal(JsonValue::String("cd".into()))),
1496 },
1497 )
1498 .unwrap();
1499 assert_eq!(result, JsonValue::Number(2.into()));
1500 }
1501
1502 #[test]
1503 fn test_string_index_of_miss() {
1504 let result = eval_string_method(
1505 "abc",
1506 ArrayMethodCall::IndexOf {
1507 item: Box::new(ValueExpr::Literal(JsonValue::String("xyz".into()))),
1508 },
1509 )
1510 .unwrap();
1511 assert_eq!(result, JsonValue::Number((-1_i64).into()));
1512 }
1513
1514 #[test]
1515 fn test_string_index_of_non_string_arg() {
1516 let result = eval_string_method(
1517 "abc",
1518 ArrayMethodCall::IndexOf {
1519 item: Box::new(ValueExpr::Literal(JsonValue::Number(1.into()))),
1520 },
1521 )
1522 .unwrap();
1523 assert_eq!(result, JsonValue::Number((-1_i64).into()));
1524 }
1525
1526 #[test]
1527 fn test_string_slice() {
1528 let result = eval_string_method(
1529 "hello world",
1530 ArrayMethodCall::Slice {
1531 start: 0,
1532 end: Some(5),
1533 },
1534 )
1535 .unwrap();
1536 assert_eq!(result, JsonValue::String("hello".into()));
1537 }
1538
1539 #[test]
1540 fn test_string_slice_no_end() {
1541 let result = eval_string_method(
1542 "hello world",
1543 ArrayMethodCall::Slice {
1544 start: 6,
1545 end: None,
1546 },
1547 )
1548 .unwrap();
1549 assert_eq!(result, JsonValue::String("world".into()));
1550 }
1551
1552 #[test]
1553 fn test_string_slice_past_end() {
1554 let result = eval_string_method(
1555 "hi",
1556 ArrayMethodCall::Slice {
1557 start: 0,
1558 end: Some(100),
1559 },
1560 )
1561 .unwrap();
1562 assert_eq!(result, JsonValue::String("hi".into()));
1563 }
1564
1565 #[test]
1566 fn test_string_concat() {
1567 let result = eval_string_method(
1568 "hello",
1569 ArrayMethodCall::Concat {
1570 other: Box::new(ValueExpr::Literal(JsonValue::String(" world".into()))),
1571 },
1572 )
1573 .unwrap();
1574 assert_eq!(result, JsonValue::String("hello world".into()));
1575 }
1576
1577 #[test]
1578 fn test_string_concat_with_number() {
1579 let result = eval_string_method(
1580 "count: ",
1581 ArrayMethodCall::Concat {
1582 other: Box::new(ValueExpr::Literal(JsonValue::Number(42.into()))),
1583 },
1584 )
1585 .unwrap();
1586 assert_eq!(result, JsonValue::String("count: 42".into()));
1587 }
1588
1589 #[test]
1590 fn test_string_map_errors() {
1591 let result = eval_string_method(
1592 "hello",
1593 ArrayMethodCall::Map {
1594 item_var: "x".into(),
1595 body: Box::new(ValueExpr::Variable("x".into())),
1596 },
1597 );
1598 assert!(result.is_err());
1599 match result {
1600 Err(ExecutionError::RuntimeError { message }) => {
1601 assert!(message.contains("String does not support .map()"));
1602 },
1603 _ => panic!("Expected RuntimeError"),
1604 }
1605 }
1606
1607 #[test]
1608 fn test_string_filter_errors() {
1609 let result = eval_string_method(
1610 "hello",
1611 ArrayMethodCall::Filter {
1612 item_var: "x".into(),
1613 predicate: Box::new(ValueExpr::Literal(JsonValue::Bool(true))),
1614 },
1615 );
1616 assert!(result.is_err());
1617 match result {
1618 Err(ExecutionError::RuntimeError { message }) => {
1619 assert!(message.contains("String does not support .filter()"));
1620 },
1621 _ => panic!("Expected RuntimeError"),
1622 }
1623 }
1624
1625 #[test]
1626 fn test_array_methods_still_work_after_string_dispatch() {
1627 let mut vars = HashMap::new();
1629 vars.insert(
1630 "arr".to_string(),
1631 JsonValue::Array(vec![
1632 JsonValue::Number(1.into()),
1633 JsonValue::Number(2.into()),
1634 JsonValue::Number(3.into()),
1635 ]),
1636 );
1637 let expr = ValueExpr::ArrayMethod {
1638 array: Box::new(ValueExpr::Variable("arr".to_string())),
1639 method: ArrayMethodCall::Includes {
1640 item: Box::new(ValueExpr::Literal(JsonValue::Number(2.into()))),
1641 },
1642 };
1643 let result = evaluate(&expr, &vars).unwrap();
1644 assert_eq!(result, JsonValue::Bool(true));
1645 }
1646
1647 #[test]
1648 fn test_array_length_still_works() {
1649 let mut vars = HashMap::new();
1650 vars.insert(
1651 "arr".to_string(),
1652 JsonValue::Array(vec![JsonValue::Null; 4]),
1653 );
1654 let expr = ValueExpr::ArrayMethod {
1655 array: Box::new(ValueExpr::Variable("arr".to_string())),
1656 method: ArrayMethodCall::Length,
1657 };
1658 let result = evaluate(&expr, &vars).unwrap();
1659 assert_eq!(result, JsonValue::Number(4.into()));
1660 }
1661
1662 #[test]
1667 fn test_parse_float() {
1668 let vars = HashMap::new();
1669 let expr = ValueExpr::BuiltinCall {
1670 func: BuiltinFunction::ParseFloat,
1671 args: vec![ValueExpr::Literal(JsonValue::String("3.14".into()))],
1672 };
1673 let result = evaluate(&expr, &vars).unwrap();
1674 #[allow(clippy::approx_constant)]
1677 let expected = serde_json::json!(3.14);
1678 assert_eq!(result, expected);
1679 }
1680
1681 #[test]
1682 fn test_parse_float_integer() {
1683 let vars = HashMap::new();
1684 let expr = ValueExpr::BuiltinCall {
1685 func: BuiltinFunction::ParseFloat,
1686 args: vec![ValueExpr::Literal(JsonValue::String("42".into()))],
1687 };
1688 let result = evaluate(&expr, &vars).unwrap();
1689 assert_eq!(result, serde_json::json!(42.0));
1690 }
1691
1692 #[test]
1693 fn test_parse_float_nan_returns_null() {
1694 let vars = HashMap::new();
1695 let expr = ValueExpr::BuiltinCall {
1696 func: BuiltinFunction::ParseFloat,
1697 args: vec![ValueExpr::Literal(JsonValue::String("not-a-number".into()))],
1698 };
1699 let result = evaluate(&expr, &vars).unwrap();
1700 assert_eq!(result, JsonValue::Null);
1701 }
1702
1703 #[test]
1704 fn test_parse_int() {
1705 let vars = HashMap::new();
1706 let expr = ValueExpr::BuiltinCall {
1707 func: BuiltinFunction::ParseInt,
1708 args: vec![ValueExpr::Literal(JsonValue::String("42.9".into()))],
1709 };
1710 let result = evaluate(&expr, &vars).unwrap();
1711 assert_eq!(result, serde_json::json!(42));
1712 }
1713
1714 #[test]
1715 fn test_parse_int_nan_returns_null() {
1716 let vars = HashMap::new();
1717 let expr = ValueExpr::BuiltinCall {
1718 func: BuiltinFunction::ParseInt,
1719 args: vec![ValueExpr::Literal(JsonValue::String("abc".into()))],
1720 };
1721 let result = evaluate(&expr, &vars).unwrap();
1722 assert_eq!(result, JsonValue::Null);
1723 }
1724
1725 #[test]
1726 fn test_math_abs() {
1727 let vars = HashMap::new();
1728 let expr = ValueExpr::BuiltinCall {
1729 func: BuiltinFunction::MathAbs,
1730 args: vec![ValueExpr::Literal(serde_json::json!(-5.0))],
1731 };
1732 let result = evaluate(&expr, &vars).unwrap();
1733 assert_eq!(result, serde_json::json!(5.0));
1734 }
1735
1736 #[test]
1737 fn test_math_max() {
1738 let vars = HashMap::new();
1739 let expr = ValueExpr::BuiltinCall {
1740 func: BuiltinFunction::MathMax,
1741 args: vec![
1742 ValueExpr::Literal(serde_json::json!(1)),
1743 ValueExpr::Literal(serde_json::json!(5)),
1744 ValueExpr::Literal(serde_json::json!(3)),
1745 ],
1746 };
1747 let result = evaluate(&expr, &vars).unwrap();
1748 assert_eq!(result, serde_json::json!(5.0));
1749 }
1750
1751 #[test]
1752 fn test_math_min() {
1753 let vars = HashMap::new();
1754 let expr = ValueExpr::BuiltinCall {
1755 func: BuiltinFunction::MathMin,
1756 args: vec![
1757 ValueExpr::Literal(serde_json::json!(10)),
1758 ValueExpr::Literal(serde_json::json!(2)),
1759 ValueExpr::Literal(serde_json::json!(7)),
1760 ],
1761 };
1762 let result = evaluate(&expr, &vars).unwrap();
1763 assert_eq!(result, serde_json::json!(2.0));
1764 }
1765
1766 #[test]
1767 fn test_math_round() {
1768 let vars = HashMap::new();
1769 let expr = ValueExpr::BuiltinCall {
1770 func: BuiltinFunction::MathRound,
1771 args: vec![ValueExpr::Literal(serde_json::json!(3.7))],
1772 };
1773 let result = evaluate(&expr, &vars).unwrap();
1774 assert_eq!(result, serde_json::json!(4.0));
1775 }
1776
1777 #[test]
1778 fn test_math_floor() {
1779 let vars = HashMap::new();
1780 let expr = ValueExpr::BuiltinCall {
1781 func: BuiltinFunction::MathFloor,
1782 args: vec![ValueExpr::Literal(serde_json::json!(3.7))],
1783 };
1784 let result = evaluate(&expr, &vars).unwrap();
1785 assert_eq!(result, serde_json::json!(3.0));
1786 }
1787
1788 #[test]
1789 fn test_math_ceil() {
1790 let vars = HashMap::new();
1791 let expr = ValueExpr::BuiltinCall {
1792 func: BuiltinFunction::MathCeil,
1793 args: vec![ValueExpr::Literal(serde_json::json!(3.2))],
1794 };
1795 let result = evaluate(&expr, &vars).unwrap();
1796 assert_eq!(result, serde_json::json!(4.0));
1797 }
1798
1799 #[test]
1800 fn test_object_keys() {
1801 let mut vars = HashMap::new();
1802 vars.insert("obj".to_string(), serde_json::json!({"a": 1, "b": 2}));
1803 let expr = ValueExpr::BuiltinCall {
1804 func: BuiltinFunction::ObjectKeys,
1805 args: vec![ValueExpr::Variable("obj".to_string())],
1806 };
1807 let result = evaluate(&expr, &vars).unwrap();
1808 let arr = result.as_array().unwrap();
1809 assert_eq!(arr.len(), 2);
1810 assert!(arr.contains(&JsonValue::String("a".into())));
1811 assert!(arr.contains(&JsonValue::String("b".into())));
1812 }
1813
1814 #[test]
1815 fn test_object_values() {
1816 let mut vars = HashMap::new();
1817 vars.insert("obj".to_string(), serde_json::json!({"x": 10, "y": 20}));
1818 let expr = ValueExpr::BuiltinCall {
1819 func: BuiltinFunction::ObjectValues,
1820 args: vec![ValueExpr::Variable("obj".to_string())],
1821 };
1822 let result = evaluate(&expr, &vars).unwrap();
1823 let arr = result.as_array().unwrap();
1824 assert_eq!(arr.len(), 2);
1825 assert!(arr.contains(&serde_json::json!(10)));
1826 assert!(arr.contains(&serde_json::json!(20)));
1827 }
1828
1829 #[test]
1830 fn test_object_entries() {
1831 let mut vars = HashMap::new();
1832 vars.insert("obj".to_string(), serde_json::json!({"key": "val"}));
1833 let expr = ValueExpr::BuiltinCall {
1834 func: BuiltinFunction::ObjectEntries,
1835 args: vec![ValueExpr::Variable("obj".to_string())],
1836 };
1837 let result = evaluate(&expr, &vars).unwrap();
1838 let arr = result.as_array().unwrap();
1839 assert_eq!(arr.len(), 1);
1840 assert_eq!(arr[0], serde_json::json!(["key", "val"]));
1841 }
1842
1843 #[test]
1844 fn test_object_keys_non_object() {
1845 let vars = HashMap::new();
1846 let expr = ValueExpr::BuiltinCall {
1847 func: BuiltinFunction::ObjectKeys,
1848 args: vec![ValueExpr::Literal(serde_json::json!(42))],
1849 };
1850 let result = evaluate(&expr, &vars).unwrap();
1851 assert_eq!(result, JsonValue::Array(vec![]));
1852 }
1853
1854 #[test]
1859 fn test_unary_plus_string_to_number() {
1860 let vars = HashMap::new();
1861 let expr = ValueExpr::UnaryOp {
1862 op: UnaryOperator::Plus,
1863 operand: Box::new(ValueExpr::Literal(JsonValue::String("42".into()))),
1864 };
1865 let result = evaluate(&expr, &vars).unwrap();
1866 assert_eq!(result, serde_json::json!(42.0));
1867 }
1868
1869 #[test]
1870 fn test_unary_plus_nan_returns_null() {
1871 let vars = HashMap::new();
1872 let expr = ValueExpr::UnaryOp {
1873 op: UnaryOperator::Plus,
1874 operand: Box::new(ValueExpr::Literal(JsonValue::String("abc".into()))),
1875 };
1876 let result = evaluate(&expr, &vars).unwrap();
1877 assert_eq!(result, JsonValue::Null);
1878 }
1879
1880 #[test]
1885 fn test_sort_ascending_comparator() {
1886 let mut vars = HashMap::new();
1887 vars.insert("arr".to_string(), serde_json::json!([3, 1, 4, 1, 5]));
1888 let expr = ValueExpr::ArrayMethod {
1889 array: Box::new(ValueExpr::Variable("arr".to_string())),
1890 method: ArrayMethodCall::Sort {
1891 comparator: Some((
1892 "a".to_string(),
1893 "b".to_string(),
1894 Box::new(ValueExpr::BinaryOp {
1895 left: Box::new(ValueExpr::Variable("a".to_string())),
1896 op: BinaryOperator::Sub,
1897 right: Box::new(ValueExpr::Variable("b".to_string())),
1898 }),
1899 )),
1900 },
1901 };
1902 let result = evaluate(&expr, &vars).unwrap();
1903 assert_eq!(result, serde_json::json!([1, 1, 3, 4, 5]));
1904 }
1905
1906 #[test]
1907 fn test_sort_descending_comparator() {
1908 let mut vars = HashMap::new();
1909 vars.insert("arr".to_string(), serde_json::json!([3, 1, 4, 1, 5]));
1910 let expr = ValueExpr::ArrayMethod {
1911 array: Box::new(ValueExpr::Variable("arr".to_string())),
1912 method: ArrayMethodCall::Sort {
1913 comparator: Some((
1914 "a".to_string(),
1915 "b".to_string(),
1916 Box::new(ValueExpr::BinaryOp {
1917 left: Box::new(ValueExpr::Variable("b".to_string())),
1918 op: BinaryOperator::Sub,
1919 right: Box::new(ValueExpr::Variable("a".to_string())),
1920 }),
1921 )),
1922 },
1923 };
1924 let result = evaluate(&expr, &vars).unwrap();
1925 assert_eq!(result, serde_json::json!([5, 4, 3, 1, 1]));
1926 }
1927
1928 #[test]
1929 fn test_sort_default_string_sort() {
1930 let mut vars = HashMap::new();
1931 vars.insert(
1932 "arr".to_string(),
1933 serde_json::json!(["banana", "apple", "cherry"]),
1934 );
1935 let expr = ValueExpr::ArrayMethod {
1936 array: Box::new(ValueExpr::Variable("arr".to_string())),
1937 method: ArrayMethodCall::Sort { comparator: None },
1938 };
1939 let result = evaluate(&expr, &vars).unwrap();
1940 assert_eq!(result, serde_json::json!(["apple", "banana", "cherry"]));
1941 }
1942
1943 #[test]
1948 fn test_array_map_large_scope_performance() {
1949 use std::time::Instant;
1954
1955 let mut vars = HashMap::new();
1956 for i in 0..20 {
1958 vars.insert(
1959 format!("var_{i}"),
1960 JsonValue::Number(serde_json::Number::from(i)),
1961 );
1962 }
1963
1964 let arr: Vec<JsonValue> = (0..1000)
1966 .map(|i| JsonValue::Number(serde_json::Number::from(i)))
1967 .collect();
1968 vars.insert("data".to_string(), JsonValue::Array(arr));
1969
1970 let expr = ValueExpr::ArrayMethod {
1972 array: Box::new(ValueExpr::Variable("data".to_string())),
1973 method: ArrayMethodCall::Map {
1974 item_var: "x".to_string(),
1975 body: Box::new(ValueExpr::Variable("x".to_string())),
1976 },
1977 };
1978
1979 let start = Instant::now();
1980 let result = evaluate(&expr, &vars).unwrap();
1981 let elapsed = start.elapsed();
1982
1983 let arr_result = result.as_array().unwrap();
1984 assert_eq!(arr_result.len(), 1000);
1985 assert_eq!(arr_result[0], JsonValue::Number(0.into()));
1987 assert_eq!(arr_result[999], JsonValue::Number(999.into()));
1988
1989 assert!(
1991 elapsed.as_millis() < 100,
1992 "Array map took {}ms, expected < 100ms",
1993 elapsed.as_millis()
1994 );
1995 }
1996
1997 #[test]
1998 fn test_array_filter_preserves_outer_scope() {
1999 let mut vars = HashMap::new();
2002 vars.insert("x".to_string(), JsonValue::String("outer".into()));
2003 vars.insert(
2004 "arr".to_string(),
2005 JsonValue::Array(vec![
2006 JsonValue::Number(1.into()),
2007 JsonValue::Number(2.into()),
2008 JsonValue::Number(3.into()),
2009 ]),
2010 );
2011
2012 let expr = ValueExpr::Block {
2014 bindings: vec![(
2015 "filtered".to_string(),
2016 ValueExpr::ArrayMethod {
2017 array: Box::new(ValueExpr::Variable("arr".to_string())),
2018 method: ArrayMethodCall::Filter {
2019 item_var: "x".to_string(),
2020 predicate: Box::new(ValueExpr::BinaryOp {
2021 left: Box::new(ValueExpr::Variable("x".to_string())),
2022 op: BinaryOperator::Gt,
2023 right: Box::new(ValueExpr::Literal(JsonValue::Number(1.into()))),
2024 }),
2025 },
2026 },
2027 )],
2028 result: Box::new(ValueExpr::Variable("x".to_string())),
2029 };
2030
2031 let result = evaluate(&expr, &vars).unwrap();
2032 assert_eq!(result, JsonValue::String("outer".into()));
2034 }
2035
2036 #[test]
2037 fn test_array_reduce_large_scope_performance() {
2038 let mut vars = HashMap::new();
2040 for i in 0..20 {
2041 vars.insert(
2042 format!("var_{i}"),
2043 JsonValue::Number(serde_json::Number::from(i)),
2044 );
2045 }
2046
2047 let arr: Vec<JsonValue> = (0..1000)
2049 .map(|i| JsonValue::Number(serde_json::Number::from(i)))
2050 .collect();
2051 vars.insert("data".to_string(), JsonValue::Array(arr));
2052
2053 let expr = ValueExpr::ArrayMethod {
2054 array: Box::new(ValueExpr::Variable("data".to_string())),
2055 method: ArrayMethodCall::Reduce {
2056 acc_var: "acc".to_string(),
2057 item_var: "x".to_string(),
2058 body: Box::new(ValueExpr::BinaryOp {
2059 left: Box::new(ValueExpr::Variable("acc".to_string())),
2060 op: BinaryOperator::Add,
2061 right: Box::new(ValueExpr::Variable("x".to_string())),
2062 }),
2063 initial: Box::new(ValueExpr::Literal(JsonValue::Number(0.into()))),
2064 },
2065 };
2066
2067 let result = evaluate(&expr, &vars).unwrap();
2068 assert_eq!(result, serde_json::json!(499_500.0));
2071 }
2072}