py_comp/
lib.rs

1//! This macro implements a syntax that emulates Python's
2//! [`generator-expression`] syntax in a form more compatible with Rust's
3//! usual syntax.
4//!
5//! This means that there are a few small differences between the Python syntax
6//! and the syntax provided in this macro:
7//!
8//! * The pattern between the `for` and `in` tokens is a fully-fledged
9//!   Rust pattern, which can be as simple as a simple token and as complex
10//!   as struct destructuring.
11//! * The expression defining the iterator after the `in` token
12//!   must evaluate to either an `Iterator` or an `impl IntoIterator`.
13//! * The conditional expression after the `if` token must evaluate to
14//!   a boolean.
15//! * You may use an `if let` clause instead of the usual `if` clause wherever
16//!   `if` clauses are allowed. Any names introduced in the `if let` clause
17//!   are available in any following clause.
18//! * The expression in the beginning of the generator expression,
19//!   the expression following the `in` token, and the expression following
20//!   the `if` token, must all end with a semicolon (;). The only exception
21//!   to this is the last expression following `in` or `if` in the macro,
22//!   which may omit the trailing semicolon.
23//!
24//! The expression replaced by the `comp!()` macro invocation is a lazy
25//! iterator whose lifetime is bound by any references it needs to capture.
26//! This means that it can be `.collect()`ed into any container you like.
27//!
28//! Note though that, at least for now, all objects named in an `in` clause,
29//! (except for the first `in` clause) must be either `Copy` or introduced by
30//! the previous `for` or `if let` clauses. This is because the macro uses a
31//! `move` closure (`FnOnce`) for each level of nesting, which may need to be
32//! instantiated more than once without implicit cloning of the captured
33//! objects.
34//! Similarly, objects named in the "yield" expression (preceding the first
35//! `for` clause) must be `Copy` types if they were not introduced by the final
36//! `for` or `if let` clauses. This is because they may be used in multiple
37//! output items.
38//!
39//! Specifying which objects should be cloned and where may be added in the
40//! future, but will probably require a breaking change.
41//!
42//! This is a BNF description of the syntax used by this macro:
43//!
44//! ```bnf
45//! comprehension ::=  expression ";" comp_for [comp_iter] [";"]
46//! comp_iter     ::=  ";" (comp_for | comp_if | comp_if_let)
47//! comp_for      ::=  "for" pattern "in" expression [comp_iter]
48//! comp_if       ::=  "if" expression [comp_iter]
49//! comp_if_let   ::=  "if" "let" pattern ("|" pattern)* "=" expression [comp_iter]
50//! ```
51//!
52//! Just like in Python, you can nest as many `for`, `if`, and `if let`
53//! clauses as you like.
54//!
55//! ## Examples
56//!
57//! Simple generator expression with a conditional:
58//!
59//! ```rust
60//! use py_comp::comp;
61//!
62//! #[derive(Debug, PartialEq, Eq)]
63//! struct Foo(i32);
64//!
65//! let arr = &[Foo(11), Foo(12)];
66//!
67//! // Notice the semicolons
68//! let comp_vector = comp!(item; for item in arr; if item.0 % 10 == 2)
69//!     .collect::<Vec<&Foo>>();
70//!
71//! assert_eq!(comp_vector, vec![&Foo(12)])
72//! ```
73//!
74//! Triple cartesian product with conditions and patterns:
75//!
76//! ```rust
77//! use py_comp::comp;
78//!
79//! #[derive(Debug, PartialEq, Eq)]
80//! struct Foo(i32);
81//!
82//! // These need to be references to arrays because of how the closures
83//! // that the macro expands to capture their environment.
84//! let x = &[(Foo(11), "foo"), (Foo(12), "bar")];
85//! let y = &[Foo(21), Foo(22)];
86//! let z = &[Foo(31), Foo(32)];
87//!
88//! let xyz = comp!(
89//!     (a, b, c);
90//!     for (a, _text) in x;  // You can use any function parameter pattern.
91//!     if a.0 % 10 == 2;
92//!     for b in y;           // Obviously not every level requires a conditional.
93//!     for c in z;
94//!     if c.0 % 10 == 2;
95//! )
96//! .collect::<Vec<(&Foo, &Foo, &Foo)>>();
97//!
98//! // The result vector here is short for illustration purposes
99//! // but can be as long as long as you need it to be.
100//! assert_eq!(xyz, vec![(&Foo(12), &Foo(21), &Foo(32)), (&Foo(12), &Foo(22), &Foo(32))])
101//! ```
102//!
103//! Flatten a triple-nested structure + complex expression:
104//!
105//! ```rust
106//! use py_comp::comp;
107//!
108//! #[derive(Debug, PartialEq, Eq)]
109//! struct Foo(i32);
110//!
111//! let nested_3 = &[
112//!     [
113//!         [Foo(0), Foo(1), Foo(2)],
114//!         [Foo(3), Foo(4), Foo(5)],
115//!         [Foo(6), Foo(7), Foo(8)],
116//!     ],
117//!     [
118//!         [Foo(9), Foo(10), Foo(11)],
119//!         [Foo(12), Foo(13), Foo(14)],
120//!         [Foo(15), Foo(16), Foo(17)],
121//!     ],
122//!     [
123//!         [Foo(18), Foo(19), Foo(20)],
124//!         [Foo(21), Foo(22), Foo(23)],
125//!         [Foo(24), Foo(25), Foo(26)],
126//!     ],
127//! ];
128//!
129//! let nested_objects = comp!(
130//!     {
131//!         let inner = nested.0;
132//!         Foo(inner + 1)
133//!     };
134//!     for nested_2 in nested_3;
135//!     for nested_1 in nested_2;
136//!     for nested in nested_1;
137//! )
138//! .collect::<Vec<Foo>>();
139//!
140//! let expected_values = (1..28).map(Foo).collect::<Vec<Foo>>();
141//!
142//! assert_eq!(expected_values, nested_objects);
143//! ```
144//!
145//! [`generator-expression`]: https://docs.python.org/3/reference/expressions.html#generator-expressions
146//!
147
148#![warn(clippy::all)]
149
150use doc_comment::doctest;
151
152doctest!("../Readme.md");
153
154/// Check that the type of the expression passed here implements IntoIterator.
155#[doc(hidden)]
156#[inline(always)]
157pub fn __py_comp_assert_impl_into_iter<T: IntoIterator>(_: &T) {}
158
159/// A Python-like lazy generator-expression
160///
161/// For details see [module level documentation][super]
162///
163/// [super]: ../py_comp/index.html
164#[macro_export(local_inner_macros)]
165macro_rules! comp {
166    // @parse_if if
167    (@parse_if
168        $item_expr: expr;
169        if $condition: expr
170    ) => {
171        if $condition {
172            Some($item_expr)
173        } else {
174            None
175        }
176    };
177
178    // @parse_if if-let
179    (@parse_if
180        $item_expr: expr;
181        if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr
182    ) => {
183        if let $( $if_let_pattern )|+ = $if_let_expr {
184            Some($item_expr)
185        } else {
186            None
187        }
188    };
189
190    // @parse_if if for ...
191    // This case returns to the main macro parsing.
192    (@parse_if
193        $item_expr: expr;
194        if $condition: expr;
195        for $($rest: tt)*
196    ) => {
197        if $condition {
198            Some(comp!($item_expr; for $($rest)*))
199        } else {
200            None
201        }
202    };
203
204    // @parse_if if-let for ...
205    // This case returns to the main macro parsing.
206    (@parse_if
207        $item_expr: expr;
208        if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr;
209        for $($rest: tt)*
210    ) => {
211        if let $( $if_let_pattern )|+ = $if_let_expr {
212            Some(comp!($item_expr; for $($rest)*))
213        } else {
214            None
215        }
216    };
217
218    // @parse_if if if ...
219    (@parse_if
220        $item_expr: expr;
221        if $condition: expr;
222        if $($rest: tt)*
223    ) => {
224        if $condition {
225            comp!(@parse_if $item_expr; if $($rest)*)
226        } else {
227            None
228        }
229    };
230
231    // @parse_if if-let if ...
232    (@parse_if
233        $item_expr: expr;
234        if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr;
235        if $($rest: tt)*
236    ) => {
237        if let $( $if_let_pattern )|+ = $if_let_expr {
238            comp!(@parse_if $item_expr; if $($rest)*)
239        } else {
240            None
241        }
242    };
243
244    // for in
245    (
246        $item_expr: expr;
247        for $pattern: pat in $into_iterator: expr $(;)?
248    ) => {{
249        let into_iterator = $into_iterator;
250        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
251        into_iterator
252            .into_iter()
253            .map(move |$pattern| $item_expr)
254    }};
255
256    // for in $( if $( if-let )* )+
257    (
258        $item_expr: expr;
259        for $pattern: pat in $into_iterator: expr
260        $(
261            ; if $condition: expr
262            $( ; if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr )*
263        )+
264        $(;)?
265    ) => {{
266        let into_iterator = $into_iterator;
267        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
268        into_iterator
269            .into_iter()
270            .filter_map(move |$pattern|
271                comp!(@parse_if
272                    $item_expr
273                    $(
274                        ; if $condition
275                        $( ; if let $( $if_let_pattern )|+ = $if_let_expr )*
276                    )+
277                )
278            )
279    }};
280
281    // for in $( if-let $( if )* )+
282    (
283        $item_expr: expr;
284        for $pattern: pat in $into_iterator: expr
285        $(
286            ; if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr
287            $( ; if $condition: expr )*
288        )+
289        $(;)?
290    ) => {{
291        let into_iterator = $into_iterator;
292        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
293        into_iterator
294            .into_iter()
295            .filter_map(move |$pattern|
296                comp!(@parse_if
297                    $item_expr
298                    $(
299                        ; if let $( $if_let_pattern )|+ = $if_let_expr
300                        $( ; if $condition )*
301                    )+
302                )
303            )
304    }};
305
306    // for in for ...
307    (
308        $item_expr: expr;
309        for $pattern: pat in $into_iterator: expr;
310        for $($rest: tt)*
311    ) => {{
312        let into_iterator = $into_iterator;
313        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
314        into_iterator
315            .into_iter()
316            .flat_map(move |$pattern|
317                comp!($item_expr; for $($rest)*)
318            )
319    }};
320
321    // for in $( if $( if-let )* )+ for ...
322    (
323        $item_expr: expr;
324        for $pattern: pat in $into_iterator: expr;
325        $(
326            if $condition: expr;
327            $( if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr; )*
328        )+
329        for $($rest: tt)*
330    ) => {{
331        let into_iterator = $into_iterator;
332        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
333        into_iterator
334            .into_iter()
335            .filter_map(move |$pattern|
336                comp!(@parse_if
337                    $item_expr;
338                    $(
339                        if $condition;
340                        $( if let $( $if_let_pattern )|+ = $if_let_expr; )*
341                    )+
342                    for $($rest)*
343                )
344            )
345            .flatten()
346    }};
347
348    // for in $( if-let $( if )* )+ for ...
349    (
350        $item_expr: expr;
351        for $pattern: pat in $into_iterator: expr;
352        $(
353            if let $( $if_let_pattern: pat )|+ = $if_let_expr: expr;
354            $( if $condition: expr; )*
355        )+
356        for $($rest: tt)*
357    ) => {{
358        let into_iterator = $into_iterator;
359        $crate::__py_comp_assert_impl_into_iter(&into_iterator);
360        into_iterator
361            .into_iter()
362            .filter_map(move |$pattern|
363                comp!(@parse_if
364                    $item_expr;
365                    $(
366                        if let $( $if_let_pattern )|+ = $if_let_expr;
367                        $( if $condition; )*
368                    )+
369                    for $($rest)*
370                )
371            )
372            .flatten()
373    }};
374}