mod ast;
pub mod builtins;
mod eval;
mod parser;
use rayon::prelude::*;
use simd_json::OwnedValue as Value;
use crate::error::{EvalError, FilterError, ParseError};
pub fn parse_check(filter_text: &str) -> Result<(), ParseError> {
if filter_text.trim().is_empty() {
return Err(ParseError {
message: "expected filter expression".to_string(),
position: 0,
});
}
parser::parse(filter_text).map(|_| ())
}
const PARALLEL_THRESHOLD: usize = 10_000;
pub fn evaluate_all(filter_text: &str, inputs: &[Value]) -> Result<Vec<Value>, FilterError> {
if filter_text.trim().is_empty() {
return Err(FilterError::Parse(ParseError {
message: "expected filter expression".to_string(),
position: 0,
}));
}
let filter = match parser::parse(filter_text) {
Ok(f) => f,
Err(parse_err) => {
for input in inputs {
if let Some(eval_err) = try_eval_prefix(filter_text, input) {
return Err(FilterError::Eval(eval_err));
}
}
return Err(FilterError::Parse(parse_err));
}
};
if inputs.len() >= PARALLEL_THRESHOLD {
let results: Result<Vec<Vec<Value>>, (usize, EvalError)> = inputs
.par_iter()
.enumerate()
.map(|(idx, input)| eval::eval(&filter, input).map_err(|e| (idx, e)))
.collect();
match results {
Ok(value_vecs) => Ok(value_vecs.into_iter().flatten().collect()),
Err((idx, mut e)) => {
let pos = find_eval_error_position(filter_text, &inputs[idx]);
e.set_position(pos);
Err(FilterError::Eval(e))
}
}
} else {
let mut all_results = Vec::new();
for input in inputs {
match eval::eval(&filter, input) {
Ok(values) => all_results.extend(values),
Err(mut e) => {
let pos = find_eval_error_position(filter_text, input);
e.set_position(pos);
return Err(FilterError::Eval(e));
}
}
}
Ok(all_results)
}
}
#[allow(dead_code)]
pub fn evaluate(filter_text: &str, input: &Value) -> Result<Vec<Value>, FilterError> {
evaluate_all(filter_text, std::slice::from_ref(input))
}
fn filter_breakpoints(filter_text: &str) -> Vec<usize> {
let mut breakpoints = vec![0];
let mut in_string = false;
let mut prev_char = ' ';
for (i, c) in filter_text.char_indices() {
if c == '"' && prev_char != '\\' {
in_string = !in_string;
}
if !in_string && (c == '.' || prev_char == ']') && !breakpoints.contains(&i) {
breakpoints.push(i);
}
if !in_string && c == '|' {
breakpoints.push(i);
}
prev_char = c;
}
if filter_text.ends_with(']') {
breakpoints.push(filter_text.len());
}
breakpoints.sort();
breakpoints.dedup();
breakpoints
}
fn find_eval_error_position(filter_text: &str, input: &Value) -> usize {
let breakpoints = filter_breakpoints(filter_text);
let mut last_good_pos = 0;
for &pos in &breakpoints {
if pos == 0 || pos > filter_text.len() {
continue;
}
let prefix = &filter_text[..pos];
if let Ok(filter) = parser::parse(prefix)
&& eval::eval(&filter, input).is_ok()
{
last_good_pos = pos;
}
}
last_good_pos
}
fn try_eval_prefix(filter_text: &str, input: &Value) -> Option<EvalError> {
let breakpoints = filter_breakpoints(filter_text);
for &pos in breakpoints.iter().rev() {
if pos == 0 || pos > filter_text.len() {
continue;
}
let prefix = &filter_text[..pos];
if let Ok(filter) = parser::parse(prefix)
&& let Err(mut e) = eval::eval(&filter, input)
{
let error_pos = find_eval_error_position(prefix, input);
e.set_position(error_pos);
return Some(e);
}
}
None
}