rustledger_parser/
error.rs

1//! Parse error types.
2
3use crate::Span;
4use std::fmt;
5
6/// A parse error with location information.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ParseError {
9    /// The kind of error.
10    pub kind: ParseErrorKind,
11    /// The span where the error occurred.
12    pub span: Span,
13    /// Optional context message.
14    pub context: Option<String>,
15}
16
17impl ParseError {
18    /// Create a new parse error.
19    #[must_use]
20    pub const fn new(kind: ParseErrorKind, span: Span) -> Self {
21        Self {
22            kind,
23            span,
24            context: None,
25        }
26    }
27
28    /// Add context to this error.
29    #[must_use]
30    pub fn with_context(mut self, context: impl Into<String>) -> Self {
31        self.context = Some(context.into());
32        self
33    }
34
35    /// Get the span of this error.
36    #[must_use]
37    pub const fn span(&self) -> (usize, usize) {
38        (self.span.start, self.span.end)
39    }
40
41    /// Get a numeric code for the error kind.
42    #[must_use]
43    pub const fn kind_code(&self) -> u32 {
44        match &self.kind {
45            ParseErrorKind::UnexpectedChar(_) => 1,
46            ParseErrorKind::UnexpectedEof => 2,
47            ParseErrorKind::Expected(_) => 3,
48            ParseErrorKind::InvalidDate(_) => 4,
49            ParseErrorKind::InvalidNumber(_) => 5,
50            ParseErrorKind::InvalidAccount(_) => 6,
51            ParseErrorKind::InvalidCurrency(_) => 7,
52            ParseErrorKind::UnclosedString => 8,
53            ParseErrorKind::InvalidEscape(_) => 9,
54            ParseErrorKind::MissingField(_) => 10,
55            ParseErrorKind::IndentationError => 11,
56            ParseErrorKind::SyntaxError(_) => 12,
57            ParseErrorKind::MissingNewline => 13,
58        }
59    }
60
61    /// Get the error message.
62    #[must_use]
63    pub fn message(&self) -> String {
64        format!("{}", self.kind)
65    }
66
67    /// Get a short label for the error.
68    #[must_use]
69    pub const fn label(&self) -> &str {
70        match &self.kind {
71            ParseErrorKind::UnexpectedChar(_) => "unexpected character",
72            ParseErrorKind::UnexpectedEof => "unexpected end of file",
73            ParseErrorKind::Expected(_) => "expected different token",
74            ParseErrorKind::InvalidDate(_) => "invalid date",
75            ParseErrorKind::InvalidNumber(_) => "invalid number",
76            ParseErrorKind::InvalidAccount(_) => "invalid account",
77            ParseErrorKind::InvalidCurrency(_) => "invalid currency",
78            ParseErrorKind::UnclosedString => "unclosed string",
79            ParseErrorKind::InvalidEscape(_) => "invalid escape",
80            ParseErrorKind::MissingField(_) => "missing field",
81            ParseErrorKind::IndentationError => "indentation error",
82            ParseErrorKind::SyntaxError(_) => "parse error",
83            ParseErrorKind::MissingNewline => "syntax error",
84        }
85    }
86}
87
88impl fmt::Display for ParseError {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{}", self.kind)?;
91        if let Some(ctx) = &self.context {
92            write!(f, " ({ctx})")?;
93        }
94        Ok(())
95    }
96}
97
98impl std::error::Error for ParseError {}
99
100/// Kinds of parse errors.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum ParseErrorKind {
103    /// Unexpected character in input.
104    UnexpectedChar(char),
105    /// Unexpected end of file.
106    UnexpectedEof,
107    /// Expected a specific token.
108    Expected(String),
109    /// Invalid date format.
110    InvalidDate(String),
111    /// Invalid number format.
112    InvalidNumber(String),
113    /// Invalid account name.
114    InvalidAccount(String),
115    /// Invalid currency code.
116    InvalidCurrency(String),
117    /// Unclosed string literal.
118    UnclosedString,
119    /// Invalid escape sequence in string.
120    InvalidEscape(char),
121    /// Missing required field.
122    MissingField(String),
123    /// Indentation error.
124    IndentationError,
125    /// Generic syntax error.
126    SyntaxError(String),
127    /// Missing final newline.
128    MissingNewline,
129}
130
131impl fmt::Display for ParseErrorKind {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            Self::UnexpectedChar(c) => write!(f, "syntax error: unexpected '{c}'"),
135            Self::UnexpectedEof => write!(f, "unexpected end of file"),
136            Self::Expected(what) => write!(f, "expected {what}"),
137            Self::InvalidDate(s) => write!(f, "invalid date '{s}'"),
138            Self::InvalidNumber(s) => write!(f, "invalid number '{s}'"),
139            Self::InvalidAccount(s) => write!(f, "invalid account '{s}'"),
140            Self::InvalidCurrency(s) => write!(f, "invalid currency '{s}'"),
141            Self::UnclosedString => write!(f, "unclosed string literal"),
142            Self::InvalidEscape(c) => write!(f, "invalid escape sequence '\\{c}'"),
143            Self::MissingField(field) => write!(f, "missing required field: {field}"),
144            Self::IndentationError => write!(f, "indentation error"),
145            Self::SyntaxError(msg) => write!(f, "parse error: {msg}"),
146            Self::MissingNewline => write!(f, "syntax error: missing final newline"),
147        }
148    }
149}