Skip to main content

gmodx_macros/
lib.rs

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), // fallback
29    }
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            // Check if it's the unit type ()
148            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}