use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::spanned::Spanned; use syn::{parse, Attribute, Ident, Item, ItemEnum, ItemStruct, ItemUnion}; #[proc_macro_derive(Component, attributes(component))] pub fn component_derive(input: TokenStream) -> TokenStream { let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); let item_ident = item.ident(); let component_attr = item.component_attribute().unwrap_or_default(); let drop_last = component_attr.drop_last; quote! { impl ecs::component::Component for #item_ident { fn drop_last(&self) -> bool { #drop_last } fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } fn as_any(&self) -> &dyn std::any::Any { self } } impl ecs::system::Input for #item_ident {} impl ecs::type_name::TypeName for #item_ident { fn type_name(&self) -> &'static str { std::any::type_name::() } } } .into() } enum TypeItem { Struct(ItemStruct), Enum(ItemEnum), Union(ItemUnion), } impl TypeItem { fn ident(&self) -> &Ident { match self { Self::Struct(struct_item) => &struct_item.ident, Self::Enum(enum_item) => &enum_item.ident, Self::Union(union_item) => &union_item.ident, } } fn component_attribute(&self) -> Option { let item_attrs = match &self { Self::Struct(struct_item) => &struct_item.attrs, &Self::Enum(enum_item) => &enum_item.attrs, &Self::Union(union_item) => &union_item.attrs, }; let mut component_attr: Option<&Attribute> = None; for item_attr in item_attrs { if component_attr.is_some() { panic!("Expected only one component attribute"); } if item_attr.path().get_ident()? == "component" { component_attr = Some(item_attr); } } Some(ComponentAttribute::from_attribute(component_attr?).unwrap()) } } impl TryFrom for TypeItem { type Error = syn::Error; fn try_from(item: Item) -> Result { match item { Item::Struct(struct_item) => Ok(Self::Struct(struct_item)), Item::Enum(enum_item) => Ok(Self::Enum(enum_item)), Item::Union(union_item) => Ok(Self::Union(union_item)), _ => Err(syn::Error::new( item.span(), "Expected a struct, a enum or a union", )), } } } impl ToTokens for TypeItem { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { match self { Self::Struct(struct_item) => struct_item.to_tokens(tokens), Self::Enum(enum_item) => enum_item.to_tokens(tokens), Self::Union(union_item) => union_item.to_tokens(tokens), } } } #[derive(Debug, Default)] struct ComponentAttribute { drop_last: bool, } impl ComponentAttribute { fn from_attribute(attribute: &Attribute) -> Result { let mut drop_last = false; attribute.parse_nested_meta(|meta| { if meta .path .get_ident() .is_some_and(|flag| flag == "drop_last") { drop_last = true; return Ok(()); } Err(meta.error("Unrecognized token")) })?; Ok(Self { drop_last }) } }