logix_type/
error.rs

1pub use crate::span::SourceSpan;
2use core::fmt;
3use owo_colors::OwoColorize;
4
5use thiserror::Error;
6
7use crate::{
8    token::{StrTag, StrTagSuffix, Token},
9    types::ShortStr,
10};
11
12pub type Result<T, E = ParseError> = std::result::Result<T, E>;
13
14#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
15pub enum TokenError {
16    #[error("invalid utf-8 sequence")]
17    LitStrNotUtf8,
18    #[error("unexpected character {0:?}")]
19    UnexpectedChar(char),
20    #[error("unexpected end of file, expected `*/`")]
21    MissingCommentTerminator,
22    #[error("unknown string tag `{0}`")]
23    UnknownStrTag(ShortStr),
24    #[error("unexpected end of the string, expected `\"`")]
25    MissingStringTerminator,
26    #[error("unexpected end of {tag} string, expected {suffix}")]
27    MissingTaggedStringTerminator { tag: StrTag, suffix: StrTagSuffix },
28}
29
30#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
31pub enum EscStrError {
32    #[error("got truncated hex escape code")]
33    TruncatedHex,
34    #[error("got invalid hex escape code")]
35    InvalidHex,
36    #[error("got invalid unicode hex escape code")]
37    InvalidUnicodeHex,
38    #[error("the code point U+{0:x} is invalid")]
39    InvalidUnicodePoint(u32),
40    #[error("got invalid unicode escape, expected `{{`")]
41    InvalidUnicodeMissingStartBrace,
42    #[error("got invalid unicode escape, expected `}}`")]
43    InvalidUnicodeMissingEndBrace,
44    #[error("got invalid escape character {0:?}")]
45    InvalidEscapeChar(char),
46}
47
48#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
49pub enum IncludeError {
50    #[error("invalid utf-8 sequence")]
51    NotUtf8,
52
53    #[error(transparent)]
54    Open(logix_vfs::Error),
55}
56
57#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
58pub enum PathError {
59    #[error("expected an absolute path")]
60    NotAbsolute,
61
62    #[error("expected a relative path")]
63    NotRelative,
64
65    #[error("the specified path is empty")]
66    EmptyPath,
67
68    #[error("expected either a file or directory name")]
69    NotName,
70
71    #[error("the path contains the invalid character {0:?}")]
72    InvalidChar(char),
73
74    #[error("expected file name or absolute path")]
75    NotFullOrNameOnly,
76
77    #[error("expected relative path when joining")]
78    JoinAbsolute,
79}
80
81#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
82pub enum Wanted {
83    Token(Token<'static>),
84    Tokens(&'static [Token<'static>]),
85    LitStr,
86    FullPath,
87    RelPath,
88    ExecutablePath,
89    NameOnlyPath,
90    ValidPath,
91    LitNum(&'static str),
92    Ident,
93    ItemOrEnd,
94    ItemDelim,
95    Item,
96    /// An error occured while parsing a delimited list of items, but not able to determine what was expected
97    ItemUnknown,
98}
99
100impl fmt::Display for Wanted {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        match self {
103            Self::Token(token) => token.write_token_display_name(f),
104            Self::Tokens([token]) => token.write_token_display_name(f),
105            Self::Tokens([a, b]) => {
106                write!(f, "either ")?;
107                a.write_token_display_name(f)?;
108                write!(f, " or ")?;
109                b.write_token_display_name(f)
110            }
111            Self::Tokens(tokens) => {
112                let (first, tokens) = tokens.split_first().unwrap();
113                let (last, tokens) = tokens.split_last().unwrap();
114
115                write!(f, "one of ")?;
116                first.write_token_display_name(f)?;
117                for token in tokens {
118                    write!(f, ", ")?;
119                    token.write_token_display_name(f)?;
120                }
121                write!(f, ", or ")?;
122                last.write_token_display_name(f)
123            }
124            Self::LitStr => write!(f, "string"),
125            Self::FullPath => write!(f, "full path"),
126            Self::RelPath => write!(f, "relative path"),
127            Self::ExecutablePath => write!(f, "executable name or full path"),
128            Self::NameOnlyPath => write!(f, "file or directory name"),
129            Self::ValidPath => write!(f, "path"),
130            Self::LitNum(name) => write!(f, "{name}"),
131            Self::Ident => write!(f, "identifier"),
132            Self::ItemOrEnd => write!(f, "item or end"),
133            Self::ItemDelim => write!(f, "delimiter"),
134            Self::Item => write!(f, "item"),
135            Self::ItemUnknown => write!(f, "item, delimiter or end"),
136        }
137    }
138}
139
140#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub enum ParseError {
142    #[error(transparent)]
143    FsError(#[from] logix_vfs::Error),
144
145    #[error(transparent)]
146    Warning(Warn),
147
148    #[error("Missing struct member `{member}` while parsing `{type_name}` in {span}")]
149    MissingStructMember {
150        span: SourceSpan,
151        type_name: &'static str,
152        member: &'static str,
153    },
154
155    #[error("Duplicate struct member `{member}` while parsing `{type_name}` in {span}")]
156    DuplicateStructMember {
157        span: SourceSpan,
158        type_name: &'static str,
159        member: &'static str,
160    },
161
162    #[error("Unexpected {got_token} while parsing `{while_parsing}`, expected {wanted} in {span}")]
163    UnexpectedToken {
164        span: SourceSpan,
165        while_parsing: &'static str,
166        got_token: &'static str,
167        wanted: Wanted,
168    },
169
170    #[error("Failed to parse string, {error} in {span}")]
171    StrEscError {
172        span: SourceSpan,
173        error: EscStrError,
174    },
175
176    #[error("Failed to parse input, {error} in {span}")]
177    TokenError { span: SourceSpan, error: TokenError },
178
179    #[error("Failed to include file as `{while_parsing}`, {error} in {span}")]
180    IncludeError {
181        span: SourceSpan,
182        while_parsing: &'static str,
183        error: IncludeError,
184    },
185
186    #[error("Failed to parse path, {error} in {span}")]
187    PathError { span: SourceSpan, error: PathError },
188}
189
190impl fmt::Debug for ParseError {
191    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192        writeln!(f)?;
193        match self {
194            Self::FsError(e) => writeln!(f, "{}{}", "error: ".bright_red().bold(), e.bold()),
195            Self::Warning(Warn::DuplicateMapEntry { span, key }) => write_error(
196                f,
197                format_args!("Duplicate entry `{key}` while parsing `Map`"),
198                span,
199                format_args!("overwrites the previous entry"),
200            ),
201            Self::MissingStructMember {
202                span,
203                type_name,
204                member,
205            } => write_error(
206                f,
207                format_args!("Missing struct member while parsing `{type_name}`"),
208                span,
209                format_args!("expected `{member}`"),
210            ),
211            Self::DuplicateStructMember {
212                span,
213                type_name,
214                member,
215            } => write_error(
216                f,
217                format_args!("Duplicate struct member while parsing `{type_name}`"),
218                span,
219                format_args!("unexpected `{member}`"),
220            ),
221            Self::UnexpectedToken {
222                span,
223                while_parsing,
224                got_token,
225                wanted,
226            } => write_error(
227                f,
228                format_args!("Unexpected {got_token} while parsing `{while_parsing}`"),
229                span,
230                format_args!("expected {wanted}"),
231            ),
232            Self::StrEscError { span, error } => {
233                write_error(f, "Failed to parse escaped string", span, error)
234            }
235            Self::TokenError { span, error } => {
236                write_error(f, "Failed to parse input", span, error)
237            }
238            Self::IncludeError {
239                span,
240                while_parsing,
241                error,
242            } => write_error(
243                f,
244                format_args!("Failed to include file as `{while_parsing}`"),
245                span,
246                error,
247            ),
248            Self::PathError { span, error } => write_error(f, "Failed to parse path", span, error),
249        }
250    }
251}
252
253fn write_error(
254    f: &mut impl fmt::Write,
255    message: impl fmt::Display,
256    span: &SourceSpan,
257    expected: impl fmt::Display,
258) -> fmt::Result {
259    let context = 1;
260    let ln_width = span.calc_ln_width(context);
261    writeln!(f, "{}{}", "error: ".bright_red().bold(), message.bold())?;
262
263    writeln!(
264        f,
265        "   {} {}:{}:{}",
266        "--->".bright_blue().bold(),
267        span.path().display(),
268        span.line(),
269        span.col(),
270    )?;
271    writeln!(f, "{:>ln_width$} {}", "", "|".bright_blue().bold(),)?;
272
273    for (ln, span, line) in span.lines(context) {
274        writeln!(
275            f,
276            "{:>ln_width$} {} {}",
277            ln.bright_blue().bold(),
278            "|".bright_blue().bold(),
279            line.trim_end(),
280        )?;
281        if let Some(span) = span {
282            let col = span.start;
283            writeln!(
284                f,
285                "{:>ln_width$} {} {:>col$}{} {}",
286                "",
287                "|".bright_blue().bold(),
288                "",
289                "^".repeat(
290                    line.get(span)
291                        .into_iter()
292                        .flat_map(|s| s.chars())
293                        .count()
294                        .max(1)
295                )
296                .bright_red()
297                .bold(),
298                expected.bright_red().bold(),
299            )?;
300        }
301    }
302
303    Ok(())
304}
305
306#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
307pub enum Warn {
308    #[error(
309        "Duplicate entry `{key}` while parsing `Map`, overwrites the previous entry in {span}"
310    )]
311    DuplicateMapEntry { span: SourceSpan, key: ShortStr },
312}
313
314#[cfg(test)]
315mod tests {
316    use std::{error::Error, path::PathBuf};
317
318    use super::*;
319
320    #[test]
321    fn coverage_hacks() {
322        assert_eq!(
323            format!("{:?}", EscStrError::InvalidUnicodeHex),
324            "InvalidUnicodeHex",
325        );
326        assert_eq!(format!("{:?}", TokenError::LitStrNotUtf8), "LitStrNotUtf8",);
327        assert_eq!(format!("{:?}", Wanted::LitStr), "LitStr",);
328        assert_eq!(
329            format!(
330                "{:?}",
331                Warn::DuplicateMapEntry {
332                    span: SourceSpan::empty(),
333                    key: "test".into()
334                }
335            ),
336            "DuplicateMapEntry { span: SourceSpan { file: CachedFile(\"\"), pos: 0, range: SingleLine { line: 0, col: 0..0 } }, key: \"test\" }",
337        );
338        assert_eq!(
339            ParseError::FsError(logix_vfs::Error::NotFound {
340                path: PathBuf::new()
341            })
342            .source()
343            .map(|e| e.to_string()),
344            None,
345        );
346    }
347}