rglua_macros/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use proc_macro::TokenStream;
use quote::{quote, ToTokens};

use syn::{parse_macro_input, parse_quote, 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);

	assert!(ast.sig.asyncness.is_none(), "Cannot be asynchronous");
	assert!(ast.sig.constness.is_none(), "Cannot be const");
	assert!(
		ast.sig.inputs.len() == 1,
		"Must have one parameter, being the Lua state (rglua::lua::LuaState)"
	);

	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 {
			assert!(
				ret.as_str() == "i32",
				"Exported function must return i32 or Result<i32, E>"
			);
		}
	} else {
		panic!("Exported function must return i32 or Result<i32, E>");
	}

	let lua_shared_param;
	let lua_shared_ty;
	// Make sure parameter is a LuaState
	match ast.sig.inputs.first().unwrap() {
		FnArg::Receiver(_) => panic!("Parameter cannot be self"),
		FnArg::Typed(arg) => {
			// In the future this could check if it is *c_void as well.
			match arg.ty.to_token_stream().to_string().as_str() {
				"LuaState" | "rglua :: lua :: LuaState" => (),
				a => panic!("Parameter must be rglua::lua::LuaState. Got {}", a),
			}

			lua_shared_ty = &arg.ty;

			match *arg.pat {
				syn::Pat::Ident(ref i) => {
					lua_shared_param = &i.ident;
				}
				syn::Pat::Wild(_) => {
					panic!("Parameter must be named. Try _foo");
				}
				_ => panic!("Parameter must be in ``ident: ty`` format"),
			}
		}
	}

	// Make sure abi is either omitted, "C", or "C-unwind"
	if let Some(abi) = &ast.sig.abi {
		match abi.name.as_ref().unwrap().value().as_str() {
			"C" | "C-unwind" => (),
			_ => panic!("Only C (or C-unwind) ABI is supported"),
		}
	} else {
		ast.sig.abi = Some(parse_quote!(extern "C"))
	}

	if let Some(ret) = returns_result {
		// We don't need to change the name of the innter function, because the outer function will compile to
		// gmod13_(open|close)
		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) => {
					// Your error must implement display / .to_string().
					// I'd recommend ``thiserror``.
					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)));

		// Prevent attributes from going onto the generated extern "C" function
		// They will be applied to the inner function.
		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" {
				panic!("Using no_mangle is unnecessary on exported functions");
			}
		}
	}

	// Attributes will go onto the inner function
	ast.attrs.push(parse_quote!(#[no_mangle]));

	ast.into_token_stream().into()
}

#[proc_macro_attribute]
/// Creates the entrypoint to garrysmod. Compiles down to gmod13_open.
///
/// Normally you would not be able to return types other than i32 through to gmod13_open,
/// this is still true, but this proc-macro allows it through unwrapping the result and containing attributes on a hidden generated function.
/// # Examples
/// ```rust
/// use rglua::prelude::*;
/// #[gmod_open]
/// fn entry(state: LuaState) -> Result<i32, std::io::Error> {
///     printgm!(state, "Hello, gmod!");
///     std::fs::write("foo.txt", "bar")?;
///
///     // We don't push objects to lua, so return 0 (# of returns)
///     Ok(0)
/// }
/// ```
pub fn gmod_open(_attr: TokenStream, item: TokenStream) -> TokenStream {
	handle_gmod(item, Some("gmod13_open"))
}

#[proc_macro_attribute]
/// Creates the exitpoint to garrysmod. Compiles down to gmod13_close.
///
/// Normally you would not be able to return types other than i32 through to gmod13_open,
/// this is still true, but this proc-macro allows it through unwrapping the result and containing attributes on a hidden generated function.
/// # Examples
/// ```rust
/// use rglua::prelude::*;
/// #[gmod_close]
/// fn exit(state: LuaState) -> Result<i32, std::io::Error> {
///     printgm!(state, "Goodbye, gmod!");
///     // Do your cleanup stuff here.
///     // We don't push objects to lua, so return 0 (# of returns)
///     Ok(0)
/// }
/// ```
pub fn gmod_close(_attr: TokenStream, item: TokenStream) -> TokenStream {
	handle_gmod(item, Some("gmod13_close"))
}

#[proc_macro_attribute]
/// Creates a valid function to be passed down to lua.
/// Note this function will not be registered automatically for you, you must use luaL_register or functions like lua_pushcfunction.
/// This may change in the future or allow for something like #[lua_function(name = "foo", auto = true)]
/// # Examples
/// ```rust
/// use rglua::prelude::*;
/// #[lua_function]
/// fn write(state: LuaState) -> Result<i32, std::io::Error> {
///     printgm!(state, "Hello, lua!");
///     std::fs::write("foo.txt", "bar")?;
///     Ok(0)
/// }
pub fn lua_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
	handle_gmod(item, None)
}