Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
161 changes: 141 additions & 20 deletions genetic-rs-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<bool>,

// darling is annoyingly restrictive and doesn't
// let me just ignore extra fields
#[darling(rename = "create_context")]
_create_context: Option<CreateContext>,

#[darling(rename = "with_context")]
_with_context: Option<syn::Path>,
}

#[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 = <Self as genetic_rs_common::prelude::RandomlyMutable>::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 = <Self as genetic_rs_common::prelude::RandomlyMutable>::Context;

fn divide(&self, ctx: &Self::Context, rate: f32, rng: &mut impl rand::Rng) -> Self {
let mut child = self.clone();
<Self as genetic_rs_common::prelude::RandomlyMutable>::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();
<Self as genetic_rs_common::prelude::RandomlyMutable>::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<syn::Path>,
create_context: Option<syn::Ident>,
create_context: Option<CreateContext>,
}

#[derive(FromMeta)]
struct CreateContext {
name: syn::Ident,
derive: Option<PathList>,
}

fn create_context_helper(
ast: &DeriveInput,
trait_name: syn::Ident,
attr_path: syn::Path,
) -> (
Option<proc_macro2::TokenStream>,
Option<proc_macro2::TokenStream>,
) {
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::<proc_macro2::TokenStream>::new();
Expand All @@ -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()),
);
}
Expand All @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion genetic-rs/examples/derive.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -15,6 +21,7 @@ impl RandomlyMutable for Foo1 {
}

#[derive(Clone, Mitosis, Debug)]
#[mitosis(use_randmut = true)]
struct Foo2(f32);

impl RandomlyMutable for Foo2 {
Expand All @@ -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,
Expand Down