mod compare;
mod functions;
use rayon::prelude::*;
use simd_json::OwnedValue as Value;
use simd_json::StaticNode;
use simd_json::prelude::*;
use super::PARALLEL_THRESHOLD;
use super::ast::{ArithOp, Filter, FilterKind, ObjectKey};
use super::builtins;
use crate::error::EvalError;
use crate::utils::type_name;
pub fn eval(filter: &Filter, value: &Value) -> Result<Vec<Value>, EvalError> {
match &filter.kind {
FilterKind::Identity => Ok(vec![value.clone()]),
FilterKind::Field(name) => match value {
Value::Object(map) => Ok(vec![
map.get(name)
.cloned()
.unwrap_or(Value::Static(StaticNode::Null)),
]),
Value::Static(StaticNode::Null) => Ok(vec![Value::Static(StaticNode::Null)]),
_ => Err(EvalError::CannotIndex {
value: value.clone(),
index_type: format!("string {:?}", name),
position: filter.span.start,
}),
},
FilterKind::Index(n) => match value {
Value::Array(arr) => {
let idx = if *n >= 0 {
*n as usize
} else {
arr.len().checked_sub((-*n) as usize).unwrap_or(usize::MAX)
};
Ok(vec![
arr.get(idx)
.cloned()
.unwrap_or(Value::Static(StaticNode::Null)),
])
}
Value::Static(StaticNode::Null) => Ok(vec![Value::Static(StaticNode::Null)]),
_ => Err(EvalError::CannotIndex {
value: value.clone(),
index_type: "number".to_string(),
position: filter.span.start,
}),
},
FilterKind::Slice(start, end) => eval_slice(*start, *end, value, filter.span.start),
FilterKind::Iterate => match value {
Value::Array(arr) if arr.len() >= PARALLEL_THRESHOLD => {
Ok(arr.par_iter().cloned().collect())
}
Value::Array(arr) => Ok(arr.to_vec()),
Value::Object(map) => Ok(map.values().cloned().collect()),
_ => Err(EvalError::CannotIterate {
value: value.clone(),
position: filter.span.start,
}),
},
FilterKind::Optional(inner) => {
match eval(inner, value) {
Ok(results) => Ok(results),
Err(_) => Ok(vec![]),
}
}
FilterKind::Pipe(left, right) => {
let left_results = eval(left, value)?;
let mut results = Vec::new();
for v in left_results {
results.extend(eval(right, &v)?);
}
Ok(results)
}
FilterKind::Builtin(b) => builtins::eval(b, value).map_err(|mut e| {
e.set_position(filter.span.start);
e
}),
FilterKind::Function(func) => functions::eval_function(func, value).map_err(|mut e| {
e.set_position(filter.span.start);
e
}),
FilterKind::Literal(lit) => Ok(vec![lit.clone()]),
FilterKind::Compare(left, op, right) => compare::eval_compare(left, *op, right, value),
FilterKind::BoolOp(left, op, right) => compare::eval_bool_op(left, *op, right, value),
FilterKind::Arith(left, op, right) => {
eval_arith(left, *op, right, value, filter.span.start)
}
FilterKind::Array(element_filters) => {
let mut array_elements = Vec::new();
for elem_filter in element_filters {
let results = eval(elem_filter, value)?;
array_elements.extend(results);
}
Ok(vec![Value::Array(Box::new(array_elements))])
}
FilterKind::Object(pairs) => {
let mut obj = simd_json::owned::Object::new();
for (key, value_filter) in pairs {
let key_str = match key {
ObjectKey::Static(s) => s.clone(),
ObjectKey::Dynamic(key_filter) => {
let key_results = eval(key_filter, value)?;
match key_results.into_iter().next() {
Some(Value::String(s)) => s,
Some(other) => {
return Err(EvalError::TypeError {
message: format!(
"object key must be string, got {}",
type_name(&other)
),
position: key_filter.span.start,
});
}
None => {
return Err(EvalError::TypeError {
message: "object key expression produced no value".to_string(),
position: key_filter.span.start,
});
}
}
}
};
let results = eval(value_filter, value)?;
let val = results
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
obj.insert(key_str, val);
}
Ok(vec![Value::Object(Box::new(obj))])
}
FilterKind::IfThenElse {
condition,
then_branch,
else_branch,
} => {
let cond_results = eval(condition, value)?;
let is_truthy = cond_results.iter().any(|v| {
!matches!(
v,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
)
});
if is_truthy {
eval(then_branch, value)
} else {
eval(else_branch, value)
}
}
FilterKind::Alternative(left, right) => {
let left_results = eval(left, value)?;
let has_value = left_results.iter().any(|v| {
!matches!(
v,
Value::Static(StaticNode::Null) | Value::Static(StaticNode::Bool(false))
)
});
if has_value {
let filtered: Vec<Value> = left_results
.into_iter()
.filter(|v| {
!matches!(
v,
Value::Static(StaticNode::Null)
| Value::Static(StaticNode::Bool(false))
)
})
.collect();
if filtered.is_empty() {
eval(right, value)
} else {
Ok(filtered)
}
} else {
eval(right, value)
}
}
}
}
fn eval_slice(
start: Option<i64>,
end: Option<i64>,
value: &Value,
position: usize,
) -> Result<Vec<Value>, EvalError> {
match value {
Value::Array(arr) => {
let len = arr.len() as i64;
let start_idx = match start {
Some(s) if s >= 0 => (s as usize).min(arr.len()),
Some(s) => (len + s).max(0) as usize,
None => 0,
};
let end_idx = match end {
Some(e) if e >= 0 => (e as usize).min(arr.len()),
Some(e) => (len + e).max(0) as usize,
None => arr.len(),
};
if start_idx >= end_idx {
Ok(vec![Value::Array(Box::default())])
} else {
Ok(vec![Value::Array(Box::new(
arr[start_idx..end_idx].to_vec(),
))])
}
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as i64;
let start_idx = match start {
Some(st) if st >= 0 => (st as usize).min(chars.len()),
Some(st) => (len + st).max(0) as usize,
None => 0,
};
let end_idx = match end {
Some(e) if e >= 0 => (e as usize).min(chars.len()),
Some(e) => (len + e).max(0) as usize,
None => chars.len(),
};
if start_idx >= end_idx {
Ok(vec![Value::String(String::new())])
} else {
Ok(vec![Value::String(
chars[start_idx..end_idx].iter().collect(),
)])
}
}
Value::Static(StaticNode::Null) => Ok(vec![Value::Static(StaticNode::Null)]),
_ => Err(EvalError::CannotIndex {
value: value.clone(),
index_type: "object".to_string(),
position,
}),
}
}
fn eval_arith(
left: &Filter,
op: ArithOp,
right: &Filter,
value: &Value,
position: usize,
) -> Result<Vec<Value>, EvalError> {
let left_results = eval(left, value)?;
let right_results = eval(right, value)?;
let left_val = left_results
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
let right_val = right_results
.into_iter()
.next()
.unwrap_or(Value::Static(StaticNode::Null));
let left_num = value_to_f64(&left_val).ok_or_else(|| EvalError::TypeError {
message: format!(
"cannot perform arithmetic on {} (not a number)",
type_name(&left_val)
),
position,
})?;
let right_num = value_to_f64(&right_val).ok_or_else(|| EvalError::TypeError {
message: format!(
"cannot perform arithmetic on {} (not a number)",
type_name(&right_val)
),
position,
})?;
let result = match op {
ArithOp::Add => left_num + right_num,
ArithOp::Sub => left_num - right_num,
ArithOp::Mul => left_num * right_num,
ArithOp::Div => {
if right_num == 0.0 {
return Err(EvalError::TypeError {
message: "division by zero".to_string(),
position,
});
}
left_num / right_num
}
};
if result.fract() == 0.0 && result.abs() < i64::MAX as f64 {
Ok(vec![Value::Static(StaticNode::I64(result as i64))])
} else {
Ok(vec![Value::Static(StaticNode::F64(result))])
}
}
fn value_to_f64(value: &Value) -> Option<f64> {
match value {
Value::Static(StaticNode::I64(n)) => Some(*n as f64),
Value::Static(StaticNode::U64(n)) => Some(*n as f64),
Value::Static(StaticNode::F64(n)) => Some(*n),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::ast::{BoolOp, CompareOp, FunctionCall, Span};
use simd_json::json;
fn f(kind: FilterKind) -> Filter {
Filter::new(kind, Span::default())
}
fn bf(kind: FilterKind) -> Box<Filter> {
Box::new(f(kind))
}
#[test]
fn test_identity() {
let value = json!({"name": "alice"});
let result = eval(&f(FilterKind::Identity), &value).unwrap();
assert_eq!(result, vec![value]);
}
#[test]
fn test_field_access() {
let value = json!({"name": "alice", "age": 30});
let result = eval(&f(FilterKind::Field("name".to_string())), &value).unwrap();
assert_eq!(result, vec![json!("alice")]);
}
#[test]
fn test_field_missing() {
let value = json!({"name": "alice"});
let result = eval(&f(FilterKind::Field("missing".to_string())), &value).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_field_on_string_errors() {
let result = eval(&f(FilterKind::Field("foo".to_string())), &json!("string"));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index string")
);
}
#[test]
fn test_field_on_number_errors() {
let result = eval(&f(FilterKind::Field("foo".to_string())), &json!(123));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index number")
);
}
#[test]
fn test_field_on_boolean_errors() {
let result = eval(&f(FilterKind::Field("foo".to_string())), &json!(true));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index boolean")
);
}
#[test]
fn test_field_on_array_errors() {
let result = eval(&f(FilterKind::Field("foo".to_string())), &json!([1, 2, 3]));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index array")
);
}
#[test]
fn test_field_on_null_returns_null() {
let result = eval(&f(FilterKind::Field("foo".to_string())), &json!(null)).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_nested_field() {
let value = json!({"user": {"name": "alice", "address": {"city": "NYC"}}});
let filter = f(FilterKind::Pipe(
bf(FilterKind::Field("user".to_string())),
bf(FilterKind::Field("name".to_string())),
));
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![json!("alice")]);
}
#[test]
fn test_deeply_nested_field() {
let value = json!({"user": {"address": {"city": "NYC"}}});
let filter = f(FilterKind::Pipe(
bf(FilterKind::Pipe(
bf(FilterKind::Field("user".to_string())),
bf(FilterKind::Field("address".to_string())),
)),
bf(FilterKind::Field("city".to_string())),
));
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![json!("NYC")]);
}
#[test]
fn test_nested_field_missing() {
let value = json!({"user": {}});
let filter = f(FilterKind::Pipe(
bf(FilterKind::Field("user".to_string())),
bf(FilterKind::Field("name".to_string())),
));
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_field_on_nested_null() {
let value = json!({"user": null});
let filter = f(FilterKind::Pipe(
bf(FilterKind::Field("user".to_string())),
bf(FilterKind::Field("name".to_string())),
));
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_index_positive() {
let value = json!([10, 20, 30]);
assert_eq!(
eval(&f(FilterKind::Index(0)), &value).unwrap(),
vec![json!(10)]
);
assert_eq!(
eval(&f(FilterKind::Index(1)), &value).unwrap(),
vec![json!(20)]
);
assert_eq!(
eval(&f(FilterKind::Index(2)), &value).unwrap(),
vec![json!(30)]
);
}
#[test]
fn test_index_negative() {
let value = json!([10, 20, 30]);
assert_eq!(
eval(&f(FilterKind::Index(-1)), &value).unwrap(),
vec![json!(30)]
);
assert_eq!(
eval(&f(FilterKind::Index(-2)), &value).unwrap(),
vec![json!(20)]
);
assert_eq!(
eval(&f(FilterKind::Index(-3)), &value).unwrap(),
vec![json!(10)]
);
}
#[test]
fn test_index_out_of_bounds() {
let value = json!([10, 20, 30]);
assert_eq!(
eval(&f(FilterKind::Index(10)), &value).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
assert_eq!(
eval(&f(FilterKind::Index(-10)), &value).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
}
#[test]
fn test_index_on_string_errors() {
let result = eval(&f(FilterKind::Index(0)), &json!("string"));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index string")
);
}
#[test]
fn test_index_on_object_errors() {
let result = eval(&f(FilterKind::Index(0)), &json!({"a": 1}));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index object")
);
}
#[test]
fn test_index_on_number_errors() {
let result = eval(&f(FilterKind::Index(0)), &json!(123));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index number")
);
}
#[test]
fn test_index_on_boolean_errors() {
let result = eval(&f(FilterKind::Index(0)), &json!(true));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index boolean")
);
}
#[test]
fn test_index_on_null_returns_null() {
assert_eq!(
eval(&f(FilterKind::Index(0)), &json!(null)).unwrap(),
vec![Value::Static(StaticNode::Null)]
);
}
#[test]
fn test_iterate_array() {
let value = json!([1, 2, 3]);
let result = eval(&f(FilterKind::Iterate), &value).unwrap();
assert_eq!(result, vec![json!(1), json!(2), json!(3)]);
}
#[test]
fn test_iterate_object() {
let value = json!({"a": 1, "b": 2});
let result = eval(&f(FilterKind::Iterate), &value).unwrap();
assert_eq!(result.len(), 2);
assert!(result.contains(&json!(1)));
assert!(result.contains(&json!(2)));
}
#[test]
fn test_iterate_on_string_fails() {
let result = eval(&f(FilterKind::Iterate), &json!("string"));
assert!(result.is_err());
}
#[test]
fn test_slice_both_indices() {
let value = json!([0, 1, 2, 3, 4, 5]);
let result = eval(&f(FilterKind::Slice(Some(1), Some(4))), &value).unwrap();
assert_eq!(result, vec![json!([1, 2, 3])]);
}
#[test]
fn test_slice_start_only() {
let value = json!([0, 1, 2, 3, 4]);
let result = eval(&f(FilterKind::Slice(Some(2), None)), &value).unwrap();
assert_eq!(result, vec![json!([2, 3, 4])]);
}
#[test]
fn test_slice_end_only() {
let value = json!([0, 1, 2, 3, 4]);
let result = eval(&f(FilterKind::Slice(None, Some(3))), &value).unwrap();
assert_eq!(result, vec![json!([0, 1, 2])]);
}
#[test]
fn test_slice_negative() {
let value = json!([0, 1, 2, 3, 4]);
let result = eval(&f(FilterKind::Slice(Some(-2), None)), &value).unwrap();
assert_eq!(result, vec![json!([3, 4])]);
}
#[test]
fn test_slice_on_number_errors() {
let result = eval(&f(FilterKind::Slice(Some(1), Some(3))), &json!(123));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index number")
);
}
#[test]
fn test_slice_on_object_errors() {
let result = eval(&f(FilterKind::Slice(Some(1), Some(3))), &json!({"a": 1}));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index object")
);
}
#[test]
fn test_slice_on_boolean_errors() {
let result = eval(&f(FilterKind::Slice(Some(1), Some(3))), &json!(true));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot index boolean")
);
}
#[test]
fn test_slice_on_null_returns_null() {
let result = eval(&f(FilterKind::Slice(Some(1), Some(3))), &json!(null)).unwrap();
assert_eq!(result, vec![Value::Static(StaticNode::Null)]);
}
#[test]
fn test_pipe_chain() {
let value = json!({"items": [{"id": 1}, {"id": 2}]});
let filter = f(FilterKind::Pipe(
bf(FilterKind::Field("items".to_string())),
bf(FilterKind::Index(0)),
));
let result = eval(&filter, &value).unwrap();
assert_eq!(result, vec![json!({"id": 1})]);
}
#[test]
fn test_optional_iterate_on_non_iterable() {
let filter = f(FilterKind::Optional(bf(FilterKind::Iterate)));
let result = eval(&filter, &json!("string")).unwrap();
let empty: Vec<Value> = vec![];
assert_eq!(result, empty);
}
#[test]
fn test_optional_iterate_on_array() {
let filter = f(FilterKind::Optional(bf(FilterKind::Iterate)));
let result = eval(&filter, &json!([1, 2])).unwrap();
assert_eq!(result, vec![json!(1), json!(2)]);
}
#[test]
fn test_optional_field_succeeds() {
let filter = f(FilterKind::Optional(bf(FilterKind::Field(
"foo".to_string(),
))));
let result = eval(&filter, &json!({"foo": 42})).unwrap();
assert_eq!(result, vec![json!(42)]);
}
#[test]
fn test_literal_number() {
let filter = f(FilterKind::Literal(json!(42)));
let result = eval(&filter, &json!(null)).unwrap();
assert_eq!(result, vec![json!(42)]);
}
#[test]
fn test_literal_string() {
let filter = f(FilterKind::Literal(json!("hello")));
let result = eval(&filter, &json!(null)).unwrap();
assert_eq!(result, vec![json!("hello")]);
}
#[test]
fn test_compare_eq() {
let filter = f(FilterKind::Compare(
bf(FilterKind::Identity),
CompareOp::Eq,
bf(FilterKind::Literal(json!(5))),
));
assert_eq!(eval(&filter, &json!(5)).unwrap(), vec![json!(true)]);
assert_eq!(eval(&filter, &json!(3)).unwrap(), vec![json!(false)]);
}
#[test]
fn test_compare_lt() {
let filter = f(FilterKind::Compare(
bf(FilterKind::Identity),
CompareOp::Lt,
bf(FilterKind::Literal(json!(10))),
));
assert_eq!(eval(&filter, &json!(5)).unwrap(), vec![json!(true)]);
assert_eq!(eval(&filter, &json!(15)).unwrap(), vec![json!(false)]);
}
#[test]
fn test_bool_and() {
let filter = f(FilterKind::BoolOp(
bf(FilterKind::Literal(json!(true))),
BoolOp::And,
bf(FilterKind::Literal(json!(false))),
));
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!(false)]);
}
#[test]
fn test_bool_or() {
let filter = f(FilterKind::BoolOp(
bf(FilterKind::Literal(json!(false))),
BoolOp::Or,
bf(FilterKind::Literal(json!(true))),
));
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!(true)]);
}
#[test]
fn test_array_construction() {
let filter = f(FilterKind::Array(vec![
f(FilterKind::Field("a".to_string())),
f(FilterKind::Field("b".to_string())),
]));
let result = eval(&filter, &json!({"a": 1, "b": 2})).unwrap();
assert_eq!(result, vec![json!([1, 2])]);
}
#[test]
fn test_object_construction() {
let filter = f(FilterKind::Object(vec![
(
ObjectKey::Static("x".to_string()),
f(FilterKind::Field("a".to_string())),
),
(
ObjectKey::Static("y".to_string()),
f(FilterKind::Field("b".to_string())),
),
]));
let result = eval(&filter, &json!({"a": 1, "b": 2})).unwrap();
assert_eq!(result, vec![json!({"x": 1, "y": 2})]);
}
#[test]
fn test_if_then_else_true() {
let filter = f(FilterKind::IfThenElse {
condition: bf(FilterKind::Literal(json!(true))),
then_branch: bf(FilterKind::Literal(json!("yes"))),
else_branch: bf(FilterKind::Literal(json!("no"))),
});
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("yes")]);
}
#[test]
fn test_if_then_else_false() {
let filter = f(FilterKind::IfThenElse {
condition: bf(FilterKind::Literal(json!(false))),
then_branch: bf(FilterKind::Literal(json!("yes"))),
else_branch: bf(FilterKind::Literal(json!("no"))),
});
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("no")]);
}
#[test]
fn test_alternative_left_truthy() {
let filter = f(FilterKind::Alternative(
bf(FilterKind::Literal(json!(42))),
bf(FilterKind::Literal(json!(0))),
));
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!(42)]);
}
#[test]
fn test_alternative_left_null() {
let filter = f(FilterKind::Alternative(
bf(FilterKind::Literal(json!(null))),
bf(FilterKind::Literal(json!("default"))),
));
assert_eq!(eval(&filter, &json!(null)).unwrap(), vec![json!("default")]);
}
#[test]
fn test_map() {
let filter = f(FilterKind::Function(FunctionCall::Map(bf(
FilterKind::Field("x".to_string()),
))));
let result = eval(&filter, &json!([{"x": 1}, {"x": 2}])).unwrap();
assert_eq!(result, vec![json!([1, 2])]);
}
#[test]
fn test_select() {
let filter = f(FilterKind::Function(FunctionCall::Select(bf(
FilterKind::Compare(
bf(FilterKind::Identity),
CompareOp::Gt,
bf(FilterKind::Literal(json!(2))),
),
))));
assert_eq!(eval(&filter, &json!(5)).unwrap(), vec![json!(5)]);
let empty: Vec<Value> = vec![];
assert_eq!(eval(&filter, &json!(1)).unwrap(), empty);
}
}