utils/parser/macros.rs
1/// Macro to define a parser which consumes a single byte and maps it using a lookup table.
2///
3/// This macro is a wrapper around [`parser::byte_lut`](crate::parser::byte_lut) to allow defining
4/// the lookup table using a match-like syntax. Each expression must be const and evaluate to a
5/// value of the same copy type.
6///
7/// # Examples
8/// ```
9/// # use utils::parser::{Parser, self};
10/// let parser = parser::byte_map!(
11/// b'#' => true,
12/// b'.' | b'S' => false,
13/// );
14/// assert_eq!(parser.parse(b"#.S##"), Ok((true, &b".S##"[..])));
15/// assert_eq!(parser.parse(b".S##"), Ok((false, &b"S##"[..])));
16/// assert_eq!(parser.parse(b"S##"), Ok((false, &b"##"[..])));
17///
18/// let (err, remaining) = parser.parse(b"abc").unwrap_err();
19/// assert_eq!(err.to_string(), "expected one of '#', '.', 'S'");
20/// assert_eq!(remaining, &b"abc"[..]);
21/// ```
22#[macro_export]
23macro_rules! parser_byte_map {
24 (
25 $($($l:literal)|+ => $e:expr),+$(,)?
26 ) => {{
27 $crate::parser::byte_lut(&const {
28 // Don't use a const item for the lut to avoid naming the value type
29 let mut lut = [None; 256];
30 $($(
31 assert!(lut[$l as usize].is_none(), "duplicate literal");
32 lut[$l as usize] = Some($e);
33 )+)+
34 lut
35 }, {
36 let mut set = 0u128;
37 $($(
38 let v: u8 = $l;
39 assert!(v < 128, "invalid ASCII");
40 set |= 1u128 << v;
41 )+)+
42 $crate::parser::ParseError::ExpectedOneOf($crate::ascii::AsciiSet::new(set))
43 })
44 }};
45}
46
47/// Macro to define a parser for one or more string literals, mapping the results.
48///
49/// This is a replacement for
50/// [`parser::one_of`](crate::parser::one_of())`(("a".map(|_| Enum::A), "b".map(|_| Enum::b)))`
51/// which produces more optimized assembly and is easier to read and write.
52///
53/// The string patterns are matched in the order provided, so strings should be ordered by length.
54///
55/// Using this makes [2017 day 11](../../year2017/struct.Day11.html), which parses a sequence of
56/// literals separated by commas, over 2x faster.
57///
58/// See also [`parser::parsable_enum!`](crate::parser::parsable_enum), which provides a macro to
59/// define an enum and literal parser together.
60///
61/// # Examples
62/// ```
63/// # use utils::parser::{Parser, self};
64/// #[derive(Debug, PartialEq)]
65/// enum Example {
66/// A,
67/// B,
68/// C,
69/// }
70///
71/// let parser = parser::literal_map!(
72/// "A" | "a" => Example::A,
73/// "B" => Example::B,
74/// "C" => Example::C,
75/// );
76/// assert_eq!(parser.parse(b"A"), Ok((Example::A, &b""[..])));
77/// assert_eq!(parser.parse(b"a"), Ok((Example::A, &b""[..])));
78/// assert_eq!(parser.parse(b"B"), Ok((Example::B, &b""[..])));
79/// assert_eq!(parser.parse(b"C"), Ok((Example::C, &b""[..])));
80/// assert!(parser.parse(b"D").is_err());
81/// ```
82#[macro_export]
83macro_rules! parser_literal_map {
84 (
85 $($($l:literal)|+ => $e:expr),+$(,)?
86 ) => {{
87 const fn coerce_to_parser<F: Fn(&[u8]) -> $crate::parser::ParseResult<'_, O>, O>(f: F) -> F { f }
88
89 coerce_to_parser(|input| {
90 $($(
91 if input.len() >= const { $l.len() } && const { $l.as_bytes() } == &input[..const { $l.len() }] {
92 return Ok((($e), &input[const { $l.len() }..]));
93 }
94 )+)*
95
96 Err(($crate::parser_literal_map!(@error $($($l)+)+), input))
97 })
98 }};
99 (@error $first:literal $($l:literal)+) => {
100 $crate::parser::ParseError::Custom(concat!("expected one of '", $first, "'", $(", '", $l, "'",)+))
101 };
102 (@error $first:literal) => {
103 $crate::parser::ParseError::ExpectedLiteral($first)
104 };
105}
106
107/// Macro to define an enum that implements [`Parseable`](crate::parser::Parseable).
108///
109/// The parser is implemented using [`parser::literal_map!`](crate::parser::literal_map).
110///
111/// # Examples
112/// ```
113/// # use utils::parser::{Parser, Parseable, self};
114/// parser::parsable_enum! {
115/// #[derive(Debug, PartialEq, Default)]
116/// enum Direction {
117/// #[default]
118/// "north" | "n" => North,
119/// "south" | "s" => South,
120/// "east" | "e" => East,
121/// "west" | "w" => West,
122/// }
123/// }
124///
125/// assert_eq!(Direction::PARSER.parse(b"north"), Ok((Direction::North, &b""[..])));
126/// assert_eq!(Direction::PARSER.parse(b"s"), Ok((Direction::South, &b""[..])));
127/// assert!(Direction::PARSER.parse(b"a").is_err());
128/// ```
129#[macro_export]
130macro_rules! parser_parsable_enum {
131 (
132 $(#[$enum_meta:meta])*
133 enum $name:ident {$(
134 $(#[$meta:meta])*
135 $($l:literal)|+ => $variant:ident $(= $value:expr)?,
136 )+}
137 ) => {
138 $(#[$enum_meta])*
139 pub enum $name {$(
140 $(#[$meta])*
141 $variant $(= $value)?,
142 )+}
143
144 impl $name {
145 const ALL: &'static [$name] = &[$(
146 Self::$variant,
147 )+];
148 }
149
150 impl $crate::parser::Parseable for $name {
151 type Parser = for<'a> fn(&'a [u8]) -> $crate::parser::ParseResult<'a, Self>;
152 const PARSER: Self::Parser = $crate::parser_literal_map!($(
153 $($l)|+ => Self::$variant,
154 )+);
155 }
156 };
157}
158
159/// Macro to define a custom parser using a `match` inspired parse tree syntax.
160///
161/// Each rule is made up of a list of chained parsers enclosed in brackets on the left-hand side.
162/// Parsers can be prefixed with an identifier followed by `@` to store the result of that parser in
163/// the supplied variable, similar to normal match patterns.
164///
165/// After the list of parsers, there is an arrow determining the functionality of the rule when the
166/// parsers match:
167/// - **Expression (`=>`)**: The expression on the right-hand is evaluated and returned.
168/// - **Fallible (`=?>`)**: Similar to Expression, but the right-hand side evaluates a result. If
169/// the expression evaluates to [`Ok`], the value contained inside is returned. Otherwise, the
170/// string contained inside the [`Err`] is handled as a custom [`ParseError`](super::ParseError),
171/// and parsing will continue with the following rule.
172/// - **Subtree (`=>>`)**: The right-hand side is a nested set of rules enclosed in braces.
173///
174/// If none of the rules match successfully, the error from the rule which parsed furthest into
175/// the input is returned.
176///
177/// # Examples
178/// ```
179/// # use utils::parser::{self, Parser};
180/// #
181/// #[derive(Debug, PartialEq)]
182/// enum Register {
183/// A, B, C
184/// }
185///
186/// #[derive(Debug, PartialEq)]
187/// enum Instruction {
188/// Add(Register, Register),
189/// AddConstant(Register, i32),
190/// Copy(Register, Register),
191/// Noop,
192/// }
193///
194/// let register = parser::literal_map!(
195/// "A" => Register::A, "B" => Register::B, "C" => Register::C,
196/// );
197///
198/// let instruction = parser::parse_tree!(
199/// ("add ", r @ register, ", ") =>> {
200/// (r2 @ register) => Instruction::Add(r, r2),
201/// (v @ parser::i32()) => Instruction::AddConstant(r, v),
202/// },
203/// ("copy ", r @ register, ", ", r2 @ register) =?> {
204/// if r == r2 {
205/// Err("cannot copy register to itself")
206/// } else {
207/// Ok(Instruction::Copy(r, r2))
208/// }
209/// },
210/// ("noop") => Instruction::Noop,
211/// );
212///
213/// assert_eq!(
214/// instruction.parse_complete("add A, B").unwrap(),
215/// Instruction::Add(Register::A, Register::B)
216/// );
217/// assert_eq!(
218/// instruction.parse_complete("add C, 100").unwrap(),
219/// Instruction::AddConstant(Register::C, 100)
220/// );
221/// assert_eq!(
222/// instruction.parse_complete("copy A, B").unwrap(),
223/// Instruction::Copy(Register::A, Register::B)
224/// );
225/// assert!(instruction
226/// .parse_complete("copy A, A")
227/// .is_err_and(|err| err.to_string().contains("cannot copy register to itself")));
228/// ```
229#[macro_export]
230macro_rules! parser_parse_tree {
231 (@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @expr $rhs:expr) => {
232 return Ok(($rhs, $input));
233 };
234 (@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @expr_res $rhs:expr) => {
235 match $rhs {
236 Ok(v) => return Ok((v, $input)),
237 Err(e) => {
238 if $input.len() < $furthest_remaining {
239 $furthest_err = $crate::parser::ParseError::Custom(e);
240 $furthest_remaining = $input.len();
241 }
242 }
243 };
244 };
245 (@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @subtree $($rhs:tt)+) => {
246 $crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($rhs)+);
247 };
248
249 (@rule $input:ident $furthest_err:ident $furthest_remaining:ident
250 [$n:ident @ $lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
251 ) => {
252 match $crate::parser::Parser::parse(&($lhs), $input) {
253 Ok(($n, $input)) => {
254 $crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining
255 [$($($tail)*)?] $($rhs)+
256 );
257 },
258 Err((err, remaining)) => {
259 if remaining.len() < $furthest_remaining {
260 $furthest_err = err;
261 $furthest_remaining = remaining.len();
262 }
263 }
264 };
265 };
266 (@rule $input:ident $furthest_err:ident $furthest_remaining:ident
267 [$lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
268 ) => {
269 match $crate::parser::Parser::parse(&($lhs), $input) {
270 Ok((_, $input)) => {
271 $crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining
272 [$($($tail)*)?] $($rhs)+
273 );
274 },
275 Err((err, remaining)) => {
276 if remaining.len() < $furthest_remaining {
277 $furthest_err = err;
278 $furthest_remaining = remaining.len();
279 }
280 }
281 };
282 };
283
284 (@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
285 ($($lhs:tt)+) => $rhs:expr $(, $($tail:tt)*)?
286 ) => {
287 $crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @expr $rhs);
288 $($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
289 };
290 (@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
291 ($($lhs:tt)+) =?> $rhs:expr $(, $($tail:tt)*)?
292 ) => {
293 $crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @expr_res $rhs);
294 $($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
295 };
296 (@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
297 ($($lhs:tt)+) =>> {$($rhs:tt)+} $(, $($tail:tt)*)?
298 ) => {
299 $crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @subtree $($rhs)+);
300 $($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
301 };
302 (@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident $(,)?) => {};
303
304 // Ensures this branch only matches inputs starting with (, giving each rule set a unique prefix
305 (($($first:tt)+) $($tail:tt)+) => {{
306 const fn coerce_to_parser<F: Fn(&[u8]) -> $crate::parser::ParseResult<'_, O>, O>(f: F) -> F { f }
307
308 coerce_to_parser(|input| {
309 let mut furthest_err = $crate::parser::ParseError::Custom("unreachable");
310 let mut furthest_remaining = usize::MAX;
311
312 $crate::parser_parse_tree!(@toplevel input furthest_err furthest_remaining ($($first)+) $($tail)+);
313
314 Err((furthest_err, &input[input.len() - furthest_remaining..]))
315 })
316 }};
317}