use std::cell::RefCell;
use std::sync::Arc;
use cljrs_value::{Value, ValueError, ValueResult};
use crate::env::env::{Env, GlobalEnv};
struct EvalContext {
globals: Arc<GlobalEnv>,
current_ns: Arc<str>,
is_async: bool,
}
thread_local! {
static EVAL_CONTEXT: RefCell<Vec<EvalContext>> = const { RefCell::new(Vec::new()) };
}
pub fn push_eval_context(env: &Env) {
EVAL_CONTEXT.with(|stack| {
stack.borrow_mut().push(EvalContext {
globals: env.globals.clone(),
current_ns: env.current_ns.clone(),
is_async: env.is_async,
});
});
}
pub fn current_is_async() -> bool {
EVAL_CONTEXT.with(|stack| stack.borrow().last().is_some_and(|ec| ec.is_async))
}
pub fn pop_eval_context() {
EVAL_CONTEXT.with(|stack| {
stack.borrow_mut().pop();
});
}
pub fn capture_eval_context() -> Option<(Arc<GlobalEnv>, Arc<str>)> {
EVAL_CONTEXT.with(|stack| {
let s = stack.borrow();
let ec = s.last()?;
Some((ec.globals.clone(), ec.current_ns.clone()))
})
}
pub fn install_eval_context(globals: Arc<GlobalEnv>, ns: Arc<str>) {
EVAL_CONTEXT.with(|stack| {
stack.borrow_mut().push(EvalContext {
globals,
current_ns: ns,
is_async: false,
});
});
}
pub struct EvalContextGuard {
_priv: (),
}
impl Drop for EvalContextGuard {
fn drop(&mut self) {
pop_eval_context();
}
}
pub fn install_eval_context_guard(globals: Arc<GlobalEnv>, ns: Arc<str>) -> EvalContextGuard {
install_eval_context(globals, ns);
EvalContextGuard { _priv: () }
}
pub fn with_eval_context<F, R>(f: F) -> Result<R, crate::env::error::EvalError>
where
F: FnOnce(&mut Env) -> Result<R, crate::env::error::EvalError>,
{
let (globals, ns) = EVAL_CONTEXT.with(|stack| {
let s = stack.borrow();
let ec = s.last().ok_or_else(|| {
crate::env::error::EvalError::Runtime(
"with_eval_context called outside eval context".to_string(),
)
})?;
Ok::<_, crate::env::error::EvalError>((ec.globals.clone(), ec.current_ns.clone()))
})?;
let mut env = Env::new(globals, &ns);
f(&mut env)
}
pub fn invoke(f: &Value, args: Vec<Value>) -> ValueResult<Value> {
let (globals, ns) = EVAL_CONTEXT.with(|stack| {
let s = stack.borrow();
let ec = s
.last()
.ok_or_else(|| ValueError::Other("invoke called outside eval context".into()))?;
Ok((ec.globals.clone(), ec.current_ns.clone()))
})?;
let mut env = Env::new(globals, &ns);
let f = f.unwrap_meta();
let result = if let Value::Fn(cljx_fn) = f {
if let Some(fut) = crate::env::apply::dispatch_if_async(f, &args, &env) {
Ok(fut)
} else {
env.call_cljrs_fn(cljx_fn.get(), &args)
}
} else {
crate::env::apply::apply_value(f, args, &mut env)
};
result.map_err(|e| match e {
crate::env::error::EvalError::Thrown(v) => ValueError::Thrown(v),
crate::env::error::EvalError::GasExhausted => ValueError::GasExhausted,
other => ValueError::Other(format!("{other}")),
})
}