1 /** Extensions to Enumerations.
2 
3 	Copyright: Per Nordlöw 2022-.
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 	mixin({ string r = "enum ChainEnum { ";
38 			string[string] names;   // lookup: enumName[memberName]
39 			foreach (T; E) {
40 				import std.range: join;
41 				foreach (m; __traits(allMembers, T)) {
42 					assert(m !in names,
43 						   "Enumerator " ~ T.stringof ~"."~m ~
44 						   " collides with " ~ names[m] ~"."~m);
45 					names[m] = T.stringof;
46 				}
47 				r ~= [__traits(allMembers, T)].join(",") ~ ",";
48 			}
49 			return r ~ " }";
50 		}());
51 }
52 
53 unittest {
54 	enum E0 { a, b, c }
55 	enum E1 { e, f, g }
56 	enum E2 { h, i, j }
57 	alias E12 = ChainEnum!(E0, E1);
58 	alias E123 = ChainEnum!(E0, E1, E2);
59 	version (print)
60 		foreach (immutable e; [EnumMembers!E123])
61 			writefln("E123.%s: %d", e, e);
62 }
63 
64 /** Unite (Join) Members (both their Names and Values) of Enumerations $(D E).
65 	All enumerator names and values of $(D E) must be unique.
66  */
67 template UnionEnum(E...) if (E.length >= 2 &&
68 							 allSatisfy!(isEnum, E) &&
69 							 is(CommonOriginalType!E)) {
70 	mixin({
71 			string r = "enum UnionEnum { ";
72 			alias O = CommonOriginalType!E;
73 			string[string] names;   // lookup: enumName[memberName]
74 			string[O] values;
75 			foreach (ix, T; E) {
76 				foreach (m; EnumMembers!T) // foreach member
77 				{
78 					// name
79 					enum n = to!string(m);
80 					assert(n !in names,
81 						   "Template argument E[" ~ to!string(ix)~
82 						   "]'s enumerator name " ~ T.stringof ~"."~n ~
83 						   " collides with " ~ names[n] ~"."~n);
84 					names[n] = T.stringof;
85 
86 					// value
87 					enum v = to!O(m);
88 					assert(v !in values,
89 						   "Template argument E[" ~ to!string(ix)~
90 						   "]'s enumerator value " ~ T.stringof ~"."~n ~" == "~ to!string(v) ~
91 						   " collides with member value of " ~ values[v]);
92 					values[v] = T.stringof;
93 
94 					r ~= to!string(n) ~ "=" ~ to!string(v) ~ ",";
95 				}
96 			}
97 			return r ~ " }";
98 		}());
99 }
100 
101 /** Instance Wrapper for UnionEnum.
102 	Provides safe assignment and explicit casts.
103 	TODO: Use opImplicitCastTo instead of opCast when it becomes available in DMD.
104 */
105 struct EnumUnion(E...) {
106 	alias OriginalType = CommonOriginalType!E;
107 	alias U = UnionEnum!(E);	// Wrapped Type.
108 	alias _value this;
109 
110 	@safe pure nothrow:
111 
112 	import std.conv : asOriginalType;
113 
114 	static if (E.length >= 1) {
115 		void opAssign(E[0] e) { _value = cast(U)e; }
116 		E[0] opCast(T : E[0])() const
117 		{
118 			bool match = false;
119 			foreach (m; EnumMembers!(E[0])) {
120 				if (m.asOriginalType == _value.asOriginalType) {
121 					match = true;
122 				}
123 			}
124 			version (assert) if (!match) { throw new RangeError(); }
125 			return cast(E[0])_value;
126 		}
127 	}
128 	static if (E.length >= 2) {
129 		void opAssign(E[1] e) { _value = cast(U)e; }
130 		E[1] opCast(T : E[1])() const
131 		{
132 			bool match = false;
133 			foreach (m; EnumMembers!(E[1])) {
134 				if (m.asOriginalType == _value.asOriginalType) {
135 					match = true;
136 				}
137 			}
138 			version (assert) if (!match) { throw new RangeError(); }
139 			return cast(E[1])_value;
140 		}
141 	}
142 
143 	/* TODO: Use (static) foreach here when it becomes available. */
144 	/* foreach (ix, E0; E) */
145 	/* { */
146 	/* } */
147 	static if (E.length >= 3) void opAssign(E[2] e) { _value = cast(U)e; }
148 	static if (E.length >= 4) void opAssign(E[3] e) { _value = cast(U)e; }
149 	static if (E.length >= 5) void opAssign(E[4] e) { _value = cast(U)e; }
150 	static if (E.length >= 6) void opAssign(E[5] e) { _value = cast(U)e; }
151 	static if (E.length >= 7) void opAssign(E[6] e) { _value = cast(U)e; }
152 	static if (E.length >= 8) void opAssign(E[7] e) { _value = cast(U)e; }
153 	static if (E.length >= 9) void opAssign(E[8] e) { _value = cast(U)e; }
154 
155 	/* ====================== */
156 	/* TODO: Why doesn't the following mixin templates have an effect? */
157 	version (linux) {
158 		mixin template genOpAssign(uint i) {
159 			static if (i == 0)
160 				auto fortytwo() { return 42; }
161 			void opAssign(E[i] e) {
162 				_value = cast(U)e;
163 			}
164 		}
165 		mixin template genOpCast(uint i) {
166 			E[i] opCast(T : E[i])() const
167 			{
168 				bool match = false;
169 				foreach (m; EnumMembers!(E[i])) {
170 					if (m == _value) {
171 						match = true;
172 					}
173 				}
174 				version (assert) if (!match) { throw new RangeError(); }
175 				return cast(E[i])_value;
176 			}
177 		}
178 		/* TODO: Alternative to this set of static if? */
179 		static if (E.length >= 1) { mixin genOpAssign!0; mixin genOpCast!0; }
180 		static if (E.length >= 2) { mixin genOpAssign!1; mixin genOpCast!1; }
181 		static if (E.length >= 3) { mixin genOpAssign!2; mixin genOpCast!2; }
182 		static if (E.length >= 4) { mixin genOpAssign!3; mixin genOpCast!3; }
183 		static if (E.length >= 5) { mixin genOpAssign!4; mixin genOpCast!4; }
184 		static if (E.length >= 6) { mixin genOpAssign!5; mixin genOpCast!5; }
185 		static if (E.length >= 7) { mixin genOpAssign!6; mixin genOpCast!6; }
186 		static if (E.length >= 8) { mixin genOpAssign!7; mixin genOpCast!7; }
187 		static if (E.length >= 9) { mixin genOpAssign!8; mixin genOpCast!8; }
188 	}
189 
190 	/* ====================== */
191 
192 	private U _value;		   // Instance.
193 }
194 
195 unittest {
196 	enum E0:ubyte  { a = 0, b = 3, c = 6 }
197 	enum E1:ushort { p = 1, q = 4, r = 7 }
198 	enum E2:uint   { x = 2, y = 5, z = 8 }
199 
200 	alias EU = EnumUnion!(E0, E1, E2);
201 	EU eu;
202 	static assert(is(EU.OriginalType == uint));
203 
204 	version (print)
205 		foreach (immutable e; [EnumMembers!(typeof(eu._value))])
206 			writefln("E123.%s: %d", e, e);
207 
208 	auto e0 = E0.max;
209 
210 	eu = e0;					// checked at compile-time
211 	assert(eu == E0.max);
212 
213 	e0 = cast(E0)eu;			// run-time check is ok
214 	assertThrown!RangeError(cast(E1)eu);// run-time check should fail
215 
216 	enum Ex:uint { x = 2, y = 5, z = 8 }
217 	static assert(!__traits(compiles, { Ex ex = Ex.max; eu = ex; } ));
218 
219 	/* check for compilation failures */
220 	enum D1 { a = 0, b = 3, c = 6 }
221 	static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D1); } ), "Should give name and value collision");
222 	enum D2 { a = 1, b = 4, c = 7 }
223 	static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D2); } ), "Should give name collision");
224 	enum D3 { x = 0, y = 3, z = 6 }
225 	static assert(!__traits(compiles, { alias ED = UnionEnum!(E0, D3); } ),  "Should give value collision");
226 }