94 lines
3.0 KiB
Rust
94 lines
3.0 KiB
Rust
|
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<Event>,
|
||
|
/// ) -> 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<String, mlua::Error> {
|
||
|
/// let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::<String>();
|
||
|
/// let tx: core::cell::Ref<tokio::sync::mpsc::Sender<Event>> = 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()
|
||
|
}
|