#![feature(slice_patterns)] #![doc(html_root_url = "https://docs.rs/improved_slice_patterns/2.0.0")] //! A tiny crate that provides two macros to help matching //! on `Vec`s and iterators using the syntax of //! [`slice_patterns`][slice_patterns] //! //! [slice_patterns]: https://doc.rust-lang.org/nightly/unstable-book/language-features/slice-patterns.html /// Destructure an iterator using the syntax of slice_patterns. /// /// Wraps the match body in `Some` if there was a match; returns /// `None` otherwise. /// /// Contrary to slice_patterns, this allows moving out /// of the iterator. /// /// A variable length pattern (`x @ ..`) is only allowed as the last /// pattern, unless the iterator is double-ended. /// /// Example: /// ```edition2018 /// use improved_slice_patterns::destructure_iter; /// /// let vec = vec![Some(1), Some(2), Some(3), None]; /// /// let res = destructure_iter!(vec.into_iter(); /// [Some(x), y @ .., z] => { /// // x: usize /// // y: impl Iterator> /// // z: Option /// (x, y.collect::>(), z) /// } /// ); /// /// assert_eq!(res, Some((1, vec![Some(2), Some(3)], None))); /// /// # Ok::<(), ()>(()) /// ``` /// /// #[macro_export] macro_rules! destructure_iter { // Variable length pattern (@match_forwards, $iter:expr, ($body:expr), $x:ident @ .., $($rest:tt)*) => { $crate::destructure_iter!(@match_backwards, $iter, ({ let $x = $iter; $body }), $($rest)* ) }; // Special variable length pattern with a common unary variant (@match_forwards, $iter:expr, ($body:expr), $variant:ident ($x:ident).., $($rest:tt)*) => { $crate::destructure_iter!(@match_backwards, $iter, ({ let $x = $iter .map(|x| match x { $variant(y) => y, _ => unreachable!(), }); $body }), $($rest)* ) }; // Variable length pattern without a binder (@match_forwards, $iter:expr, ($body:expr), .., $($rest:tt)*) => { $crate::destructure_iter!(@match_backwards, $iter, ($body), $($rest)* ) }; // Single item pattern (@match_forwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => { if let std::option::Option::Some($x) = $iter.next() { $crate::destructure_iter!(@match_forwards, $iter, ($body), $($rest)* ) } else { std::option::Option::None } }; // Single item pattern after a variable length one: declare reversed and take from the end (@match_backwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => { $crate::destructure_iter!(@match_backwards, $iter, ( if let std::option::Option::Some($x) = $iter.next_back() { $body } else { std::option::Option::None } ), $($rest)*) }; // Check no elements remain (@match_forwards, $iter:expr, ($body:expr) $(,)*) => { if $iter.next().is_some() { std::option::Option::None } else { $body } }; // After a variable length pattern, everything has already been consumed (@match_backwards, $iter:expr, ($body:expr) $(,)*) => { $body }; ($iter:expr; [$($args:tt)*] => $body:expr) => { { #[allow(unused_mut)] let mut iter = $iter; $crate::destructure_iter!(@match_forwards, iter, (std::option::Option::Some($body)), $($args)*, ) } }; } /// Pattern-match on a vec using the syntax of slice_patterns. /// /// Wraps the match body in `Ok` if there was a match; returns /// an `Err` containing the ownership of the vec otherwise. /// /// Contrary to slice_patterns, this allows moving out /// of the `Vec`. /// /// A variable length pattern (`x @ ..`) returns an iterator. /// /// Example: /// ```edition2018 /// #![feature(slice_patterns)] /// use improved_slice_patterns::match_vec; /// /// let vec = vec![Some(1), Some(2), Some(3), None]; /// /// let res = match_vec!(vec; /// [Some(_), y @ .., None] => { /// y.collect::>() /// }, /// [None, None] => { /// vec![] /// }, /// [..] => vec![] /// ); /// /// assert_eq!(res, Ok(vec![Some(2), Some(3)])); /// /// /// let vec = vec![Some(1), Some(2), Some(3), None]; /// /// let res = match_vec!(vec; /// [Some(_), y @ .., Some(_)] => { /// y.collect::>() /// }, /// [None, None] => { /// vec![] /// }, /// ); /// /// assert!(res.is_err()); // there was no match /// /// # Ok::<(), ()>(()) /// ``` /// /// #[macro_export] macro_rules! match_vec { // Variable length pattern (@make_pat; ($($acc:tt)*), $x:ident @ .., $($rest:tt)*) => { $crate::match_vec!(@make_pat; ($($acc)*, $x @ ..), $($rest)* ) }; // Special variable length pattern with a common unary variant (@make_pat; ($($acc:tt)*), $variant:ident ($x:ident).., $($rest:tt)*) => { $crate::match_vec!(@make_pat; ($($acc)*, $x @ ..), $($rest)* ) }; // Variable length pattern without a binder (@make_pat; ($($acc:tt)*), .., $($rest:tt)*) => { $crate::match_vec!(@make_pat; ($($acc)*, ..), $($rest)* ) }; // Single item pattern (@make_pat; ($($acc:tt)*), $x:pat, $($rest:tt)*) => { $crate::match_vec!(@make_pat; ($($acc)*, $x), $($rest)* ) }; (@make_pat; (, $($acc:tt)*), $(,)*) => { [$($acc)*] }; (@make_pat; ($($acc:tt)*), $(,)*) => { [$($acc)*] }; (@make_filter; $x:ident @ .., $($rest:tt)*) => { $crate::match_vec!(@make_filter; $($rest)* ) }; (@make_filter; $variant:ident ($x:ident).., $($rest:tt)*) => { { // Circumvent https://github.com/rust-lang/rust/issues/59803 let is_all_variant = || $x.iter() .all(|x| match x { $variant(_) => true, _ => false, }); is_all_variant() } && $crate::match_vec!(@make_filter; $($rest)* ) }; (@make_filter; .., $($rest:tt)*) => { $crate::match_vec!(@make_filter; $($rest)* ) }; (@make_filter; $x:pat, $($rest:tt)*) => { $crate::match_vec!(@make_filter; $($rest)* ) }; (@make_filter; $(,)*) => { true }; ($arg:expr; $( [$($args:tt)*] => $body:expr ),* $(,)*) => { { let vec = $arg; // Match as references to decide which branch to take // I think `match_default_bindings` should make this always work but // there may be some patterns this doesn't capture. #[allow(unused_variables, unreachable_patterns)] match vec.as_slice() { $( $crate::match_vec!(@make_pat; (), $($args)*,) if $crate::match_vec!(@make_filter; $($args)*,) => { // Actually consume the values #[allow(unused_mut)] let mut iter = vec.into_iter(); let ret = $crate::destructure_iter!(iter; [$($args)*] => $body ); match ret { Some(x) => Ok(x), None => unreachable!(), // Hopefully } } )* _ => std::result::Result::Err(vec), } } }; } #[test] fn test() { let test = |v: Vec>| { match_vec!(v.into_iter(); [Some(_x), None, None] => 4, [Some(_x), None] => 2, [None, Some(y)] => 1, [None, _y @ ..] => 3, [_x @ .., Some(y), Some(z), None] => y - z, [Some(ys)..] => ys.sum(), [] => 0, [..] => -1, ) .unwrap() }; assert_eq!(test(vec![Some(0), None, None]), 4); assert_eq!(test(vec![Some(0), None]), 2); assert_eq!(test(vec![None, Some(0)]), 1); assert_eq!(test(vec![Some(1), Some(2), Some(5), Some(14), None]), -9); assert_eq!(test(vec![Some(1), Some(2), Some(3), Some(4)]), 10); assert_eq!(test(vec![None]), 3); assert_eq!(test(vec![]), 0); assert_eq!(test(vec![Some(0), None, Some(1)]), -1); // Test move out of pattern #[derive(Debug)] struct Foo; let _: (Foo, Foo) = match_vec!(vec![Some(Foo), Some(Foo)]; [Some(f1), Some(f2)] => (f1, f2), ) .unwrap(); // Test return ownership if no match let _: Vec = match_vec!(vec![Foo]; [] => "Error".to_string(), ) .unwrap_err(); }