This repository has been archived on 2024-05-26. You can view files and clone it, but cannot push or open issues or pull requests.
core/language_macros/src/lib.rs

201 lines
5.8 KiB
Rust
Raw Normal View History

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Attribute, Ident,
Token, Type,
};
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 input to the `parse_command_enum` proc macro.
///
/// For example this rust code:
/// ```no_run
/// parse_command_enum! {
/// /// Greets the user
/// greet: fn(String) -> String,
/// }
/// ```
/// results in this expanded code:
/// ```no_run
/// #[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)));
/// }
/// };
/// }
/// ```
#[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);
// Build the language wrappers
let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input);
// Build the final enum
let command_enum = generate::command_enum(&input);
let output = quote! {
#command_enum
#lua_wrapper
};
output.into()
}