use winnow::{
error::{ContextError, ParseError as WinnowParseError, StrContext, StrContextValue},
stream::AsBStr,
};
use crate::StatefulInput;
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub line_number: usize,
pub column: usize,
pub line_content: String,
pub label: Option<String>,
pub expected: Vec<String>,
}
impl From<WinnowParseError<StatefulInput<'_, '_>, ContextError>> for ParseError {
fn from(value: WinnowParseError<StatefulInput, ContextError>) -> Self {
let offset = value.offset();
let input_str = value.input().as_bstr();
let mut line_start_byte = 0;
let mut line_end_byte = input_str.len();
let mut line_number = 1;
let mut column = 1;
for (index, byte) in input_str[0..offset].iter().enumerate() {
if *byte == b'\n' {
line_start_byte = index + 1;
line_number += 1;
column = 1;
} else {
column += 1;
}
}
for (index, byte) in input_str[offset..].iter().enumerate() {
if *byte == b'\n' {
line_end_byte = index + offset;
break;
}
}
let line_content =
unsafe { str::from_utf8_unchecked(&input_str[line_start_byte..line_end_byte]) }
.to_string();
let mut label = None;
let mut expected = Vec::new();
for ctx in value.inner().context() {
match ctx {
StrContext::Label(str) => {
let _ = label.get_or_insert(str.to_string());
}
StrContext::Expected(val) => match val {
StrContextValue::CharLiteral(c) => expected.push(format!("`{c}`")),
StrContextValue::StringLiteral(s) => expected.push(format!("\"{s}\"")),
StrContextValue::Description(d) => expected.push(d.to_string()),
_ => {}
},
_ => {}
}
}
Self {
line_number,
column,
line_content,
label,
expected,
}
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = self
.label
.as_ref()
.map(|l| format!("Invalid {l}"))
.unwrap_or_else(|| "Invalid token".to_string());
writeln!(f, "Error: {label}")?;
writeln!(f, " --> line {}, column {}", self.line_number, self.column)?;
let gutter_width = self.line_number.to_string().len();
writeln!(f, "{:>gwidth$} |", "", gwidth = gutter_width)?;
writeln!(
f,
"{:>gwidth$} | {}",
self.line_number,
self.line_content,
gwidth = gutter_width
)?;
writeln!(
f,
"{:>gwidth$} | {:>cwidth$}^",
"",
"",
gwidth = gutter_width,
cwidth = self.column.saturating_sub(1)
)?;
write!(f, "{:>gwidth$} = Expected ", "", gwidth = gutter_width)?;
match self.expected.as_slice() {
[] => {}
[single] => write!(f, "{single}")?,
[all @ .., last] => {
write!(f, "{} or {last}", all.join(", "))?;
}
}
Ok(())
}
}
impl std::error::Error for ParseError {}