use std::error::{Error};
use std::fmt::{self, Debug, Display, Formatter};
use super::engine::{glsp, Guard, Span, with_vm};
use super::val::{Val};
use super::vm::{Frame};
use super::wrap::{IntoVal};
pub type GResult<T> = Result<T, GError>;
pub struct GError {
pub(crate) payload: Box<Payload>
}
pub(crate) enum Payload {
Error {
val: Val,
file_location: Option<String>,
stack_trace: Option<String>,
defer_chain: Option<GError>,
source: Option<Box<dyn Error + 'static>>
},
MacroNoOp
}
impl GError {
pub fn new() -> GError {
GError::from_str("explicit call to bail!, error!, or GError::new")
}
pub fn from_str(st: &str) -> GError {
GError::from_val(st)
}
pub fn from_val<T: IntoVal>(t: T) -> GError {
let val = t.into_val().unwrap_or(Val::Nil);
let file_location = glsp::file_location();
let stack_trace = if glsp::errors_verbose() {
Some(glsp::stack_trace())
} else {
None
};
GError {
payload: Box::new(Payload::Error {
val,
file_location,
stack_trace,
defer_chain: None,
source: None
})
}
}
pub fn macro_no_op() -> GError {
with_vm(|vm| {
if vm.in_expander() {
GError {
payload: Box::new(Payload::MacroNoOp)
}
} else {
GError::from_str("(macro-no-op) called outside of any macro expander")
}
})
}
pub fn is_macro_no_op(&self) -> bool {
match &*self.payload {
Payload::MacroNoOp => true,
Payload::Error { .. } => false
}
}
pub fn val(&self) -> Val {
match &*self.payload {
Payload::MacroNoOp => panic!(),
Payload::Error { val, .. } => val.clone()
}
}
pub fn stack_trace(&self) -> Option<&str> {
match &*self.payload {
Payload::MacroNoOp => panic!(),
Payload::Error { stack_trace, .. } => stack_trace.as_ref().map(|s| &**s)
}
}
#[allow(dead_code)]
pub(crate) fn defer_chain(&self) -> Option<&GError> {
match &*self.payload {
Payload::MacroNoOp => panic!(),
Payload::Error { defer_chain, .. } => defer_chain.as_ref()
}
}
pub(crate) fn chain_defer_error(&mut self, defer_error: GError) {
if self.is_macro_no_op() {
*self = defer_error;
} else {
match &mut *self.payload {
Payload::Error { ref mut defer_chain, .. } => {
if let Some(ref mut defer_chain) = defer_chain {
defer_chain.chain_defer_error(defer_error);
} else {
*defer_chain = Some(defer_error);
}
}
Payload::MacroNoOp => unreachable!()
}
}
}
pub fn with_source(mut self, source_to_add: impl Error + 'static) -> GError {
match &mut *self.payload {
Payload::MacroNoOp => panic!(),
Payload::Error { source, .. } => *source = Some(Box::new(source_to_add))
}
self
}
#[doc(hidden)]
pub fn new_at(span: Span) -> GError {
glsp::push_frame(Frame::ErrorAt(span));
let _guard = Guard::new(|| glsp::pop_frame());
GError::new()
}
#[doc(hidden)]
pub fn from_str_at(span: Span, st: &str) -> GError {
glsp::push_frame(Frame::ErrorAt(span));
let _guard = Guard::new(|| glsp::pop_frame());
GError::from_str(st)
}
#[doc(hidden)]
pub fn from_val_at<T: IntoVal>(span: Span, t: T) -> GError {
glsp::push_frame(Frame::ErrorAt(span));
let _guard = Guard::new(|| glsp::pop_frame());
GError::from_val(t)
}
}
impl Error for GError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let Payload::Error { source: Some(ref source), .. } = *self.payload {
Some(source.as_ref())
} else {
None
}
}
}
impl Debug for GError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self)
}
}
impl Display for GError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match &*self.payload {
Payload::MacroNoOp => panic!(),
Payload::Error { val, file_location, stack_trace, source, defer_chain } => {
match (file_location, stack_trace) {
(&None, &None) => {
write!(f, "{:?}", val)
}
(&Some(ref file_location), &None) => {
write!(f, "{}: {:?}", file_location, val)
}
(_, &Some(ref stack_trace)) => {
write!(f, "stack trace:\n")?;
for line in stack_trace.lines() {
write!(f, " {}\n", line)?;
}
if let Some(ref source) = *source {
write!(f, "\nerrors:")?;
fn write_source(
f: &mut Formatter,
source: &(dyn Error + 'static)
) -> fmt::Result {
match source.downcast_ref::<GError>() {
Some(error) => write!(f, "\n {}", error.val())?,
None => write!(f, "\n {}", source)?
}
if let Some(source2) = source.source() {
write_source(f, source2)
} else {
Ok(())
}
}
write!(f, "\n {}", &val)?;
write_source(f, source.as_ref())?;
} else {
write!(f, "\nerror: {}", &val)?;
}
if let Some(ref defer_chain) = defer_chain {
write!(f, "\n\nwhile this error was unwinding, \
a (defer) form also failed:\n")?;
write!(f, "\n{}", defer_chain)?;
}
Ok(())
}
}
}
}
}
}
#[macro_export]
macro_rules! error {
() => ($crate::GError::new());
($val:expr) => ($crate::GError::from_val($val));
($fmt:literal, $($arg:tt)+) => ($crate::GError::from_str(&format!($fmt, $($arg)+)));
}
#[doc(hidden)]
#[macro_export]
macro_rules! error_at {
($span:expr) => ($crate::GError::new_at($span));
($span:expr, $val:expr) => ($crate::GError::from_val_at($span, $val));
($span:expr, $fmt:literal, $($arg:tt)+) => (
$crate::GError::from_str_at($span, &format!($fmt, $($arg)+))
);
}
#[macro_export]
macro_rules! bail {
($($arg:tt)*) => (return Err($crate::error!($($arg)*)));
}
#[doc(hidden)]
#[macro_export]
macro_rules! bail_at {
($span:expr) => (return Err($crate::error_at!($span)));
($span:expr, $($arg:tt)+) => (return Err($crate::error_at!($span, $($arg)*)));
}
#[macro_export]
macro_rules! ensure {
($condition:expr) => (
if !($condition) {
bail!("ensure!({}) failed", stringify!($condition))
}
);
($condition:expr, $($arg:tt)*) => (
if !($condition) {
bail!($($arg)*)
}
);
}
#[doc(hidden)]
#[macro_export]
macro_rules! ensure_at {
($span:expr, $condition:expr) => (
if !($condition) {
bail_at!($span, "ensure!({}) failed", stringify!($condition))
}
);
($span:expr, $condition:expr, $($arg:tt)*) => (
if !($condition) {
bail_at!($span, $($arg)*)
}
);
}
#[macro_export]
macro_rules! macro_no_op {
() => (return Err($crate::GError::macro_no_op()));
}