1 /++ 2 This module contains functions that in one way or another converts its 3 arguments into something else. 4 5 Credit for [Enum] goes to Stephan Koch (https://github.com/UplinkCoder). 6 7 Example: 8 --- 9 enum SomeEnum { one, two, three }; 10 11 SomeEnum foo = Enum!SomeEnum.fromString("one"); 12 SomeEnum bar = Enum!SomeEnum.fromString("three"); 13 14 assert(foo == SomeEnum.one); 15 assert(bar == SomeEnum.three); 16 --- 17 +/ 18 module lu.conv; 19 20 private: 21 22 import std.range.primitives : isOutputRange; 23 import std.traits : isIntegral; 24 import std.typecons : Flag, No, Yes; 25 26 public: 27 28 @safe: 29 30 31 // Enum 32 /++ 33 Template housing optimised functions to get the string name of an enum 34 member, or the enum member of a name string. 35 36 [std.conv.to] is typically the go-to for this job; however it quickly bloats 37 the binary and is not performant on larger enums. 38 39 Params: 40 E = enum to base this template on. 41 +/ 42 template Enum(E) 43 if (is(E == enum)) 44 { 45 // fromString 46 /++ 47 Takes the member of an enum by string and returns that enum member. 48 49 It lowers to a big switch of the enum member strings. It is faster than 50 [std.conv.to] and generates less template bloat. However, it does not work 51 with enums where multiple members share the same values, as the big switch 52 ends up getting duplicate cases. 53 54 Taken from: https://forum.dlang.org/post/bfnwstkafhfgihavtzsz@forum.dlang.org 55 written by Stephan Koch (https://github.com/UplinkCoder). 56 Used with permission. 57 58 Example: 59 --- 60 enum SomeEnum { one, two, three }; 61 62 SomeEnum foo = Enum!SomeEnum.fromString("one"); 63 SomeEnum bar = Enum!SomeEnum.fromString("three"); 64 65 assert(foo == SomeEnum.one); 66 assert(bar == SomeEnum.three); 67 --- 68 69 Params: 70 enumstring = the string name of an enum member. 71 72 Returns: 73 The enum member whose name matches the enumstring string (not whose 74 *value* matches the string). 75 76 Throws: [std.conv.ConvException|ConvException] if no matching enum member with the 77 passed name could be found. 78 79 Bugs: 80 Does not work with enums that have members with duplicate values. 81 +/ 82 E fromString(const string enumstring) pure 83 { 84 enum enumSwitch = () 85 { 86 string enumSwitch = "import std.conv : ConvException;\n" ~ 87 "with (E) switch (enumstring)\n{\n"; 88 89 foreach (immutable memberstring; __traits(allMembers, E)) 90 { 91 enumSwitch ~= `case "` ~ memberstring ~ `": return ` ~ memberstring ~ ";\n"; 92 } 93 94 enumSwitch ~= "default:\n" ~ 95 " //import std.traits : fullyQualifiedName;\n" ~ 96 ` throw new ConvException("No such " ~ E.stringof ~ ": " ~ enumstring);` ~ "\n}"; 97 98 return enumSwitch; 99 }(); 100 101 mixin(enumSwitch); 102 } 103 104 105 // toString 106 /++ 107 The inverse of [fromString], this function takes an enum member value 108 and returns its string identifier. 109 110 It lowers to a big switch of the enum members. It is faster than 111 [std.conv.to] and generates less template bloat. 112 113 Taken from: https://forum.dlang.org/post/bfnwstkafhfgihavtzsz@forum.dlang.org 114 written by Stephan Koch (https://github.com/UplinkCoder). 115 Used with permission. 116 117 Example: 118 --- 119 enum SomeEnum { one, two, three }; 120 121 string foo = Enum!SomeEnum.toString(one); 122 assert(foo == "one"); 123 --- 124 125 Params: 126 value = Enum member whose string name we want. 127 128 Returns: 129 The string name of the passed enum member. 130 +/ 131 string toString(E value) pure nothrow 132 { 133 switch (value) 134 { 135 136 foreach (immutable m; __traits(allMembers, E)) 137 { 138 case mixin("E." ~ m) : return m; 139 } 140 141 default: 142 string result = "cast(" ~ E.stringof ~ ")"; 143 uint val = value; 144 enum headLength = E.stringof.length + "cast()".length; 145 146 immutable log10Val = 147 (val < 10) ? 0 : 148 (val < 100) ? 1 : 149 (val < 1_000) ? 2 : 150 (val < 10_000) ? 3 : 151 (val < 100_000) ? 4 : 152 (val < 1_000_000) ? 5 : 153 (val < 10_000_000) ? 6 : 154 (val < 100_000_000) ? 7 : 155 (val < 1_000_000_000) ? 8 : 9; 156 157 result.length += log10Val + 1; 158 159 for (uint i; i != log10Val + 1; ++i) 160 { 161 cast(char)result[headLength + log10Val - i] = cast(char)('0' + (val % 10)); 162 val /= 10; 163 } 164 165 return result; 166 } 167 } 168 } 169 170 /// 171 @system 172 unittest 173 { 174 import std.conv : ConvException; 175 import std.exception : assertThrown; 176 177 enum T 178 { 179 UNSET, 180 QUERY, 181 PRIVMSG, 182 RPL_ENDOFMOTD 183 } 184 185 with (T) 186 { 187 static assert(Enum!T.fromString("QUERY") == QUERY); 188 static assert(Enum!T.fromString("PRIVMSG") == PRIVMSG); 189 static assert(Enum!T.fromString("RPL_ENDOFMOTD") == RPL_ENDOFMOTD); 190 static assert(Enum!T.fromString("UNSET") == UNSET); 191 assertThrown!ConvException(Enum!T.fromString("DOESNTEXIST")); // needs @system 192 } 193 194 with (T) 195 { 196 static assert(Enum!T.toString(QUERY) == "QUERY"); 197 static assert(Enum!T.toString(PRIVMSG) == "PRIVMSG"); 198 static assert(Enum!T.toString(RPL_ENDOFMOTD) == "RPL_ENDOFMOTD"); 199 } 200 } 201 202 203 // numFromHex 204 /++ 205 Returns the decimal value of a hex number in string form. 206 207 Example: 208 --- 209 int fifteen = numFromHex("F"); 210 int twofiftyfive = numFromHex("FF"); 211 --- 212 213 Params: 214 hex = Hexadecimal number in string form. 215 acceptLowercase = Flag of whether or not to accept `rrggbb` in lowercase form. 216 217 Returns: 218 An integer equalling the value of the passed hexadecimal string. 219 220 Throws: [std.conv.ConvException|ConvException] if the hex string was malformed. 221 +/ 222 auto numFromHex( 223 const string hex, 224 const Flag!"acceptLowercase" acceptLowercase = Yes.acceptLowercase) pure 225 out (total; (total < 16^^hex.length), "`numFromHex` output is too large") 226 { 227 import std.string : representation; 228 229 int val = -1; 230 int total; 231 232 foreach (immutable c; hex.representation) 233 { 234 switch (c) 235 { 236 case '0': 237 .. 238 case '9': 239 val = (c - 48); 240 goto case 'F'; 241 242 case 'a': 243 .. 244 case 'f': 245 if (acceptLowercase) 246 { 247 val = (c - (55+32)); 248 goto case 'F'; 249 } 250 else 251 { 252 goto default; 253 } 254 255 case 'A': 256 .. 257 case 'F': 258 if (val < 0) val = (c - 55); 259 total *= 16; 260 total += val; 261 val = -1; 262 break; 263 264 default: 265 import std.conv : ConvException; 266 throw new ConvException("Invalid hex string: " ~ hex); 267 } 268 } 269 270 return total; 271 } 272 273 274 // rgbFromHex 275 /++ 276 Convenience wrapper that takes a hex string and populates a Voldemort 277 struct with its RR, GG and BB components. 278 279 This is to be used when mapping a `#RRGGBB` colour to their decimal 280 red/green/blue equivalents. 281 282 Params: 283 hexString = Hexadecimal number (colour) in string form. 284 acceptLowercase = Whether or not to accept the `rrggbb` string in 285 lowercase letters. 286 287 Returns: 288 A Voldemort struct with `r`, `g` and `b` members, 289 +/ 290 auto rgbFromHex( 291 const string hexString, 292 const Flag!"acceptLowercase" acceptLowercase = No.acceptLowercase) 293 { 294 struct RGB 295 { 296 int r; 297 int g; 298 int b; 299 } 300 301 RGB rgb; 302 immutable hex = (hexString[0] == '#') ? hexString[1..$] : hexString; 303 304 rgb.r = numFromHex(hex[0..2], acceptLowercase); 305 rgb.g = numFromHex(hex[2..4], acceptLowercase); 306 rgb.b = numFromHex(hex[4..$], acceptLowercase); 307 308 return rgb; 309 } 310 311 /// 312 unittest 313 { 314 import std.conv : text; 315 { 316 auto rgb = rgbFromHex("000102"); 317 318 assert((rgb.r == 0), rgb.r.text); 319 assert((rgb.g == 1), rgb.g.text); 320 assert((rgb.b == 2), rgb.b.text); 321 } 322 { 323 auto rgb = rgbFromHex("#FFFFFF"); 324 325 assert((rgb.r == 255), rgb.r.text); 326 assert((rgb.g == 255), rgb.g.text); 327 assert((rgb.b == 255), rgb.b.text); 328 } 329 { 330 auto rgb = rgbFromHex("#3C507D"); 331 332 assert((rgb.r == 60), rgb.r.text); 333 assert((rgb.g == 80), rgb.b.text); 334 assert((rgb.b == 125), rgb.b.text); 335 } 336 { 337 auto rgb = rgbFromHex("9a4B7c", Yes.acceptLowercase); 338 339 assert((rgb.r == 154), rgb.r.text); 340 assert((rgb.g == 75), rgb.g.text); 341 assert((rgb.b == 124), rgb.b.text); 342 } 343 } 344 345 346 // toAlphaInto 347 /++ 348 Translates an integer into an alphanumeric string. Assumes ASCII. 349 Overload that takes an output range sink. 350 351 Example: 352 --- 353 Appender!(char[]) sink; 354 int num = 12345; 355 num.toAlphaInto(sink); 356 assert(sink.data == "12345"); 357 assert(sink.data == num.to!string); 358 --- 359 360 Params: 361 maxDigits = The maximum number of digits to expect input of. 362 leadingZeroes = The minimum amount of leading zeroes to include in the 363 output, mirroring the format specifier "`%0nd`". 364 num = Integer to translate into string. 365 sink = Output range sink. 366 +/ 367 void toAlphaInto(size_t maxDigits = 19, uint leadingZeroes = 0, Num, Sink) 368 (const Num num, auto ref Sink sink) 369 if (isIntegral!Num && isOutputRange!(Sink, char[])) 370 { 371 static assert((maxDigits >= leadingZeroes), "Cannot pass more leading zeroes " ~ 372 "than max digits to `toAlphaInto`"); 373 374 if (num == 0) 375 { 376 static if (leadingZeroes > 0) 377 { 378 foreach (immutable i; 0..leadingZeroes) 379 { 380 sink.put('0'); 381 } 382 } 383 else 384 { 385 sink.put('0'); 386 } 387 return; 388 } 389 else if (num < 0) 390 { 391 sink.put('-'); 392 } 393 394 static if (leadingZeroes > 0) 395 { 396 // Need default-initialised fields to be zeroes 397 ubyte[maxDigits] digits; 398 } 399 else 400 { 401 ubyte[maxDigits] digits = void; 402 } 403 404 size_t pos; 405 406 for (Num window = num; window != 0; window /= 10) 407 { 408 import std.math : abs; 409 digits[pos++] = cast(ubyte)abs(window % 10); 410 } 411 412 static if (leadingZeroes > 0) 413 { 414 import std.algorithm.comparison : max; 415 size_t startingPos = max(leadingZeroes, pos); 416 } 417 else 418 { 419 alias startingPos = pos; 420 } 421 422 foreach_reverse (immutable digit; digits[0..startingPos]) 423 { 424 sink.put(cast(char)(digit + '0')); 425 } 426 } 427 428 /// 429 unittest 430 { 431 import std.array : Appender; 432 433 Appender!(char[]) sink; 434 435 { 436 enum num = 123_456; 437 num.toAlphaInto(sink); 438 assert((sink.data == "123456"), sink.data); 439 sink.clear(); 440 } 441 { 442 enum num = 0; 443 num.toAlphaInto(sink); 444 assert((sink.data == "0"), sink.data); 445 sink.clear(); 446 } 447 { 448 enum num = 999; 449 num.toAlphaInto(sink); 450 assert((sink.data == "999"), sink.data); 451 sink.clear(); 452 } 453 { 454 enum num = -987; 455 num.toAlphaInto(sink); 456 assert((sink.data == "-987"), sink.data); 457 sink.clear(); 458 } 459 { 460 enum num = 123; 461 num.toAlphaInto!(12, 6)(sink); 462 assert((sink.data == "000123"), sink.data); 463 sink.clear(); 464 } 465 { 466 enum num = -1; 467 num.toAlphaInto!(3, 3)(sink); 468 assert((sink.data == "-001"), sink.data); 469 sink.clear(); 470 } 471 { 472 enum num = -123_456_789_012_345L; 473 num.toAlphaInto!15(sink); 474 assert((sink.data == "-123456789012345"), sink.data); 475 sink.clear(); 476 } 477 { 478 enum num = long.min; 479 num.toAlphaInto(sink); 480 assert((sink.data == "-9223372036854775808"), sink.data); 481 //sink.clear(); 482 } 483 } 484 485 486 // toAlpha 487 /++ 488 Translates an integer into an alphanumeric string. Assumes ASCII. 489 Overload that returns the string. 490 491 Merely leverages [toAlphaInto]. 492 493 Example: 494 --- 495 int num = 12345; 496 string asString = num.toAlpha; 497 assert(asString == "12345"); 498 assert(asString == num.to!string); 499 --- 500 501 Params: 502 maxDigits = The maximum number of digits to expect input of. 503 leadingZeroes = The minimum amount of leading zeroes to include in the 504 output, mirroring the format specifier "`%0nd`". 505 num = Integer to translate into string. 506 507 Returns: 508 The passed integer `num` in string form. 509 +/ 510 string toAlpha(size_t maxDigits = 19, uint leadingZeroes = 0, Num)(const Num num) pure 511 { 512 import std.array : Appender; 513 514 Appender!(char[]) sink; 515 sink.reserve((num >= 0) ? maxDigits : maxDigits+1); 516 num.toAlphaInto!(maxDigits, leadingZeroes, Num)(sink); 517 return sink.data; 518 } 519 520 /// 521 unittest 522 { 523 { 524 enum num = 123_456; 525 immutable translated = num.toAlpha; 526 assert((translated == "123456"), translated); 527 } 528 { 529 enum num = 0; 530 immutable translated = num.toAlpha; 531 assert((translated == "0"), translated); 532 } 533 { 534 enum num = 999; 535 immutable translated = num.toAlpha; 536 assert((translated == "999"), translated); 537 } 538 { 539 enum num = -987; 540 immutable translated = num.toAlpha; 541 assert((translated == "-987"), translated); 542 } 543 { 544 enum num = 123; 545 immutable translated = num.toAlpha!(12, 6); 546 assert((translated == "000123"), translated); 547 } 548 { 549 enum num = -1; 550 immutable translated = num.toAlpha!(3, 3); 551 assert((translated == "-001"), translated); 552 } 553 { 554 enum num = -123_456_789_012_345L; 555 immutable translated = num.toAlpha!15; 556 assert((translated == "-123456789012345"), translated); 557 } 558 { 559 enum num = long.min; 560 immutable translated = num.toAlpha; 561 assert((translated == "-9223372036854775808"), translated); 562 } 563 }