gmod_macros/
lib.rs

1#[macro_use]
2extern crate syn;
3
4#[macro_use]
5extern crate quote;
6
7use proc_macro::TokenStream;
8use quote::ToTokens;
9use syn::ItemFn;
10
11macro_rules! wrap_compile_error {
12	($input:ident, $code:expr) => {{
13		let orig_tokens = $input.clone();
14		match (|| -> Result<TokenStream, syn::Error> { $code })() {
15			Ok(tokens) => tokens,
16			Err(_) => return orig_tokens
17		}
18	}};
19}
20
21fn check_lua_function(input: &mut ItemFn) {
22	assert!(input.sig.asyncness.is_none(), "Cannot be async");
23	assert!(input.sig.constness.is_none(), "Cannot be const");
24	assert!(input.sig.inputs.len() == 1, "There can only be one argument, and it should be a pointer to the Lua state (gmod::lua::State)");
25	assert!(input.sig.abi.is_none() || input.sig.abi.as_ref().and_then(|abi| abi.name.as_ref()).map(|abi| abi.value() == "C-unwind").unwrap_or(true), "Do not specify an ABI");
26	input.sig.abi = Some(syn::parse_quote!(extern "C-unwind"));
27}
28
29fn genericify_return(item_fn: &mut ItemFn) {
30	let stmts = std::mem::take(&mut item_fn.block.stmts);
31	let output = std::mem::replace(&mut item_fn.sig.output, parse_quote!(-> i32));
32	item_fn.block.stmts = vec![syn::parse2(quote!({::gmod::lua::ValuesReturned::from((|| #output {#(#stmts);*})()).into()})).unwrap()];
33}
34
35#[proc_macro_attribute]
36pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
37	wrap_compile_error!(tokens, {
38		let mut input = syn::parse::<ItemFn>(tokens)?;
39
40		let lua_ident = format_ident!("{}", match &input.sig.inputs[0] {
41			syn::FnArg::Typed(arg) => arg.pat.to_token_stream().to_string(),
42			_ => unreachable!(),
43		});
44
45		// Capture the Lua state
46		input.block.stmts.insert(0, syn::parse2(quote!(::gmod::lua::__set_state__internal(#lua_ident);)).unwrap());
47
48		// Load lua_shared
49		input.block.stmts.insert(0, syn::parse2(quote!(#[allow(unused_unsafe)] unsafe { ::gmod::lua::load() })).unwrap());
50
51		// Make sure it's valid
52		check_lua_function(&mut input);
53
54		// No mangling
55		input.attrs.push(parse_quote!(#[no_mangle]));
56
57		// Make the return type nice and dynamic
58		genericify_return(&mut input);
59
60		Ok(input.into_token_stream().into())
61	})
62}
63
64#[proc_macro_attribute]
65pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
66	wrap_compile_error!(tokens, {
67		let mut input = syn::parse::<ItemFn>(tokens)?;
68
69		// Make sure it's valid
70		check_lua_function(&mut input);
71
72		// No mangling
73		input.attrs.push(parse_quote!(#[no_mangle]));
74
75		// Shutdown gmcl thread if it's running
76		#[cfg(feature = "gmcl")] {
77			let stmts = std::mem::take(&mut input.block.stmts);
78			input.block.stmts = vec![syn::parse2(quote!({
79				let ret = (|| {#(#stmts);*})();
80				::gmod::gmcl::restore_stdout();
81				ret
82			})).unwrap()];
83		}
84
85		// Make the return type nice and dynamic
86		genericify_return(&mut input);
87
88		Ok(input.into_token_stream().into())
89	})
90}
91
92#[proc_macro_attribute]
93pub fn lua_function(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
94	wrap_compile_error!(tokens, {
95		let mut input = syn::parse::<ItemFn>(tokens)?;
96
97		// Make sure it's valid
98		check_lua_function(&mut input);
99
100		// Make the return type nice and dynamic
101		genericify_return(&mut input);
102
103		Ok(input.into_token_stream().into())
104	})
105}