arithmetic_parser/
error.rs

1//! Error handling.
2
3use core::fmt;
4
5use nom::{
6    error::{ContextError, ErrorKind as NomErrorKind, FromExternalError, ParseError},
7    Input,
8};
9
10use crate::{
11    BinaryOp, ExprType, InputSpan, LocatedSpan, Location, LvalueType, Op, Spanned, StatementType,
12    UnaryOp,
13};
14
15/// Parsing context.
16// TODO: Add more fine-grained contexts.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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(err) => write!(formatter, "Invalid literal: {err}"),
111            Self::LiteralName => formatter.write_str("Literal used in place of an identifier"),
112
113            Self::Type(err) => write!(formatter, "Invalid type annotation: {err}"),
114
115            Self::UnsupportedOp(op) => write!(
116                formatter,
117                "Encountered operation switched off in the parser features: {op}"
118            ),
119
120            Self::UnexpectedChar { context: Some(ctx) } => {
121                write!(formatter, "Unexpected character in {ctx}")
122            }
123            Self::UnexpectedChar { .. } => formatter.write_str("Unexpected character"),
124
125            Self::UnexpectedTerm { context: Some(ctx) } => write!(formatter, "Unfinished {ctx}"),
126            Self::UnexpectedTerm { .. } => formatter.write_str("Unfinished expression"),
127
128            Self::Leftovers => formatter.write_str("Uninterpreted characters after parsing"),
129            Self::Incomplete => formatter.write_str("Incomplete input"),
130            Self::UnfinishedComment => formatter.write_str("Unfinished comment"),
131            Self::ChainedComparison => formatter.write_str("Chained comparisons"),
132            Self::Other { .. } => write!(formatter, "Cannot parse sequence"),
133        }
134    }
135}
136
137impl ErrorKind {
138    /// Creates a `Literal` variant with the specified error.
139    pub fn literal<T: Into<anyhow::Error>>(error: T) -> Self {
140        Self::Literal(error.into())
141    }
142
143    fn context_mut(&mut self) -> Option<&mut Option<Context>> {
144        match self {
145            Self::UnexpectedChar { context }
146            | Self::UnexpectedTerm { context }
147            | Self::Other { context, .. } => Some(context),
148            _ => None,
149        }
150    }
151
152    /// Returns optional error context.
153    pub fn context(&self) -> Option<Context> {
154        match self {
155            Self::UnexpectedChar { context }
156            | Self::UnexpectedTerm { context }
157            | Self::Other { context, .. } => *context,
158            _ => None,
159        }
160    }
161
162    /// Returns `true` if this is `Incomplete`.
163    pub fn is_incomplete(&self) -> bool {
164        matches!(self, Self::Incomplete)
165    }
166
167    #[doc(hidden)]
168    pub fn with_span<T>(self, span: &Spanned<'_, T>) -> Error {
169        Error {
170            inner: span.copy_with_extra(self).map_fragment(str::len),
171        }
172    }
173}
174
175#[cfg(feature = "std")]
176impl std::error::Error for ErrorKind {
177    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178        match self {
179            Self::Literal(err) | Self::Type(err) => Some(err.as_ref()),
180            _ => None,
181        }
182    }
183}
184
185/// Parsing error with a generic code span.
186///
187/// Two primary cases of the `Span` type param are `&str` (for original errors produced by
188/// the parser) and `usize` (for *stripped* errors that have a static lifetime).
189#[derive(Debug)]
190pub struct Error {
191    inner: Location<ErrorKind>,
192}
193
194impl Error {
195    pub(crate) fn new(span: InputSpan<'_>, kind: ErrorKind) -> Self {
196        Self {
197            inner: LocatedSpan::from(span)
198                .map_fragment(str::len)
199                .copy_with_extra(kind),
200        }
201    }
202
203    /// Returns the kind of this error.
204    pub fn kind(&self) -> &ErrorKind {
205        &self.inner.extra
206    }
207
208    /// Returns the span of this error.
209    pub fn location(&self) -> Location {
210        self.inner.with_no_extra()
211    }
212}
213
214impl fmt::Display for Error {
215    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
216        write!(
217            formatter,
218            "{}:{}: {}",
219            self.inner.location_line(),
220            self.inner.get_column(),
221            self.inner.extra
222        )
223    }
224}
225
226#[cfg(feature = "std")]
227impl std::error::Error for Error {
228    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
229        std::error::Error::source(&self.inner.extra)
230    }
231}
232
233impl<'a> ParseError<InputSpan<'a>> for Error {
234    fn from_error_kind(mut input: InputSpan<'a>, kind: NomErrorKind) -> Self {
235        if kind == NomErrorKind::Char && !input.fragment().is_empty() {
236            // Truncate the error span to the first ineligible char.
237            input = input.take(1);
238        }
239
240        let error_kind = if kind == NomErrorKind::Char {
241            if input.fragment().is_empty() {
242                ErrorKind::UnexpectedTerm { context: None }
243            } else {
244                ErrorKind::UnexpectedChar { context: None }
245            }
246        } else {
247            ErrorKind::Other {
248                kind,
249                context: None,
250            }
251        };
252
253        Error::new(input, error_kind)
254    }
255
256    fn append(_: InputSpan<'a>, _: NomErrorKind, other: Self) -> Self {
257        other
258    }
259}
260
261impl<'a> ContextError<InputSpan<'a>> for Error {
262    fn add_context(input: InputSpan<'a>, ctx: &'static str, mut target: Self) -> Self {
263        let ctx = Context::new(ctx);
264        if ctx == Context::Comment {
265            target.inner.extra = ErrorKind::UnfinishedComment;
266        }
267
268        if input.location_offset() < target.inner.location_offset() {
269            if let Some(context) = target.inner.extra.context_mut() {
270                *context = Some(ctx);
271            }
272        }
273        target
274    }
275}
276
277impl<'a> FromExternalError<InputSpan<'a>, ErrorKind> for Error {
278    fn from_external_error(input: InputSpan<'a>, _: NomErrorKind, err: ErrorKind) -> Self {
279        Self::new(input, err)
280    }
281}
282
283/// Description of a construct not supported by a certain module (e.g., interpreter
284/// or type inference).
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286#[non_exhaustive]
287pub enum UnsupportedType {
288    /// Unary operation.
289    UnaryOp(UnaryOp),
290    /// Binary operation.
291    BinaryOp(BinaryOp),
292    /// Expression.
293    Expr(ExprType),
294    /// Statement.
295    Statement(StatementType),
296    /// Lvalue.
297    Lvalue(LvalueType),
298}
299
300impl fmt::Display for UnsupportedType {
301    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
302        match self {
303            Self::UnaryOp(op) => write!(formatter, "unary op: {op}"),
304            Self::BinaryOp(op) => write!(formatter, "binary op: {op}"),
305            Self::Expr(expr) => write!(formatter, "expression: {expr}"),
306            Self::Statement(statement) => write!(formatter, "statement: {statement}"),
307            Self::Lvalue(lvalue) => write!(formatter, "lvalue: {lvalue}"),
308        }
309    }
310}
311
312impl From<UnaryOp> for UnsupportedType {
313    fn from(value: UnaryOp) -> Self {
314        Self::UnaryOp(value)
315    }
316}
317
318impl From<BinaryOp> for UnsupportedType {
319    fn from(value: BinaryOp) -> Self {
320        Self::BinaryOp(value)
321    }
322}
323
324impl From<ExprType> for UnsupportedType {
325    fn from(value: ExprType) -> Self {
326        Self::Expr(value)
327    }
328}
329
330impl From<StatementType> for UnsupportedType {
331    fn from(value: StatementType) -> Self {
332        Self::Statement(value)
333    }
334}
335
336impl From<LvalueType> for UnsupportedType {
337    fn from(value: LvalueType) -> Self {
338        Self::Lvalue(value)
339    }
340}