pelican_ui_proc 0.3.0

Procedural macros for the Pelican Engine..
Documentation
use syn::{parse_macro_input,  Data, DeriveInput, Fields, Field, Path, Type, TypePath};
use proc_macro2::{TokenStream, TokenTree, Literal};
use quote::quote;

enum ChildType {
    Vector(TokenTree),
    Child(TokenTree),
    HashMap(TokenTree),
    Opt(TokenTree),
    OptBox(TokenTree),
    Boxed(TokenTree),
    VecBox(TokenTree),
}

impl ChildType {
    fn from_field(ty: &Type, ident: TokenTree) -> Self {
        match ty {
            Type::Path(TypePath{path: Path{segments, ..}, ..}) if segments.first().filter(|s| s.ident.to_string() == "Option".to_string()).is_some() => {
                let optbox = if let syn::PathArguments::AngleBracketed(args) = &segments.first().unwrap().arguments {
                    if let syn::GenericArgument::Type(ty) = args.args.first().unwrap() {
                        if let Type::Path(TypePath{path: Path{segments, ..}, ..}) = ty {
                            segments.first().filter(|s| s.ident.to_string() == "Box".to_string()).is_some()
                        } else {false}
                    } else {false}
                } else {false};
                if optbox {ChildType::OptBox(ident)} else {ChildType::Opt(ident)}
            },
            Type::Path(TypePath{path: Path{segments, ..}, ..}) if segments.first().filter(|s| s.ident.to_string() == "Vec".to_string()).is_some() => {
                let vecbox = if let syn::PathArguments::AngleBracketed(args) = &segments.first().unwrap().arguments {
                    if let syn::GenericArgument::Type(ty) = args.args.first().unwrap() {
                        if let Type::Path(TypePath{path: Path{segments, ..}, ..}) = ty {
                            segments.first().filter(|s| s.ident.to_string() == "Box".to_string()).is_some()
                        } else {false}
                    } else {false}
                } else {false};
                if vecbox {ChildType::VecBox(ident)} else {ChildType::Vector(ident)}
            },
            Type::Path(TypePath{path: Path{segments, ..}, ..}) if segments.first().filter(|s| s.ident.to_string() == "HashMap" || s.ident.to_string() == "IndexMap").is_some() => {
                ChildType::HashMap(ident)
            },
            Type::Path(TypePath{path: Path{segments, ..}, ..}) if segments.first().filter(|s| s.ident.to_string() == "Box".to_string()).is_some() => {
                ChildType::Boxed(ident)
            },
            _ => ChildType::Child(ident)
        }
    }
}

