use anyhow::{Result, bail};
use reblessive::tree::Stk;
use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::expr::part::{RecurseInstruction, RecursionPlan};
use crate::expr::{FlowResultExt as _, Part};
use crate::val::{Array, Value};
#[derive(Clone, Copy, Debug)]
pub(crate) struct Recursion<'a> {
pub min: u32,
pub max: Option<u32>,
pub iterated: u32,
pub current: &'a Value,
pub path: &'a [Part],
pub plan: Option<&'a RecursionPlan>,
pub instruction: Option<&'a RecurseInstruction>,
}
impl<'a> Recursion<'a> {
pub fn with_iterated(self, iterated: u32) -> Self {
Self {
iterated,
..self
}
}
pub fn with_current(self, current: &'a Value) -> Self {
Self {
current,
..self
}
}
}
pub(crate) fn is_final(v: &Value) -> bool {
match v {
Value::None => true,
Value::Null => true,
Value::Array(v) => v.is_empty() || v.is_all_none_or_null(),
_ => false,
}
}
pub(crate) fn get_final(v: &Value) -> Value {
match v {
Value::Array(_) => Value::Array(Array(vec![])),
Value::Null => Value::Null,
_ => Value::None,
}
}
pub(crate) fn clean_iteration(v: Value) -> Value {
if let Value::Array(v) = v {
Value::from(v.0.into_iter().filter(|v| !is_final(v)).collect::<Vec<Value>>()).flatten()
} else {
v
}
}
pub(crate) async fn compute_idiom_recursion(
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
rec: Recursion<'_>,
) -> Result<Value> {
let limit = ctx.config.idiom_recursion_limit;
let marked_recursive = rec.plan.is_some();
if marked_recursive && is_final(rec.current) {
return Ok(get_final(rec.current));
}
let mut i = rec.iterated.to_owned();
let mut current = rec.current.to_owned();
let mut finished = vec![];
macro_rules! output {
() => {
if rec.instruction.is_some() {
Value::from(finished)
} else {
current
}
};
}
if marked_recursive {
if let Some(max) = rec.max {
if i >= max {
return Ok(current);
}
} else if i >= limit {
bail!(Error::IdiomRecursionLimitExceeded {
limit,
});
}
}
loop {
i += 1;
let v = match rec.instruction {
Some(instruction) => {
instruction
.compute(
stk,
ctx,
opt,
doc,
rec.with_iterated(i).with_current(¤t),
&mut finished,
)
.await?
}
_ => stk.run(|stk| current.get(stk, ctx, opt, doc, rec.path)).await.catch_return()?,
};
let v = match rec.plan {
Some(p) => p.compute(stk, ctx, opt, doc, rec.with_iterated(i).with_current(&v)).await?,
_ => v,
};
let v = if rec.instruction.is_none() {
clean_iteration(v)
} else {
v
};
match v {
v if is_final(&v) || v == current => {
let res: Value = match rec.instruction {
Some(_) if i < rec.min => Value::Array(Array::new()),
Some(_) => Value::from(finished),
None if i <= rec.min => get_final(&v),
None => output!(),
};
return Ok(res);
}
v => {
current = v;
}
};
if let Some(max) = rec.max {
if i >= max {
return Ok(output!());
}
} else if i >= limit {
bail!(Error::IdiomRecursionLimitExceeded {
limit,
});
}
if marked_recursive {
return Ok(current);
}
}
}