diff --git a/README.md b/README.md index 4c90a04..de2fe98 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ use genetic_rs::prelude::*; // `Mitosis` can be derived if both `Clone` and `RandomlyMutable` are present. #[derive(Clone, Debug, Mitosis)] +#[mitosis(use_randmut = true)] struct MyGenome { field1: f32, } diff --git a/genetic-rs-macros/src/lib.rs b/genetic-rs-macros/src/lib.rs index e679a6d..b7ed25f 100644 --- a/genetic-rs-macros/src/lib.rs +++ b/genetic-rs-macros/src/lib.rs @@ -1,6 +1,8 @@ extern crate proc_macro; +use darling::util::PathList; use darling::FromAttributes; +use darling::FromMeta; use proc_macro::TokenStream; use quote::quote; use quote::quote_spanned; @@ -13,7 +15,8 @@ use syn::{parse_macro_input, Data, DeriveInput}; pub fn randmut_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let (def_ctx, mut ctx_ident) = create_context_helper(&ast, parse_quote!(RandomlyMutable)); + let (def_ctx, mut ctx_ident) = + create_context_helper(&ast, parse_quote!(RandomlyMutable), parse_quote!(randmut)); let custom_context = ctx_ident.is_some(); let name = ast.ident; @@ -78,53 +81,170 @@ pub fn randmut_derive(input: TokenStream) -> TokenStream { } } -#[proc_macro_derive(Mitosis)] +#[derive(FromAttributes)] +#[darling(attributes(mitosis))] +struct MitosisSettings { + use_randmut: Option, + + // darling is annoyingly restrictive and doesn't + // let me just ignore extra fields + #[darling(rename = "create_context")] + _create_context: Option, + + #[darling(rename = "with_context")] + _with_context: Option, +} + +#[proc_macro_derive(Mitosis, attributes(mitosis))] pub fn mitosis_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let name = &ast.ident; - quote! { - #[automatically_derived] - impl genetic_rs_common::prelude::Mitosis for #name { - type Context = ::Context; + let mitosis_settings = MitosisSettings::from_attributes(&ast.attrs).unwrap(); + if mitosis_settings.use_randmut.is_some() && mitosis_settings.use_randmut.unwrap() { + quote! { + #[automatically_derived] + impl genetic_rs_common::prelude::Mitosis for #name { + type Context = ::Context; + + fn divide(&self, ctx: &Self::Context, rate: f32, rng: &mut impl rand::Rng) -> Self { + let mut child = self.clone(); + ::mutate(&mut child, ctx, rate, rng); + child + } + } + } + .into() + } else { + let (def_ctx, mut ctx_ident) = + create_context_helper(&ast, parse_quote!(RandomlyMutable), parse_quote!(mitosis)); + let custom_context = ctx_ident.is_some(); + + let name = ast.ident; + + match ast.data { + Data::Struct(s) => { + let mut is_tuple_struct = false; + let mut inner = Vec::new(); + + for (i, field) in s.fields.into_iter().enumerate() { + let ty = field.ty; + let span = ty.span(); - fn divide(&self, ctx: &Self::Context, rate: f32, rng: &mut impl rand::Rng) -> Self { - let mut child = self.clone(); - ::mutate(&mut child, ctx, rate, rng); - child + if ctx_ident.is_none() { + ctx_ident = Some( + quote_spanned! {span=> <#ty as genetic_rs_common::prelude::Mitosis>::Context }, + ); + } + + if let Some(field_name) = field.ident { + if custom_context { + inner.push(quote_spanned! {span=> + #field_name: <#ty as genetic_rs_common::prelude::Mitosis>::divide(&self.#field_name, &ctx.#field_name, rate, rng), + }); + } else { + inner.push(quote_spanned! {span=> + #field_name: <#ty as genetic_rs_common::prelude::Mitosis>::divide(&self.#field_name, ctx, rate, rng), + }); + } + } else if custom_context { + is_tuple_struct = true; + inner.push(quote_spanned! {span=> + <#ty as genetic_rs_common::prelude::Mitosis>::divide(&self.#i, &ctx.#i, rate, rng), + }); + } else { + is_tuple_struct = true; + inner.push(quote_spanned! {span=> + <#ty as genetic_rs_common::prelude::Mitosis>::divide(&self.#i, ctx, rate, rng), + }); + } + } + + let inner: proc_macro2::TokenStream = inner.into_iter().collect(); + let child = if is_tuple_struct { + quote! { + Self(#inner) + } + } else { + quote! { + Self { + #inner + } + } + }; + + quote! { + #[automatically_derived] + impl genetic_rs_common::prelude::Mitosis for #name { + type Context = #ctx_ident; + + fn divide(&self, ctx: &Self::Context, rate: f32, rng: &mut impl rand::Rng) -> Self { + #child + } + } + + #def_ctx + } + .into() + } + Data::Enum(_e) => { + panic!("enums not yet supported"); + } + Data::Union(_u) => { + panic!("unions not yet supported"); } } } - .into() } -#[derive(FromAttributes)] -#[darling(attributes(randmut, crossover))] +#[derive(FromMeta)] struct ContextArgs { with_context: Option, - create_context: Option, + create_context: Option, +} + +#[derive(FromMeta)] +struct CreateContext { + name: syn::Ident, + derive: Option, } fn create_context_helper( ast: &DeriveInput, trait_name: syn::Ident, + attr_path: syn::Path, ) -> ( Option, Option, ) { let name = &ast.ident; - let doc = quote! { #[doc = concat!("Autogenerated context struct for ", stringify!(#name))] }; + let doc = + quote! { #[doc = concat!("Autogenerated context struct for [`", stringify!(#name), "`]")] }; let vis = ast.vis.to_token_stream(); - let args = ContextArgs::from_attributes(&ast.attrs).unwrap(); + let attr = ast.attrs.iter().find(|a| a.path() == &attr_path); + if attr.is_none() { + return (None, None); + } + + let meta = &attr.unwrap().meta; + + let args = ContextArgs::from_meta(meta).unwrap(); if args.create_context.is_some() && args.with_context.is_some() { panic!("cannot have both create_context and with_context"); } - if let Some(ident) = args.create_context { + if let Some(create_ctx) = args.create_context { + let ident = &create_ctx.name; + let derives = create_ctx.derive.map(|paths| { + quote! { + #[derive(#(#paths,)*)] + } + }); + match &ast.data { Data::Struct(s) => { let mut inner = Vec::::new(); @@ -150,13 +270,13 @@ fn create_context_helper( if tuple_struct { return ( - Some(quote! { #doc #vis struct #ident (#inner);}), + Some(quote! { #doc #derives #vis struct #ident (#inner);}), Some(ident.to_token_stream()), ); } return ( - Some(quote! { #doc #vis struct #ident {#inner};}), + Some(quote! { #doc #derives #vis struct #ident {#inner};}), Some(ident.to_token_stream()), ); } @@ -177,7 +297,8 @@ fn create_context_helper( pub fn crossover_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let (def_ctx, mut context) = create_context_helper(&ast, parse_quote!(Crossover)); + let (def_ctx, mut context) = + create_context_helper(&ast, parse_quote!(Crossover), parse_quote!(crossover)); let custom_context = context.is_some(); let name = ast.ident; diff --git a/genetic-rs/examples/derive.rs b/genetic-rs/examples/derive.rs index 885a728..7c79d9e 100644 --- a/genetic-rs/examples/derive.rs +++ b/genetic-rs/examples/derive.rs @@ -1,9 +1,15 @@ +#![allow(dead_code)] + use genetic_rs::prelude::*; +#[derive(Clone, Debug, Default)] struct Context1; + +#[derive(Clone, Debug, Default)] struct Context2; #[derive(Clone, Mitosis, Debug)] +#[mitosis(use_randmut = true)] struct Foo1(f32); impl RandomlyMutable for Foo1 { @@ -15,6 +21,7 @@ impl RandomlyMutable for Foo1 { } #[derive(Clone, Mitosis, Debug)] +#[mitosis(use_randmut = true)] struct Foo2(f32); impl RandomlyMutable for Foo2 { @@ -26,7 +33,8 @@ impl RandomlyMutable for Foo2 { } #[derive(Clone, RandomlyMutable, Mitosis, Debug)] -#[randmut(create_context = BarCtx)] +#[randmut(create_context(name = BarCtx, derive(Clone, Debug, Default)))] +#[mitosis(with_context = BarCtx)] struct Bar { a: Foo1, b: Foo2,