foreignc_derive 0.1.1

Creates methods for deriving ffi abis. Made as a part of the foreignc crate
Documentation
/// # foreignc_derive
/// Provide the following macros
///  - #[derive(Box)] - mark a struct as using a box accross the ffi barrier
///  - #[derive(Json)] - mark a struct as using a json accross the ffi barrier
///  - #[with_abi] - auto generate the abi of all methods in a impl block or a single function
///  - generate_free_methods - create functions free_string, free_coption, free_cresult

extern crate proc_macro;

#[macro_use]
mod error;
mod generate;

use generate::*;
use foreignc_util::{throw_err, to_snake_case};

use proc_macro::TokenStream as TokenStream1;
use proc_macro2::TokenStream as TokenStream2;
#[allow(unused_imports)]
use proc_macro2::Span;
use syn::parse::{Parse, ParseStream};
use quote::*;
use syn::*;

use core::convert::From;
use syn::spanned::Spanned;

#[cfg(feature = "template")]
mod template;
#[cfg(feature = "template")]
mod _template {
    pub use crate::template::*;
    pub use foreignc_template::derived_input::*;
    pub use foreignc_template::*;
}
#[cfg(feature = "template")]
use _template::*;

struct Items {
    pub impls: Option<ItemImpl>,
    pub items: Option<ItemFn>,
}

impl Parse for Items {
    fn parse(input: ParseStream) -> Result<Self> {
        if input.peek(Token![pub]) | input.peek(Token![crate]) | input.peek(Token![fn]) {
            Ok(Items {
                items: Some(input.parse()?),
                impls: None,
            })
        } else if input.peek(Token![impl]) {
            Ok(Items {
                items: None,
                impls: Some(input.parse()?),
            })
        } else {
            Ok(Items {
                impls: None,
                items: None,
            })
        }
    }
}

#[proc_macro_attribute]
pub fn with_abi(_attr: TokenStream1, input: TokenStream1) -> TokenStream1 {
    let items: Items = throw_err!(parse(input));

    let ffi_impl = if let Some(item) = items.items {
        convert_item(item)
    } else if let Some(impls) = items.impls {
        let mut cimpls: TokenStream1 = impls.clone().into_token_stream().into();
        cimpls.extend::<TokenStream1>(convert_impls(impls));
        cimpls
    } else {
        throw_err!(msg: "Can only implement extern on impl of fn block")
    };

    ffi_impl
}

fn convert_impls(impls: ItemImpl) -> TokenStream1 {
    let span = impls.span().clone();
    let mut implementation = TokenStream1::new();

    #[cfg(feature = "template")]
    let mut implement = {
        let mut implement = RustStructure::default();
        if let Type::Path(ref p) = &*impls.self_ty {
            implement.self_ty = p.path.segments[0].ident.to_string();
        }
        implement
    };

    for item in impls.items {
        if let ImplItem::Method(item_fn) = item {
            let method_name = item_fn.sig.ident.clone();
            let item = throw_err!(convert_item_fn(&impls.self_ty, item_fn));

            #[cfg(feature = "template")]
            {
                let f = to_rust_func(&item, Some((&*impls.self_ty, &method_name)));
                implement.methods.push(throw_err!(f));
            }
            
            let extern_item_fn = throw_err!(to_extern_item_fn(item, Some((&*impls.self_ty, method_name))));
            implementation.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
            implementation.extend::<TokenStream1>(extern_item_fn.into_token_stream().into());
        }
    }

    #[cfg(feature = "template")]
    {
        throw_err!(
            add_to_path(implement)
                .map_err(|e| syn::Error::new(span, &e))
        );
    }

    implementation.into()
}

fn convert_item(item: ItemFn) -> TokenStream1 {
    let _span = item.span().clone();
    let mut implementation = TokenStream1::new();

    #[cfg(feature = "template")]
    {
        let func = throw_err!(to_rust_func(&item, None));
        throw_err!(
                add_to_path(func)
                .map_err(|e| syn::Error::new(_span, &e))
        );
    }

    implementation.extend::<TokenStream1>(item.to_token_stream().into());
    let mut extern_item_fn = throw_err!(to_extern_item_fn(item, None));
    extern_item_fn.sig.ident = Ident::new(&format!("{}_ffi", extern_item_fn.sig.ident), extern_item_fn.sig.ident.span().clone());
    let extern_stream: TokenStream2 = extern_item_fn.into_token_stream();
    implementation.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
    implementation.extend::<TokenStream1>(extern_stream.into());

    implementation
}

#[proc_macro]
pub fn generate_free_methods(_item: TokenStream1) -> TokenStream1 {
    let mut output = TokenStream1::new();

    output.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
    output.extend::<TokenStream1>(
        quote!(
            pub extern "C" fn free_coption(ptr: *mut foreignc::c_void) {
                unsafe {
                    foreignc::free_libc(ptr);
                }
            }
        )
        .into(),
    );

    output.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
    output.extend::<TokenStream1>(
        quote!(
            pub extern "C" fn free_cresult(ptr: *mut foreignc::CResult<foreignc::c_void, foreignc::c_void>) {
                unsafe {
                    let res = &*ptr;
                    foreignc::free_libc(res.value);
                    foreignc::free_libc(ptr as *mut foreignc::c_void);
                };
            }
        )
        .into(),
    );

    output.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
    output.extend::<TokenStream1>(
        quote!(
            pub extern "C" fn free_string(ptr: *mut std::os::raw::c_char) {
                let _s = unsafe { foreignc::CString::from_raw(ptr) };
            }
        )
        .into(),
    );

    #[cfg(feature = "template")]
    {
        let free_string = RustFreeFunction {
            ty: RustTypes::String,
            func: RustFunction {
                name: "free_string".to_owned(),
                extern_name: "free_string".to_owned(),
                inputs: vec![RustArgument {
                    name: "ptr".to_owned(),
                    ty: RustTypes::String,
                }],
                output: None,
            },
        };

        throw_err!(
            add_to_path(free_string)
                .map_err(|e| syn::Error::new(Span::call_site(), &e))
        );
    }

    output
}

