use nom::error::{ErrorKind, ParseError};
use super::Span;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
InvalidTagName,
UnclosedBracket,
UnclosedParenthesis,
UnclosedQuote,
ExpectedAttributeValue,
InvalidAttributeKey,
DuplicateId,
DuplicateClass,
MixedIndentation,
DuplicateAttribute,
VoidElementContent,
EmptyAttributes,
}
impl ErrorCode {
pub const ALL: &[ErrorCode] = &[
Self::InvalidTagName,
Self::UnclosedBracket,
Self::UnclosedParenthesis,
Self::UnclosedQuote,
Self::ExpectedAttributeValue,
Self::InvalidAttributeKey,
Self::DuplicateId,
Self::DuplicateClass,
Self::MixedIndentation,
Self::DuplicateAttribute,
Self::VoidElementContent,
Self::EmptyAttributes,
];
pub fn code(&self) -> &'static str {
match self {
Self::InvalidTagName => "E001",
Self::UnclosedBracket => "E002",
Self::UnclosedParenthesis => "E003",
Self::UnclosedQuote => "E004",
Self::ExpectedAttributeValue => "E005",
Self::InvalidAttributeKey => "E006",
Self::DuplicateId => "W001",
Self::DuplicateClass => "W002",
Self::MixedIndentation => "W003",
Self::DuplicateAttribute => "W004",
Self::VoidElementContent => "W005",
Self::EmptyAttributes => "W006",
}
}
pub fn message(&self) -> &'static str {
match self {
Self::InvalidTagName => "Tag name must start with an ASCII letter",
Self::UnclosedBracket => "Unclosed bracket",
Self::UnclosedParenthesis => "Unclosed parenthesis",
Self::UnclosedQuote => "Unclosed quote in attribute value",
Self::ExpectedAttributeValue => "Expected quoted attribute value",
Self::InvalidAttributeKey => "Invalid attribute key",
Self::DuplicateId => "Duplicate attribute 'id' is not allowed",
Self::DuplicateClass => "Duplicate class",
Self::MixedIndentation => "Mixed tabs and spaces in indentation",
Self::DuplicateAttribute => "Duplicate attribute",
Self::VoidElementContent => "Void element cannot have content",
Self::EmptyAttributes => "Empty attribute parentheses",
}
}
pub fn severity(&self) -> Severity {
match self {
Self::InvalidTagName => Severity::Error,
Self::UnclosedBracket => Severity::Error,
Self::UnclosedParenthesis => Severity::Error,
Self::UnclosedQuote => Severity::Error,
Self::ExpectedAttributeValue => Severity::Error,
Self::InvalidAttributeKey => Severity::Error,
Self::DuplicateId => Severity::Warning,
Self::DuplicateClass => Severity::Warning,
Self::MixedIndentation => Severity::Warning,
Self::DuplicateAttribute => Severity::Warning,
Self::VoidElementContent => Severity::Warning,
Self::EmptyAttributes => Severity::Warning,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HsmlError<'a> {
pub span: Span<'a>,
pub kind: ErrorKind,
pub message: Option<String>,
pub error_code: Option<ErrorCode>,
pub severity: Severity,
}
impl<'a> HsmlError<'a> {
pub fn from_kind(span: Span<'a>, kind: ErrorKind) -> Self {
Self {
span,
kind,
message: None,
error_code: None,
severity: Severity::Error,
}
}
pub fn new(span: Span<'a>, message: impl Into<String>) -> Self {
Self {
span,
kind: ErrorKind::Fail,
message: Some(message.into()),
error_code: None,
severity: Severity::Error,
}
}
pub fn from_code(span: Span<'a>, error_code: ErrorCode) -> Self {
Self {
span,
kind: ErrorKind::Fail,
message: Some(error_code.message().to_string()),
error_code: Some(error_code),
severity: error_code.severity(),
}
}
pub fn err(span: Span<'a>, kind: ErrorKind) -> nom::Err<Self> {
nom::Err::Error(Self::from_kind(span, kind))
}
pub fn fail(span: Span<'a>, kind: ErrorKind) -> nom::Err<Self> {
nom::Err::Failure(Self::from_kind(span, kind))
}
pub fn fail_msg(span: Span<'a>, message: impl Into<String>) -> nom::Err<Self> {
nom::Err::Failure(Self::new(span, message))
}
pub fn fail_code(span: Span<'a>, error_code: ErrorCode) -> nom::Err<Self> {
nom::Err::Failure(Self::from_code(span, error_code))
}
pub fn code(&self) -> Option<&'static str> {
self.error_code.map(|c| c.code())
}
pub fn line(&self) -> u32 {
self.span.location_line()
}
pub fn column(&self) -> usize {
self.span.get_column()
}
}
impl<'a> ParseError<Span<'a>> for HsmlError<'a> {
fn from_error_kind(input: Span<'a>, kind: ErrorKind) -> Self {
Self {
span: input,
kind,
message: None,
error_code: None,
severity: Severity::Error,
}
}
fn append(_input: Span<'a>, _kind: ErrorKind, other: Self) -> Self {
other
}
}
impl<'a> std::fmt::Display for HsmlError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(ref msg) = self.message {
if let Some(code) = self.code() {
write!(
f,
"[{}] {} at line {}, column {}",
code,
msg,
self.line(),
self.column()
)
} else {
write!(
f,
"{} at line {}, column {}",
msg,
self.line(),
self.column()
)
}
} else {
write!(
f,
"parse error ({:?}) at line {}, column {}",
self.kind,
self.line(),
self.column()
)
}
}
}
#[cfg(test)]
mod tests;