Skip to main content

meowtonin_impl/
lib.rs

1// SPDX-License-Identifier: 0BSD
2use darling::FromMeta;
3use proc_macro::TokenStream;
4use proc_macro2::TokenStream as TokenStream2;
5use quote::{ToTokens, quote};
6use syn::{FnArg, ItemFn, PatType, ReturnType, parse_macro_input, spanned::Spanned};
7
8#[derive(Debug, FromMeta, Copy, Clone)]
9#[darling(derive_syn_parse)]
10struct ByondFnArgs {
11	#[darling(default)]
12	variadic: bool,
13	#[darling(default)]
14	debug_log: bool,
15}
16
17/// Generates argument parsing code for a function parameter
18fn generate_arg_parser(input: &FnArg, idx: usize) -> TokenStream2 {
19	if let FnArg::Typed(PatType { attrs, pat, ty, .. }) = input {
20		let mutability = attrs.iter().find(|attr| attr.path().is_ident("mut"));
21		let arg_name = syn::Ident::new(&format!("__arg_{idx}"), pat.span());
22		let error_message = format!(
23			"failed to parse argument {idx} ({pat}: {ty})",
24			idx = idx + 1,
25			pat = pat.to_token_stream(),
26			ty = ty.to_token_stream(),
27		);
28		quote! {
29			let #mutability #pat: #ty = ::meowtonin::FromByond::from_byond(#arg_name)
30				.expect(#error_message);
31		}
32	} else {
33		quote!()
34	}
35}
36
37/// Generates the return type conversion code based on the function's return
38/// type
39fn generate_return_conversion(ret_type: &ReturnType) -> (TokenStream2, TokenStream2) {
40	match ret_type {
41		ReturnType::Default => (quote!(()), quote! {
42			Ok(::meowtonin::ByondValue::NULL)
43		}),
44		ReturnType::Type(_, ty) => {
45			let ty_name = quote!(#ty).to_string();
46			let conversion = if ty_name.contains("Result") {
47				quote! {
48					ret
49						.map_err(::std::boxed::Box::from)
50						.and_then(|inner_ret| ::meowtonin::ByondValue::new_value(inner_ret)
51							.map_err(::std::boxed::Box::from))
52				}
53			} else {
54				quote! {
55					::meowtonin::ByondValue::new_value(ret).map_err(::std::boxed::Box::from)
56				}
57			};
58			(quote!(#ty), conversion)
59		}
60	}
61}
62
63/// Generates the wrapper function that handles argument parsing and return
64/// conversion
65fn generate_wrapper_fn(
66	wrapper_ident: &syn::Ident,
67	parse_args: &[TokenStream2],
68	return_type: &TokenStream2,
69	return_conversion: &TokenStream2,
70	body: &syn::Block,
71	arg_count: usize,
72	variadic: bool,
73) -> TokenStream2 {
74	let args_ident = if variadic {
75		quote! { __args: ::std::vec::Vec<::meowtonin::ByondValue> }
76	} else if arg_count > 0 {
77		let arg_params: Vec<_> = (0..arg_count)
78			.map(|i| {
79				let arg_name =
80					syn::Ident::new(&format!("__arg_{i}"), proc_macro2::Span::call_site());
81				quote! { #arg_name: ::meowtonin::ByondValue }
82			})
83			.collect();
84		quote! { #(#arg_params),* }
85	} else {
86		quote! {}
87	};
88
89	let parse_block = if !variadic {
90		quote! {
91			#(#parse_args)*
92		}
93	} else {
94		quote! {}
95	};
96
97	let call_block = if variadic {
98		quote! {
99			let mut __func = move || -> #return_type {
100				let args = __args;
101				#body
102			};
103			let ret = __func();
104		}
105	} else {
106		quote! {
107			let mut __func = move || -> #return_type {
108				#body
109			};
110			let ret = __func();
111		}
112	};
113
114	quote! {
115		fn #wrapper_ident(#args_ident)
116			-> ::std::result::Result<::meowtonin::ByondValue, ::std::boxed::Box<dyn ::std::error::Error>>
117		{
118			#parse_block
119
120			#call_block
121
122			#return_conversion
123		}
124	}
125}
126
127fn generate_debug_msg(func_name: &str, msg_type: &str, args: &ByondFnArgs) -> TokenStream2 {
128	if !args.debug_log {
129		return quote! {};
130	}
131	let msg = format!("debug: {func_name} {msg_type}");
132	quote! {
133		{ println!(#msg) };
134	}
135}
136
137/// Generates the FFI export function that handles panic catching and error
138/// conversion
139fn generate_export_fn(
140	func_name: &syn::Ident,
141	wrapper_ident: &syn::Ident,
142	length: usize,
143	args: &ByondFnArgs,
144) -> TokenStream2 {
145	let func_name_str = func_name.to_string();
146
147	let debug_start = generate_debug_msg(&func_name_str, "start", args);
148	let debug_end = generate_debug_msg(&func_name_str, "end", args);
149	let debug_crash = generate_debug_msg(&func_name_str, "CRASH!!!", args);
150
151	let let_args = if args.variadic || length > 0 {
152		quote! {
153			let mut __args = unsafe { ::meowtonin::parse_args(__argc, __argv) };
154		}
155	} else {
156		quote! {}
157	};
158
159	let do_call = if args.variadic {
160		quote! {
161			#wrapper_ident(__args)
162		}
163	} else if length > 0 {
164		let args: Vec<_> = (0..length)
165			.map(|_| {
166				quote! {
167					__args_iter
168						.next()
169						.unwrap_or(::meowtonin::ByondValue::NULL)
170				}
171			})
172			.collect();
173		quote! {
174			let mut __args_iter = __args.into_iter();
175			#wrapper_ident(#(#args),*)
176		}
177	} else {
178		quote! {
179			#wrapper_ident()
180		}
181	};
182
183	quote! {
184		#[unsafe(no_mangle)]
185		#[inline(never)]
186		pub unsafe extern "C-unwind" fn #func_name(
187			__argc: ::meowtonin::sys::u4c,
188			__argv: *mut ::meowtonin::sys::CByondValue
189		) -> ::meowtonin::sys::CByondValue {
190			::meowtonin::setup_once();
191			let __retval: std::result::Result<::meowtonin::ByondValue, std::string::String>;
192			{
193				#debug_start
194				#let_args
195
196				match ::std::panic::catch_unwind(move || {
197					#do_call
198				}) {
199					Ok(Ok(value)) => {
200						__retval = Ok(value);
201					},
202					Ok(Err(err)) => {
203						__retval = Err(format!(
204							"panic at {source}: {error}",
205							error = err.to_string(),
206							source = #func_name_str.to_string()
207						));
208					},
209					Err(_err) => match ::meowtonin::panic::get_stack_trace() {
210						Some(message) => {
211							__retval = Err(message);
212						}
213						None => {
214							__retval = Err("unknown error".to_owned());
215						}
216					}
217				}
218			}
219			match __retval {
220				Ok(value) => {
221					#debug_end
222					value.detach()
223				},
224				Err(error) => {
225					#debug_crash
226					::meowtonin::panic::byond_crash(error)
227				}
228			}
229		}
230	}
231}
232
233/// Main proc macro attribute that generates BYOND FFI bindings
234#[proc_macro_attribute]
235pub fn byond_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
236	let args: ByondFnArgs = match syn::parse(attr) {
237		Ok(v) => v,
238		Err(e) => {
239			return e.to_compile_error().into();
240		}
241	};
242
243	let func = parse_macro_input!(item as ItemFn);
244
245	let func_name = &func.sig.ident;
246	let wrapper_name = format!("__byond_{func_name}_inner");
247	let wrapper_ident = syn::Ident::new(&wrapper_name, func_name.span());
248
249	let mod_name = format!("__byond_export_{func_name}");
250	let mod_ident = syn::Ident::new(&mod_name, func_name.span());
251
252	// Generate argument parsing code for each parameter (only for non-variadic)
253	let parse_args: Vec<_> = if !args.variadic {
254		func.sig
255			.inputs
256			.iter()
257			.enumerate()
258			.map(|(idx, input)| generate_arg_parser(input, idx))
259			.collect()
260	} else {
261		vec![]
262	};
263
264	// Generate return type handling code
265	let (return_type, return_conversion) = generate_return_conversion(&func.sig.output);
266
267	// Generate the wrapper function
268	let wrapper_fn = generate_wrapper_fn(
269		&wrapper_ident,
270		&parse_args,
271		&return_type,
272		&return_conversion,
273		&func.block,
274		func.sig.inputs.len(),
275		args.variadic,
276	);
277
278	// Generate the exported FFI function
279	let export_fn = generate_export_fn(func_name, &wrapper_ident, func.sig.inputs.len(), &args);
280
281	// Combine everything into the final output
282	quote! {
283		#func
284
285		#[doc(hidden)]
286		mod #mod_ident {
287			use super::*;
288
289			#wrapper_fn
290			#export_fn
291		}
292	}
293	.into()
294}