byond_fn_impl 0.5.1

proc macro implementation for byond_fn. Do not use directly.
Documentation
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{FnArg, Signature};

use crate::{is_option_type, FFITokens};

fn return_type_token() -> TokenStream {
    quote! { *const ::std::os::raw::c_char }
}

fn args_tokens() -> TokenStream {
    quote! { argc: ::std::os::raw::c_int, argv: *const *const ::std::os::raw::c_char }
}

fn fn_body_tokens(sig: &Signature) -> TokenStream {
    let Signature { ident, inputs, .. } = sig;

    let min_args = inputs.iter().filter(|arg| !is_option_type(arg)).count();
    let max_args = inputs.len();
    let args_binding = inputs.iter().enumerate().map(|(num, arg)| {
        if let FnArg::Typed(arg) = arg {
            let arg = *arg.pat.clone();
            let arg_string = arg.to_token_stream().to_string();
            quote! {
                let #arg = match byond_fn::str_ffi::StrArg::map_arg(args.get(#num).map(|arg| *arg), #min_args, #max_args, #arg_string, #num) {
                    Ok(arg) => arg,
                    Err(err) => {
                        return byond_fn::str_ffi::byond_return(err);
                    },
                };
            }
        } else {
            panic!("Byond functions can't have self argument")
        }
    });

    let return_args = inputs.iter().map(|arg| {
        if let FnArg::Typed(arg) = arg {
            let pat = *arg.pat.clone();
            quote! { #pat }
        } else {
            panic!("Byond functions can't have self argument")
        }
    });

    let min_args_i32 = min_args as i32;
    let max_args_i32 = max_args as i32;

    let actual_check = if min_args == max_args {
        quote! { argc != #min_args_i32 }
    } else {
        quote! { argc < #min_args_i32 || argc > #max_args_i32 }
    };

    let range_check = quote! {
        if #actual_check {
            return byond_fn::str_ffi::byond_return(byond_fn::str_ffi::TransportError::WrongArgCount {
                expected_min: #min_args,
                expected_max: #max_args,
                got: argc as usize,
            });
        }
    };

    let arg_stuff = if !inputs.is_empty() {
        quote! {
            #range_check
            let args = match byond_fn::str_ffi::parse_str_args(argc, argv) {
                Ok(args) => args,
                Err(err) => {
                    return byond_fn::str_ffi::byond_return(err);
                },
            };
            #(#args_binding)*
        }
    } else {
        quote! {}
    };

    quote! {
        #arg_stuff
        byond_fn::str_ffi::byond_return(super::#ident(#(#return_args),*))
    }
}

pub(crate) fn tokens(sig: &Signature) -> FFITokens {
    FFITokens {
        fn_args: args_tokens(),
        return_type: return_type_token(),
        fn_body: fn_body_tokens(sig),
    }
}