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 }