The OpenD Programming Language

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 }