use std::borrow::Cow;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Kind {
Length,
Rank,
Index,
Limit,
Domain,
Overflow,
Interrupt,
Timeout,
}
macro_rules! reporters {
($($kind:ident: $message:literal),+ $(,)?) => {
impl Kind {
pub fn reporter(self) -> extern "C" fn() {
match self { $(Self::$kind => {
extern "C" fn report() { record(Error::new(Kind::$kind, $message)); }
report
}),+ }
}
}
};
}
reporters! {
Length: "lengths do not agree", Rank: "argument has the wrong rank", Index: "read or write position is outside its source",
Limit: "array limit exceeded", Domain: "argument is outside the function's domain", Overflow: "integer arithmetic overflowed",
Interrupt: "computation interrupted", Timeout: "computation timed out",
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error { pub kind: Kind, pub message: Cow<'static, str> }
impl Error { pub fn new(kind: Kind, message: impl Into<Cow<'static, str>>) -> Self { Self { kind, message: message.into() } } }
impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.message) } }
impl std::error::Error for Error {}
#[derive(Clone, Default)]
pub struct ErrorSlot(std::sync::Arc<Errors>);
#[derive(Default)]
struct Errors { count: std::sync::atomic::AtomicU64, first: std::sync::Mutex<Option<Error>> }
thread_local! { static ACTIVE: std::cell::RefCell<Option<ErrorSlot>> = const { std::cell::RefCell::new(None) }; }
struct Restore(Option<ErrorSlot>);
impl Drop for Restore { fn drop(&mut self) { ACTIVE.with(|active| active.replace(self.0.take())); } }
impl ErrorSlot {
pub fn new() -> Self { Self::default() }
pub fn count(&self) -> u64 { self.0.count.load(std::sync::atomic::Ordering::Relaxed) }
pub fn pending(&self) -> bool { self.0.first.lock().unwrap().is_some() }
pub fn take(&self) -> Option<Error> { self.0.first.lock().unwrap().take() }
pub fn current() -> Option<Self> { ACTIVE.with(|active| active.borrow().clone()) }
pub fn within<R>(&self, run: impl FnOnce() -> R) -> R {
let (mut run, mut result) = (Some(run), None);
self.enter(&mut || result = run.take().map(|run| run()));
result.expect("the run ran")
}
fn enter(&self, run: &mut dyn FnMut()) { let _restore = Restore(ACTIVE.with(|active| active.replace(Some(self.clone())))); run() }
}
pub fn record(error: Error) {
ACTIVE.with(|active| {
if let Some(slot) = active.borrow().as_ref() {
slot.0.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut first = slot.0.first.lock().unwrap();
if first.is_none() { *first = Some(error); }
}
});
}
pub fn checked<R>(run: impl FnOnce() -> Result<R, Error>) -> Result<R, Error> {
let (mut run, mut result) = (Some(run), None);
let recorded = recorded(&mut || result = run.take().map(|run| run()));
let result = result.expect("the run ran")?;
recorded.map_or(Ok(result), Err)
}
fn recorded(run: &mut dyn FnMut()) -> Option<Error> {
if ACTIVE.with(|active| active.borrow().is_some()) { run(); return None; }
thread_local! { static SPARE: ErrorSlot = ErrorSlot::new(); }
SPARE.with(|slot| {
slot.take();
slot.enter(run);
slot.take()
})
}