use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, spanned::Spanned, Attribute, AttributeArgs, Block, Error, Item, ItemFn, Lit,
Meta, NestedMeta, Path, Result, Signature, Visibility,
};
const UID_SUFFIX: &str = "x7bf707c839bc2554fa3f1913a8dc699b68236726c5da18b31f660948ca7f542a267de9b";
#[proc_macro_attribute]
pub fn timer_println(attr: TokenStream, input: TokenStream) -> TokenStream {
builder(
&parse_macro_input!(attr as AttributeArgs),
&parse_macro_input!(input as Item),
"e!( println! ),
)
.unwrap()
}
#[proc_macro_attribute]
pub fn timer_log_info(attr: TokenStream, input: TokenStream) -> TokenStream {
builder(
&parse_macro_input!(attr as AttributeArgs),
&parse_macro_input!(input as Item),
"e!( log::info! ),
)
.unwrap()
}
#[proc_macro_attribute]
pub fn timer_log_debug(attr: TokenStream, input: TokenStream) -> TokenStream {
builder(
&parse_macro_input!(attr as AttributeArgs),
&parse_macro_input!(input as Item),
"e!( log::debug! ),
)
.unwrap()
}
#[proc_macro_attribute]
pub fn timer_log_trace(attr: TokenStream, input: TokenStream) -> TokenStream {
builder(
&parse_macro_input!(attr as AttributeArgs),
&parse_macro_input!(input as Item),
"e!( log::trace! ),
)
.unwrap()
}
#[derive(Debug, Copy, Clone)]
enum TimeUnit {
S,
MS,
US,
NS,
}
impl TimeUnit {
fn quote(self) -> TokenStream2 {
match self {
TimeUnit::S => quote!(as_secs()),
TimeUnit::MS => quote!(as_millis()),
TimeUnit::US => quote!(as_micros()),
TimeUnit::NS => quote!(as_nanos()),
}
}
}
impl std::fmt::Display for TimeUnit {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
TimeUnit::S => write!(f, "s"),
TimeUnit::MS => write!(f, "ms"),
TimeUnit::US => write!(f, "us"),
TimeUnit::NS => write!(f, "ns"),
}
}
}
impl std::convert::From<String> for TimeUnit {
fn from(s: String) -> Self {
match s.to_uppercase().as_str() {
"S" => TimeUnit::S,
"MS" => TimeUnit::MS,
"US" => TimeUnit::US,
"NS" => TimeUnit::NS,
_ => panic!("Invalid unit of time, only `s`, `ms`, `us`, `ns` are supported"),
}
}
}
fn builder(args: &AttributeArgs, body: &Item, outputter: &TokenStream2) -> Result<TokenStream> {
let (f_attrs, f_vis, f_sig, f_block) = parse_body(body)?;
let f_name = &f_sig.ident.to_string();
let (t_unit, o_format) = parse_args(args, f_name)?;
return Ok((move || {
let t_formatter = t_unit.quote();
let v_now = format_ident!("now_{}", UID_SUFFIX);
let v_result = format_ident!("result_{}", UID_SUFFIX);
let f_stmts = &f_block.stmts;
quote!(
#(#f_attrs)* #f_vis #f_sig {
let #v_now = std::time::Instant::now();
let #v_result = (move ||{ #(#f_stmts)* })();
#outputter(#o_format, #v_now . elapsed(). #t_formatter );
return #v_result;
}
)
})()
.into());
fn parse_args(args: &AttributeArgs, fn_name: &String) -> Result<(TimeUnit, String)> {
match &args[..] {
[] => Ok((TimeUnit::MS, format!("fn:{} cost {{}}ms", fn_name))),
[u] => {
let u = read_time_unit(u)?;
Ok((u, format!("fn:{} cost {{}}{}", fn_name, u)))
}
[u, f] => Ok((read_time_unit(u)?, read_output_format(f)?)),
_ => panic!("Invalid arguments, usage: [(TimeUnit[, OutputFormatString])]"),
}
}
fn parse_body(body: &Item) -> Result<(&Vec<Attribute>, &Visibility, &Signature, &Box<Block>)> {
match body {
Item::Fn(f @ _) => {
let ItemFn {
attrs,
vis,
sig,
block,
} = f;
Ok((attrs, vis, sig, block))
}
_ => Err(Error::new(
body.span(),
"Statement other than function are not supported",
)),
}
}
fn read_time_unit(u: &NestedMeta) -> Result<TimeUnit> {
if let NestedMeta::Meta(Meta::Path(Path { segments, .. })) = u {
return Ok(segments[0].ident.to_string().into());
}
Err(syn::Error::new(
u.span(),
"Invalid argument, `TimeUnit` expected",
))
}
fn read_output_format(f: &NestedMeta) -> Result<String> {
match f {
NestedMeta::Lit(Lit::Str(s)) => Ok(s.value()),
_ => Err(syn::Error::new(
f.span(),
"Invalid FormatString, the usage is similar with macro `println!` or `format!`",
)),
}
}
}