1use proc_macro::TokenStream;
97use proc_macro2::{TokenStream as TokenStream2, TokenTree};
98use quote::quote;
99
100use std::collections::HashSet;
101
102struct PrintOption {
103 printer: Printer,
104 time_unit: TimeUnit,
105 features: Vec<String>,
106 function_name: String,
107 original_function: syn::ItemFn,
108}
109impl PrintOption {
110 fn parse(attr: TokenStream, function: TokenStream) -> Self {
112 let (printer, time_unit, features) = Self::parse_attributes(attr);
113 let (function_name, original_function) = Self::parse_function(function);
114
115 Self {
116 printer,
117 time_unit,
118 features,
119 function_name,
120 original_function,
121 }
122 }
123 fn parse_function(function: TokenStream) -> (String, syn::ItemFn) {
124 let original_function: syn::ItemFn = match syn::parse(function) {
125 Ok(item_fn) => item_fn,
126 Err(err) => panic!("{}", err)
127 };
128 let function_name = original_function.sig.ident.to_string();
129
130 (function_name, original_function)
131 }
132 fn parse_attributes(attr: TokenStream) -> (Printer, TimeUnit, Vec<String>) {
133 let mut optional_printer: Option<Printer> = None;
134 let mut optional_time_unit: Option<TimeUnit> = None;
135 let mut features = Vec::new();
136
137 let attr = proc_macro2::TokenStream::from(attr);
138 for (index, token_tree) in attr.into_iter().enumerate() {
139 if index % 2 == 1 { let is_comma = Self::token_is_comma(token_tree);
141 if !is_comma {
142 panic!("Commas(,) are used as attribute delimiter.")
143 }
144 continue;
145 }
146
147 match token_tree {
148 TokenTree::Ident(ident) => {
149 let tag = ident.to_string();
150 Self::parse_from_tag(tag, &mut optional_printer, &mut optional_time_unit);
151 },
152 TokenTree::Literal(literal) => {
153 let tag = literal.to_string();
154 let tag = tag.replace("\"", ""); Self::parse_from_tag(tag, &mut optional_printer, &mut optional_time_unit);
156 },
157 TokenTree::Group(group) => {
158 features = Self::parse_features(group);
159 },
160 _ => {
161 panic!("Unknown attributes")
162 },
163 }
164 }
165
166 let printer = match optional_printer {
167 Some(printer) => printer,
168 None => Printer::default(),
169 };
170 let time_unit = match optional_time_unit {
171 Some(time_unit) => time_unit,
172 None => TimeUnit::default(),
173 };
174
175 (printer, time_unit, features)
176 }
177 fn parse_from_tag(
178 tag: String,
179 optional_printer: &mut Option<Printer>,
180 optional_time_unit: &mut Option<TimeUnit>,
181 ) {
182 let printer = match tag.as_ref() {
184 "stdout" => Some(Printer::StdOut),
185 "stderr" => Some(Printer::StdErr),
186 "both" => Some(Printer::Both),
187 _ => None,
188 };
189 if printer.is_some() {
190 if optional_printer.is_none() {
191 *optional_printer = printer;
192 return
193 } else {
194 panic!("Printer attribute is assigned multiple times.")
195 }
196 }
197
198 let time_unit = match tag.as_ref() {
200 "auto" => Some(TimeUnit::Auto),
201 "s" => Some(TimeUnit::S),
202 "ms" => Some(TimeUnit::Ms),
203 "us" => Some(TimeUnit::Us),
204 "ns" => Some(TimeUnit::Ns),
205 _ => {
206 panic!("Attribute allows printer settings(stdout, stderr, both) and time unit(auto, s, ms, us, ns)")
207 }
208 };
209 if time_unit.is_some() {
210 if optional_time_unit.is_none() {
211 *optional_time_unit = time_unit;
212 return
213 } else {
214 panic!("Time unit attribute is assigned multiple times.")
215 }
216 }
217 }
218 fn parse_features(group: proc_macro2::Group) -> Vec<String> {
219 let mut features_set = HashSet::new();
220
221 for (index, token_tree) in group.stream().into_iter().enumerate() {
222 if index % 2 == 1 {
223 let is_comma = Self::token_is_comma(token_tree);
224 if !is_comma {
225 panic!("Commas(,) are used as feature delimiter.")
226 }
227 continue;
228 }
229
230 let feature = match token_tree {
231 TokenTree::Ident(ident) => {
232 ident.to_string()
233 },
234 TokenTree::Literal(literal) => {
235 let tag = literal.to_string();
236 tag.replace("\"", "")
237 },
238 _ => {
239 panic!("Features allows only attribute");
240 },
241 };
242 features_set.insert(feature);
243 }
244
245 features_set.into_iter().collect()
246 }
247 fn token_is_comma(token_tree: TokenTree) -> bool {
248 if let TokenTree::Punct(punct) = token_tree {
249 if punct.to_string() == "," {
250 true
251 } else {
252 false
253 }
254 } else {
255 false
256 }
257 }
258
259 fn new_token_stream(&self) -> TokenStream {
261 let function_name = &self.function_name;
262
263 let attrs = &self.original_function.attrs;
264 let vis = &self.original_function.vis;
265 let constness = &self.original_function.sig.constness;
266 let asyncness = &self.original_function.sig.asyncness;
267 let unsafety = &self.original_function.sig.unsafety;
268 let abi = &self.original_function.sig.abi;
269 let ident = &self.original_function.sig.ident;
270 let generics = &self.original_function.sig.generics;
271 let inputs = &self.original_function.sig.inputs;
272 let variadic = &self.original_function.sig.variadic;
273 let output = &self.original_function.sig.output;
274 let block = &self.original_function.block;
275
276 let duration_token = self.time_unit.duration_token();
277 let print_token = self.printer.print_token(&self.time_unit, function_name);
278
279 let features = &self.features;
280
281 let tokens = if features.len() == 0 {
282 quote! {
283 #(#attrs),*
284 #vis #constness #asyncness #unsafety #abi fn #ident #generics(#inputs #variadic) #output {
285 let start = std::time::Instant::now();
286 let result = #block;
287 #duration_token
288 #print_token
289 result
290 }
291 }
292 } else {
293 quote! {
294 #(#attrs),*
295 #vis #constness #asyncness #unsafety #abi fn #ident #generics(#inputs #variadic) #output {
296 #[cfg(any(#(feature=#features),*))]
297 {
298 let start = std::time::Instant::now();
299 let result = #block;
300 #duration_token
301 #print_token
302 result
303 }
304 #[cfg(not(any(#(feature=#features),*)))]
305 #block
306 }
307 }
308 };
309
310 tokens.into()
311 }
312}
313
314enum Printer {
315 StdOut,
316 StdErr,
317 Both,
318}
319impl Default for Printer {
320 fn default() -> Self {
321 Self::StdOut
322 }
323}
324impl Printer {
325 fn print_token(&self, time_unit: &TimeUnit, function_name: &String) -> TokenStream2 {
326 match self {
327 Self::StdOut => {
328 time_unit.print_to_stdout_token(function_name)
329 },
330 Self::StdErr => {
331 time_unit.print_to_stderr_token(function_name)
332 },
333 Self::Both => {
334 let print_to_stdout_token = time_unit.print_to_stdout_token(function_name);
335 let print_to_stderr_token = time_unit.print_to_stderr_token(function_name);
336 quote! {
337 #print_to_stdout_token
338 #print_to_stderr_token
339 }
340 },
341 }
342 }
343}
344
345enum TimeUnit {
346 Auto,
347 S,
348 Ms,
349 Us,
350 Ns,
351}
352impl Default for TimeUnit {
353 fn default() -> Self {
354 Self::Auto
355 }
356}
357impl TimeUnit {
358 fn duration_token(&self) -> TokenStream2 {
359 match self {
360 Self::Auto => {
361 quote! {
362 let duration = start.elapsed();
363 }
364 },
365 Self::S => {
366 quote! {
367 let duration = start.elapsed().as_secs();
368 }
369 },
370 Self::Ms => {
371 quote! {
372 let duration = start.elapsed().as_millis();
373 }
374 },
375 Self::Us => {
376 quote! {
377 let duration = start.elapsed().as_micros();
378 }
379 },
380 Self::Ns => {
381 quote! {
382 let duration = start.elapsed().as_nanos();
383 }
384 },
385 }
386 }
387 fn print_to_stdout_token(&self, function_name: &String) -> TokenStream2 {
388 match self {
389 Self::Auto => {
390 quote! {
391 println!("{}, {:?}", #function_name, duration);
392 }
393 },
394 Self::S => {
395 quote! {
396 println!("{}, {}s", #function_name, duration);
397 }
398 },
399 Self::Ms => {
400 quote! {
401 println!("{}, {}ms", #function_name, duration);
402 }
403 },
404 Self::Us => {
405 quote! {
406 println!("{}, {}us", #function_name, duration);
407 }
408 },
409 Self::Ns => {
410 quote! {
411 println!("{}, {}ns", #function_name, duration);
412 }
413 },
414 }
415 }
416 fn print_to_stderr_token(&self, function_name: &String) -> TokenStream2 {
417 match self {
418 Self::Auto => {
419 quote! {
420 eprintln!("{}, {:?}", #function_name, duration);
421 }
422 },
423 Self::S => {
424 quote! {
425 eprintln!("{}, {}s", #function_name, duration);
426 }
427 },
428 Self::Ms => {
429 quote! {
430 eprintln!("{}, {}ms", #function_name, duration);
431 }
432 },
433 Self::Us => {
434 quote! {
435 eprintln!("{}, {}us", #function_name, duration);
436 }
437 },
438 Self::Ns => {
439 quote! {
440 eprintln!("{}, {}ns", #function_name, duration);
441 }
442 },
443 }
444 }
445}
446
447#[proc_macro_attribute]
452pub fn print_elapsed(attr: TokenStream, function: TokenStream) -> TokenStream {
453 let print_option = PrintOption::parse(attr, function);
454 let new_token_stream = print_option.new_token_stream();
455 new_token_stream
456}