twors_derive 0.4.0

The macros crate for twors
Documentation
mod child_component;
mod field_validator;

use child_component::Attr;
use field_validator::{renderable, transform};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use std::result;
use syn::{
    Data, DataStruct, DeriveInput, Fields, FieldsNamed, parse_macro_input, spanned::Spanned,
};

#[proc_macro_derive(Component, attributes(child, children))]
pub fn component(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    match gen_component_getter_impl(&input) {
        Ok(getter_impl) => getter_impl,
        Err(compile_error) => compile_error,
    }
    .into()
}

fn gen_component_getter_impl(input: &DeriveInput) -> Result<TokenStream, TokenStream> {
    let data_struct = extract_data_struct(input)?;
    let fields = extract_named_fields(data_struct)?;

    transform::validate(fields)?;
    renderable::validate(fields)?;

    let child_cmp_fields = child_component::parse_fields(fields)?;
    let children_impl = gen_children_impl(&child_cmp_fields);
    let children_mut_impl = gen_children_mut_impl(&child_cmp_fields);

    let ident = &input.ident;

    Ok(quote! {
        impl twors::ComponentGetter for #ident {
            fn transform(&self) -> &twors::Transform {
                &self.transform
            }

            fn transform_mut(&mut self) -> &mut twors::Transform {
                &mut self.transform
            }

            fn renderables(&self) -> &[twors::Renderable] {
                &self.renderables
            }

            fn children(&self) -> Vec<&dyn twors::Component> {
                #children_impl
            }

            fn children_mut(&mut self) -> Vec<&mut dyn twors::Component> {
                #children_mut_impl
            }
        }

    })
}

fn extract_data_struct(input: &DeriveInput) -> result::Result<&DataStruct, TokenStream> {
    match &input.data {
        Data::Struct(data_struct) => Ok(data_struct),
        _ => Err(build_error(
            &input,
            "the macro should only be used on structs",
        )),
    }
}

fn extract_named_fields(data_struct: &DataStruct) -> result::Result<&FieldsNamed, TokenStream> {
    match &data_struct.fields {
        Fields::Named(fields) => Ok(fields),
        _ => Err(build_error(
            &data_struct.fields,
            "the struct should contain only named fields",
        )),
    }
}

enum Mutability {
    Immutable,
    Mutable,
}

fn gen_children_impl(fields: &[child_component::Field]) -> TokenStream {
    gen_children_parameterized(fields, Mutability::Immutable)
}

fn gen_children_mut_impl(fields: &[child_component::Field]) -> TokenStream {
    gen_children_parameterized(fields, Mutability::Mutable)
}

fn gen_children_parameterized(
    fields: &[child_component::Field],
    mutability: Mutability,
) -> TokenStream {
    if fields.is_empty() {
        return quote!(Vec::default());
    }

    let ref_type = match mutability {
        Mutability::Immutable => quote!(&),
        Mutability::Mutable => quote!(&mut),
    };

    let iter_type = match mutability {
        Mutability::Immutable => quote!(.iter()),
        Mutability::Mutable => quote!(.iter_mut()),
    };

    let mut extensions = Vec::default();
    for field in fields {
        match field.attr {
            Attr::Child => {
                let ident = &field.field.ident;
                extensions.push(quote! {
                    children.push(
                        #ref_type self.#ident as #ref_type dyn twors::Component
                    );
                });
            }
            Attr::Children => {
                let ident = &field.field.ident;
                extensions.push(quote! {
                    children.extend(
                        self.#ident
                            #iter_type
                            .map(|cmp| cmp as #ref_type dyn twors::Component),
                    );
                })
            }
        }
    }

    quote! {
        let mut children = Vec::default();
        #(#extensions)*
        children
    }
}

fn build_error(item: &dyn Spanned, error: &str) -> TokenStream {
    let error = format!("\"component\" macro error: {}.", error);
    quote_spanned! {
        item.span() => compile_error!(#error);
    }
}