ferment-sys 0.2.14

Syntax tree morphing of FFI-compatible stuff
Documentation
use quote::{quote, ToTokens};
use proc_macro2::TokenStream as TokenStream2;
use syn::{Attribute, GenericParam, Generics, Lifetime, LifetimeParam, ReturnType, Type};
use crate::ast::{CommaPunctuated, CommaPunctuatedTokens};
use crate::composer::{CommaPunctuatedArgs, TypePair};
use crate::presentation::{DictionaryExpr, DictionaryName, InterfacesMethodExpr};

#[allow(unused)]
#[derive(Clone, Debug)]
pub enum InterfacePresentation {
    Empty,
    Ctor {
        attrs: Vec<Attribute>,
        generics: Option<Generics>,
        lifetimes: Vec<Lifetime>,
        ffi_type: Type,
        args: CommaPunctuatedTokens,
        presentation: TokenStream2,
    },
    ConversionFrom {
        attrs: Vec<Attribute>,
        types: (
            Type, // FFI
            Type // Original
        ),
        conversions: (
            TokenStream2,
            Option<Generics>,
            Vec<Lifetime>
        ),
    },
    ConversionTo {
        attrs: Vec<Attribute>,
        types: (
            Type, // FFI
            Type // Original
        ),
        conversions: (
            TokenStream2,
            Option<Generics>,
            Vec<Lifetime>
        ),
    },
    Callback {
        attrs: Vec<Attribute>,
        lifetimes: Vec<Lifetime>,
        ffi_type: Type,
        inputs: CommaPunctuatedArgs,
        output: ReturnType,
        body: TokenStream2,
    },
    SendAndSync {
        attrs: Vec<Attribute>,
        ffi_type: Type,
    },
    Drop {
        attrs: Vec<Attribute>,
        ty: Type,
        body: TokenStream2
    }
}

impl InterfacePresentation {
    pub fn conversion_from_root<T: ToTokens>(attrs: &[Attribute], types: &TypePair, body: T, generics: &Option<Generics>, lifetimes: &[Lifetime]) -> Self {
        Self::conversion_from(attrs, types, DictionaryExpr::from_root(body), generics, lifetimes)
    }
    pub fn conversion_to_boxed<T: ToTokens + 'static>(attrs: &[Attribute], types: &TypePair, body: T, generics: &Option<Generics>, lifetimes: &[Lifetime]) -> Self {
        Self::conversion_to(attrs, types, InterfacesMethodExpr::Boxed(body), generics, lifetimes)
    }
    pub fn conversion_to_boxed_self_destructured<T: ToTokens>(attrs: &[Attribute], types: &TypePair, body: T, generics: &Option<Generics>, lifetimes: &[Lifetime]) -> Self {
        Self::conversion_to_boxed(attrs, types, DictionaryExpr::self_destruct(body), generics, lifetimes)
    }
    pub fn conversion_from<T: ToTokens>(attrs: &[Attribute], types: &TypePair, method_body: T, generics: &Option<Generics>, lifetimes: &[Lifetime]) -> Self {
        InterfacePresentation::ConversionFrom {
            attrs: attrs.to_owned(),
            types: types.clone(),
            conversions: (method_body.to_token_stream(), generics.clone(), lifetimes.to_owned())
        }
    }
    pub fn non_generic_conversion_from<T: ToTokens>(attrs: &[Attribute], types: &TypePair, method_body: T, lifetimes: &[Lifetime]) -> Self {
        InterfacePresentation::ConversionFrom {
            attrs: attrs.to_owned(),
            types: types.clone(),
            conversions: (method_body.to_token_stream(), None, lifetimes.to_owned())
        }
    }
    pub fn conversion_to<T: ToTokens>(attrs: &[Attribute], types: &TypePair, method_body: T, generics: &Option<Generics>, lifetimes: &[Lifetime]) -> Self {
        InterfacePresentation::ConversionTo {
            attrs: attrs.to_owned(),
            types: types.clone(),
            conversions: (method_body.to_token_stream(), generics.clone(), lifetimes.to_owned())
        }
    }
    pub fn non_generic_conversion_to<T: ToTokens>(attrs: &[Attribute], types: &TypePair, method_body: T, lifetimes: &[Lifetime]) -> Self {
        InterfacePresentation::ConversionTo {
            attrs: attrs.to_owned(),
            types: types.clone(),
            conversions: (method_body.to_token_stream(), None, lifetimes.to_owned())
        }
    }
    pub fn drop<T: ToTokens>(attrs: &[Attribute], ty: Type, body: T) -> Self {
        InterfacePresentation::Drop { attrs: attrs.to_owned(), ty, body: body.to_token_stream() }
    }

    pub fn callback<T: ToTokens, U: ToTokens>(attrs: &[Attribute], lifetimes: &[Lifetime], ffi_type: Type, inputs: CommaPunctuatedArgs, output: ReturnType, args_conversions: T, result_conversion: U) -> Self {
        InterfacePresentation::Callback {
            attrs: attrs.to_owned(),
            ffi_type,
            inputs,
            output,
            lifetimes: lifetimes.to_owned(),
            body: DictionaryExpr::callback_caller(args_conversions, result_conversion).to_token_stream(),
        }
    }
    pub fn send_sync(attrs: &[Attribute], ffi_type: &Type) -> Self {
        InterfacePresentation::SendAndSync { attrs: attrs.to_owned(), ffi_type: ffi_type.clone() }
    }
}

