#![cfg_attr(tarpaulin, skip)]
use super::{FResult, FunctionError, Result};
use crate::ast::query::ARGS_CONST_ID;
use crate::ast::{Expr, Exprs, ImutExpr, ImutExprInt, ImutExprs, InvokeAggrFn};
use crate::interpreter::{AggrType, Cont, Env, ExecOpts, LocalStack};
use simd_json::prelude::*;
use simd_json::BorrowedValue as Value;
use std::borrow::Cow;
const RECUR_STR: &str = "recur";
pub(crate) const RECUR_PTR: Option<*const u8> = Some(RECUR_STR.as_ptr());
pub(crate) const RECUR: Value<'static> = Value::String(Cow::Borrowed(RECUR_STR));
#[derive(Debug, Clone, PartialEq, Serialize)]
pub(crate) struct CustomFn<'script> {
pub name: Cow<'script, str>,
pub body: Exprs<'script>,
pub args: Vec<String>,
pub open: bool,
pub locals: usize,
pub is_const: bool,
pub inline: bool,
}
impl<'script> CustomFn<'script> {
pub(crate) fn is_const(&self) -> bool {
self.is_const
}
pub(crate) fn can_inline(&self) -> bool {
if self.body.len() != 1 {
return false;
}
if self.inline {
return true;
}
let i = match self.body.get(0) {
Some(Expr::Imut(ImutExprInt::Invoke1(i)))
| Some(Expr::Imut(ImutExprInt::Invoke2(i)))
| Some(Expr::Imut(ImutExprInt::Invoke3(i)))
| Some(Expr::Imut(ImutExprInt::Invoke(i))) => i,
_ => return false,
};
let mut a_idx = 0;
for a in &i.args {
if let ImutExpr(ImutExprInt::Local { idx, .. }) = a {
if *idx != a_idx {
return false;
}
a_idx += 1;
} else {
return false;
}
}
true
}
pub(crate) fn inline(
&self,
args: ImutExprs<'script>,
mid: usize,
) -> Result<ImutExprInt<'script>> {
if self.body.len() != 1 {
return Err(format!("can't inline {}: too large body", self.name).into());
}
let i = match self.body.get(0) {
Some(Expr::Imut(ImutExprInt::Invoke1(i)))
| Some(Expr::Imut(ImutExprInt::Invoke2(i)))
| Some(Expr::Imut(ImutExprInt::Invoke3(i)))
| Some(Expr::Imut(ImutExprInt::Invoke(i))) => i,
Some(e) => {
return Err(format!("can't inline {}: bad expression: {:?}", self.name, e).into())
}
None => return Err(format!("can't inline {}: no body", self.name).into()),
};
if i.args.len() != self.args.len() {
return Err(format!("can't inline {}: different argc", self.name).into());
}
let mut i = i.clone();
i.mid = mid;
i.args = args;
Ok(match i.args.len() {
1 => ImutExprInt::Invoke1(i),
2 => ImutExprInt::Invoke2(i),
3 => ImutExprInt::Invoke3(i),
_ => ImutExprInt::Invoke(i),
})
}
#[allow(mutable_transmutes, clippy::transmute_ptr_to_ptr)]
pub(crate) fn invoke<'event, 'run>(
&'script self,
env: &'run Env<'run, 'event, 'script>,
args: &'run [&'run Value<'event>],
) -> FResult<Value<'event>>
where
'script: 'event,
'event: 'run,
{
use std::mem;
const NO_AGGRS: [InvokeAggrFn<'static>; 0] = [];
let mut args_const = Value::from(
args.iter()
.skip(self.locals)
.map(|v| v.clone_static())
.collect::<Vec<Value<'static>>>(),
);
let consts: &'run mut [Value<'event>] = unsafe { mem::transmute(env.consts) };
mem::swap(&mut consts[ARGS_CONST_ID], &mut args_const);
let mut this_local = LocalStack::with_size(self.locals);
for (i, arg) in args.iter().enumerate() {
if i == self.locals {
break;
}
this_local.values[i] = Some((*arg).clone_static());
}
let opts = ExecOpts {
result_needed: false,
aggr: AggrType::Tick,
};
let env = Env {
context: env.context,
consts: env.consts,
aggrs: &NO_AGGRS,
meta: env.meta,
recursion_limit: env.recursion_limit,
};
let mut recursion_depth = 0;
'recur: loop {
let mut exprs = self.body.iter().peekable();
let mut no_event = Value::null();
let mut no_meta = Value::null();
let mut state = Value::null().into_static();
while let Some(expr) = exprs.next() {
if exprs.peek().is_none() {
let r = expr.run(
opts.with_result(),
&env,
&mut no_event,
&mut state,
&mut no_meta,
&mut this_local,
);
if r.is_err() {
mem::swap(&mut consts[ARGS_CONST_ID], &mut args_const);
};
match r? {
Cont::Cont(v) => {
return Ok(v.into_owned());
}
Cont::Drop => {
recursion_depth += 1;
if recursion_depth == env.recursion_limit {
mem::swap(&mut consts[ARGS_CONST_ID], &mut args_const);
return Err(FunctionError::Error(Box::new(
"recursion limit reached".into(),
)));
}
for local in this_local.values.iter_mut().skip(args.len()) {
*local = None;
}
continue 'recur;
}
_ => {
mem::swap(&mut consts[ARGS_CONST_ID], &mut args_const);
return Err(FunctionError::Error(Box::new("can't emit here".into())));
}
};
} else {
let r = expr.run(
opts,
&env,
&mut no_event,
&mut state,
&mut no_meta,
&mut this_local,
);
if r.is_err() {
mem::swap(&mut consts[ARGS_CONST_ID], &mut args_const);
};
r?;
}
}
}
}
}