1 /**
2  * An `Enumap` maps each member of an enum to a single value.
3  *
4  * An `Enumap` is effectively a lightweight associative array with some benefits.
5  *
6  * You can think of an `Enumap!(MyEnum, int)` somewhat like a `int[MyEnum]`,
7  * with the following differences:
8  *
9  * - `Enumap!(K,V)` is a value type and requires no dynamic memory allocation
10  *
11  * - `Enumap!(K,V)` has a pre-initialized entry for every member of `K`
12  *
13  * - `Enumap!(K,V)` supports the syntax `map.name` as an alias to `map[K.name]`
14  *
15  * - `Enumap!(K,V)` supports array-wise operations
16  *
17  * Authors: Ryan Roden-Corrent ($(LINK2 https://github.com/rcorre, rcorre))
18  * License: MIT
19  * Copyright: © 2015, Ryan Roden-Corrent
20  *
21  * Examples:
22  * Suppose you are building a good ol' dungeon-crawling RPG.
23  * Where to start? How about the classic 6 attributes:
24  *
25  * ---
26  * enum Attribute {
27  *  strength, dexterity, constitution, wisdom, intellect, charisma
28  * };
29  *
30  * struct Character {
31  *  Enumap!(Attribute, int) attributes;
32  * }
33  * ---
34  *
35  * Lets roll some stats!
36  *
37  * ---
38  * Character hero;
39  * hero.attributes = sequence!((a,n) => uniform!"[]"(0, 20))().take(6);
40  * ---
41  *
42  * Note that we can assign directly from a range!
43  * Just like static array assignment, it will fail if the length doesn't match.
44  *
45  * We can access those values using either `opIndex` or `opDispatch`:
46  *
47  * ---
48  * if (hero.attributes[Attribute.wisdom] < 5) hero.drink(unidentifiedPotion);
49  * // equivalent
50  * if (hero.attributes.wisdom < 5) hero.drink(unidentifiedPotion);
51  * ---
52  *
53  * We can also perform binary operations between `Enumap`s:
54  *
55  * ---
56  * // note the convenient assignment from an associative array:
57  * Enumap!(Attribute, int) bonus = {Attribute.charisma: 2, Attribute.wisom: 1};
58  *
59  * // level up! adds 2 to charisma and 1 to wisdom.
60  * hero.attributes += bonus;
61  * ---
62  *
63  * Finally, note that we can break the `Enumap` down into a range when needed:
64  *
65  * ---
66  * hero.attributes = hero.attributes.byValue.map!(x => x + 1);
67  * ---
68  *
69  * See the full documentation of `Enumap` for all the operations it supports.
70  *
71  */
72 module enumap;
73 
74 import std.algorithm : map;
75 import std.conv      : to;
76 import std.meta      : aliasSeqOf;
77 import std.range;
78 import std.traits    : Unqual, EnumMembers;
79 import std.typecons  : tuple;
80 
81 /**
82  * A structure that maps each member of an enum to a single value.
83  *
84  * An `Enumap` is a lightweight alternative to an associative array that is
85  * useable when your key type is an enum.
86  *
87  * It provides some added benefits over the AA, such as array-wise operations,
88  * default values for all keys, and some nice `opDispatch` based syntactic
89  * sugar for element access.
90  *
91  * The key enum must be backed by an integral type and have 'default' numbering.
92  * The backing value must start at 0 and grows by 1 for each member.
93  *
94  * Params:
95  * K = The type of enum used as a key.
96  *     The enum values must start at 0, and increase by 1 for each entry.
97  * V = The type of value stored for each enum member
98  */
99 struct Enumap(K, V)
100   if(EnumMembers!K == aliasSeqOf!(EnumMembers!K.length.iota))
101 {
102   private static immutable _keys = [EnumMembers!K];
103 
104   /// The number of entries in the `Enumap`
105   static immutable length = _keys.length;
106 
107   /// Assuming Element consists of air, earth, water, and fire (4 members):
108   unittest {
109     static assert(Enumap!(Element, int).length == 4);
110   }
111 
112   private V[length] _store;
113 
114   /// Construct an Enumap from a static array.
115   this(V[length] values) {
116     _store = values;
117   }
118 
119   ///
120   @nogc unittest {
121     auto elements = Enumap!(Element, int)([1, 2, 3, 4]);
122 
123     assert(elements[Element.air]   == 1);
124     assert(elements[Element.earth] == 2);
125     assert(elements[Element.water] == 3);
126     assert(elements[Element.fire]  == 4);
127   }
128 
129   /// Assign from a range with a number of elements exactly matching `length`.
130   this(R)(R values) if (isInputRange!R && is(ElementType!R : V)) {
131     int i = 0;
132     foreach(val ; values) {
133       assert(i < length, "range contains more values than Enumap");
134       _store[i++] = val;
135     }
136     assert(i == length, "range contains less values than Enumap");
137   }
138 
139   ///
140   @nogc unittest {
141     import std.range : repeat;
142     Enumap!(Element, int) elements = 9.repeat(4);
143     assert(elements.air   == 9);
144     assert(elements.earth == 9);
145     assert(elements.water == 9);
146     assert(elements.fire  == 9);
147   }
148 
149   /// An Enumap can be assigned from an array or range of values
150   void opAssign(T)(T val) if (is(typeof(typeof(this)(val)))) {
151     this = typeof(this)(val);
152   }
153 
154   /// Assign an Enumap from a static array.
155   @nogc unittest {
156     Enumap!(Element, int) elements;
157     int[elements.length] arr = [1, 2, 3, 4];
158     elements = arr;
159 
160     with (Element) {
161       assert(elements[air]   == 1);
162       assert(elements[earth] == 2);
163       assert(elements[water] == 3);
164       assert(elements[fire]  == 4);
165     }
166   }
167 
168   /// Assign an Enumap from a range
169   @nogc unittest {
170     import std.range : iota;
171 
172     Enumap!(Element, int) elements;
173 
174     with (Element) {
175       elements = iota(0, 4);
176 
177       assert(elements[air]   == 0);
178       assert(elements[earth] == 1);
179       assert(elements[water] == 2);
180       assert(elements[fire]  == 3);
181     }
182   }
183 
184   /**
185    * Access the value at the index specified by an enum member.
186    *
187    * Indexing returns a reference, so it can be used as a getter or setter.
188    */
189   ref auto opIndex(K key) inout {
190     return _store[key];
191   }
192 
193   ///
194   @nogc unittest {
195     Enumap!(Element, int) elements;
196     elements[Element.fire] = 4;
197     assert(elements[Element.fire] == 4);
198   }
199 
200   /**
201    * Access the value at the index specified by the name of an enum member.
202    *
203    * The value is returned by reference, so it can used for assignment.
204    * `map.name` is just syntactic sugar for `map[SomeEnum.name]`.
205    */
206   ref auto opDispatch(string s)() inout {
207     enum key = s.to!K;
208     return this[key];
209   }
210 
211   ///
212   @nogc unittest {
213     Enumap!(Element, int) elements;
214     elements.water = 5;
215     assert(elements.water == 5);
216   }
217 
218   /// Execute a foreach statement over (EnumMember, value) pairs.
219   int opApply(scope int delegate(K, const V) dg) const {
220     // declare the callee as @nogc so @nogc callers can use foreach
221     // oddly, this works even if dg is non-nogc... huh?
222     // I'm just gonna take this and run before the compiler catches me.
223     alias nogcDelegate = @nogc int delegate(K, const V);
224     auto callme = cast(nogcDelegate) dg;
225 
226     int res = 0;
227 
228     foreach(key ; _keys) {
229       res = callme(key, this[key]);
230       if (res) break;
231     }
232 
233     return res;
234   }
235 
236   /// foreach iterates over (EnumMember, value) pairs.
237   @nogc unittest {
238     const auto elements = enumap(Element.water, 4, Element.air, 3);
239 
240     foreach(key, value ; elements) {
241       assert(
242           key == Element.water && value == 4 ||
243           key == Element.air   && value == 3 ||
244           value == 0);
245     }
246   }
247 
248   /// Execute foreach over (EnumMember, ref value) pairs to modify elements.
249   int opApply(scope int delegate(K, ref V) dg) {
250     // declare the callee as @nogc so @nogc callers can use foreach
251     // oddly, this works even if dg is non-nogc... huh?
252     // I'm just gonna take this and run before the compiler catches me.
253     alias nogcDelegate = @nogc int delegate(K, ref V);
254     auto callme = cast(nogcDelegate) dg;
255 
256     int res = 0;
257 
258     foreach(key ; _keys) {
259       res = callme(key, this[key]);
260       if (res) break;
261     }
262 
263     return res;
264   }
265 
266   /// foreach can modify values by ref
267   @nogc unittest {
268     Enumap!(Element, int) elements;
269 
270     foreach(key, ref value ; elements) {
271       if      (key == Element.air)   value = 4;
272       else if (key == Element.water) value = 2;
273     }
274 
275     assert(elements.air   == 4);
276     assert(elements.water == 2);
277   }
278 
279   // make sure you can foreach with a non-nogc delegate
280   unittest {
281     foreach(key, ref value ; enumap(Element.water, "here comes..."))
282       value ~= "a spurious allocation!";
283   }
284 
285   /// Apply an array-wise operation between two `Enumap`s.
286   auto opBinary(string op)(inout typeof(this) other) const
287     if (is(typeof(mixin("V.init"~op~"V.init")) : V))
288   {
289     Unqual!(typeof(this)) result;
290 
291     foreach(member ; _keys) {
292       result[member] = mixin("this[member]"~op~"other[member]");
293     }
294 
295     return result;
296   }
297 
298   ///
299   @nogc unittest {
300     immutable base  = enumap(Element.water, 4, Element.air , 3);
301     immutable bonus = enumap(Element.water, 5, Element.fire, 2);
302 
303     immutable sum  = base + bonus;
304     immutable diff = base - bonus;
305     immutable prod = base * bonus;
306 
307     assert(sum.water == 4 + 5);
308     assert(sum.air   == 3 + 0);
309     assert(sum.fire  == 0 + 2);
310     assert(sum.earth == 0 + 0);
311 
312     assert(diff.water == 4 - 5);
313     assert(diff.air   == 3 - 0);
314     assert(diff.fire  == 0 - 2);
315     assert(diff.earth == 0 - 0);
316 
317     assert(prod.water == 4 * 5);
318     assert(prod.air   == 3 * 0);
319     assert(prod.fire  == 0 * 2);
320     assert(prod.earth == 0 * 0);
321   }
322 
323   ///
324   unittest {
325     auto inventory = enumap(
326         ItemType.junk  , [ "Gemstone"        ],
327         ItemType.normal, [ "Sword", "Shield" ],
328         ItemType.key   , [ "Bronze Key"      ]);
329 
330     auto loot = enumap(
331         ItemType.junk  , [ "Potato"       ],
332         ItemType.normal, [ "Potion"       ],
333         ItemType.key   , [ "Skeleton Key" ]);
334 
335     inventory ~= loot;
336 
337     assert(inventory.junk   == [ "Gemstone", "Potato"          ]);
338     assert(inventory.normal == [ "Sword", "Shield" , "Potion"  ]);
339     assert(inventory.key    == [ "Bronze Key" , "Skeleton Key" ]);
340   }
341 
342   /// Perform an in-place operation.
343   auto opOpAssign(string op)(inout typeof(this) other)
344     if (is(typeof(this.opBinary!op(other)) : typeof(this)))
345   {
346     this = this.opBinary!op(other);
347   }
348 
349   ///
350   @nogc unittest {
351     auto  base  = enumap(Element.water, 4, Element.air , 3);
352     const bonus = enumap(Element.water, 5, Element.fire, 2);
353 
354     base += bonus;
355 
356     assert(base.water == 4 + 5);
357     assert(base.air   == 3 + 0);
358     assert(base.fire  == 0 + 2);
359     assert(base.earth == 0 + 0);
360 
361     base -= bonus; // cancel out the previous addition
362 
363     assert(base.water == 4);
364     assert(base.air   == 3);
365     assert(base.fire  == 0);
366     assert(base.earth == 0);
367 
368     base *= bonus;
369     assert(base.water == 4 * 5);
370     assert(base.air   == 3 * 0);
371     assert(base.fire  == 0 * 2);
372     assert(base.earth == 0 * 0);
373   }
374 
375   /// Perform a unary operation on each entry.
376   auto opUnary(string op)() const
377     if (is(typeof(mixin(op~"V.init")) : V))
378   {
379     V[length] result = mixin(op~"_store[]");
380     return typeof(this)(result);
381   }
382 
383   @nogc unittest {
384     immutable elements = enumap(Element.water, 4);
385     assert((-elements).water == -4);
386   }
387 
388   /// Get a range iterating over the members of the enum `K`.
389   auto byKey() const { return _keys; }
390 
391   @nogc unittest {
392     import std.range     : only;
393     import std.algorithm : equal;
394 
395     Enumap!(Element, int) e;
396     with (Element) {
397       assert(e.byKey.equal(only(air, earth, water, fire)));
398     }
399   }
400 
401   /// Get a range iterating over the stored values.
402   auto byValue() inout { return _store[]; }
403 
404   /// you can use byValue to perform range based operations on the values:
405   @nogc unittest {
406     import std.range     : iota;
407     import std.algorithm : map, equal;
408 
409     const Enumap!(Element, int) e1 = iota(0, 4);
410     const Enumap!(Element, int) e2 = e1.byValue.map!(x => x + 2);
411     assert(e2.byValue.equal(iota(2, 6)));
412   }
413 
414   /// `byValue` supports ref access:
415   @nogc unittest {
416     with (Element) {
417       auto elements = enumap(air, 1, water, 2, fire, 3, earth, 4);
418       foreach(ref val ; elements.byValue) ++val;
419       assert(elements == enumap(air, 2, water, 3, fire, 4, earth, 5));
420     }
421   }
422 
423   /**
424    * Return a range of (EnumMember, value) pairs.
425    *
426    * Note that byKeyValue does _not_ support modifying the underlying values by
427    * reference.
428    * For that, you should just use foreach directly (see `opApply`).
429    */
430   auto byKeyValue() const {
431     alias StoreType = typeof(_store);
432 
433     struct Result {
434       private StoreType _source;
435       private int _idx;
436 
437       bool empty() { return _idx >= length; }
438       auto front() { return tuple(_keys[_idx], _source[_idx]); }
439       void popFront() { ++_idx; }
440     }
441 
442     return Result(_store);
443   }
444 
445   ///
446   @nogc unittest {
447     import std.typecons  : tuple;
448     import std.algorithm : map;
449 
450     immutable elements = enumap(Element.water, 4, Element.air, 3);
451 
452     auto pairs = elements.byKeyValue.map!(pair => tuple(pair[0], pair[1] + 1));
453 
454     foreach(key, value ; pairs) {
455       assert(
456           key == Element.water && value == 5 ||
457           key == Element.air   && value == 4 ||
458           value == 1);
459     }
460   }
461 }
462 
463   /**
464    * Construct an `Enumap` from a sequence of key/value pairs.
465    *
466    * Any values not specified default to `V.init`.
467    */
468 @nogc auto enumap(T...)(T pairs) if (T.length >= 2 && T.length % 2 == 0) {
469   alias K = T[0];
470   alias V = T[1];
471 
472   Enumap!(K, V) result;
473 
474   // pop a key/vaue pair, assign it to the enumap, and recurse until empty
475   void helper(U...)(U params) {
476     static assert(is(U[0] == K), "enumap: mismatched key type");
477     static assert(is(U[1] == V), "enumap: mismatched value type");
478 
479     auto key = params[0];
480     auto val = params[1];
481 
482     result[key] = val;
483 
484     static if (U.length > 2) {
485       helper(params[2..$]);
486     }
487   }
488 
489   helper(pairs);
490   return result;
491 }
492 
493 ///
494 @nogc unittest {
495   with (Element) {
496     auto elements = enumap(air, 1, earth, 2, water, 3);
497 
498     assert(elements[air]   == 1);
499     assert(elements[earth] == 2);
500     assert(elements[water] == 3);
501     assert(elements[fire]  == 0); // unspecified values default to V.init
502   }
503 }
504 
505 version (unittest) {
506   // define some enums for testing
507   private enum Element { air, earth, water, fire };
508   private enum ItemType { junk, normal, key };
509 }
510 
511 // make sure the readme examples work:
512 unittest {
513   import std.algorithm, std.range, std.random;
514 
515   enum Attribute {
516     strength, dexterity, constitution, wisdom, intellect, charisma
517   }
518 
519   Enumap!(Attribute, int) attributes;
520   assert(attributes.wisdom == 0); // default value check
521 
522   attributes[Attribute.strength] = 10;
523 
524   // generate is not implemented for GDC (maybe others too?)
525   static if (is(typeof(std.range.generate)))
526     attributes = generate!(() => uniform!"[]"(1, 20)).take(6);
527 
528   // make sure accessors compile:
529   if (attributes[Attribute.wisdom] < 5) { }
530   if (attributes.wisdom < 5) { }
531 
532   // key/value constructor
533   auto bonus = enumap(Attribute.charisma, 2, Attribute.wisdom, 1);
534 
535   // opBinary
536   attributes += bonus;
537 
538   // nogc test
539   void donFancyHat(int[Attribute] attrs) { attrs[Attribute.charisma] += 1; }
540   @nogc void donFancyHat2(Enumap!(Attribute, int) attrs) { attrs.charisma += 1; }
541 }
542 
543 // The enum members must have ascending values in sequence, from 0 on.
544 unittest {
545   enum Acceptable { zero = 0, one = 1, two = 2, three = 3 }
546   enum WrongOrder { zero = 0, one = 2, two = 1, three = 3 }
547   enum WrongStart { zero = 3, one = 4, two = 5, three = 6 }
548   assert(__traits(compiles, enumap!(Acceptable, int)));
549   assert(!__traits(compiles, enumap!(WrongOrder, int)));
550   assert(!__traits(compiles, enumap!(WrongStart, int)));
551 }
552 
553 // constness tests:
554 unittest {
555   auto      mmap = enumap(Element.air, 2, Element.water, 4);
556   const     cmap = enumap(Element.air, 2, Element.water, 4);
557   immutable imap = enumap(Element.air, 2, Element.water, 4);
558 
559   // value access permitted on all
560   static assert(__traits(compiles, mmap[Element.air] == 2));
561   static assert(__traits(compiles, cmap[Element.air] == 2));
562   static assert(__traits(compiles, imap[Element.air] == 2));
563   static assert(__traits(compiles, mmap.air == 2));
564   static assert(__traits(compiles, cmap.air == 2));
565   static assert(__traits(compiles, imap.air == 2));
566 
567   // value setters only permitted if mutable
568   static assert( __traits(compiles, { mmap[Element.air] = 2; }));
569   static assert(!__traits(compiles, { cmap[Element.air] = 2; }));
570   static assert(!__traits(compiles, { imap[Element.air] = 2; }));
571   static assert( __traits(compiles, { mmap.air = 2; }));
572   static assert(!__traits(compiles, { cmap.air = 2; }));
573   static assert(!__traits(compiles, { imap.air = 2; }));
574 
575   // readonly foreach permitted on all
576   static assert(__traits(compiles, { foreach(k,v ; mmap) {} }));
577   static assert(__traits(compiles, { foreach(k,v ; cmap) {} }));
578   static assert(__traits(compiles, { foreach(k,v ; imap) {} }));
579 
580   // foreach with modification permitted only if mutable
581   static assert( __traits(compiles, { foreach(k, ref v ; mmap) {++v;} }));
582   static assert(!__traits(compiles, { foreach(k, ref v ; cmap) {++v;} }));
583   static assert(!__traits(compiles, { foreach(k, ref v ; imap) {++v;} }));
584 
585   // opBinary permitted on all
586   static assert(__traits(compiles, { immutable res = mmap + mmap; }));
587   static assert(__traits(compiles, { immutable res = cmap + cmap; }));
588   static assert(__traits(compiles, { immutable res = imap + imap; }));
589   static assert(__traits(compiles, { immutable res = mmap + imap; }));
590   static assert(__traits(compiles, { immutable res = imap + mmap; }));
591 
592   // opBinaryAssign permitted only if mutable
593   static assert( __traits(compiles, { mmap += mmap; }));
594   static assert( __traits(compiles, { mmap += cmap; }));
595   static assert( __traits(compiles, { mmap += imap; }));
596   static assert(!__traits(compiles, { cmap += mmap; }));
597   static assert(!__traits(compiles, { cmap += cmap; }));
598   static assert(!__traits(compiles, { imap += imap; }));
599 
600   // opUnary permitted on all
601   static assert(__traits(compiles, { -mmap; }));
602   static assert(__traits(compiles, { -cmap; }));
603   static assert(__traits(compiles, { -imap; }));
604 
605   // opUnary permitted on all
606   static assert(__traits(compiles, { -mmap; }));
607   static assert(__traits(compiles, { -cmap; }));
608   static assert(__traits(compiles, { -imap; }));
609 
610   // byKey permitted on all
611   static assert(__traits(compiles, { mmap.byKey; }));
612   static assert(__traits(compiles, { cmap.byKey; }));
613   static assert(__traits(compiles, { imap.byKey; }));
614 
615   // byValue permitted on all
616   static assert(__traits(compiles, { foreach(v ; mmap.byValue) {} }));
617   static assert(__traits(compiles, { foreach(v ; cmap.byValue) {} }));
618   static assert(__traits(compiles, { foreach(v ; imap.byValue) {} }));
619 
620   // byValue with ref assignment permitted only if mutable
621   static assert( __traits(compiles, { foreach(ref v ; mmap.byValue) ++v; }));
622   static assert(!__traits(compiles, { foreach(ref v ; cmap.byValue) ++v; }));
623   static assert(!__traits(compiles, { foreach(ref v ; imap.byValue) ++v; }));
624 
625   // byKeyValue permitted on all
626   static assert(__traits(compiles, { foreach(k, v ; mmap.byKeyValue) {} }));
627   static assert(__traits(compiles, { foreach(k, v ; cmap.byKeyValue) {} }));
628   static assert(__traits(compiles, { foreach(k, v ; imap.byKeyValue) {} }));
629 }