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,
},
Truthy {
var_path: Box<[crate::node::PathSegment]>,
},
LooseStrEq {
var_path: Box<[crate::node::PathSegment]>,
literal: Box<str>,
negate: bool,
},
InStrLits {
var_path: Box<[crate::node::PathSegment]>,
items: Box<[Box<str>]>,
},
AllOf(Box<[FastPredicate]>),
AnyOf(Box<[FastPredicate]>),
Not(Box<FastPredicate>),
}
const MAX_PREDICATE_DEPTH: u32 = 4;
impl FastPredicate {
pub(crate) fn try_detect_owned(opcode: OpCode, pred_args: &[CompiledNode]) -> Option<Self> {
Self::detect_op(opcode, pred_args, 0)
}
fn detect_op(opcode: OpCode, args: &[CompiledNode], depth: u32) -> Option<Self> {
if depth >= MAX_PREDICATE_DEPTH {
return None;
}
match opcode {
OpCode::And | OpCode::Or if !args.is_empty() => {
let preds: Option<Box<[FastPredicate]>> = args
.iter()
.map(|a| Self::detect_operand(a, depth + 1))
.collect();
let preds = preds?;
Some(if matches!(opcode, OpCode::And) {
FastPredicate::AllOf(preds)
} else {
FastPredicate::AnyOf(preds)
})
}
OpCode::Not if args.len() == 1 => Some(FastPredicate::Not(Box::new(
Self::detect_operand(&args[0], depth + 1)?,
))),
OpCode::BoolCast if args.len() == 1 => Self::detect_operand(&args[0], depth + 1),
OpCode::In if args.len() == 2 => {
let CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} = &args[0]
else {
return None;
};
let CompiledNode::Value {
value: datavalue::OwnedDataValue::Array(items),
..
} = &args[1]
else {
return None;
};
let strs: Option<Box<[Box<str>]>> = items
.iter()
.map(|it| match it {
datavalue::OwnedDataValue::String(s) => Some(s.as_str().into()),
_ => None,
})
.collect();
Some(FastPredicate::InStrLits {
var_path: segments.clone(),
items: strs?,
})
}
_ => Self::detect_cmp_leaf(opcode, args),
}
}
fn detect_operand(node: &CompiledNode, depth: u32) -> Option<Self> {
match node {
CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} => Some(FastPredicate::Truthy {
var_path: segments.clone(),
}),
CompiledNode::BuiltinOperator { opcode, args, .. } => {
Self::detect_op(*opcode, args, depth)
}
_ => None,
}
}
fn detect_cmp_leaf(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,
});
}
if let datavalue::OwnedDataValue::String(s) = literal {
let negate = matches!(opcode, OpCode::NotEquals);
return Some(FastPredicate::LooseStrEq {
var_path,
literal: s.as_str().into(),
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]
pub(super) fn evaluate_opt<'b>(
&self,
item: &'b DataValue<'b>,
engine: &crate::Engine,
) -> Option<bool> {
match self {
FastPredicate::StrictEq {
var_path,
literal,
negate,
} => {
let av = Self::resolve_value(var_path, item).unwrap_or(&DataValue::Null);
Some(value_equals_serde(av, literal) != *negate)
}
FastPredicate::NumericCmp {
var_path,
literal_f,
opcode,
var_is_lhs,
} => match Self::resolve_value(var_path, item) {
Some(DataValue::Number(n)) => {
let val_f = n.as_f64();
let (lhs, rhs) = if *var_is_lhs {
(val_f, *literal_f)
} else {
(*literal_f, val_f)
};
Some(inline_numeric_cmp(lhs, rhs, *opcode))
}
_ => None,
},
FastPredicate::LooseNumericEq {
var_path,
literal_f,
negate,
} => match Self::resolve_value(var_path, item) {
Some(DataValue::Number(n)) => Some((n.as_f64() == *literal_f) != *negate),
_ => None,
},
FastPredicate::Truthy { var_path } => Some(match Self::resolve_value(var_path, item) {
Some(av) => crate::arena::truthy_arena(av, engine),
None => false,
}),
FastPredicate::LooseStrEq {
var_path,
literal,
negate,
} => match Self::resolve_value(var_path, item) {
Some(DataValue::String(s)) => Some((*s == &**literal) != *negate),
_ => None,
},
FastPredicate::InStrLits { var_path, items } => {
let found = match Self::resolve_value(var_path, item) {
Some(DataValue::String(s)) => items.iter().any(|lit| &**lit == *s),
_ => false,
};
Some(found)
}
FastPredicate::AllOf(preds) => {
for p in preds.iter() {
if !p.evaluate_opt(item, engine)? {
return Some(false);
}
}
Some(true)
}
FastPredicate::AnyOf(preds) => {
for p in preds.iter() {
if p.evaluate_opt(item, engine)? {
return Some(true);
}
}
Some(false)
}
FastPredicate::Not(inner) => inner.evaluate_opt(item, engine).map(|b| !b),
}
}
}
#[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(())
}
pub(super) struct FieldCursor<'n> {
segments: &'n [crate::node::PathSegment],
single_key: Option<&'n str>,
hint: usize,
}
impl<'n> FieldCursor<'n> {
#[inline]
pub(super) fn new(segments: &'n [crate::node::PathSegment]) -> Self {
use crate::node::PathSegment;
let single_key = match segments {
[PathSegment::Field(k)] => Some(k.as_ref()),
[PathSegment::FieldOrIndex(k, _)] => Some(k.as_ref()),
_ => None,
};
Self {
segments,
single_key,
hint: 0,
}
}
#[inline(always)]
pub(super) fn resolve<'a>(&mut self, item: &'a DataValue<'a>) -> Option<&'a DataValue<'a>> {
if let (Some(key), DataValue::Object(pairs)) = (self.single_key, item) {
return crate::arena::value::object_lookup_field_hinted(pairs, key, &mut self.hint);
}
if self.segments.is_empty() {
Some(item)
} else {
crate::arena::value::traverse_segments(item, self.segments)
}
}
}
pub(super) enum FusedMapBody<'n> {
Extract {
segments: &'n [crate::node::PathSegment],
},
ArithVarLit {
op: OpCode,
segments: &'n [crate::node::PathSegment],
lit: &'n datavalue::OwnedDataValue,
var_is_lhs: bool,
},
ArithVarVar {
op: OpCode,
a_segments: &'n [crate::node::PathSegment],
b_segments: &'n [crate::node::PathSegment],
},
}
#[inline]
fn plain_var_segments(node: &CompiledNode) -> Option<&[crate::node::PathSegment]> {
if let CompiledNode::Var {
scope_level: 0,
segments,
reduce_hint: ReduceHint::None,
metadata_hint: MetadataHint::None,
default_value: None,
..
} = node
{
Some(segments.as_ref())
} else {
None
}
}
impl FusedMapBody<'_> {
pub(super) fn detect(body: &CompiledNode) -> Option<FusedMapBody<'_>> {
if let Some(segments) = plain_var_segments(body) {
return Some(FusedMapBody::Extract { segments });
}
let CompiledNode::BuiltinOperator { opcode, args, .. } = body else {
return None;
};
if args.len() != 2 || !matches!(opcode, OpCode::Add | OpCode::Subtract | OpCode::Multiply) {
return None;
}
let op = *opcode;
match (&args[0], &args[1]) {
(var, CompiledNode::Value { value, .. }) => {
let segments = plain_var_segments(var)?;
Some(FusedMapBody::ArithVarLit {
op,
segments,
lit: value,
var_is_lhs: true,
})
}
(CompiledNode::Value { value, .. }, var) => {
let segments = plain_var_segments(var)?;
Some(FusedMapBody::ArithVarLit {
op,
segments,
lit: value,
var_is_lhs: false,
})
}
(a, b) => {
let a_segments = plain_var_segments(a)?;
let b_segments = plain_var_segments(b)?;
Some(FusedMapBody::ArithVarVar {
op,
a_segments,
b_segments,
})
}
}
}
}