#[proc_macro_derive(Component, attributes(skip))]
pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    let has_tag = |field: &Field, tag: &str| field.attrs.iter().any(|attr| attr.meta.path().get_ident().map(|i| &i.to_string() == tag).unwrap_or_default());

    match input.data {
        Data::Struct(struc) => {
            let (layout, children): (TokenTree, Vec<ChildType>) = match struc.fields {
                Fields::Named(named) => {
                    let mut iterator = named.named.into_iter();
                    (
                        TokenTree::Ident(iterator.next().map(|f| f.ident).flatten().unwrap_or_else(|| {panic!("Component requires the first field of the structure to be the layout");})),
                        iterator.flat_map(|field|
                            (!has_tag(&field, "skip")).then(|| ChildType::from_field(&field.ty, TokenTree::Ident(field.ident.unwrap())))
                        ).collect()
                    )
                },
                Fields::Unnamed(unnamed) => {
                    let mut iterator = unnamed.unnamed.iter().enumerate();
                    iterator.next().unwrap_or_else(|| {panic!("Component requires the first field of the structure to be the layout");});
                    (
                        TokenTree::Literal(Literal::usize_unsuffixed(0)),
                        iterator.flat_map(|(index, field)|
                            (!has_tag(&field, "skip")).then(|| ChildType::from_field(&field.ty, TokenTree::Literal(Literal::usize_unsuffixed(index))))
                        ).collect()
                    )
                },
                Fields::Unit => {panic!("Component requires a Layout and at least one child")}
            };
            children.is_empty().then(|| {panic!("Component requires at least one child component in the structure");});

            let children_mut = TokenStream::from_iter(children.iter().map(|child| match child {
                ChildType::Vector(name) => quote!{children.extend(self.#name.iter_mut().map(|c| c as &mut dyn roost::drawable::Drawable));},
                ChildType::HashMap(name) => quote!{children.extend(self.#name.values_mut().map(|v| v as &mut dyn roost::drawable::Drawable));},
                ChildType::Child(name) => quote!{children.push(&mut self.#name as &mut dyn roost::drawable::Drawable);},
                ChildType::Opt(name) => quote!{if let Some(item) = self.#name.as_mut() {children.push(item as &mut dyn roost::drawable::Drawable);}},
                ChildType::OptBox(name) => quote!{if let Some(item) = self.#name.as_mut() {children.push(&mut **item as &mut dyn roost::drawable::Drawable);}},
                ChildType::Boxed(name) => quote!{children.push(&mut *self.#name as &mut dyn roost::drawable::Drawable);},
                ChildType::VecBox(name) => quote!{children.extend(self.#name.iter_mut().map(|c| &mut **c as &mut dyn roost::drawable::Drawable));}
            }));
            let children = TokenStream::from_iter(children.iter().map(|child| match child {
                ChildType::Vector(name) => quote!{children.extend(self.#name.iter().map(|c| c as &dyn roost::drawable::Drawable));},
                ChildType::HashMap(name) => quote!{children.extend(self.#name.values().map(|v| v as &dyn roost::drawable::Drawable));},
                ChildType::Child(name) => quote!{children.push(&self.#name as &dyn roost::drawable::Drawable);},
                ChildType::Opt(name) => quote!{if let Some(item) = self.#name.as_ref() {children.push(item as &dyn roost::drawable::Drawable);}},
                ChildType::OptBox(name) => quote!{if let Some(item) = self.#name.as_ref() {children.push(&**item as &dyn roost::drawable::Drawable);}},
                ChildType::Boxed(name) => quote!{children.push(&*self.#name as &dyn roost::drawable::Drawable);},
                ChildType::VecBox(name) => quote!{children.extend(self.#name.iter().map(|c| &**c as &dyn roost::drawable::Drawable));}
            }));

            proc_macro::TokenStream::from(quote!{
                impl #impl_generics Component for #name #ty_generics #where_clause {
                    fn children_mut(&mut self) -> Vec<&mut dyn roost::drawable::Drawable> {
                        let mut children = vec![];
                        #children_mut
                        children
                    }
                    fn children(&self) -> Vec<&dyn roost::drawable::Drawable> {
                        let mut children = vec![];
                        #children
                        children
                    }

                    fn request_size(&self, ctx: &mut Context, children: Vec<roost::layout::SizeRequest>) -> roost::layout::SizeRequest {
                        roost::layout::Layout::request_size(&self.#layout, ctx, children)
                    }
                    fn build(&mut self, ctx: &mut Context, size: (f32, f32), children: Vec<roost::layout::SizeRequest>) -> Vec<roost::layout::Area> {
                        roost::layout::Layout::build(&mut self.#layout, ctx, size, children)
                    }
                }
            })
        },
        Data::Enum(enu) => {
            let starts = enu.variants.into_iter().map(|v| {
                let name = v.ident;
                match v.fields {
                    Fields::Named(named) => {
                        let mut iterator = named.named.into_iter();
                        iterator.next().map(|f| (
                            if let Type::Path(TypePath{path: Path{segments, ..}, ..}) = f.ty {
                                segments.first().filter(|s| s.ident.to_string() == "Box".to_string()).is_some()
                            } else {false},
                            TokenStream::from(quote!{Self::#name{c, ..} => })
                        ))
                    },
                    Fields::Unnamed(unnamed) => {
                        let mut iterator = unnamed.unnamed.iter();
                        iterator.next().map(|f| (
                            if let Type::Path(TypePath{path: Path{segments, ..}, ..}) = &f.ty {
                                segments.first().filter(|s| s.ident.to_string() == "Box".to_string()).is_some()
                            } else {false},
                            TokenStream::from(quote!{Self::#name(c, ..) => })
                        ))
                    },
                    Fields::Unit => None,
                }.unwrap_or_else(|| {panic!("Enumerator Component requires the first field of each variant to be a Component");})
            }).collect::<Vec<_>>();
            let children_mut = TokenStream::from_iter(starts.iter().map(|(b, s)| if !b {quote!{#s vec![c as &mut dyn roost::drawable::Drawable],}} else {quote!{#s vec![&mut **c as &mut dyn roost::drawable::Drawable],}}));
            let children = TokenStream::from_iter(starts.iter().map(|(b, s)| if !b {quote!{#s vec![c as &dyn roost::drawable::Drawable],}} else {quote!{#s vec![&**c as &dyn roost::drawable::Drawable],}}));
            proc_macro::TokenStream::from(quote!{
                impl #impl_generics Component for #name #ty_generics #where_clause {
                    fn children_mut(&mut self) -> Vec<&mut dyn roost::drawable::Drawable> {
                        match self {
                            #children_mut
                        }
                    }
                    fn children(&self) -> Vec<&dyn roost::drawable::Drawable> {
                        match self {
                            #children
                        }
                    }

                    fn request_size(&self, ctx: &mut Context, mut children: Vec<roost::layout::SizeRequest>) -> roost::layout::SizeRequest {
                        children.remove(0)
                    }
                    fn build(&mut self, ctx: &mut Context, size: (f32, f32), children: Vec<roost::layout::SizeRequest>) -> Vec<roost::layout::Area> {
                        vec![roost::layout::Area{offset: (0, 0), size}]
                    }
                }
            })
        },
        Data::Union(_) => {panic!("Cannot implement Component for a Union")}
    }
}

#[proc_macro_derive(Plugin)]
pub fn derive_plugin(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    proc_macro::TokenStream::from(quote!{
        impl #impl_generics Plugin for #name #ty_generics #where_clause {}
    })
}