use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, AeriError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Span {
pub start: usize,
pub end: usize,
pub line: usize,
pub column: usize,
}
impl Span {
pub fn new(start: usize, end: usize, line: usize, column: usize) -> Self {
Self {
start,
end,
line,
column,
}
}
pub fn join(self, other: Span) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
line: self.line,
column: self.column,
}
}
}
#[derive(Debug, Error)]
pub enum AeriError {
#[error("{file}:{line}:{column}: {message}{snippet}")]
Diagnostic {
file: String,
line: usize,
column: usize,
length: usize,
message: String,
snippet: String,
},
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
}
impl AeriError {
pub fn at(
file: impl Into<String>,
line: usize,
column: usize,
message: impl Into<String>,
) -> Self {
Self::Diagnostic {
file: file.into(),
line,
column,
length: 1,
message: message.into(),
snippet: String::new(),
}
}
pub fn at_span(file: impl Into<String>, span: Span, message: impl Into<String>) -> Self {
Self::Diagnostic {
file: file.into(),
line: span.line,
column: span.column,
length: span.end.saturating_sub(span.start).max(1),
message: message.into(),
snippet: String::new(),
}
}
pub fn with_source(self, source: &str) -> Self {
let Self::Diagnostic {
file,
line,
column,
length,
message,
snippet,
} = self
else {
return self;
};
if !snippet.is_empty() {
return Self::Diagnostic {
file,
line,
column,
length,
message,
snippet,
};
}
let source_line = source.lines().nth(line.saturating_sub(1)).unwrap_or("");
let caret_width = length
.min(source_line.len().saturating_sub(column.saturating_sub(1)))
.max(1);
let marker = format!(
"{}{}",
" ".repeat(column.saturating_sub(1)),
"^".repeat(caret_width)
);
Self::Diagnostic {
file,
line,
column,
length,
message,
snippet: format!("\n {source_line}\n {marker}"),
}
}
}