Fix(lua_macros): Expand to generate the required types and functions
This commit is contained in:
parent
8f9a2a3f22
commit
3e8722433d
|
@ -4,9 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate_type = ["proc-macro"]
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
convert_case = "0.6.0"
|
||||||
proc-macro2 = "1.0.64"
|
proc-macro2 = "1.0.64"
|
||||||
quote = "1.0.29"
|
quote = "1.0.29"
|
||||||
syn = "2.0.25"
|
syn = { version = "2.0.25", features = ["extra-traits", "full", "parsing"] }
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::ToTokens;
|
||||||
|
|
||||||
|
// TODO: Do we need this noop?
|
||||||
|
pub fn generate_default_lua_function(input: &syn::Field) -> TokenStream2 {
|
||||||
|
let output: TokenStream2 = syn::parse(input.into_token_stream().into())
|
||||||
|
.expect("This is generated from valid rust code, it should stay that way.");
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
|
@ -1,59 +1,68 @@
|
||||||
|
mod generate_noop_lua_function;
|
||||||
|
mod mark_as_ci_command;
|
||||||
|
mod struct_to_ci_enum;
|
||||||
|
|
||||||
|
use generate_noop_lua_function::generate_default_lua_function;
|
||||||
|
use mark_as_ci_command::generate_final_function;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn;
|
use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function};
|
||||||
|
use syn::{self, ItemFn, Field, parse::Parser};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn generate_ci_functions(_: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
// Construct a representation of Rust code as a syntax tree
|
||||||
// that we can manipulate
|
// that we can manipulate
|
||||||
let input = syn::parse(input)
|
let input = syn::parse(input)
|
||||||
.expect("This should always be valid rust code, as it's extracted from direct code");
|
.expect("This should always be valid rust code, as it's extracted from direct code");
|
||||||
|
|
||||||
// Build the trait implementation
|
// Build the trait implementation
|
||||||
generate_generate_ci_functions(&input)
|
let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input);
|
||||||
|
|
||||||
|
let command_enum = generate_command_enum(&input);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#command_enum
|
||||||
|
#generate_ci_function
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_generate_ci_functions(input: &syn::DeriveInput) -> TokenStream {
|
/// Generate a default lua function implementation.
|
||||||
let input_tokens: TokenStream2 = match &input.data {
|
#[proc_macro_attribute]
|
||||||
syn::Data::Struct(input) => match &input.fields {
|
pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
syn::Fields::Named(named_fields) => named_fields
|
// Construct a representation of Rust code as a syntax tree
|
||||||
.named
|
// that we can manipulate
|
||||||
.iter()
|
//
|
||||||
.map(|field| -> TokenStream2 {
|
let parser = Field::parse_named;
|
||||||
let field_ident = field.ident.as_ref().expect(
|
let input = parser.parse(input)
|
||||||
"These are only the named field, thus they all should have a name.",
|
.expect("This is only defined for named fileds.");
|
||||||
);
|
|
||||||
let function_name_ident = format_ident!("fun_{}", field_ident);
|
|
||||||
let function_name = format!("{}", field_ident);
|
|
||||||
quote! {
|
|
||||||
let #function_name_ident = context.create_function(#field_ident).expect(
|
|
||||||
&format!(
|
|
||||||
"The function: `{}` should be defined",
|
|
||||||
#function_name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
globals.set(#function_name, #function_name_ident).expect(
|
|
||||||
&format!(
|
|
||||||
"Setting a static global value ({}, fun_{}) should work",
|
|
||||||
#function_name,
|
|
||||||
#function_name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
_ => unimplemented!("Only implemented for named fileds"),
|
|
||||||
},
|
|
||||||
_ => unimplemented!("Only for implemented for structs"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
pub fn generate_ci_functions(context: &mut Context) {
|
// Build the trait implementation
|
||||||
let globals = context.globals();
|
let default_lua_function: TokenStream2 = generate_default_lua_function(&input);
|
||||||
#input_tokens
|
|
||||||
}
|
quote! {
|
||||||
};
|
#default_lua_function
|
||||||
gen.into()
|
}
|
||||||
|
.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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote, ToTokens};
|
||||||
|
use syn::{Block, Expr, ExprBlock, GenericArgument, ReturnType, Stmt, Type};
|
||||||
|
|
||||||
|
pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 {
|
||||||
|
append_tx_send_code(input);
|
||||||
|
|
||||||
|
let output: TokenStream2 = syn::parse(input.into_token_stream().into())
|
||||||
|
.expect("This is generated from valid rust code, it should stay that way.");
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
|
||||||
|
let function_name_pascal = format_ident!(
|
||||||
|
"{}",
|
||||||
|
input
|
||||||
|
.sig
|
||||||
|
.ident
|
||||||
|
.clone()
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::Snake)
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
|
||||||
|
let tx_send = match &input.sig.output {
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
todo!(
|
||||||
|
"Does this case even trigger? All functions should have a output of (Result<$type, rlua::Error>)"
|
||||||
|
);
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event> =
|
||||||
|
context
|
||||||
|
.named_registry_value("sender_for_ci_commands")
|
||||||
|
.expect("This exists, it was set before");
|
||||||
|
|
||||||
|
tx
|
||||||
|
.send(Event::CommandEvent(Command::#function_name_pascal))
|
||||||
|
.expect("This should work, as the reciever is not dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syn::ReturnType::Type(_, ret_type) => {
|
||||||
|
let return_type = match *(ret_type.clone()) {
|
||||||
|
syn::Type::Path(path) => {
|
||||||
|
match path
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.first()
|
||||||
|
.expect("This is expected to be only one path segment")
|
||||||
|
.arguments
|
||||||
|
.to_owned()
|
||||||
|
{
|
||||||
|
syn::PathArguments::AngleBracketed(angled_path) => {
|
||||||
|
let angled_path = angled_path.args.to_owned();
|
||||||
|
let filtered_paths: Vec<_> = angled_path
|
||||||
|
.into_iter()
|
||||||
|
.filter(|generic_arg| {
|
||||||
|
if let GenericArgument::Type(generic_type) = generic_arg {
|
||||||
|
if let Type::Path(_) = generic_type {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// There should only be two segments (the type is <String, rlua::Error>)
|
||||||
|
if filtered_paths.len() > 2 {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if filtered_paths.len() == 2 {
|
||||||
|
// There is something else than rlua
|
||||||
|
let gen_type = if let GenericArgument::Type(ret_type) =
|
||||||
|
filtered_paths
|
||||||
|
.first()
|
||||||
|
.expect("One path segment should exists")
|
||||||
|
.to_owned()
|
||||||
|
{
|
||||||
|
ret_type
|
||||||
|
} else {
|
||||||
|
unreachable!("These were filtered above.");
|
||||||
|
};
|
||||||
|
let return_type_as_type_prepared = quote! {-> #gen_type};
|
||||||
|
|
||||||
|
let return_type_as_return_type: ReturnType = syn::parse(
|
||||||
|
return_type_as_type_prepared.to_token_stream().into(),
|
||||||
|
)
|
||||||
|
.expect("This is valid.");
|
||||||
|
return_type_as_return_type
|
||||||
|
} else {
|
||||||
|
// There is only rlua
|
||||||
|
ReturnType::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Only for angled paths"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Only for path types"),
|
||||||
|
};
|
||||||
|
match return_type {
|
||||||
|
ReturnType::Default => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event> =
|
||||||
|
context
|
||||||
|
.named_registry_value("sender_for_ci_commands")
|
||||||
|
.expect("This exists, it was set before");
|
||||||
|
|
||||||
|
tx
|
||||||
|
.send(Event::CommandEvent(Command::#function_name_pascal))
|
||||||
|
.expect("This should work, as the reciever is not dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReturnType::Type(_, _) => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event> =
|
||||||
|
context
|
||||||
|
.named_registry_value("sender_for_ci_commands")
|
||||||
|
.expect("This exists, it was set before");
|
||||||
|
|
||||||
|
tx
|
||||||
|
.send(Event::CommandEvent(Command::#function_name_pascal(input_str)))
|
||||||
|
.expect("This should work, as the reciever is not dropped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
block: tx_send_block,
|
||||||
|
};
|
||||||
|
let mut tx_send_stmt = vec![Stmt::Expr(Expr::Block(tx_send_expr_block), None)];
|
||||||
|
|
||||||
|
let mut new_stmts: Vec<Stmt> = Vec::with_capacity(input.block.stmts.len() + 1);
|
||||||
|
new_stmts.append(&mut tx_send_stmt);
|
||||||
|
new_stmts.append(&mut input.block.stmts);
|
||||||
|
input.block.stmts = new_stmts;
|
||||||
|
input
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{self, ReturnType};
|
||||||
|
|
||||||
|
pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
|
||||||
|
let mut functions_to_generate: Vec<TokenStream2> = 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::<syn::Path>("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]
|
||||||
|
fn #function_name(context: Context, input_str: String) -> Result<(), rlua::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(
|
||||||
|
context: &mut rlua::Context,
|
||||||
|
tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event>)
|
||||||
|
{
|
||||||
|
context.set_named_registry_value("sender_for_ci_commands", tx).expect("This should always work, as the value is added before all else");
|
||||||
|
let globals = context.globals();
|
||||||
|
#input_tokens
|
||||||
|
}
|
||||||
|
#functions_to_generate
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_ci_part(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! {
|
||||||
|
let #function_name_ident = context.create_function(#field_ident).expect(
|
||||||
|
&format!(
|
||||||
|
"The function: `{}` should be defined",
|
||||||
|
#function_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
globals.set(#function_name, #function_name_ident).expect(
|
||||||
|
&format!(
|
||||||
|
"Setting a static global value ({}, fun_{}) should work",
|
||||||
|
#function_name,
|
||||||
|
#function_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 {
|
||||||
|
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 {
|
||||||
|
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"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => 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(),
|
||||||
|
_ => unimplemented!("Only implemented for named fileds"),
|
||||||
|
},
|
||||||
|
_ => unimplemented!("Only implemented for structs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let gen = quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
#input_tokens
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
Reference in New Issue