From c243c90cabc1b68a747ed099c83795543020b43c Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 20 Jul 2023 21:43:41 +0200 Subject: [PATCH] Fix(lua_macros): Rework to support new command focused layout The internal commands are wrapped by a lua api, which allows to write the whole thing a little bit more language agnostic. --- lua_macros/src/lib.rs | 84 ++++++----- lua_macros/src/mark_as_ci_command.rs | 56 +++++-- .../generate_command_enum.rs | 65 +++++++++ .../generate_generate_ci_function.rs} | 137 +++++++----------- .../generate_help_function.rs | 53 +++++++ lua_macros/src/struct_to_ci_enum/mod.rs | 7 + 6 files changed, 261 insertions(+), 141 deletions(-) create mode 100644 lua_macros/src/struct_to_ci_enum/generate_command_enum.rs rename lua_macros/src/{struct_to_ci_enum.rs => struct_to_ci_enum/generate_generate_ci_function.rs} (50%) create mode 100644 lua_macros/src/struct_to_ci_enum/generate_help_function.rs create mode 100644 lua_macros/src/struct_to_ci_enum/mod.rs diff --git a/lua_macros/src/lib.rs b/lua_macros/src/lib.rs index 7e4b612..871dd3f 100644 --- a/lua_macros/src/lib.rs +++ b/lua_macros/src/lib.rs @@ -3,61 +3,67 @@ mod struct_to_ci_enum; use mark_as_ci_command::generate_final_function; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function}; -use syn::{self, ItemFn, Field, parse::Parser}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function, generate_help_function}; +use syn::{self, parse_quote, parse_str, DeriveInput, FieldMutability, ItemFn, Token, Visibility}; #[proc_macro_attribute] -pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream { +pub fn turn_struct_to_ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate - let input = syn::parse(input) - .expect("This should always be valid rust code, as it's extracted from direct code"); + let mut input: DeriveInput = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); + + let mut named_fields = match &input.data { + syn::Data::Struct(input) => match &input.fields { + syn::Fields::Named(named_fields) => named_fields, + _ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"), + }, + _ => unimplemented!("The macro only works for structs"), + } + .to_owned(); + + let attr_parsed = parse_quote! { + /// This is a help function + }; + + named_fields.named.push(syn::Field { + attrs: vec![attr_parsed], + // attrs: attr_parser + // .parse("#[doc = r\"This is a help function\"]".to_token_stream().into()) + // .expect("See reason for other one"), + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(format_ident!("help")), + colon_token: Some(Token![:](Span::call_site())), + ty: parse_str("fn(Option) -> String").expect("This is static and valid rust code"), + }); + + match &mut input.data { + syn::Data::Struct(input) => input.fields = syn::Fields::Named(named_fields.clone()), + _ => unreachable!("This was a DataStruct before"), + }; // Build the trait implementation let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input); - let command_enum = generate_command_enum(&input); + let command_enum = generate_command_enum(&named_fields); + + let help_function = generate_help_function(&named_fields); quote! { #command_enum + #generate_ci_function + + //#help_function } .into() } -/// Generate a default lua function implementation. -// TODO: Is this needed? -#[proc_macro_attribute] -pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let parser = Field::parse_named; - let input = parser.parse(input) - .expect("This is only defined for named fileds."); - - - quote! { - #input - } - .into() -} - -/// Turn a function into a valid ci command function #[proc_macro_attribute] pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let mut input: ItemFn = syn::parse(input) - .expect("This should always be valid rust code, as it's extracted from direct code"); - - // Build the trait implementation - let output_function: TokenStream2 = generate_final_function(&mut input); - - //panic!("{:#?}", output_function); - quote! { - #output_function - } - .into() + let mut input: ItemFn = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); + let output_function = generate_final_function(&mut input); + output_function.into() } diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs index 69d7511..76e2fc1 100644 --- a/lua_macros/src/mark_as_ci_command.rs +++ b/lua_macros/src/mark_as_ci_command.rs @@ -58,18 +58,13 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { // There should only be two segments (the type is ) if filtered_paths.len() > 2 { - unreachable!( - "There should be no more than two filtered_output, but got: {:#?}", - filtered_paths - ) + unreachable!("There should be no more than two filtered_output, but got: {:#?}", filtered_paths) } else if filtered_paths.len() <= 0 { - unreachable!( - "There should be more than zero filtered_output, but got: {:#?}", - filtered_paths - ) + unreachable!("There should be more than zero filtered_output, but got: {:#?}", filtered_paths) } + if filtered_paths.len() == 2 { - // There is something else than rlua + // There is something else than mlua::Error let gen_type = if let GenericArgument::Type(ret_type) = filtered_paths .first() @@ -97,36 +92,64 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { } _ => unimplemented!("Only for path types"), }; + let send_data = match return_type { ReturnType::Default => { quote! { { - Event::CommandEvent(Command::#function_name_pascal) + Event::CommandEvent(Command::#function_name_pascal, None) } } } ReturnType::Type(_, _) => { quote! { { - Event::CommandEvent(Command::#function_name_pascal(input_str.clone())) + Event::CommandEvent(Command::#function_name_pascal(input.clone()), Some(callback_tx)) } } } }; + + let output_return = match return_type { + ReturnType::Default => { + quote! { + { + return Ok(()); + } + } + } + ReturnType::Type(_, _) => { + quote! { + { + if let Some(output) = callback_rx.recv().await { + callback_rx.close(); + return Ok(output); + } else { + return Err(mlua::Error::ExternalError(Arc::new(Error::new( + ErrorKind::Other, + "Callback reciever dropped", + )))); + } + } + } + } + }; + quote! { { + let (callback_tx, mut callback_rx) = tokio::sync::mpsc::channel::(256); + let tx: - core::cell::Ref< - tokio::sync::mpsc::Sender - > = + core::cell::Ref> = lua .app_data_ref() .expect("This exists, it was set before"); (*tx) - .send(#send_data) - .await + .try_send(#send_data) .expect("This should work, as the reciever is not dropped"); + + #output_return } } } @@ -134,6 +157,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { let tx_send_block: Block = syn::parse(tx_send.into()).expect("This is a static string, it will always parse"); + let tx_send_expr_block = ExprBlock { attrs: vec![], label: None, diff --git a/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs b/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs new file mode 100644 index 0000000..8e5fd7e --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs @@ -0,0 +1,65 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::Type; + +pub fn generate_command_enum(input: &syn::FieldsNamed) -> TokenStream2 { + let input_tokens: TokenStream2 = input + .named + .iter() + .map(|field| -> TokenStream2 { + let field_ident = field + .ident + .as_ref() + .expect("These are only the named fields, thus they should all have a ident."); + + let enum_variant_type = match &field.ty { + syn::Type::BareFn(function) => { + let return_path = &function.inputs; + + let input_type: Option = if return_path.len() == 1 { + Some( + return_path + .last() + .expect("The last element exists") + .ty + .clone(), + ) + } else if return_path.len() == 0 { + None + } else { + panic!("The Function can only take on argument, or none"); + }; + input_type + } + _ => unimplemented!("This is only implemented for bare function types"), + }; + + let enum_variant_name = format_ident!( + "{}", + field_ident + .to_string() + .from_case(Case::Snake) + .to_case(Case::Pascal) + ); + if enum_variant_type.is_some() { + quote! { + #enum_variant_name (#enum_variant_type), + } + .into() + } else { + quote! { + #enum_variant_name, + } + } + }) + .collect(); + + let gen = quote! { + #[derive(Debug)] + pub enum Command { + #input_tokens + } + }; + gen.into() +} diff --git a/lua_macros/src/struct_to_ci_enum.rs b/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs similarity index 50% rename from lua_macros/src/struct_to_ci_enum.rs rename to lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs index 7f7ee65..3c690ec 100644 --- a/lua_macros/src/struct_to_ci_enum.rs +++ b/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs @@ -1,63 +1,13 @@ -use convert_case::{Case, Casing}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{self, ReturnType}; +use syn::{parse_quote, ReturnType, Type}; -pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { - let mut functions_to_generate: Vec = vec![]; - let input_tokens: TokenStream2 = match &input.data { - syn::Data::Struct(input) => match &input.fields { - syn::Fields::Named(named_fields) => named_fields - .named - .iter() - .map(|field| -> TokenStream2 { - if field.attrs.iter().any(|attribute| { - attribute.path() - == &syn::parse_str::("gen_default_lua_function") - .expect("This is valid rust code") - }) { - let function_name = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - functions_to_generate.push(quote! { - #[ci_command] - async fn #function_name(lua: &mlua::Lua, input_str: String) -> Result<(), mlua::Error> { - Ok(()) - } - }); - generate_ci_part(field) - } else { - generate_ci_part(field) - } - }) - .collect(), - - _ => unimplemented!("Only implemented for named fileds"), - }, - _ => unimplemented!("Only implemented for structs"), - }; - - let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); - let gen = quote! { - pub fn generate_ci_functions( - lua: &mut mlua::Lua, - tx: tokio::sync::mpsc::Sender) - { - lua.set_app_data(tx); - let globals = lua.globals(); - #input_tokens - } - #functions_to_generate - }; - gen.into() -} - -fn generate_ci_part(field: &syn::Field) -> TokenStream2 { +fn generate_ci_function_exposure(field: &syn::Field) -> TokenStream2 { let field_ident = field .ident .as_ref() .expect("These are only the named field, thus they all should have a name."); + let function_name_ident = format_ident!("fun_{}", field_ident); let function_name = format!("{}", field_ident); quote! { @@ -79,62 +29,77 @@ fn generate_ci_part(field: &syn::Field) -> TokenStream2 { .into() } -pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 { - let input_tokens: TokenStream2 = match &input.data { +pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { + let mut functions_to_generate: Vec = vec![]; + + let functions_to_export_in_lua: TokenStream2 = match &input.data { syn::Data::Struct(input) => match &input.fields { syn::Fields::Named(named_fields) => named_fields .named .iter() .map(|field| -> TokenStream2 { - let field_ident = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - - let enum_variant_type = match &field.ty { - syn::Type::BareFn(function) => { - let return_path: &ReturnType = &function.output; - match return_path { - ReturnType::Default => None, - ReturnType::Type(_, return_type) => Some(match *(return_type.to_owned()) { - syn::Type::Path(path_type) => path_type - .path - .get_ident() - .expect("A path should either be complete, or only conain one segment") - .to_owned(), - _ => unimplemented!("This is only implemented for path types"), - }), + let input_type = match &field.ty { + syn::Type::BareFn(bare_fn) => { + if bare_fn.inputs.len() == 1 { + bare_fn.inputs.last().expect("The last element exists").ty.clone() + } else if bare_fn.inputs.len() == 0 { + let input_type: Type = parse_quote! {()}; + input_type + } else { + panic!("The Function can only take on argument, or none"); } } _ => unimplemented!("This is only implemented for bare function types"), }; + let return_type = match &field.ty { + syn::Type::BareFn(function) => { + let return_path: &ReturnType = &function.output; + match return_path { + ReturnType::Default => None, + ReturnType::Type(_, return_type) => Some(return_type.to_owned()) } + } + _ => unimplemented!("This is only implemented for bare function types"), + }; - let enum_variant_name = format_ident!( - "{}", - field_ident.to_string().from_case(Case::Snake).to_case(Case::Pascal) - ); - if enum_variant_type.is_some() { - quote! { - #enum_variant_name (#enum_variant_type), + let function_name = field + .ident + .as_ref() + .expect("These are only the named field, thus they all should have a name."); + + if let Some(ret_type) = return_type { + functions_to_generate.push(quote! { + #[ci_command] + async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<#ret_type, mlua::Error> { } - .into() + }); } else { - quote! { - #enum_variant_name, + functions_to_generate.push(quote! { + #[ci_command] + async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<(), mlua::Error> { } + }); } + generate_ci_function_exposure(field) }) .collect(), + _ => unimplemented!("Only implemented for named fileds"), }, _ => unimplemented!("Only implemented for structs"), }; + let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); let gen = quote! { - #[derive(Debug)] - pub enum Command { - #input_tokens + pub fn generate_ci_functions( + lua: &mut mlua::Lua, + tx: tokio::sync::mpsc::Sender + ) + { + lua.set_app_data(tx); + let globals = lua.globals(); + #functions_to_export_in_lua } + #functions_to_generate }; gen.into() } diff --git a/lua_macros/src/struct_to_ci_enum/generate_help_function.rs b/lua_macros/src/struct_to_ci_enum/generate_help_function.rs new file mode 100644 index 0000000..2987272 --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/generate_help_function.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; + +pub fn generate_help_function(input: &syn::FieldsNamed) -> TokenStream2 { + let input: Vec<_> = input.named.iter().collect(); + + let combined_help_text: TokenStream2 = input + .iter() + .map(|field| { + let attrs_with_doc: Vec = field + .attrs + .iter() + .filter_map(|attr| { + if attr.path().is_ident("doc") { + let help_text = attr + .meta + .require_name_value() + .expect("This is a named value type, because all doc comments work this way") + .value + .clone(); + Some(help_text.into_token_stream().into()) + } else { + return None; + } + }) + .collect(); + if attrs_with_doc.len() == 0 { + // TODO there should be a better panic function, than the generic one + panic!( + "The command named: `{}`, does not provide a help message", + field.ident.as_ref().expect("These are all named") + ); + } else { + let help_text_for_one_command_combined: TokenStream2 = attrs_with_doc.into_iter().collect(); + return help_text_for_one_command_combined; + } + }) + .collect(); + + quote! { + #[ci_command] + async fn help( + lua: &mlua::Lua, + input_str: Option + ) -> Result { + // TODO add a way to filter the help based on the input + + let output = "These functions exist:\n"; + output.push_str(#combined_help_text); + Ok(output) + } + } +} diff --git a/lua_macros/src/struct_to_ci_enum/mod.rs b/lua_macros/src/struct_to_ci_enum/mod.rs new file mode 100644 index 0000000..4d93f98 --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/mod.rs @@ -0,0 +1,7 @@ +pub mod generate_command_enum; +pub mod generate_generate_ci_function; +pub mod generate_help_function; + +pub use generate_command_enum::*; +pub use generate_generate_ci_function::*; +pub use generate_help_function::*;