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}