2023-07-26 14:59:05 +00:00
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::TokenStream as TokenStream2;
|
|
|
|
use quote::quote;
|
2023-09-20 17:21:44 +00:00
|
|
|
use syn::{
|
|
|
|
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Attribute, Ident,
|
|
|
|
Token, Type,
|
|
|
|
};
|
2023-07-26 14:59:05 +00:00
|
|
|
|
|
|
|
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
|
2023-09-20 17:21:44 +00:00
|
|
|
/// the input to the `parse_command_enum` proc macro.
|
2023-07-26 14:59:05 +00:00
|
|
|
///
|
|
|
|
/// For example this rust code:
|
2023-09-20 17:21:44 +00:00
|
|
|
/// ```no_run
|
|
|
|
/// parse_command_enum! {
|
2023-07-26 14:59:05 +00:00
|
|
|
/// /// Greets the user
|
|
|
|
/// greet: fn(String) -> String,
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
/// results in this expanded code:
|
2023-09-20 17:21:44 +00:00
|
|
|
/// ```no_run
|
2023-07-26 14:59:05 +00:00
|
|
|
/// #[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)));
|
|
|
|
/// }
|
|
|
|
/// };
|
|
|
|
/// }
|
|
|
|
/// ```
|
2023-09-20 17:21:44 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct DataCommandEnum {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
commands_token: kw::commands,
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
brace_token: token::Brace,
|
|
|
|
|
|
|
|
fields: Punctuated<Field, Token![,]>,
|
|
|
|
}
|
|
|
|
|
|
|
|
mod kw {
|
|
|
|
syn::custom_keyword!(commands);
|
|
|
|
syn::custom_keyword!(namespace);
|
|
|
|
syn::custom_keyword!(declare);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum Field {
|
|
|
|
Function(FunctionDeclaration),
|
|
|
|
Namespace(Namespace),
|
|
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct Namespace {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
namespace_token: kw::namespace,
|
|
|
|
|
|
|
|
path: NamespacePath,
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
brace_token: token::Brace,
|
|
|
|
|
|
|
|
fields: Punctuated<Field, Token![,]>,
|
|
|
|
}
|
|
|
|
type NamespacePath = Punctuated<Ident, Token![::]>;
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct FunctionDeclaration {
|
|
|
|
#[allow(dead_code)]
|
|
|
|
function_token: kw::declare,
|
|
|
|
|
|
|
|
name: Ident,
|
|
|
|
|
|
|
|
#[allow(dead_code)]
|
|
|
|
colon_token: Token![:],
|
|
|
|
|
|
|
|
ty: Type,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for DataCommandEnum {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
let content;
|
|
|
|
Ok(DataCommandEnum {
|
|
|
|
commands_token: input.parse()?,
|
|
|
|
brace_token: braced!(content in input),
|
|
|
|
fields: content.parse_terminated(Field::parse, Token![,])?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl Parse for Field {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
let lookahead = input.lookahead1();
|
|
|
|
if input.peek(Token![#]) {
|
|
|
|
// FIXME(@soispha): We ignore doc comments, which should probably be replaced by adding
|
|
|
|
// them to the output <2023-09-19>
|
|
|
|
let _output = input.call(Attribute::parse_outer).unwrap_or(vec![]);
|
|
|
|
let lookahead = input.lookahead1();
|
|
|
|
|
|
|
|
if lookahead.peek(kw::namespace) {
|
|
|
|
input.parse().map(Field::Namespace)
|
|
|
|
} else if lookahead.peek(kw::declare) {
|
|
|
|
input.parse().map(Field::Function)
|
|
|
|
} else {
|
|
|
|
Err(lookahead.error())
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if lookahead.peek(kw::declare) {
|
|
|
|
input.parse().map(Field::Function)
|
|
|
|
} else if lookahead.peek(kw::namespace) {
|
|
|
|
input.parse().map(Field::Namespace)
|
|
|
|
} else {
|
|
|
|
Err(lookahead.error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parse for FunctionDeclaration {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
Ok(FunctionDeclaration {
|
|
|
|
function_token: input.parse()?,
|
|
|
|
name: input.parse()?,
|
|
|
|
colon_token: input.parse()?,
|
|
|
|
ty: input.parse()?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl Parse for Namespace {
|
|
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
|
|
let content;
|
|
|
|
Ok(Namespace {
|
|
|
|
namespace_token: input.parse()?,
|
|
|
|
path: NamespacePath::parse_separated_nonempty(input)?,
|
|
|
|
brace_token: braced!(content in input),
|
|
|
|
fields: content.parse_terminated(Field::parse, Token![,])?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[proc_macro]
|
|
|
|
pub fn parse_command_enum(input: TokenStream) -> TokenStream {
|
|
|
|
let input = parse_macro_input!(input as DataCommandEnum);
|
2023-07-26 14:59:05 +00:00
|
|
|
|
|
|
|
// Build the language wrappers
|
|
|
|
let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input);
|
|
|
|
|
|
|
|
// Build the final enum
|
|
|
|
let command_enum = generate::command_enum(&input);
|
|
|
|
|
2023-09-20 17:21:44 +00:00
|
|
|
let output = quote! {
|
2023-07-26 14:59:05 +00:00
|
|
|
#command_enum
|
|
|
|
#lua_wrapper
|
2023-09-20 17:21:44 +00:00
|
|
|
};
|
|
|
|
output.into()
|
2023-07-26 14:59:05 +00:00
|
|
|
}
|