use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::DeriveInput; mod generate; /// This is the heart of the command api /// It mainly does two things: /// - Generate a command enum /// - Wrap the enum in all supported languages (only lua for now) /// - Generate wrapper lua function for each command /// - Generate a `add_lua_functions_to_globals` function, which adds /// the rust wrapper functions to the lua globals. /// /// The input and output values of the wrapped functions are derived from the values specified in /// the `Commands` struct. /// The returned values will be returned directly to the lua context, this allows to nest functions. /// /// For example this rust code: /// ```rust /// #[ci_command_enum] /// struct Commands { /// /// Greets the user /// greet: fn(String) -> String, /// } /// ``` /// results in this expanded code: /// ```rust /// #[derive(Debug)] /// pub enum Command { /// Greet(String), /// } /// pub fn add_lua_functions_to_globals( /// lua: mlua::Lua, /// tx: tokio::sync::mpsc::Sender, /// ) -> mlua::Lua { /// lua.set_app_data(tx); /// let globals = lua.globals(); /// { /// let wrapped_lua_function_greet = lua /// .create_async_function(greet) /// .expect( /// format!( /// "The function: `{}` should be defined", /// "greet", /// ) /// ); /// globals /// .set("greet", wrapped_lua_function_greet) /// .expect("Setting a static global value should work"); /// } /// drop(globals); /// lua /// } /// async fn greet(lua: &mlua::Lua, input: String) -> Result { /// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::(); /// let tx: core::cell::Ref> = lua /// .app_data_ref() /// .expect("This should exist, it was set before"); /// (*tx) /// .send(Event::CommandEvent(Command::Greet(input.clone()), Some(callback_tx))) /// .await /// .expect("This should work, as the receiver is not dropped"); /// match callback_rx.await { /// Ok(output) => { /// return Ok(output); /// } /// Err(err) => { /// return Err(mlua::Error::ExternalError(std::sync::Arc::new(err))); /// } /// }; /// } /// ``` #[proc_macro_attribute] pub fn ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate let input: DeriveInput = syn::parse(input) .expect("This should always be valid rust code, as it's extracted from direct code"); // Build the language wrappers let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input); // Build the final enum let command_enum = generate::command_enum(&input); quote! { #command_enum #lua_wrapper } .into() }