rglua_macros/
lib.rs

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	// Make sure parameter is a LuaState
40	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			// In the future this could check if it is *c_void as well.
44			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	// Make sure abi is either omitted, "C", or "C-unwind"
64	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		// We don't need to change the name of the innter function, because the outer function will compile to
75		// gmod13_(open|close)
76		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					// Your error must implement display / .to_string().
91					// I'd recommend ``thiserror``.
92					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		// Prevent attributes from going onto the generated extern "C" function
111		// They will be applied to the inner function.
112		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	// Attributes will go onto the inner function
128	ast.attrs.push(parse_quote!(#[no_mangle]));
129
130	ast.into_token_stream().into()
131}
132
133#[proc_macro_attribute]
134/// Creates the entrypoint to garrysmod. Compiles down to gmod13_open.
135///
136/// Normally you would not be able to return types other than i32 through to gmod13_open,
137/// this is still true, but this proc-macro allows it through unwrapping the result and containing attributes on a hidden generated function.
138/// # Examples
139/// ```rust
140/// use rglua::prelude::*;
141/// #[gmod_open]
142/// fn entry(state: LuaState) -> Result<i32, std::io::Error> {
143///     printgm!(state, "Hello, gmod!");
144///     std::fs::write("foo.txt", "bar")?;
145///
146///     // We don't push objects to lua, so return 0 (# of returns)
147///     Ok(0)
148/// }
149/// ```
150pub fn gmod_open(_attr: TokenStream, item: TokenStream) -> TokenStream {
151	handle_gmod(item, Some("gmod13_open"))
152}
153
154#[proc_macro_attribute]
155/// Creates the exitpoint to garrysmod. Compiles down to gmod13_close.
156///
157/// Normally you would not be able to return types other than i32 through to gmod13_open,
158/// this is still true, but this proc-macro allows it through unwrapping the result and containing attributes on a hidden generated function.
159/// # Examples
160/// ```rust
161/// use rglua::prelude::*;
162/// #[gmod_close]
163/// fn exit(state: LuaState) -> Result<i32, std::io::Error> {
164///     printgm!(state, "Goodbye, gmod!");
165///     // Do your cleanup stuff here.
166///     // We don't push objects to lua, so return 0 (# of returns)
167///     Ok(0)
168/// }
169/// ```
170pub fn gmod_close(_attr: TokenStream, item: TokenStream) -> TokenStream {
171	handle_gmod(item, Some("gmod13_close"))
172}
173
174#[proc_macro_attribute]
175/// Creates a valid function to be passed down to lua.
176/// Note this function will not be registered automatically for you, you must use luaL_register or functions like lua_pushcfunction.
177/// This may change in the future or allow for something like #[lua_function(name = "foo", auto = true)]
178/// # Examples
179/// ```rust
180/// use rglua::prelude::*;
181/// #[lua_function]
182/// fn write(state: LuaState) -> Result<i32, std::io::Error> {
183///     printgm!(state, "Hello, lua!");
184///     std::fs::write("foo.txt", "bar")?;
185///     Ok(0)
186/// }
187pub fn lua_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
188	handle_gmod(item, None)
189}