use std::{any::Any, borrow::Cow, ffi::CString, fmt, mem::transmute, ops::Deref, os::raw::c_int};
use rb_sys::{
rb_bug, rb_ensure, rb_errinfo, rb_exc_raise, rb_iter_break, rb_iter_break_value, rb_jump_tag,
rb_protect, rb_set_errinfo, rb_warning, ruby_special_consts, VALUE,
};
use crate::{
class::Class,
exception::{self, Exception, ExceptionClass},
module::Module,
r_string::RString,
value::{ReprValue, Value, QNIL},
};
#[derive(Debug)]
pub enum Error {
Jump(Tag),
Error(ExceptionClass, Cow<'static, str>),
Exception(Exception),
}
impl Error {
pub fn new<T>(class: ExceptionClass, msg: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Error(class, msg.into())
}
pub fn runtime_error<T>(msg: T) -> Self
where
T: Into<Cow<'static, str>>,
{
Self::Error(exception::runtime_error(), msg.into())
}
pub fn iter_break<T>(val: Option<T>) -> Self
where
T: Into<Value>,
{
match val {
Some(val) => {
let val = val.into();
protect(|| {
unsafe { rb_iter_break_value(val.as_rb_value()) };
QNIL
})
.unwrap_err()
}
None => protect(|| {
unsafe { rb_iter_break() };
QNIL
})
.unwrap_err(),
}
}
pub fn is_kind_of<T>(&self, class: T) -> bool
where
T: Deref<Target = Value> + Module,
{
match self {
Error::Jump(_) => false,
Error::Error(c, _) => c.is_inherited(class),
Error::Exception(e) => e.is_kind_of(class),
}
}
fn exception(self) -> Exception {
match self {
Error::Jump(_) => panic!("Error::exception() called on {}", self),
Error::Error(class, msg) => match class.new_instance((RString::new(msg.as_ref()),)) {
Ok(e) | Err(Error::Exception(e)) => e,
Err(err) => unreachable!("*very* unexpected error: {}", err),
},
Error::Exception(e) => e,
}
}
pub(crate) fn from_panic(e: Box<dyn Any + Send + 'static>) -> Self {
let msg = if let Some(&m) = e.downcast_ref::<&'static str>() {
m.into()
} else if let Some(m) = e.downcast_ref::<String>() {
m.clone().into()
} else {
"panic".into()
};
Self::Error(exception::fatal(), msg)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Jump(s) => s.fmt(f),
Error::Error(e, m) => write!(f, "{}: {}", e, m),
Error::Exception(e) => e.fmt(f),
}
}
}
impl From<Tag> for Error {
fn from(val: Tag) -> Self {
Self::Jump(val)
}
}
impl From<Exception> for Error {
fn from(val: Exception) -> Self {
Self::Exception(val)
}
}
#[derive(Debug)]
#[repr(i32)]
pub enum Tag {
Return = 1,
Break = 2,
Next = 3,
Retry = 4,
Redo = 5,
Raise = 6,
Throw = 7,
Fatal = 8,
}
impl Tag {
fn resume(self) -> ! {
unsafe { rb_jump_tag(self as c_int) };
unreachable!()
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Return => write!(f, "Return"),
Self::Break => write!(f, "Break"),
Self::Next => write!(f, "Next"),
Self::Retry => write!(f, "Retry"),
Self::Redo => write!(f, "Redo"),
Self::Raise => write!(f, "Raise"),
Self::Throw => write!(f, "Throw"),
Self::Fatal => write!(f, "Fatal"),
}
}
}
pub(crate) fn protect<F, T>(func: F) -> Result<T, Error>
where
F: FnOnce() -> T,
T: ReprValue,
{
unsafe extern "C" fn call<F, T>(arg: VALUE) -> VALUE
where
F: FnOnce() -> T,
T: ReprValue,
{
let closure = (&mut *(arg as *mut Option<F>)).take().unwrap();
(closure)().to_value().as_rb_value()
}
let mut state = 0;
let result = unsafe {
let mut some_func = Some(func);
let closure = &mut some_func as *mut Option<F> as VALUE;
rb_protect(Some(call::<F, T>), closure, &mut state as *mut c_int)
};
match state {
0 => unsafe { Ok(T::from_value_unchecked(Value::new(result))) },
6 => unsafe {
let ex = Exception::from_rb_value_unchecked(rb_errinfo());
rb_set_errinfo(QNIL.as_rb_value());
Err(Error::Exception(ex))
},
other => Err(Error::Jump(unsafe { transmute(other) })),
}
}
pub(crate) fn ensure<F1, F2>(func: F1, ensure: F2) -> Value
where
F1: FnOnce() -> Value,
F2: FnOnce(),
{
unsafe extern "C" fn call_func<F1>(arg: VALUE) -> VALUE
where
F1: FnOnce() -> Value,
{
let closure = (&mut *(arg as *mut Option<F1>)).take().unwrap();
(closure)().as_rb_value()
}
unsafe extern "C" fn call_ensure<F2>(arg: VALUE) -> VALUE
where
F2: FnOnce(),
{
let closure = (&mut *(arg as *mut Option<F2>)).take().unwrap();
(closure)();
ruby_special_consts::RUBY_Qnil as VALUE
}
let result = unsafe {
let call_func_ptr = call_func::<F1> as unsafe extern "C" fn(VALUE) -> VALUE;
#[cfg(ruby_lt_2_7)]
let call_func_ptr: unsafe extern "C" fn() -> VALUE = std::mem::transmute(call_func_ptr);
let mut some_func = Some(func);
let func_closure = &mut some_func as *mut Option<F1> as VALUE;
let call_ensure_ptr = call_ensure::<F2> as unsafe extern "C" fn(VALUE) -> VALUE;
#[cfg(ruby_lt_2_7)]
let call_ensure_ptr: unsafe extern "C" fn() -> VALUE = std::mem::transmute(call_ensure_ptr);
let mut some_ensure = Some(ensure);
let ensure_closure = &mut some_ensure as *mut Option<F2> as VALUE;
rb_ensure(
Some(call_func_ptr),
func_closure,
Some(call_ensure_ptr),
ensure_closure,
)
};
Value::new(result)
}
pub(crate) fn raise(e: Error) -> ! {
match e {
Error::Jump(tag) => tag.resume(),
err => {
unsafe { rb_exc_raise(err.exception().as_rb_value()) }
unreachable!()
}
};
}
pub(crate) fn bug_from_panic(e: Box<dyn Any + Send + 'static>, or: &str) -> ! {
let msg: Cow<'_, str> = if let Some(&m) = e.downcast_ref::<&'static str>() {
m.into()
} else if let Some(m) = e.downcast_ref::<String>() {
m.clone().into()
} else {
or.into()
};
bug(&msg)
}
pub fn bug(s: &str) -> ! {
let s = CString::new(s).unwrap_or_else(|_| CString::new("panic").unwrap());
unsafe { rb_bug(s.as_ptr()) };
unreachable!()
}
pub fn warning(s: &str) {
let s = CString::new(s).unwrap();
unsafe { rb_warning(s.as_ptr()) };
}