use std::cell::RefCell;
use std::sync::Arc;
use cljrs_value::{Value, ValueError, ValueResult};
use crate::apply::apply_value;
use crate::env::{Env, GlobalEnv};
struct EvalContext {
globals: Arc<GlobalEnv>,
current_ns: Arc<str>,
}
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(),
});
});
}
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,
});
});
}
pub fn with_eval_context<F, R>(f: F) -> Result<R, crate::error::EvalError>
where
F: FnOnce(&mut Env) -> Result<R, crate::error::EvalError>,
{
let (globals, ns) = EVAL_CONTEXT.with(|stack| {
let s = stack.borrow();
let ec = s.last().ok_or_else(|| {
crate::error::EvalError::Runtime(
"with_eval_context called outside eval context".to_string(),
)
})?;
Ok::<_, crate::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);
apply_value(f, args, &mut env).map_err(|e| match e {
crate::error::EvalError::Thrown(v) => ValueError::Thrown(v),
other => ValueError::Other(format!("{other}")),
})
}