use crate::arena::{ContextStack, DataValue, IterGuard};
use crate::node::{MetadataHint, ReduceHint};
use crate::opcode::OpCode;
use crate::{CompiledNode, Engine, Result};
use bumpalo::Bump;
use std::ops::ControlFlow;
#[inline]
pub(super) fn is_filter_invariant(node: &CompiledNode) -> bool {
match node {
CompiledNode::Value { .. } => true,
CompiledNode::Var { scope_level, .. } => *scope_level > 0,
_ => false,
}
}
#[inline]
pub(super) fn try_extract_filter_field_cmp<'a>(
a: &'a CompiledNode,
b: &'a CompiledNode,
) -> Option<(&'a [crate::node::PathSegment], &'a CompiledNode)> {
if let CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} = a
{
if !segments.is_empty() && is_filter_invariant(b) {
return Some((segments, b));
}
}
None
}
#[inline]
pub(super) fn evaluate_invariant_no_push<'a>(
invariant_node: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
if let CompiledNode::Value { value, .. } = invariant_node {
return Ok(arena.alloc(value.to_arena(arena)));
}
let null_av: &'a DataValue<'a> = crate::arena::singletons::singleton_null();
ctx.push(null_av);
let result = engine.dispatch_node(invariant_node, ctx, arena);
ctx.pop();
result
}
#[derive(Debug, Clone)]
pub(crate) enum FastPredicate {
StrictEq {
var_path: Box<[crate::node::PathSegment]>,
literal: datavalue::OwnedDataValue,
negate: bool,
},
NumericCmp {
var_path: Box<[crate::node::PathSegment]>,
literal_f: f64,
opcode: OpCode,
var_is_lhs: bool,
},
LooseNumericEq {
var_path: Box<[crate::node::PathSegment]>,
literal_f: f64,
negate: bool,
},
}
impl FastPredicate {
pub(crate) fn try_detect_owned(opcode: OpCode, pred_args: &[CompiledNode]) -> Option<Self> {
if pred_args.len() != 2 {
return None;
}
for (var_idx, lit_idx, var_is_lhs) in [(0, 1, true), (1, 0, false)] {
if let CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} = &pred_args[var_idx]
{
if let CompiledNode::Value { value: literal, .. } = &pred_args[lit_idx] {
let var_path: Box<[crate::node::PathSegment]> = segments.clone();
match opcode {
OpCode::StrictEquals | OpCode::StrictNotEquals => {
let negate = matches!(opcode, OpCode::StrictNotEquals);
return Some(FastPredicate::StrictEq {
var_path,
literal: literal.clone(),
negate,
});
}
OpCode::Equals | OpCode::NotEquals => {
if let Some(lit_f) = literal.as_f64() {
let negate = matches!(opcode, OpCode::NotEquals);
return Some(FastPredicate::LooseNumericEq {
var_path,
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 {
var_path,
literal_f: lit_f,
opcode,
var_is_lhs,
});
}
}
_ => {}
}
}
}
}
None
}
#[inline]
pub(super) fn from_node(predicate: &CompiledNode) -> Option<&FastPredicate> {
if let CompiledNode::BuiltinOperator { predicate_hint, .. } = predicate {
return predicate_hint.as_deref();
}
None
}
#[inline(always)]
fn resolve_value<'b>(
segments: &[crate::node::PathSegment],
item: &'b DataValue<'b>,
) -> Option<&'b DataValue<'b>> {
if segments.is_empty() {
Some(item)
} else {
crate::arena::value::traverse_segments(item, segments)
}
}
#[inline(always)]
pub(super) fn evaluate<'b>(&self, item: &'b DataValue<'b>) -> bool {
match self {
FastPredicate::StrictEq {
var_path,
literal,
negate,
} => {
let matches = match Self::resolve_value(var_path, item) {
Some(av) => value_equals_serde(av, literal),
None => false,
};
if *negate { !matches } else { matches }
}
FastPredicate::NumericCmp {
var_path,
literal_f,
opcode,
var_is_lhs,
} => match Self::resolve_value(var_path, item).and_then(|v| v.as_f64()) {
Some(val_f) => {
let (lhs, rhs) = if *var_is_lhs {
(val_f, *literal_f)
} else {
(*literal_f, val_f)
};
inline_numeric_cmp(lhs, rhs, *opcode)
}
None => false,
},
FastPredicate::LooseNumericEq {
var_path,
literal_f,
negate,
} => {
let matches = Self::resolve_value(var_path, item)
.and_then(|v| v.as_f64())
.is_some_and(|val_f| val_f == *literal_f);
if *negate { !matches } else { matches }
}
}
}
}
#[inline(always)]
fn value_equals_serde(av: &DataValue<'_>, v: &datavalue::OwnedDataValue) -> bool {
use datavalue::OwnedDataValue;
match (av, v) {
(DataValue::Null, OwnedDataValue::Null) => true,
(DataValue::Bool(a), OwnedDataValue::Bool(b)) => a == b,
(DataValue::Number(a), OwnedDataValue::Number(b)) => a == b,
(DataValue::String(s), OwnedDataValue::String(b)) => *s == b.as_str(),
(DataValue::Array(_), OwnedDataValue::Array(_))
| (DataValue::Object(_), OwnedDataValue::Object(_)) => value_equals_serde_compound(av, v),
_ => false,
}
}
#[inline(never)]
fn value_equals_serde_compound(av: &DataValue<'_>, v: &datavalue::OwnedDataValue) -> bool {
use datavalue::OwnedDataValue;
match (av, v) {
(DataValue::Array(items), OwnedDataValue::Array(b)) => {
items.len() == b.len()
&& items
.iter()
.zip(b.iter())
.all(|(x, y)| value_equals_serde(x, y))
}
(DataValue::Object(pairs), OwnedDataValue::Object(b)) => {
if pairs.len() != b.len() {
return false;
}
for (k, av) in *pairs {
match b.iter().find(|(bk, _)| bk == *k) {
Some((_, bv)) => {
if !value_equals_serde(av, bv) {
return false;
}
}
None => return false,
}
}
true
}
_ => false,
}
}
#[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,
}
}
#[derive(Clone, Copy)]
pub(crate) struct IterSrc<'a>(pub(crate) &'a [DataValue<'a>]);
impl<'a> IterSrc<'a> {
#[inline]
pub(crate) fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub(crate) fn get(&self, i: usize) -> &'a DataValue<'a> {
&self.0[i]
}
}
pub(crate) enum ResolvedInput<'a> {
Iterable(IterSrc<'a>),
Empty,
Bridge(&'a DataValue<'a>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IterArgKind {
RootVarBorrow { path_segments_empty: bool },
General,
}
impl IterArgKind {
pub(crate) fn classify(arg: &CompiledNode) -> Self {
if let CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} = arg
{
return IterArgKind::RootVarBorrow {
path_segments_empty: segments.is_empty(),
};
}
IterArgKind::General
}
}
#[inline(always)]
pub(crate) fn resolve_iter_input<'a>(
arg: &'a CompiledNode,
kind: IterArgKind,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<ResolvedInput<'a>> {
if let IterArgKind::RootVarBorrow {
path_segments_empty,
} = kind
{
if ctx.depth() == 0 {
let root = ctx.root_input();
let av = if path_segments_empty {
Some(root)
} else if let CompiledNode::Var { segments, .. } = arg {
crate::arena::value::traverse_segments(root, segments)
} else {
None
};
if let Some(av) = av {
return Ok(value_as_iter(av));
}
}
}
let av = engine.dispatch_node(arg, ctx, arena)?;
Ok(value_as_iter(av))
}
#[inline]
fn value_as_iter<'a>(av: &'a DataValue<'a>) -> ResolvedInput<'a> {
match av {
DataValue::Null => ResolvedInput::Empty,
DataValue::Array(items) => ResolvedInput::Iterable(IterSrc(items)),
_ => ResolvedInput::Bridge(av),
}
}
#[inline]
pub(super) fn item_is_null(av: &DataValue<'_>) -> bool {
matches!(av, DataValue::Null)
}
#[inline]
pub(super) fn for_each_iter_array<'a, F>(
items: &'a [DataValue<'a>],
body: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
mut step_fn: F,
) -> Result<()>
where
F: FnMut(usize, &'a DataValue<'a>, &'a DataValue<'a>) -> Result<ControlFlow<()>>,
{
let total = items.len() as u32;
let mut guard = IterGuard::new(ctx);
for (i, item_av) in items.iter().enumerate() {
guard.step_indexed(item_av, i);
let av = engine.run_iter_body(body, guard.stack(), arena, i as u32, total)?;
if step_fn(i, item_av, av)?.is_break() {
break;
}
}
drop(guard);
Ok(())
}
#[inline]
pub(super) fn for_each_iter_object<'a, F>(
pairs: &'a [(&'a str, DataValue<'a>)],
body: &'a CompiledNode,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
mut step_fn: F,
) -> Result<()>
where
F: FnMut(usize, &'a DataValue<'a>, &'a str, &'a DataValue<'a>) -> Result<ControlFlow<()>>,
{
let total = pairs.len() as u32;
let mut guard = IterGuard::new(ctx);
for (i, (k, v)) in pairs.iter().enumerate() {
guard.step_keyed(v, i, k);
let av = engine.run_iter_body(body, guard.stack(), arena, i as u32, total)?;
if step_fn(i, v, k, av)?.is_break() {
break;
}
}
drop(guard);
Ok(())
}