mashup_impl/
lib.rs

1#![doc(html_root_url = "https://docs.rs/mashup-impl/0.1.14+deprecated")]
2#![cfg_attr(feature = "cargo-clippy", allow(renamed_and_removed_lints))]
3#![cfg_attr(feature = "cargo-clippy", allow(needless_borrowed_reference))]
4#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
5
6#[macro_use]
7extern crate proc_macro_hack;
8
9extern crate proc_macro2;
10use proc_macro2::{Delimiter, Ident, Literal, TokenStream, TokenTree};
11
12use std::collections::BTreeMap as Map;
13use std::str::FromStr;
14
15proc_macro_item_impl! {
16    pub fn mashup_macro_impl(s: &str) -> String {
17        let tts = TokenStream::from_str(s).unwrap();
18        let input = parse(tts);
19
20        let mut macros = String::new();
21        for (name, concat) in input {
22            macros += &make_macro(name, concat);
23        }
24        macros
25    }
26}
27
28type Input = Map<String, SubstitutionMacro>;
29
30struct SubstitutionMacro {
31    attrs: Vec<TokenStream>,
32    patterns: Vec<Concat>,
33}
34
35struct Concat {
36    tag: TokenStream,
37    pieces: Vec<TokenTree>,
38}
39
40impl Concat {
41    fn mashup(&self) -> String {
42        self.pieces
43            .iter()
44            .map(|tt| match *tt {
45                TokenTree::Literal(ref lit) => identify(lit),
46                _ => tt.to_string(),
47            }).collect()
48    }
49}
50
51/// Returns a `to_string()` impl for `Literals`
52/// which is safe for use in idents
53fn identify(lit: &Literal) -> String {
54    let stringified = lit.to_string();
55    match stringified.chars().next() {
56        Some(ch) if ch == '"' || ch == '\'' => stringified.trim_matches(ch).replace('-', "_"),
57        _ => stringified,
58    }
59}
60
61fn parse(tts: TokenStream) -> Input {
62    let mut tts = tts.into_iter().peekable();
63    let mut map = Map::new();
64    let mut attrs = Vec::new();
65
66    while let Some(next) = tts.next() {
67        match next {
68            TokenTree::Punct(punct) => {
69                if punct.as_char() == '#' {
70                    if let Some(TokenTree::Group(group)) = tts.next() {
71                        if group.delimiter() == Delimiter::Bracket {
72                            attrs.push(group.stream());
73                            continue;
74                        }
75                    }
76                }
77                panic!("unexpected mashup input");
78            }
79            TokenTree::Ident(ident) => {
80                let name = ident.to_string();
81
82                let tag = match tts.next() {
83                    Some(TokenTree::Group(group)) => {
84                        assert_eq!(group.delimiter(), Delimiter::Bracket);
85                        group.stream()
86                    }
87                    _ => panic!("unexpected mashup input"),
88                };
89
90                assert_eq!(tts.next().unwrap().to_string(), "=");
91
92                let mut pieces = Vec::new();
93                while let Some(tt) = tts.next() {
94                    match tt {
95                        TokenTree::Ident(mut ident) => {
96                            let fragment = ident.to_string();
97                            if fragment.starts_with("r#") {
98                                ident = Ident::new(&fragment[2..], ident.span());
99                            }
100                            if fragment == "env" {
101                                let resolve_env = match tts.peek() {
102                                    Some(&TokenTree::Punct(ref p)) => p.as_char() == '!',
103                                    _ => false,
104                                };
105                                if resolve_env {
106                                    match tts.next().and_then(|_| tts.next()) {
107                                        Some(TokenTree::Group(ref grp))
108                                            if grp.delimiter() == Delimiter::Parenthesis =>
109                                        {
110                                            match grp.stream().into_iter().next() {
111                                                Some(TokenTree::Literal(ref varname)) => {
112                                                    let resolved = std::env::var(identify(varname))
113                                                        .unwrap_or_else(|_| {
114                                                            panic!(
115                                                                "unresolvable mashup env var {}",
116                                                                varname
117                                                            )
118                                                        });
119                                                    pieces.push(TokenTree::Literal(
120                                                        Literal::string(&resolved),
121                                                    ));
122                                                    continue;
123                                                }
124                                                _ => panic!("unexpected mashup input"),
125                                            }
126                                        }
127                                        _ => panic!("unexpected mashup input"),
128                                    }
129                                }
130                            }
131                            pieces.push(TokenTree::Ident(ident));
132                        }
133                        TokenTree::Literal(_) => {
134                            pieces.push(tt);
135                        }
136                        TokenTree::Punct(tt) => match tt.as_char() {
137                            '_' | '\'' => pieces.push(TokenTree::Punct(tt)),
138                            ';' => break,
139                            other => panic!("unexpected op {:?}", other),
140                        },
141                        TokenTree::Group(_) => panic!("unexpected mashup input"),
142                    }
143                }
144
145                let substitution_macro = map.entry(name.to_string()).or_insert_with(|| {
146                    SubstitutionMacro {
147                        attrs: Vec::new(),
148                        patterns: Vec::new(),
149                    }
150                });
151
152                substitution_macro.attrs.append(&mut attrs);
153                substitution_macro.patterns.push(Concat {
154                    tag: tag,
155                    pieces: pieces,
156                });
157            }
158            _ => panic!("unexpected mashup input"),
159        }
160    }
161
162    map
163}
164
165fn make_macro(name: String, substitution_macro: SubstitutionMacro) -> String {
166    let mut attrs = String::new();
167    let mut rules = String::new();
168
169    for attr in substitution_macro.attrs {
170        attrs += &format!("#[{}]", attr);
171    }
172
173    rules += &"
174        // Open parenthesis.
175        (@($($v:tt)*) ($($stack:tt)*) ($($first:tt)*) $($rest:tt)*) => {
176            __mashup_replace! {
177                @($($v)*) (() $($stack)*) $($first)* __mashup_close_paren $($rest)*
178            }
179        };
180
181        // Open square bracket.
182        (@($($v:tt)*) ($($stack:tt)*) [$($first:tt)*] $($rest:tt)*) => {
183            __mashup_replace! {
184                @($($v)*) (() $($stack)*) $($first)* __mashup_close_bracket $($rest)*
185            }
186        };
187
188        // Open curly brace.
189        (@($($v:tt)*) ($($stack:tt)*) {$($first:tt)*} $($rest:tt)*) => {
190            __mashup_replace! {
191                @($($v)*) (() $($stack)*) $($first)* __mashup_close_brace $($rest)*
192            }
193        };
194
195        // Close parenthesis.
196        (@($($v:tt)*) (($($close:tt)*) ($($top:tt)*) $($stack:tt)*) __mashup_close_paren $($rest:tt)*) => {
197            __mashup_replace! {
198                @($($v)*) (($($top)* ($($close)*)) $($stack)*) $($rest)*
199            }
200        };
201
202        // Close square bracket.
203        (@($($v:tt)*) (($($close:tt)*) ($($top:tt)*) $($stack:tt)*) __mashup_close_bracket $($rest:tt)*) => {
204            __mashup_replace! {
205                @($($v)*) (($($top)* [$($close)*]) $($stack)*) $($rest)*
206            }
207        };
208
209        // Close curly brace.
210        (@($($v:tt)*) (($($close:tt)*) ($($top:tt)*) $($stack:tt)*) __mashup_close_brace $($rest:tt)*) => {
211            __mashup_replace! {
212                @($($v)*) (($($top)* {$($close)*}) $($stack)*) $($rest)*
213            }
214        };
215        "
216        .replace("__mashup_replace", &name);
217
218    let mut all = String::new();
219    for (i, p) in substitution_macro.patterns.iter().enumerate() {
220        all += " ";
221        all += &p.mashup();
222
223        let mut quadratic = String::new();
224        for j in 0..substitution_macro.patterns.len() {
225            quadratic += &format!(" $v{}:tt", j);
226        }
227
228        rules += &"
229            // Replace target tokens with concatenated ident.
230            (@(__mashup_all) (($($top:tt)*) $($stack:tt)*) __mashup_pattern $($rest:tt)*) => {
231                __mashup_replace! {
232                    @(__mashup_continue) (($($top)* __mashup_current) $($stack)*) $($rest)*
233                }
234            };
235            "
236            .replace("__mashup_replace", &name)
237            .replace("__mashup_pattern", &p.tag.to_string())
238            .replace("__mashup_all", &quadratic)
239            .replace("__mashup_current", &format!("$v{}", i))
240            .replace("__mashup_continue", &quadratic.replace(":tt", ""));
241    }
242
243    rules += &"
244        // Munch a token that is not one of the targets.
245        (@($($v:tt)*) (($($top:tt)*) $($stack:tt)*) $first:tt $($rest:tt)*) => {
246            __mashup_replace! {
247                @($($v)*) (($($top)* $first) $($stack)*) $($rest)*
248            }
249        };
250
251        // Done.
252        (@($($v:tt)*) (($($top:tt)+))) => {
253            $($top)+
254        };
255
256        // Launch.
257        ($($tt:tt)*) => {
258            __mashup_replace! {
259                @(__mashup_all) (()) $($tt)*
260            }
261        }
262        "
263        .replace("__mashup_replace", &name)
264        .replace("__mashup_all", &all);
265
266    format!("{} macro_rules! {} {{ {} }}", attrs, name, rules)
267}