1 /++ 2 Enum utilities. 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 Authors: Ilia Ki 6 Macros: 7 +/ 8 module mir.enums; 9 10 private bool hasSeqGrow(T)(T[] elems) 11 if (__traits(isIntegral, T)) 12 { 13 assert(elems.length); 14 auto min = elems[0]; 15 foreach (i, e; elems) 16 if (i != e - min) 17 return false; 18 return true; 19 } 20 21 /++ 22 Enum index that corresponds of the list returned by `std.traits.EnumMembers`. 23 Returns: 24 enum member position index in the enum definition that corresponds the `value`. 25 +/ 26 bool getEnumIndex(T)(const T value, ref uint index) 27 @safe pure nothrow @nogc 28 if (is(T == enum)) 29 { 30 import std.traits: EnumMembers, isSomeString; 31 import mir.utility: _expect; 32 33 static if (__traits(isFloating, T)) 34 { 35 // TODO: index based binary searach 36 foreach (i, member; enumMembers!T) 37 { 38 if (value == member) 39 { 40 index = cast(uint) i; 41 return true; 42 } 43 } 44 return false; 45 } 46 else 47 static if (!__traits(isIntegral, T)) //strings 48 { 49 enum string[1] stringEnumValue(alias symbol) = [symbol]; 50 return getEnumIndexFromKey!(T, false, stringEnumValue)(value, index); 51 } 52 else 53 static if (hasSeqGrow(enumMembers!T)) 54 { 55 import std.traits: Unsigned; 56 const shifted = cast(Unsigned!(typeof(value - T.min)))(value - T.min); 57 if (_expect(shifted < enumMembers!T.length, true)) 58 { 59 index = cast(uint) shifted; 60 return true; 61 } 62 return false; 63 } 64 else 65 static if (is(T : bool)) 66 { 67 index = !value; 68 return true; 69 } 70 else 71 { 72 import std.traits: Unsigned; 73 alias U = Unsigned!(typeof(T.max - T.min + 1)); 74 75 enum length = cast(size_t)cast(U)(T.max - T.min + 1); 76 77 const shifted = cast(size_t)cast(U)(value - T.min); 78 79 static if (length <= 255) 80 { 81 static immutable ubyte[length] table = (){ 82 ubyte[length] ret; 83 foreach (i, member; enumMembers!T) 84 { 85 ret[member - T.min] = cast(ubyte)(i + 1); 86 } 87 return ret; 88 }(); 89 90 if (_expect(shifted < length, true)) 91 { 92 int id = table[shifted] - 1; 93 if (_expect(id >= 0, true)) 94 { 95 index = id; 96 return true; 97 } 98 } 99 return false; 100 } 101 else 102 { 103 switch (value) 104 { 105 foreach (i, member; EnumMembers!T) 106 { 107 case member: 108 index = i; 109 return true; 110 } 111 default: return false; 112 } 113 } 114 } 115 } 116 117 /// 118 @safe pure nothrow @nogc 119 version(mir_core_test) unittest 120 { 121 import std.meta: AliasSeq; 122 123 enum Common { a, b, c } 124 enum Reversed { a = 1, b = 0, c = -1 } 125 enum Shifted { a = -4, b, c } 126 enum Small { a = -4, b, c = 10 } 127 enum Big { a = -4, b, c = 1000 } 128 enum InverseBool { True = true, False = false } 129 enum FP : float { a = -4, b, c } 130 enum S : string { a = "а", b = "б", c = "ц" } 131 132 uint index = -1; 133 foreach (E; AliasSeq!(Common, Reversed, Shifted, Small, Big, FP, S)) 134 { 135 assert(getEnumIndex(E.a, index) && index == 0); 136 assert(getEnumIndex(E.b, index) && index == 1); 137 assert(getEnumIndex(E.c, index) && index == 2); 138 } 139 140 assert(getEnumIndex(InverseBool.True, index) && index == 0); 141 assert(getEnumIndex(InverseBool.False, index) && index == 1); 142 } 143 144 /++ 145 Static immutable instance of `[std.traits.EnumMembers!T]`. 146 +/ 147 template enumMembers(T) 148 if (is(T == enum)) 149 { 150 import std.traits: EnumMembers; 151 /// 152 static immutable T[EnumMembers!T.length] enumMembers = [EnumMembers!T]; 153 } 154 155 /// 156 version(mir_core_test) unittest 157 { 158 enum E {a = 1, b = -1, c} 159 static assert(enumMembers!E == [E.a, E.b, E.c]); 160 } 161 162 /++ 163 Static immutable instance of Enum Identifiers. 164 +/ 165 template enumIdentifiers(T) 166 if (is(T == enum)) 167 { 168 import std.traits: EnumMembers; 169 static immutable string[EnumMembers!T.length] enumIdentifiers = () { 170 string[EnumMembers!T.length] identifiers; 171 static foreach(i, member; EnumMembers!T) 172 identifiers[i] = __traits(identifier, EnumMembers!T[i]); 173 return identifiers; 174 } (); 175 } 176 177 /// 178 version(mir_core_test) unittest 179 { 180 enum E {z = 1, b = -1, c} 181 static assert(enumIdentifiers!E == ["z", "b", "c"]); 182 } 183 184 /++ 185 Aliases itself to $(LREF enumMembers) for string enums and 186 $(LREF enumIdentifiers) for integral and floating point enums. 187 +/ 188 template enumStrings(T) 189 if (is(T == enum)) 190 { 191 static if (is(T : C[], C)) 192 alias enumStrings = enumMembers!T; 193 else 194 alias enumStrings = enumIdentifiers!T; 195 } 196 197 /// 198 version(mir_core_test) unittest 199 { 200 enum E {z = 1, b = -1, c} 201 static assert(enumStrings!E == ["z", "b", "c"]); 202 203 enum S {a = "A", b = "B", c = ""} 204 static assert(enumStrings!S == [S.a, S.b, S.c]); 205 } 206 207 /++ 208 Params: 209 index = enum index `std.traits.EnumMembers!T` 210 Returns: 211 A enum value that corresponds to the index. 212 Note: 213 The function doesn't check that index is less then `EnumMembers!T.length`. 214 +/ 215 T unsafeEnumFromIndex(T)(size_t index) 216 @trusted pure nothrow @nogc 217 if (is(T == enum)) 218 { 219 static if (__traits(isIntegral, T)) 220 enum bool fastConv = hasSeqGrow(enumMembers!T); 221 else 222 enum bool fastConv = false; 223 224 assert(index < enumMembers!T.length); 225 226 static if (fastConv) 227 { 228 return cast(T) (index + enumMembers!T[0]); 229 } 230 else 231 { 232 return enumMembers!T[index]; 233 } 234 } 235 236 /// 237 version(mir_core_test) 238 unittest 239 { 240 enum Linear 241 { 242 one = 1, 243 two = 2 244 } 245 246 static assert(is(typeof(unsafeEnumFromIndex!Linear(0)) == Linear)); 247 assert(unsafeEnumFromIndex!Linear(0) == Linear.one); 248 assert(unsafeEnumFromIndex!Linear(1) == Linear.two); 249 250 enum Mixed 251 { 252 one = 1, 253 oneAgain = 1, 254 two = 2 255 } 256 257 assert(unsafeEnumFromIndex!Mixed(0) == Mixed.one); 258 assert(unsafeEnumFromIndex!Mixed(1) == Mixed.one); 259 assert(unsafeEnumFromIndex!Mixed(2) == Mixed.two); 260 } 261 262 /++ 263 Params: 264 T = enum type to introspect 265 key = some string that corresponds to some key name of the given enum 266 index = resulting enum index if this method returns true. 267 Returns: 268 boolean whether the key was found in the enum keys and if so, index is set. 269 +/ 270 template getEnumIndexFromKey(T, bool caseInsensitive = true, getKeysTemplate...) 271 if (is(T == enum) && getKeysTemplate.length <= 1) 272 { 273 /// 274 bool getEnumIndexFromKey(C)(scope const(C)[] key, ref uint index) 275 @safe pure nothrow @nogc 276 if (is(C == char) || is(C == wchar) || is(C == dchar)) 277 { 278 import mir.string_table; 279 import mir.utility: simpleSort, _expect; 280 import std.traits: EnumMembers; 281 import std.meta: staticIndexOf; 282 283 alias String = immutable(C)[]; 284 285 static if (getKeysTemplate.length) 286 { 287 alias keysOfImpl = getKeysTemplate[0]; 288 enum String[] keysOf(alias symbol) = keysOfImpl!symbol; 289 } 290 else 291 static if (is(T : W[], W)) 292 enum String[1] keysOf(alias symbol) = [cast(String)symbol]; 293 else 294 enum String[1] keysOf(alias symbol) = [__traits(identifier, symbol)]; 295 296 enum keys = () { 297 String[] keys; 298 foreach(i, member; EnumMembers!T) 299 keys ~= keysOf!(EnumMembers!T[i]); 300 return keys; 301 } (); 302 303 static if (keys.length == 0) 304 { 305 return false; 306 } 307 else 308 { 309 enum indexLength = keys.length + 1; 310 alias ct = createTable!C; 311 static immutable table = ct!(keys, caseInsensitive); 312 static immutable indices = () 313 { 314 minimalSignedIndexType!indexLength[indexLength] indices; 315 316 foreach (i, member; EnumMembers!T) 317 foreach (key; keysOf!(EnumMembers!T[i])) 318 { 319 static if (caseInsensitive) 320 { 321 key = key.dup.fastToUpperInPlace; 322 } 323 indices[table[key]] = i; 324 } 325 326 return indices; 327 } (); 328 329 uint stringId = void; 330 if (_expect(table.get(key, stringId), true)) 331 { 332 index = indices[stringId]; 333 return true; 334 } 335 return false; 336 } 337 } 338 } 339 340 /// 341 unittest 342 { 343 enum Short 344 { 345 hello, 346 world 347 } 348 349 enum Long 350 { 351 This, 352 Is, 353 An, 354 Enum, 355 With, 356 Lots, 357 Of, 358 Very, 359 Long, 360 EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm 361 } 362 363 uint i; 364 assert(getEnumIndexFromKey!Short("hello", i)); 365 assert(i == 0); 366 assert(getEnumIndexFromKey!Short("world", i)); 367 assert(i == 1); 368 assert(!getEnumIndexFromKey!Short("foo", i)); 369 370 assert(getEnumIndexFromKey!Short("HeLlO", i)); 371 assert(i == 0); 372 assert(getEnumIndexFromKey!Short("WoRLd", i)); 373 assert(i == 1); 374 375 assert(!getEnumIndexFromKey!(Short, false)("HeLlO", i)); 376 assert(!getEnumIndexFromKey!(Short, false)("WoRLd", i)); 377 378 assert(getEnumIndexFromKey!Long("Is", i)); 379 assert(i == 1); 380 assert(getEnumIndexFromKey!Long("Long", i)); 381 assert(i == 8); 382 assert(getEnumIndexFromKey!Long("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm", i)); 383 assert(i == 9); 384 assert(!getEnumIndexFromKey!Long("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCodeatm", i)); 385 386 assert(!getEnumIndexFromKey!(Long, false)("EntriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tM", i)); 387 assert(!getEnumIndexFromKey!(Long, false)("entriesThatArePartiallyAlsoVeryLongInStringLengthAsWeNeedToTestALotOfDifferentCasesThatCouldHappenInRealWorldCode_tm", i)); 388 }