1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3
4use syn::{parse_macro_input, parse_quote, spanned::Spanned, FnArg, ItemFn, ReturnType, Type};
5
6fn handle_gmod(item: TokenStream, export: Option<&str>) -> TokenStream {
7 let mut returns_result: Option<&Box<Type>> = None;
8
9 let mut ast = parse_macro_input!(item as ItemFn);
10
11 if ast.sig.asyncness.is_some() {
12 return syn::Error::new(ast.sig.span(), "Cannot be async").into_compile_error().into();
13 }
14
15 if ast.sig.constness.is_some() {
16 return syn::Error::new(ast.sig.span(), "Cannot be const").into_compile_error().into();
17 }
18
19 if ast.sig.inputs.len() != 1 {
20 return syn::Error::new(ast.sig.span(), "Must have one parameter, being the Lua state (rglua::lua::LuaState)").into_compile_error().into();
21 }
22
23 if let ReturnType::Type(_, ty) = &ast.sig.output {
24 let mut ret = ty.to_token_stream().to_string();
25 if ret.starts_with("Result < i32") | ret.starts_with("std :: result :: Result < i32") {
26 ret.retain(|c| !c.is_whitespace());
27 returns_result = Some(ty);
28 } else {
29 if ret.as_str() != "i32" {
30 return syn::Error::new(ast.sig.output.span(), "Exported function must return i32 or Result<i32, E>").into_compile_error().into();
31 }
32 }
33 } else {
34 return syn::Error::new(ast.sig.output.span(), "Exported function must return i32 or Result<i32, E>").into_compile_error().into();
35 }
36
37 let lua_shared_param;
38 let lua_shared_ty;
39 match ast.sig.inputs.first().unwrap() {
41 FnArg::Receiver(_) => return syn::Error::new(ast.sig.inputs.span(), "Parameter cannot be self").into_compile_error().into(),
42 FnArg::Typed(arg) => {
43 match arg.ty.to_token_stream().to_string().as_str() {
45 "LuaState" | "rglua :: lua :: LuaState" => (),
46 a => return syn::Error::new(arg.ty.span(), format!("Parameter must be rglua::lua::LuaState. Got {a}")).to_compile_error().into(),
47 }
48
49 lua_shared_ty = &arg.ty;
50
51 match *arg.pat {
52 syn::Pat::Ident(ref i) => {
53 lua_shared_param = &i.ident;
54 }
55 syn::Pat::Wild(_) => {
56 return syn::Error::new(arg.pat.span(), "Parameter must be named. Try _foo").to_compile_error().into();
57 }
58 _ => return syn::Error::new(arg.pat.span(), "Parameter must be in 'ident: ty' format").to_compile_error().into(),
59 }
60 }
61 }
62
63 if let Some(abi) = &ast.sig.abi {
65 match abi.name.as_ref().unwrap().value().as_str() {
66 "C" | "C-unwind" => (),
67 _ => return syn::Error::new(abi.span(), "Only C or C-unwind are supported").to_compile_error().into(),
68 }
69 } else {
70 ast.sig.abi = Some(parse_quote!(extern "C"))
71 }
72
73 if let Some(ret) = returns_result {
74 let inner_fn = &ast.sig.ident;
77 let inner_stmts = &ast.block.stmts;
78 let attrs = &ast.attrs;
79
80 let inner = quote! {
81 #(#attrs)*
82 fn #inner_fn(#lua_shared_param: #lua_shared_ty) -> #ret {
83 #(#inner_stmts)*
84 }
85 };
86
87 let resultant = quote! {
88 match #inner_fn(#lua_shared_param) {
89 Err(why) => {
90 let err = why.to_string();
93 let err = cstr!(err);
94 rglua::lua::luaL_error(#lua_shared_param, cstr!("%s"), err.as_ptr());
95 },
96 Ok(n) => { return n }
97 }
98 };
99
100 ast.block
101 .stmts
102 .insert(0, syn::parse2(inner).expect("Error parsing inner fn"));
103 ast.block
104 .stmts
105 .insert(1, syn::parse2(resultant).expect("Error parsing resultant"));
106 ast.block.stmts.truncate(2);
107
108 ast.sig.output = ReturnType::Type(Default::default(), Box::new(parse_quote!(i32)));
109
110 ast.attrs.clear();
113 }
114
115 if let Some(export) = export {
116 ast.sig.ident = quote::format_ident!("{}", export);
117 }
118
119 for attr in &ast.attrs {
120 if let Some(id) = attr.path.get_ident() {
121 if id == "no_mangle" {
122 return syn::Error::new(id.span(), "Using no_mangle is unnecessary on exported functions").into_compile_error().into();
123 }
124 }
125 }
126
127 ast.attrs.push(parse_quote!(#[no_mangle]));
129
130 ast.into_token_stream().into()
131}
132
133#[proc_macro_attribute]
134pub fn gmod_open(_attr: TokenStream, item: TokenStream) -> TokenStream {
151 handle_gmod(item, Some("gmod13_open"))
152}
153
154#[proc_macro_attribute]
155pub fn gmod_close(_attr: TokenStream, item: TokenStream) -> TokenStream {
171 handle_gmod(item, Some("gmod13_close"))
172}
173
174#[proc_macro_attribute]
175pub fn lua_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
188 handle_gmod(item, None)
189}