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
51fn 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}