use smol_str::SmolStr;
use thiserror::Error;
use crate::{Token, module::ModuleId, selector};
#[derive(Error, Debug, PartialEq)]
pub enum SyntaxError {
#[error("Undefined environment variable `{1}`")]
EnvNotFound(Token, SmolStr),
#[error("Unexpected token `{}`", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
UnexpectedToken(Token),
#[error("Unexpected end of input")]
UnexpectedEOFDetected(ModuleId),
#[error("Insufficient tokens `{}`", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
InsufficientTokens(Token),
#[error("Expected a closing parenthesis `)` but got `{}` delimiter", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
ExpectedClosingParen(Token, Option<Box<Token>>),
#[error("Expected a closing brace `}}` but got `{}` delimiter", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
ExpectedClosingBrace(Token, Option<Box<Token>>),
#[error("Expected a closing bracket `]` but got `{}` delimiter", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
ExpectedClosingBracket(Token, Option<Box<Token>>),
#[error("Invalid assignment target: expected an identifier but got `{}`", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
InvalidAssignmentTarget(Token),
#[error(transparent)]
UnknownSelector(selector::UnknownSelector),
#[error(
"Non-default parameter `{}` cannot follow a parameter with a default value",
if .0.is_eof() { "EOF".to_string() } else { .0.to_string() }
)]
ParameterWithoutDefaultAfterDefault(Token),
#[error("Macro parameters cannot have default values")]
MacroParametersCannotHaveDefaults(Token),
#[error("Variadic parameter must be the last parameter")]
VariadicParameterMustBeLast(Token),
#[error("Multiple variadic parameters are not allowed")]
MultipleVariadicParameters(Token),
#[error("Macro parameters cannot be variadic")]
MacroParametersCannotBeVariadic(Token),
#[error("Expected an expression after `{}` but reached end of file", if .0.is_eof() { "EOF".to_string() } else { .0.to_string() })]
UnexpectedEOFAfterToken(Token),
#[error("Unexpected `end` keyword — no open block to close")]
UnmatchedEnd(Token),
}
impl SyntaxError {
#[cold]
pub fn token(&self) -> Option<&Token> {
match self {
SyntaxError::EnvNotFound(token, _) => Some(token),
SyntaxError::UnexpectedToken(token) => Some(token),
SyntaxError::UnexpectedEOFDetected(_) => None,
SyntaxError::InsufficientTokens(token) => Some(token),
SyntaxError::ExpectedClosingParen(token, _) => Some(token),
SyntaxError::ExpectedClosingBrace(token, _) => Some(token),
SyntaxError::ExpectedClosingBracket(token, _) => Some(token),
SyntaxError::InvalidAssignmentTarget(token) => Some(token),
SyntaxError::UnknownSelector(selector::UnknownSelector(token)) => Some(token),
SyntaxError::ParameterWithoutDefaultAfterDefault(token) => Some(token),
SyntaxError::MacroParametersCannotHaveDefaults(token) => Some(token),
SyntaxError::VariadicParameterMustBeLast(token) => Some(token),
SyntaxError::MultipleVariadicParameters(token) => Some(token),
SyntaxError::MacroParametersCannotBeVariadic(token) => Some(token),
SyntaxError::UnexpectedEOFAfterToken(token) => Some(token),
SyntaxError::UnmatchedEnd(token) => Some(token),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Range, TokenKind, arena::ArenaId, selector};
use rstest::rstest;
fn eof_token() -> Token {
Token {
range: Range::default(),
kind: TokenKind::Eof,
module_id: ArenaId::new(0),
}
}
#[rstest]
#[case(SyntaxError::EnvNotFound(eof_token(), "VAR".into()), true)]
#[case(SyntaxError::UnexpectedToken(eof_token()), true)]
#[case(SyntaxError::UnexpectedEOFDetected(ArenaId::new(0)), false)]
#[case(SyntaxError::InsufficientTokens(eof_token()), true)]
#[case(SyntaxError::ExpectedClosingParen(eof_token(), None), true)]
#[case(SyntaxError::ExpectedClosingBrace(eof_token(), None), true)]
#[case(SyntaxError::ExpectedClosingBracket(eof_token(), None), true)]
#[case(SyntaxError::InvalidAssignmentTarget(eof_token()), true)]
#[case(SyntaxError::UnknownSelector(selector::UnknownSelector(eof_token())), true)]
#[case(SyntaxError::ParameterWithoutDefaultAfterDefault(eof_token()), true)]
#[case(SyntaxError::MacroParametersCannotHaveDefaults(eof_token()), true)]
#[case(SyntaxError::VariadicParameterMustBeLast(eof_token()), true)]
#[case(SyntaxError::MultipleVariadicParameters(eof_token()), true)]
#[case(SyntaxError::MacroParametersCannotBeVariadic(eof_token()), true)]
#[case(SyntaxError::UnexpectedEOFAfterToken(eof_token()), true)]
#[case(SyntaxError::UnmatchedEnd(eof_token()), true)]
fn test_token_presence(#[case] err: SyntaxError, #[case] has_token: bool) {
assert_eq!(err.token().is_some(), has_token);
}
#[rstest]
#[case(SyntaxError::UnexpectedEOFDetected(ArenaId::new(0)), "Unexpected end of input")]
#[case(
SyntaxError::MacroParametersCannotHaveDefaults(eof_token()),
"Macro parameters cannot have default values"
)]
#[case(
SyntaxError::VariadicParameterMustBeLast(eof_token()),
"Variadic parameter must be the last parameter"
)]
#[case(
SyntaxError::MultipleVariadicParameters(eof_token()),
"Multiple variadic parameters are not allowed"
)]
#[case(
SyntaxError::MacroParametersCannotBeVariadic(eof_token()),
"Macro parameters cannot be variadic"
)]
#[case(
SyntaxError::UnmatchedEnd(eof_token()),
"Unexpected `end` keyword — no open block to close"
)]
fn test_error_display(#[case] err: SyntaxError, #[case] expected: &str) {
assert_eq!(err.to_string(), expected);
}
}