Skip to main content

litedoc_core/
error.rs

1use crate::span::Span;
2use std::fmt;
3
4/// Error kinds for categorizing parse errors.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ParseErrorKind {
7    /// Unexpected end of input
8    UnexpectedEof,
9    /// Unclosed delimiter (code block, fenced block, etc.)
10    UnclosedDelimiter,
11    /// Invalid syntax that couldn't be parsed
12    InvalidSyntax,
13    /// Unknown directive or block type
14    UnknownDirective,
15    /// Malformed metadata
16    InvalidMetadata,
17    /// Generic parse error
18    Other,
19}
20
21/// A parse error with location and recovery information.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ParseError {
24    /// Human-readable error message
25    pub message: String,
26    /// Source location where the error occurred
27    pub span: Option<Span>,
28    /// Error categorization
29    pub kind: ParseErrorKind,
30    /// Whether parsing can continue after this error
31    pub recoverable: bool,
32}
33
34impl ParseError {
35    /// Create a new parse error.
36    pub fn new(message: impl Into<String>, span: Option<Span>) -> Self {
37        Self {
38            message: message.into(),
39            span,
40            kind: ParseErrorKind::Other,
41            recoverable: true,
42        }
43    }
44
45    /// Create an error for unexpected end of input.
46    pub fn unexpected_eof(span: Option<Span>) -> Self {
47        Self {
48            message: "unexpected end of input".to_string(),
49            span,
50            kind: ParseErrorKind::UnexpectedEof,
51            recoverable: false,
52        }
53    }
54
55    /// Create an error for unclosed delimiters.
56    pub fn unclosed_delimiter(delimiter: &str, span: Option<Span>) -> Self {
57        Self {
58            message: format!("unclosed {}", delimiter),
59            span,
60            kind: ParseErrorKind::UnclosedDelimiter,
61            recoverable: true,
62        }
63    }
64
65    /// Create an error for invalid syntax.
66    pub fn invalid_syntax(context: &str, span: Option<Span>) -> Self {
67        Self {
68            message: format!("invalid syntax in {}", context),
69            span,
70            kind: ParseErrorKind::InvalidSyntax,
71            recoverable: true,
72        }
73    }
74
75    /// Create an error for unknown directives.
76    pub fn unknown_directive(directive: &str, span: Option<Span>) -> Self {
77        Self {
78            message: format!("unknown directive: {}", directive),
79            span,
80            kind: ParseErrorKind::UnknownDirective,
81            recoverable: true,
82        }
83    }
84
85    /// Set the error kind.
86    pub fn with_kind(mut self, kind: ParseErrorKind) -> Self {
87        self.kind = kind;
88        self
89    }
90
91    /// Mark this error as non-recoverable.
92    pub fn non_recoverable(mut self) -> Self {
93        self.recoverable = false;
94        self
95    }
96}
97
98impl fmt::Display for ParseError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "{}", self.message)?;
101        if let Some(span) = self.span {
102            write!(f, " at bytes {}..{}", span.start, span.end)?;
103        }
104        Ok(())
105    }
106}
107
108impl std::error::Error for ParseError {}
109
110/// A collection of parse errors encountered during parsing.
111#[derive(Debug, Clone, Default)]
112pub struct ParseErrors {
113    errors: Vec<ParseError>,
114}
115
116impl ParseErrors {
117    /// Create an empty error collection.
118    pub fn new() -> Self {
119        Self { errors: Vec::new() }
120    }
121
122    /// Add an error to the collection.
123    pub fn push(&mut self, error: ParseError) {
124        self.errors.push(error);
125    }
126
127    /// Check if any errors were collected.
128    pub fn is_empty(&self) -> bool {
129        self.errors.is_empty()
130    }
131
132    /// Get the number of errors.
133    pub fn len(&self) -> usize {
134        self.errors.len()
135    }
136
137    /// Iterate over the errors.
138    pub fn iter(&self) -> impl Iterator<Item = &ParseError> {
139        self.errors.iter()
140    }
141
142    /// Check if any non-recoverable errors exist.
143    pub fn has_fatal(&self) -> bool {
144        self.errors.iter().any(|e| !e.recoverable)
145    }
146}
147
148impl IntoIterator for ParseErrors {
149    type Item = ParseError;
150    type IntoIter = std::vec::IntoIter<ParseError>;
151
152    fn into_iter(self) -> Self::IntoIter {
153        self.errors.into_iter()
154    }
155}