use std::path::PathBuf as FsPathBuf; use proc_macro::TokenStream; use quote::{quote, ToTokens}; use syn::spanned::Spanned; use syn::{ parse, Attribute, Generics, Ident, Item, ItemEnum, ItemStruct, ItemUnion, Path, }; use toml::value::{Table as TomlTable, Value as TomlValue}; macro_rules! syn_path { ($first_segment: ident $(::$segment: ident)*) => { ::syn::Path { leading_colon: None, segments: ::syn::punctuated::Punctuated::from_iter([ syn_path_segment!($first_segment), $(syn_path_segment!($segment),)* ]) } }; } macro_rules! syn_path_segment { ($segment: ident) => { ::syn::PathSegment { ident: ::proc_macro2::Ident::new( stringify!($segment), ::proc_macro2::Span::call_site(), ), arguments: ::syn::PathArguments::None, } }; } #[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 .attribute::("component") .unwrap_or_default(); let drop_last = component_attr.drop_last; let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); quote! { impl #impl_generics #ecs_path::component::Component for #item_ident #type_generics #where_clause { type Component = Self; type RefMut<'component> = #ecs_path::system::ComponentRefMut<'component, Self>; 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 #impl_generics #ecs_path::system::Input for #item_ident #type_generics #where_clause { } impl #impl_generics #ecs_path::type_name::TypeName for #item_ident #type_generics #where_clause { fn type_name(&self) -> &'static str { std::any::type_name::() } } } .into() } #[proc_macro_derive(Sole, attributes(sole))] pub fn sole_derive(input: TokenStream) -> TokenStream { let item: TypeItem = parse::(input).unwrap().try_into().unwrap(); let item_ident = item.ident(); let sole_attr = item.attribute::("sole").unwrap_or_default(); let drop_last = sole_attr.drop_last; let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl(); let ecs_path = find_engine_ecs_crate_path().unwrap_or_else(|| syn_path!(ecs)); quote! { impl #impl_generics #ecs_path::sole::Sole for #item_ident #type_generics #where_clause { 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 #impl_generics #ecs_path::type_name::TypeName for #item_ident #type_generics #where_clause { fn type_name(&self) -> &'static str { std::any::type_name::() } } } .into() } trait FromAttribute: Sized { fn from_attribute(attribute: &Attribute) -> Result; } 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 attribute(&self, attr_ident: &str) -> 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 attr: Option<&Attribute> = None; for item_attr in item_attrs { if attr.is_some() { panic!("Expected only one {} attribute", attr_ident); } if item_attr.path().get_ident()? == attr_ident { attr = Some(item_attr); } } Some(Attr::from_attribute(attr?).unwrap()) } fn generics(&self) -> &Generics { match self { Self::Struct(struct_item) => &struct_item.generics, Self::Enum(enum_item) => &enum_item.generics, Self::Union(union_item) => &union_item.generics, } } } 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 FromAttribute for 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 }) } } #[derive(Debug, Default)] struct SoleAttribute { drop_last: bool, } impl FromAttribute for SoleAttribute { 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 }) } } fn find_engine_ecs_crate_path() -> Option { let cargo_manifest_dir = FsPathBuf::from(std::env::var("CARGO_MANIFEST_DIR").ok()?); let crate_manifest = std::fs::read_to_string(cargo_manifest_dir.join("Cargo.toml")) .ok()? .parse::() .expect("Failed to parse crate manifest file"); let crate_dependencies = match crate_manifest.get("dependencies")? { TomlValue::Table(dependencies) => Some(dependencies), _ => None, }?; crate_dependencies.iter().find_map(|(crate_dep_name, _)| { if crate_dep_name == "engine" { return Some(syn_path!(engine::ecs)); } None }) }