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 }