use std::borrow::Cow;
use annotate_snippets::{Level, Renderer, Snippet};
use crate::{InputRange, ParserExpectation, ParsingError};
#[derive(Clone)]
pub struct ErrorReport<'a, 'b, 'c> {
source: &'a str,
source_path: &'b str,
err_msg: Cow<'c, str>,
offset: usize,
len: usize,
}
impl<'a, 'b, 'c> ErrorReport<'a, 'b, 'c> {
pub fn parsing_error(
source: &'a str,
source_path: &'b str,
parsing_err: &'c ParsingError,
) -> Self {
Self {
source,
source_path,
err_msg: match parsing_err.critical_message() {
Some(nature) => Cow::Borrowed(nature),
None => match parsing_err.inner().expected() {
ParserExpectation::Char(c) => Cow::Owned(format!("expected char '{c}'")),
ParserExpectation::Str(str) => Cow::Owned(format!("expected '{str}'")),
ParserExpectation::Custom(msg) => Cow::Borrowed(msg),
ParserExpectation::Break => Cow::Borrowed("got break (should not be possible)"),
},
},
offset: parsing_err.inner().at().start.offset,
len: parsing_err.inner().at().len,
}
}
pub const fn with_range(
source: &'a str,
source_path: &'b str,
at: InputRange,
msg: &'c str,
) -> Self {
Self {
source,
source_path,
offset: at.start.offset,
len: at.len,
err_msg: Cow::Borrowed(msg),
}
}
}
impl std::fmt::Display for ErrorReport<'_, '_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ErrorReport {
source,
source_path,
err_msg,
offset,
len,
} = self;
let line = source[..*offset].chars().filter(|&c| c == '\n').count();
let extract_start_line = line.saturating_sub(2) + 1;
let extract_start_offset = if extract_start_line == 1 {
0
} else {
let mut line_counter = 1;
let mut shift = 0;
source[..*offset]
.chars()
.find_map(|c| {
if c == '\n' {
line_counter += 1;
}
shift += c.len_utf8();
if line_counter == extract_start_line {
Some(shift)
} else {
None
}
})
.unwrap_or(0)
};
let mut line_counter = 0;
let afterwards = &source[offset + len..].chars().position(|c| {
if c == '\n' {
line_counter += 1;
}
line_counter == 2
});
let extract_end = match afterwards {
Some(pos) => offset + len + pos + 1,
None => source.len(),
};
let extract = format!("{} ", &source[extract_start_offset..extract_end]);
let range_chars_len = if offset + (*len).max(1) == source.len() + 1 {
1
} else {
source[*offset..*offset + (*len).max(1)].len()
};
let snippet = Level::Error.title("Parsing failed").snippet(
Snippet::source(&extract)
.line_start(extract_start_line)
.origin(source_path)
.fold(false)
.annotation(
Level::Error
.span(
offset - extract_start_offset
..offset - extract_start_offset + range_chars_len,
)
.label(err_msg.as_ref()),
),
);
let renderer = Renderer::styled();
let rendered = renderer.render(snippet);
write!(f, "{rendered}")
}
}