From 49f6bd2bdefe91c0df15798c315d4af47f9f35b3 Mon Sep 17 00:00:00 2001 From: silvanshade Date: Tue, 28 May 2024 09:02:34 -0600 Subject: [PATCH] Refactor `dispatch!`; fixes #344 --- src/combinator/branch.rs | 2 + src/macros/dispatch.rs | 82 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/combinator/branch.rs b/src/combinator/branch.rs index 8913dcb9..ec0b6f7d 100644 --- a/src/combinator/branch.rs +++ b/src/combinator/branch.rs @@ -3,6 +3,8 @@ use crate::error::{ErrMode, ErrorKind, ParserError}; use crate::stream::Stream; use crate::*; +#[doc(hidden)] +pub use crate::__dispatch_cases; #[doc(inline)] pub use crate::dispatch; diff --git a/src/macros/dispatch.rs b/src/macros/dispatch.rs index b763d4ba..3fe2846f 100644 --- a/src/macros/dispatch.rs +++ b/src/macros/dispatch.rs @@ -37,19 +37,85 @@ /// /// assert_eq!(escaped.parse_peek("\\nHello"), Ok(("Hello", '\n'))); /// ``` +#[macro_export] +#[doc(hidden)] // forced to be visible in intended location +macro_rules! __dispatch_cases { + ([ ] [ $i:tt $initial:tt $($acc:tt)* ]) => { + match $initial { + $($acc)* + } + }; + ([ $pat:pat $(if $pred:expr)? => $expr:expr $(,)? ] [ $i:tt $($acc:tt)* ]) => { + $crate::combinator::__dispatch_cases!([ ] [ $i $($acc)* $pat $(if $pred)? => $expr.parse_next($i), ]) + }; + ([ $pat:pat $(if $pred:expr)? => $expr:expr, $($rest:tt)+ ] [ $i:tt $($acc:tt)* ]) => { + $crate::combinator::__dispatch_cases!([ $($rest)+ ] [ $i $($acc)* $pat $(if $pred)? => $expr.parse_next($i), ]) + }; + // NOTE: Must be the last rule otherwise the `{ ... },` (trailing comma) will fail to parse. + ([ $pat:pat $(if $pred:expr)? => { $($body:tt)* } $($rest:tt)+ ] [ $i:tt $($acc:tt)* ]) => { + $crate::combinator::__dispatch_cases!([ $($rest)+ ] [ $i $($acc)* $pat $(if $pred)? => { $($body)* }.parse_next($i), ]) + }; +} + #[macro_export] #[doc(hidden)] // forced to be visible in intended location macro_rules! dispatch { - ($match_parser: expr; $( $pat:pat $(if $pred:expr)? => $expr: expr ),+ $(,)? ) => { - $crate::combinator::trace("dispatch", move |i: &mut _| - { + ($match_parser:expr; $($cases:tt)+) => { + $crate::combinator::trace("dispatch", move |i: &mut _| { use $crate::Parser; let initial = $match_parser.parse_next(i)?; - match initial { - $( - $pat $(if $pred)? => $expr.parse_next(i), - )* - } + $crate::combinator::__dispatch_cases!([ $($cases)+ ] [ i initial ]) }) + }; +} + +#[cfg(test)] +mod test { + #[test] + fn parse_block_with_trailing_comma() { + use crate::combinator::{dispatch, empty, fail, preceded}; + use crate::prelude::*; + use crate::token::any; + + #[allow(unused)] + fn escaped(input: &mut &str) -> PResult { + preceded('\\', escape_seq_char).parse_next(input) + } + + #[allow(unused)] + fn escape_seq_char(input: &mut &str) -> PResult { + dispatch! {any; + 'b' => { + empty.value('\u{8}') + }, + 'f' => empty.value('\u{c}'), + _ => fail::<_, char, _>, + } + .parse_next(input) + } + } + + #[test] + fn parse_block_sans_trailing_comma() { + use crate::combinator::{dispatch, empty, fail, preceded}; + use crate::prelude::*; + use crate::token::any; + + #[allow(unused)] + fn escaped(input: &mut &str) -> PResult { + preceded('\\', escape_seq_char).parse_next(input) + } + + #[allow(unused)] + fn escape_seq_char(input: &mut &str) -> PResult { + dispatch! {any; + 'b' => { + empty.value('\u{8}') + } + 'f' => empty.value('\u{c}'), + _ => fail::<_, char, _>, + } + .parse_next(input) + } } }