use crate::lexer::LexError;
use crate::token::Span;
use std::fmt;
use std::path::{Path, PathBuf};
use unicode_width::UnicodeWidthStr;
#[derive(Debug)]
pub struct Error {
inner: Box<ErrorInner>,
}
#[derive(Debug)]
struct ErrorInner {
text: Option<Text>,
file: Option<PathBuf>,
span: Span,
kind: ErrorKind,
}
#[derive(Debug)]
struct Text {
line: usize,
col: usize,
snippet: String,
}
#[derive(Debug)]
enum ErrorKind {
Lex(LexError),
Custom(String),
}
impl Error {
pub(crate) fn lex(span: Span, content: &str, kind: LexError) -> Error {
let mut ret = Error {
inner: Box::new(ErrorInner {
text: None,
file: None,
span,
kind: ErrorKind::Lex(kind),
}),
};
ret.set_text(content);
ret
}
pub(crate) fn parse(span: Span, content: &str, message: String) -> Error {
let mut ret = Error {
inner: Box::new(ErrorInner {
text: None,
file: None,
span,
kind: ErrorKind::Custom(message),
}),
};
ret.set_text(content);
ret
}
pub fn new(span: Span, message: String) -> Error {
Error {
inner: Box::new(ErrorInner {
text: None,
file: None,
span,
kind: ErrorKind::Custom(message),
}),
}
}
pub fn span(&self) -> Span {
self.inner.span
}
pub fn set_text(&mut self, contents: &str) {
if self.inner.text.is_some() {
return;
}
self.inner.text = Some(Text::new(contents, self.inner.span));
}
pub fn set_path(&mut self, path: &Path) {
if self.inner.file.is_some() {
return;
}
self.inner.file = Some(path.to_path_buf());
}
pub fn lex_error(&self) -> Option<&LexError> {
match &self.inner.kind {
ErrorKind::Lex(e) => Some(e),
_ => None,
}
}
pub fn message(&self) -> String {
match &self.inner.kind {
ErrorKind::Lex(e) => e.to_string(),
ErrorKind::Custom(e) => e.clone(),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let err = match &self.inner.kind {
ErrorKind::Lex(e) => e as &dyn fmt::Display,
ErrorKind::Custom(e) => e as &dyn fmt::Display,
};
let text = match &self.inner.text {
Some(text) => text,
None => {
return write!(f, "{err} at byte offset {}", self.inner.span.offset);
}
};
let file = self
.inner
.file
.as_ref()
.and_then(|p| p.to_str())
.unwrap_or("<anon>");
let col = text.col + 1;
let line = text.line + 1;
if col > 500 {
return write!(f, "{err} at {file}:{line}:{col}");
}
write!(
f,
"\
{err}
--> {file}:{line}:{col}
|
{line:4} | {text}
| {marker:>0$}",
col,
text = text.snippet,
marker = "^",
)
}
}
impl std::error::Error for Error {}
impl Text {
fn new(content: &str, span: Span) -> Text {
let (line, col) = span.linecol_in(content);
let contents = content.lines().nth(line).unwrap_or("");
let mut snippet = String::new();
for ch in contents.chars() {
match ch {
'\t' => {
snippet.push_str(" ");
}
'\u{202a}' | '\u{202b}' | '\u{202d}' | '\u{202e}' | '\u{2066}' | '\u{2067}'
| '\u{2068}' | '\u{206c}' | '\u{2069}' => {}
c => snippet.push(c),
}
}
let col = snippet.get(..col).map(|s| s.width()).unwrap_or(col);
Text { line, col, snippet }
}
}