1 /*******************************************************************************
2 
3     Mixin for an enum class with the following basic features:
4         * Contains an enum, called E, with members specified by an associative
5           array passed to the mixin.
6         * Implements an interface, IEnum, with common shared methods:
7             * opIndex: look up an enum member's name by its value and
8               vice-versa.
9             * opIn_r: check whether a value (int) or name (char[]) is a member
10               of the enum.
11             * opApply: iteration over over the names & values of the enum's
12               members.
13             * length: returns the number of members in the enum.
14             * min & max: return the minimum/maximum value of the enum's members.
15         * A static opCall() method which returns a singleton instance of the
16           class. This is the most convenient means of calling the methods listed
17           above.
18 
19     Basic usage example:
20 
21     ---
22 
23         // Define enum class by implementing IEnum and mixing in EnumBase with
24         // an associative array defining the enum members
25         class Commands : IEnum
26         {
27             // Note: the [] after the first string ensures that the associative
28             // array is of type int[char[]], not int[char[3]].
29             mixin EnumBase!([
30                 "Get"[]:1,
31                 "Put":2,
32                 "Remove":3
33             ]);
34         }
35 
36         // Look up enum member names by value. (Note that the singleton instance
37         // of the enum class is passed, using the static opCall method.)
38         assert(Commands()["Get"] == 1);
39 
40         // Look up enum member values by name
41         assert(Commands()[1] == "Get");
42 
43         // Check whether a value is in the enum
44         assert(!(5 in Commands()));
45 
46         // Check whether a name is in the enum
47         assert(!("Delete" in Commands()));
48 
49         // Iterate over enum members
50         import ocean.io.Stdout;
51 
52         foreach ( n, v; Commands() )
53         {
54             Stdout.formatln("{}: {}", n, v);
55         }
56 
57     ---
58 
59     The mixin also supports the following more advanced features:
60         * One enum class can be inherited from another, using standard class
61           inheritance. The enum members in a derived enum class extend those of
62           the super class.
63         * The use of normal class inheritance, along with the IEnum interface,
64           allows enum classes to be used abstractly.
65 
66     Advanced usage example:
67 
68     ---
69 
70         import ocean.core.Enum;
71 
72         // Basic enum class
73         class BasicCommands : IEnum
74         {
75             mixin EnumBase!([
76                 "Get"[]:1,
77                 "Put":2,
78                 "Remove":3
79             ]);
80         }
81 
82         // Inherited enum class
83         class ExtendedCommands : BasicCommands
84         {
85             mixin EnumBase!([
86                 "GetAll"[]:4,
87                 "RemoveAll":5
88             ]);
89         }
90 
91         // Check for a few names.
92         assert("Get" in BasicCommands());
93         assert("Get" in ExtendedCommands());
94         assert(!("GetAll" in BasicCommands()));
95         assert("GetAll" in ExtendedCommands());
96 
97         // Example of abstract usage of enum classes
98         import ocean.io.Stdout;
99 
100         void printEnumMembers ( IEnum e )
101         {
102             foreach ( n, v; e )
103             {
104                 Stdout.formatln("{}: {}", n, v);
105             }
106         }
107 
108         printEnumMembers(BasicCommands());
109         printEnumMembers(ExtendedCommands());
110 
111     ---
112 
113     TODO: does it matter that the enum values are always int? We could add a
114     template parameter to specify the base type, but I think it'd be a shame to
115     make things more complex. IEnum would have to become a template then.
116 
117     Copyright:
118         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
119         All rights reserved.
120 
121     License:
122         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
123         Alternatively, this file may be distributed under the terms of the Tango
124         3-Clause BSD License (see LICENSE_BSD.txt for details).
125 
126 *******************************************************************************/
127 
128 module ocean.core.Enum;
129 
130 
131 
132 import ocean.meta.types.Qualifiers;
133 import ocean.core.Test;
134 
135 /*******************************************************************************
136 
137     Interface defining the basic functionality of an enum class.
138 
139 *******************************************************************************/
140 
141 public interface IEnum
142 {
143     /***************************************************************************
144 
145         Aliases for the types of an enum class' names & values.
146 
147     ***************************************************************************/
148 
149     public alias istring Name;
150     public alias int Value;
151 
152 
153     /***************************************************************************
154 
155         Looks up an enum member's name from its value.
156 
157         Params:
158             v = value to look up
159 
160         Returns:
161             pointer to corresponding name, or null if value doesn't exist in
162             enum
163 
164     ***************************************************************************/
165 
166     public Name* opIn_r ( Value v );
167 
168 
169     /***************************************************************************
170 
171         Looks up an enum member's value from its name.
172 
173         Params:
174             n = name to look up
175 
176         Returns:
177             pointer to corresponding value, or null if name doesn't exist in
178             enum
179 
180     ***************************************************************************/
181 
182     public Value* opIn_r ( Name n );
183 
184 
185     /***************************************************************************
186 
187         Support for the 'in' operator
188 
189         Aliased to opIn_r, for backwards compatibility
190 
191     ***************************************************************************/
192 
193     public alias opBinaryRight ( istring op : "in" ) = opIn_r;
194 
195 
196     /***************************************************************************
197 
198         Looks up an enum member's name from its value, using opIndex.
199 
200         Params:
201             v = value to look up
202 
203         Returns:
204             corresponding name
205 
206         Throws:
207             ArrayBoundsException if value doesn't exist in enum
208 
209     ***************************************************************************/
210 
211     public Name opIndex ( Value v );
212 
213 
214     /***************************************************************************
215 
216         Looks up an enum member's value from its name, using opIndex.
217 
218         Params:
219             n = name to look up
220 
221         Returns:
222             corresponding value
223 
224         Throws:
225             ArrayBoundsException if name doesn't exist in enum
226 
227     ***************************************************************************/
228 
229     public Value opIndex ( Name n );
230 
231 
232     /***************************************************************************
233 
234         Returns:
235             the number of members in the enum
236 
237     ***************************************************************************/
238 
239     public size_t length ( );
240 
241 
242     /***************************************************************************
243 
244         Returns:
245             the lowest value in the enum
246 
247     ***************************************************************************/
248 
249     Value min ( );
250 
251 
252     /***************************************************************************
253 
254         Returns:
255             the highest value in the enum
256 
257     ***************************************************************************/
258 
259     Value max ( );
260 
261 
262     /***************************************************************************
263 
264         foreach iteration over the names and values in the enum.
265 
266     ***************************************************************************/
267 
268     public int opApply ( scope int delegate ( ref const(Name) name,
269         ref const(Value) value ) dg );
270 
271 
272     /***************************************************************************
273 
274         foreach iteration over the names and values in the enum and their
275         indices.
276 
277     ***************************************************************************/
278 
279     public int opApply ( scope int delegate ( ref size_t i, ref const(Name) name,
280         ref const(Value) value ) dg );
281 }
282 
283 
284 /*******************************************************************************
285 
286     Template which evaluates to a string containing the code for a list of enum
287     members, as specified by the first two members of the passed tuple, which
288     must be an array of strings and an array of integers, respectively. The
289     strings specify the names of the enum members, and the integers their
290     values.
291 
292     This template is public for technical reason, and should not be needed
293     in client code - See IEnum and EnumBase for template / interface you
294     should use.
295 
296     Params:
297         T = tuple:
298             T[0] must be an array of strings
299             T[1] must be an array of ints
300         (Note that the template accepts a tuple purely as a workaround for the
301         compiler's inability to handle templates which accept values of types
302         such as char[][] and int[].)
303 
304 *******************************************************************************/
305 
306 public template EnumValues ( size_t i, T ... )
307 {
308     static assert(T.length == 2);
309     static assert(is(typeof(T[0]) : const(istring[])));
310     static assert(is(typeof(T[1]) : const(int[])));
311 
312     static if ( i == T[0].length - 1 )
313     {
314         static immutable EnumValues = T[0][i] ~ "=" ~ T[1][i].stringof;
315     }
316     else
317     {
318         static immutable EnumValues = T[0][i] ~ "=" ~ T[1][i].stringof ~ ","
319             ~ EnumValues!(i + 1, T);
320     }
321 }
322 
323 
324 /*******************************************************************************
325 
326     Template which evaluates to a size_t corresponding to the index in the type
327     tuple T which contains a class implementing the IEnum interface. If no such
328     type exists in T, then the template evaluates to T.length.
329 
330     This template is public for technical reason, and should not be needed
331     in client code - See IEnum and EnumBase for template / interface you
332     should use.
333 
334     Params:
335         i = recursion index over T
336         T = type tuple
337 
338 *******************************************************************************/
339 
340 public template SuperClassIndex ( size_t i, T ... )
341 {
342     static if ( i == T.length )
343     {
344         static immutable size_t SuperClassIndex = i;
345     }
346     else
347     {
348         static if ( is(T[i] == class) && is(T[i] : IEnum) )
349         {
350             static immutable size_t SuperClassIndex = i;
351         }
352         else
353         {
354             static immutable size_t SuperClassIndex = SuperClassIndex!(i + 1, T);
355         }
356     }
357 }
358 
359 
360 /*******************************************************************************
361 
362     Template mixin to add enum functionality to a class.
363 
364     Note that the [0..$] which is used in places in this method is a workaround
365     for various weird compiler issues / segfaults.
366 
367     Params:
368         T = tuple:
369             T[0] must be an associative array of type int[char[]]
370         (Note that the template accepts a tuple purely as a workaround for the
371         compiler's inability to handle templates which accept associative array
372         values.)
373 
374     TODO: adapt to accept *either* an AA or a simple list of names (for an
375     auto-enum with values starting at 0).
376 
377 *******************************************************************************/
378 
379 public template EnumBase ( T ... )
380 {
381     import ocean.meta.types.Qualifiers;
382 
383     alias IEnum.Name Name;
384     alias IEnum.Value Value;
385 
386     /***************************************************************************
387 
388         Ensure that the class into which this template is mixed is an IEnum.
389 
390     ***************************************************************************/
391 
392     static assert(is(typeof(this) : IEnum));
393 
394 
395     /***************************************************************************
396 
397         Ensure that the tuple T contains a single element which is of type
398         int[char[]].
399 
400     ***************************************************************************/
401 
402     static assert(T.length == 1);
403     static assert(is(typeof(T[0].keys) : const(char[][])));
404     static assert(is(typeof(T[0].values) : const(int[])));
405 
406 
407     /***************************************************************************
408 
409         Constants determining whether this class is derived from another class
410         which implements IEnum.
411 
412     ***************************************************************************/
413 
414     static if ( is(typeof(this) S == super) )
415     {
416         private static immutable super_class_index = SuperClassIndex!(0, S);
417 
418         private static immutable is_derived_enum = super_class_index < S.length;
419     }
420     else
421     {
422         private static immutable is_derived_enum = false;
423     }
424 
425 
426     /***************************************************************************
427 
428         Constant arrays of enum member names and values.
429 
430         If the class into which this template is mixed has a super class which
431         is also an IEnum, the name and value arrays of the super class are
432         concatenated with those in the associative array in T[0].
433 
434     ***************************************************************************/
435 
436     static if ( is_derived_enum )
437     {
438         public static immutable _internal_names =
439             S[super_class_index]._internal_names[0..$] ~ T[0].keys[0..$];
440         public static immutable _internal_values =
441             S[super_class_index]._internal_values[0..$] ~ T[0].values[0..$];
442     }
443     else
444     {
445         public static immutable _internal_names = T[0].keys;
446         public static immutable _internal_values = T[0].values;
447     }
448 
449     static assert(_internal_names.length == _internal_values.length);
450 
451     private static names = _internal_names;
452     private static values = _internal_values;
453 
454     /***************************************************************************
455 
456         The actual enum, E.
457 
458     ***************************************************************************/
459 
460     mixin("enum E {" ~ EnumValues!(0, _internal_names[0..$],
461         _internal_values[0..$]) ~ "}");
462 
463 
464     /***************************************************************************
465 
466         Internal maps from names <-> values. The maps are filled in the static
467         constructor.
468 
469     ***************************************************************************/
470 
471     static protected Value[Name] n_to_v;
472     static protected Name[Value] v_to_n;
473 
474     static this ( )
475     {
476         foreach ( i, n; names )
477         {
478             n_to_v[n] = values[i];
479         }
480         n_to_v.rehash;
481 
482         foreach ( i, v; values )
483         {
484             v_to_n[v] = names[i];
485         }
486         v_to_n.rehash;
487     }
488 
489 
490     /***************************************************************************
491 
492         Protected constructor, prevents external instantiation. (Use the
493         singleton instance returned by opCall().)
494 
495     ***************************************************************************/
496 
497     protected this ( )
498     {
499         static if ( is_derived_enum )
500         {
501             super();
502         }
503     }
504 
505 
506     /***************************************************************************
507 
508         Singleton instance of this class (used to access the IEnum methods).
509 
510     ***************************************************************************/
511 
512     private alias typeof(this) This;
513 
514     static private This inst;
515 
516 
517     /***************************************************************************
518 
519         Returns:
520             class singleton instance
521 
522     ***************************************************************************/
523 
524     static public This opCall ( )
525     {
526         if ( !inst )
527         {
528             inst = new This;
529         }
530         return inst;
531     }
532 
533 
534     /***************************************************************************
535 
536         Looks up an enum member's name from its value.
537 
538         Params:
539             v = value to look up
540 
541         Returns:
542             pointer to corresponding name, or null if value doesn't exist in
543             enum
544 
545     ***************************************************************************/
546 
547     public override Name* opIn_r ( Value v )
548     {
549         return v in v_to_n;
550     }
551 
552 
553     /***************************************************************************
554 
555         Looks up an enum member's value from its name.
556 
557         Params:
558             n = name to look up
559 
560         Returns:
561             pointer to corresponding value, or null if name doesn't exist in
562             enum
563 
564     ***************************************************************************/
565 
566     public override Value* opIn_r ( Name n )
567     {
568         return n in n_to_v;
569     }
570 
571 
572     /***************************************************************************
573 
574         Looks up an enum member's name from its value, using opIndex.
575 
576         Params:
577             v = value to look up
578 
579         Returns:
580             corresponding name
581 
582         Throws:
583             (in non-release builds) ArrayBoundsException if value doesn't exist
584             in enum
585 
586     ***************************************************************************/
587 
588     public override Name opIndex ( Value v )
589     {
590         return v_to_n[v];
591     }
592 
593 
594     /***************************************************************************
595 
596         Looks up an enum member's value from its name, using opIndex.
597 
598         Params:
599             n = name to look up
600 
601         Returns:
602             corresponding value
603 
604         Throws:
605             (in non-release builds) ArrayBoundsException if value doesn't exist
606             in enum
607 
608     ***************************************************************************/
609 
610     public override Value opIndex ( Name n )
611     {
612         return n_to_v[n];
613     }
614 
615 
616     /***************************************************************************
617 
618         Returns:
619             the number of members in the enum
620 
621     ***************************************************************************/
622 
623     public override size_t length ( )
624     {
625         return names.length;
626     }
627 
628 
629     /***************************************************************************
630 
631         Returns:
632             the lowest value in the enum
633 
634     ***************************************************************************/
635 
636     public override Value min ( )
637     {
638         return E.min;
639     }
640 
641 
642     /***************************************************************************
643 
644         Returns:
645             the highest value in the enum
646 
647     ***************************************************************************/
648 
649     public override Value max ( )
650     {
651         return E.max;
652     }
653 
654 
655     /***************************************************************************
656 
657         foreach iteration over the names and values in the enum.
658 
659         Note that the iterator passes the enum values as type Value (i.e. int),
660         rather than values of the real enum E. This is in order to keep the
661         iteration functionality in the IEnum interface, which knows nothing of
662         E.
663 
664     ***************************************************************************/
665 
666     public override int opApply ( scope int delegate ( ref const(Name) name,
667         ref const(Value) value ) dg )
668     {
669         int res;
670         foreach ( i, name; this.names )
671         {
672             res = dg(name, values[i]);
673             if ( res ) break;
674         }
675         return res;
676     }
677 
678 
679     /***************************************************************************
680 
681         foreach iteration over the names and values in the enum and their
682         indices.
683 
684         Note that the iterator passes the enum values as type Value (i.e. int),
685         rather than values of the real enum E. This is in order to keep the
686         iteration functionality in the IEnum interface, which knows nothing of
687         E.
688 
689     ***************************************************************************/
690 
691     public override int opApply ( scope int delegate ( ref size_t i,
692         ref const(Name) name, ref const(Value) value ) dg )
693     {
694         int res;
695         foreach ( i, name; this.names )
696         {
697             res = dg(i, name, values[i]);
698             ++i;
699             if ( res ) break;
700         }
701         return res;
702     }
703 }
704 
705 
706 
707 /*******************************************************************************
708 
709     Unit test.
710 
711     Tests:
712         * All IEnum interface methods.
713         * Enum class inheritance.
714 
715 *******************************************************************************/
716 
717 version (unittest)
718 {
719     /***************************************************************************
720 
721         Runs a series of tests to check that the specified enum type contains
722         members with the specified names and values. The name and value lists
723         are assumed to be in the same order (i.e. names[i] corresponds to
724         values[i]).
725 
726         Params:
727             E = enum type to check
728 
729         Params:
730             names = list of names expected to be in the enum
731             values = list of values expected to be in the enum
732 
733     ***************************************************************************/
734 
735     void checkEnum ( E : IEnum ) ( istring[] names, int[] values )
736     {
737         test(names.length == values.length);
738         test(names.length);
739         test(E.names == names);
740         test(E.values == values);
741 
742         // opIn_r lookup by name
743         foreach ( i, n; names )
744         {
745             test(n in E());
746             test(*(n in E()) == values[i]);
747         }
748 
749         // opIn_r lookup by value
750         foreach ( i, v; values )
751         {
752             test(v in E());
753             test(*(v in E()) == names[i]);
754         }
755 
756         // opIndex lookup by name
757         foreach ( i, n; names )
758         {
759             test(E()[n] == values[i]);
760         }
761 
762         // opIndex lookup by value
763         foreach ( i, v; values )
764         {
765             test(E()[v] == names[i]);
766         }
767 
768         // length
769         test(E().length == names.length);
770 
771         // Check min & max
772         int min = int.max;
773         int max = int.min;
774         foreach ( v; values )
775         {
776             if ( v < min ) min = v;
777             if ( v > max ) max = v;
778         }
779         test(E().min == min);
780         test(E().max == max);
781 
782         // opApply 1
783         size_t outer_i;
784         foreach ( n, v; E() )
785         {
786             test(n == names[outer_i]);
787             test(v == values[outer_i]);
788             outer_i++;
789         }
790 
791         // opApply 2
792         foreach ( i, n, v; E() )
793         {
794             test(n == names[i]);
795             test(v == values[i]);
796         }
797     }
798 
799     class Enum1 : IEnum
800     {
801         mixin EnumBase!(["a"[]:1, "b":2, "c":3]);
802     }
803 
804     class Enum2 : Enum1
805     {
806         mixin EnumBase!(["d"[]:4, "e":5, "f":6]);
807     }
808 }
809 
810 unittest
811 {
812     checkEnum!(Enum1)(["a", "b", "c"], [1, 2, 3]);
813     checkEnum!(Enum2)(["a", "b", "c", "d", "e", "f"], [1, 2, 3, 4, 5, 6]);
814 }