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