alchemist-macros 0.0.0

Hot-reload game engine & editor (WIP—MVP coming)
Documentation
use super::*;
use quote::*;
use syn::*;

pub fn manifestable(_attr: TokenStream, input: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(input as Item);
    let trait_item = match &mut input {
        Item::Trait(trait_item) => trait_item,
        _ => return TokenStream::from(quote! {
            ::core::compile_error!("The #[manifestable] attribute can only be used on trait declarations");
        }),
    };

    let ident = trait_item.ident.clone();

    insert_any_supertrait(trait_item);

    let core_path = utils::core_path_attr(&trait_item.attrs);

    let default_type_path = match default_attr(&mut trait_item.attrs) {
        Some(path) => path,
        None => return TokenStream::from(quote! {
            ::core::compile_error!("The #[manifestable] attribute must be followed by #[default(<TypePath>)]");
        }),
    };

    let custom_traits = custom_attr(&mut trait_item.attrs);
    let mut custom_qualify = false;
    let mut custom_default = false;
    let mut custom_encode = false;
    let mut custom_encompass = false;
    let mut custom_inspect = false;
    let mut custom_debug = false;

    for path in custom_traits {
        match path.get_ident() {
            Some(ident) => match ident.to_string().as_str() {
                "Qualify" => custom_qualify = true,
                "Default" => custom_default = true,
                "Encode" => custom_encode = true,
                "Encompass" => custom_encompass = true,
                "Inspect" => custom_inspect = true,
                "Debug" => custom_debug = true,
                other => return TokenStream::from(quote ! {
                    ::core::compile_error!(::core::concat!("Unrecognized identifier ", ::core::stringify!(#other), " in #[custom] attribute"));
                })
            }
            None => return TokenStream::from(quote! {
                ::core::compile_error!("Failed to read identifier in #[custom] attribute");
            }),
        }
    }

    let derived_module_ident = format_ident!("{}_manifestable", ident);

    let registry_implementation = quote! {
        #[allow(non_snake_case)]
        mod #derived_module_ident {
            use super::*;

            struct Registry {
                meta_by_type_id: ::std::collections::HashMap<::std::any::TypeId, #core_path::manifest::ManifestableMeta<Box<dyn #ident>>>,
                meta_by_qualifier: ::std::collections::HashMap<&'static str, #core_path::manifest::ManifestableMeta<Box<dyn #ident>>>,
            }

            static REGISTRY: #core_path::once_cell::sync::Lazy<Registry> = #core_path::once_cell::sync::Lazy::new(|| {
                let mut meta_by_type_id = ::std::collections::HashMap::new();
                let mut meta_by_qualifier = ::std::collections::HashMap::new();

                for registration in #core_path::inventory::iter::<#core_path::manifest::ManifestableRegistration<Box<dyn #ident>>> {

                    let type_id = (registration.type_id_fn)();
                    let qualifier = (registration.qualifier_fn)();

                    let meta = #core_path::manifest::ManifestableMeta {
                        type_id,
                        qualifier,
                        default_instance: Box::leak(Box::new((registration.default_fn)())),
                        default_fn: registration.default_fn,
                        encode_fn: registration.encode_fn,
                        decode_fn: registration.decode_fn,
                        encompass_fn: registration.encompass_fn,
                        inspect_fn: registration.inspect_fn,
                        debug_fn: registration.debug_fn,
                    };
                    let meta_clone = meta.clone();

                    meta_by_type_id.insert(type_id, meta);
                    meta_by_qualifier.insert(qualifier, meta_clone);
                }
                Registry { meta_by_type_id, meta_by_qualifier }
            });

            impl dyn #ident {
                pub fn iter_meta() -> #core_path::manifest::ManifestableMetaIterator<'static, Box<dyn #ident>> {
                    #core_path::manifest::ManifestableMetaIterator::<'static, Box<dyn #ident>>::new(
                        REGISTRY.meta_by_type_id.values()
                    )
                }
                pub fn get_meta_by_type_id(type_id: ::std::any::TypeId) -> Option<#core_path::manifest::ManifestableMeta<Box<dyn #ident>>> {
                    let meta = REGISTRY.meta_by_type_id.get(&type_id)?;
                    Some(meta.clone())
                }
                pub fn get_meta_by_qualifier(qualifier: &str) -> Option<#core_path::manifest::ManifestableMeta<Box<dyn #ident>>> {
                    let meta = REGISTRY.meta_by_qualifier.get(qualifier)?;
                    Some(meta.clone())
                }
            }
        }
    };

    let qualify_implementation = if custom_qualify { quote! {} } else {
        quote! {
            impl #core_path::manifest::Qualify for Box<dyn #ident> {
                fn qualifier() -> &'static str {
                    ::core::concat!(::core::module_path!(), "::", ::core::stringify!(#ident))
                }
            }
        }
    };

    let default_implementation = if custom_default { quote! {} } else {
        quote! {
            impl ::std::default::Default for Box<dyn #ident> {
                fn default() -> Self {
                    Box::new(#default_type_path::default())
                }
            }
        }
    };

    let encode_implementation = if custom_encode { quote! {} } else {
        quote! {
            impl #core_path::encode::Encode for Box<dyn #ident> {
                fn encode(&self, encoder: &mut #core_path::encode::Encoder) {
                    use ::std::any::Any;

                    let type_id = (**self).type_id();
                    let qualifier = <dyn #ident>::get_meta_by_type_id(type_id).map(|meta| meta.qualifier).unwrap_or_default();

                    encoder.output_dyn_start();
                    encoder.output_dyn_qualifier(qualifier);

                    match <dyn #ident>::get_meta_by_type_id(type_id) {
                        None => encoder.output_null(),
                        Some(meta) => (meta.encode_fn)(self, encoder),
                    }
                }

                fn decode(&mut self, decoder: &mut #core_path::encode::Decoder) -> Result<(), Box<dyn ::std::error::Error>> {
                    match decoder.maybe_dyn_start()? {
                        None => decoder.skip_value()?,
                        Some(_) => {
                            let qualifier = decoder.expect_dyn_qualifier()?;

                            match <dyn #ident>::get_meta_by_qualifier(&qualifier) {
                                None => decoder.skip_value()?,
                                Some(meta) => {
                                    (meta.decode_fn)(self, decoder)?;
                                }
                            }
                        }
                    }
                    Ok(())
                }
            }
        }
    };

    let encompass_implementation = if custom_encompass { quote!{} } else {
        quote! {
            impl #core_path::encompass::Encompass for Box<dyn #ident> {
                fn encompass(&self, v: &mut #core_path::encompass::EncompassVisitor) -> Result<(), Box<dyn std::error::Error>> {
                    let type_id = (**self).type_id();

                    match <dyn #ident>::get_meta_by_type_id(type_id) {
                        None => (),
                        Some(meta) => (meta.encompass_fn)(self, v)?,
                    }

                    Ok(())
                }
            }
        }
    };

    let inspect_implementation = if custom_inspect { quote!{} } else {
        quote! {
            impl #core_path::inspect::Inspect for Box<dyn #ident> {
                fn inspect(&mut self, ctx: &mut #core_path::inspect::InspectContext, ui: &mut #core_path::egui::Ui) {
                    let type_id = (**self).type_id();

                    match <dyn #ident>::get_meta_by_type_id(type_id) {
                        None => (),
                        Some(meta) => (meta.inspect_fn)(self, ctx, ui),
                    }
                }
            }
        }
    };

    let debug_implementation = if custom_debug { quote!{} } else {
        quote! {
            impl ::std::fmt::Debug for Box<dyn #ident> {
                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
                    use std::any::Any;

                    let type_id = (**self).type_id();
                    let qualifier = <dyn #ident>::get_meta_by_type_id(type_id).map(|meta| meta.qualifier).unwrap_or_default();

                    let mut debug_tuple = f.debug_tuple(::core::concat!("dyn ", ::core::stringify!(#ident)));
                    debug_tuple.field(&qualifier);

                    match <dyn #ident>::get_meta_by_type_id(type_id) {
                        Some(meta) => {
                            struct DebugWrapper<'a>(&'a Box<dyn #ident>, fn(&Box<dyn #ident>, &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result);

                            impl<'a> std::fmt::Debug for DebugWrapper<'a> {
                                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                                    (self.1)(self.0, f)
                                }
                            }

                            debug_tuple.field(&DebugWrapper(self, meta.debug_fn));
                        }
                        None => return Err(std::fmt::Error),
                    }
                    debug_tuple.finish()
                }
            }
        }
    };

    let manifest_implementation = quote! {
        impl #core_path::manifest::Manifest for Box<dyn #ident> {}

        #core_path::inventory::submit! {
            #core_path::manifest::ManifestRegistration {
                type_id_fn: || ::std::any::TypeId::of::<Box<dyn #ident>>(),
                qualifier_fn: || {
                    use #core_path::manifest::Qualify;
                    Box::<dyn #ident>::qualifier()
                },
                default_fn: || {
                    use ::std::default::Default;
                    Box::new(Box::<dyn #ident>::default())
                },
                encode_fn: |v, encoder| {
                    let force_downcasted: &Box<dyn #ident> = unsafe {
                        let (data, _vtable): (*const (), *const ()) = ::std::mem::transmute(v);
                        ::std::mem::transmute(data)
                    };
                    encoder.delegate_encoding(force_downcasted)
                },
                decode_fn: |v, decoder| -> Result<(), Box<dyn ::std::error::Error>> {
                    let force_downcasted: &mut Box<dyn #ident> = unsafe {
                        let (data, _vtable): (*const (), *const ()) = ::std::mem::transmute(v);
                        ::std::mem::transmute(data)
                    };
                    decoder.delegate_decoding(force_downcasted)?;
                    Ok(())
                },
                encompass_fn: |v, visitor| {
                    let force_downcasted: &Box<dyn #ident> = unsafe {
                        let (data, _vtable): (*const (), *const ()) = ::std::mem::transmute(v);
                        ::std::mem::transmute(data)
                    };
                    use #core_path::encompass::Encompass;
                    force_downcasted.encompass(visitor)
                },
                inspect_fn: |v, ui_context, ui| {
                    let force_downcasted: &mut Box<dyn #ident> = unsafe {
                        let (data, _vtable): (*const (), *const ()) = ::std::mem::transmute(v);
                        ::std::mem::transmute(data)
                    };
                    use #core_path::inspect::Inspect;
                    force_downcasted.inspect(ui_context, ui);
                },
                debug_fn: |v, formatter| {
                    let force_downcasted: &Box<dyn #ident> = unsafe {
                        let (data, _vtable): (*const (), *const ()) = ::std::mem::transmute(v);
                        ::std::mem::transmute(data)
                    };
                    use ::std::fmt::Debug;
                    force_downcasted.fmt(formatter)
                },

            }
        }

    };

    let full_implementation = quote! {
        #trait_item
        #registry_implementation
        #qualify_implementation
        #default_implementation
        #encode_implementation
        #encompass_implementation
        #inspect_implementation
        #debug_implementation
        #manifest_implementation
    };

    full_implementation.into()
}

