dynec-codegen 0.2.1

dynec codegen library implementation
Documentation
use std::iter;

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;

use crate::util::Result;

pub(crate) fn imp(input: TokenStream) -> Result<TokenStream> {
    let Inputs(inputs) = syn::parse2(input)?;

    let mut output = TokenStream::new();

    for Input { debug_print, crate_name, meta, vis, ident, fields, .. } in inputs {
        let debug_print = debug_print.is_some();

        if fields.is_empty() {
            return Err(syn::Error::new(
                Span::call_site(),
                "zip structs must have at least one field",
            ));
        }

        let crate_name = match crate_name {
            Some((_, _, _, crate_name)) => crate_name,
            None => quote!(::dynec),
        };

        let field_tys: Vec<_> =
            (0..fields.len()).map(|i| quote::format_ident!("__T{}", i)).collect();
        let field_meta: Vec<_> = fields
            .iter()
            .map(|field| {
                let meta = &field.meta;
                quote!(#(#meta)*)
            })
            .collect();
        let field_vis: Vec<_> = fields.iter().map(|field| &field.vis).collect();
        let field_idents: Vec<_> = fields.iter().map(|field| &field.ident).collect();

        let first_field_ident = field_idents.first().expect("struct checked to be non empty");
        let first_field_ty = field_tys.first().expect("struct checked to be non empty");
        let mut stacked_zip = quote!(<#first_field_ty as #crate_name::system::ZipChunked::<__Arch>>::chunk_to_entities(#first_field_ident));
        let mut stacked_tuples = quote!(#first_field_ident);
        for (&field_ident, field_ty) in iter::zip(&field_idents, &field_tys).skip(1) {
            stacked_zip = quote!(::core::iter::zip(#stacked_zip, <#field_ty as #crate_name::system::ZipChunked::<__Arch>>::chunk_to_entities(#field_ident)));
            stacked_tuples = quote!((#stacked_tuples, #field_ident));
        }

        let item = quote! {
            #(#meta)*
            #vis struct #ident<#(#field_tys),*> {
                #(#field_meta #field_vis #field_idents: #field_tys,)*
            }

            impl<__Arch: #crate_name::Archetype, #(#field_tys),*>
                #crate_name::system::IntoZip<__Arch>
                for #ident<#(#field_tys,)*>
            where
                #(#field_tys: #crate_name::system::IntoZip<__Arch>,)*
            {
                type IntoZip = #ident<#(<#field_tys as #crate_name::system::IntoZip<__Arch>>::IntoZip,)*>;

                fn into_zip(self) -> Self::IntoZip {
                    let Self { #(#field_idents,)* } = self;
                    #ident { #(
                        #field_idents: <#field_tys as #crate_name::system::IntoZip<__Arch>>::into_zip(#field_idents),
                    )* }
                }
            }

            impl<__Arch: #crate_name::Archetype, #(#field_tys),*>
                #crate_name::system::Zip<__Arch>
                for #ident<#(#field_tys,)*>
            where
                #(#field_tys: #crate_name::system::Zip<__Arch>,)*
            {
                fn split(&mut self, offset: __Arch::RawEntity) -> Self {
                    let Self { #(#field_idents,)* } = self;
                    #ident { #(
                        #field_idents: <#field_tys as #crate_name::system::Zip<__Arch>>::split(#field_idents, offset),
                    )* }
                }

                type Item = #ident<
                    #(<#field_tys as #crate_name::system::Zip<__Arch>>::Item,)*
                >;
                fn get<E: #crate_name::entity::Ref<Archetype = __Arch>>(self, __dynec_entity: E) -> Self::Item {
                    let Self { #(#field_idents,)* } = self;
                    let __dynec_entity = #crate_name::entity::TempRef::<__Arch>::new(__dynec_entity.id());
                    #ident { #(
                        #field_idents: <#field_tys as #crate_name::system::Zip<__Arch>>::get(
                            #field_idents,
                            __dynec_entity,
                        ),
                    )* }
                }
            }

            impl<__Arch: #crate_name::Archetype, #(#field_tys),*>
                #crate_name::system::ZipChunked<__Arch>
                for #ident<#(#field_tys,)*>
            where
                #(#field_tys: #crate_name::system::ZipChunked<__Arch>,)*
            {
                type Chunk = #ident<
                    #(<#field_tys as #crate_name::system::ZipChunked<__Arch>>::Chunk,)*
                >;
                fn get_chunk(self, __dynec_chunk: #crate_name::entity::TempRefChunk<__Arch>) -> Self::Chunk {
                    let Self { #(#field_idents,)* } = self;
                    #ident { #(
                        #field_idents: <#field_tys as #crate_name::system::ZipChunked<__Arch>>::get_chunk(
                            #field_idents,
                            __dynec_chunk,
                        ),
                    )* }
                }

                fn chunk_to_entities(chunk: Self::Chunk) -> impl Iterator<Item = #ident<
                    #(<#field_tys as #crate_name::system::Zip<__Arch>>::Item,)*
                >> {
                    let #ident { #(#field_idents,)* } = chunk;
                    #stacked_zip.map(|#stacked_tuples| #ident { #(#field_idents,)* })
                }
            }
        };

        if debug_print {
            println!("{item}");
        }
        output.extend(item);
    }

    Ok(output)
}

struct Inputs(Vec<Input>);

impl Parse for Inputs {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut inputs = Vec::new();
        while !input.is_empty() {
            inputs.push(input.parse()?);
        }
        Ok(Self(inputs))
    }
}

struct Input {
    debug_print: Option<(syn::Token![@], kw::__debug_print)>,
    crate_name:  Option<(syn::Token![@], kw::dynec_as, syn::token::Paren, TokenStream)>,
    meta:        Vec<syn::Attribute>,
    vis:         syn::Visibility,
    ident:       syn::Ident,
    _braces:     syn::token::Brace,
    fields:      Punctuated<Field, syn::Token![,]>,
}

impl Parse for Input {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut debug_print = None;
        let mut crate_name = None;

        while input.peek(syn::Token![@]) {
            let at = input.parse()?;
            let lh = input.lookahead1();
            if lh.peek(kw::__debug_print) {
                debug_print = Some((at, input.parse()?));
            } else if lh.peek(kw::dynec_as) {
                let kw = input.parse()?;
                let inner;
                let paren = syn::parenthesized!(inner in input);
                let crate_name_ts = inner.parse()?;
                crate_name = Some((at, kw, paren, crate_name_ts));
            } else {
                return Err(lh.error());
            }
        }

        let meta = input.call(syn::Attribute::parse_outer)?;
        let vis = input.parse()?;
        let ident = input.parse()?;

        let inner;
        let braces = syn::braced!(inner in input);
        let fields = Punctuated::parse_terminated(&inner)?;

        Ok(Self { debug_print, crate_name, meta, vis, ident, _braces: braces, fields })
    }
}

struct Field {
    meta:  Vec<syn::Attribute>,
    vis:   syn::Visibility,
    ident: syn::Ident,
}

impl Parse for Field {
    fn parse(input: ParseStream) -> Result<Self> {
        let meta = input.call(syn::Attribute::parse_outer)?;
        let vis = input.parse()?;
        let ident = input.parse()?;

        Ok(Self { meta, vis, ident })
    }
}

mod kw {
    syn::custom_keyword!(dynec_as);
    syn::custom_keyword!(__debug_print);
}