#[doc(no_inline)]
use std::{any::Any, fmt::Display, mem::MaybeUninit, result, thread};
pub use anyhow::{self, Error};
use thiserror::Error;
use emacs_module::*;
use crate::{
Env, Value, IntoLisp,
GlobalRef,
symbol::{self, IntoLispSymbol},
call::IntoLispArgs,
};
pub(crate) const RETURN: emacs_funcall_exit = emacs_funcall_exit_return;
pub(crate) const SIGNAL: emacs_funcall_exit = emacs_funcall_exit_signal;
pub(crate) const THROW: emacs_funcall_exit = emacs_funcall_exit_throw;
#[derive(Debug)]
pub struct TempValue {
raw: emacs_value,
}
#[macro_export]
macro_rules! define_errors {
($( $name:ident $message:literal $( ( $( $parent:ident )+ ) )? )*) => {
$crate::global_refs! {__emrs_init_global_refs_to_error_symbols__(init_to_symbol) =>
$( $name )*
}
#[$crate::deps::ctor::ctor]
fn __emrs_define_errors__() {
$crate::init::__CUSTOM_ERRORS__.try_lock()
.expect("Failed to acquire a write lock on the list of initializers for custom error signals")
.push(::std::boxed::Box::new(|env| {
$(
env.define_error($name, $message, [
$(
$(
env.intern($crate::deps::emacs_macros::lisp_name!($parent))?
),+
)?
])?;
)*
Ok(())
}));
}
}
}
#[derive(Debug, Error)]
pub enum ErrorKind {
#[error("Non-local signal: symbol={symbol:?} data={data:?}")]
Signal { symbol: TempValue, data: TempValue },
#[error("Non-local throw: tag={tag:?} value={value:?}")]
Throw { tag: TempValue, value: TempValue },
#[error("expected: {expected}")]
WrongTypeUserPtr { expected: &'static str },
}
pub type Result<T> = result::Result<T, Error>;
impl TempValue {
unsafe fn new(raw: emacs_value) -> Self {
Self { raw }
}
pub unsafe fn value<'e>(&self, env: &'e Env) -> Value<'e> {
Value::new(self.raw, env).protect()
}
}
unsafe impl Send for TempValue {}
unsafe impl Sync for TempValue {}
impl Env {
#[inline]
pub(crate) fn handle_exit<T>(&self, result: T) -> Result<T> {
let mut symbol = MaybeUninit::uninit();
let mut data = MaybeUninit::uninit();
let status = self.non_local_exit_get(&mut symbol, &mut data);
match (status, symbol, data) {
(RETURN, ..) => Ok(result),
(SIGNAL, symbol, data) => {
self.non_local_exit_clear();
Err(ErrorKind::Signal {
symbol: unsafe { TempValue::new(symbol.assume_init()) },
data: unsafe { TempValue::new(data.assume_init()) },
}.into())
}
(THROW, tag, value) => {
self.non_local_exit_clear();
Err(ErrorKind::Throw {
tag: unsafe { TempValue::new(tag.assume_init()) },
value: unsafe { TempValue::new(value.assume_init()) },
}.into())
}
_ => panic!("Unexpected non local exit status {}", status),
}
}
#[inline]
pub(crate) unsafe fn maybe_exit(&self, result: Result<Value<'_>>) -> emacs_value {
match result {
Ok(v) => v.raw,
Err(error) => match error.downcast_ref::<ErrorKind>() {
Some(err) => self.handle_known(err),
_ => self
.signal_internal(symbol::rust_error, &format!("{}", error))
.unwrap_or_else(|_| panic!("Failed to signal {}", error)),
},
}
}
#[inline]
pub(crate) fn handle_panic(&self, result: thread::Result<emacs_value>) -> emacs_value {
match result {
Ok(v) => v,
Err(error) => {
let mut m: result::Result<String, Box<dyn Any>> = Err(error);
if let Err(error) = m {
m = error.downcast::<String>().map(|v| *v);
}
if let Err(error) = m {
m = match error.downcast::<ErrorKind>() {
Ok(err) => unsafe { return self.handle_known(&*err); },
Err(error) => Err(error),
}
}
if let Err(error) = m {
m = Ok(format!("{:#?}", error));
}
self.signal_internal(symbol::rust_panic, &m.expect("Logic error")).expect("Fail to signal panic")
}
}
}
pub(crate) fn define_core_errors(&self) -> Result<()> {
self.define_error(symbol::rust_panic, "Rust panic", (symbol::error, ))?;
self.define_error(symbol::rust_error, "Rust error", (symbol::error, ))?;
self.define_error(
symbol::rust_wrong_type_user_ptr,
"Wrong type user-ptr",
(symbol::rust_error, self.intern("wrong-type-argument")?),
)?;
Ok(())
}
unsafe fn handle_known(&self, err: &ErrorKind) -> emacs_value {
match err {
ErrorKind::Signal { symbol, data } => self.non_local_exit_signal(symbol.raw, data.raw),
ErrorKind::Throw { tag, value } => self.non_local_exit_throw(tag.raw, value.raw),
ErrorKind::WrongTypeUserPtr { .. } => self
.signal_internal(symbol::rust_wrong_type_user_ptr, &format!("{}", err))
.unwrap_or_else(|_| panic!("Failed to signal {}", err)),
}
}
fn signal_internal(&self, symbol: &GlobalRef, message: &str) -> Result<emacs_value> {
let message = message.into_lisp(&self)?;
let data = self.list([message])?;
unsafe { Ok(self.non_local_exit_signal(symbol.bind(self).raw, data.raw)) }
}
pub fn define_error<'e, N, P>(&'e self, name: N, message: &str, parents: P) -> Result<Value<'e>>
where N: IntoLispSymbol<'e>, P: IntoLispArgs<'e> {
self.call("define-error", (name.into_lisp_symbol(self)?, message, self.list(parents)?))
}
pub fn signal<'e, S, D, T>(&'e self, symbol: S, data: D) -> Result<T> where S: IntoLispSymbol<'e>, D: IntoLispArgs<'e> {
let symbol = TempValue { raw: symbol.into_lisp_symbol(self)?.raw };
let data = TempValue { raw: self.list(data)?.raw };
Err(ErrorKind::Signal { symbol, data }.into())
}
pub(crate) fn non_local_exit_get(
&self,
symbol: &mut MaybeUninit<emacs_value>,
data: &mut MaybeUninit<emacs_value>,
) -> emacs_funcall_exit {
unsafe_raw_call_no_exit!(self, non_local_exit_get, symbol.as_mut_ptr(), data.as_mut_ptr())
}
pub(crate) fn non_local_exit_clear(&self) {
unsafe_raw_call_no_exit!(self, non_local_exit_clear)
}
#[allow(unused_unsafe)]
pub(crate) unsafe fn non_local_exit_throw(&self, tag: emacs_value, value: emacs_value) -> emacs_value {
unsafe_raw_call_no_exit!(self, non_local_exit_throw, tag, value);
tag
}
#[allow(unused_unsafe)]
pub(crate) unsafe fn non_local_exit_signal(&self, symbol: emacs_value, data: emacs_value) -> emacs_value {
unsafe_raw_call_no_exit!(self, non_local_exit_signal, symbol, data);
symbol
}
}
pub trait ResultExt<T, E> {
fn or_signal<'e, S>(self, env: &'e Env, symbol: S) -> Result<T> where S: IntoLispSymbol<'e>;
}
impl<T, E: Display> ResultExt<T, E> for result::Result<T, E> {
fn or_signal<'e, S>(self, env: &'e Env, symbol: S) -> Result<T> where S: IntoLispSymbol<'e> {
self.or_else(|err| env.signal(symbol, (
format!("{}", err),
)))
}
}