use crate::{Engine, Result};
use super::eager::Cx;
use super::extract::{Any, RestArgs};
use crate::arena::{ContextStack, DataValue};
use bumpalo::Bump;
#[inline(always)]
fn lookup_data<'a>(ctx: &ContextStack<'a>) -> &'a DataValue<'a> {
ctx.current().data()
}
#[inline]
pub(crate) fn missing<'a>(
cx: &mut Cx<'_, 'a>,
paths: RestArgs<'a, Any>,
) -> Result<&'a DataValue<'a>> {
let lookup = lookup_data(cx.ctx);
let mut missing: Option<bumpalo::collections::Vec<'a, DataValue<'a>>> = None;
for i in 0..paths.len() {
let av = paths.get(i, cx)?;
accumulate_dynamic_missing(av, lookup, &mut missing, paths.len(), cx.ctx, cx.arena)?;
}
match missing {
Some(v) if !v.is_empty() => Ok(cx.alloc(DataValue::Array(v.into_bump_slice()))),
_ => Ok(crate::arena::singletons::singleton_empty_array()),
}
}
#[inline]
pub(crate) fn missing_some<'a>(
cx: &mut Cx<'_, 'a>,
min_av: &'a DataValue<'a>,
paths_av: &'a DataValue<'a>,
) -> Result<&'a DataValue<'a>> {
let (ctx, arena) = (&mut *cx.ctx, cx.arena);
let min_present = min_present(min_av);
let lookup = lookup_data(ctx);
eval_missing_some_dynamic(paths_av, lookup, min_present, ctx, arena)
}
#[inline]
fn eval_missing_some_dynamic<'a>(
paths_av: &'a DataValue<'a>,
lookup: &'a DataValue<'a>,
min_present: usize,
ctx: &mut ContextStack<'a>,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let mut missing: Option<bumpalo::collections::Vec<'a, DataValue<'a>>> = None;
let mut present_count: usize = 0;
let short_circuit = match paths_av {
DataValue::Array(items) => {
ctx.charge(items.len() as u64)?;
let cap = items.len();
items.iter().any(|it| {
it.as_str().is_some_and(|p| {
check_path(
p,
lookup,
&mut missing,
cap,
&mut present_count,
min_present,
arena,
)
})
})
}
_ => false,
};
if short_circuit || present_count >= min_present {
return Ok(crate::arena::singletons::singleton_empty_array());
}
match missing {
Some(v) if !v.is_empty() => Ok(arena.alloc(DataValue::Array(v.into_bump_slice()))),
_ => Ok(crate::arena::singletons::singleton_empty_array()),
}
}
use crate::node::{
CompiledMissingArg, CompiledMissingData, CompiledMissingMin, CompiledMissingPaths,
CompiledMissingSomeData,
};
#[inline]
pub(crate) fn evaluate_compiled_missing<'a>(
data: &'a CompiledMissingData,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let lookup = lookup_data(ctx);
let mut missing: Option<bumpalo::collections::Vec<'a, DataValue<'a>>> = None;
for arg in data.args.iter() {
match arg {
CompiledMissingArg::Now((path, segments)) => {
if !crate::arena::value::path_exists_segments(lookup, segments) {
missing
.get_or_insert_with(|| {
bumpalo::collections::Vec::with_capacity_in(data.args.len(), arena)
})
.push(DataValue::String(path.as_ref()));
}
}
CompiledMissingArg::Later(node) => {
let av = engine.dispatch_node(node, ctx, arena)?;
accumulate_dynamic_missing(av, lookup, &mut missing, data.args.len(), ctx, arena)?;
}
}
}
match missing {
Some(v) if !v.is_empty() => Ok(arena.alloc(DataValue::Array(v.into_bump_slice()))),
_ => Ok(crate::arena::singletons::singleton_empty_array()),
}
}
#[inline]
pub(crate) fn evaluate_compiled_missing_some<'a>(
data: &'a CompiledMissingSomeData,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let min_present = match &data.min_present {
CompiledMissingMin::Now(n) => *n,
CompiledMissingMin::Later(node) => {
let av = engine.dispatch_node(node, ctx, arena)?;
min_present(av)
}
};
let lookup = lookup_data(ctx);
match &data.paths {
CompiledMissingPaths::Now(paths) => {
let mut missing: Option<bumpalo::collections::Vec<'a, DataValue<'a>>> = None;
let mut present = 0usize;
for (path, segments) in paths.iter() {
if crate::arena::value::path_exists_segments(lookup, segments) {
present += 1;
if present >= min_present {
return Ok(crate::arena::singletons::singleton_empty_array());
}
} else {
missing
.get_or_insert_with(|| {
bumpalo::collections::Vec::with_capacity_in(paths.len(), arena)
})
.push(DataValue::String(path.as_ref()));
}
}
match missing {
Some(v) if present < min_present && !v.is_empty() => {
Ok(arena.alloc(DataValue::Array(v.into_bump_slice())))
}
_ => Ok(crate::arena::singletons::singleton_empty_array()),
}
}
CompiledMissingPaths::Later(node) => {
let paths_av = engine.dispatch_node(node, ctx, arena)?;
eval_missing_some_dynamic(paths_av, lookup, min_present, ctx, arena)
}
}
}
#[inline]
pub(crate) fn min_present(av: &DataValue<'_>) -> usize {
match av {
DataValue::Number(n) => match n.as_i64() {
Some(i) => usize::try_from(i.max(0)).unwrap_or(usize::MAX),
None => n.as_f64().ceil().max(0.0) as usize,
},
_ => 1,
}
}
#[inline]
fn check_path<'a>(
path: &'a str,
lookup: &'a DataValue<'a>,
missing: &mut Option<bumpalo::collections::Vec<'a, DataValue<'a>>>,
cap: usize,
present: &mut usize,
min_present: usize,
arena: &'a Bump,
) -> bool {
if !crate::arena::value::path_exists_str(lookup, path) {
missing
.get_or_insert_with(|| bumpalo::collections::Vec::with_capacity_in(cap, arena))
.push(DataValue::String(path));
} else {
*present += 1;
if *present >= min_present {
return true;
}
}
false
}
#[inline]
fn accumulate_dynamic_missing<'a>(
av: &'a DataValue<'a>,
lookup: &'a DataValue<'a>,
missing: &mut Option<bumpalo::collections::Vec<'a, DataValue<'a>>>,
cap: usize,
ctx: &mut ContextStack<'a>,
arena: &'a Bump,
) -> Result<()> {
match av {
DataValue::Array(items) => {
ctx.charge(items.len() as u64)?;
for it in *items {
if let Some(path) = it.as_str()
&& !crate::arena::value::path_exists_str(lookup, path)
{
missing
.get_or_insert_with(|| {
bumpalo::collections::Vec::with_capacity_in(cap.max(items.len()), arena)
})
.push(DataValue::String(path));
}
}
}
DataValue::String(s) if !crate::arena::value::path_exists_str(lookup, s) => {
missing
.get_or_insert_with(|| bumpalo::collections::Vec::with_capacity_in(cap, arena))
.push(DataValue::String(s));
}
_ => {}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::min_present;
use crate::arena::DataValue;
#[test]
fn a_minimum_past_usize_saturates() {
let big = DataValue::from_i64(i64::MAX);
assert_eq!(
min_present(&big),
usize::try_from(i64::MAX).unwrap_or(usize::MAX)
);
assert!(min_present(&DataValue::from_i64((1 << 32) + 1)) > 1);
assert_eq!(min_present(&DataValue::from_i64(-3)), 0);
assert_eq!(min_present(&DataValue::from_f64(2.5)), 3);
}
}