use std::error::Error as StdError;
use std::fmt::{self, Display, Formatter};
use std::ops::Range;
use crate::token::Operator;
pub(crate) fn src_line_no(end: usize, src: &str) -> (usize, usize) {
let mut line_no = 1;
let mut pos = 0;
while let Some(p) = &src[pos..end].find('\n') {
pos += p + 1;
line_no += 1;
}
let col = 1 + end - pos;
(line_no, col)
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub line: usize,
pub col: usize,
pub src: String,
}
impl StdError for ParseError {}
impl ParseError {
pub(crate) fn new(range: Range<usize>, src: &str, kind: ParseErrorKind) -> Self {
let (line, col) = src_line_no(range.end, src);
Self {
line,
col,
src: src.to_string(),
kind,
}
}
}
impl Display for ParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let start_line = self.line;
let lines = self.src.lines().enumerate().skip(start_line.saturating_sub(2)).take(3);
let msg = match &self.kind {
ParseErrorKind::UnterminatedString => "unterminated string".into(),
ParseErrorKind::UnterminatedAttributes => "unterminated attributes (missing `]`)".into(),
ParseErrorKind::UnterminatedAssociation => "unterminated association (missing `)`)".into(),
ParseErrorKind::UnterminatedElement => "unterminated element".into(),
ParseErrorKind::InvalidToken { expected } => {
format!("invalid token (expected: \"{expected}\")")
}
ParseErrorKind::InvalidNumber => "invalid number".into(),
ParseErrorKind::InvalidIndex => "invalid index".into(),
ParseErrorKind::InvalidPath => "invalid path".into(),
ParseErrorKind::InvalidHexValue => "invalid hex value".into(),
ParseErrorKind::UnexpectedEof => "unexpected end of file".into(),
ParseErrorKind::TrailingPipe => "trailing pipe character".into(),
ParseErrorKind::InvalidDedent => "dedent does not match previous indentation levels".into(),
ParseErrorKind::InvalidOperator(op) => format!("invalid operator: {op}"),
ParseErrorKind::UnexpectedToken(msg) => format!("unexpected token: {msg}"),
ParseErrorKind::UnregisteredComponent(name) => format!("unregistered component: {name}"),
ParseErrorKind::InvalidKey => todo!(),
};
writeln!(f, "error on line {start_line}: {msg}")?;
for (no, line) in lines {
let no = no + 1;
let mark = if self.line == no { "-> " } else { " " };
let mark_line = format!("{mark}{no}");
writeln!(f, "{mark_line} {line}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseErrorKind {
UnterminatedString,
UnterminatedElement,
UnterminatedAttributes,
UnterminatedAssociation,
InvalidToken { expected: &'static str },
InvalidNumber,
InvalidIndex,
InvalidHexValue,
UnexpectedEof,
TrailingPipe,
InvalidDedent,
InvalidPath,
InvalidOperator(Operator),
UnexpectedToken(String),
InvalidKey,
UnregisteredComponent(String),
}