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