//! Direct interpretation of a [`CompiledFilter`] against an [`EvalContext`].
//!
//! The compiled form is a flat vector of "nodes" indexed by compile-time
//! offsets. That keeps the hot path branchy but cache-friendly.
use crate::filter::dsl::ast::Cmp;
use crate::filter::dsl::compile::{CompiledFilter, Node};
use super::context::EvalContext;
pub fn evaluate(filter: &CompiledFilter, ctx: &EvalContext) -> bool {
eval_node(filter, 0, ctx)
}
fn eval_node(filter: &CompiledFilter, index: usize, ctx: &EvalContext) -> bool {
match &filter.nodes[index] {
Node::And { left, right } => {
eval_node(filter, *left, ctx) && eval_node(filter, *right, ctx)
}
Node::Or { left, right } => {
eval_node(filter, *left, ctx) || eval_node(filter, *right, ctx)
}
Node::Not { inner } => !eval_node(filter, *inner, ctx),
Node::CompareStr { field, op, value } => {
compare_strings(ctx.get(field), *op, value)
}
Node::CompareInt { field, op, value } => match ctx.get_int(field) {
Some(lhs) => compare_ints(lhs, *op, *value),
None => false,
},
Node::Match { field, regex } => regex.is_match(ctx.get(field)),
}
}
fn compare_strings(lhs: &str, op: Cmp, rhs: &str) -> bool {
match op {
Cmp::Eq => lhs == rhs,
Cmp::Ne => lhs != rhs,
Cmp::Lt => lhs < rhs,
Cmp::Le => lhs <= rhs,
Cmp::Gt => lhs > rhs,
Cmp::Ge => lhs >= rhs,
Cmp::Match => false,
}
}
fn compare_ints(lhs: i64, op: Cmp, rhs: i64) -> bool {
match op {
Cmp::Eq => lhs == rhs,
Cmp::Ne => lhs != rhs,
Cmp::Lt => lhs < rhs,
Cmp::Le => lhs <= rhs,
Cmp::Gt => lhs > rhs,
Cmp::Ge => lhs >= rhs,
Cmp::Match => false,
}
}