extdot_impl/
lib.rs

1extern crate proc_macro;
2
3mod helpers;
4
5use helpers::IdentifyFirstLast;
6use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
7use proc_macro_hack::proc_macro_hack;
8use std::iter::FromIterator;
9
10#[proc_macro_hack]
11pub fn expr(input: TokenStream) -> TokenStream {
12    let input = input.into_iter();
13
14    let trees = extdot(input);
15
16    let output = TokenStream::from_iter(trees);
17
18    TokenStream::from(TokenTree::Group(Group::new(Delimiter::Brace, output)))
19}
20
21/// Allows the use of the extended dot notation in expressions.
22#[proc_macro]
23pub fn item(input: TokenStream) -> TokenStream {
24    let input = input.into_iter();
25
26    let trees = extdot(input);
27
28    TokenStream::from_iter(trees)
29}
30
31
32fn extdot(trees: impl Iterator<Item = TokenTree>) -> impl Iterator<Item = TokenTree> {
33    let mut last_expression = vec![];
34    let mut was_dot = false;
35
36    trees.identify_first_last().flat_map(move |(_first, last, token)| {
37        let mut rv: Vec<Box<Iterator<Item = TokenTree>>> = vec![];
38
39        let token = match (was_dot, &token) {
40            (true, TokenTree::Group(ref grp)) => {
41                was_dot = false;
42
43                // remove last '.' that's part of extdot
44                last_expression.pop();
45
46                // re-arrange extended dot syntax into something parsable
47                transliterate(&mut last_expression, grp)
48            }
49            (false, TokenTree::Group(ref grp)) => {
50                let block = extdot(grp.stream().into_iter());
51                let block = TokenStream::from_iter(block);
52
53                TokenTree::Group(Group::new(grp.delimiter(), block))
54            }
55            (_, TokenTree::Punct(ref p)) if p.as_char() == '.' => {
56                was_dot = true;
57                token
58            }
59            _ => {
60                was_dot = false;
61                token
62            }
63        };
64
65        let last_token = last_expression.last().cloned();
66        last_expression.push(token.clone());
67
68        if last || !is_expressionable(&last_token, &token) {
69            rv.push(Box::new(last_expression.clone().into_iter()));
70            last_expression.clear();
71        }
72
73        rv.into_iter().flatten()
74    })
75}
76
77fn transliterate(expr: &mut Vec<TokenTree>, grp: &Group) -> TokenTree {
78    use std::str::FromStr;
79
80    if grp.stream().is_empty() {
81        #[cfg(nightly)]
82        grp.span().warning("empty extended dot, consider removing it");
83
84        return TokenTree::Group(Group::new(Delimiter::Brace, grp.stream()));
85    }
86
87    let mut output: Vec<TokenTree> = vec![];
88
89    output.extend(TokenStream::from_str("let mut it = ").unwrap());
90    output.append(expr);
91    output.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
92
93    // Process group tokens before processing implicits and running replace_it so that recursive
94    // usage is handled properly.
95    let mut gstream = extdot(grp.stream().into_iter()).into_iter().collect::<Vec<_>>();
96
97    let split_subexprs = |tok: &TokenTree| match tok {
98        TokenTree::Punct(ref p) if p.as_char() == ',' => true,
99        _ => false,
100    };
101
102    for mut subexpr in gstream.split_mut(split_subexprs) {
103        if subexpr.is_empty() {
104            output.extend(TokenStream::from_str("it").unwrap());
105        } else if is_ident(&subexpr) {
106            replace_it(subexpr);
107            output.extend_from_slice(subexpr);
108            output.extend(TokenStream::from_str("(it)").unwrap());
109        } else if is_fn_call(&subexpr) && has_no_it(&subexpr) {
110            output.extend(implicit_method_call(subexpr));
111        } else {
112            replace_it(&mut subexpr);
113            output.extend(subexpr.iter().cloned());
114        }
115
116        output.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
117    }
118
119    output.pop();
120
121    TokenTree::Group(Group::new(
122        Delimiter::Brace,
123        TokenStream::from_iter(output.into_iter()),
124    ))
125}
126
127fn replace_it(block: &mut [TokenTree]) {
128    for token in block {
129        match token {
130            TokenTree::Ident(ref idnt) if idnt.to_string() == "it" => {
131                // Replace Span with one that can resolve to extdot's `it`
132                *token = TokenTree::Ident(Ident::new("it", Span::call_site()));
133            }
134            TokenTree::Group(ref grp) => {
135                let mut nested = grp.stream().into_iter().collect::<Vec<_>>();
136
137                replace_it(&mut nested);
138
139                *token = TokenTree::Group(Group::new(
140                    grp.delimiter(),
141                    TokenStream::from_iter(nested.into_iter()),
142                ))
143            }
144            _ => (),
145        }
146    }
147}
148
149fn is_expressionable(last_token: &Option<TokenTree>, token: &TokenTree) -> bool {
150    match token {
151        TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Parenthesis => true,
152        TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Brace => true,
153        TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Bracket => true,
154        TokenTree::Ident(_) => true,
155        TokenTree::Literal(_) => true,
156        TokenTree::Punct(ref punct) if punct.as_char() == '.' => true,
157        TokenTree::Punct(ref punct) if punct.as_char() == ':' && punct.spacing() == Spacing::Joint => true,
158        TokenTree::Punct(ref punct) if punct.as_char() == ':' && punct.spacing() == Spacing::Alone => {
159            match last_token {
160                Some(TokenTree::Punct(ref p)) if p.as_char() == ':' && p.spacing() == Spacing::Joint => true,
161                _ => false,
162            }
163        }
164        TokenTree::Punct(ref punct) if punct.as_char() == '<' => true,
165        TokenTree::Punct(ref punct) if punct.as_char() == '>' => true,
166        TokenTree::Punct(ref punct) if punct.as_char() == '?' => true,
167        _ => false,
168    }
169}
170
171fn is_ident(trees: &[TokenTree]) -> bool {
172    for token in trees {
173        match token {
174            TokenTree::Ident(_) => (),
175            TokenTree::Punct(ref punct) if punct.as_char() == ':' => (),
176            TokenTree::Literal(_) => return false,
177            TokenTree::Group(_) => return false,
178            TokenTree::Punct(_) => return false,
179        }
180    }
181
182    true
183}
184
185fn implicit_method_call(trees: &[TokenTree]) -> Vec<TokenTree> {
186    let mut output = vec![];
187
188    let mut trees = trees.iter();
189
190    if let Some(mut last_token) = trees.next() {
191        for token in trees {
192            match (last_token, token) {
193                (TokenTree::Ident(_), TokenTree::Group(ref grp))
194                    if grp.delimiter() == Delimiter::Parenthesis => {
195                        output.push(TokenTree::Ident(Ident::new("it", Span::call_site())));
196                        output.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
197                        output.push(last_token.clone());
198                    }
199                _ => output.push(last_token.clone()),
200            }
201
202            last_token = token;
203        }
204
205        output.push(last_token.clone());
206    }
207
208    output
209}
210
211fn has_no_it(trees: &[TokenTree]) -> bool {
212    for token in trees {
213        match token {
214            TokenTree::Ident(ref idnt) if idnt.to_string() == "it" => return false,
215            _ => ()
216        }
217    }
218
219    true
220}
221
222fn is_fn_call(trees: &[TokenTree]) -> bool {
223    let mut last_token = None;
224
225    for token in trees {
226        match token {
227            TokenTree::Group(ref grp) if grp.delimiter() == Delimiter::Parenthesis => {
228                if let Some(TokenTree::Ident(_)) = last_token {
229                    return true
230                }
231            }
232            _ => ()
233        }
234
235        last_token = Some(token.clone());
236    }
237
238    false
239}