1#![doc = include_str!("../README.md")]
2#![allow(clippy::needless_doctest_main)]
3
4use proc_macro::{
5 Delimiter, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream,
6 TokenTree,
7};
8
9fn span_setter(span: Span) -> impl Fn(TokenTree) -> TokenTree {
10 move |mut tt| {
11 tt.set_span(span);
12 tt
13 }
14}
15
16#[must_use]
17fn stream<I>(iter: I) -> TokenStream
18where I: IntoIterator<Item = TokenTree>,
19{
20 TokenStream::from_iter(iter)
21}
22
23fn err(msg: &str, span: Span) -> TokenStream {
24 let s = span_setter(span);
25 stream([
26 s(Punct::new(':', Joint).into()),
27 s(Punct::new(':', Joint).into()),
28 s(Ident::new("core", span).into()),
29 s(Punct::new(':', Joint).into()),
30 s(Punct::new(':', Joint).into()),
31 s(Ident::new("compile_error", span).into()),
32 s(Punct::new('!', Joint).into()),
33 s(Group::new(Delimiter::Brace, stream([
34 s(Literal::string(msg).into()),
35 ])).into()),
36 ])
37}
38
39#[derive(Default, Clone)]
40struct Closure<'a> {
41 it: Option<TokenTree>,
42 catch_it: &'a str,
43}
44impl Closure<'_> {
45 fn make_closure(&mut self) -> TokenStream {
46 let Some(it) = self.it.take() else {
47 return TokenStream::new();
48 };
49 let s = span_setter(it.span());
50 stream([
51 s(Punct::new('|', Joint).into()),
52 it,
53 s(Punct::new('|', Joint).into()),
54 ])
55 }
56
57 fn ext_proc_it(&mut self, input: TokenStream) -> TokenStream {
58 let ext = &mut Self { catch_it: self.catch_it, ..Default::default() };
59 let proc_it = ext.proc_it(input);
60 ext.make_closure().into_iter().chain(proc_it).collect()
61 }
62
63 fn proc_it(&mut self, input: TokenStream) -> TokenStream {
64 let iter = &mut input.into_iter().peekable();
65 let mut result = TokenStream::new();
66
67 while let Some(tt) = iter.next() {
68 match tt {
69 TokenTree::Group(group)
70 if group.delimiter() == Delimiter::Parenthesis =>
71 {
72 let grouped = self.ext_proc_it(group.stream());
73 result.extend([
74 Group::new(group.delimiter(), grouped).into(),
75 ] as [TokenTree; 1]);
76 },
77 TokenTree::Group(group) => {
78 let grouped = self.proc_it(group.stream());
79 result.extend([
80 Group::new(group.delimiter(), grouped).into(),
81 ] as [TokenTree; 1]);
82 },
83 TokenTree::Ident(ref ident)
84 if ident.to_string() == self.catch_it =>
85 {
86 result.extend([self.it.get_or_insert(tt).clone()]);
87 },
88 TokenTree::Ident(_) | TokenTree::Literal(_) => {
89 result.extend([tt]);
90 },
91 TokenTree::Punct(ref punct)
92 if matches!(punct.as_char(), ',' | ';') =>
93 {
94 result.extend([tt]);
95 result.extend(self.ext_proc_it(iter.collect()));
96 },
97 TokenTree::Punct(ref punct)
98 if punct.as_char() == '='
99 && punct.spacing() == Joint
100 && iter.peek().is_some_and(|p| {
101 matches!(p, TokenTree::Punct(p)
102 if p.as_char() == '>')
103 }) =>
104 {
105 result.extend([tt, iter.next().unwrap()]);
106 result.extend(self.ext_proc_it(iter.collect()));
107 },
108 TokenTree::Punct(_) => {
109 result.extend([tt]);
110 },
111 }
112 }
113
114 result
115 }
116}
117
118fn get_catch_it<F>(attr: TokenStream, f: F) -> TokenStream
119where F: FnOnce(&str) -> TokenStream,
120{
121 let iter = &mut attr.into_iter();
122 let catch_it = match iter.next() {
123 Some(TokenTree::Ident(ident)) => &*ident.to_string(),
124 Some(attr) => return err("invalid input", attr.span()),
125 _ => "it",
126 };
127 if let Some(extra) = iter.next() {
128 return err("invalid input", extra.span());
129 }
130 f(catch_it)
131}
132
133#[proc_macro_attribute]
155pub fn closure_it(attr: TokenStream, item: TokenStream) -> TokenStream {
156 get_catch_it(attr, |catch_it| {
157 Closure { catch_it, ..Default::default() }
158 .ext_proc_it(item)
159 })
160}