use crate::lexer::Span;
use std::fmt;
use thiserror::Error;
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, Clone, Error)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub span: Span,
pub help: Option<String>,
pub notes: Vec<String>,
}
impl ParseError {
pub fn new(kind: ParseErrorKind, span: Span) -> Self {
Self {
kind,
span,
help: None,
notes: Vec::new(),
}
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn message(&self) -> String {
self.kind.to_string()
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(help) = &self.help {
write!(f, "\n help: {}", help)?;
}
for note in &self.notes {
write!(f, "\n note: {}", note)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ParseErrorKind {
#[error("unexpected token: {found}")]
UnexpectedToken { found: String },
#[error("expected {expected}, found {found}")]
Expected { expected: String, found: String },
#[error("unexpected end of file")]
UnexpectedEof,
#[error("invalid expression")]
InvalidExpression,
#[error("invalid left-hand side of assignment")]
InvalidAssignTarget,
#[error("expected operand")]
MissingOperand,
#[error("unclosed {delimiter}")]
UnclosedDelimiter { delimiter: String },
#[error("mismatched {expected}, found {found}")]
MismatchedDelimiter { expected: String, found: String },
#[error("invalid statement")]
InvalidStatement,
#[error("expected `;`")]
ExpectedSemicolon,
#[error("invalid item declaration")]
InvalidItem,
#[error("duplicate `{modifier}` modifier")]
DuplicateModifier { modifier: String },
#[error("`{first}` and `{second}` are mutually exclusive")]
ConflictingModifiers { first: String, second: String },
#[error("invalid type")]
InvalidType,
#[error("expected type")]
ExpectedType,
#[error("invalid pattern")]
InvalidPattern,
#[error("expected pattern")]
ExpectedPattern,
#[error("`..` must be at the end of the pattern")]
RestPatternNotLast,
#[error("invalid generic parameter")]
InvalidGenericParam,
#[error("invalid where clause")]
InvalidWhereClause,
#[error("invalid attribute")]
InvalidAttribute,
#[error("inner attribute not allowed in this context")]
InnerAttributeNotAllowed,
#[error("invalid macro invocation")]
InvalidMacroInvocation,
#[error("lexer error: {0}")]
LexerError(String),
#[error("internal parser error: {0}")]
Internal(String),
}
impl ParseErrorKind {
pub fn suggestion(&self) -> Option<&'static str> {
match self {
ParseErrorKind::ExpectedSemicolon => Some("add a `;` at the end of the statement"),
ParseErrorKind::UnclosedDelimiter { .. } => Some("add the missing closing delimiter"),
ParseErrorKind::InvalidAssignTarget => {
Some("only variables, fields, and dereferences can be assigned to")
}
_ => None,
}
}
pub fn is_recoverable(&self) -> bool {
!matches!(
self,
ParseErrorKind::UnexpectedEof | ParseErrorKind::Internal(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ParseError::new(
ParseErrorKind::Expected {
expected: "identifier".to_string(),
found: "`+`".to_string(),
},
Span::dummy(),
);
assert!(err.to_string().contains("expected"));
assert!(err.to_string().contains("identifier"));
}
#[test]
fn test_error_with_help() {
let err = ParseError::new(ParseErrorKind::InvalidAssignTarget, Span::dummy())
.with_help("try using a variable name");
assert!(err.help.is_some());
}
}