use runmat_builtins::Value;
#[cfg(test)]
use once_cell::sync::Lazy;
#[cfg(test)]
use std::sync::Mutex;
type AssignFn = fn(&str, Value) -> Result<(), String>;
type ClearFn = fn() -> Result<(), String>;
type RemoveFn = fn(&str) -> Result<(), String>;
pub struct WorkspaceResolver {
pub lookup: fn(&str) -> Option<Value>,
pub snapshot: fn() -> Vec<(String, Value)>,
pub globals: fn() -> Vec<String>,
pub assign: Option<AssignFn>,
pub clear: Option<ClearFn>,
pub remove: Option<RemoveFn>,
}
mod resolver_storage {
use super::WorkspaceResolver;
pub(super) fn set(resolver: WorkspaceResolver) {
imp::set(resolver)
}
pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
imp::with(f)
}
#[cfg(test)]
mod imp {
use super::WorkspaceResolver;
use std::cell::RefCell;
thread_local! {
static RESOLVER: RefCell<Option<WorkspaceResolver>> = const { RefCell::new(None) };
}
pub(super) fn set(resolver: WorkspaceResolver) {
RESOLVER.with(|slot| {
*slot.borrow_mut() = Some(resolver);
});
}
pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
RESOLVER.with(|slot| {
let guard = slot.borrow();
f(guard.as_ref())
})
}
}
#[cfg(not(test))]
mod imp {
use super::WorkspaceResolver;
use once_cell::sync::Lazy;
use std::sync::RwLock;
static RESOLVER: Lazy<RwLock<Option<WorkspaceResolver>>> = Lazy::new(|| RwLock::new(None));
pub(super) fn set(resolver: WorkspaceResolver) {
let mut guard = RESOLVER
.write()
.unwrap_or_else(|poison| poison.into_inner());
*guard = Some(resolver);
}
pub(super) fn with<R>(f: impl FnOnce(Option<&WorkspaceResolver>) -> R) -> R {
let guard = RESOLVER.read().unwrap_or_else(|poison| poison.into_inner());
f(guard.as_ref())
}
}
}
#[cfg(test)]
static TEST_WORKSPACE_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
pub fn register_workspace_resolver(resolver: WorkspaceResolver) {
resolver_storage::set(resolver);
}
pub fn lookup(name: &str) -> Option<Value> {
resolver_storage::with(|resolver| resolver.and_then(|r| (r.lookup)(name)))
}
pub fn snapshot() -> Option<Vec<(String, Value)>> {
resolver_storage::with(|resolver| resolver.map(|r| (r.snapshot)()))
}
pub fn global_names() -> Vec<String> {
resolver_storage::with(|resolver| resolver.map(|r| (r.globals)()).unwrap_or_default())
}
pub fn assign(name: &str, value: Value) -> Result<(), String> {
resolver_storage::with(|resolver| {
let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
let assign = resolver
.assign
.ok_or_else(|| "workspace assignment unavailable".to_string())?;
(assign)(name, value)
})
}
pub fn clear() -> Result<(), String> {
resolver_storage::with(|resolver| {
let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
let clear = resolver
.clear
.ok_or_else(|| "workspace clearing unavailable".to_string())?;
(clear)()
})
}
pub fn remove(name: &str) -> Result<(), String> {
resolver_storage::with(|resolver| {
let resolver = resolver.ok_or_else(|| "workspace state unavailable".to_string())?;
let remove = resolver
.remove
.ok_or_else(|| "workspace removal unavailable".to_string())?;
(remove)(name)
})
}
pub fn is_available() -> bool {
resolver_storage::with(|resolver| resolver.is_some())
}
#[cfg(test)]
pub(crate) fn test_guard() -> std::sync::MutexGuard<'static, ()> {
TEST_WORKSPACE_LOCK
.lock()
.unwrap_or_else(|poison| poison.into_inner())
}