use crate::types::Span;
use std::fmt;
pub mod context;
pub mod format;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub(crate) span: Span,
pub(crate) kind: ErrorKind,
}
#[allow(clippy::unnecessary_box_returns)] impl ParseError {
pub(crate) fn unexpected(
span: Span,
expected: impl Into<Vec<String>>,
found: impl Into<String>,
) -> Box<Self> {
Box::new(Self {
span,
kind: ErrorKind::UnexpectedToken {
expected: expected.into(),
found: found.into(),
},
})
}
pub(crate) fn invalid(
span: Span,
description: impl Into<String>,
hint: Option<String>,
) -> Box<Self> {
Box::new(Self {
span,
kind: ErrorKind::InvalidSyntax {
description: description.into(),
hint,
},
})
}
#[must_use]
pub(crate) fn unclosed(span: Span, delimiter: char, opening_span: Span) -> Box<Self> {
Box::new(Self {
span,
kind: ErrorKind::UnclosedDelimiter {
delimiter,
opening_span,
},
})
}
#[must_use]
pub(crate) fn missing(span: Span, token: &str, after: &str) -> Box<Self> {
Box::new(Self {
span,
kind: ErrorKind::MissingToken {
token: token.into(),
after: after.into(),
},
})
}
#[must_use]
pub fn message(&self) -> String {
match &self.kind {
ErrorKind::UnexpectedToken { expected, found } => {
if expected.is_empty() {
format!("unexpected token: {found}")
} else if expected.len() == 1 {
format!("expected {}, found {}", expected[0], found)
} else {
format!("expected one of {}, found {}", expected.join(", "), found)
}
}
ErrorKind::UnclosedDelimiter { delimiter, .. } => match delimiter {
'\'' => "unclosed indented string (missing closing '')".to_string(),
'"' => "unclosed string literal (missing closing '\"')".to_string(),
_ => format!("unclosed delimiter '{delimiter}'"),
},
ErrorKind::MissingToken { token, after } => {
format!("missing {token} after {after}")
}
ErrorKind::InvalidSyntax { description, .. } => description.clone(),
ErrorKind::ChainedComparison {
first_op,
second_op,
} => {
format!(
"chained comparison operators '{first_op}' and '{second_op}' are not allowed"
)
}
}
}
#[must_use]
pub const fn byte_range(&self) -> std::ops::Range<usize> {
self.span.start as usize..self.span.end as usize
}
#[must_use]
pub const fn code(&self) -> Option<&str> {
match &self.kind {
ErrorKind::UnexpectedToken { .. } => Some("E001"),
ErrorKind::UnclosedDelimiter { .. } => Some("E002"),
ErrorKind::MissingToken { .. } => Some("E003"),
ErrorKind::InvalidSyntax { .. } => Some("E005"),
ErrorKind::ChainedComparison { .. } => Some("E006"),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Parse error at byte {}: {}",
self.span.start,
self.message()
)
}
}
impl std::error::Error for ParseError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErrorKind {
UnexpectedToken {
expected: Vec<String>,
found: String,
},
UnclosedDelimiter {
delimiter: char,
opening_span: Span,
},
MissingToken {
token: String,
after: String,
},
InvalidSyntax {
description: String,
hint: Option<String>,
},
ChainedComparison {
first_op: String,
second_op: String,
},
}
pub type Result<T> = std::result::Result<T, Box<ParseError>>;