1use std::sync::atomic::AtomicUsize;
2use std::sync::atomic::Ordering;
3
4use proc_macro::TokenStream;
5use proc_macro_crate::{FoundCrate, crate_name};
6use quote::quote;
7use syn::ItemFn;
8use syn::Pat;
9use syn::PatIdent;
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 get_crate_name() -> proc_macro2::TokenStream {
22 match crate_name("gmodx") {
23 Ok(FoundCrate::Itself) => quote!(crate),
24 Ok(FoundCrate::Name(name)) => {
25 let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
26 quote!(::#ident)
27 }
28 Err(_) => quote!(::gmodx), }
30}
31
32fn parse_lua_ident(input: &syn::FnArg) -> syn::Ident {
33 match input {
34 syn::FnArg::Typed(arg) => {
35 if let Pat::Ident(PatIdent { ident, .. }) = &*arg.pat {
36 ident.clone()
37 } else {
38 panic!("Can't use self for lua functions!")
39 }
40 }
41 syn::FnArg::Receiver(_) => panic!("Needs to be a lua state!"),
42 }
43}
44
45fn check_lua_function(input: &mut ItemFn) {
46 assert!(input.sig.asyncness.is_none(), "Cannot be async");
47 assert!(input.sig.constness.is_none(), "Cannot be const");
48 assert!(
49 input.sig.inputs.len() == 1,
50 "There can only be one argument, and it should be a pointer to the Lua state (gmodx::lua::State)"
51 );
52 assert!(input.sig.abi.is_none(), "Do not specify an ABI");
53 assert!(input.sig.unsafety.is_none(), "Cannot be unsafe");
54}
55
56#[proc_macro_attribute]
57pub fn gmod13_open(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
58 wrap_compile_error!(tokens, {
59 let mut input = syn::parse::<ItemFn>(tokens)?;
60
61 check_gmod13_function(&mut input, "gmod13_open");
62
63 let inputs = &input.sig.inputs;
64 let lua_ident = parse_lua_ident(&inputs[0]);
65 let param_ty = match &inputs[0] {
66 syn::FnArg::Typed(arg) => &arg.ty,
67 syn::FnArg::Receiver(_) => panic!("Needs to be a lua state!"),
68 };
69 let crate_name = get_crate_name();
70
71 let block = input.block;
72
73 let output = quote! {
74 #[unsafe(no_mangle)]
75 pub extern "C-unwind" fn gmod13_open(#lua_ident: #crate_name::lua::State) -> i32 {
76 {
77 trait AssertSame<T> {}
78 impl AssertSame<#crate_name::lua::State> for #crate_name::lua::State {}
79 fn assert_impl<T: AssertSame<#crate_name::lua::State>>() {}
80 assert_impl::<#param_ty>();
81 }
82
83 #crate_name::open_close::load_all(&#lua_ident);
84
85 #block
86
87 0
88 }
89 };
90
91 Ok(output.into())
92 })
93}
94
95#[proc_macro_attribute]
96pub fn gmod13_close(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
97 wrap_compile_error!(tokens, {
98 let mut input = syn::parse::<ItemFn>(tokens)?;
99
100 check_gmod13_function(&mut input, "gmod13_close");
101
102 let inputs = &input.sig.inputs;
103 let lua_ident = parse_lua_ident(&input.sig.inputs[0]);
104 let param_ty = match &inputs[0] {
105 syn::FnArg::Typed(arg) => &arg.ty,
106 syn::FnArg::Receiver(_) => panic!("Needs to be a lua state!"),
107 };
108 let crate_name = get_crate_name();
109
110 let block = input.block;
111
112 let output = quote! {
113 #[unsafe(no_mangle)]
114 pub extern "C-unwind" fn gmod13_close(#lua_ident: #crate_name::lua::State) -> i32 {
115 {
116 trait AssertSame<T> {}
117 impl AssertSame<#crate_name::lua::State> for #crate_name::lua::State {}
118 fn assert_impl<T: AssertSame<#crate_name::lua::State>>() {}
119 assert_impl::<#param_ty>();
120 }
121
122 #block
123
124 #crate_name::open_close::unload_all(&#lua_ident);
125
126 0
127 }
128 };
129
130 Ok(output.into())
131 })
132}
133
134fn check_gmod13_function(input: &mut ItemFn, expected_name: &str) {
135 check_lua_function(input);
136
137 assert!(
138 input.sig.ident == expected_name,
139 "Function must be named '{}', found '{}'",
140 expected_name,
141 input.sig.ident
142 );
143
144 match &input.sig.output {
145 syn::ReturnType::Default => {}
146 syn::ReturnType::Type(_, ty) => {
147 if let syn::Type::Tuple(tuple) = &**ty {
149 if !tuple.elems.is_empty() {
150 panic!("Function must not return anything",);
151 }
152 } else {
153 panic!("Function must not return anything",);
154 }
155 }
156 }
157}
158
159static COUNTER: AtomicUsize = AtomicUsize::new(0);
160
161#[proc_macro]
162pub fn unique_id(input: TokenStream) -> TokenStream {
163 let unique_str = {
164 let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
165 let timestamp = std::time::SystemTime::now()
166 .duration_since(std::time::UNIX_EPOCH)
167 .unwrap()
168 .as_nanos();
169
170 let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown".to_string());
171 format!("__GMODX_UNIQUE_ID_{}_{}_{}", version, timestamp, counter)
172 };
173 let input_str = input.to_string();
174 let is_c_string = input_str.trim() == "cstr";
175 if is_c_string {
176 format!("c\"{}\"", unique_str).parse().unwrap()
177 } else {
178 format!("\"{}\"", unique_str).parse().unwrap()
179 }
180}