use super::super::compiled::CompiledLogic;
use super::helpers;
use super::{types::*, Evaluator};
use serde_json::{Map as JsonMap, Value};
impl Evaluator {
pub(super) fn eval_quantifier(
&self,
quantifier: Quantifier,
array_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = array_val {
for item in arr {
let result =
self.evaluate_with_context(logic_expr, &item, &Value::Null, depth + 1)?;
let truthy = helpers::is_truthy(&result);
match quantifier {
Quantifier::All if !truthy => return Ok(Value::Bool(false)),
Quantifier::Some if truthy => return Ok(Value::Bool(true)),
Quantifier::None if truthy => return Ok(Value::Bool(false)),
_ => {}
}
}
Ok(Value::Bool(match quantifier {
Quantifier::All => true,
Quantifier::Some => false,
Quantifier::None => true,
}))
} else {
Ok(Value::Bool(match quantifier {
Quantifier::All | Quantifier::Some => false,
Quantifier::None => true,
}))
}
}
pub(super) fn eval_map(
&self,
array_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = array_val {
let mut results = Vec::with_capacity(arr.len());
for item in &arr {
results.push(self.evaluate_with_context(
logic_expr,
item,
&Value::Null,
depth + 1,
)?);
}
Ok(Value::Array(results))
} else {
Ok(Value::Array(vec![]))
}
}
pub(super) fn eval_filter(
&self,
array_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = array_val {
let mut results = Vec::with_capacity(arr.len());
for item in arr.into_iter() {
let result =
self.evaluate_with_context(logic_expr, &item, &Value::Null, depth + 1)?;
if helpers::is_truthy(&result) {
results.push(item);
}
}
Ok(Value::Array(results))
} else {
Ok(Value::Array(vec![]))
}
}
pub(super) fn eval_reduce(
&self,
array_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
initial_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
let mut accumulator =
self.evaluate_with_context(initial_expr, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = array_val {
for item in arr {
let mut context = JsonMap::with_capacity(2);
context.insert("current".to_string(), item);
context.insert("accumulator".to_string(), accumulator);
let combined = Value::Object(context);
accumulator =
self.evaluate_with_context(logic_expr, &combined, &Value::Null, depth + 1)?;
}
}
Ok(accumulator)
}
pub(super) fn eval_merge(
&self,
items: &[CompiledLogic],
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let mut merged = Vec::new();
for item in items {
let val = self.evaluate_with_context(item, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = val {
merged.extend(arr);
} else {
merged.push(val);
}
}
Ok(Value::Array(merged))
}
pub(super) fn eval_in(
&self,
value_expr: &CompiledLogic,
array_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
use rapidhash::{HashSetExt, RapidHashSet};
const HASH_SET_THRESHOLD: usize = 32;
let value =
self.evaluate_with_context(value_expr, user_data, internal_context, depth + 1)?;
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
if let Value::Array(arr) = array_val {
if arr.len() > HASH_SET_THRESHOLD {
if let Some(key) = helpers::scalar_hash_key(&value) {
let mut set = RapidHashSet::with_capacity(arr.len());
let mut all_scalar = true;
for item in &arr {
if let Some(item_key) = helpers::scalar_hash_key(item) {
set.insert(item_key);
} else {
all_scalar = false;
break;
}
}
if all_scalar {
return Ok(Value::Bool(set.contains(&key)));
}
}
}
for item in arr {
if helpers::loose_equal(&value, &item) {
return Ok(Value::Bool(true));
}
}
Ok(Value::Bool(false))
} else if let Value::String(s) = array_val {
if let Value::String(needle) = value {
Ok(Value::Bool(s.contains(&needle)))
} else {
Ok(Value::Bool(false))
}
} else {
Ok(Value::Bool(false))
}
}
pub(super) fn eval_sum(
&self,
array_expr: &CompiledLogic,
field_expr: &Option<Box<CompiledLogic>>,
threshold_expr: &Option<Box<CompiledLogic>>,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let array_val =
self.evaluate_with_context(array_expr, user_data, internal_context, depth + 1)?;
let threshold = if let Some(thresh_e) = threshold_expr {
let thresh_val =
self.evaluate_with_context(thresh_e, user_data, internal_context, depth + 1)?;
helpers::to_f64(&thresh_val) as i64
} else {
-1 };
let sum = match &array_val {
Value::Array(arr) => {
let field_name_opt = if let Some(field_e) = field_expr {
let field_val = self.evaluate_with_context(
field_e,
user_data,
internal_context,
depth + 1,
)?;
match field_val {
Value::String(s) => Some(s),
Value::Null => None, _ => None,
}
} else {
None
};
let items_to_process = if threshold >= 0 {
let limit = (threshold as usize + 1).min(arr.len());
&arr[..limit]
} else {
arr
};
if let Some(field_name) = field_name_opt {
let mut sum = 0.0_f64;
for item in items_to_process {
if let Value::Object(obj) = item {
if let Some(val) = obj.get(&field_name) {
sum += helpers::to_f64(val);
}
}
}
sum
} else {
items_to_process
.iter()
.map(|item| helpers::to_f64(item))
.sum()
}
}
_ => helpers::to_f64(&array_val),
};
Ok(self.f64_to_json(sum))
}
pub(super) fn eval_for(
&self,
start_expr: &CompiledLogic,
end_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let next_depth = depth + 1;
let start_val =
self.evaluate_with_context(start_expr, user_data, internal_context, next_depth)?;
let end_val =
self.evaluate_with_context(end_expr, user_data, internal_context, next_depth)?;
let start = helpers::to_number(&start_val) as i64;
let end = helpers::to_number(&end_val) as i64;
let mut results = Vec::new();
for i in start..end {
let loop_context = serde_json::json!({
"$loopIteration": i
});
let result =
self.evaluate_with_context(logic_expr, user_data, &loop_context, next_depth)?;
results.push(result);
}
Ok(Value::Array(results))
}
pub(super) fn eval_multiplies(
&self,
items: &[CompiledLogic],
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
if items.len() == 1 {
if let CompiledLogic::For(start_expr, end_expr, logic_expr) = &items[0] {
return self.eval_multiplies_for(
start_expr,
end_expr,
logic_expr,
user_data,
internal_context,
depth,
);
}
}
let values = self.flatten_array_values(items, user_data, internal_context, depth)?;
if values.is_empty() {
return Ok(Value::Null);
}
if values.len() == 1 {
return Ok(self.f64_to_json(values[0]));
}
let result = values.iter().skip(1).fold(values[0], |acc, n| acc * n);
Ok(self.f64_to_json(result))
}
fn eval_multiplies_for(
&self,
start_expr: &CompiledLogic,
end_expr: &CompiledLogic,
logic_expr: &CompiledLogic,
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let next_depth = depth + 1;
let start_val =
self.evaluate_with_context(start_expr, user_data, internal_context, next_depth)?;
let end_val =
self.evaluate_with_context(end_expr, user_data, internal_context, next_depth)?;
let start = helpers::to_number(&start_val) as i64;
let end = helpers::to_number(&end_val) as i64;
if start >= end {
return Ok(Value::Null);
}
let mut product = 1.0_f64;
for i in start..end {
let loop_context = serde_json::json!({
"$loopIteration": i
});
let val =
self.evaluate_with_context(logic_expr, user_data, &loop_context, next_depth)?;
product *= helpers::to_f64(&val);
}
Ok(self.f64_to_json(product))
}
pub(super) fn eval_divides(
&self,
items: &[CompiledLogic],
user_data: &Value,
internal_context: &Value,
depth: usize,
) -> Result<Value, String> {
let values = self.flatten_array_values(items, user_data, internal_context, depth)?;
if values.is_empty() {
return Ok(Value::Null);
}
if values.len() == 1 {
return Ok(self.f64_to_json(values[0]));
}
let result = values
.iter()
.skip(1)
.fold(values[0], |acc, n| if *n == 0.0 { acc } else { acc / n });
Ok(self.f64_to_json(result))
}
}