arma_rs_proc/
lib.rs

1mod derive;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5use quote::quote;
6use syn::{DeriveInput, Error, ItemFn};
7
8#[proc_macro_attribute]
9/// Used to generate the necessary boilerplate for an Arma extension.
10/// It should be applied to a function that takes no arguments and returns an extension.
11pub fn arma(_attr: TokenStream, item: TokenStream) -> TokenStream {
12    let ast = syn::parse_macro_input!(item as ItemFn);
13    let init = ast.sig.ident.clone();
14
15    let extern_type = if cfg!(windows) { "stdcall" } else { "C" };
16
17    let ext_init = quote! {
18        if RV_EXTENSION.is_none() {
19            RV_EXTENSION = Some(#init());
20        }
21    };
22
23    #[cfg(all(target_os = "windows", target_arch = "x86"))]
24    let prefix = "safe32_";
25
26    #[cfg(not(all(target_os = "windows", target_arch = "x86")))]
27    let prefix = "";
28
29    macro_rules! fn_ident {
30        ( $name:literal ) => {
31            Ident::new(&format!("{prefix}{}", $name), Span::call_site())
32        };
33    }
34    let versionfn = fn_ident!("RVExtensionVersion");
35    let noargfn = fn_ident!("RVExtension");
36    let argfn = fn_ident!("RVExtensionArgs");
37    let callbackfn = fn_ident!("RVExtensionRegisterCallback");
38    let contextfn = fn_ident!("RVExtensionContext");
39
40    TokenStream::from(quote! {
41        use arma_rs::libc as arma_rs_libc;
42
43        static mut RV_EXTENSION: Option<Extension> = None;
44
45        #[cfg(all(target_os="windows", target_arch="x86"))]
46        arma_rs::link_args::windows! {
47            unsafe {
48                raw("/EXPORT:_RVExtensionVersion@8=_safe32_RVExtensionVersion@8");
49                raw("/EXPORT:_RVExtension@12=_safe32_RVExtension@12");
50                raw("/EXPORT:_RVExtensionArgs@20=_safe32_RVExtensionArgs@20");
51                raw("/EXPORT:_RVExtensionRegisterCallback@4=_safe32_RVExtensionRegisterCallback@4");
52                raw("/EXPORT:_RVExtensionContext@8=_safe32_RVExtensionContext@8");
53            }
54        }
55
56        /// Returns extension version, called by Arma on extension load.
57        /// This function is generated by the [`arma_rs::arma`] proc macro.
58        #[no_mangle]
59        #[doc(hidden)]
60        pub unsafe extern #extern_type fn #versionfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t) -> arma_rs_libc::c_int {
61            #ext_init
62            if let Some(ext) = &RV_EXTENSION {
63                arma_rs::write_cstr(ext.version().to_string(), output, size);
64            }
65            0
66        }
67
68        /// Run extension function, called by Arma on `callExtension` without arguments.
69        /// This function is generated by the [`arma_rs::arma`] proc macro.
70        #[no_mangle]
71        #[doc(hidden)]
72        pub unsafe extern #extern_type fn #noargfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t, function: *mut arma_rs_libc::c_char) {
73            #ext_init
74            if let Some(ext) = &RV_EXTENSION {
75                if ext.allow_no_args() {
76                    ext.handle_call(function, output, size, None, None, true);
77                }
78            }
79        }
80
81        /// Run extension function with arguments, called by Arma on `callExtension` with arguments.
82        /// This function is generated by the [`arma_rs::arma`] proc macro.
83        #[no_mangle]
84        #[doc(hidden)]
85        pub unsafe extern #extern_type fn #argfn(output: *mut arma_rs_libc::c_char, size: arma_rs_libc::size_t, function: *mut arma_rs_libc::c_char, args: *mut *mut arma_rs_libc::c_char, arg_count: arma_rs_libc::c_int) -> arma_rs_libc::c_int {
86            #ext_init
87            if let Some(ext) = &RV_EXTENSION {
88                ext.handle_call(function, output, size, Some(args), Some(arg_count), true)
89            } else {
90                0
91            }
92        }
93
94        /// Set extension callback, called by Arma on extension load.
95        /// This function is generated by the [`arma_rs::arma`] proc macro.
96        #[no_mangle]
97        #[doc(hidden)]
98        pub unsafe extern #extern_type fn #callbackfn(callback: arma_rs::Callback) {
99            #ext_init
100            if let Some(ext) = &mut RV_EXTENSION {
101                ext.register_callback(callback);
102                ext.run_callbacks();
103            }
104        }
105
106        /// Provide extension call context, called by Arma on `callExtension`.
107        /// This function is generated by the [`arma_rs::arma`] proc macro.
108        #[no_mangle]
109        #[doc(hidden)]
110        pub unsafe extern #extern_type fn #contextfn(args: *mut *mut arma_rs_libc::c_char, arg_count: arma_rs_libc::c_int) {
111            #ext_init
112            if let Some(ext) = &mut RV_EXTENSION {
113                ext.handle_call_context(args, arg_count);
114            }
115        }
116
117        #ast
118    })
119}
120
121/// Derive implementation of `FromArma`, only supports structs.
122/// - Map structs are converted from an hashmap.
123/// - Tuple structs are converted from an array.
124/// - Newtype structs directly use's the value's `FromArma` implementation.
125/// - Unit-like structs are not supported.
126///
127/// ### Container Attributes
128/// - `#[arma(transparent)]`: treat single field map structs as if its a newtype structs.
129/// - `#[arma(default)]`: any missing field will be filled by the structs `Default` implementation.
130///
131/// ### Field Attributes
132/// - `#[arma(from_str)]`: use the types `std::str::FromStr` instead of `FromArma`.
133/// - `#[arma(default)]`: if missing use its `Default` implementation (takes precedence over container).
134#[proc_macro_derive(FromArma, attributes(arma))]
135pub fn derive_from_arma(item: TokenStream) -> TokenStream {
136    let input = syn::parse_macro_input!(item as DeriveInput);
137    derive::generate_from_arma(input)
138        .unwrap_or_else(Error::into_compile_error)
139        .into()
140}
141
142/// Derive implementation of `IntoArma`, only supports structs.
143/// - Map structs are converted to an hashmap.
144/// - Tuple structs are converted to an array.
145/// - Newtype structs directly use's the value's `IntoArma` implementation.
146/// - Unit-like structs are not supported.
147///
148/// ### Container Attributes
149/// - `#[arma(transparent)]`: treat single field map structs as if its a newtype structs.
150///
151/// ### Field Attributes
152/// - `#[arma(to_string)]`: use the types `std::string::ToString` instead of `IntoArma`.
153#[proc_macro_derive(IntoArma, attributes(arma))]
154pub fn derive_into_arma(item: TokenStream) -> TokenStream {
155    let input = syn::parse_macro_input!(item as DeriveInput);
156    derive::generate_into_arma(input)
157        .unwrap_or_else(Error::into_compile_error)
158        .into()
159}