use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, spanned::Spanned, FnArg, ItemFn, ReturnType, Type};
fn handle_gmod(item: TokenStream, export: Option<&str>) -> TokenStream {
let mut returns_result: Option<&Box<Type>> = None;
let mut ast = parse_macro_input!(item as ItemFn);
if ast.sig.asyncness.is_some() {
return syn::Error::new(ast.sig.span(), "Cannot be async").into_compile_error().into();
}
if ast.sig.constness.is_some() {
return syn::Error::new(ast.sig.span(), "Cannot be const").into_compile_error().into();
}
if ast.sig.inputs.len() != 1 {
return syn::Error::new(ast.sig.span(), "Must have one parameter, being the Lua state (rglua::lua::LuaState)").into_compile_error().into();
}
if let ReturnType::Type(_, ty) = &ast.sig.output {
let mut ret = ty.to_token_stream().to_string();
if ret.starts_with("Result < i32") | ret.starts_with("std :: result :: Result < i32") {
ret.retain(|c| !c.is_whitespace());
returns_result = Some(ty);
} else {
if ret.as_str() != "i32" {
return syn::Error::new(ast.sig.output.span(), "Exported function must return i32 or Result<i32, E>").into_compile_error().into();
}
}
} else {
return syn::Error::new(ast.sig.output.span(), "Exported function must return i32 or Result<i32, E>").into_compile_error().into();
}
let lua_shared_param;
let lua_shared_ty;
match ast.sig.inputs.first().unwrap() {
FnArg::Receiver(_) => return syn::Error::new(ast.sig.inputs.span(), "Parameter cannot be self").into_compile_error().into(),
FnArg::Typed(arg) => {
match arg.ty.to_token_stream().to_string().as_str() {
"LuaState" | "rglua :: lua :: LuaState" => (),
a => return syn::Error::new(arg.ty.span(), format!("Parameter must be rglua::lua::LuaState. Got {a}")).to_compile_error().into(),
}
lua_shared_ty = &arg.ty;
match *arg.pat {
syn::Pat::Ident(ref i) => {
lua_shared_param = &i.ident;
}
syn::Pat::Wild(_) => {
return syn::Error::new(arg.pat.span(), "Parameter must be named. Try _foo").to_compile_error().into();
}
_ => return syn::Error::new(arg.pat.span(), "Parameter must be in 'ident: ty' format").to_compile_error().into(),
}
}
}
if let Some(abi) = &ast.sig.abi {
match abi.name.as_ref().unwrap().value().as_str() {
"C" | "C-unwind" => (),
_ => return syn::Error::new(abi.span(), "Only C or C-unwind are supported").to_compile_error().into(),
}
} else {
ast.sig.abi = Some(parse_quote!(extern "C"))
}
if let Some(ret) = returns_result {
let inner_fn = &ast.sig.ident;
let inner_stmts = &ast.block.stmts;
let attrs = &ast.attrs;
let inner = quote! {
#(#attrs)*
fn #inner_fn(#lua_shared_param: #lua_shared_ty) -> #ret {
#(#inner_stmts)*
}
};
let resultant = quote! {
match #inner_fn(#lua_shared_param) {
Err(why) => {
let err = why.to_string();
let err = cstr!(err);
rglua::lua::luaL_error(#lua_shared_param, cstr!("%s"), err.as_ptr());
},
Ok(n) => { return n }
}
};
ast.block
.stmts
.insert(0, syn::parse2(inner).expect("Error parsing inner fn"));
ast.block
.stmts
.insert(1, syn::parse2(resultant).expect("Error parsing resultant"));
ast.block.stmts.truncate(2);
ast.sig.output = ReturnType::Type(Default::default(), Box::new(parse_quote!(i32)));
ast.attrs.clear();
}
if let Some(export) = export {
ast.sig.ident = quote::format_ident!("{}", export);
}
for attr in &ast.attrs {
if let Some(id) = attr.path.get_ident() {
if id == "no_mangle" {
return syn::Error::new(id.span(), "Using no_mangle is unnecessary on exported functions").into_compile_error().into();
}
}
}
ast.attrs.push(parse_quote!(#[no_mangle]));
ast.into_token_stream().into()
}
#[proc_macro_attribute]
pub fn gmod_open(_attr: TokenStream, item: TokenStream) -> TokenStream {
handle_gmod(item, Some("gmod13_open"))
}
#[proc_macro_attribute]
pub fn gmod_close(_attr: TokenStream, item: TokenStream) -> TokenStream {
handle_gmod(item, Some("gmod13_close"))
}
#[proc_macro_attribute]
pub fn lua_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
handle_gmod(item, None)
}