flexi_parse/error/
mod.rs

1//! Types for error reporting.
2//!
3//! This module is built around the [`Error`] type, which is returned when any
4//! parsing function encounters an error. Typically, these will not be created
5//! directly, but instead propagated up from the built-in parsing functions.
6//! The exception to this is [`Lookahead::error`][lookahead-error].
7//!
8//! [lookahead-error]: crate::lookahead::Lookahead::error
9//! [parsebuffer-error]: crate::ParseBuffer::error
10
11use crate::SourceFile;
12use crate::Span;
13
14use std::collections::HashSet;
15use std::fmt;
16use std::sync::Arc;
17
18use strum::EnumDiscriminants;
19
20#[cfg(feature = "ariadne")]
21mod ariadne;
22#[cfg(feature = "ariadne")]
23pub use self::ariadne::Report;
24
25#[derive(Debug, Clone, EnumDiscriminants)]
26#[strum_discriminants(repr(u16))]
27#[repr(u16)]
28pub(crate) enum ErrorKind {
29    Silent,
30    UnknownCharacter(Span),
31    UnterminatedGroup {
32        start: String,
33        span: Span,
34    },
35    UnterminatedChar(Span),
36    LongChar(Span),
37    UnterminatedString(Span),
38    UnexpectedToken {
39        expected: HashSet<String>,
40        span: Span,
41    },
42    EndOfFile(usize),
43    Custom {
44        message: String,
45        span: Span,
46        code: u16,
47    },
48}
49
50impl ErrorKind {
51    fn code(&self) -> u16 {
52        // SAFETY: ErrorKind is `repr(u16)`, making it a `repr(C)` struct with
53        // a `u16` as its first field
54        let discriminant = ErrorKindDiscriminants::from(self) as u16;
55        if let ErrorKind::Custom { code, .. } = self {
56            discriminant + code
57        } else {
58            discriminant
59        }
60    }
61
62    fn start(&self) -> usize {
63        match self {
64            ErrorKind::Silent => panic!("called `start` on `ErrorKind::Silent`"),
65            ErrorKind::Custom { span, .. }
66            | ErrorKind::UnknownCharacter(span)
67            | ErrorKind::UnterminatedGroup { span, .. }
68            | ErrorKind::UnterminatedChar(span)
69            | ErrorKind::LongChar(span)
70            | ErrorKind::UnterminatedString(span)
71            | ErrorKind::UnexpectedToken { span, .. } => span.start,
72            ErrorKind::EndOfFile(n) => *n,
73        }
74    }
75}
76
77fn unexpected_token_message(expected: &HashSet<String>) -> String {
78    if expected.len() == 1 {
79        format!("Expected {}", expected.iter().next().unwrap())
80    } else if expected.len() == 2 {
81        let mut iter = expected.iter();
82        format!(
83            "Expected {} or {}",
84            iter.next().unwrap(),
85            iter.next().unwrap()
86        )
87    } else {
88        let mut message = "Expected one of: ".to_string();
89        for (i, token) in expected.iter().enumerate() {
90            message.push_str(token);
91            if i + 1 < expected.len() {
92                message.push_str(", ");
93            }
94        }
95        message
96    }
97}
98
99#[derive(Debug, Clone)]
100pub(crate) struct SingleError {
101    source: Arc<SourceFile>,
102    kind: ErrorKind,
103}
104
105/// An error or collection of errors raised during parsing.
106///
107/// These errors are intended to be reported using [`ariadne`][ariadne], but an
108/// implementation of [`ToString`] is provided as an alternative if that is not
109/// possible.
110///
111/// [ariadne]: https://docs.rs/ariadne/latest/ariadne/
112#[derive(Debug, Clone)]
113pub struct Error {
114    errors: Vec<SingleError>,
115}
116
117impl Error {
118    pub(crate) fn new(source: Arc<SourceFile>, kind: ErrorKind) -> Error {
119        Error {
120            errors: vec![SingleError { source, kind }],
121        }
122    }
123
124    pub(crate) const fn empty() -> Error {
125        Error { errors: vec![] }
126    }
127
128    pub(crate) fn is_empty(&self) -> bool {
129        self.errors.is_empty()
130    }
131
132    pub(crate) fn eof_to_group(&mut self, span: &Span, start: &str) {
133        for error in &mut self.errors {
134            if let ErrorKind::EndOfFile(_) = error.kind {
135                error.kind = ErrorKind::UnterminatedGroup {
136                    start: start.to_string(),
137                    span: span.clone(),
138                };
139            }
140        }
141    }
142
143    pub(crate) fn group_to_string(&mut self) {
144        for error in &mut self.errors {
145            if let ErrorKind::UnterminatedGroup { span, .. } = &error.kind {
146                let span = span.to_owned();
147                error.kind = ErrorKind::UnterminatedString(span);
148            }
149        }
150    }
151
152    pub(crate) fn string_to_char(&mut self) {
153        for error in &mut self.errors {
154            if let ErrorKind::UnterminatedString(span) = &error.kind {
155                let span = span.to_owned();
156                error.kind = ErrorKind::UnterminatedChar(span);
157            }
158        }
159    }
160
161    /// Appends the given error to this one.
162    pub fn add(&mut self, mut other: Error) {
163        self.errors.append(&mut other.errors);
164    }
165
166    /// Consumes `self` and `other`, returning a new error with the contents of
167    /// both.
168    #[must_use]
169    pub fn with(mut self, other: Error) -> Self {
170        self.add(other);
171        self
172    }
173}
174
175impl fmt::Display for Error {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        for error in &self.errors {
178            match &error.kind {
179                ErrorKind::Silent => {}
180                ErrorKind::Custom { message, span, .. } => {
181                    writeln!(f, "[E{:02}] Error: {}", error.kind.code(), message)?;
182                    let (line, col) = span.start_location();
183                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
184                }
185                ErrorKind::UnknownCharacter(span) => {
186                    writeln!(
187                        f,
188                        "[E{:02}] Error: Unrecognised character",
189                        error.kind.code()
190                    )?;
191                    let (line, col) = span.start_location();
192                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
193                }
194                ErrorKind::UnterminatedGroup { start, span } => {
195                    writeln!(
196                        f,
197                        "[E{:02}] Error: Unmatched '{}'",
198                        error.kind.code(),
199                        start
200                    )?;
201                    let (line, col) = span.start_location();
202                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
203                }
204                ErrorKind::UnterminatedChar(span) => {
205                    writeln!(
206                        f,
207                        "[E{:02}] Error: Unterminated character literal",
208                        error.kind.code()
209                    )?;
210                    let (line, col) = span.start_location();
211                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
212                }
213                ErrorKind::LongChar(span) => {
214                    writeln!(
215                        f,
216                        "[E{:02}] Error: Character literals must be exactly one character long",
217                        error.kind.code()
218                    )?;
219                    let (line, col) = span.start_location();
220                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
221                }
222                ErrorKind::UnterminatedString(span) => {
223                    writeln!(
224                        f,
225                        "[E{:02}] Error: Unterminated string literal",
226                        error.kind.code()
227                    )?;
228                    let (line, col) = span.start_location();
229                    write!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
230                }
231                ErrorKind::UnexpectedToken { expected, span } => {
232                    writeln!(f, "[E{:02}] Error: Unexpected token", error.kind.code())?;
233                    let (line, col) = span.start_location();
234                    writeln!(f, "[{}:{}:{}]", error.source.id(), line, col)?;
235                    write!(f, "{}", unexpected_token_message(expected))?;
236                }
237                ErrorKind::EndOfFile(_) => write!(f, "Unexpected end of file while parsing")?,
238            }
239        }
240
241        Ok(())
242    }
243}