1 module unit_threaded.randomized.gen; 2 3 import unit_threaded.from; 4 5 /* Return $(D true) if the passed $(D T) is a $(D Gen) struct. 6 7 A $(D Gen!T) is something that implicitly converts to $(D T), has a method 8 called $(D gen) that is accepting a $(D ref Random). 9 10 This module already brings Gens for numeric types, strings and ascii strings. 11 12 If a function needs to be benchmarked that has a parameter of custom type a 13 custom $(D Gen) is required. 14 */ 15 template isGen(T) 16 { 17 static if (is(T : Gen!(S), S...)) 18 enum isGen = true; 19 else 20 enum isGen = false; 21 } 22 23 /// 24 @safe pure unittest 25 { 26 static assert(!isGen!int); 27 static assert(isGen!(Gen!(int, 0, 10))); 28 } 29 30 private template minimum(T) { 31 import std.traits: isIntegral, isFloatingPoint, isSomeChar; 32 static if(isIntegral!T || isSomeChar!T) 33 enum minimum = T.min; 34 else static if (isFloatingPoint!T) 35 enum mininum = T.min_normal; 36 else 37 enum minimum = T.init; 38 } 39 40 private template maximum(T) { 41 import std.traits: isNumeric; 42 static if(isNumeric!T) 43 enum maximum = T.max; 44 else 45 enum maximum = T.init; 46 } 47 48 /** A $(D Gen) type that generates numeric values between the values of the 49 template parameter $(D low) and $(D high). 50 */ 51 mixin template GenNumeric(T, T low, T high) { 52 53 import std.random: Random; 54 55 static assert(is(typeof(() { 56 T[] res = frontLoaded(); 57 })), "GenNumeric needs a function frontLoaded returning " ~ T.stringof ~ "[]"); 58 59 alias Value = T; 60 61 T value; 62 63 T gen(ref Random gen) 64 { 65 import std.random: uniform; 66 67 static assert(low <= high); 68 69 this.value = _index < frontLoaded.length 70 ? frontLoaded[_index++] 71 : uniform!("[]")(low, high, gen); 72 73 return this.value; 74 } 75 76 ref T opCall() 77 { 78 return this.value; 79 } 80 81 void toString(scope void delegate(const(char)[]) sink) @trusted 82 { 83 import std.format : formattedWrite; 84 import std.traits: isFloatingPoint; 85 86 static if (isFloatingPoint!T) 87 { 88 static if (low == T.min_normal && high == T.max) 89 { 90 formattedWrite(sink, "'%s'", this.value); 91 } 92 } 93 else static if (low == T.min && high == T.max) 94 { 95 formattedWrite(sink, "'%s'", this.value); 96 } 97 else 98 { 99 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, 100 low, high); 101 } 102 } 103 104 alias opCall this; 105 106 107 private int _index; 108 } 109 110 /** A $(D Gen) type that generates numeric values between the values of the 111 template parameter $(D low) and $(D high). 112 */ 113 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isIntegral!T) 114 { 115 private static T[] frontLoaded() @safe pure nothrow { 116 import std.algorithm: filter; 117 import std.array: array; 118 T[] values = [0, 1, T.min, T.max]; 119 return values.filter!(a => a >= low && a <= high).array; 120 } 121 122 mixin GenNumeric!(T, low, high); 123 } 124 125 struct Gen(T, T low = 0, T high = 6.022E23) if(from!"std.traits".isFloatingPoint!T) { 126 private static T[] frontLoaded() @safe pure nothrow { 127 import std.algorithm: filter; 128 import std.array: array; 129 T[] values = [0, T.epsilon, T.min_normal, high]; 130 return values.filter!(a => a >= low && a <= high).array; 131 } 132 133 mixin GenNumeric!(T, low, high); 134 } 135 136 137 /** A $(D Gen) type that generates ASCII strings with a number of 138 characters that is between template parameter $(D low) and $(D high). 139 140 If $(D low) and $(D high) are very close together, this might return 141 values that are too short. They should differ by at least three for 142 char strings, one for wstrings, and zero for dstrings. 143 */ 144 struct Gen(T, size_t low = 0, size_t high = 32) if (from!"std.traits".isSomeString!T) 145 { 146 static const dchar[] charset; 147 import std.random: Random, uniform; 148 149 static immutable size_t numCharsInCharSet; 150 alias Value = T; 151 152 T value; 153 shared static this() 154 { 155 import std.array : array; 156 import std.uni : unicode; 157 import std.format : format; 158 import std.range : chain, iota; 159 import std.algorithm : map, joiner; 160 import std.conv : to; 161 import std.utf : count; 162 163 charset = chain( 164 // \t and \n 165 iota(0x09, 0x0B), 166 // \r 167 iota(0x0D, 0x0E), 168 // ' ' through '~'; next is DEL 169 iota(0x20, 0x7F), 170 // Vulgar fractions, punctuation, letters with accents, Greek 171 iota(0xA1, 0x377), 172 // More Greek 173 iota(0x37A, 0x37F), 174 iota(0x384, 0x38A), 175 iota(0x38C, 0x38C), 176 iota(0x38E, 0x3A1), 177 // Greek, Cyrillic, a bit of Armenian 178 iota(0x3A3, 0x52F), 179 // Armenian 180 iota(0x531, 0x556), 181 iota(0x559, 0x55F), 182 // Arabic 183 iota(0xFBD3, 0xFD3F), 184 iota(0xFD50, 0xFD8F), 185 iota(0xFD92, 0xFDC7), 186 // Linear B, included because it's a high character set 187 iota(0x1003C, 0x1003D), 188 iota(0x1003F, 0x1004D), 189 iota(0x10050, 0x1005D), 190 iota(0x10080, 0x100FA), 191 // Emoji 192 iota(0x1F300, 0x1F6D4) 193 ) 194 .map!(a => cast(dchar)a) 195 .array; 196 numCharsInCharSet = charset.length; 197 } 198 199 T gen(ref Random gen) 200 { 201 static assert(low <= high); 202 import std.range.primitives : ElementType; 203 import std.array : appender; 204 import std.utf : encode; 205 206 if(_index < frontLoaded.length) { 207 value = frontLoaded[_index++]; 208 return value; 209 } 210 211 auto app = appender!T(); 212 app.reserve(high); 213 size_t numElems = uniform!("[]")(low, high, gen); 214 static if ((ElementType!T).sizeof == 1) 215 { 216 char[4] buf; 217 } 218 else static if ((ElementType!T).sizeof == 2) 219 { 220 wchar[2] buf; 221 } 222 else 223 { 224 dchar[1] buf; 225 } 226 227 size_t appLength = 0; 228 while (appLength < numElems) 229 { 230 size_t charIndex = uniform!("[)")(0, charset.length, gen); 231 auto len = encode(buf, charset[charIndex]); 232 appLength += len; 233 if (appLength > high) break; 234 app.put(buf[0..len]); 235 } 236 237 this.value = app.data; 238 return this.value; 239 } 240 241 ref T opCall() 242 { 243 return this.value; 244 } 245 246 void toString(scope void delegate(const(char)[]) sink) 247 { 248 import std.format : formattedWrite; 249 250 static if (low == 0 && high == 32) 251 { 252 formattedWrite(sink, "'%s'", this.value); 253 } 254 else 255 { 256 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, 257 low, high); 258 } 259 } 260 261 alias opCall this; 262 263 private: 264 265 int _index; 266 267 T[] frontLoaded() @safe pure nothrow const { 268 import std.algorithm: filter; 269 import std.array: array; 270 T[] values = ["", "a", "é"]; 271 return values.filter!(a => a.length >= low && a.length <= high).array; 272 } 273 } 274 275 276 /// DITTO This random $(D string)s only consisting of ASCII character 277 struct GenASCIIString(size_t low = 1, size_t high = 32) 278 { 279 import std.random: Random; 280 281 static string charSet; 282 static immutable size_t numCharsInCharSet; 283 284 string value; 285 286 shared static this() 287 { 288 import std.array : array; 289 import std.uni : unicode; 290 import std.format : format; 291 import std.range : chain, iota; 292 import std.algorithm : map, joiner; 293 import std.conv : to; 294 import std.utf : byDchar, count; 295 296 GenASCIIString!(low, high).charSet = to!string(chain(iota(0x21, 297 0x7E).map!(a => to!char(cast(dchar) a)).array)); 298 299 GenASCIIString!(low, high).numCharsInCharSet = count(charSet); 300 } 301 302 string gen(ref Random gen) 303 { 304 import std.array : appender; 305 import std.random: uniform; 306 307 if(_index < frontLoaded.length) { 308 value = frontLoaded[_index++]; 309 return value; 310 } 311 312 auto app = appender!string(); 313 app.reserve(high); 314 size_t numElems = uniform!("[]")(low, high, gen); 315 316 for (size_t i = 0; i < numElems; ++i) 317 { 318 size_t toSelect = uniform!("[)")(0, numCharsInCharSet, gen); 319 app.put(charSet[toSelect]); 320 } 321 322 this.value = app.data; 323 return this.value; 324 } 325 326 ref string opCall() 327 { 328 return this.value; 329 } 330 331 void toString(scope void delegate(const(char)[]) sink) 332 { 333 import std.format : formattedWrite; 334 335 static if (low == 0 && high == 32) 336 { 337 formattedWrite(sink, "'%s'", this.value); 338 } 339 else 340 { 341 formattedWrite(sink, "'%s' low = '%s' high = '%s'", this.value, 342 low, high); 343 } 344 } 345 346 alias opCall this; 347 348 private: 349 350 int _index; 351 352 string[] frontLoaded() @safe pure nothrow const { 353 return ["", "a"]; 354 } 355 } 356 357 358 359 struct Gen(T, size_t low = 1, size_t high = 1024) 360 if(from!"std.range.primitives".isInputRange!T && !from!"std.traits".isSomeString!T) 361 { 362 363 import std.traits: Unqual, isIntegral, isFloatingPoint; 364 import std.range: ElementType; 365 import std.random: Random; 366 367 alias Value = T; 368 alias E = Unqual!(ElementType!T); 369 370 T value; 371 Gen!E elementGen; 372 373 T gen(ref Random rnd) { 374 value = _index < frontLoaded.length 375 ? frontLoaded[_index++] 376 : genArray(rnd); 377 return value; 378 } 379 380 alias value this; 381 382 private: 383 384 size_t _index; 385 //these values are always generated 386 T[] frontLoaded() @safe nothrow { 387 T[] ret = [[]]; 388 return ret; 389 } 390 391 T genArray(ref Random rnd) { 392 import std.array: appender; 393 import std.random: uniform; 394 395 immutable length = uniform(low, high, rnd); 396 auto app = appender!T; 397 app.reserve(length); 398 foreach(i; 0 .. length) { 399 app.put(elementGen.gen(rnd)); 400 } 401 402 return app.data; 403 } 404 } 405 406 static assert(isGen!(Gen!(int[]))); 407 408 409 struct Gen(T) if(is(T == bool)) { 410 import std.random: Random; 411 412 bool value; 413 alias value this; 414 415 bool gen(ref Random rnd) @safe { 416 import std.random: uniform; 417 value = [false, true][uniform(0, 2, rnd)]; 418 return value; 419 } 420 } 421 422 423 struct Gen(T, T low = minimum!T, T high = maximum!T) if (from!"std.traits".isSomeChar!T) 424 { 425 private static T[] frontLoaded() @safe pure nothrow { return []; } 426 mixin GenNumeric!(T, low, high); 427 } 428 429 430 private template AggregateTuple(T...) { 431 import unit_threaded.randomized.random: ParameterToGen; 432 import std.meta: staticMap; 433 alias AggregateTuple = staticMap!(ParameterToGen, T); 434 } 435 436 struct Gen(T) if(from!"std.traits".isAggregateType!T) { 437 438 import std.traits: Fields; 439 import std.random: Random; 440 441 AggregateTuple!(Fields!T) generators; 442 443 alias Value = T; 444 Value value; 445 446 T gen(ref Random rnd) @safe { 447 static if(is(T == class)) 448 if(value is null) 449 value = new T; 450 451 foreach(i, ref g; generators) { 452 value.tupleof[i] = g.gen(rnd); 453 } 454 455 return value; 456 } 457 458 inout(T) opCall() inout { 459 return this.value; 460 } 461 462 alias opCall this; 463 464 }