mapcomp/
lib.rs

1//! Macros for container comprehensions similar to Python's list comprehension.
2//!
3//! This crate adds vector, set, map, and coroutine comprehensions.  It is
4//! meant to complement [maplit](https://docs.rs/maplit/) which provides
5//! macro literals for the same standard containers.
6//!
7//! ```rust
8//! # #![feature(match_default_bindings)]
9//! # #[macro_use] extern crate mapcomp;
10//! # fn main() {
11//! let v = vec![3, 2, 6, 9, 5];
12//!
13//! let even_squares = vecc![x * x; for x in v.iter(); if x % 2 == 0];
14//!
15//! assert_eq!(even_squares, vec![4, 36]);
16//! # }
17//! ```
18//!
19//! The macro names are the same as maplit's container literal macros but with
20//! a **c** at the end for **c**omprehension.  There is an additional macro
21//! `iterc!()` for creating lazily evaluated coroutine expressions.
22//!
23//! List comprehensions exist [in many languages](https://en.wikipedia.org/wiki/List_comprehension)
24//! and in many styles.  This crate uses the same syntax as Python's list
25//! comprehensions due to it's ease of use, readability, and established
26//! popularity.  If you understand Python's list comprehension, then you
27//! understand `mapcomp`'s comprehensions.
28//!
29//! One minor deviation from Python's syntax is the presence of semicolons
30//! between clauses which is a limitation of Rust's macro language.  Another
31//! difference is that the map comprehensions use the `=>` token to separate
32//! the key from the value instead of a colon the way Python does it.
33//!
34//! The `for var in iteratable` clause can be nested as many times as you want
35//! and the `if conditional` is optional after each loop clause.
36//!
37//! ```rust
38//! # #[macro_use] extern crate mapcomp;
39//! # fn main() {
40//! let grid = vec![vec![-2, 5], vec![3, -7, 6], vec![4, 2]];
41//!
42//! let v = vecc![x; for row in grid; if row.len() < 3; for x in row; if x > 0];
43//!
44//! assert_eq!(v, vec![5, 4, 2]);
45//! # }
46//! ```
47
48#![cfg_attr(
49    feature = "coroutines",
50    feature(coroutines, coroutine_trait, stmt_expr_attributes)
51)]
52
53#[cfg(feature = "coroutines")]
54#[doc(hidden)]
55pub mod iterator_comprehension {
56    /// This is an implementation detail used by `iterc!()` and it should not be
57    /// directly instantiated.
58    #[doc(hidden)]
59    pub struct CoroutineIterator<C: ::std::ops::Coroutine + ::std::marker::Unpin> {
60        coroutine: C,
61    }
62
63    impl<C: ::std::ops::Coroutine + ::std::marker::Unpin> CoroutineIterator<C> {
64        pub fn new(coroutine: C) -> CoroutineIterator<C> {
65            CoroutineIterator { coroutine }
66        }
67    }
68
69    impl<C: ::std::ops::Coroutine + ::std::marker::Unpin> Iterator for CoroutineIterator<C> {
70        type Item = C::Yield;
71
72        fn next(&mut self) -> Option<Self::Item> {
73            use ::std::ops::CoroutineState;
74            match ::std::pin::Pin::new(&mut self.coroutine).resume(()) {
75                CoroutineState::Yielded(y) => Some(y),
76                _ => None,
77            }
78        }
79    }
80
81    /// Iterator Comprehension
82    ///
83    /// Returns an iterator over the contents of the comprehension.  It is
84    /// analogous to [Python's generator comprehensions](https://www.python.org/dev/peps/pep-0289/).
85    /// Syntactically, it is similar to the `vecc![]` macro but it returns a lazily
86    /// evaluated iterator instead of a container.  It's use requires the experimental
87    /// coroutines feature.
88    ///
89    #[cfg_attr(
90        feature = "coroutines",
91        doc = r##"
92```rust
93#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
94#[macro_use]
95extern crate mapcomp;
96
97# fn main() {
98let numbers = [8, 3, 5, 7];
99
100let mut powers_of_two = iterc!(1 << x; for x in &numbers);
101
102assert_eq!(Some(256), powers_of_two.next());
103assert_eq!(Some(8), powers_of_two.next());
104assert_eq!(Some(32), powers_of_two.next());
105assert_eq!(Some(128), powers_of_two.next());
106assert_eq!(None, powers_of_two.next());
107# }
108```
109
110Since it only creates an iterator and not a fully populated container, the
111comprehension can be created over an unbounded or infinite iterator.
112
113```rust
114# #![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
115# #[macro_use] extern crate mapcomp;
116# fn main() {
117let mut odd_squares = iterc!(x * x; for x in 1..; if x % 2 == 1);
118
119assert_eq!(Some(1), odd_squares.next());
120assert_eq!(Some(9), odd_squares.next());
121assert_eq!(Some(25), odd_squares.next());
122assert_eq!(Some(49), odd_squares.next());
123# }
124```
125"##
126    )]
127    #[macro_export]
128    macro_rules! iterc {
129        (@__ $exp:expr; for $item:pat in $iter:expr; if $cond:expr) => (
130            for $item in $iter {
131                if $cond {
132                    yield $exp;
133                }
134            }
135        );
136
137        (@__ $exp:expr; for $item:pat in $iter:expr) => (
138            for $item in $iter {
139                yield $exp;
140            }
141        );
142
143        (@__ $exp:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
144            for $item in $iter {
145                if $cond {
146                    iterc!(@__ $exp; $($tail)+)
147                }
148            }
149        );
150
151        (@__ $exp:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
152            for $item in $iter {
153                iterc!(@__ $exp; $($tail)+)
154            }
155        );
156
157        ($exp:expr; $($tail:tt)+) => ({
158            let mut coroutine = #[coroutine] || {
159                iterc!(@__ $exp; $($tail)+)
160            };
161            ::mapcomp::iterator_comprehension::CoroutineIterator::new(coroutine)
162        });
163    }
164}
165
166/// Vector Comprehension
167///
168/// Creates a new `Vec` from the contents of the comprehension.  Vector
169/// comprehensions are analogous to Python's list comprehension.
170///
171/// Python code:
172///
173/// ```python
174/// items = [4, 7, 2]
175///
176/// even_squares = [x*x for x in items if x % 2 == 0]
177/// ```
178///
179/// Rust equivalent code:
180///
181/// ```
182/// # #[macro_use] extern crate mapcomp; fn main() {
183/// let items = [4, 7, 2];
184///
185/// let even_squares = vecc![x*x; for x in &items; if x % 2 == 0];
186///
187/// assert_eq!(even_squares, vec![16, 4]);
188/// # }
189/// ```
190#[macro_export]
191macro_rules! vecc {
192    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr) => (
193        for $item in $iter {
194            if $cond {
195                $acc.push($exp);
196            }
197        }
198    );
199
200    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr) => (
201        for $item in $iter {
202            $acc.push($exp);
203        }
204    );
205
206    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
207        for $item in $iter {
208            if $cond {
209                vecc![@__ $acc, $exp; $($tail)+];
210            }
211        }
212    );
213
214    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
215        for $item in $iter {
216            vecc![@__ $acc, $exp; $($tail)+];
217        }
218    );
219
220    ($exp:expr; $($tail:tt)+) => ({
221        let mut ret = ::std::vec::Vec::new();
222        vecc![@__ ret, $exp; $($tail)+];
223        ret
224    });
225}
226
227/// Hash Set Comprehension
228///
229/// Creates a `HashSet` from the contents of the comprehension.  It is
230/// analogous to Python's set comprehension.
231///
232/// Python code:
233///
234/// ```python
235/// matrix = [[3, 8, 7], [9, 5, 3], [4, 5, 6]]
236///
237/// members = {n for row in matrix for n in row}
238/// ```
239///
240/// Rust equivalent code:
241///
242/// ```
243/// # #[macro_use] extern crate mapcomp;
244/// # fn main() {
245/// let matrix = [[3, 8, 7], [9, 5, 3], [4, 5, 6]];
246///
247/// let members = hashsetc!{n; for row in &matrix; for n in row};
248///
249/// for n in &[3, 8, 7, 9, 5, 4, 6] {
250///     assert!(members.contains(n));
251/// }
252/// # }
253/// ```
254#[macro_export]
255macro_rules! hashsetc {
256    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr) => (
257        for $item in $iter {
258            if $cond {
259                $acc.insert($exp);
260            }
261        }
262    );
263
264    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr) => (
265        for $item in $iter {
266            $acc.insert($exp);
267        }
268    );
269
270    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
271        for $item in $iter {
272            if $cond {
273                hashsetc!{@__ $acc, $exp; $($tail)+};
274            }
275        }
276    );
277
278    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
279        for $item in $iter {
280            hashsetc!{@__ $acc, $exp; $($tail)+};
281        }
282    );
283
284    ($exp:expr; $($tail:tt)+) => ({
285        let mut ret = ::std::collections::HashSet::new();
286        hashsetc!{@__ ret, $exp; $($tail)+};
287        ret
288    });
289}
290
291/// Hash Map Comprehension
292///
293/// Creates a `HashMap` from the contents of the comprehension.  It is
294/// analogous to Python's dictionary comprehension except that it uses the `=>`
295/// token instead of a colon.
296///
297/// Python code:
298///
299/// ```python
300/// numbers = [6, 4, 18]
301///
302/// halves = {str(x): x / 2 for x in numbers}
303/// ```
304///
305/// Rust equivalent code:
306///
307/// ```rust
308/// # #[macro_use] extern crate mapcomp;
309/// # fn main() {
310/// let numbers = [6, 4, 18];
311///
312/// let halves = hashmapc!{x.to_string() => x / 2; for x in numbers.iter()};
313///
314/// for &(k, v) in &[("6", 3), ("4", 2), ("18", 9)] {
315///     assert_eq!(halves[k], v);
316/// }
317/// # }
318/// ```
319#[macro_export]
320macro_rules! hashmapc {
321    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; if $cond:expr) => (
322        for $item in $iter {
323            if $cond {
324                $acc.insert($key, $val);
325            }
326        }
327    );
328
329    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr) => (
330        for $item in $iter {
331            $acc.insert($key, $val);
332        }
333    );
334
335    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
336        for $item in $iter {
337            if $cond {
338                hashmapc!{@__ $acc, $key => $val; $($tail)+};
339            }
340        }
341    );
342
343    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
344        for $item in $iter {
345            hashmapc!{@__ $acc, $key => $val; $($tail)+};
346        }
347    );
348
349    ($key:expr => $val:expr; $($tail:tt)+) => ({
350        let mut ret = ::std::collections::HashMap::new();
351        hashmapc!{@__ ret, $key => $val; $($tail)+};
352        ret
353    });
354}
355
356/// BTree Set Comprehension
357///
358/// Creates a `BTreeSet` from the contents of the comprehension.  Same syntax as
359/// `hashsetc!{}`.
360///
361/// ```rust
362/// # #![feature(match_default_bindings)]
363/// # #[macro_use] extern crate mapcomp;
364/// # fn main() {
365/// let pairs = btreesetc!{(i, j); for i in 4..7; for j in 10..12};
366///
367/// for i in 4..7 {
368///     for j in 10..12 {
369///         assert!(pairs.contains(&(i, j)));
370///     }
371/// }
372/// # }
373/// ```
374#[macro_export]
375macro_rules! btreesetc {
376    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr) => (
377        for $item in $iter {
378            if $cond {
379                $acc.insert($exp);
380            }
381        }
382    );
383
384    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr) => (
385        for $item in $iter {
386            $acc.insert($exp);
387        }
388    );
389
390    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
391        for $item in $iter {
392            if $cond {
393                btreesetc!{@__ $acc, $exp; $($tail)+};
394            }
395        }
396    );
397
398    (@__ $acc:ident, $exp:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
399        for $item in $iter {
400            btreesetc!{@__ $acc, $exp; $($tail)+};
401        }
402    );
403
404    ($exp:expr; $($tail:tt)+) => ({
405        let mut ret = ::std::collections::BTreeSet::new();
406        btreesetc!{@__ ret, $exp; $($tail)+};
407        ret
408    });
409}
410
411/// BTree Map Comprehension
412///
413/// Creates a `BTreeMap` from the contents of the comprehension.  Same syntax
414/// as `hashmapc!{}`.
415///
416/// ```rust
417/// # #[macro_use] extern crate mapcomp;
418/// # fn main() {
419/// let array = [5, 3, 9, 6];
420///
421/// let index_map = hashmapc!{x => i; for (i, x) in array.iter().enumerate()};
422///
423/// for (i, x) in array.iter().enumerate() {
424///     assert_eq!(index_map[x], i);
425/// }
426/// # }
427/// ```
428#[macro_export]
429macro_rules! btreemapc {
430    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; if $cond:expr) => (
431        for $item in $iter {
432            if $cond {
433                $acc.insert($key, $val);
434            }
435        }
436    );
437
438    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr) => (
439        for $item in $iter {
440            $acc.insert($key, $val);
441        }
442    );
443
444    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; if $cond:expr; $($tail:tt)+) => (
445        for $item in $iter {
446            if $cond {
447                btreemapc!{@__ $acc, $key => $val; $($tail)+};
448            }
449        }
450    );
451
452    (@__ $acc:ident, $key:expr => $val:expr; for $item:pat in $iter:expr; $($tail:tt)+) => (
453        for $item in $iter {
454            btreemapc!{@__ $acc, $key => $val; $($tail)+};
455        }
456    );
457
458    ($key:expr => $val:expr; $($tail:tt)+) => ({
459        let mut ret = ::std::collections::BTreeMap::new();
460        btreemapc!{@__ ret, $key => $val; $($tail)+};
461        ret
462    });
463}
464
465#[cfg(test)]
466mod tests {
467    #[test]
468    fn it_works() {
469        let _v = vecc![x * x; for x in 1..10; if x % 2 == 0];
470    }
471}