From 645d3f8d22136d58d65f034fc15ea748d751eb96 Mon Sep 17 00:00:00 2001 From: Alexander van Ratingen <470642+alvra@users.noreply.github.com> Date: Thu, 21 Apr 2022 22:16:02 +0200 Subject: Initial commit --- derive-macro/Cargo.toml | 21 +++++++++++++++ derive-macro/src/handle_enum.rs | 55 +++++++++++++++++++++++++++++++++++++++ derive-macro/src/handle_struct.rs | 15 +++++++++++ derive-macro/src/lib.rs | 46 ++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+) create mode 100644 derive-macro/Cargo.toml create mode 100644 derive-macro/src/handle_enum.rs create mode 100644 derive-macro/src/handle_struct.rs create mode 100644 derive-macro/src/lib.rs (limited to 'derive-macro') diff --git a/derive-macro/Cargo.toml b/derive-macro/Cargo.toml new file mode 100644 index 0000000..24bfa19 --- /dev/null +++ b/derive-macro/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "generate-random-macro" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +authors = ["Alexander van Ratingen"] +homepage = "https://github.com/alvra/generate-random" +repository = "https://github.com/alvra/generate-random" +documentation = "https://docs.rs/generate-random" +description = "Generate random data" +readme = "README.md" +keywords = ["random"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = { version = "1.0", features = ["full", "extra-traits"] } +quote = "1.0" +proc_macro2_helper = "0.2" diff --git a/derive-macro/src/handle_enum.rs b/derive-macro/src/handle_enum.rs new file mode 100644 index 0000000..b2b14c0 --- /dev/null +++ b/derive-macro/src/handle_enum.rs @@ -0,0 +1,55 @@ +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::quote; +use syn::{DataEnum, Variant}; +use super::generate_fields; + +fn variant_weight(variant: &Variant) -> Literal { + for attr in variant.attrs.iter() { + if attr.path.is_ident("weight") { + return attr.parse_args::().expect("expected literal for `#[weight(...)]`") + } + } + Literal::u64_suffixed(1) +} + +pub fn generate(name: &Ident, ty: DataEnum) -> TokenStream { + let mut variant_weights = ty.variants.into_iter() + .map(|variant| (variant_weight(&variant), variant)); + + let mut arms = TokenStream::new(); + let mut total_weight = quote! { 0 }; + if let Some((weight, variant)) = variant_weights.next() { + let variant_name = variant.ident; + let fields = generate_fields(variant.fields); + arms.extend(quote! { + let end = #weight; + if 0 <= value && value < end { + return Self::#variant_name #fields + } + }); + total_weight = quote! { #weight }; + for (weight, variant) in variant_weights { + let variant_name = variant.ident; + let fields = generate_fields(variant.fields); + arms.extend(quote! { + let start = end; + let end = start + #weight; + if start <= value && value < end { + return Self::#variant_name #fields + } + }); + total_weight = quote! { #total_weight + #weight }; + } + } + + quote! { + impl generate_random::GenerateRandom for #name { + fn generate_random(rng: &mut R) -> Self { + let total_weight = #total_weight; + let value = rng.gen_range(0..total_weight); + #arms + unreachable!() + } + } + } +} diff --git a/derive-macro/src/handle_struct.rs b/derive-macro/src/handle_struct.rs new file mode 100644 index 0000000..988bd06 --- /dev/null +++ b/derive-macro/src/handle_struct.rs @@ -0,0 +1,15 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::DataStruct; +use super::generate_fields; + +pub fn generate(name: &Ident, ty: DataStruct) -> TokenStream { + let fields = generate_fields(ty.fields); + quote! { + impl generate_random::GenerateRandom for #name { + fn generate_random(rng: &mut R) -> Self { + Self #fields + } + } + } +} diff --git a/derive-macro/src/lib.rs b/derive-macro/src/lib.rs new file mode 100644 index 0000000..9aa93f5 --- /dev/null +++ b/derive-macro/src/lib.rs @@ -0,0 +1,46 @@ +//! This crate provide the [`GenerateRandom`] derive macro +//! that implements the trait of the same name from the `generate-random` crate. +//! Refer to the documentation of that crate for more information. + +use syn::{DeriveInput, Data, Fields}; + +mod handle_struct; +mod handle_enum; + +#[proc_macro_derive(GenerateRandom, attributes(weight))] +pub fn derive_generate_random(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: DeriveInput = syn::parse(input).unwrap(); + match input.data { + Data::Struct(ty) => handle_struct::generate(&input.ident, ty), + Data::Enum(ty) => handle_enum::generate(&input.ident, ty), + Data::Union(_) => panic!("Unions are not supported"), + }.into() +} + +fn generate_fields(fields: Fields) -> proc_macro2::TokenStream { + use quote::quote; + match fields { + Fields::Named(fields) => { + let fields = fields.named.into_iter() + .map(|field| { + let field = field.ident.unwrap(); + quote! { + #field: generate_random::GenerateRandom::generate_random(rng), + } + }) + .collect::(); + quote! { { #fields } } + } + Fields::Unnamed(fields) => { + let fields = fields.unnamed.into_iter() + .map(|_field| { + quote! { + generate_random::GenerateRandom::generate_random(rng), + } + }) + .collect::(); + quote! { ( #fields ) } + } + Fields::Unit => quote! {}, + } +} -- cgit v1.2.3