use std::sync::Arc;
use ariadne::{Color, Label, Report, ReportKind, Source};
use crate::source::SourceFile;
use crate::span::Span;
#[derive(Debug, Clone)]
struct ErrorDetail {
primary: (Span, String),
labels: Vec<(Span, String)>,
source: Option<Arc<String>>,
source_name: Option<String>,
help: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Error {
message: String,
line: Option<usize>,
reason: Option<ErrorReason>,
detail: Option<Box<ErrorDetail>>,
}
#[derive(Debug, Clone)]
pub struct ErrorReason {
error_type: Reason,
data: Option<Vec<String>>,
}
#[derive(Clone, Copy, Debug)]
pub enum Reason {
Parse,
AST,
Lexer,
Interpreter,
Utils,
Compile,
Runtime,
}
impl Error {
pub fn at(kind: Reason, message: impl Into<String>, span: Span) -> Self {
let message = message.into();
#[cfg(feature = "debug")]
log::debug!("Error: {}", message);
Self {
message: message.clone(),
line: None,
reason: Some(ErrorReason::init(kind, None)),
detail: Some(Box::new(ErrorDetail {
primary: (span, message),
labels: Vec::new(),
source: None,
source_name: None,
help: None,
})),
}
}
pub fn with_primary_label(mut self, label: impl Into<String>) -> Self {
if let Some(d) = &mut self.detail {
d.primary.1 = label.into();
}
self
}
pub fn with_label(mut self, span: Span, label: impl Into<String>) -> Self {
if let Some(d) = &mut self.detail {
d.labels.push((span, label.into()));
}
self
}
pub fn with_source(mut self, source: Arc<String>) -> Self {
if let Some(d) = &mut self.detail {
d.source = Some(source);
}
self
}
pub fn with_source_name(mut self, name: impl Into<String>) -> Self {
if let Some(d) = &mut self.detail {
d.source_name = Some(name.into());
}
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
if let Some(d) = &mut self.detail {
d.help = Some(help.into());
}
self
}
pub fn with_source_file(mut self, file: &SourceFile) -> Self {
if let Some(d) = &mut self.detail {
d.source = Some(Arc::clone(&file.text));
d.source_name = Some(file.name.to_string());
}
self
}
pub fn print_error(&self) {
self.report_to_stderr();
panic!("rl error");
}
pub fn report_to_stderr(&self) {
if let Some(d) = &self.detail
&& let Some(src) = &d.source
{
let name: &str = d.source_name.as_deref().unwrap_or("<source>");
let (sp, primary_label) = &d.primary;
let mut builder = Report::build(ReportKind::Error, (name, sp.start..sp.end))
.with_message(&self.message)
.with_label(
Label::new((name, sp.start..sp.end))
.with_message(primary_label)
.with_color(Color::Red),
);
for (lsp, label) in &d.labels {
builder = builder.with_label(
Label::new((name, lsp.start..lsp.end))
.with_message(label)
.with_color(Color::Yellow),
);
}
if let Some(help) = &d.help {
builder = builder.with_help(help);
}
let _ = builder.finish().eprint((name, Source::from(src.as_str())));
return;
}
self.fallback_text();
}
fn fallback_text(&self) {
match &self.line {
Some(l) => println!("[{}) Error: {}]", l, self.message),
None => println!("[Error: {}]", self.message),
}
if let Some(r) = &self.reason {
match &r.data {
Some(d) => {
println!("[{}]", r.get_type_string());
for l in d {
println!("{}", l);
}
}
_ => println!("[{}]", r.get_type_string()),
}
}
}
pub fn span(&self) -> Option<crate::span::Span> {
self.detail.as_ref().map(|d| d.primary.0)
}
}
impl ErrorReason {
pub fn init(error_type: Reason, data: Option<Vec<String>>) -> Self {
Self { error_type, data }
}
fn get_type_string(&self) -> String {
match &self.error_type {
Reason::Parse => "Parse Error",
Reason::AST => "AST Error",
Reason::Lexer => "Lexer Error",
Reason::Interpreter => "Interpreter Error",
Reason::Utils => "Utils Error",
Reason::Compile => "Compile Error",
Reason::Runtime => "Runtime Error",
}
.to_string()
}
}
impl Error {
pub fn message(&self) -> &str {
&self.message
}
}