1 /** Extensions to Enumerations.
2 
3     Copyright: Per Nordlöw 2018-.
4     License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
5     Authors: $(WEB Per Nordlöw)
6 
7     TODO: Implement bidirection conversions: http://forum.dlang.org/thread/tuafkxmnntqjgycziixn@forum.dlang.org#post-tuafkxmnntqjgycziixn:40forum.dlang.org
8 
9     TODO: Join logic for ChainEnum and UnionEnum into common and then define:
10     - UnionEnum: only names must be unique
11     - StrictUnionEnum: both names and values must be unique
12 
13     TODO: Move to std.typecons (Type Constructor) in Phobos when ready.
14  */
15 module nxt.enums;
16 
17 import std.meta: allSatisfy, staticMap;
18 import std.traits: EnumMembers, CommonType, OriginalType;
19 import std.conv: to;
20 import core.exception;
21 import std.exception: assertThrown;
22 
23 /* version = print; */
24 version(print) import std.stdio: writefln;
25 
26 /* Helpers */
27 private enum isEnum(T) = is(T == enum);
28 private alias CommonOriginalType(T...) = CommonType!(staticMap!(OriginalType, T));
29 
30 /** Chain (Append, Concatenate) Member Names of Enumerations $(D E).
31     All enumerator names of $(D E) must be unique.
32     See_Also: http://forum.dlang.org/thread/f9vc6p$1b7k$1@digitalmars.com
33 */
34 template ChainEnum(E...) if (E.length >= 2 &&
35                              allSatisfy!(isEnum, E) &&
36                              is(CommonOriginalType!E))
37 {
38     mixin({ string r = "enum ChainEnum { ";
39             string[string] names;   // lookup: enumName[memberName]
40             foreach (T; E)
41             {
42                 import std.range: join;
43                 foreach (m; __traits(allMembers, T))
44                 {
45                     assert(m !in names,
46                            "Enumerator " ~ T.stringof ~"."~m ~
47                            " collides with " ~ names[m] ~"."~m);
48                     names[m] = T.stringof;
49                 }
50                 r ~= [__traits(allMembers, T)].join(",") ~ ",";
51             }
52             return r ~ " }";
53         }());
54 }
55 
56 unittest
57 {
58     enum E0 { a, b, c }
59     enum E1 { e, f, g }
60     enum E2 { h, i, j }
61     alias E12 = ChainEnum!(E0, E1);
62     alias E123 = ChainEnum!(E0, E1, E2);
63     version(print)
64         foreach (immutable e; [EnumMembers!E123])
65             writefln("E123.%s: %d", e, e);
66 }
67 
68 /** Unite (Join) Members (both their Names and Values) of Enumerations $(D E).
69     All enumerator names and values of $(D E) must be unique.
70  */
71 template UnionEnum(E...) if (E.length >= 2 &&
72                              allSatisfy!(isEnum, E) &&
73                              is(CommonOriginalType!E))
74 {
75     mixin({
76             string r = "enum UnionEnum { ";
77             alias O = CommonOriginalType!E;
78             string[string] names;   // lookup: enumName[memberName]
79             string[O] values;
80             foreach (ix, T; E)
81             {
82                 foreach (m; EnumMembers!T) // foreach member
83                 {
84                     // name
85                     enum n = to!string(m);
86                     assert(n !in names,
87                            "Template argument E[" ~ to!string(ix)~
88                            "]'s enumerator name " ~ T.stringof ~"."~n ~
89                            " collides with " ~ names[n] ~"."~n);
90                     names[n] = T.stringof;
91 
92                     // value
93                     enum v = to!O(m);
94                     assert(v !in values,
95                            "Template argument E[" ~ to!string(ix)~
96                            "]'s enumerator value " ~ T.stringof ~"."~n ~" == "~ to!string(v) ~
97                            " collides with member value of " ~ values[v]);
98                     values[v] = T.stringof;
99 
100                     r ~= to!string(n) ~ "=" ~ to!string(v) ~ ",";
101                 }
102             }
103             return r ~ " }";
104         }());
105 }
106 
107 /** Instance Wrapper for UnionEnum.
108     Provides safe assignment and explicit casts.
109     TODO: Use opImplicitCastTo instead of opCast when it becomes available in DMD.
110 */
111 struct EnumUnion(E...)
112 {
113     alias OriginalType = CommonOriginalType!E;
114     alias U = UnionEnum!(E);    // Wrapped Type.
115     alias _value this;
116 
117     @safe pure nothrow:
118 
119     import std.conv : asOriginalType;
120 
121     static if (E.length >= 1)
122     {
123         void opAssign(E[0] e) { _value = cast(U)e; }
124         E[0] opCast(T : E[0])() const
125         {
126             bool match = false;
127             foreach (m; EnumMembers!(E[0]))
128             {
129                 if (m.asOriginalType == _value.asOriginalType)
130                 {
131                     match = true;
132                 }
133             }
134             version(assert) if (!match) { throw new RangeError(); }
135             return cast(E[0])_value;
136         }
137     }
138     static if (E.length >= 2)
139     {
140         void opAssign(E[1] e) { _value = cast(U)e; }
141         E[1] opCast(T : E[1])() const
142         {
143             bool match = false;
144             foreach (m; EnumMembers!(E[1]))
145             {
146                 if (m.asOriginalType == _value.asOriginalType)
147                 {
148                     match = true;
149                 }
150             }
151             version(assert) if (!match) { throw new RangeError(); }
152             return cast(E[1])_value;
153         }
154     }
155 
156     /* TODO: Use (static) foreach here when it becomes available. */
157     /* foreach (ix, E0; E) */
158     /* { */
159     /* } */
160     static if (E.length >= 3) void opAssign(E[2] e) { _value = cast(U)e; }
161     static if (E.length >= 4) void opAssign(E[3] e) { _value = cast(U)e; }
162     static if (E.length >= 5) void opAssign(E[4] e) { _value = cast(U)e; }
163     static if (E.length >= 6) void opAssign(E[5] e) { _value = cast(U)e; }
164     static if (E.length >= 7) void opAssign(E[6] e) { _value = cast(U)e; }
165     static if (E.length >= 8) void opAssign(E[7] e) { _value = cast(U)e; }
166     static if (E.length >= 9) void opAssign(E[8] e) { _value = cast(U)e; }
167 
168     /* ====================== */
169     /* TODO: Why doesn't the following mixin templates have an effect? */
170     version(linux)
171     {
172         mixin template genOpAssign(uint i)
173         {
174             static if (i == 0)
175                 auto fortytwo() { return 42; }
176             void opAssign(E[i] e)
177             {
178                 _value = cast(U)e;
179             }
180         }
181         mixin template genOpCast(uint i)
182         {
183             E[i] opCast(T : E[i])() const
184             {
185                 bool match = false;
186                 foreach (m; EnumMembers!(E[i]))
187                 {
188                     if (m == _value)
189                     {
190                         match = true;
191                     }
192                 }
193                 version(assert) if (!match) { throw new RangeError(); }
194                 return cast(E[i])_value;
195             }
196         }
197         /* TODO: Alternative to this set of static if? */
198         static if (E.length >= 1) { mixin genOpAssign!0; mixin genOpCast!0; }
199         static if (E.length >= 2) { mixin genOpAssign!1; mixin genOpCast!1; }
200         static if (E.length >= 3) { mixin genOpAssign!2; mixin genOpCast!2; }
201         static if (E.length >= 4) { mixin genOpAssign!3; mixin genOpCast!3; }
202         static if (E.length >= 5) { mixin genOpAssign!4; mixin genOpCast!4; }
203         static if (E.length >= 6) { mixin genOpAssign!5; mixin genOpCast!5; }
204         static if (E.length >= 7) { mixin genOpAssign!6; mixin genOpCast!6; }
205         static if (E.length >= 8) { mixin genOpAssign!7; mixin genOpCast!7; }
206         static if (E.length >= 9) { mixin genOpAssign!8; mixin genOpCast!8; }
207     }
208 
209     /* ====================== */
210 
211     private U _value;           // Instance.
212 }
213 
214 unittest
215 {
216     enum E0:ubyte  { a = 0, b = 3, c = 6 }
217     enum E1:ushort { p = 1, q = 4, r = 7 }
218     enum E2:uint   { x = 2, y = 5, z = 8 }
219 
220     alias EU = EnumUnion!(E0, E1, E2);
221     EU eu;
222     static assert(is(EU.OriginalType == uint));
223 
224     version(print)
225         foreach (immutable e; [EnumMembers!(typeof(eu._value))])
226             writefln("E123.%s: %d", e, e);
227 
228     auto e0 = E0.max;
229 
230     eu = e0;                    // checked at compile-time
231     assert(eu == E0.max);
232 
233     e0 = cast(E0)eu;            // run-time check is ok
234     assertThrown!RangeError(cast(E1)eu);// run-time check should fail
235 
236     enum Ex:uint { x = 2, y = 5, z = 8 }
237     static assert(!__traits(compiles, { Ex ex = Ex.max; eu = ex; } ));
238 
239     /* check for compilation failures */
240     enum D1 { a = 0, b = 3, c = 6 }
241     static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D1); } ), "Should give name and value collision");
242     enum D2 { a = 1, b = 4, c = 7 }
243     static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D2); } ), "Should give name collision");
244     enum D3 { x = 0, y = 3, z = 6 }
245     static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D3); } ),  "Should give value collision");
246 }