Skip to main content

lalrpop_util/
lib.rs

1//! Runtime utilities for lalrpop generated parsers.
2//!
3//! For information about using lalrpop, see the [lalrpop
4//! documentation](https://docs.rs/lalrpop/latest/lalrpop).
5//!
6//! This crate is designed to be used in conjunction with the lalrpop crate to provide runtime
7//! support when interacting with lalrpop generated parsers. Generally speaking, if you are using
8//! lalrpop, you want lalrpop specified as a dev-dependency and lalrpop-util as a dependency.
9//! Version numbers for the two crates are kept in sync, and we recommend using the same version
10//! number for each crate.
11#![cfg_attr(not(feature = "std"), no_std)]
12#![warn(rust_2018_idioms)]
13#![warn(missing_docs)]
14
15extern crate alloc;
16
17use alloc::{string::String, vec::Vec};
18use core::error::Error;
19use core::fmt;
20
21#[cfg(feature = "lexer")]
22pub mod lexer;
23pub mod state_machine;
24
25/// Error type for errors returned by lalrpop parsers.
26///
27/// For the built-in lexer, the generic parameters default to:
28/// ParseError<usize, lexer::Token<'_>, &'static str>.
29///
30/// L: the location of the Token where the error occurred
31/// T: The token encountered where the error occurred
32/// E: A custom user-defined error
33///
34/// L and T are fixed types as listed above for built in lexers, and can be defined to whatever
35/// type you would like if implementing a custom lexer.
36///
37/// The type of E can be overridden by specifying `type Error` in your grammar like so:
38///
39/// ```ignore
40/// extern {
41///     type Error = MyCustomErrorType;
42/// }
43/// ```
44#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub enum ParseError<L, T, E> {
46    /// Generated by the internal lexer when it encounters a token (or EOF) it
47    /// did not expect.
48    InvalidToken {
49        /// The end of the invalid token.
50        location: L,
51    },
52
53    /// Generated by the parser when it encounters an EOF it did not expect.
54    UnrecognizedEof {
55        /// The end of the final token.
56        location: L,
57
58        /// The set of expected tokens: these names are taken from the
59        /// grammar and hence may not necessarily be suitable for
60        /// presenting to the user.
61        expected: Vec<String>,
62    },
63
64    /// Generated by the parser when it encounters a token it did not expect.
65    ///
66    /// This means that the next token in the stream was not valid at this
67    /// point in the grammar.
68    UnrecognizedToken {
69        /// The unexpected token of type `T` with a span given by the two `L` values.
70        token: (L, T, L),
71
72        /// The set of expected tokens: these names are taken from the
73        /// grammar and hence may not necessarily be suitable for
74        /// presenting to the user.
75        expected: Vec<String>,
76    },
77
78    /// Generated by the parser when it encounters additional, unexpected tokens.
79    ExtraToken {
80        /// The extra token, with a type of `T` with a span given by the two `L` values.
81        token: (L, T, L),
82    },
83
84    /// Custom error type.
85    User {
86        /// Custom user error.
87        error: E,
88    },
89}
90
91impl<L, T, E> ParseError<L, T, E> {
92    fn map_intern<LL, TT, EE>(
93        self,
94        mut loc_op: impl FnMut(L) -> LL,
95        tok_op: impl FnOnce(T) -> TT,
96        err_op: impl FnOnce(E) -> EE,
97    ) -> ParseError<LL, TT, EE> {
98        // The signature of token is (L, T, L), so we need to call loc_op on both the "start" and
99        // "end" values for a token.
100        let maptok = |(s, t, e): (L, T, L)| (loc_op(s), tok_op(t), loc_op(e));
101        match self {
102            ParseError::InvalidToken { location } => ParseError::InvalidToken {
103                location: loc_op(location),
104            },
105            ParseError::UnrecognizedEof { location, expected } => ParseError::UnrecognizedEof {
106                location: loc_op(location),
107                expected,
108            },
109            ParseError::UnrecognizedToken { token, expected } => ParseError::UnrecognizedToken {
110                token: maptok(token),
111                expected,
112            },
113            ParseError::ExtraToken { token } => ParseError::ExtraToken {
114                token: maptok(token),
115            },
116            ParseError::User { error } => ParseError::User {
117                error: err_op(error),
118            },
119        }
120    }
121
122    /// Transform a `ParseError` by applying a function to the location field.
123    ///
124    /// This could be useful to ensure that all fields implement some trait, or
125    /// to apply an offset to a location.
126    ///
127    /// (Note that unlike `map_token()` and `map_error()`, the closure argument
128    /// for this function is `FnMut`.  This is so that it can be called
129    /// multiple times to apply to the starting and ending location of tokens.)
130    pub fn map_location<LL>(self, op: impl FnMut(L) -> LL) -> ParseError<LL, T, E> {
131        self.map_intern(op, |x| x, |x| x)
132    }
133
134    /// Transform a `ParseError` by applying a function to the token field.
135    ///
136    /// This could be useful to ensure that all fields implement some trait, or
137    /// to transform a token in some way (eg escaping).
138    pub fn map_token<TT>(self, op: impl FnOnce(T) -> TT) -> ParseError<L, TT, E> {
139        self.map_intern(|x| x, op, |x| x)
140    }
141
142    /// Transform a `ParseError` by applying a function to the error field.
143    ///
144    /// This could be useful to ensure that all fields implement some trait, or
145    /// to transform to a different error type in place.
146    pub fn map_error<EE>(self, op: impl FnOnce(E) -> EE) -> ParseError<L, T, EE> {
147        self.map_intern(|x| x, |x| x, op)
148    }
149}
150
151/// Format a list of expected tokens.
152fn fmt_expected(f: &mut fmt::Formatter<'_>, expected: &[String]) -> fmt::Result {
153    if !expected.is_empty() {
154        writeln!(f)?;
155        for (i, e) in expected.iter().enumerate() {
156            let sep = match i {
157                0 => "Expected one of",
158                _ if i < expected.len() - 1 => ",",
159                // Last expected message to be written
160                _ => " or",
161            };
162            write!(f, "{sep} {e}")?;
163        }
164    }
165    Ok(())
166}
167
168impl<L, T, E> fmt::Display for ParseError<L, T, E>
169where
170    L: fmt::Display,
171    T: fmt::Display,
172    E: fmt::Display,
173{
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        use self::ParseError::*;
176        match *self {
177            User { ref error } => write!(f, "{error}"),
178            InvalidToken { ref location } => write!(f, "Invalid token at {location}"),
179            UnrecognizedEof {
180                ref location,
181                ref expected,
182            } => {
183                write!(f, "Unrecognized EOF found at {location}")?;
184                fmt_expected(f, expected)
185            }
186            UnrecognizedToken {
187                token: (ref start, ref token, ref end),
188                ref expected,
189            } => {
190                write!(f, "Unrecognized token `{token}` found at {start}:{end}")?;
191                fmt_expected(f, expected)
192            }
193            ExtraToken {
194                token: (ref start, ref token, ref end),
195            } => write!(f, "Extra token {token} found at {start}:{end}"),
196        }
197    }
198}
199
200impl<L, T, E> From<E> for ParseError<L, T, E> {
201    fn from(error: E) -> Self {
202        ParseError::User { error }
203    }
204}
205
206impl<L, T, E> Error for ParseError<L, T, E>
207where
208    L: fmt::Debug + fmt::Display,
209    T: fmt::Debug + fmt::Display,
210    E: fmt::Debug + fmt::Display,
211{
212    fn description(&self) -> &str {
213        "parse error"
214    }
215}
216
217/// The error type of a recoverable parse error
218///
219/// For a full description of error recovery, see [the lalrpop
220/// book](https://lalrpop.github.io/lalrpop/tutorial/008_error_recovery.html).
221///
222/// This is the type of the variable resulting from binding a `!` symbol in your lalrpop grammar.
223#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
224pub struct ErrorRecovery<L, T, E> {
225    /// The parse error that was recovered from
226    pub error: ParseError<L, T, E>,
227    /// The tokens discarded prior to resuming parsing
228    pub dropped_tokens: Vec<(L, T, L)>,
229}
230
231#[cfg(not(build_os_windows))]
232#[doc(hidden)]
233#[macro_export]
234macro_rules! path_separator {
235    {} => { "/" }
236}
237
238#[cfg(build_os_windows)]
239#[doc(hidden)]
240#[macro_export]
241macro_rules! path_separator {
242    {} => { "\\" }
243}
244
245/// Define a module using the generated parse from a `.lalrpop` file.
246///
247/// You have to specify the name of the module and the path of the file
248/// generated by LALRPOP. If the input is in the root directory, you can
249/// omit it.
250///
251/// # Example
252/// ```ignore
253/// // load parser in src/parser.lalrpop
254/// lalrpop_mod!(parser);
255///
256/// // load parser in src/lex/parser.lalrpop
257/// lalrpop_mod!(parser, "/lex/parser.rs");
258///
259/// // define a public module
260/// lalrpop_mod!(pub parser);
261///
262/// // specify attributes for the generated module
263/// lalrpop_mod!(#[allow(clippy::ptr_arg)]#[rustfmt::skip] parser);
264/// ```
265#[macro_export]
266macro_rules! lalrpop_mod {
267    ($(#[$attr:meta])* $vis:vis $modname:ident) => {
268        lalrpop_util::lalrpop_mod!($(#[$attr])* $vis $modname, concat!("/", stringify!($modname), ".rs"));
269    };
270
271    ($(#[$attr:meta])* $vis:vis $modname:ident, $source:expr) => {
272        #[rustfmt::skip]
273        #[allow(clippy::extra_unused_lifetimes)]
274        #[allow(clippy::needless_lifetimes)]
275        #[allow(clippy::let_unit_value)]
276        #[allow(clippy::just_underscores_and_digits)]
277        $(#[$attr])* $vis mod $modname { include!(concat!(env!("OUT_DIR"), lalrpop_util::path_separator!(), $source)); }
278    };
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284    use alloc::{format, string::ToString, vec};
285
286    #[test]
287    fn test() {
288        let err = ParseError::UnrecognizedToken::<i32, &str, &str> {
289            token: (1, "t0", 2),
290            expected: vec!["t1", "t2", "t3"]
291                .into_iter()
292                .map(|s| s.to_string())
293                .collect(),
294        };
295        assert_eq!(
296            format!("{err}"),
297            "Unrecognized token `t0` found at 1:2\n\
298             Expected one of t1, t2 or t3"
299        );
300    }
301}