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}