#[proc_macro_derive(Boxed)]
pub fn derive_boxed(input: TokenStream1) -> TokenStream1 {
    let item: Item = throw_err!(parse(input));
    let _span = item.span().clone();
    let name = match &item {
        Item::Enum(ref e) => &e.ident,
        Item::Struct(ref s) => &s.ident,
        _ => throw_err!(msg: "Cannot derive for that"),
    };

    let mut t: TokenStream1 = quote!(
        unsafe impl foreignc::IntoFFi<*mut #name> for #name {
            fn into_ffi(v: Self) ->  FFiResult<*mut #name> {
                Ok(unsafe { Box::into_raw(Box::new(v)) })
            }
        }
        unsafe impl foreignc::FromFFi<*mut #name> for &mut #name {
            fn from_ffi(ptr: *mut #name) -> foreignc::FFiResult<Self> {
                Ok(unsafe {
                    ptr
                        .as_mut()
                        .ok_or_else(|| FFiError::from("Recieved null pointer to #name"))?
                })
            }
        }
        unsafe impl foreignc::FromFFi<*mut #name> for &#name {
            fn from_ffi(ptr: *mut #name) -> foreignc::FFiResult<Self> {
                Ok(unsafe {
                    ptr
                        .as_ref()
                        .ok_or_else(|| FFiError::from("Recieved null pointer to #name"))?
                })
            }
        }
    )
    .into();

    let tt_name = Ident::new(
        &to_snake_case("free_".to_owned() + &name.to_string()),
        item.span(),
    );
    let tt: TokenStream1 = quote!(
        pub extern "C" fn #tt_name(ptr: *mut #name) {
            let _: Box<#name> = unsafe{ Box::from_raw(ptr) };
        }
    )
    .into();

    #[cfg(feature = "template")]
    {
        let free = RustFreeFunction {
            ty: RustTypes::Ptr(name.to_string()),
            func: RustFunction {
                name: tt_name.to_string(),
                extern_name: tt_name.to_string(),
                inputs: vec![RustArgument {
                    name: "ptr".to_owned(),
                    ty: RustTypes::Ptr(tt_name.to_string()),
                }],
                output: None,
            },
        };

        throw_err!(
            add_to_path(free)
                .map_err(|e| syn::Error::new(_span, &e))
        );

        let s = RustStructure{
            self_ty: name.to_string(),
            methods: Vec::new(),
            destructor: Some(tt_name.to_string()),
            ty: StructTypes::Boxed
        };
        throw_err!(
            add_to_path(s)
                .map_err(|e| syn::Error::new(_span, &e))
        );
    }

    t.extend::<TokenStream1>("#[no_mangle]".parse::<TokenStream1>().expect("Failed to parse no_mangle"));
    t.extend(tt);

    t
}


#[proc_macro_derive(Json)]
pub fn derive_json(input: TokenStream1) -> TokenStream1 {
    let item: Item = throw_err!(parse(input));
    let _span = item.span().clone();
    let name = match &item {
        Item::Enum(ref e) => &e.ident,
        Item::Struct(ref s) => &s.ident,
        _ => throw_err!(msg: "Can only implement extern on impl of fn block"),
    };

    #[cfg(feature = "template")]
    {
        let s = RustStructure{
            self_ty: name.to_string(),
            methods: Vec::new(),
            destructor: Some("free_string".to_owned()),
            ty: StructTypes::Json
        };
        throw_err!(
            add_to_path(s)
                .map_err(|e| syn::Error::new(_span, &e))
        );
    }
    
    quote!(
        unsafe impl foreignc::FromFFi<*mut #name> for #name{
            fn from_ffi(p: *mut #name) -> foreignc::FFiResult<Self> {
                let s = foreignc::FromFFi::from_ffi(p as *mut std::os::raw::c_char);
                let json = serde_json::from_str(s?);
                Ok(json?)
            }
        }

        unsafe impl foreignc::IntoFFi<*mut #name> for &mut #name {
            fn into_ffi(v: Self) -> FFiResult<*mut #name> {
                let s = serde_json::to_string(v)?;
                Ok(foreignc::IntoFFi::into_ffi(s)? as *mut #name)
            }
        }

        unsafe impl foreignc::IntoFFi<*mut #name> for &#name {
            fn into_ffi(v: Self) -> FFiResult<*mut #name> {
                let s = serde_json::to_string(v)?;
                Ok(foreignc::IntoFFi::into_ffi(s)? as *mut #name)
            }
        }

        unsafe impl foreignc::IntoFFi<*mut #name> for #name {
            fn into_ffi(v: Self) -> FFiResult<*mut #name> {
                let s = serde_json::to_string(&v)?;
                Ok(foreignc::IntoFFi::into_ffi(s)? as *mut #name)
            }
        }
    )
    .into()
}