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.transition;
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         Looks up an enum member's name from its value, using opIndex.
188 
189         Params:
190             v = value to look up
191 
192         Returns:
193             corresponding name
194 
195         Throws:
196             ArrayBoundsException if value doesn't exist in enum
197 
198     ***************************************************************************/
199 
200     public Name opIndex ( Value v );
201 
202 
203     /***************************************************************************
204 
205         Looks up an enum member's value from its name, using opIndex.
206 
207         Params:
208             n = name to look up
209 
210         Returns:
211             corresponding value
212 
213         Throws:
214             ArrayBoundsException if name doesn't exist in enum
215 
216     ***************************************************************************/
217 
218     public Value opIndex ( Name n );
219 
220 
221     /***************************************************************************
222 
223         Returns:
224             the number of members in the enum
225 
226     ***************************************************************************/
227 
228     public size_t length ( );
229 
230 
231     /***************************************************************************
232 
233         Returns:
234             the lowest value in the enum
235 
236     ***************************************************************************/
237 
238     Value min ( );
239 
240 
241     /***************************************************************************
242 
243         Returns:
244             the highest value in the enum
245 
246     ***************************************************************************/
247 
248     Value max ( );
249 
250 
251     /***************************************************************************
252 
253         foreach iteration over the names and values in the enum.
254 
255     ***************************************************************************/
256 
257     public int opApply ( scope int delegate ( ref Const!(Name) name,
258         ref Const!(Value) value ) dg );
259 
260 
261     /***************************************************************************
262 
263         foreach iteration over the names and values in the enum and their
264         indices.
265 
266     ***************************************************************************/
267 
268     public int opApply ( scope int delegate ( ref size_t i, ref Const!(Name) name,
269         ref Const!(Value) value ) dg );
270 }
271 
272 
273 /*******************************************************************************
274 
275     Template which evaluates to a string containing the code for a list of enum
276     members, as specified by the first two members of the passed tuple, which
277     must be an array of strings and an array of integers, respectively. The
278     strings specify the names of the enum members, and the integers their
279     values.
280 
281     This template is public for technical reason, and should not be needed
282     in client code - See IEnum and EnumBase for template / interface you
283     should use.
284 
285     Params:
286         T = tuple:
287             T[0] must be an array of strings
288             T[1] must be an array of ints
289         (Note that the template accepts a tuple purely as a workaround for the
290         compiler's inability to handle templates which accept values of types
291         such as char[][] and int[].)
292 
293 *******************************************************************************/
294 
295 public template EnumValues ( size_t i, T ... )
296 {
297     static assert(T.length == 2);
298     static assert(is(typeof(T[0]) : Const!(istring[])));
299     static assert(is(typeof(T[1]) : Const!(int[])));
300 
301     static if ( i == T[0].length - 1 )
302     {
303         static immutable EnumValues = T[0][i] ~ "=" ~ T[1][i].stringof;
304     }
305     else
306     {
307         static immutable EnumValues = T[0][i] ~ "=" ~ T[1][i].stringof ~ ","
308             ~ EnumValues!(i + 1, T);
309     }
310 }
311 
312 
313 /*******************************************************************************
314 
315     Template which evaluates to a size_t corresponding to the index in the type
316     tuple T which contains a class implementing the IEnum interface. If no such
317     type exists in T, then the template evaluates to T.length.
318 
319     This template is public for technical reason, and should not be needed
320     in client code - See IEnum and EnumBase for template / interface you
321     should use.
322 
323     Params:
324         i = recursion index over T
325         T = type tuple
326 
327 *******************************************************************************/
328 
329 public template SuperClassIndex ( size_t i, T ... )
330 {
331     static if ( i == T.length )
332     {
333         static immutable size_t SuperClassIndex = i;
334     }
335     else
336     {
337         static if ( is(T[i] == class) && is(T[i] : IEnum) )
338         {
339             static immutable size_t SuperClassIndex = i;
340         }
341         else
342         {
343             static immutable size_t SuperClassIndex = SuperClassIndex!(i + 1, T);
344         }
345     }
346 }
347 
348 
349 /*******************************************************************************
350 
351     Template mixin to add enum functionality to a class.
352 
353     Note that the [0..$] which is used in places in this method is a workaround
354     for various weird compiler issues / segfaults.
355 
356     Params:
357         T = tuple:
358             T[0] must be an associative array of type int[char[]]
359         (Note that the template accepts a tuple purely as a workaround for the
360         compiler's inability to handle templates which accept associative array
361         values.)
362 
363     TODO: adapt to accept *either* an AA or a simple list of names (for an
364     auto-enum with values starting at 0).
365 
366 *******************************************************************************/
367 
368 public template EnumBase ( T ... )
369 {
370     import ocean.transition;
371 
372     alias IEnum.Name Name;
373     alias IEnum.Value Value;
374 
375     /***************************************************************************
376 
377         Ensure that the class into which this template is mixed is an IEnum.
378 
379     ***************************************************************************/
380 
381     static assert(is(typeof(this) : IEnum));
382 
383 
384     /***************************************************************************
385 
386         Ensure that the tuple T contains a single element which is of type
387         int[char[]].
388 
389     ***************************************************************************/
390 
391     static assert(T.length == 1);
392     static assert(is(typeof(T[0].keys) : Const!(char[][])));
393     static assert(is(typeof(T[0].values) : Const!(int[])));
394 
395 
396     /***************************************************************************
397 
398         Constants determining whether this class is derived from another class
399         which implements IEnum.
400 
401     ***************************************************************************/
402 
403     static if ( is(typeof(this) S == super) )
404     {
405         private static immutable super_class_index = SuperClassIndex!(0, S);
406 
407         private static immutable is_derived_enum = super_class_index < S.length;
408     }
409     else
410     {
411         private static immutable is_derived_enum = false;
412     }
413 
414 
415     /***************************************************************************
416 
417         Constant arrays of enum member names and values.
418 
419         If the class into which this template is mixed has a super class which
420         is also an IEnum, the name and value arrays of the super class are
421         concatenated with those in the associative array in T[0].
422 
423     ***************************************************************************/
424 
425     static if ( is_derived_enum )
426     {
427         public static immutable _internal_names =
428             S[super_class_index]._internal_names[0..$] ~ T[0].keys[0..$];
429         public static immutable _internal_values =
430             S[super_class_index]._internal_values[0..$] ~ T[0].values[0..$];
431     }
432     else
433     {
434         public static immutable _internal_names = T[0].keys;
435         public static immutable _internal_values = T[0].values;
436     }
437 
438     static assert(_internal_names.length == _internal_values.length);
439 
440     private static names = _internal_names;
441     private static values = _internal_values;
442 
443     /***************************************************************************
444 
445         The actual enum, E.
446 
447     ***************************************************************************/
448 
449     mixin("enum E {" ~ EnumValues!(0, _internal_names[0..$],
450         _internal_values[0..$]) ~ "}");
451 
452 
453     /***************************************************************************
454 
455         Internal maps from names <-> values. The maps are filled in the static
456         constructor.
457 
458     ***************************************************************************/
459 
460     static protected Value[Name] n_to_v;
461     static protected Name[Value] v_to_n;
462 
463     static this ( )
464     {
465         foreach ( i, n; names )
466         {
467             n_to_v[n] = values[i];
468         }
469         n_to_v.rehash;
470 
471         foreach ( i, v; values )
472         {
473             v_to_n[v] = names[i];
474         }
475         v_to_n.rehash;
476     }
477 
478 
479     /***************************************************************************
480 
481         Protected constructor, prevents external instantiation. (Use the
482         singleton instance returned by opCall().)
483 
484     ***************************************************************************/
485 
486     protected this ( )
487     {
488         static if ( is_derived_enum )
489         {
490             super();
491         }
492     }
493 
494 
495     /***************************************************************************
496 
497         Singleton instance of this class (used to access the IEnum methods).
498 
499     ***************************************************************************/
500 
501     private alias typeof(this) This;
502 
503     static private This inst;
504 
505 
506     /***************************************************************************
507 
508         Returns:
509             class singleton instance
510 
511     ***************************************************************************/
512 
513     static public This opCall ( )
514     {
515         if ( !inst )
516         {
517             inst = new This;
518         }
519         return inst;
520     }
521 
522 
523     /***************************************************************************
524 
525         Looks up an enum member's name from its value.
526 
527         Params:
528             v = value to look up
529 
530         Returns:
531             pointer to corresponding name, or null if value doesn't exist in
532             enum
533 
534     ***************************************************************************/
535 
536     public override Name* opIn_r ( Value v )
537     {
538         return v in v_to_n;
539     }
540 
541 
542     /***************************************************************************
543 
544         Looks up an enum member's value from its name.
545 
546         Params:
547             n = name to look up
548 
549         Returns:
550             pointer to corresponding value, or null if name doesn't exist in
551             enum
552 
553     ***************************************************************************/
554 
555     public override Value* opIn_r ( Name n )
556     {
557         return n in n_to_v;
558     }
559 
560 
561     /***************************************************************************
562 
563         Looks up an enum member's name from its value, using opIndex.
564 
565         Params:
566             v = value to look up
567 
568         Returns:
569             corresponding name
570 
571         Throws:
572             (in non-release builds) ArrayBoundsException if value doesn't exist
573             in enum
574 
575     ***************************************************************************/
576 
577     public override Name opIndex ( Value v )
578     {
579         return v_to_n[v];
580     }
581 
582 
583     /***************************************************************************
584 
585         Looks up an enum member's value from its name, using opIndex.
586 
587         Params:
588             n = name to look up
589 
590         Returns:
591             corresponding value
592 
593         Throws:
594             (in non-release builds) ArrayBoundsException if value doesn't exist
595             in enum
596 
597     ***************************************************************************/
598 
599     public override Value opIndex ( Name n )
600     {
601         return n_to_v[n];
602     }
603 
604 
605     /***************************************************************************
606 
607         Returns:
608             the number of members in the enum
609 
610     ***************************************************************************/
611 
612     public override size_t length ( )
613     {
614         return names.length;
615     }
616 
617 
618     /***************************************************************************
619 
620         Returns:
621             the lowest value in the enum
622 
623     ***************************************************************************/
624 
625     public override Value min ( )
626     {
627         return E.min;
628     }
629 
630 
631     /***************************************************************************
632 
633         Returns:
634             the highest value in the enum
635 
636     ***************************************************************************/
637 
638     public override Value max ( )
639     {
640         return E.max;
641     }
642 
643 
644     /***************************************************************************
645 
646         foreach iteration over the names and values in the enum.
647 
648         Note that the iterator passes the enum values as type Value (i.e. int),
649         rather than values of the real enum E. This is in order to keep the
650         iteration functionality in the IEnum interface, which knows nothing of
651         E.
652 
653     ***************************************************************************/
654 
655     public override int opApply ( scope int delegate ( ref Const!(Name) name,
656         ref Const!(Value) value ) dg )
657     {
658         int res;
659         foreach ( i, name; this.names )
660         {
661             res = dg(name, values[i]);
662             if ( res ) break;
663         }
664         return res;
665     }
666 
667 
668     /***************************************************************************
669 
670         foreach iteration over the names and values in the enum and their
671         indices.
672 
673         Note that the iterator passes the enum values as type Value (i.e. int),
674         rather than values of the real enum E. This is in order to keep the
675         iteration functionality in the IEnum interface, which knows nothing of
676         E.
677 
678     ***************************************************************************/
679 
680     public override int opApply ( scope int delegate ( ref size_t i,
681         ref Const!(Name) name, ref Const!(Value) value ) dg )
682     {
683         int res;
684         foreach ( i, name; this.names )
685         {
686             res = dg(i, name, values[i]);
687             ++i;
688             if ( res ) break;
689         }
690         return res;
691     }
692 }
693 
694 
695 
696 /*******************************************************************************
697 
698     Unit test.
699 
700     Tests:
701         * All IEnum interface methods.
702         * Enum class inheritance.
703 
704 *******************************************************************************/
705 
706 version ( UnitTest )
707 {
708     /***************************************************************************
709 
710         Runs a series of tests to check that the specified enum type contains
711         members with the specified names and values. The name and value lists
712         are assumed to be in the same order (i.e. names[i] corresponds to
713         values[i]).
714 
715         Params:
716             E = enum type to check
717 
718         Params:
719             names = list of names expected to be in the enum
720             values = list of values expected to be in the enum
721 
722     ***************************************************************************/
723 
724     void checkEnum ( E : IEnum ) ( istring[] names, int[] values )
725     {
726         test(names.length == values.length);
727         test(names.length);
728         test(E.names == names);
729         test(E.values == values);
730 
731         // opIn_r lookup by name
732         foreach ( i, n; names )
733         {
734             test(n in E());
735             test(*(n in E()) == values[i]);
736         }
737 
738         // opIn_r lookup by value
739         foreach ( i, v; values )
740         {
741             test(v in E());
742             test(*(v in E()) == names[i]);
743         }
744 
745         // opIndex lookup by name
746         foreach ( i, n; names )
747         {
748             test(E()[n] == values[i]);
749         }
750 
751         // opIndex lookup by value
752         foreach ( i, v; values )
753         {
754             test(E()[v] == names[i]);
755         }
756 
757         // length
758         test(E().length == names.length);
759 
760         // Check min & max
761         int min = int.max;
762         int max = int.min;
763         foreach ( v; values )
764         {
765             if ( v < min ) min = v;
766             if ( v > max ) max = v;
767         }
768         test(E().min == min);
769         test(E().max == max);
770 
771         // opApply 1
772         size_t i;
773         foreach ( n, v; E() )
774         {
775             test(n == names[i]);
776             test(v == values[i]);
777             i++;
778         }
779 
780         // opApply 2
781         foreach ( i, n, v; E() )
782         {
783             test(n == names[i]);
784             test(v == values[i]);
785         }
786     }
787 
788     class Enum1 : IEnum
789     {
790         mixin EnumBase!(["a"[]:1, "b":2, "c":3]);
791     }
792 
793     class Enum2 : Enum1
794     {
795         mixin EnumBase!(["d"[]:4, "e":5, "f":6]);
796     }
797 }
798 
799 unittest
800 {
801     checkEnum!(Enum1)(["a", "b", "c"], [1, 2, 3]);
802     checkEnum!(Enum2)(["a", "b", "c", "d", "e", "f"], [1, 2, 3, 4, 5, 6]);
803 }