utils/parser/
macros.rs

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