developer_debug_tools/
lib.rs1use proc_macro::TokenStream;
54use quote::{format_ident, quote};
55use syn::{ItemFn, Token, WhereClause, parse::Parse, parse_quote};
56
57mod keyword {
58 syn::custom_keyword!(print_each_pass);
59 syn::custom_keyword!(print_recursion_counter);
60}
61
62#[derive(Default)]
63struct RecursionConfig{
64 print_each_pass: bool,
65 print_recursion_counter: bool
66}
67
68impl Parse for RecursionConfig{
69 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
70 let mut config = RecursionConfig::default();
71 while input.peek(keyword::print_each_pass) || input.peek(keyword::print_recursion_counter) {
72 let token_result = input.parse::<keyword::print_each_pass>();
73 if token_result.is_ok(){
74 config.print_each_pass = true;
75 }else{
76 let token_result = input.parse::<keyword::print_recursion_counter>();
77 if token_result.is_ok(){
78 config.print_recursion_counter = true;
79 }
80 }
81 if input.peek(Token![,]){
82 let _comman_token: Token![,] = input.parse()?;
83 }
84 }
85 Ok(config)
86 }
87}
88
89#[proc_macro_attribute]
90pub fn print_recursion_tree(attr: TokenStream, item: TokenStream) -> TokenStream {
91 let mut item_fn: ItemFn = syn::parse_macro_input!(item);
92
93 let config = syn::parse_macro_input!(attr as RecursionConfig);
95 let print_each_pass_ident = format_ident!("{}", config.print_each_pass);
96 let print_recursion_counter_ident = format_ident!("{}", config.print_recursion_counter);
97
98
99 let fn_gen = &mut item_fn.sig.generics;
101 let fn_gen_params = &fn_gen.params;
102 let type_params_name:Vec<_> = fn_gen_params.iter().filter(|p| match p{
103 syn::GenericParam::Type(_) => true,
104 _ => false
105 }).map(|p| match p{
106 syn::GenericParam::Type(type_param) => &type_param.ident,
107 syn::GenericParam::Const(const_param) => &const_param.ident,
108 syn::GenericParam::Lifetime(life_time) => &life_time.lifetime.ident,
109 }).collect();
110
111 let where_clause_originial = fn_gen.where_clause.clone();
113 let mut fn_generics_clone = fn_gen.clone();
114 let where_clause_modified = fn_generics_clone.where_clause.get_or_insert_with(|| WhereClause {
115 where_token: parse_quote!(where),
116 predicates: syn::punctuated::Punctuated::new(),
117 });
118 type_params_name.iter().for_each(|p| {
119 where_clause_modified.predicates.push(parse_quote!(#p: std::fmt::Debug));
120 });
121
122
123 let async_fn = item_fn.sig.asyncness.is_some();
124 if async_fn{
125 panic!("async function are not supported by print_recursion_tree");
126 }
127
128 let input_args = &item_fn.sig.inputs;
130 let fn_ident = &item_fn.sig.ident;
131 let fn_name = item_fn.sig.ident.to_string();
132 let fn_return_type = &item_fn.sig.output;
133 let fn_body = &item_fn.block;
134 let fn_visibility = &item_fn.vis;
135
136 let renamed_fn_name_string = format!("__debug_recursion__{}", &fn_name);
139 let fn_name_renamed = format_ident!("{}", renamed_fn_name_string);
140
141 let recursion_call_counter_ident = format_ident!("{}{}", renamed_fn_name_string.to_uppercase(), "_COUNTER");
142 let recursion_call_remining_counter_ident = format_ident!("{}{}", renamed_fn_name_string.to_uppercase(), "_REMAINING");
143
144 let get_tree_fn_ident = format_ident!("{}{}", "__debug_recursion__get_tree_", &fn_name);
145
146 let get_counter_fn_ident = format_ident!("{}{}", "__debug_recursion__get_counter_", &fn_name);
147
148
149 let tree_builder_ident = format_ident!("{}{}", renamed_fn_name_string.to_uppercase(), "_TREE");
150
151 let input_args_clone1 = input_args.clone();
153 let callable_args = input_args_clone1.iter().filter_map(|arg| {
154 if let syn::FnArg::Typed(pat_type) = arg {
155 if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
156 Some(&pat_ident.ident)
157 } else {
158 None
159 }
160 } else {
161 None
162 }
163 }).collect::<Vec<_>>();
164
165 let renamed_fn: proc_macro2::TokenStream = quote! {
167 #fn_visibility fn #fn_name_renamed #fn_gen (#input_args) #fn_return_type #where_clause_originial #fn_body
168 };
169
170 let debug_str = callable_args.clone().iter().map(|_| "{:?}").collect::<Vec<&str>>().join(",");
171
172 let proxy_fn: proc_macro2::TokenStream = quote! {
175 #fn_visibility fn #fn_ident #fn_gen (#input_args) #fn_return_type #where_clause_modified {
176 {
177 use ptree::TreeBuilder;
178 use ptree::print_tree;
179 {
180 let args = format!(#debug_str, #(#callable_args),*);
181 let mut total_call_counter = #recursion_call_counter_ident.lock().unwrap();
182
183 let mut remaining_call_counter = #recursion_call_remining_counter_ident.lock().unwrap();
184 let mut tree = #tree_builder_ident.lock().unwrap();
185 if *remaining_call_counter == 0{
186 *tree = TreeBuilder::new(#fn_name.to_string());
187 *total_call_counter = 0;
188 }
189
190 *total_call_counter = *total_call_counter + 1;
191 *remaining_call_counter = *remaining_call_counter + 1;
192 tree.begin_child(args);
193 }
194 let result = #fn_name_renamed(#(#callable_args),*);
195 let mut print_flag = false;
196 {
197 let mut tree = #tree_builder_ident.lock().unwrap();
198 let mut remaining_call_counter = #recursion_call_remining_counter_ident.lock().unwrap();
199 let mut total_call_counter = #recursion_call_counter_ident.lock().unwrap();
200 if *remaining_call_counter > 0{
201 *remaining_call_counter = *remaining_call_counter - 1;
202 }
203 tree.add_empty_child(format!("={:?}", result));
204 print_flag = #print_each_pass_ident || *remaining_call_counter == 0;
205 if print_flag {
206 println!("---------------");
207 print_tree(&tree.build()).unwrap();
208 println!("---------------");
209 }
210
211 tree.end_child();
212 }
213
214 if #print_recursion_counter_ident && print_flag {
215 let count = #get_counter_fn_ident();
216 println!("Total Number Of Recursions: {}", count);
217 }
218 return result;
219 }
220 }
221 };
222
223 let get_tree_fn: proc_macro2::TokenStream = quote! {
224 fn #get_tree_fn_ident() -> String{
225 let mut tree = #tree_builder_ident.lock().unwrap();
226 let mut tree_as_vec = Vec::<u8>::new();
227 ptree::write_tree(&(*tree).build(), &mut tree_as_vec).unwrap();
228 String::from_utf8(tree_as_vec).unwrap()
229 }
230 };
231
232 let get_counter_fn: proc_macro2::TokenStream = quote! {
233 fn #get_counter_fn_ident() -> u16{
234 let counter = #recursion_call_counter_ident.lock().unwrap();
235 if *counter > 0 {
236 return *counter - 1;
237 }
238 return *counter;
239 }
240 };
241
242 let lazy_static_initialization: proc_macro2::TokenStream = quote! {
243 lazy_static::lazy_static! {
244 static ref #recursion_call_counter_ident: std::sync::Mutex<u16> = std::sync::Mutex::new(0u16);
246
247 static ref #recursion_call_remining_counter_ident: std::sync::Mutex<u16> = std::sync::Mutex::new(0u16);
249
250 static ref #tree_builder_ident:std::sync::Mutex<ptree::TreeBuilder> = std::sync::Mutex::new(ptree::TreeBuilder::new("tree".to_string()));
252 }
253 };
254
255 let mut token_stream: proc_macro2::TokenStream = renamed_fn.into();
256 token_stream.extend(get_counter_fn.into_iter());
257 token_stream.extend(get_tree_fn.into_iter());
258 token_stream.extend(lazy_static_initialization.into_iter());
259 token_stream.extend(proxy_fn.into_iter());
260 return token_stream.into();
261}