arithmetic_parser/
error.rs

1//! Error handling.
2
3use nom::{
4    error::{ContextError, ErrorKind as NomErrorKind, FromExternalError, ParseError},
5    Slice,
6};
7
8use core::fmt;
9
10use crate::{
11    BinaryOp, ExprType, InputSpan, LocatedSpan, LvalueType, Op, Spanned, StatementType, StripCode,
12    UnaryOp,
13};
14
15/// Parsing context.
16// TODO: Add more fine-grained contexts.
17#[derive(Debug, Clone, Copy, PartialEq)]
18#[non_exhaustive]
19pub enum Context {
20    /// Variable name.
21    Var,
22    /// Function invocation.
23    Fun,
24    /// Arithmetic expression.
25    Expr,
26    /// Comment.
27    Comment,
28}
29
30impl fmt::Display for Context {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        formatter.write_str(match self {
33            Self::Var => "variable",
34            Self::Fun => "function call",
35            Self::Expr => "arithmetic expression",
36            Self::Comment => "comment",
37        })
38    }
39}
40
41impl Context {
42    pub(crate) fn new(s: &str) -> Self {
43        match s {
44            "var" => Self::Var,
45            "fn" => Self::Fun,
46            "expr" => Self::Expr,
47            "comment" => Self::Comment,
48            _ => unreachable!(),
49        }
50    }
51
52    pub(crate) fn to_str(self) -> &'static str {
53        match self {
54            Self::Var => "var",
55            Self::Fun => "fn",
56            Self::Expr => "expr",
57            Self::Comment => "comment",
58        }
59    }
60}
61
62/// Parsing error kind.
63#[derive(Debug)]
64#[non_exhaustive]
65pub enum ErrorKind {
66    /// Input is not in ASCII.
67    NonAsciiInput,
68    /// Error parsing literal.
69    Literal(anyhow::Error),
70    /// Literal is used where a name is expected, e.g., as a function identifier.
71    ///
72    /// An example of input triggering this error is `1(2, x)`; `1` is used as the function
73    /// identifier.
74    LiteralName,
75    /// Error parsing type annotation.
76    Type(anyhow::Error),
77    /// Unary or binary operation switched off in the parser features.
78    UnsupportedOp(Op),
79    /// No rules where expecting this character.
80    UnexpectedChar {
81        /// Parsing context.
82        context: Option<Context>,
83    },
84    /// Unexpected expression end.
85    UnexpectedTerm {
86        /// Parsing context.
87        context: Option<Context>,
88    },
89    /// Leftover symbols after parsing.
90    Leftovers,
91    /// Input is incomplete.
92    Incomplete,
93    /// Unfinished comment.
94    UnfinishedComment,
95    /// Chained comparison, such as `1 < 2 < 3`.
96    ChainedComparison,
97    /// Other parsing error.
98    Other {
99        /// `nom`-defined error kind.
100        kind: NomErrorKind,
101        /// Parsing context.
102        context: Option<Context>,
103    },
104}
105
106impl fmt::Display for ErrorKind {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::NonAsciiInput => formatter.write_str("Non-ASCII inputs are not supported"),
110            Self::Literal(e) => write!(formatter, "Invalid literal: {}", e),
111            Self::LiteralName => formatter.write_str("Literal used in place of an identifier"),
112
113            Self::Type(e) => write!(formatter, "Invalid type annotation: {}", e),
114
115            Self::UnsupportedOp(op) => write!(
116                formatter,
117                "Encountered operation switched off in the parser features: {}",
118                op
119            ),
120
121            Self::UnexpectedChar { context: Some(ctx) } => {
122                write!(formatter, "Unexpected character in {}", ctx)
123            }
124            Self::UnexpectedChar { .. } => formatter.write_str("Unexpected character"),
125
126            Self::UnexpectedTerm { context: Some(ctx) } => write!(formatter, "Unfinished {}", ctx),
127            Self::UnexpectedTerm { .. } => formatter.write_str("Unfinished expression"),
128
129            Self::Leftovers => formatter.write_str("Uninterpreted characters after parsing"),
130            Self::Incomplete => formatter.write_str("Incomplete input"),
131            Self::UnfinishedComment => formatter.write_str("Unfinished comment"),
132            Self::ChainedComparison => formatter.write_str("Chained comparisons"),
133            Self::Other { .. } => write!(formatter, "Cannot parse sequence"),
134        }
135    }
136}
137
138impl ErrorKind {
139    /// Creates a `Literal` variant with the specified error.
140    pub fn literal<T: Into<anyhow::Error>>(error: T) -> Self {
141        Self::Literal(error.into())
142    }
143
144    fn context_mut(&mut self) -> Option<&mut Option<Context>> {
145        match self {
146            Self::UnexpectedChar { context }
147            | Self::UnexpectedTerm { context }
148            | Self::Other { context, .. } => Some(context),
149            _ => None,
150        }
151    }
152
153    /// Returns optional error context.
154    pub fn context(&self) -> Option<Context> {
155        match self {
156            Self::UnexpectedChar { context }
157            | Self::UnexpectedTerm { context }
158            | Self::Other { context, .. } => *context,
159            _ => None,
160        }
161    }
162
163    /// Returns `true` if this is `Incomplete`.
164    pub fn is_incomplete(&self) -> bool {
165        matches!(self, Self::Incomplete)
166    }
167
168    #[doc(hidden)]
169    pub fn with_span<'a, T>(self, span: &Spanned<'a, T>) -> Error<'a> {
170        Error {
171            inner: span.copy_with_extra(self),
172        }
173    }
174}
175
176#[cfg(feature = "std")]
177impl std::error::Error for ErrorKind {
178    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
179        match self {
180            Self::Literal(err) | Self::Type(err) => Some(err.as_ref()),
181            _ => None,
182        }
183    }
184}
185
186/// Parsing error with a generic code span.
187///
188/// Two primary cases of the `Span` type param are `&str` (for original errors produced by
189/// the parser) and `usize` (for *stripped* errors that have a static lifetime).
190#[derive(Debug)]
191pub struct SpannedError<Span> {
192    inner: LocatedSpan<Span, ErrorKind>,
193}
194
195/// Error with code span available as a string reference.
196pub type Error<'a> = SpannedError<&'a str>;
197
198impl<'a> Error<'a> {
199    pub(crate) fn new(span: InputSpan<'a>, kind: ErrorKind) -> Self {
200        Self {
201            inner: Spanned::new(span, kind),
202        }
203    }
204}
205
206impl<Span> SpannedError<Span> {
207    /// Returns the kind of this error.
208    pub fn kind(&self) -> &ErrorKind {
209        &self.inner.extra
210    }
211}
212
213impl<Span: Copy> SpannedError<Span> {
214    /// Returns the span of this error.
215    pub fn span(&self) -> LocatedSpan<Span> {
216        self.inner.with_no_extra()
217    }
218}
219
220impl<Span> fmt::Display for SpannedError<Span> {
221    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
222        write!(
223            formatter,
224            "{}:{}: {}",
225            self.inner.location_line(),
226            self.inner.get_column(),
227            self.inner.extra
228        )
229    }
230}
231
232#[cfg(feature = "std")]
233impl<Span: fmt::Debug> std::error::Error for SpannedError<Span> {
234    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
235        std::error::Error::source(&self.inner.extra)
236    }
237}
238
239impl StripCode for Error<'_> {
240    type Stripped = SpannedError<usize>;
241
242    fn strip_code(self) -> Self::Stripped {
243        SpannedError {
244            inner: self.inner.map_fragment(str::len),
245        }
246    }
247}
248
249impl<'a> ParseError<InputSpan<'a>> for Error<'a> {
250    fn from_error_kind(mut input: InputSpan<'a>, kind: NomErrorKind) -> Self {
251        if kind == NomErrorKind::Char && !input.fragment().is_empty() {
252            // Truncate the error span to the first ineligible char.
253            input = input.slice(..1);
254        }
255
256        let error_kind = if kind == NomErrorKind::Char {
257            if input.fragment().is_empty() {
258                ErrorKind::UnexpectedTerm { context: None }
259            } else {
260                ErrorKind::UnexpectedChar { context: None }
261            }
262        } else {
263            ErrorKind::Other {
264                kind,
265                context: None,
266            }
267        };
268
269        Error::new(input, error_kind)
270    }
271
272    fn append(_: InputSpan<'a>, _: NomErrorKind, other: Self) -> Self {
273        other
274    }
275}
276
277impl<'a> ContextError<InputSpan<'a>> for Error<'a> {
278    fn add_context(input: InputSpan<'a>, ctx: &'static str, mut target: Self) -> Self {
279        let ctx = Context::new(ctx);
280        if ctx == Context::Comment {
281            target.inner.extra = ErrorKind::UnfinishedComment;
282        }
283
284        if input.location_offset() < target.inner.location_offset() {
285            if let Some(context) = target.inner.extra.context_mut() {
286                *context = Some(ctx);
287            }
288        }
289        target
290    }
291}
292
293impl<'a> FromExternalError<InputSpan<'a>, ErrorKind> for Error<'a> {
294    fn from_external_error(input: InputSpan<'a>, _: NomErrorKind, err: ErrorKind) -> Self {
295        Self::new(input, err)
296    }
297}
298
299/// Description of a construct not supported by a certain module (e.g., interpreter
300/// or type inference).
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
302#[non_exhaustive]
303pub enum UnsupportedType {
304    /// Unary operation.
305    UnaryOp(UnaryOp),
306    /// Binary operation.
307    BinaryOp(BinaryOp),
308    /// Expression.
309    Expr(ExprType),
310    /// Statement.
311    Statement(StatementType),
312    /// Lvalue.
313    Lvalue(LvalueType),
314}
315
316impl fmt::Display for UnsupportedType {
317    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
318        match self {
319            Self::UnaryOp(op) => write!(formatter, "unary op: {}", op),
320            Self::BinaryOp(op) => write!(formatter, "binary op: {}", op),
321            Self::Expr(expr) => write!(formatter, "expression: {}", expr),
322            Self::Statement(statement) => write!(formatter, "statement: {}", statement),
323            Self::Lvalue(lvalue) => write!(formatter, "lvalue: {}", lvalue),
324        }
325    }
326}
327
328impl From<UnaryOp> for UnsupportedType {
329    fn from(value: UnaryOp) -> Self {
330        Self::UnaryOp(value)
331    }
332}
333
334impl From<BinaryOp> for UnsupportedType {
335    fn from(value: BinaryOp) -> Self {
336        Self::BinaryOp(value)
337    }
338}
339
340impl From<ExprType> for UnsupportedType {
341    fn from(value: ExprType) -> Self {
342        Self::Expr(value)
343    }
344}
345
346impl From<StatementType> for UnsupportedType {
347    fn from(value: StatementType) -> Self {
348        Self::Statement(value)
349    }
350}
351
352impl From<LvalueType> for UnsupportedType {
353    fn from(value: LvalueType) -> Self {
354        Self::Lvalue(value)
355    }
356}