macro_log_macros/
lib.rs

1mod fs;
2use proc_macro::TokenStream;
3use quote::{quote, ToTokens};
4use syn::{ItemFn, FnArg, punctuated::Punctuated, token::Comma, Attribute};
5
6// see: https://dengjianping.github.io/2019/02/28/%E5%A6%82%E4%BD%95%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E8%BF%87%E7%A8%8B%E5%AE%8F(proc-macro).html
7
8#[derive(Clone, Copy)]
9enum When {
10    Call,
11    Return,
12}
13
14#[proc_macro_attribute]
15pub fn param(args: TokenStream, func: TokenStream) -> TokenStream {
16    parse(args, func, When::Call)
17}
18
19#[proc_macro_attribute]
20pub fn debug(args: TokenStream, func: TokenStream) -> TokenStream {
21    parse(args, func, When::Return)
22}
23
24#[proc_macro]
25pub fn read_dir(args: TokenStream) -> TokenStream {
26    fs::read_dir(args)
27}
28
29fn parse(_: TokenStream, func: TokenStream, when: When) -> TokenStream {
30    let func = syn::parse_macro_input!(func as ItemFn);
31    let func_attrs = func.attrs; // e.g. #[derive(xx)]
32    let attrs = parse_attrs(func_attrs);
33    let func_vis = &func.vis; // pub
34    let func_block = &func.block; // { code block }
35
36    let sig = &func.sig;
37    let func_constness = &sig.constness; // const
38    let func_async = &sig.asyncness; // const
39    let func_abi = &sig.abi; // extern "abi"
40    let func_name = &sig.ident; // fn name
41    let func_generics = &sig.generics; // <'a, T>
42    let func_where_clause = &func_generics.where_clause; // where
43    let func_inputs = &sig.inputs; // arguments
44    let func_output = &sig.output; // return value
45    
46    let params = parse_params(func_inputs);
47    let (format, values) = get_log_format_values(&func_name.to_string(), params);
48    let format = match when {
49        When::Call => format!("call {format}"),
50        When::Return => format!("called {format}"),
51    };
52    let log = match (when, func_output) {
53        (When::Call, _) => quote! {
54            macro_log::d!(#format, #values);
55        },
56        (_, syn::ReturnType::Default) => quote! {
57            macro_log::d!("{}", call);
58        },
59        (_, syn::ReturnType::Type(_, _)) => quote! {
60            macro_log::d!("{} => {:?}", call, return_value);
61        },
62    };
63
64    let caller = match when {
65        When::Call => quote! {
66            #attrs
67            #func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
68                #log
69                #func_block
70            }
71        },
72        When::Return => quote! {
73            #attrs
74            #func_vis #func_constness #func_async #func_abi fn #func_name #func_generics(#func_inputs) #func_output #func_where_clause {
75                let call = format!(#format, #values);
76                let return_value = #func_block;
77                #log
78                return_value
79            }
80        },
81    };
82    // println!("compile result: \n---------------------\n{}\n---------------------", caller.to_string());
83    caller.into()
84}
85
86/// #[xxx] #[xxx]
87fn parse_attrs(attrs: Vec<Attribute>) -> proc_macro2::TokenStream {
88    attrs
89        .iter()
90        .map(|attr| attr.to_token_stream().to_string())
91        .collect::<Vec<String>>().join(" ")
92        .parse().unwrap()
93}
94
95/// fn(a: ?, b: ?) -> ["a", "b"]
96fn parse_params(func_inputs: &Punctuated<FnArg, Comma>) -> Vec<String> {
97    let mut args = vec![];
98    for arg in func_inputs.into_iter() {
99        match arg {
100            // The self argument of an associated method
101            FnArg::Receiver(arg) => {
102                // This is self: Self or self: &Self
103                let tokens = quote!(#arg).to_string();
104                // println!("tokens -> {:?}", tokens); // tokens -> "value : & 'a T"
105                let (name, _vartype) = tokens.split_once(" : ").unwrap();
106                args.push(name.into());
107            }
108            // A function argument accepted by pattern and type
109            FnArg::Typed(_) => { // _ same as arg
110                let tokens = quote!(#arg).to_string();
111                // println!("tokens -> {:?}", tokens); // tokens -> "value : & 'a T"
112                let (name, _vartype) = tokens.split_once(" : ").unwrap();
113                args.push(name.into());
114            }
115        }
116    }
117    args
118}
119
120/// for quote!{ macro_log::d!(#format, #values); }
121fn get_log_format_values(func_name: &str, args: Vec<String>) -> (String, proc_macro2::TokenStream) {
122    let format_args = args.iter()
123        .map(|it| if it.as_str() != "_" {
124            format!("{it} = {{:?}}")
125        } else {
126            "_ = ?".to_string()
127        })
128        .collect::<Vec<String>>().join(", ");
129    let format = format!("fn {func_name}({format_args})");
130    // println!("format -> {format:?}"); // format -> "call fn test(value = {:?}, another = {:?}, arg = {:?})"
131
132    let values = args.iter()
133        .filter(|it| it.as_str() != "_")
134        .map(|it| format!("{it}")).collect::<Vec<String>>().join(",");
135    let values = values.parse::<proc_macro2::TokenStream>().unwrap();
136    // println!("values -> {values}"); // values -> value, another, arg
137
138    (format, values)
139}