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