|
|
@@ -0,0 +1,124 @@
|
|
|
+extern crate proc_macro;
|
|
|
+use proc_macro::{TokenStream, TokenTree};
|
|
|
+
|
|
|
+#[proc_macro_attribute]
|
|
|
+pub fn log(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
|
+ let mut tokens = item.into_iter();
|
|
|
+
|
|
|
+ let logger_function_name = "trace";
|
|
|
+ let skip_types = &["&mut Cursive"];
|
|
|
+ let log_id = utils::rng::random_numeric_string(3);
|
|
|
+
|
|
|
+ let mut function_name = String::new();
|
|
|
+ let mut do_function_name_check = true;
|
|
|
+ let mut args: Vec<(String, String)> = Vec::new();
|
|
|
+
|
|
|
+ let mut function_signature = String::new();
|
|
|
+ let mut function_body = String::new();
|
|
|
+ let mut token_start = String::new();
|
|
|
+ let mut token_end = String::new();
|
|
|
+
|
|
|
+ let mut in_block = false;
|
|
|
+
|
|
|
+ while let Some(token) = tokens.next() {
|
|
|
+ match &token {
|
|
|
+ TokenTree::Group(group)
|
|
|
+ if group.delimiter() == proc_macro::Delimiter::Brace && !in_block =>
|
|
|
+ {
|
|
|
+ let body = group.stream().to_string();
|
|
|
+
|
|
|
+ let mut args_with_values = String::new();
|
|
|
+
|
|
|
+ for (name, ty) in &args {
|
|
|
+ if skip_types.contains(&ty.as_str()) {
|
|
|
+ args_with_values.push_str(&format!("{name}: {ty}\n"));
|
|
|
+ } else {
|
|
|
+ args_with_values.push_str(&format!("{name}: {ty} = {{{name}:?}}\n"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ args_with_values = args_with_values.trim_end_matches("\n").to_string();
|
|
|
+
|
|
|
+ token_start = format!(
|
|
|
+ r#"
|
|
|
+ let args = format!("{args_with_values}");
|
|
|
+ let log_instance_id = utils::rng::random_numeric_string(3);
|
|
|
+ let message1 = format!("Running function call #{{log_instance_id}}\n{function_name}\n{{args}}");
|
|
|
+ {logger_function_name}(&message1);"#
|
|
|
+ );
|
|
|
+
|
|
|
+ token_end = format!(
|
|
|
+ r#"
|
|
|
+ let message2 = message1.replacen("Running", "Finished", 1);
|
|
|
+ {logger_function_name}(&message2);"#
|
|
|
+ );
|
|
|
+
|
|
|
+ function_body = body;
|
|
|
+ in_block = true;
|
|
|
+ }
|
|
|
+ TokenTree::Group(group) if group.delimiter() == proc_macro::Delimiter::Parenthesis => {
|
|
|
+ let args_stream = group.stream().to_string();
|
|
|
+
|
|
|
+ for arg in args_stream.split(", ") {
|
|
|
+ let Some((name, ty)) = arg.split_once(":") else {
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+ args.push((name.trim().to_string(), ty.trim().to_string()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ TokenTree::Ident(ident) if do_function_name_check => {
|
|
|
+ if function_name == "fn" {
|
|
|
+ do_function_name_check = false;
|
|
|
+ }
|
|
|
+ function_name = ident.to_string();
|
|
|
+ }
|
|
|
+
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+
|
|
|
+ if !in_block {
|
|
|
+ function_signature.push_str(&token.to_string());
|
|
|
+ function_signature.push(' ');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function_signature = function_signature
|
|
|
+ .replace("- >", "->")
|
|
|
+ .replace(" : : ", "::")
|
|
|
+ .replace(" < ", "<")
|
|
|
+ .replace(" > ", ">")
|
|
|
+ .replacen(&format!("{function_name} "), &function_name, 1);
|
|
|
+ function_body = function_body.replace(";", ";\n");
|
|
|
+
|
|
|
+ let mut arg_names_in_order = String::new();
|
|
|
+ for (idx, (name, _)) in args.iter().enumerate() {
|
|
|
+ arg_names_in_order.push_str(&name);
|
|
|
+
|
|
|
+ if idx < args.len() - 1 {
|
|
|
+ arg_names_in_order.push_str(", ");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // The original function being wrapped
|
|
|
+ let hidden_function_name = format!("hidden_{log_id}_{function_name}");
|
|
|
+ let hidden_function_signature =
|
|
|
+ function_signature.replacen(&function_name, &hidden_function_name, 1);
|
|
|
+
|
|
|
+ let output = format!(
|
|
|
+ r#"
|
|
|
+{hidden_function_signature} {{
|
|
|
+ {function_body}
|
|
|
+}}
|
|
|
+
|
|
|
+{function_signature} {{
|
|
|
+ use logging::{logger_function_name};
|
|
|
+ {token_start}
|
|
|
+ let result = {hidden_function_name}({arg_names_in_order});
|
|
|
+ {token_end}
|
|
|
+ result
|
|
|
+}}
|
|
|
+ "#
|
|
|
+ );
|
|
|
+
|
|
|
+ output.parse().unwrap()
|
|
|
+}
|