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 }