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 }