1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use quote::quote;
4use syn::{spanned::Spanned, Lit};
5
6fn extract_args(a: &syn::FnArg) -> &syn::PatType {
7 match a {
8 syn::FnArg::Typed(p) => p,
9 _ => panic!("Not supported on types with `self`!"),
10 }
11}
12#[allow(clippy::test_attr_in_doctest)]
14#[proc_macro_attribute]
35pub fn bind(attr: TokenStream, item: TokenStream) -> TokenStream {
36 let input = syn::parse_macro_input!(item as syn::ItemFn);
37 let proc = syn::parse_macro_input!(attr as Option<syn::Lit>);
38
39 let func_name = &input.sig.ident;
40 let func_name_disp = quote!(#func_name).to_string();
41
42 let func_name_ffi = format!("{func_name_disp}_ffi");
43 let func_name_ffi = Ident::new(&func_name_ffi, func_name.span());
44 let func_name_ffi_disp = quote!(#func_name_ffi).to_string();
45
46 let args = &input.sig.inputs;
47
48 let all_docs = input
49 .attrs
50 .iter()
51 .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer))
52 .filter_map(|attr| match &attr.meta {
53 syn::Meta::NameValue(nameval) => {
54 let ident = nameval.path.get_ident()?;
55 if ident.to_string() == "doc".to_string() {
56 match &nameval.value {
57 syn::Expr::Lit(literal) => match &literal.lit {
58 syn::Lit::Str(docstring) => {
59 Some(format!("///{}\n", docstring.value(),))
60 }
61 _ => None,
62 },
63 _ => None,
64 }
65 } else {
66 None
67 }
68 }
69 _ => None,
70 })
71 .collect::<String>();
72
73 let func_return = match &input.sig.output {
75 syn::ReturnType::Default => {
76 return syn::Error::new(
77 input.span(),
78 "Empty returns are not allowed, please return a Result",
79 )
80 .to_compile_error()
81 .into()
82 }
83
84 syn::ReturnType::Type(_, ty) => match ty.as_ref() {
85 &syn::Type::Path(_) => &input.sig.output,
86 _ => {
87 return syn::Error::new(input.span(), "Invalid return type, please return a Result")
88 .to_compile_error()
89 .into()
90 }
91 },
92 };
93
94 let signature = quote! {
95 #[no_mangle]
96 pub unsafe extern "C-unwind" fn #func_name_ffi (
97 __argc: ::byondapi::sys::u4c,
98 __argv: *mut ::byondapi::value::ByondValue
99 ) -> ::byondapi::value::ByondValue
100 };
101
102 let body = &input.block;
103 let mut arg_names: syn::punctuated::Punctuated<syn::Ident, syn::Token![,]> =
104 syn::punctuated::Punctuated::new();
105 let mut proc_arg_unpacker: syn::punctuated::Punctuated<
106 proc_macro2::TokenStream,
107 syn::Token![,],
108 > = syn::punctuated::Punctuated::new();
109
110 for arg in args.iter().map(extract_args) {
111 if let syn::Pat::Ident(p) = &*arg.pat {
112 arg_names.push(p.ident.clone());
113 let index = arg_names.len() - 1;
114 proc_arg_unpacker.push(quote! {
115 args.get(#index).map(::byondapi::value::ByondValue::clone).unwrap_or_default()
116 });
117 }
118 }
119
120 let arg_names_disp = quote!(#arg_names).to_string();
121
122 let cthook_prelude = match &proc {
124 Some(Lit::Str(p)) => {
125 quote! {
126 ::byondapi::inventory::submit!({
127 ::byondapi::binds::Bind {
128 proc_path: #p,
129 func_name: #func_name_ffi_disp,
130 func_arguments: #arg_names_disp,
131 docs: #all_docs,
132 is_variadic: false,
133 }
134 });
135 }
136 }
137 Some(other_literal) => {
138 return syn::Error::new(
139 other_literal.span(),
140 "Bind attributes must be a string literal or empty",
141 )
142 .to_compile_error()
143 .into()
144 }
145 None => {
146 let mut func_name_disp = func_name_disp.clone();
147 func_name_disp.insert_str(0, "/proc/");
148 quote! {
149 ::byondapi::inventory::submit!({
150 ::byondapi::binds::Bind{
151 proc_path: #func_name_disp,
152 func_name: #func_name_ffi_disp,
153 func_arguments: #arg_names_disp,
154 docs: #all_docs,
155 is_variadic: false,
156 }
157 });
158 }
159 }
160 };
161
162 let crash_syntax = if cfg!(feature = "old-crash-workaround") {
163 quote! {
164 let error_string = ::byondapi::value::ByondValue::try_from(error_string).unwrap();
165 ::byondapi::global_call::call_global_id({
166 static STACK_TRACE: ::std::sync::OnceLock<u32> = ::std::sync::OnceLock::new();
167 *STACK_TRACE.get_or_init(|| ::byondapi::byond_string::str_id_of("byondapi_stack_trace")
168 .expect("byondapi-rs implicitly expects byondapi_stack_trace to exist as a proc for error reporting purposes, this proc doesn't exist!")
169 )
170 }
171 ,&[error_string]).unwrap();
172 }
173 } else {
174 quote! {
175 ::byondapi::runtime::byond_runtime(error_string);
176 }
177 };
178
179 let result = quote! {
180 #cthook_prelude
181 #signature {
182 let args = unsafe { ::byondapi::parse_args(__argc, __argv) };
183 match #func_name(#proc_arg_unpacker) {
184 Ok(val) => val,
185 Err(e) => {
186 let error_string = ::std::format!("{e:?}");
187 #crash_syntax
188 ::byondapi::value::ByondValue::null()
189 }
190 }
191
192 }
193 fn #func_name(#args) #func_return
194 #body
195 };
196 result.into()
197}
198
199#[proc_macro_attribute]
202pub fn bind_raw_args(attr: TokenStream, item: TokenStream) -> TokenStream {
203 let input = syn::parse_macro_input!(item as syn::ItemFn);
204 let proc = syn::parse_macro_input!(attr as Option<syn::Lit>);
205
206 let func_name = &input.sig.ident;
207 let func_name_disp = quote!(#func_name).to_string();
208
209 let func_name_ffi = format!("{func_name_disp}_ffi");
210 let func_name_ffi = Ident::new(&func_name_ffi, func_name.span());
211 let func_name_ffi_disp = quote!(#func_name_ffi).to_string();
212
213 let all_docs = input
214 .attrs
215 .iter()
216 .filter(|attr| matches!(attr.style, syn::AttrStyle::Outer))
217 .filter_map(|attr| match &attr.meta {
218 syn::Meta::NameValue(nameval) => {
219 let ident = nameval.path.get_ident()?;
220 if ident.to_string() == "doc".to_string() {
221 match &nameval.value {
222 syn::Expr::Lit(literal) => match &literal.lit {
223 syn::Lit::Str(docstring) => {
224 Some(format!("///{}\n", docstring.value(),))
225 }
226 _ => None,
227 },
228 _ => None,
229 }
230 } else {
231 None
232 }
233 }
234 _ => None,
235 })
236 .collect::<String>();
237
238 let func_return = match &input.sig.output {
240 syn::ReturnType::Default => {
241 return syn::Error::new(
242 input.span(),
243 "Empty returns are not allowed, please return a Result",
244 )
245 .to_compile_error()
246 .into()
247 }
248
249 syn::ReturnType::Type(_, ty) => match ty.as_ref() {
250 &syn::Type::Path(_) => &input.sig.output,
251 _ => {
252 return syn::Error::new(input.span(), "Invalid return type, please return a Result")
253 .to_compile_error()
254 .into()
255 }
256 },
257 };
258
259 if !input.sig.inputs.is_empty() {
260 return syn::Error::new(
261 input.sig.inputs.span(),
262 "Do not specify arguments for raw arg binds",
263 )
264 .to_compile_error()
265 .into();
266 }
267
268 let signature = quote! {
269 #[no_mangle]
270 pub unsafe extern "C-unwind" fn #func_name_ffi (
271 __argc: ::byondapi::sys::u4c,
272 __argv: *mut ::byondapi::value::ByondValue
273 ) -> ::byondapi::value::ByondValue
274 };
275
276 let body = &input.block;
277
278 let cthook_prelude = match proc {
280 Some(Lit::Str(p)) => {
281 quote! {
282 ::byondapi::inventory::submit!({
283 ::byondapi::binds::Bind {
284 proc_path: #p,
285 func_name: #func_name_ffi_disp,
286 func_arguments: "",
287 docs: #all_docs,
288 is_variadic: true,
289 }
290 });
291 }
292 }
293 Some(other_literal) => {
294 return syn::Error::new(
295 other_literal.span(),
296 "Bind attributes must be a string literal or empty",
297 )
298 .to_compile_error()
299 .into()
300 }
301 None => {
302 let mut func_name_disp = func_name_disp.clone();
303 func_name_disp.insert_str(0, "/proc/");
304 quote! {
305 ::byondapi::inventory::submit!({
306 ::byondapi::binds::Bind{
307 proc_path: #func_name_disp,
308 func_name: #func_name_ffi_disp,
309 func_arguments: "",
310 docs: #all_docs,
311 is_variadic: true,
312 }
313 });
314 }
315 }
316 };
317 let crash_syntax = if cfg!(feature = "old-crash-workaround") {
318 quote! {
319 let error_string = ::byondapi::value::ByondValue::try_from(error_string).unwrap();
320 ::byondapi::global_call::call_global_id({
321 static STACK_TRACE: ::std::sync::OnceLock<u32> = ::std::sync::OnceLock::new();
322 *STACK_TRACE.get_or_init(|| ::byondapi::byond_string::str_id_of("byondapi_stack_trace")
323 .expect("byondapi-rs implicitly expects byondapi_stack_trace to exist as a proc for error reporting purposes, this proc doesn't exist!")
324 )
325 }
326 ,&[error_string]).unwrap();
327 }
328 } else {
329 quote! {
330 ::byondapi::runtime::byond_runtime(error_string);
331 }
332 };
333
334 let result = quote! {
335 #cthook_prelude
336 #signature {
337 let mut args = unsafe { ::byondapi::parse_args(__argc, __argv) };
338 match #func_name(args) {
339 Ok(val) => val,
340 Err(e) => {
341 let error_string = ::std::format!("{e:?}");
342 #crash_syntax
343 ::byondapi::value::ByondValue::null()
344 }
345 }
346 }
347 fn #func_name(args: &mut [::byondapi::value::ByondValue]) #func_return
348 #body
349 };
350 result.into()
351}
352
353#[proc_macro_attribute]
354pub fn init(_: TokenStream, item: TokenStream) -> TokenStream {
355 let input = syn::parse_macro_input!(item as syn::ItemFn);
356 let func_name = &input.sig.ident;
357 quote! {
358 #input
359 ::byondapi::inventory::submit!({::byondapi::InitFunc(#func_name)});
360 }
361 .into()
362}