1 /++
2 Enum utilities.
3 
4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
5 Authors: Ilya Yaroshenko 
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;
229     }
230     else
231     {
232         return enumMembers!T[index];
233     }
234 }
235 
236 /++
237 +/
238 template getEnumIndexFromKey(T, bool caseInsesetive = true, getKeysTemplate...)
239     if (is(T == enum) && getKeysTemplate.length <= 1)
240 {
241     ///
242     bool getEnumIndexFromKey(C)(scope const(C)[] key, ref uint index)
243         @safe pure nothrow @nogc
244         if (is(C == char) || is(C == wchar) || is(C == dchar))
245     {
246         import mir.string_table;
247         import mir.utility: simpleSort, _expect;
248         import std.traits: EnumMembers;
249         import std.meta: staticIndexOf;
250 
251         alias String = immutable(C)[];
252 
253         static if (getKeysTemplate.length)
254         {
255             alias keysOfImpl = getKeysTemplate[0];
256             enum String[] keysOf(alias symbol) = keysOfImpl!symbol;
257         }
258         else
259         static if (is(T : W[], W))
260             enum String[1] keysOf(alias symbol) = [cast(String)symbol];
261         else
262             enum String[1] keysOf(alias symbol) = [__traits(identifier, symbol)];
263 
264         enum keys = () {
265             String[] keys;
266             foreach(i, member; EnumMembers!T)
267                 keys ~= keysOf!(EnumMembers!T[i]);
268             return keys;
269         } ();
270 
271         static if (keys.length == 0)
272         {
273             return false;
274         }
275         else
276         {
277             enum indexLength = keys.length + 1;
278             alias ct = createTable!C;
279             static immutable table = ct!(keys, caseInsesetive);
280             static immutable indices = ()
281             {
282                 minimalSignedIndexType!indexLength[indexLength] indices;
283 
284                 foreach (i, member; EnumMembers!T)
285                 foreach (key; keysOf!(EnumMembers!T[i]))
286                 {
287                     static if (caseInsesetive)
288                     {
289                         key = key.dup.fastToUpperInPlace;
290                     }
291                     indices[table[key]] = i;
292                 }
293 
294                 return indices;
295             } ();
296 
297             static if (!caseInsesetive && keys.length <= 6 && keys[$ - 1].length <= 32)
298             {
299                 
300             }
301             else
302             {
303                 
304             }
305 
306             uint stringId = void;
307             if (_expect(table.get(key, stringId), true))
308             {
309                 index = indices[stringId];
310                 return true;
311             }
312             return false;
313         }
314     }
315 }