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 }