developer_debug_tools/
lib.rs

1//! # developer_debug_tools
2//! The crate aids developers to their code
3//! e.g debug recurive function by printing the recursion tree of call
4//! 
5//! Annotate any recursive function with proc macro `print_recursion_tree`. Recursion tree is printed in the stdout
6//! at the end of recursive call.
7//! The pre condition is that all arguments and return type of the function must implement std::fmt::Debug trait
8//! ```
9//! use developer_debug_tools::print_recursion_tree;
10//! #[print_recursion_tree(print_recursion_counter, print_each_pass)]
11//! fn fibonacci_recursive(n: u16) -> u16 {
12//!    if n <= 1 {
13//!        return n;
14//!    } else {
15//!        return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2);
16//!    }
17//! } 
18//! ```
19//! Output of fibonacci_recursive(3) is as below
20//! ```text
21//! fibonacci_recursive
22//!  └─ 3
23//!     ├─ 2
24//!     │  ├─ 1
25//!     │  │  └─ =1
26//!     │  ├─ 0
27//!     │  │  └─ =0
28//!     │  └─ =1
29//!     ├─ 1
30//!     │  └─ =1
31//!     └─ =2
32//!  ```
33//! `fibonacci_recursive` is the function name
34//! 
35//! `└─3`, `└─2` denotes argument passed to the function
36//! 
37//! `└─ =2`, `└─ =1` are the return value of each recursive call
38//! 
39//! Two flags(attributes) are supported by this proc-macro
40//! ## 1. print_each_pass 
41//! This prints the recursion tree on each recursive call. If this flag is not provided, 
42//! recursion tree is printed only once at the end of the recursion 
43//! 
44//! ## 2.print_recursion_counter 
45//! This prints the total recursion calls evaluated. This can be useful to check the 
46//! optimization of recursive call e.g using memoization
47//! ```text
48//! Total Number Of Recursions: 4
49//! ```
50//! 
51
52
53use 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    // Read Config from attr
94    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    //Get type params
100    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    // modify where clause to specify Debug type bound        
112    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    // Read other attributes of function
129    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    // 
137
138    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    // Convert input args to callable arguments
152    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    // Rename the current function with a new name 
166    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    //Generate a function with current function name
173
174    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            // counter to hold the total number of recursions    
245            static ref #recursion_call_counter_ident: std::sync::Mutex<u16> = std::sync::Mutex::new(0u16);
246
247            // counter to hold the remaining number of recursions to complete
248            static ref #recursion_call_remining_counter_ident: std::sync::Mutex<u16> = std::sync::Mutex::new(0u16);
249
250            // ptree::TreeBuilder
251            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}