pub fn default_attr(attrs: &mut Vec<Attribute>) -> Option<Path> {
    let mut default_type_path = None;

    attrs.retain(|attr| {
        let remove_attribute: bool;

        if attr.path().is_ident("default") {
            match attr.parse_args::<Path>() {
                Ok(path) => {
                    default_type_path = Some(path);
                    remove_attribute = true;
                }
                Err(_) => remove_attribute = false,
            }
        } else {
            remove_attribute = false
        }

        !remove_attribute
    });

    default_type_path
}
pub fn custom_attr(attrs: &mut Vec<Attribute>) -> Vec<Path> {
    let mut custom_traits = Vec::new();

    attrs.retain(|attr| {
        let remove_attribute: bool;

        if attr.path().is_ident("custom") {
            match attr.parse_args_with(syn::punctuated::Punctuated::<Path, Token![,]>::parse_terminated) {
                Ok(punctuated) => {
                    custom_traits = punctuated.into_iter().collect();
                    remove_attribute = true;
                }
                Err(_) => remove_attribute = false,
            }
        } else {
            remove_attribute = false
        }

        !remove_attribute
    });

    return custom_traits;
}
pub fn insert_any_supertrait(trait_item: &mut ItemTrait) {

    let any_path: Path = parse2(quote!(::std::any::Any)).unwrap();

    let trait_bound = TraitBound {
        paren_token: None,
        modifier: TraitBoundModifier::None,
        lifetimes: None,
        path: any_path,
    };

    let any_bound = TypeParamBound::Trait(trait_bound);

    trait_item.supertraits.push(any_bound);
}