breadth_first_zip_macros/
lib.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! Automatically implement `BreadthFirstIterator` for tuples up to a finite but huge size.
8
9use core::ops::RangeInclusive;
10use proc_macro2::{Span, TokenStream};
11use quote::{quote, ToTokens};
12use syn::{punctuated::Punctuated, spanned::Spanned};
13
14const START_CHAR: u8 = b'A';
15const END_CHAR: u8 = b'A' + 7; // Inclusive
16const TO_LOWERCASE: u8 = b'a' - b'A';
17
18#[proc_macro]
19pub fn implement_flatten(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
20    flatten_fallible(ts.into())
21        .unwrap_or_else(syn::Error::into_compile_error)
22        .into()
23}
24
25#[proc_macro]
26pub fn implement(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
27    fallible(ts.into())
28        .unwrap_or_else(syn::Error::into_compile_error)
29        .into()
30}
31
32#[inline]
33fn flatten_fallible(ts: TokenStream) -> syn::Result<TokenStream> {
34    if !ts.is_empty() {
35        return Err(syn::Error::new(ts.span(), "This macro takes no arguments"));
36    }
37    let mut out = TokenStream::new();
38    for endc in START_CHAR..=END_CHAR {
39        let chars = START_CHAR..=endc;
40        let mut a_good_start: syn::ItemImpl = syn::parse2(quote! {
41            impl<TODO> crate::Flatten for TODO {}
42        })?;
43        a_good_start.generics.params = chars
44            .clone()
45            .map(|ref c| {
46                syn::GenericParam::Type(syn::TypeParam {
47                    attrs: vec![],
48                    ident: cr2i(c),
49                    colon_token: None,
50                    bounds: Punctuated::new(),
51                    eq_token: None,
52                    default: None,
53                })
54            })
55            .collect();
56        a_good_start.self_ty = Box::new(huge_nested_tuple(chars.clone())?);
57        a_good_start.items = vec![
58            type_flattened_equals(chars.clone())?,
59            fn_flatten(chars.clone())?,
60        ];
61        a_good_start.to_tokens(&mut out);
62    }
63    Ok(out)
64}
65
66#[inline]
67fn fallible(ts: TokenStream) -> syn::Result<TokenStream> {
68    if !ts.is_empty() {
69        return Err(syn::Error::new(ts.span(), "This macro takes no arguments"));
70    }
71    let mut out = TokenStream::new();
72    for endc in START_CHAR..=END_CHAR {
73        let chars = START_CHAR..=endc;
74        let mut a_good_start: syn::ItemImpl = syn::parse2(quote! {
75            impl<TODO> BreadthFirstZip<TODO> for TODO {}
76        })?;
77        let span = a_good_start.span();
78        a_good_start.generics.params = impl_generics(chars.clone())?;
79        a_good_start
80            .trait_
81            .as_mut()
82            .ok_or(syn::Error::new(span, "Internal error"))?
83            .1
84            .segments
85            .first_mut()
86            .ok_or(syn::Error::new(span, "Internal error"))?
87            .arguments = {
88            syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
89                colon2_token: None,
90                lt_token: syn::token::Lt {
91                    spans: [Span::call_site()],
92                },
93                args: [syn::GenericArgument::Lifetime(syn::Lifetime {
94                    apostrophe: Span::call_site(),
95                    ident: syn::Ident::new("item", Span::call_site()),
96                })]
97                .into_iter()
98                .collect(),
99                gt_token: syn::token::Gt {
100                    spans: [Span::call_site()],
101                },
102            })
103        };
104        a_good_start.self_ty = Box::new(flat_tuple_type(chars.clone())?);
105        a_good_start.generics.where_clause = Some(where_clause(chars.clone())?);
106        a_good_start.items = vec![
107            type_nested_equals(chars.clone())?,
108            fn_breadth_first()?,
109            fn_unflatten(chars)?,
110        ];
111        a_good_start.to_tokens(&mut out);
112    }
113    Ok(out)
114}
115
116#[inline]
117fn cr2s(c: &u8) -> &str {
118    core::str::from_utf8(core::slice::from_ref(c)).unwrap()
119}
120#[inline]
121fn cr2i(c: &u8) -> syn::Ident {
122    syn::Ident::new(cr2s(c), Span::call_site())
123}
124
125#[inline]
126fn impl_generics(
127    chars: RangeInclusive<u8>,
128) -> syn::Result<Punctuated<syn::GenericParam, syn::token::Comma>> {
129    Ok([syn::GenericParam::Lifetime(syn::LifetimeParam {
130        attrs: vec![],
131        lifetime: syn::Lifetime {
132            apostrophe: Span::call_site(),
133            ident: syn::Ident::new("item", Span::call_site()),
134        },
135        colon_token: None,
136        bounds: Punctuated::new(),
137    })]
138    .into_iter()
139    .chain(chars.map(|ref c| {
140        syn::GenericParam::Type(syn::TypeParam {
141            attrs: vec![],
142            ident: cr2i(c),
143            colon_token: Some(syn::token::Colon {
144                spans: [Span::call_site()],
145            }),
146            bounds: {
147                let iterator = syn::TypeParamBound::Trait(syn::TraitBound {
148                    paren_token: None,
149                    modifier: syn::TraitBoundModifier::None,
150                    lifetimes: None,
151                    path: syn::Path {
152                        leading_colon: Some(syn::token::PathSep {
153                            spans: [Span::call_site(), Span::call_site()],
154                        }),
155                        segments: [
156                            syn::PathSegment {
157                                ident: syn::Ident::new("core", Span::call_site()),
158                                arguments: syn::PathArguments::None,
159                            },
160                            syn::PathSegment {
161                                ident: syn::Ident::new("iter", Span::call_site()),
162                                arguments: syn::PathArguments::None,
163                            },
164                            syn::PathSegment {
165                                ident: syn::Ident::new("Iterator", Span::call_site()),
166                                arguments: syn::PathArguments::None,
167                            },
168                        ]
169                        .into_iter()
170                        .collect(),
171                    },
172                });
173                [iterator].into_iter().collect()
174            },
175            eq_token: None,
176            default: None,
177        })
178    }))
179    .collect())
180}
181
182#[inline]
183fn where_clause(chars: RangeInclusive<u8>) -> syn::Result<syn::WhereClause> {
184    Ok(syn::WhereClause {
185        where_token: syn::parse2(quote!(where))?,
186        predicates: chars
187            .map(|ref c| {
188                syn::WherePredicate::Type(syn::PredicateType {
189                    lifetimes: None,
190                    bounded_ty: syn::Type::Path(syn::TypePath {
191                        qself: None,
192                        path: syn::Path {
193                            leading_colon: None,
194                            segments: [
195                                syn::PathSegment {
196                                    ident: cr2i(c),
197                                    arguments: syn::PathArguments::None,
198                                },
199                                syn::PathSegment {
200                                    ident: syn::Ident::new("Item", Span::call_site()),
201                                    arguments: syn::PathArguments::None,
202                                },
203                            ]
204                            .into_iter()
205                            .collect(),
206                        },
207                    }),
208                    colon_token: syn::token::Colon {
209                        spans: [Span::call_site()],
210                    },
211                    bounds: [syn::TypeParamBound::Lifetime(syn::Lifetime {
212                        apostrophe: Span::call_site(),
213                        ident: syn::Ident::new("item", Span::call_site()),
214                    })]
215                    .into_iter()
216                    .collect(),
217                })
218            })
219            .collect(),
220    })
221}
222
223#[inline]
224fn flat_tuple_type(chars: RangeInclusive<u8>) -> syn::Result<syn::Type> {
225    Ok(syn::Type::Tuple(syn::TypeTuple {
226        paren_token: paren_token(),
227        elems: chars
228            .map(|ref c| {
229                syn::Type::Path(syn::TypePath {
230                    qself: None,
231                    path: syn::Path {
232                        leading_colon: None,
233                        segments: [syn::PathSegment {
234                            ident: cr2i(c),
235                            arguments: syn::PathArguments::None,
236                        }]
237                        .into_iter()
238                        .collect(),
239                    },
240                })
241            })
242            .collect(),
243    }))
244}
245
246#[inline]
247fn paren_token() -> syn::token::Paren {
248    syn::token::Paren {
249        span: proc_macro2::Group::new(proc_macro2::Delimiter::Parenthesis, TokenStream::new())
250            .delim_span(),
251    }
252}
253
254#[inline]
255fn type_nested_equals(chars: RangeInclusive<u8>) -> syn::Result<syn::ImplItem> {
256    Ok(syn::ImplItem::Type(syn::ImplItemType {
257        attrs: vec![],
258        vis: syn::Visibility::Inherited,
259        defaultness: None,
260        type_token: syn::parse2(quote!(type))?,
261        ident: syn::Ident::new("Nested", Span::call_site()),
262        generics: syn::Generics {
263            lt_token: None,
264            params: Punctuated::new(),
265            gt_token: None,
266            where_clause: None,
267        },
268        eq_token: syn::parse2(quote!(=))?,
269        ty: huge_nested_type(chars)?,
270        semi_token: syn::parse2(quote!(;))?,
271    }))
272}
273
274#[inline]
275fn huge_nested_type(chars: RangeInclusive<u8>) -> syn::Result<syn::Type> {
276    Ok(
277        chars.rfold(syn::parse2(quote!(crate::BaseCase))?, |acc, ref c| {
278            syn::Type::Path(syn::TypePath {
279                qself: None,
280                path: syn::Path {
281                    leading_colon: None,
282                    segments: [syn::PathSegment {
283                        ident: syn::Ident::new("BreadthFirstZipped", Span::call_site()),
284                        arguments: syn::PathArguments::AngleBracketed(
285                            syn::AngleBracketedGenericArguments {
286                                colon2_token: None,
287                                lt_token: syn::token::Lt {
288                                    spans: [Span::call_site()],
289                                },
290                                args: [
291                                    syn::GenericArgument::Lifetime(syn::Lifetime {
292                                        apostrophe: Span::call_site(),
293                                        ident: syn::Ident::new("item", Span::call_site()),
294                                    }),
295                                    syn::GenericArgument::Type(syn::Type::Path(syn::TypePath {
296                                        qself: None,
297                                        path: syn::Path {
298                                            leading_colon: None,
299                                            segments: [syn::PathSegment {
300                                                ident: cr2i(c),
301                                                arguments: syn::PathArguments::None,
302                                            }]
303                                            .into_iter()
304                                            .collect(),
305                                        },
306                                    })),
307                                    syn::GenericArgument::Type(acc),
308                                ]
309                                .into_iter()
310                                .collect(),
311                                gt_token: syn::token::Gt {
312                                    spans: [Span::call_site()],
313                                },
314                            },
315                        ),
316                    }]
317                    .into_iter()
318                    .collect(),
319                },
320            })
321        }),
322    )
323}
324
325#[inline]
326fn huge_nested_tuple(chars: RangeInclusive<u8>) -> syn::Result<syn::Type> {
327    Ok(chars.rfold(syn::parse2(quote!(()))?, |acc, ref c| {
328        syn::Type::Tuple(syn::TypeTuple {
329            paren_token: paren_token(),
330            elems: [
331                syn::Type::Path(syn::TypePath {
332                    qself: None,
333                    path: syn::Path {
334                        leading_colon: None,
335                        segments: [syn::PathSegment {
336                            ident: cr2i(c),
337                            arguments: syn::PathArguments::None,
338                        }]
339                        .into_iter()
340                        .collect(),
341                    },
342                }),
343                acc,
344            ]
345            .into_iter()
346            .collect(),
347        })
348    }))
349}
350
351#[inline]
352fn fn_breadth_first() -> syn::Result<syn::ImplItem> {
353    syn::parse2(quote! {
354        #[inline(always)]
355        #[must_use]
356        fn breadth_first(self) -> BreadthFirstManager<'item, Self::Nested> {
357            BreadthFirstManager::new(self.unflatten())
358        }
359    })
360}
361
362#[inline]
363fn type_flattened_equals(chars: RangeInclusive<u8>) -> syn::Result<syn::ImplItem> {
364    Ok(syn::ImplItem::Type(syn::ImplItemType {
365        attrs: vec![],
366        vis: syn::Visibility::Inherited,
367        defaultness: None,
368        type_token: syn::parse2(quote!(type))?,
369        ident: syn::Ident::new("Flattened", Span::call_site()),
370        generics: syn::Generics {
371            lt_token: None,
372            params: Punctuated::new(),
373            gt_token: None,
374            where_clause: None,
375        },
376        eq_token: syn::parse2(quote!(=))?,
377        ty: flat_tuple_type(chars)?,
378        semi_token: syn::parse2(quote!(;))?,
379    }))
380}
381
382#[inline]
383fn fn_flatten(mut chars: RangeInclusive<u8>) -> syn::Result<syn::ImplItem> {
384    let mut a_good_start: syn::ImplItemFn = syn::parse2(quote! {
385        #[inline(always)]
386        #[must_use]
387        fn flatten(self) -> Self::Flattened {}
388    })?;
389    chars.next(); // discard the head
390    a_good_start.block.stmts = vec![
391        syn::Stmt::Local(syn::Local {
392            attrs: vec![],
393            let_token: syn::parse2(quote!(let))?,
394            pat: syn::Pat::Tuple(syn::PatTuple {
395                attrs: vec![],
396                paren_token: paren_token(),
397                elems: chars
398                    .clone()
399                    .map(|ref c| {
400                        syn::Pat::Ident(syn::PatIdent {
401                            attrs: vec![],
402                            by_ref: None,
403                            mutability: None,
404                            ident: cr2i(&(c + TO_LOWERCASE)),
405                            subpat: None,
406                        })
407                    })
408                    .collect(),
409            }),
410            init: Some(syn::LocalInit {
411                eq_token: syn::parse2(quote!(=))?,
412                expr: Box::new(syn::parse2(if chars.len() != 1 {
413                    quote!(self.1.flatten())
414                } else {
415                    // FIXME: The `syn` bug again
416                    quote!(self.1.flatten().0)
417                })?),
418                diverge: None,
419            }),
420            semi_token: syn::parse2(quote!(;))?,
421        }),
422        syn::Stmt::Expr(
423            syn::Expr::Tuple(syn::ExprTuple {
424                attrs: vec![],
425                paren_token: paren_token(),
426                elems: [syn::parse2(quote!(self.0))?]
427                    .into_iter()
428                    .chain(chars.map(|c| {
429                        syn::Expr::Path(syn::ExprPath {
430                            attrs: vec![],
431                            qself: None,
432                            path: syn::Path {
433                                leading_colon: None,
434                                segments: [syn::PathSegment {
435                                    ident: cr2i(&(c + TO_LOWERCASE)),
436                                    arguments: syn::PathArguments::None,
437                                }]
438                                .into_iter()
439                                .collect(),
440                            },
441                        })
442                    }))
443                    .collect(),
444            }),
445            None,
446        ),
447    ];
448    Ok(syn::ImplItem::Fn(a_good_start))
449}
450
451#[inline]
452fn fn_unflatten(chars: RangeInclusive<u8>) -> syn::Result<syn::ImplItem> {
453    let mut a_good_start: syn::ImplItemFn = syn::parse2(quote! {
454        #[inline(always)]
455        #[must_use]
456        fn unflatten(self) -> Self::Nested {}
457    })?;
458    a_good_start.block.stmts = vec![
459        syn::Stmt::Local(syn::Local {
460            attrs: vec![],
461            let_token: syn::parse2(quote!(let))?,
462            pat: syn::Pat::Tuple(syn::PatTuple {
463                attrs: vec![],
464                paren_token: paren_token(),
465                elems: chars
466                    .clone()
467                    .map(|c| {
468                        syn::Pat::Ident(syn::PatIdent {
469                            attrs: vec![],
470                            by_ref: None,
471                            mutability: None,
472                            ident: cr2i(&(c + TO_LOWERCASE)),
473                            subpat: None,
474                        })
475                    })
476                    .collect(),
477            }),
478            init: Some(syn::LocalInit {
479                eq_token: syn::parse2(quote!(=))?,
480                expr: Box::new(syn::parse2(if chars.len() != 1 {
481                    quote!(self)
482                } else {
483                    // FIXME: The `syn` bug again
484                    quote!(self.0)
485                })?),
486                diverge: None,
487            }),
488            semi_token: syn::parse2(quote!(;))?,
489        }),
490        syn::Stmt::Expr(
491            chars.rfold(
492                syn::parse2(quote!(BaseCase(::core::cell::Cell::new(true))))?,
493                |acc, ref c| {
494                    syn::Expr::Call(syn::ExprCall {
495                        attrs: vec![],
496                        func: Box::new(syn::Expr::Path(syn::ExprPath {
497                            attrs: vec![],
498                            qself: None,
499                            path: syn::Path {
500                                leading_colon: None,
501                                segments: [
502                                    syn::PathSegment {
503                                        ident: syn::Ident::new(
504                                            "BreadthFirstZipped",
505                                            Span::call_site(),
506                                        ),
507                                        arguments: syn::PathArguments::None,
508                                    },
509                                    syn::PathSegment {
510                                        ident: syn::Ident::new("new", Span::call_site()),
511                                        arguments: syn::PathArguments::None,
512                                    },
513                                ]
514                                .into_iter()
515                                .collect(),
516                            },
517                        })),
518                        paren_token: paren_token(),
519                        args: [
520                            syn::Expr::Path(syn::ExprPath {
521                                attrs: vec![],
522                                qself: None,
523                                path: syn::Path {
524                                    leading_colon: None,
525                                    segments: [syn::PathSegment {
526                                        ident: cr2i(&(c + TO_LOWERCASE)),
527                                        arguments: syn::PathArguments::None,
528                                    }]
529                                    .into_iter()
530                                    .collect(),
531                                },
532                            }),
533                            acc,
534                        ]
535                        .into_iter()
536                        .collect(),
537                    })
538                },
539            ),
540            None,
541        ),
542    ];
543    Ok(syn::ImplItem::Fn(a_good_start))
544}