use std::{cell::RefCell, fmt::Debug};
thread_local! {
pub static ERROR_STACK: RefCell<Option<*const dyn Entry>> = RefCell::new(None);
}
pub trait Entry {
fn write(&self, writer: &mut dyn std::fmt::Write);
}
pub struct DataScope<Data> {
previous: Option<*const dyn Entry>,
module_path: &'static str,
file: &'static str,
line: u32,
message: &'static str,
data: Data,
}
impl<Data: Debug> Entry for DataScope<Data> {
fn write(&self, writer: &mut dyn std::fmt::Write) {
write!(
writer,
" {} {}:{}: {} {:?}\n",
self.module_path, self.file, self.line, self.message, self.data
)
.ok();
unsafe {
if let Some(previous) = self.previous.as_ref().and_then(|p| p.as_ref()) {
previous.write(writer);
}
}
}
}
impl<Data: Debug> DataScope<Data> {
pub fn new(module_path: &'static str, file: &'static str, line: u32, message: &'static str, data: Data) -> Self {
let previous = ERROR_STACK.with(|stack| stack.borrow().clone());
DataScope {
previous,
module_path,
file,
line,
message,
data,
}
}
}
impl<Data> Drop for DataScope<Data> {
fn drop(&mut self) {
ERROR_STACK.with(|stack| *stack.borrow_mut() = self.previous);
}
}
pub struct EmptyDebug {}
impl std::fmt::Debug for EmptyDebug {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
pub fn print_econtext() {
let context = econtext_string();
if !context.is_empty() {
eprintln!("ERROR CONTEXT:");
eprintln!("{}", context);
}
}
pub fn econtext_string() -> String {
ERROR_STACK.with(|value| unsafe {
if let Some(entry) = value.borrow().as_ref().and_then(|p| p.as_ref()) {
let mut output = String::new();
entry.write(&mut output);
output
} else {
Default::default()
}
})
}
pub fn add_panic_hook() {
let previous_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info: &std::panic::PanicInfo| {
print_econtext();
previous_hook(panic_info);
}));
}
pub fn type_name_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
#[macro_export]
macro_rules! current_function_name {
() => {{
fn f() {}
let name = $crate::type_name_of(f);
&name.get(..name.len() - 3).unwrap()
}};
}
#[macro_export]
macro_rules! econtext {
($message:expr) => {
let _scope = $crate::DataScope::new(module_path!(), file!(), line!(), $message, $crate::EmptyDebug {});
$crate::ERROR_STACK.with(|stack| *stack.borrow_mut() = Some(&_scope));
};
}
#[macro_export]
macro_rules! econtext_data {
($message:expr, $data:expr) => {
let _scope = $crate::DataScope::new(module_path!(), file!(), line!(), $message, $data);
$crate::ERROR_STACK.with(|stack| *stack.borrow_mut() = Some(&_scope));
};
}
#[macro_export]
macro_rules! econtext_function {
() => {
let _scope = $crate::DataScope::new(
module_path!(),
file!(),
line!(),
$crate::current_function_name!(),
$crate::EmptyDebug {},
);
$crate::ERROR_STACK.with(|stack| *stack.borrow_mut() = Some(&_scope));
};
}
#[macro_export]
macro_rules! econtext_function_data {
($data:expr) => {
let _scope = $crate::DataScope::new(
module_path!(),
file!(),
line!(),
$crate::current_function_name!(),
$data,
);
$crate::ERROR_STACK.with(|stack| *stack.borrow_mut() = Some(&_scope));
};
}