1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use proc_macro2::{Ident, TokenStream as TokenStream2};
6use proc_macro_error::{abort, proc_macro_error};
7use quote::quote;
8use syn::spanned::Spanned;
9use syn::{FnArg, ItemFn, Signature, Type};
10
11#[cfg(feature = "ffi_v2")]
12mod ffi_v2;
13mod str_ffi;
14
15pub(crate) struct FFITokens {
16 fn_args: TokenStream2,
17 return_type: TokenStream2,
18 fn_body: TokenStream2,
19}
20
21fn is_option_type(arg: &FnArg) -> bool {
22 match arg {
23 FnArg::Receiver(_) => abort!(arg.span(), "byond_fn can't have self argument"),
24 FnArg::Typed(arg) => match *arg.ty {
25 Type::Path(ref path) => path.path.segments.last().unwrap().ident == "Option",
26 _ => false,
27 },
28 }
29}
30
31#[proc_macro_error]
32#[proc_macro_attribute]
33pub fn byond_fn(args: TokenStream, input: TokenStream) -> TokenStream {
34 byond_fn2(args.into(), input.into()).into()
35}
36
37const STR_FFI_DESC: &str = "\"str\" (default): FFI with C Strings as the interop type";
38const FFI_V2_DESC: &str =
39 "\"v2\": New FFI Format added with BYOND 515 that uses `ByondType` as the FFI medium";
40
41fn byond_fn2(proc_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
42 let original_fn: ItemFn = syn::parse2(input).unwrap();
43
44 let proc_args: Ident =
45 syn::parse2(proc_args.clone()).unwrap_or(Ident::new("default", proc_args.span()));
46
47 let sig = &original_fn.sig;
48
49 let Signature { ident, inputs, .. } = &sig;
50
51 let mut optional_encountered = false;
53 for arg in inputs.iter() {
54 if optional_encountered && !is_option_type(arg) {
55 abort!(
56 arg.span(),
57 "Optional arguments must be at the end of the function signature"
58 );
59 } else {
60 optional_encountered = is_option_type(arg);
61 }
62 }
63
64 let mangled_name = Ident::new(
65 format!("__byond_fn_{}", ident.to_string()).as_str(),
66 ident.span(),
67 );
68
69 let FFITokens {
70 fn_args,
71 return_type,
72 fn_body,
73 } = str_ffi::tokens(sig);
74
75 quote! {
76 #original_fn
77 mod #mangled_name {
78 #[no_mangle]
79 pub unsafe extern "C" fn #ident(#fn_args) -> #return_type {
80 #fn_body
81 }
82 }
83 }
84}
85
86#[cfg(test)]
87mod test {
88 use quote::quote;
89
90 use super::*;
91
92 #[test]
93 fn is_optional_valid() {
94 let arg: FnArg = syn::parse2(quote! { foo: i32 }).unwrap();
95 assert!(!is_option_type(&arg));
96
97 let arg: FnArg = syn::parse2(quote! { foo: Option<i32> }).unwrap();
98 assert!(is_option_type(&arg));
99 }
100}