Skip to main content

utils/parser/
macros.rs

1use crate::parser::ParseError;
2
3/// Helper to create a [`parser::byte_lut`](super::byte_lut) parser using `match`-like syntax.
4///
5/// Each expression must be const and must evaluate to a value of the same copy type.
6///
7/// # Examples
8/// ```
9/// # use utils::parser::{Leaf, 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        }, const {
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/// Helper to create a [`Leaf`](super::Leaf) parser matching string literals using `match`-like
48/// syntax.
49///
50/// This is a replacement for
51/// [`parser::one_of`](crate::parser::one_of())`(("a".map(|_| Enum::A), "b".map(|_| Enum::b)))`
52/// which produces more optimized assembly and is easier to read and write.
53///
54/// The string patterns are matched in the order provided, so strings should be ordered by length.
55///
56/// Using this makes [2017 day 11](../../year2017/struct.Day11.html), which parses a sequence of
57/// literals separated by commas, over 2x faster.
58///
59/// See also [`parser::parsable_enum!`](crate::parser::parsable_enum), which provides a macro to
60/// define an enum and literal parser together.
61///
62/// # Examples
63/// ```
64/// # use utils::parser::{Leaf, self};
65/// #[derive(Debug, PartialEq)]
66/// enum Example {
67///     A,
68///     B,
69///     C,
70/// }
71///
72/// let parser = parser::literal_map!(
73///     "A" | "a" => Example::A,
74///     "B" => Example::B,
75///     "C" => Example::C,
76/// );
77/// assert_eq!(parser.parse(b"A"), Ok((Example::A, &b""[..])));
78/// assert_eq!(parser.parse(b"a"), Ok((Example::A, &b""[..])));
79/// assert_eq!(parser.parse(b"B"), Ok((Example::B, &b""[..])));
80/// assert_eq!(parser.parse(b"C"), Ok((Example::C, &b""[..])));
81/// assert!(parser.parse(b"D").is_err());
82/// ```
83#[macro_export]
84macro_rules! parser_literal_map {
85    (
86        $($($l:literal)|+ => $e:expr),+$(,)?
87    ) => {{
88        $crate::parser::from_leaf_fn(|input| {
89            $($(
90                if input.len() >= const { $l.len() } && const { $l.as_bytes() } == &input[..const { $l.len() }] {
91                    return Ok((($e), &input[const { $l.len() }..]));
92                }
93            )+)*
94
95            Err(($crate::parser_literal_map!(@error $($($l)+)+), input))
96        })
97    }};
98    (@error $first:literal $($l:literal)+) => {
99        $crate::parser::ParseError::Custom(concat!("expected one of '", $first, "'", $(", '", $l, "'",)+))
100    };
101    (@error $first:literal) => {
102        $crate::parser::ParseError::ExpectedLiteral($first)
103    };
104}
105
106/// Helper to define a [`Parseable`](crate::parser::Parseable) fieldless unit-only enum.
107///
108/// The parser is implemented using [`parser::literal_map!`](crate::parser::literal_map) and
109/// [`enumerable_enum!`](crate::enumerable_enum!).
110///
111/// # Examples
112/// ```
113/// # use utils::parser::{Leaf, 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/// assert_eq!(Direction::COUNT, 4);
130/// ```
131///
132/// With discriminant helpers (requires an explicit `#[repr(...)]` attribute first):
133/// ```
134/// # use utils::parser::{Leaf, Parseable, self};
135/// parser::parsable_enum! {
136///     #[repr(u8)]
137///     #[derive(Debug, PartialEq)]
138///     enum Operation {
139///         "add" => Add,
140///         "mul" => Mul,
141///         "div" => Div,
142///         "mod" => Mod,
143///         "eql" => Eql,
144///     }
145/// }
146///
147/// assert_eq!(Operation::PARSER.parse(b"add5"), Ok((Operation::Add, &b"5"[..])));
148/// assert_eq!(Operation::PARSER.parse(b"eql"), Ok((Operation::Eql, &b""[..])));
149///
150/// assert_eq!(Operation::COUNT, 5);
151/// assert_eq!(Operation::checked_from_discriminant(2), Some(Operation::Div));
152/// ```
153#[macro_export]
154macro_rules! parser_parsable_enum {
155    (
156        $(#[$($enum_meta:tt)+])*
157        enum $name:ident {$(
158            $(#[$meta:meta])*
159            $($l:literal)|+ => $variant:ident $(= $value:expr)?,
160        )+}
161    ) => {
162        // Use tt for enum_meta to avoid the attributes being captured as opaque fragments, which
163        // is required for the correct enumerable_enum arm to be selected for repr enums.
164        $crate::enumerable_enum! {
165            $(#[$($enum_meta)+])*
166            enum $name {$(
167                $(#[$meta])*
168                $variant $(= $value)?,
169            )+}
170        }
171
172        impl $crate::parser::Parseable for $name {
173            type Parser = for<'a> fn(&'a [u8]) -> $crate::parser::LeafResult<'a, Self>;
174            const PARSER: Self::Parser = $crate::parser_literal_map!($(
175                $($l)|+ => Self::$variant,
176            )+);
177        }
178    };
179}
180
181/// Helper to define a custom [`Parser`](super::Parser) using a `match` inspired parse tree syntax.
182///
183/// Each rule is made up of a list of chained parsers enclosed in brackets on the left-hand side.
184/// Parsers can be prefixed with an identifier followed by `@` to store the result of that parser in
185/// the supplied variable, similar to normal match patterns.
186///
187/// After the list of parsers, there is an arrow determining the functionality of the rule when the
188/// parsers match:
189/// - **Expression (`=>`)**: The expression on the right-hand is evaluated and returned.
190/// - **Fallible (`=?>`)**: Similar to Expression, but the right-hand side evaluates a result. If
191///   the expression evaluates to [`Ok`], the value contained inside is returned. Otherwise, the
192///   string contained inside the [`Err`] is handled as a custom [`ParseError`], and parsing will
193///   continue with the following rule.
194/// - **Subtree (`=>>`)**: The right-hand side is a nested set of rules enclosed in braces.
195///
196/// Both the top-level and each `=>>` subtree create their own commit scopes. If a parser commits,
197/// no more branches within the current scope are tried.
198///
199/// As an optimisation, when every rule in a scope starts with a string or byte literal, the
200/// generated parser matches each leading literal with a simple comparison for more efficient
201/// dispatch between rules. If no rule in such a scope matches, the error is "expected one of ..."
202/// listing the rules' leading literals. All leading literals in a scope must be the same type.
203///
204/// # Examples
205/// ```
206/// # use utils::parser::{self, Parser};
207/// #
208/// #[derive(Debug, PartialEq)]
209/// enum Register {
210///     A, B, C
211/// }
212///
213/// #[derive(Debug, PartialEq)]
214/// enum Instruction {
215///     Add(Register, Register),
216///     AddConstant(Register, i32),
217///     Copy(Register, Register),
218///     Noop,
219/// }
220///
221/// let register = parser::literal_map!(
222///     "A" => Register::A, "B" => Register::B, "C" => Register::C,
223/// );
224///
225/// let instruction = parser::parse_tree!(
226///     ("add ", r @ register, ", ") =>> {
227///         (r2 @ register) => Instruction::Add(r, r2),
228///         (v @ parser::i32()) => Instruction::AddConstant(r, v),
229///     },
230///     ("copy ", r @ register, ", ", r2 @ register) =?> {
231///         if r == r2 {
232///             Err("cannot copy register to itself")
233///         } else {
234///             Ok(Instruction::Copy(r, r2))
235///         }
236///     },
237///     ("noop") => Instruction::Noop,
238/// );
239///
240/// assert_eq!(
241///     instruction.parse_complete("add A, B").unwrap(),
242///     Instruction::Add(Register::A, Register::B)
243/// );
244/// assert_eq!(
245///     instruction.parse_complete("add C, 100").unwrap(),
246///     Instruction::AddConstant(Register::C, 100)
247/// );
248/// assert_eq!(
249///     instruction.parse_complete("copy A, B").unwrap(),
250///     Instruction::Copy(Register::A, Register::B)
251/// );
252/// assert!(instruction
253///     .parse_complete("copy A, A")
254///     .is_err_and(|err| err.to_string().contains("cannot copy register to itself")));
255/// assert!(instruction
256///     .parse_complete("mov A, B")
257///     .is_err_and(|err| err.to_string().contains(r#"expected one of "add ", "copy ", "noop""#)));
258/// ```
259#[macro_export]
260macro_rules! parser_parse_tree {
261    (@rule $input:ident $state:ident $commit:ident $token:ident [$(,)?] @expr $rhs:expr) => {
262        return Ok(($rhs, $input));
263    };
264    (@rule $input:ident $state:ident $commit:ident $token:ident [$(,)?] @expr_res $rhs:expr) => {
265        match $rhs {
266            Ok(v) => return Ok((v, $input)),
267            Err(e) => {
268                $token = $state.error($crate::parser::ParseError::Custom(e), $input);
269                if ($commit) {
270                    return Err($token);
271                }
272            }
273        };
274    };
275    (@rule $input:ident $state:ident $commit:ident $token:ident [$(,)?] @subtree $($rhs:tt)+) => {
276        // Consider
277        //  ("add ".commit(), r @ register, ", ") =>> {
278        //      (r2 @ register) => Instruction::Add(r, r2),
279        //      (v @ parser::i32()) => Instruction::AddConstant(r, v),
280        //  },
281        // The inner alternative has its own commit scope, so both branches are tried normally.
282        // However, if both fail then an error should be returned.
283        {
284            let mut $commit = false;
285            $crate::parser_parse_tree!(@scope $input $state $commit $token $($rhs)+);
286        }
287        if ($commit) {
288            return Err($token);
289        }
290    };
291
292    (@rule $input:ident $state:ident $commit:ident $token:ident
293        [$n:ident @ $lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
294    ) => {
295        match $crate::parser::Parser::parse_ctx(&($lhs), $input, $state, &mut $commit, false) {
296            Ok(($n, $input)) => {
297                $crate::parser_parse_tree!(@rule $input $state $commit $token
298                    [$($($tail)*)?] $($rhs)+
299                );
300            }
301            Err(t) if $commit => return Err(t),
302            Err(t) => $token = t,
303        };
304    };
305    (@rule $input:ident $state:ident $commit:ident $token:ident
306        [$lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
307    ) => {
308        match $crate::parser::Parser::parse_ctx(&($lhs), $input, $state, &mut $commit, false) {
309            Ok((_, $input)) => {
310                $crate::parser_parse_tree!(@rule $input $state $commit $token
311                    [$($($tail)*)?] $($rhs)+
312                );
313            }
314            Err(t) if $commit => return Err(t),
315            Err(t) => $token = t,
316        };
317    };
318
319    // Check whether every rule in the scope starts with a literal, taking a copy of the rules
320    // to emit once the answer is known
321    (@scope $input:ident $state:ident $commit:ident $token:ident $($rules:tt)+) => {
322        $crate::parser_parse_tree!(@check $input $state $commit $token [$($rules)+] $($rules)+)
323    };
324    (@check $input:ident $state:ident $commit:ident $token:ident [$($all:tt)+]
325        ($first:literal $(, $($lhs:tt)*)?) => $rhs:expr $(, $($tail:tt)*)?
326    ) => {
327        $crate::parser_parse_tree!(@check $input $state $commit $token [$($all)+] $($($tail)*)?)
328    };
329    (@check $input:ident $state:ident $commit:ident $token:ident [$($all:tt)+]
330        ($first:literal $(, $($lhs:tt)*)?) =?> $rhs:expr $(, $($tail:tt)*)?
331    ) => {
332        $crate::parser_parse_tree!(@check $input $state $commit $token [$($all)+] $($($tail)*)?)
333    };
334    (@check $input:ident $state:ident $commit:ident $token:ident [$($all:tt)+]
335        ($first:literal $(, $($lhs:tt)*)?) =>> {$($rhs:tt)+} $(, $($tail:tt)*)?
336    ) => {
337        $crate::parser_parse_tree!(@check $input $state $commit $token [$($all)+] $($($tail)*)?)
338    };
339    (@check $input:ident $state:ident $commit:ident $token:ident [$($all:tt)+]) => {
340        $crate::parser_parse_tree!(@lit $input $state $commit $token [] $($all)+)
341    };
342    (@check $input:ident $state:ident $commit:ident $token:ident [$($all:tt)+] $($rest:tt)+) => {
343        $crate::parser_parse_tree!(@seq $input $state $commit $token $($all)+)
344    };
345
346    // Every rule starts with a literal: try to match each literal with a
347    // `LiteralPrefix::strip_literal` comparison instead of the full parser for each literal.
348    // Eliminating the usual parser error path allows the comparisons to be optimized into a
349    // shared byte load and compare chain which dispatches between the rules. If no rule
350    // matches, an error containing all the leading literals is recorded.
351    (@lit $input:ident $state:ident $commit:ident $token:ident [$($lits:tt)*]
352        ($first:literal $(, $($lhs:tt)*)?) => $rhs:expr $(, $($tail:tt)*)?
353    ) => {
354        if let Some($input) = $crate::parser::LiteralPrefix::strip_literal(&$first, $input) {
355            $crate::parser_parse_tree!(@rule $input $state $commit $token
356                [$($($lhs)*)?] @expr $rhs);
357        }
358        $crate::parser_parse_tree!(@lit $input $state $commit $token
359            [$($lits)* $first] $($($tail)*)?);
360    };
361    (@lit $input:ident $state:ident $commit:ident $token:ident [$($lits:tt)*]
362        ($first:literal $(, $($lhs:tt)*)?) =?> $rhs:expr $(, $($tail:tt)*)?
363    ) => {
364        if let Some($input) = $crate::parser::LiteralPrefix::strip_literal(&$first, $input) {
365            $crate::parser_parse_tree!(@rule $input $state $commit $token
366                [$($($lhs)*)?] @expr_res $rhs);
367        }
368        $crate::parser_parse_tree!(@lit $input $state $commit $token
369            [$($lits)* $first] $($($tail)*)?);
370    };
371    (@lit $input:ident $state:ident $commit:ident $token:ident [$($lits:tt)*]
372        ($first:literal $(, $($lhs:tt)*)?) =>> {$($rhs:tt)+} $(, $($tail:tt)*)?
373    ) => {
374        if let Some($input) = $crate::parser::LiteralPrefix::strip_literal(&$first, $input) {
375            $crate::parser_parse_tree!(@rule $input $state $commit $token
376                [$($($lhs)*)?] @subtree $($rhs)+);
377        }
378        $crate::parser_parse_tree!(@lit $input $state $commit $token
379            [$($lits)* $first] $($($tail)*)?);
380    };
381    (@lit $input:ident $state:ident $commit:ident $token:ident [$($first:literal)+]) => {
382        $token = $state.error(
383            $crate::parser::LiteralPrefix::expected_one_of(&[$($first),+]),
384            $input,
385        );
386    };
387
388    // At least one rule starts with a parser: try each rule in order, with every failing rule
389    // recording an error
390    (@seq $input:ident $state:ident $commit:ident $token:ident
391        ($($lhs:tt)+) => $rhs:expr $(, $($tail:tt)*)?
392    ) => {
393        $crate::parser_parse_tree!(@rule $input $state $commit $token [$($lhs)+] @expr $rhs);
394        $($crate::parser_parse_tree!(@seq $input $state $commit $token $($tail)*);)?
395    };
396    (@seq $input:ident $state:ident $commit:ident $token:ident
397        ($($lhs:tt)+) =?> $rhs:expr $(, $($tail:tt)*)?
398    ) => {
399        $crate::parser_parse_tree!(@rule $input $state $commit $token [$($lhs)+] @expr_res $rhs);
400        $($crate::parser_parse_tree!(@seq $input $state $commit $token $($tail)*);)?
401    };
402    (@seq $input:ident $state:ident $commit:ident $token:ident
403        ($($lhs:tt)+) =>> {$($rhs:tt)+} $(, $($tail:tt)*)?
404    ) => {
405        $crate::parser_parse_tree!(@rule $input $state $commit $token [$($lhs)+] @subtree $($rhs)+);
406        $($crate::parser_parse_tree!(@seq $input $state $commit $token $($tail)*);)?
407    };
408    (@seq $input:ident $state:ident $commit:ident $token:ident $(,)?) => {};
409
410    // Ensures this branch only matches inputs starting with (, giving each rule set a unique prefix
411    (($($first:tt)+) $($tail:tt)+) => {{
412        $crate::parser::from_parser_fn(|input, state, _, _| {
413            let mut commit = false;
414            let mut token;
415
416            $crate::parser_parse_tree!(@scope input state commit token ($($first)+) $($tail)+);
417
418            Err(token)
419        })
420    }};
421}
422
423/// Helper trait for rule literals in [`parser::parse_tree!`](crate::parser::parse_tree) scopes.
424pub trait LiteralPrefix: Sized + 'static {
425    /// Returns the remaining input if the input starts with this literal.
426    fn strip_literal<'i>(&self, input: &'i [u8]) -> Option<&'i [u8]>;
427
428    /// Returns a [`ParseError`] containing the expected literals.
429    fn expected_one_of(literals: &'static [Self]) -> ParseError;
430}
431
432impl LiteralPrefix for &'static str {
433    #[inline]
434    fn strip_literal<'i>(&self, input: &'i [u8]) -> Option<&'i [u8]> {
435        let bytes = self.as_bytes();
436
437        // Deliberately redundant with strip_prefix below: LLVM merges the first byte checks
438        // from a parse_tree! scope's successive rules into one shared byte load and compare
439        // chain, dispatching between rules before the full literal comparisons.
440        if !bytes.is_empty() && input.first() != bytes.first() {
441            return None;
442        }
443
444        input.strip_prefix(bytes)
445    }
446
447    #[inline]
448    fn expected_one_of(literals: &'static [Self]) -> ParseError {
449        ParseError::ExpectedOneOfLiterals(literals)
450    }
451}
452
453impl LiteralPrefix for u8 {
454    #[inline]
455    fn strip_literal<'i>(&self, input: &'i [u8]) -> Option<&'i [u8]> {
456        match input {
457            [first, rest @ ..] if first == self => Some(rest),
458            _ => None,
459        }
460    }
461
462    #[inline]
463    fn expected_one_of(literals: &'static [Self]) -> ParseError {
464        ParseError::ExpectedOneOfBytes(literals)
465    }
466}