diff options
-rw-r--r-- | Cargo.lock | 10 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | gen_random/Cargo.lock | 45 | ||||
-rw-r--r-- | gen_random/Cargo.toml | 3 | ||||
-rw-r--r-- | gen_random/src/lib.rs | 207 | ||||
-rw-r--r-- | gen_random_proc_macro/src/lib.rs | 10 | ||||
-rw-r--r-- | gen_random_test/Cargo.lock | 128 | ||||
-rw-r--r-- | gen_random_test/Cargo.toml | 11 | ||||
-rw-r--r-- | gen_random_test/src/lib.rs | 77 | ||||
-rw-r--r-- | src/main.rs | 1 |
10 files changed, 229 insertions, 264 deletions
@@ -23,7 +23,6 @@ version = "0.1.0" dependencies = [ "gen_random", "gen_random_proc_macro", - "gen_random_test", "gl", "nalgebra", "rand", @@ -58,15 +57,6 @@ dependencies = [ ] [[package]] -name = "gen_random_test" -version = "0.1.0" -dependencies = [ - "gen_random", - "gen_random_proc_macro", - "rand", -] - -[[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -11,4 +11,3 @@ nalgebra = "0.31" rand = "0.8" gen_random_proc_macro = { path = "./gen_random_proc_macro" } gen_random = { path = "./gen_random" } -gen_random_test = { path = "./gen_random_test" } diff --git a/gen_random/Cargo.lock b/gen_random/Cargo.lock index ad87cc3..b7e5b20 100644 --- a/gen_random/Cargo.lock +++ b/gen_random/Cargo.lock @@ -12,10 +12,20 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "gen_random" version = "0.1.0" dependencies = [ + "gen_random_proc_macro", "rand", ] [[package]] +name = "gen_random_proc_macro" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -39,6 +49,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -69,6 +97,23 @@ dependencies = [ ] [[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/gen_random/Cargo.toml b/gen_random/Cargo.toml index 1d14093..384227b 100644 --- a/gen_random/Cargo.toml +++ b/gen_random/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" [dependencies] rand = "0.8" + +[dev-dependencies] +gen_random_proc_macro = { path = "../gen_random_proc_macro" } diff --git a/gen_random/src/lib.rs b/gen_random/src/lib.rs index b9cdb4f..c21a1b1 100644 --- a/gen_random/src/lib.rs +++ b/gen_random/src/lib.rs @@ -4,11 +4,61 @@ use std::rc::Rc; use std::sync::Arc; use std::cell::{Cell, RefCell}; +/// Generate random structs and enums! +/// +/// You shouldn't implement this trait yourself — instead, use the `derive` macro: +/// ``` +/// use gen_random_proc_macro::GenRandom; +/// use gen_random::GenRandom; +/// +/// #[derive(GenRandom, Debug)] +/// enum MyType { +/// // this variant will be chosen 7 / 10.5 = 2/3 of the time +/// #[prob = 7] +/// Variant1(f64), +/// // this variant will be chosen 3.5 / 10.5 = 1/3 of the time +/// #[prob = 3.5] +/// Variant2 { +/// // bias & scale attributes can be used for fields of type f32/f64. +/// // this makes `a` range from 2 to 6 (as opposed to the default 0 to 1). +/// #[bias = 2.0] +/// #[scale = 4.0] +/// a: f64, +/// // we can even include a randomly-generated MyType inside this MyType! +/// // be careful when doing this or else you might try to generate an infinite struct! +/// b: Box<MyType> +/// } +/// } +/// +/// fn main() { +/// let my_value = MyType::gen_thread_random(); +/// println!("{my_value:?}"); +/// } +/// ``` + pub trait GenRandom: Sized { - fn gen_random(rng: &mut impl Rng) -> Self; - fn gen_thread_random() -> Self { + /// To allow recursive structs like binary trees, + /// we provide a `max_depth` functionality. + /// If your struct isn't recursive, you can use [GenRandom::gen_random] instead. + /// If `max_depth <= 0` the **first** variant of an `enum` is always chosen + /// (so make sure `Empty` or whatever comes first). + /// For `Option<T>`, if `max_depth <= 0`, `None` is always chosen. + fn gen_random_max_depth(rng: &mut impl Rng, max_depth: isize) -> Self; + + /// Generate a random instance of this struct using the given random number generator. + fn gen_random(rng: &mut impl Rng) -> Self { + Self::gen_random_max_depth(rng, isize::MAX) + } + + /// Generate a random instance of this struct using `rand::thread_rng()` with a maximum depth. + fn gen_thread_random_max_depth(max_depth: isize) -> Self { let mut thread_rng = rand::thread_rng(); - Self::gen_random(&mut thread_rng) + Self::gen_random_max_depth(&mut thread_rng, max_depth) + } + + /// Generate a random instance of this struct using `rand::thread_rng()`. + fn gen_thread_random() -> Self { + Self::gen_thread_random_max_depth(isize::MAX) } } @@ -21,96 +71,189 @@ pub fn gen_thread_random_vec<T: GenRandom>(len: usize) -> Vec<T> { } impl GenRandom for f32 { - fn gen_random(rng: &mut impl Rng) -> Self { + fn gen_random_max_depth(rng: &mut impl Rng, _depth: isize) -> Self { rng.gen_range(0.0..1.0) } } impl GenRandom for f64 { - fn gen_random(rng: &mut impl Rng) -> Self { + fn gen_random_max_depth(rng: &mut impl Rng, _depth: isize) -> Self { rng.gen_range(0.0..1.0) } } impl<T: GenRandom> GenRandom for Box<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - Box::new(T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + Box::new(T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for [T; 1] { - fn gen_random(rng: &mut impl Rng) -> Self { - [T::gen_random(rng)] + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + [T::gen_random_max_depth(rng, depth)] } } impl<T: GenRandom> GenRandom for [T; 2] { - fn gen_random(rng: &mut impl Rng) -> Self { - [T::gen_random(rng), T::gen_random(rng)] + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + [T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)] } } impl<T: GenRandom> GenRandom for [T; 3] { - fn gen_random(rng: &mut impl Rng) -> Self { - [T::gen_random(rng), T::gen_random(rng), T::gen_random(rng)] + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + [T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)] } } impl<T: GenRandom> GenRandom for [T; 4] { - fn gen_random(rng: &mut impl Rng) -> Self { - [T::gen_random(rng), T::gen_random(rng), T::gen_random(rng), T::gen_random(rng)] + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + [T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)] } } impl<T: GenRandom> GenRandom for (T, T) { - fn gen_random(rng: &mut impl Rng) -> Self { - (T::gen_random(rng), T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + (T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for (T, T, T) { - fn gen_random(rng: &mut impl Rng) -> Self { - (T::gen_random(rng), T::gen_random(rng), T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + (T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for (T, T, T, T) { - fn gen_random(rng: &mut impl Rng) -> Self { - (T::gen_random(rng), T::gen_random(rng), T::gen_random(rng), T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + (T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth), T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for Rc<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - Self::new(T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + Self::new(T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for Arc<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - Self::new(T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + Self::new(T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for Cell<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - Self::new(T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + Self::new(T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for RefCell<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - Self::new(T::gen_random(rng)) + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + Self::new(T::gen_random_max_depth(rng, depth)) } } impl<T: GenRandom> GenRandom for Option<T> { - fn gen_random(rng: &mut impl Rng) -> Self { - if rng.gen_range(0..2) == 0 { + fn gen_random_max_depth(rng: &mut impl Rng, depth: isize) -> Self { + if depth <= 0 { + None + } else if rng.gen_range(0..2) == 0 { None } else { - Some(T::gen_random(rng)) + Some(T::gen_random_max_depth(rng, depth)) } } } + +#[cfg(test)] +mod tests { + extern crate gen_random_proc_macro; + extern crate rand; + use super::{gen_thread_random_vec, GenRandom}; + use gen_random_proc_macro::GenRandom; + + #[derive(GenRandom, Debug)] + enum Test1 { + #[prob = 0.2] + A(f32), + #[prob = 0.8] + B(Option<f32>), + } + + #[derive(GenRandom, Debug)] + #[allow(dead_code)] + enum Test2 { + #[prob = 0.1] + Variant1, + #[prob = 0.7] + Variant2 { x: f32, y: f64, z: Test1 }, + #[prob = 0.2] + Variant3(f32, Box<Test2>), + } + + #[derive(GenRandom, Debug)] + enum LinkedList { + #[prob = 10] + Empty, + #[prob = 90] + Cons(f32, Box<LinkedList>), + } + + #[derive(GenRandom, Debug)] + enum BinaryTree { + #[prob = 1] + Empty, + #[prob = 99] + Node(f64, Box<BinaryTree>, Box<BinaryTree>) + } + + #[derive(GenRandom, Debug)] + struct ScaleBias { + #[bias = 1.0] + #[scale = 10.0] + a: f32, + #[bias = 2.0] + #[scale = 0.0] + b: f32, + } + + #[test] + fn basic() { + let tests1: Vec<Test1> = gen_thread_random_vec(10); + println!("{tests1:?}"); + } + + #[test] + fn many_types_of_variants() { + let tests2: Vec<Test2> = gen_thread_random_vec(10); + println!("{tests2:?}"); + } + + #[test] + fn linked_list() { + let ll = LinkedList::gen_thread_random(); + println!("{ll:?}"); + } + + #[test] + fn scale_bias() { + let sb: Vec<ScaleBias> = gen_thread_random_vec(10); + println!("{sb:?}"); + for x in sb.iter() { + if x.a < 1.0 || x.a > 11.0 { + panic!("a field should be between 1 and 11; got {}", x.a); + } + if x.b != 2.0 { + panic!("b field should be exactly 2; got {}", x.b); + } + } + } + + #[test] + fn binary_tree_max_depth() { + let bintree = BinaryTree::gen_thread_random_max_depth(5); + println!("{bintree:?}"); + } +} diff --git a/gen_random_proc_macro/src/lib.rs b/gen_random_proc_macro/src/lib.rs index fa2d5dd..5e28e16 100644 --- a/gen_random_proc_macro/src/lib.rs +++ b/gen_random_proc_macro/src/lib.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use std::any::type_name; use quote::quote; +/// See `gen_random::GenRandom`. #[proc_macro_derive(GenRandom, attributes(prob, scale, bias))] pub fn gen_random_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree @@ -60,7 +61,7 @@ fn generate_fields(fields: &syn::Fields) -> impl quote::ToTokens { field_values.extend(quote! {#name: }); } let ty = &field.ty; - field_values.extend(quote! { <#ty as GenRandom>::gen_random(rng) }); + field_values.extend(quote! { <#ty as GenRandom>::gen_random_max_depth(rng, _depth - 1) }); if let Some(scale) = get_attribute_literal(&field.attrs, "scale") { field_values.extend(quote! { * #scale }); @@ -129,7 +130,7 @@ fn impl_gen_random(ast: &syn::DeriveInput) -> TokenStream { } // ideally we would just do - // let mut variant: f64 = rng.gen_range(0.0..1.0); + // let mut variant: f64 = rng.gen_range(0.0..prob_sum); // variant -= variant1_probability; // if variant < 0.0 { bla bla bla } // variant -= variant2_probability; @@ -160,7 +161,8 @@ fn impl_gen_random(ast: &syn::DeriveInput) -> TokenStream { function_body.extend(quote! { variant -= #probability; - if variant < 0.0 { return Self::#name #field_values; } + // note: if _depth <= 0, we will always return the first variant. + if _depth <= 0 || variant < 0.0 { return Self::#name #field_values; } }); test_variant -= probability; @@ -187,7 +189,7 @@ fn impl_gen_random(ast: &syn::DeriveInput) -> TokenStream { let gen = quote! { impl GenRandom for #name { - fn gen_random(rng: &mut impl rand::Rng) -> Self { + fn gen_random_max_depth(rng: &mut impl rand::Rng, _depth: isize) -> Self { #function_body } } diff --git a/gen_random_test/Cargo.lock b/gen_random_test/Cargo.lock deleted file mode 100644 index 8b5a9ff..0000000 --- a/gen_random_test/Cargo.lock +++ /dev/null @@ -1,128 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "gen_random" -version = "0.1.0" -dependencies = [ - "rand", -] - -[[package]] -name = "gen_random_proc_macro" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gen_random_test" -version = "0.1.0" -dependencies = [ - "gen_random", - "gen_random_proc_macro", - "rand", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "libc" -version = "0.2.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "syn" -version = "1.0.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/gen_random_test/Cargo.toml b/gen_random_test/Cargo.toml deleted file mode 100644 index b712853..0000000 --- a/gen_random_test/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "gen_random_test" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -gen_random_proc_macro = { path = "../gen_random_proc_macro" } -gen_random = { path = "../gen_random" } -rand = "0.8" diff --git a/gen_random_test/src/lib.rs b/gen_random_test/src/lib.rs deleted file mode 100644 index 0d2b1cc..0000000 --- a/gen_random_test/src/lib.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[cfg(test)] -mod tests { - extern crate gen_random; - extern crate gen_random_proc_macro; - extern crate rand; - use gen_random::{gen_thread_random_vec, GenRandom}; - use gen_random_proc_macro::GenRandom; - - #[derive(GenRandom, Debug)] - enum Test1 { - #[prob = 0.2] - A(f32), - #[prob = 0.8] - B(Option<f32>), - } - - #[derive(GenRandom, Debug)] - #[allow(dead_code)] - enum Test2 { - #[prob = 0.1] - Variant1, - #[prob = 0.7] - Variant2 { x: f32, y: f64, z: Test1 }, - #[prob = 0.2] - Variant3(f32, Box<Test2>), - } - - #[derive(GenRandom, Debug)] - enum LinkedList { - #[prob = 10] - Empty, - #[prob = 90] - Cons(f32, Box<LinkedList>), - } - - #[derive(GenRandom, Debug)] - struct ScaleBias { - #[bias = 1.0] - #[scale = 10.0] - a: f32, - #[bias = 2.0] - #[scale = 0.0] - b: f32, - } - - #[test] - fn basic() { - let tests1: Vec<Test1> = gen_thread_random_vec(10); - println!("{tests1:?}"); - } - - #[test] - fn many_types_of_variants() { - let tests2: Vec<Test2> = gen_thread_random_vec(10); - println!("{tests2:?}"); - } - - #[test] - fn linked_list() { - let ll = LinkedList::gen_thread_random(); - println!("{ll:?}"); - } - - #[test] - fn scale_bias() { - let sb: Vec<ScaleBias> = gen_thread_random_vec(10); - println!("{sb:?}"); - for x in sb.iter() { - if x.a < 1.0 || x.a > 11.0 { - panic!("a field should be between 1 and 11; got {}", x.a); - } - if x.b != 2.0 { - panic!("b field should be exactly 2; got {}", x.b); - } - } - } -} diff --git a/src/main.rs b/src/main.rs index d1afbce..3e0a55d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ /* @TODO: -- use 0..(sum of probs) for variant - fullscreen key - mathematical analysis - options for: |