1use 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
17fn 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
37fn 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
63fn 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
137fn 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#[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 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 let (return_type, return_conversion) = generate_return_conversion(&func.sig.output);
266
267 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 let export_fn = generate_export_fn(func_name, &wrapper_ident, func.sig.inputs.len(), &args);
280
281 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}