improved_slice_patterns/
lib.rs

1#![doc(html_root_url = "https://docs.rs/improved_slice_patterns/2.0.1")]
2
3//! A tiny crate that provides two macros to help matching
4//! on `Vec`s and iterators using the syntax of
5//! [`slice_patterns`][slice_patterns]
6//!
7//! [slice_patterns]: https://doc.rust-lang.org/nightly/unstable-book/language-features/slice-patterns.html
8
9/// Destructure an iterator using the syntax of slice_patterns.
10///
11/// Wraps the match body in `Some` if there was a match; returns
12/// `None` otherwise.
13///
14/// Contrary to slice_patterns, this allows moving out
15/// of the iterator.
16///
17/// A variable length pattern (`x @ ..`) is only allowed as the last
18/// pattern, unless the iterator is double-ended.
19///
20/// Example:
21/// ```edition2018
22/// use improved_slice_patterns::destructure_iter;
23///
24/// let vec = vec![Some(1), Some(2), Some(3), None];
25///
26/// let res = destructure_iter!(vec.into_iter();
27///     [Some(x), y @ .., z] => {
28///         // x: usize
29///         // y: impl Iterator<Option<usize>>
30///         // z: Option<usize>
31///         (x, y.collect::<Vec<_>>(), z)
32///     }
33/// );
34///
35/// assert_eq!(res, Some((1, vec![Some(2), Some(3)], None)));
36///
37/// # Ok::<(), ()>(())
38/// ```
39///
40///
41#[macro_export]
42macro_rules! destructure_iter {
43    // Variable length pattern
44    (@match_forwards, $iter:expr, ($body:expr),
45            $x:ident @ .., $($rest:tt)*) => {
46        $crate::destructure_iter!(@match_backwards,
47            $iter,
48            ({
49                let $x = $iter;
50                $body
51            }),
52            $($rest)*
53        )
54    };
55    // Special variable length pattern with a common unary variant
56    (@match_forwards, $iter:expr, ($body:expr),
57            $variant:ident ($x:ident).., $($rest:tt)*) => {
58        $crate::destructure_iter!(@match_backwards,
59            $iter,
60            ({
61                let $x = $iter
62                    .map(|x| match x {
63                        $variant(y) => y,
64                        _ => unreachable!(),
65                    });
66                $body
67            }),
68            $($rest)*
69        )
70    };
71    // Variable length pattern without a binder
72    (@match_forwards, $iter:expr, ($body:expr), .., $($rest:tt)*) => {
73        $crate::destructure_iter!(@match_backwards,
74            $iter,
75            ($body),
76            $($rest)*
77        )
78    };
79    // Single item pattern
80    (@match_forwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => {
81        if let std::option::Option::Some($x) = $iter.next() {
82            $crate::destructure_iter!(@match_forwards,
83                $iter,
84                ($body),
85                $($rest)*
86            )
87        } else {
88            std::option::Option::None
89        }
90    };
91    // Single item pattern after a variable length one: declare reversed and take from the end
92    (@match_backwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => {
93        $crate::destructure_iter!(@match_backwards, $iter, (
94            if let std::option::Option::Some($x) = $iter.next_back() {
95                $body
96            } else {
97                std::option::Option::None
98            }
99        ), $($rest)*)
100    };
101
102    // Check no elements remain
103    (@match_forwards, $iter:expr, ($body:expr) $(,)*) => {
104        if $iter.next().is_some() {
105            std::option::Option::None
106        } else {
107            $body
108        }
109    };
110    // After a variable length pattern, everything has already been consumed
111    (@match_backwards, $iter:expr, ($body:expr) $(,)*) => {
112        $body
113    };
114
115    ($iter:expr; [$($args:tt)*] => $body:expr) => {
116        {
117            #[allow(unused_mut)]
118            let mut iter = $iter;
119            $crate::destructure_iter!(@match_forwards,
120                iter,
121                (std::option::Option::Some($body)),
122                $($args)*,
123            )
124        }
125    };
126}
127
128/// Pattern-match on a vec using the syntax of slice_patterns.
129///
130/// Wraps the match body in `Ok` if there was a match; returns
131/// an `Err` containing the ownership of the vec otherwise.
132///
133/// Contrary to slice_patterns, this allows moving out
134/// of the `Vec`.
135///
136/// A variable length pattern (`x @ ..`) returns an iterator.
137///
138/// Example:
139/// ```edition2018
140/// #![feature(slice_patterns)]
141/// use improved_slice_patterns::match_vec;
142///
143/// let vec = vec![Some(1), Some(2), Some(3), None];
144///
145/// let res = match_vec!(vec;
146///     [Some(_), y @ .., None] => {
147///         y.collect::<Vec<_>>()
148///     },
149///     [None, None] => {
150///         vec![]
151///     },
152///     [..] => vec![]
153/// );
154///
155/// assert_eq!(res, Ok(vec![Some(2), Some(3)]));
156///
157///
158/// let vec = vec![Some(1), Some(2), Some(3), None];
159///
160/// let res = match_vec!(vec;
161///     [Some(_), y @ .., Some(_)] => {
162///         y.collect::<Vec<_>>()
163///     },
164///     [None, None] => {
165///         vec![]
166///     },
167/// );
168///
169/// assert!(res.is_err()); // there was no match
170///
171/// # Ok::<(), ()>(())
172/// ```
173///
174///
175#[macro_export]
176macro_rules! match_vec {
177    // Variable length pattern
178    (@make_pat; ($($acc:tt)*), $x:ident @ .., $($rest:tt)*) => {
179        $crate::match_vec!(@make_pat;
180            ($($acc)*, $x @ ..),
181            $($rest)*
182        )
183    };
184    // Special variable length pattern with a common unary variant
185    (@make_pat; ($($acc:tt)*), $variant:ident ($x:ident).., $($rest:tt)*) => {
186        $crate::match_vec!(@make_pat;
187            ($($acc)*, $x @ ..),
188            $($rest)*
189        )
190    };
191    // Variable length pattern without a binder
192    (@make_pat; ($($acc:tt)*), .., $($rest:tt)*) => {
193        $crate::match_vec!(@make_pat;
194            ($($acc)*, ..),
195            $($rest)*
196        )
197    };
198    // Single item pattern
199    (@make_pat; ($($acc:tt)*), $x:pat, $($rest:tt)*) => {
200        $crate::match_vec!(@make_pat;
201            ($($acc)*, $x),
202            $($rest)*
203        )
204    };
205    (@make_pat; (, $($acc:tt)*), $(,)*) => {
206        [$($acc)*]
207    };
208    (@make_pat; ($($acc:tt)*), $(,)*) => {
209        [$($acc)*]
210    };
211
212    (@make_filter; $x:ident @ .., $($rest:tt)*) => {
213        $crate::match_vec!(@make_filter;
214            $($rest)*
215        )
216    };
217    (@make_filter; $variant:ident ($x:ident).., $($rest:tt)*) => {
218        {
219            // Circumvent https://github.com/rust-lang/rust/issues/59803
220            let is_all_variant = || $x.iter()
221                .all(|x| match x {
222                    $variant(_) => true,
223                    _ => false,
224                });
225            is_all_variant()
226        }
227        &&
228        $crate::match_vec!(@make_filter;
229            $($rest)*
230        )
231    };
232    (@make_filter; .., $($rest:tt)*) => {
233        $crate::match_vec!(@make_filter;
234            $($rest)*
235        )
236    };
237    (@make_filter; $x:pat, $($rest:tt)*) => {
238        $crate::match_vec!(@make_filter;
239            $($rest)*
240        )
241    };
242    (@make_filter; $(,)*) => {
243        true
244    };
245
246    ($arg:expr; $( [$($args:tt)*] => $body:expr ),* $(,)*) => {
247        {
248            let vec = $arg;
249            // Match as references to decide which branch to take
250            // I think `match_default_bindings` should make this always work but
251            // there may be some patterns this doesn't capture.
252            #[allow(unused_variables, unreachable_patterns)]
253            match vec.as_slice() {
254                $(
255                    $crate::match_vec!(@make_pat; (), $($args)*,)
256                    if
257                    $crate::match_vec!(@make_filter; $($args)*,)
258                    => {
259                        // Actually consume the values
260                        #[allow(unused_mut)]
261                        let mut iter = vec.into_iter();
262                        let ret =
263                            $crate::destructure_iter!(iter;
264                                [$($args)*] => $body
265                            );
266                        match ret {
267                            Some(x) => Ok(x),
268                            None => unreachable!(), // Hopefully
269                        }
270                    }
271                )*
272                _ => std::result::Result::Err(vec),
273            }
274        }
275    };
276}
277
278#[test]
279fn test() {
280    let test = |v: Vec<Option<isize>>| {
281        match_vec!(v.into_iter();
282            [Some(_x), None, None] => 4,
283            [Some(_x), None] => 2,
284            [None, Some(y)] => 1,
285            [None, _y @ ..] => 3,
286            [_x @ .., Some(y), Some(z), None] => y - z,
287            [Some(ys)..] => ys.sum(),
288            [] => 0,
289            [..] => -1,
290        )
291        .unwrap()
292    };
293
294    assert_eq!(test(vec![Some(0), None, None]), 4);
295    assert_eq!(test(vec![Some(0), None]), 2);
296    assert_eq!(test(vec![None, Some(0)]), 1);
297    assert_eq!(test(vec![Some(1), Some(2), Some(5), Some(14), None]), -9);
298    assert_eq!(test(vec![Some(1), Some(2), Some(3), Some(4)]), 10);
299    assert_eq!(test(vec![None]), 3);
300    assert_eq!(test(vec![]), 0);
301    assert_eq!(test(vec![Some(0), None, Some(1)]), -1);
302
303    // Test move out of pattern
304    #[derive(Debug)]
305    struct Foo;
306    let _: (Foo, Foo) = match_vec!(vec![Some(Foo), Some(Foo)];
307        [Some(f1), Some(f2)] => (f1, f2),
308    )
309    .unwrap();
310
311    // Test return ownership if no match
312    let _: Vec<Foo> = match_vec!(vec![Foo];
313        [] => "Error".to_string(),
314    )
315    .unwrap_err();
316}