1 /**
2 *   Copyright: © 2015 Anton Gushcha
3 *   License: Subject to the terms of the Boost 1.0 license as specified in LICENSE file.
4 *   Authors: Anton Gushcha <ncrashed@gmail.com>
5 *
6 *   An analog of Boost MPL maps that have several types of keys and
7 *   only one value per each key type.
8 */
9 module stribog.container.multiKeyMap;
10 
11 mixin template makeMultiKeyMap(string mapTypeName, KeyValuesRaw...)
12 {
13     import stribog.meta;
14     import std.conv;
15     import std.typetuple : Reverse;
16     import std.traits : fullyQualifiedName;
17 
18     private template KeyValue(_Key, _Value, size_t _i)
19     {
20         enum i = _i;
21         alias Key = _Key;
22         alias Value = _Value;
23         
24         template setNumber(size_t i) {
25             alias setNumber = KeyValue!(Key, Value, i);
26         }
27     
28         template mapName() {
29             enum mapName = text("map", i);
30         }
31         
32         template genCode() {
33             enum genCode = text(fullyQualifiedName!Value ~ "[" ~ fullyQualifiedName!Key ~ "] " ~ mapName!() ~ ";\n");
34         }
35     }
36     
37     private template SplitKeyValue(T...)
38     {
39         static assert(T.length % 2 == 0, text("Key-values pairs count isn't even, got ", T.length, " elements"));
40         
41         private template makeKeyValue(K, V)
42         {
43             alias makeKeyValue = KeyValue!(K, V, 0);
44         }
45         
46         private template incNumber(alias Pair, T...)
47         {
48             enum iacc = Pair.expand[0];
49             alias kv = T[0];
50             alias kvs = Pair.expand[1];
51             alias incNumber = StrictExpressionList!(iacc+1, StrictExpressionList!(kv.setNumber!(iacc), kvs.expand));
52         }
53         
54         alias Temp = staticFold!( incNumber, StrictExpressionList!(0, StrictExpressionList!()), staticMap2!(makeKeyValue, T)).expand;
55         alias Temp2 = Temp[1];
56         alias SplitKeyValue = Temp2.expand;
57     }
58     
59     private template GenMaps(T...)
60     {
61         private template accString(string acc, T...)
62         {
63             enum accString = acc ~ T[0].genCode!();
64         }
65         
66         enum GenMaps = staticFold!(accString, "", T);
67     }
68     
69     private template hasKeyImpl(U, T...)
70     {
71         private template hasKeyImplImpl(bool acc, Params...)
72         {
73             static if(acc) 
74                 enum hasKeyImplImpl = true;
75             else {
76                 alias Param = Params[0];
77                 alias Key = Param.Key;
78                 enum hasKeyImplImpl = is(Key == U);
79             }
80         }
81         
82         alias hasKeyImpl = staticFold!(hasKeyImplImpl, false, T);
83     }
84     
85     private template getPair(Key, T...)
86     {
87         private template getPairImpl(alias Res, Params...)
88         {
89             static if(Res.expand.length > 0) 
90                 alias getPairImpl = Res;
91             else {
92                 alias Param = Params[0];
93                 alias LocalKey = Param.Key;
94                 static if(is(LocalKey == Key)) 
95                     alias getPairImpl = StrictExpressionList!(Param);
96                 else
97                     alias getPairImpl = StrictExpressionList!();
98             }
99         }
100         
101         alias getPair = staticFold!(getPairImpl, StrictExpressionList!(), T).expand[0];
102     }
103     
104     private template takeKey(alias T) {
105         alias takeKey = T.Key;
106     }
107     
108     private template takeValue(alias T) {
109         alias takeValue = T.Value;
110     }
111     
112     mixin(q{class }~mapTypeName~q{
113     {
114         private alias KeyValues = SplitKeyValue!KeyValuesRaw;
115         
116         public alias KeyTypes = Reverse!(staticMap!(takeKey, KeyValues));
117         public alias ValueTypes = Reverse!(staticMap!(takeValue, KeyValues));
118         
119         size_t length(K)()
120             if(hasKeyImpl!(K, KeyValues))
121         {
122             alias KV = getPair!(K, KeyValues); 
123             mixin("return " ~ KV.mapName!() ~ ".length;");  
124         }
125         
126         auto keys(K)()
127         	if(hasKeyImpl!(K, KeyValues))
128         {
129             alias KV = getPair!(K, KeyValues); 
130             mixin("return " ~ KV.mapName!() ~ ".keys;");  
131         }
132         
133         auto values(K)()
134             if(hasKeyImpl!(K, KeyValues))
135         {
136             alias KV = getPair!(K, KeyValues); 
137             mixin("return " ~ KV.mapName!() ~ ".values;");  
138         }
139         
140         auto byKey(K)()
141             if(hasKeyImpl!(K, KeyValues))
142         {
143             alias KV = getPair!(K, KeyValues); 
144             mixin("return " ~ KV.mapName!() ~ ".byKey;");  
145         }
146         
147         auto byValue(K)()
148             if(hasKeyImpl!(K, KeyValues))
149         {
150             alias KV = getPair!(K, KeyValues); 
151             mixin("return " ~ KV.mapName!() ~ ".byValue;");  
152         }
153         
154         auto byKeyValue(K)()
155             if(hasKeyImpl!(K, KeyValues))
156         {
157             alias KV = getPair!(K, KeyValues); 
158             mixin("return " ~ KV.mapName!() ~ ".byKeyValue;");  
159         }
160         
161         auto opIndex(K)(K val)
162             if(hasKeyImpl!(K, KeyValues))
163         {
164             alias KV = getPair!(K, KeyValues); 
165             mixin("return " ~ KV.mapName!() ~ "[val];");  
166         }
167         
168         void opIndexAssign(K,V)(V val, K key)
169             if(hasKeyImpl!(K, KeyValues) && is(getPair!(K, KeyValues).Value == V))
170         {
171             alias KV = getPair!(K, KeyValues); 
172             mixin(KV.mapName!() ~ "[key] = val;"); 
173         }
174         
175         auto remove(K)(K val)
176             if(hasKeyImpl!(K, KeyValues))
177         {
178             alias KV = getPair!(K, KeyValues); 
179             mixin("return " ~ KV.mapName!() ~ ".remove(val);");  
180         }
181         
182         bool hasKey(K)(K key) if(!hasKeyImpl!(K, KeyValues))
183         {
184             return false;
185         }
186         
187         bool hasKey(K)(K key) if(hasKeyImpl!(K, KeyValues))
188         {
189             alias KV = getPair!(K, KeyValues); 
190             mixin("return (key in " ~ KV.mapName!() ~ ") !is null;");
191         }
192         
193         //pragma(msg, GenMaps!KeyValues);
194         mixin(GenMaps!KeyValues);
195     }});
196 }
197 
198 version(unittest)
199 {
200     import std.range;
201     
202     mixin makeMultiKeyMap!("MultiKeyMap", 
203         int, uint,
204         char, ubyte,
205         ulong, char[17],
206         int[42], bool
207     );
208     
209     static assert(staticEqual!(
210             StrictExpressionList!(MultiKeyMap.KeyTypes),
211             StrictExpressionList!(int, char, ulong, int[42]))
212     );
213     
214     static assert(staticEqual!(
215             StrictExpressionList!(MultiKeyMap.ValueTypes),
216             StrictExpressionList!(uint, ubyte, char[17], bool))
217     );
218 }
219 unittest
220 {
221     MultiKeyMap map = new MultiKeyMap();
222     
223     assert(map.keys!int == []);
224     map[cast(int)5] = 42u; 
225     assert(map[cast(int)5] == 42u);
226     assert(map.keys!int == [5]);
227     
228     map['c'] = cast(ubyte)42u; 
229     assert(map['c'] == cast(ubyte)42u);
230     assert(map.keys!int == [5]);
231     assert(map.keys!char == ['c']);
232     
233     char[17] str = "1234567890qwertyu".dup[0 .. 17];
234     map[cast(ulong)23u] = str; 
235     assert(map[cast(ulong)23u] == str);
236     assert(map.keys!ulong == [23u]);
237     
238     int[42] arr = 42.repeat(42).array[0 .. 42];
239     map[arr] = true; 
240     assert(map[arr]);
241     assert(map.keys!(int[42]) == [arr]);
242         
243     assert(map.hasKey!char('c'));
244     map.remove!char('c');
245     assert(!map.hasKey!char('c'));
246     
247     assert(!map.hasKey!bool(true));
248 }