use serde_json::Value;
use std::cmp::Ordering;
use std::collections::HashMap;
use super::helpers::is_truthy;
use super::variable;
use crate::constants::INVALID_ARGS;
use crate::node::{MetadataHint, ReduceHint};
use crate::opcode::OpCode;
use crate::trace::TraceCollector;
use crate::{CompiledNode, ContextStack, DataLogic, Error, Result};
#[inline]
fn is_filter_invariant(node: &CompiledNode) -> bool {
match node {
CompiledNode::Value { .. } => true,
CompiledNode::CompiledVar { scope_level, .. } => *scope_level > 0,
_ => false,
}
}
#[inline]
fn try_extract_filter_field_cmp<'a>(
a: &'a CompiledNode,
b: &'a CompiledNode,
) -> Option<(&'a [crate::node::PathSegment], &'a CompiledNode)> {
if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
} = a
&& !segments.is_empty()
&& is_filter_invariant(b)
{
return Some((segments, b));
}
None
}
fn evaluate_invariant_no_push(
invariant_node: &CompiledNode,
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
match invariant_node {
CompiledNode::Value { value, .. } => Ok(value.clone()),
CompiledNode::CompiledVar {
scope_level,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value,
} if *scope_level > 0 => variable::evaluate_compiled_var(
scope_level - 1,
segments,
ReduceHint::None,
MetadataHint::None,
default_value.as_deref(),
context,
engine,
),
_ => {
context.push(Value::Null);
let result = engine.evaluate_node(invariant_node, context)?;
context.pop();
Ok(result)
}
}
}
enum FastPredicate<'a> {
StrictEq {
segments: Option<&'a [crate::node::PathSegment]>,
literal: &'a Value,
negate: bool,
},
NumericCmp {
segments: Option<&'a [crate::node::PathSegment]>,
literal_f: f64,
opcode: OpCode,
var_is_lhs: bool,
},
LooseNumericEq {
segments: Option<&'a [crate::node::PathSegment]>,
literal_f: f64,
negate: bool,
},
}
impl<'a> FastPredicate<'a> {
fn try_detect(predicate: &'a CompiledNode) -> Option<Self> {
if let CompiledNode::BuiltinOperator {
opcode,
args: pred_args,
} = predicate
&& pred_args.len() == 2
{
for (var_idx, lit_idx, var_is_lhs) in [(0, 1, true), (1, 0, false)] {
if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
} = &pred_args[var_idx]
&& let CompiledNode::Value { value: literal } = &pred_args[lit_idx]
{
let segs = if segments.is_empty() {
None
} else {
Some(&**segments)
};
match opcode {
OpCode::StrictEquals | OpCode::StrictNotEquals => {
let negate = matches!(opcode, OpCode::StrictNotEquals);
return Some(FastPredicate::StrictEq {
segments: segs,
literal,
negate,
});
}
OpCode::Equals | OpCode::NotEquals => {
if let Some(lit_f) = literal.as_f64() {
let negate = matches!(opcode, OpCode::NotEquals);
return Some(FastPredicate::LooseNumericEq {
segments: segs,
literal_f: lit_f,
negate,
});
}
}
OpCode::GreaterThan
| OpCode::GreaterThanEqual
| OpCode::LessThan
| OpCode::LessThanEqual => {
if let Some(lit_f) = literal.as_f64() {
return Some(FastPredicate::NumericCmp {
segments: segs,
literal_f: lit_f,
opcode: *opcode,
var_is_lhs,
});
}
}
_ => {}
}
}
}
}
None
}
#[inline]
fn resolve_value<'v>(
segments: Option<&[crate::node::PathSegment]>,
item: &'v Value,
) -> Option<&'v Value> {
match segments {
None => Some(item),
Some(segs) => super::variable::try_traverse_segments(item, segs),
}
}
#[inline]
fn evaluate(&self, item: &Value) -> bool {
match self {
FastPredicate::StrictEq {
segments,
literal,
negate,
} => {
let matches = Self::resolve_value(*segments, item) == Some(*literal);
if *negate { !matches } else { matches }
}
FastPredicate::NumericCmp {
segments,
literal_f,
opcode,
var_is_lhs,
} => {
if let Some(val) = Self::resolve_value(*segments, item)
&& let Some(val_f) = val.as_f64()
{
let (lhs, rhs) = if *var_is_lhs {
(val_f, *literal_f)
} else {
(*literal_f, val_f)
};
inline_numeric_cmp(lhs, rhs, *opcode)
} else {
false
}
}
FastPredicate::LooseNumericEq {
segments,
literal_f,
negate,
} => {
let matches = if let Some(val) = Self::resolve_value(*segments, item)
&& let Some(val_f) = val.as_f64()
{
val_f == *literal_f
} else {
false
};
if *negate { !matches } else { matches }
}
}
}
}
#[inline(always)]
fn inline_numeric_cmp(lhs: f64, rhs: f64, opcode: OpCode) -> bool {
match opcode {
OpCode::GreaterThan => lhs > rhs,
OpCode::GreaterThanEqual => lhs >= rhs,
OpCode::LessThan => lhs < rhs,
OpCode::LessThanEqual => lhs <= rhs,
_ => false,
}
}
fn try_reduce_fast_path(
arr: &[Value],
initial: &Value,
body_args: &[CompiledNode],
opcode: OpCode,
) -> Option<Value> {
let (current_arg, _acc_arg) = match (&body_args[0], &body_args[1]) {
(
CompiledNode::CompiledVar {
reduce_hint: hint0, ..
},
CompiledNode::CompiledVar {
reduce_hint: hint1, ..
},
) => match (hint0, hint1) {
(
ReduceHint::Current | ReduceHint::CurrentPath,
ReduceHint::Accumulator | ReduceHint::AccumulatorPath,
) => (&body_args[0], &body_args[1]),
(
ReduceHint::Accumulator | ReduceHint::AccumulatorPath,
ReduceHint::Current | ReduceHint::CurrentPath,
) => (&body_args[1], &body_args[0]),
_ => return None,
},
_ => return None,
};
let current_segments = if let CompiledNode::CompiledVar {
segments,
reduce_hint,
..
} = current_arg
{
match reduce_hint {
ReduceHint::Current => &[][..], ReduceHint::CurrentPath => {
if segments.len() >= 2 {
&segments[1..]
} else {
return None;
}
}
_ => return None,
}
} else {
return None;
};
let mut acc_i = initial.as_i64();
if acc_i.is_some() {
let mut all_int = true;
for item in arr {
let current_val = if current_segments.is_empty() {
item
} else {
super::variable::try_traverse_segments(item, current_segments)?
};
if let Some(cur_i) = current_val.as_i64() {
let a = acc_i.unwrap();
acc_i = Some(match opcode {
OpCode::Add => a.wrapping_add(cur_i),
OpCode::Multiply => a.wrapping_mul(cur_i),
OpCode::Subtract => a.wrapping_sub(cur_i),
_ => return None,
});
} else {
all_int = false;
break;
}
}
if all_int {
return acc_i.map(Value::from);
}
}
let mut acc_f = initial.as_f64()?;
for item in arr {
let current_val = if current_segments.is_empty() {
item
} else {
super::variable::try_traverse_segments(item, current_segments)?
};
let cur_f = current_val.as_f64()?;
acc_f = match opcode {
OpCode::Add => acc_f + cur_f,
OpCode::Multiply => acc_f * cur_f,
OpCode::Subtract => acc_f - cur_f,
_ => return None,
};
}
Some(Value::from(acc_f))
}
#[inline]
pub fn evaluate_merge(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
let mut result = Vec::new();
for arg in args {
let value = engine.evaluate_node(arg, context)?;
match value {
Value::Array(arr) => {
result.extend(arr.into_iter().filter(|v| !v.is_null()))
}
Value::Null => {
}
v => result.push(v),
}
}
Ok(Value::Array(result))
}
#[inline]
pub fn evaluate_map(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
let logic = &args[1];
match collection {
Value::Array(arr) => {
if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
} = logic
{
if segments.is_empty() {
return Ok(Value::Array(arr));
}
let mut results = Vec::with_capacity(arr.len());
for item in arr.iter() {
let val = super::variable::try_traverse_segments(item, segments)
.cloned()
.unwrap_or(Value::Null);
results.push(val);
}
return Ok(Value::Array(results));
}
if let CompiledNode::BuiltinOperator {
opcode,
args: body_args,
} = logic
&& body_args.len() == 2
&& matches!(
opcode,
OpCode::Add
| OpCode::Subtract
| OpCode::Multiply
| OpCode::Divide
| OpCode::Modulo
)
{
for (var_idx, lit_idx) in [(0, 1), (1, 0)] {
if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
} = &body_args[var_idx]
&& segments.is_empty()
&& let CompiledNode::Value { value: lit_val } = &body_args[lit_idx]
&& let Some(lit_f) = lit_val.as_f64()
{
let mut results = Vec::with_capacity(arr.len());
for item in &arr {
if let Some(item_f) = item.as_f64() {
let (lhs, rhs) = if var_idx == 0 {
(item_f, lit_f)
} else {
(lit_f, item_f)
};
let r = match opcode {
OpCode::Add => lhs + rhs,
OpCode::Subtract => lhs - rhs,
OpCode::Multiply => lhs * rhs,
OpCode::Divide => lhs / rhs,
OpCode::Modulo => lhs % rhs,
_ => unreachable!(),
};
if r.fract() == 0.0 && r >= i64::MIN as f64 && r <= i64::MAX as f64
{
results.push(Value::from(r as i64));
} else {
results.push(Value::from(r));
}
} else {
results.push(Value::Null);
}
}
return Ok(Value::Array(results));
}
}
}
let len = arr.len();
let mut results = Vec::with_capacity(len);
let mut pushed = false;
for (index, item) in arr.into_iter().enumerate() {
if !pushed {
context.push_with_index(item, 0);
pushed = true;
} else {
context.replace_top_data(item, index);
}
let result = engine.evaluate_node(logic, context)?;
results.push(result);
}
if len > 0 {
context.pop();
}
Ok(Value::Array(results))
}
Value::Object(obj) => {
let mut results = Vec::with_capacity(obj.len());
for (index, (key, value)) in obj.iter().enumerate() {
if index == 0 {
context.push_with_key_index(value.clone(), 0, key.clone());
} else {
context.replace_top_key_data(value.clone(), index, key.clone());
}
let result = engine.evaluate_node(logic, context)?;
results.push(result);
}
if !obj.is_empty() {
context.pop();
}
Ok(Value::Array(results))
}
Value::Null => Ok(Value::Array(vec![])),
other => {
context.push_with_index(other, 0);
let result = engine.evaluate_node(logic, context)?;
context.pop();
Ok(Value::Array(vec![result]))
}
}
}
#[inline]
pub fn evaluate_filter(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
let predicate = &args[1];
match collection {
Value::Array(arr) => {
if let CompiledNode::BuiltinOperator {
opcode,
args: pred_args,
} = predicate
&& pred_args.len() == 2
&& matches!(opcode, OpCode::StrictEquals | OpCode::StrictNotEquals)
{
let fast = try_extract_filter_field_cmp(&pred_args[0], &pred_args[1])
.or_else(|| try_extract_filter_field_cmp(&pred_args[1], &pred_args[0]));
if let Some((segments, invariant_node)) = fast {
let invariant_val =
evaluate_invariant_no_push(invariant_node, context, engine)?;
let is_eq = matches!(opcode, OpCode::StrictEquals);
let results: Vec<Value> = arr
.into_iter()
.filter(|item| {
let matches = super::variable::try_traverse_segments(item, segments)
== Some(&invariant_val);
if is_eq { matches } else { !matches }
})
.collect();
return Ok(Value::Array(results));
}
}
if let Some(fast_pred) = FastPredicate::try_detect(predicate) {
let results: Vec<Value> = arr
.into_iter()
.filter(|item| fast_pred.evaluate(item))
.collect();
return Ok(Value::Array(results));
}
let len = arr.len();
let mut results = Vec::with_capacity(arr.len());
let mut pushed = false;
for (index, item) in arr.into_iter().enumerate() {
if !pushed {
context.push_with_index(item, 0);
pushed = true;
} else {
context.replace_top_data(item, index);
}
let keep = engine.evaluate_node(predicate, context)?;
if is_truthy(&keep, engine) {
results.push(context.take_top_data());
}
}
if len > 0 {
context.pop();
}
Ok(Value::Array(results))
}
Value::Object(obj) => {
let mut result_obj = serde_json::Map::new();
for (index, (key, value)) in obj.iter().enumerate() {
if index == 0 {
context.push_with_key_index(value.clone(), 0, key.clone());
} else {
context.replace_top_key_data(value.clone(), index, key.clone());
}
let keep = engine.evaluate_node(predicate, context)?;
if is_truthy(&keep, engine) {
result_obj.insert(key.clone(), value.clone());
}
}
if !obj.is_empty() {
context.pop();
}
Ok(Value::Object(result_obj))
}
Value::Null => Ok(Value::Array(vec![])),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_reduce(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 3 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let array = engine.evaluate_node(&args[0], context)?;
let logic = &args[1];
let initial = engine.evaluate_node(&args[2], context)?;
match array {
Value::Array(arr) => {
if arr.is_empty() {
return Ok(initial);
}
if let CompiledNode::BuiltinOperator {
opcode,
args: body_args,
} = logic
&& body_args.len() == 2
&& matches!(opcode, OpCode::Add | OpCode::Multiply | OpCode::Subtract)
&& let Some(result) = try_reduce_fast_path(&arr, &initial, body_args, *opcode)
{
return Ok(result);
}
let len = arr.len();
let mut accumulator = initial;
let mut pushed = false;
for current in arr.into_iter() {
if !pushed {
context.push_reduce(current, accumulator);
pushed = true;
} else {
context.replace_reduce_data(current, accumulator);
}
accumulator = engine.evaluate_node(logic, context)?;
}
if len > 0 {
context.pop();
}
Ok(accumulator)
}
Value::Null => Ok(initial),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_all(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
let predicate = &args[1];
match collection {
Value::Array(arr) if !arr.is_empty() => {
if let CompiledNode::BuiltinOperator {
opcode,
args: pred_args,
} = predicate
&& pred_args.len() == 2
&& matches!(opcode, OpCode::StrictEquals | OpCode::StrictNotEquals)
{
let fast = try_extract_filter_field_cmp(&pred_args[0], &pred_args[1])
.or_else(|| try_extract_filter_field_cmp(&pred_args[1], &pred_args[0]));
if let Some((segments, invariant_node)) = fast {
let invariant_val =
evaluate_invariant_no_push(invariant_node, context, engine)?;
let is_eq = matches!(opcode, OpCode::StrictEquals);
return Ok(Value::Bool(arr.iter().all(|item| {
let matches = super::variable::try_traverse_segments(item, segments)
== Some(&invariant_val);
if is_eq { matches } else { !matches }
})));
}
}
if let Some(fast_pred) = FastPredicate::try_detect(predicate) {
return Ok(Value::Bool(arr.iter().all(|item| fast_pred.evaluate(item))));
}
let len = arr.len();
let mut pushed = false;
for (index, item) in arr.into_iter().enumerate() {
if !pushed {
context.push_with_index(item, 0);
pushed = true;
} else {
context.replace_top_data(item, index);
}
let result = engine.evaluate_node(predicate, context)?;
if !is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(false));
}
}
if len > 0 {
context.pop();
}
Ok(Value::Bool(true))
}
Value::Array(arr) if arr.is_empty() => Ok(Value::Bool(false)),
Value::Null => Ok(Value::Bool(false)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_some(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
let predicate = &args[1];
match collection {
Value::Array(arr) => {
if let CompiledNode::BuiltinOperator {
opcode,
args: pred_args,
} = predicate
&& pred_args.len() == 2
&& matches!(opcode, OpCode::StrictEquals | OpCode::StrictNotEquals)
{
let fast = try_extract_filter_field_cmp(&pred_args[0], &pred_args[1])
.or_else(|| try_extract_filter_field_cmp(&pred_args[1], &pred_args[0]));
if let Some((segments, invariant_node)) = fast {
let invariant_val =
evaluate_invariant_no_push(invariant_node, context, engine)?;
let is_eq = matches!(opcode, OpCode::StrictEquals);
return Ok(Value::Bool(arr.iter().any(|item| {
let matches = super::variable::try_traverse_segments(item, segments)
== Some(&invariant_val);
if is_eq { matches } else { !matches }
})));
}
}
if let Some(fast_pred) = FastPredicate::try_detect(predicate) {
return Ok(Value::Bool(arr.iter().any(|item| fast_pred.evaluate(item))));
}
let len = arr.len();
let mut pushed = false;
for (index, item) in arr.into_iter().enumerate() {
if !pushed {
context.push_with_index(item, 0);
pushed = true;
} else {
context.replace_top_data(item, index);
}
let result = engine.evaluate_node(predicate, context)?;
if is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(true));
}
}
if len > 0 {
context.pop();
}
Ok(Value::Bool(false))
}
Value::Null => Ok(Value::Bool(false)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_none(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
let predicate = &args[1];
match collection {
Value::Array(arr) => {
if let CompiledNode::BuiltinOperator {
opcode,
args: pred_args,
} = predicate
&& pred_args.len() == 2
&& matches!(opcode, OpCode::StrictEquals | OpCode::StrictNotEquals)
{
let fast = try_extract_filter_field_cmp(&pred_args[0], &pred_args[1])
.or_else(|| try_extract_filter_field_cmp(&pred_args[1], &pred_args[0]));
if let Some((segments, invariant_node)) = fast {
let invariant_val =
evaluate_invariant_no_push(invariant_node, context, engine)?;
let is_eq = matches!(opcode, OpCode::StrictEquals);
return Ok(Value::Bool(!arr.iter().any(|item| {
let matches = super::variable::try_traverse_segments(item, segments)
== Some(&invariant_val);
if is_eq { matches } else { !matches }
})));
}
}
if let Some(fast_pred) = FastPredicate::try_detect(predicate) {
return Ok(Value::Bool(
!arr.iter().any(|item| fast_pred.evaluate(item)),
));
}
let len = arr.len();
let mut pushed = false;
for (index, item) in arr.into_iter().enumerate() {
if !pushed {
context.push_with_index(item, 0);
pushed = true;
} else {
context.replace_top_data(item, index);
}
let result = engine.evaluate_node(predicate, context)?;
if is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(false));
}
}
if len > 0 {
context.pop();
}
Ok(Value::Bool(true))
}
Value::Null => Ok(Value::Bool(true)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_sort(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
if let CompiledNode::Value { value, .. } = &args[0]
&& value.is_null()
{
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let array_value = engine.evaluate_node(&args[0], context)?;
let mut array = match array_value {
Value::Array(arr) => arr,
Value::Null => return Ok(Value::Null), _ => return Err(Error::InvalidArguments(INVALID_ARGS.into())),
};
let ascending = if args.len() > 1 {
let dir = engine.evaluate_node(&args[1], context)?;
match dir {
Value::Bool(b) => b,
_ => true, }
} else {
true
};
let has_extractor = args.len() > 2;
if has_extractor {
let extractor = &args[2];
let keys = if let CompiledNode::CompiledVar {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
} = extractor
{
if !segments.is_empty() {
let mut keys: Vec<Value> = Vec::with_capacity(array.len());
for item in array.iter() {
let key = super::variable::try_traverse_segments(item, segments)
.cloned()
.unwrap_or(Value::Null);
keys.push(key);
}
Some(keys)
} else {
None
}
} else {
None
};
let keys = if let Some(k) = keys {
k
} else {
let mut keys: Vec<Value> = Vec::with_capacity(array.len());
let mut pushed = false;
for (index, item) in array.iter().enumerate() {
if !pushed {
context.push_with_index(item.clone(), 0);
pushed = true;
} else {
context.replace_top_data(item.clone(), index);
}
keys.push(engine.evaluate_node(extractor, context)?);
}
if pushed {
context.pop();
}
keys
};
let mut indices: Vec<usize> = (0..array.len()).collect();
indices.sort_by(|&a, &b| {
let cmp = compare_values(&keys[a], &keys[b]);
if ascending { cmp } else { cmp.reverse() }
});
let mut sorted = Vec::with_capacity(array.len());
for i in indices {
sorted.push(std::mem::replace(&mut array[i], Value::Null));
}
array = sorted;
} else {
array.sort_by(|a, b| {
let cmp = compare_values(a, b);
if ascending { cmp } else { cmp.reverse() }
});
}
Ok(Value::Array(array))
}
#[inline]
fn extract_opt_i64(
node: &CompiledNode,
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Option<i64>> {
match node {
CompiledNode::Value {
value: Value::Number(n),
..
} => Ok(n.as_i64()),
CompiledNode::Value {
value: Value::Null, ..
} => Ok(None),
_ => {
let val = engine.evaluate_node(node, context)?;
match val {
Value::Number(n) => Ok(n.as_i64()),
Value::Null => Ok(None),
_ => Err(Error::InvalidArguments("NaN".to_string())),
}
}
}
}
#[inline]
pub fn evaluate_slice(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
) -> Result<Value> {
if args.is_empty() {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node(&args[0], context)?;
if collection == Value::Null {
return Ok(Value::Null);
}
let start = if args.len() > 1 {
extract_opt_i64(&args[1], context, engine)?
} else {
None
};
let end = if args.len() > 2 {
extract_opt_i64(&args[2], context, engine)?
} else {
None
};
let step = if args.len() > 3 {
let s = extract_opt_i64(&args[3], context, engine)?.unwrap_or(1);
if s == 0 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
s
} else {
1
};
match collection {
Value::Array(arr) => {
let len = arr.len() as i64;
let result = slice_sequence(&arr, len, start, end, step);
Ok(Value::Array(result))
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
let len = chars.len() as i64;
let result_string = slice_chars(&chars, len, start, end, step);
Ok(Value::String(result_string))
}
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
fn compare_values(a: &Value, b: &Value) -> Ordering {
match (a, b) {
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Less,
(_, Value::Null) => Ordering::Greater,
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Number(a), Value::Number(b)) => {
let a_f = a.as_f64().unwrap_or(0.0);
let b_f = b.as_f64().unwrap_or(0.0);
if a_f < b_f {
Ordering::Less
} else if a_f > b_f {
Ordering::Greater
} else {
Ordering::Equal
}
}
(Value::String(a), Value::String(b)) => a.cmp(b),
(Value::Bool(_), Value::Number(_)) => Ordering::Less,
(Value::Bool(_), Value::String(_)) => Ordering::Less,
(Value::Bool(_), Value::Array(_)) => Ordering::Less,
(Value::Bool(_), Value::Object(_)) => Ordering::Less,
(Value::Number(_), Value::Bool(_)) => Ordering::Greater,
(Value::Number(_), Value::String(_)) => Ordering::Less,
(Value::Number(_), Value::Array(_)) => Ordering::Less,
(Value::Number(_), Value::Object(_)) => Ordering::Less,
(Value::String(_), Value::Bool(_)) => Ordering::Greater,
(Value::String(_), Value::Number(_)) => Ordering::Greater,
(Value::String(_), Value::Array(_)) => Ordering::Less,
(Value::String(_), Value::Object(_)) => Ordering::Less,
(Value::Array(_), _) => Ordering::Greater,
(_, Value::Array(_)) => Ordering::Less,
(Value::Object(_), Value::Object(_)) => Ordering::Equal,
(Value::Object(_), _) => Ordering::Greater,
}
}
fn slice_sequence(
arr: &[Value],
len: i64,
start: Option<i64>,
end: Option<i64>,
step: i64,
) -> Vec<Value> {
let mut result = Vec::new();
let (actual_start, actual_end) = if step > 0 {
let s = normalize_index(start.unwrap_or(0), len);
let e = normalize_index(end.unwrap_or(len), len);
(s, e)
} else {
let default_start = len.saturating_sub(1);
let s = normalize_index(start.unwrap_or(default_start), len);
let e = if let Some(e) = end {
normalize_index(e, len)
} else {
-1 };
(s, e)
};
if step > 0 {
let mut i = actual_start;
while i < actual_end && i < len {
if i >= 0 && (i as usize) < arr.len() {
result.push(arr[i as usize].clone());
}
i = i.saturating_add(step);
if step > 0 && i < actual_start {
break;
}
}
} else {
let mut i = actual_start;
while i > actual_end && i >= 0 && i < len {
if (i as usize) < arr.len() {
result.push(arr[i as usize].clone());
}
let next_i = i.saturating_add(step);
if step < 0 && next_i > i {
break;
}
i = next_i;
}
}
result
}
fn slice_chars(
chars: &[char],
len: i64,
start: Option<i64>,
end: Option<i64>,
step: i64,
) -> String {
let mut result = String::new();
let (actual_start, actual_end) = if step > 0 {
let s = normalize_index(start.unwrap_or(0), len);
let e = normalize_index(end.unwrap_or(len), len);
(s, e)
} else {
let default_start = len.saturating_sub(1);
let s = normalize_index(start.unwrap_or(default_start), len);
let e = if let Some(e) = end {
normalize_index(e, len)
} else {
-1
};
(s, e)
};
if step > 0 {
let mut i = actual_start;
while i < actual_end && i < len {
if i >= 0 && (i as usize) < chars.len() {
result.push(chars[i as usize]);
}
i = i.saturating_add(step);
if step > 0 && i < actual_start {
break;
}
}
} else {
let mut i = actual_start;
while i > actual_end && i >= 0 && i < len {
if (i as usize) < chars.len() {
result.push(chars[i as usize]);
}
let next_i = i.saturating_add(step);
if step < 0 && next_i > i {
break;
}
i = next_i;
}
}
result
}
fn normalize_index(index: i64, len: i64) -> i64 {
if index < 0 {
let adjusted = len.saturating_add(index);
adjusted.max(0)
} else {
index.min(len)
}
}
#[inline]
pub fn evaluate_map_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let logic = &args[1];
match &collection {
Value::Array(arr) => {
let total = arr.len() as u32;
let mut results = Vec::with_capacity(arr.len());
for (index, item) in arr.iter().enumerate() {
if index == 0 {
context.push_with_index(item.clone(), 0);
} else {
context.replace_top_data(item.clone(), index);
}
collector.push_iteration(index as u32, total);
let result = engine.evaluate_node_traced(logic, context, collector, node_id_map)?;
results.push(result);
collector.pop_iteration();
}
if !arr.is_empty() {
context.pop();
}
Ok(Value::Array(results))
}
Value::Object(obj) => {
let total = obj.len() as u32;
let mut results = Vec::with_capacity(obj.len());
for (index, (key, value)) in obj.iter().enumerate() {
if index == 0 {
context.push_with_key_index(value.clone(), 0, key.clone());
} else {
context.replace_top_key_data(value.clone(), index, key.clone());
}
collector.push_iteration(index as u32, total);
let result = engine.evaluate_node_traced(logic, context, collector, node_id_map)?;
results.push(result);
collector.pop_iteration();
}
if !obj.is_empty() {
context.pop();
}
Ok(Value::Array(results))
}
Value::Null => Ok(Value::Array(vec![])),
_ => {
context.push_with_index(collection, 0);
collector.push_iteration(0, 1);
let result = engine.evaluate_node_traced(logic, context, collector, node_id_map)?;
collector.pop_iteration();
context.pop();
Ok(Value::Array(vec![result]))
}
}
}
#[inline]
pub fn evaluate_filter_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let predicate = &args[1];
match &collection {
Value::Array(arr) => {
let total = arr.len() as u32;
let mut results = Vec::new();
for (index, item) in arr.iter().enumerate() {
if index == 0 {
context.push_with_index(item.clone(), 0);
} else {
context.replace_top_data(item.clone(), index);
}
collector.push_iteration(index as u32, total);
let keep =
engine.evaluate_node_traced(predicate, context, collector, node_id_map)?;
collector.pop_iteration();
if is_truthy(&keep, engine) {
results.push(item.clone());
}
}
if !arr.is_empty() {
context.pop();
}
Ok(Value::Array(results))
}
Value::Object(obj) => {
let total = obj.len() as u32;
let mut result_obj = serde_json::Map::new();
for (index, (key, value)) in obj.iter().enumerate() {
if index == 0 {
context.push_with_key_index(value.clone(), 0, key.clone());
} else {
context.replace_top_key_data(value.clone(), index, key.clone());
}
collector.push_iteration(index as u32, total);
let keep =
engine.evaluate_node_traced(predicate, context, collector, node_id_map)?;
collector.pop_iteration();
if is_truthy(&keep, engine) {
result_obj.insert(key.clone(), value.clone());
}
}
if !obj.is_empty() {
context.pop();
}
Ok(Value::Object(result_obj))
}
Value::Null => Ok(Value::Array(vec![])),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_reduce_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 3 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let array = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let logic = &args[1];
let initial = engine.evaluate_node_traced(&args[2], context, collector, node_id_map)?;
match &array {
Value::Array(arr) => {
if arr.is_empty() {
return Ok(initial);
}
let total = arr.len() as u32;
let mut accumulator = initial;
for (index, current) in arr.iter().enumerate() {
if index == 0 {
context.push_reduce(current.clone(), accumulator);
} else {
context.replace_reduce_data(current.clone(), accumulator);
}
collector.push_iteration(index as u32, total);
accumulator =
engine.evaluate_node_traced(logic, context, collector, node_id_map)?;
collector.pop_iteration();
}
if !arr.is_empty() {
context.pop();
}
Ok(accumulator)
}
Value::Null => Ok(initial),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_all_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let predicate = &args[1];
match &collection {
Value::Array(arr) if !arr.is_empty() => {
let total = arr.len() as u32;
for (index, item) in arr.iter().enumerate() {
if index == 0 {
context.push_with_index(item.clone(), 0);
} else {
context.replace_top_data(item.clone(), index);
}
collector.push_iteration(index as u32, total);
let result =
engine.evaluate_node_traced(predicate, context, collector, node_id_map)?;
collector.pop_iteration();
if !is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(false));
}
}
context.pop();
Ok(Value::Bool(true))
}
Value::Array(arr) if arr.is_empty() => Ok(Value::Bool(false)),
Value::Null => Ok(Value::Bool(false)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_some_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let predicate = &args[1];
match &collection {
Value::Array(arr) => {
let total = arr.len() as u32;
for (index, item) in arr.iter().enumerate() {
if index == 0 {
context.push_with_index(item.clone(), 0);
} else {
context.replace_top_data(item.clone(), index);
}
collector.push_iteration(index as u32, total);
let result =
engine.evaluate_node_traced(predicate, context, collector, node_id_map)?;
collector.pop_iteration();
if is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(true));
}
}
if !arr.is_empty() {
context.pop();
}
Ok(Value::Bool(false))
}
Value::Null => Ok(Value::Bool(false)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}
#[inline]
pub fn evaluate_none_traced(
args: &[CompiledNode],
context: &mut ContextStack,
engine: &DataLogic,
collector: &mut TraceCollector,
node_id_map: &HashMap<usize, u32>,
) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(INVALID_ARGS.into()));
}
let collection = engine.evaluate_node_traced(&args[0], context, collector, node_id_map)?;
let predicate = &args[1];
match &collection {
Value::Array(arr) => {
let total = arr.len() as u32;
for (index, item) in arr.iter().enumerate() {
if index == 0 {
context.push_with_index(item.clone(), 0);
} else {
context.replace_top_data(item.clone(), index);
}
collector.push_iteration(index as u32, total);
let result =
engine.evaluate_node_traced(predicate, context, collector, node_id_map)?;
collector.pop_iteration();
if is_truthy(&result, engine) {
context.pop();
return Ok(Value::Bool(false));
}
}
if !arr.is_empty() {
context.pop();
}
Ok(Value::Bool(true))
}
Value::Null => Ok(Value::Bool(true)),
_ => Err(Error::InvalidArguments(INVALID_ARGS.into())),
}
}