fn generics_presentation(generics: &Option<Generics>, lifetimes: &[Lifetime]) -> (TokenStream2, TokenStream2) {
    let result = match generics {
        Some(generics) => {
            let mut params = CommaPunctuated::from_iter(lifetimes.iter().map(|lt| GenericParam::Lifetime(LifetimeParam {
                attrs: vec![],
                lifetime: lt.clone(),
                colon_token: None,
                bounds: Default::default(),
            })));
            params.extend(generics.params.clone());

            let gens = params.iter().map(ToTokens::to_token_stream);
            let where_clause = match &generics.where_clause {
                Some(where_clause) => {
                    let where_predicates = where_clause.predicates.iter().map(ToTokens::to_token_stream);
                    quote!(where <#(#where_predicates)*,>)
                }
                None => Default::default()
            };
            let generic_bounds = if gens.len() > 0 {
                quote!(<#(#gens)*,>)
            } else {
                Default::default()
            };
            (generic_bounds, where_clause)
        },
        None => {
            let lifetimes = CommaPunctuated::from_iter(lifetimes.iter().map(|lt| GenericParam::Lifetime(LifetimeParam {
                attrs: vec![],
                lifetime: lt.clone(),
                colon_token: None,
                bounds: Default::default(),
            })));
            let bounds = if lifetimes.is_empty() { Default::default() } else { quote!(<#lifetimes>) };
            (bounds, Default::default())
        }
    };
    result
}

impl ToTokens for InterfacePresentation {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        match self {
            Self::Empty =>
                quote!(),
            Self::Ctor { attrs, ffi_type, generics, lifetimes, args, presentation } => {
                let (generic_bounds, where_clause) = generics_presentation(generics, lifetimes);
                quote! {
                    #(#attrs)*
                    impl #generic_bounds #ffi_type #where_clause {
                        pub fn new(#args) -> Self {
                            #presentation
                        }
                    }
                }
            },
            Self::ConversionFrom {
                attrs,
                types: (ffi_type, target_type),
                conversions: (presentation, generics, lifetimes),
            } => {
                let (generic_bounds, where_clause) = generics_presentation(generics, lifetimes);
                let package = DictionaryName::Package;
                let interface_from = DictionaryName::InterfaceFrom;
                quote! {
                    #(#attrs)*
                    impl #generic_bounds #package::#interface_from<#target_type> for #ffi_type #where_clause {
                        unsafe fn ffi_from_const(ffi: *const #ffi_type) -> #target_type {
                            #presentation
                        }
                    }
                }
            },
            Self::ConversionTo {
                attrs,
                types: (ffi_type, target_type),
                conversions: (presentation, generics, lifetimes),
            } => {
                let (generic_bounds, where_clause) = generics_presentation(generics, lifetimes);
                let package = DictionaryName::Package;
                let interface_to = DictionaryName::InterfaceTo;
                let obj = DictionaryName::Obj;
                quote! {
                    #(#attrs)*
                    impl #generic_bounds #package::#interface_to<#target_type> for #ffi_type #where_clause {
                        unsafe fn ffi_to_const(#obj: #target_type) -> *const #ffi_type {
                            #presentation
                        }
                    }
                }
            },
            Self::Callback { attrs, ffi_type, inputs, output, lifetimes, body } => {
                let lifetimes = CommaPunctuated::from_iter(lifetimes.iter().map(|lt| GenericParam::Lifetime(LifetimeParam {
                    attrs: vec![],
                    lifetime: lt.clone(),
                    colon_token: None,
                    bounds: Default::default(),
                })));
                let bounds = if lifetimes.is_empty() {
                    Default::default()
                } else {
                    quote!(<#lifetimes>)
                };
                quote! {
                    #(#attrs)*
                    impl #ffi_type {
                        pub unsafe fn call #bounds(&self, #inputs) #output {
                            #body
                        }
                    }

                }
            }
            Self::SendAndSync { attrs, ffi_type } => {
                quote! {
                    #(#attrs)*
                    unsafe impl Send for #ffi_type {}
                    #(#attrs)*
                    unsafe impl Sync for #ffi_type {}
                }
            },
            Self::Drop { attrs, ty, body } => quote! {
                #(#attrs)*
                impl Drop for #ty { fn drop(&mut self) { unsafe { #body; } } }
            }
        }.to_tokens(tokens)
    }
}