use std::{convert::Infallible, error::Error, fmt, io, mem::take, path::PathBuf, sync::Arc};
use crate::{
CodeSpan, DiagnosticKind, Ident, Inputs, Report, ReportFragment, ReportKind, Sp, Span,
function::FunctionId, parse::ParseError, value::Value,
};
#[derive(Debug, Clone)]
#[must_use]
pub struct UiuaError {
pub kind: Box<UiuaErrorKind>,
pub meta: Box<ErrorMeta>,
}
#[derive(Debug, Clone)]
pub struct ErrorMeta {
pub trace: Vec<TraceFrame>,
pub is_fill: bool,
pub is_case: bool,
pub multi: Vec<UiuaError>,
pub infos: Vec<(String, Option<Span>)>,
}
#[derive(Debug, Clone)]
pub enum UiuaErrorKind {
Load(PathBuf, Arc<io::Error>),
Format(PathBuf, Arc<io::Error>),
Parse(Vec<Sp<ParseError>>, Box<Inputs>),
Run {
message: Sp<String, Span>,
info: Vec<Sp<String, Span>>,
inputs: Box<Inputs>,
},
Throw(Box<Value>, Span, Box<Inputs>),
Timeout(Span, Box<Inputs>),
CompilerPanic(String),
Interrupted,
}
impl UiuaErrorKind {
pub fn error(self) -> UiuaError {
self.into()
}
}
impl From<UiuaErrorKind> for UiuaError {
fn from(kind: UiuaErrorKind) -> Self {
Self {
kind: kind.into(),
meta: ErrorMeta {
trace: Vec::new(),
is_fill: false,
is_case: false,
multi: Vec::new(),
infos: Vec::new(),
}
.into(),
}
}
}
pub type UiuaResult<T = ()> = Result<T, UiuaError>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceFrame {
pub id: Option<FunctionId>,
pub span: Span,
}
impl fmt::Display for UiuaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &*self.kind {
UiuaErrorKind::Load(path, e) => {
write!(f, "failed to load {}: {e}", path.to_string_lossy())
}
UiuaErrorKind::Format(path, e) => {
write!(f, "failed to format {}: {e}", path.to_string_lossy())
}
UiuaErrorKind::Parse(errors, _) => {
for error in errors {
writeln!(f, "{error}")?;
}
Ok(())
}
UiuaErrorKind::Run { message: error, .. } => write!(f, "{error}"),
UiuaErrorKind::Throw(value, span, _) => write!(f, "{span}: {value}"),
UiuaErrorKind::Timeout(..) => write!(f, "Maximum execution time exceeded"),
UiuaErrorKind::CompilerPanic(message) => message.fmt(f),
UiuaErrorKind::Interrupted => write!(f, "# Program interrupted"),
}
}
}
impl UiuaError {
pub fn with_info(mut self, info: impl IntoIterator<Item = (String, Option<Span>)>) -> Self {
self.meta.infos.extend(info);
self
}
pub fn value(self) -> Value {
match *self.kind {
UiuaErrorKind::Throw(value, ..) => *value,
_ => self.to_string().into(),
}
}
pub fn into_multi(mut self) -> Vec<Self> {
let mut multi = take(&mut self.meta.multi);
multi.insert(0, self);
multi
}
pub fn from_multi(multi: impl IntoIterator<Item = Self>) -> Self {
let mut iter = multi.into_iter();
let mut error = iter
.next()
.unwrap_or_else(|| UiuaErrorKind::CompilerPanic("Unknown error".into()).error());
error.meta.multi.extend(iter);
error
}
pub(crate) fn fill(mut self) -> Self {
self.meta.is_fill = true;
self
}
pub fn trace(mut self, span: CodeSpan) -> Self {
let frame = TraceFrame {
id: None,
span: Span::Code(span),
};
self.meta.trace.push(frame);
self
}
pub(crate) fn trace_macro(mut self, name: Option<Ident>, span: CodeSpan) -> Self {
let frame = TraceFrame {
id: Some(FunctionId::Macro(name, span.clone())),
span: Span::Code(span),
};
self.meta.trace.push(frame);
self
}
pub(crate) fn track_caller(&mut self, new_span: impl Into<Span>) {
self.meta.trace.clear();
match &mut *self.kind {
UiuaErrorKind::Run { message, .. } => message.span = new_span.into(),
UiuaErrorKind::Throw(_, span, _) => *span = new_span.into(),
_ => {}
}
}
pub fn load(path: PathBuf, error: io::Error) -> Self {
UiuaErrorKind::Load(path, Arc::new(error)).into()
}
pub fn format(path: PathBuf, error: io::Error) -> Self {
UiuaErrorKind::Format(path, Arc::new(error)).into()
}
}
fn format_trace(trace: &[TraceFrame]) -> Vec<String> {
let max_id_length = trace
.iter()
.filter(|frame| frame.span != Span::Builtin)
.map(|frame| (frame.id.as_ref()).map_or(0, |id| id.to_string().chars().count()))
.max()
.unwrap_or(0);
let max_span_length = trace
.iter()
.map(|frame| match &frame.span {
Span::Code(span) => span.to_string().chars().count(),
Span::Builtin => 0,
})
.max()
.unwrap_or(0);
let mut lines: Vec<String> = Vec::new();
let mut i = 0;
'outer: while i < trace.len() {
let frame = &trace[i];
if frame.id == Some(FunctionId::Main) {
i += 1;
continue;
}
for n in 1..=4 {
if i >= n
&& i + n < trace.len()
&& trace[i - n..][..n]
.iter()
.zip(&trace[i..][..n])
.all(|(a, b)| a.id == b.id)
{
for (i, line) in lines.iter_mut().rev().take(n).rev().enumerate() {
let sep = match (n, i) {
(1, _) => " ×",
(_, 0) => " ┬×",
(n, i) if i == n - 1 => " ┘",
_ => " ┤",
};
if let Some((msg, n)) = line
.rsplit_once(sep)
.and_then(|(a, b)| b.parse::<usize>().ok().map(|n| (a, n)))
{
*line = format!("{msg}{sep}{}", n + 1);
} else {
if !line.ends_with(sep) {
line.push_str(sep);
}
if i == 0 {
line.push('2');
}
}
}
i += n;
continue 'outer;
}
}
lines.push(match (&frame.id, &frame.span) {
(Some(id), Span::Code(span)) => {
format!(" in {id:max_id_length$} at {span:max_span_length$}")
}
(Some(id), Span::Builtin) => format!(" in {id:max_id_length$}"),
(None, Span::Code(span)) => {
format!(" at {span:max_span_length$}")
}
(None, Span::Builtin) => {
i += 1;
continue;
}
});
i += 1;
}
lines
}
impl Error for UiuaError {}
impl From<Infallible> for UiuaError {
fn from(value: Infallible) -> Self {
match value {}
}
}
impl UiuaError {
pub fn report(&self) -> Report {
let kind = ReportKind::Error;
let mut report = match &*self.kind {
UiuaErrorKind::Parse(errors, inputs) => Report::new_multi(
kind,
inputs,
errors
.iter()
.map(|error| (error.value.to_string(), error.span.clone().into())),
),
UiuaErrorKind::Run {
message,
info,
inputs,
} => {
let mut report =
Report::new_multi(kind, inputs, [(&message.value, message.span.clone())]);
for info in info {
report.fragments.push(ReportFragment::Newline);
report.fragments.extend(
Report::new_multi(
DiagnosticKind::Info.into(),
inputs,
[(&info.value, info.span.clone())],
)
.fragments,
);
}
report
}
UiuaErrorKind::Throw(message, span, inputs) => {
Report::new_multi(kind, inputs, [(&message, span.clone())])
}
UiuaErrorKind::Timeout(span, inputs) => Report::new_multi(
kind,
inputs,
[("Maximum execution time exceeded", span.clone())],
),
UiuaErrorKind::CompilerPanic(message) => Report::new(kind, message),
UiuaErrorKind::Load(..) | UiuaErrorKind::Format(..) => {
Report::new(kind, self.to_string())
}
UiuaErrorKind::Interrupted => {
return Report {
fragments: vec![ReportFragment::Plain(self.to_string())],
color: true,
};
}
};
report = report_trace(report, &self.meta.trace);
let default_inputs = Inputs::default();
let inputs = match &*self.kind {
UiuaErrorKind::Parse(_, inputs)
| UiuaErrorKind::Run { inputs, .. }
| UiuaErrorKind::Throw(_, _, inputs)
| UiuaErrorKind::Timeout(_, inputs) => inputs,
_ => &default_inputs,
};
for (info, span) in &self.meta.infos {
report.fragments.push(ReportFragment::Newline);
if let Some(span) = span {
report.fragments.extend(
Report::new_multi(DiagnosticKind::Info.into(), inputs, [(info, span.clone())])
.fragments,
);
} else {
report.fragments.push(ReportFragment::Colored(
"Info".into(),
DiagnosticKind::Info.into(),
));
report.fragments.push(ReportFragment::Plain(": ".into()));
report.fragments.push(ReportFragment::Plain(info.into()));
}
}
for error in &self.meta.multi {
report.fragments.push(ReportFragment::Newline);
report.fragments.extend(error.report().fragments);
}
report
}
}
fn report_trace(mut report: Report, trace: &[TraceFrame]) -> Report {
for line in format_trace(trace) {
report.fragments.push(ReportFragment::Newline);
report.fragments.push(ReportFragment::Plain(line));
}
report
}