1 /**
2 *   Copyright: © 2014-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 *   Template for compile time key-value list - extension of std.traits;
7 */
8 module stribog.meta.keyvalue;
9 
10 import stribog.meta.base;
11 import stribog.meta.map;
12 
13 /**
14 *   Static associative map.
15 *
16 *   $(B Pairs) is a list of pairs key-value.
17 */
18 template KeyValueList(Pairs...)
19 {
20     static assert(Pairs.length % 2 == 0, text("KeyValueList is expecting even count of elements, not ", Pairs.length));
21     
22     /// Number of entries in the map
23     enum length = Pairs.length / 2;
24     
25     /**
26     *   Getting values by keys. If $(B Keys) is a one key, then
27     *   returns unwrapped value, else a ExpressionExpressionList of values.
28     */
29     template get(Keys...)
30     {
31         static assert(Keys.length > 0, "KeyValueList.get is expecting an argument!");
32         static if(Keys.length == 1)
33         {
34             static if(is(Keys[0])) { 
35                 alias Key = Keys[0];
36             } else {
37                 enum Key = Keys[0];
38                 static assert(__traits(compiles, Key == Key), text(typeof(Key).stringof, " must have a opCmp!"));
39             }
40             
41             private static template innerFind(T...)
42             {
43                 static if(T.length == 0) {
44                     alias innerFind = ExpressionList!();
45                 } else
46                 {
47                     static if(is(Keys[0])) { 
48                         static if(is(T[0] == Key)) {
49                             static if(is(T[1])) {
50                                 alias innerFind = T[1];
51                             } else {
52                                 enum innerFind = T[1];
53                             }
54                         } else {
55                             alias innerFind = innerFind!(T[2 .. $]);
56                         }
57                     } else
58                     {
59                         static if(T[0] == Key) {
60                             static if(is(T[1])) {
61                                 alias innerFind = T[1];
62                             } else {
63                                 // hack to avoid compile-time lambdas
64                                 // see http://forum.dlang.org/thread/lkl0lp$204h$1@digitalmars.com
65                                 static if(__traits(compiles, {enum innerFind = T[1];}))
66                                 {
67                                     enum innerFind = T[1];
68                                 } else
69                                 {
70                                     alias innerFind = T[1];
71                                 }
72                             }
73                         } else {
74                             alias innerFind = innerFind!(T[2 .. $]);
75                         }
76                     }
77                 }
78             }
79 
80             alias get = innerFind!Pairs; 
81         } else {
82             alias get = ExpressionList!(get!(Keys[0 .. $/2]), get!(Keys[$/2 .. $]));
83         }
84     }
85     
86     /// Returns true if map has a $(B Key)
87     template has(Key...)
88     {
89         static assert(Key.length == 1);
90         enum has = ExpressionList!(get!Key).length > 0; 
91     }
92     
93     /// Setting values to specific keys (or adding new key-values)
94     template set(KeyValues...)
95     {
96         static assert(KeyValues.length >= 2, "KeyValueList.set is expecting at least one pair!");
97         static assert(KeyValues.length % 2 == 0, "KeyValuesExpressionList.set is expecting even count of arguments!");
98         
99         template inner(KeyValues...)
100         {
101             static if(KeyValues.length == 2) {
102                 static if(is(KeyValues[0])) {
103                     alias Key = KeyValues[0];
104                 } else {
105                     enum Key = KeyValues[0];
106                 }
107                 
108                 static if(is(KeyValues[1])) {
109                     alias Value = KeyValues[1];
110                 } else {
111                     enum Value = KeyValues[1];
112                 }
113                 
114                 private template innerFind(T...)
115                 {
116                     static if(T.length == 0) {
117                         alias innerFind = ExpressionList!(Key, Value);
118                     } else
119                     {
120                         static if(is(Key)) { 
121                             static if(is(T[0] == Key)) {
122                                 alias innerFind = ExpressionList!(Key, Value, T[2 .. $]);
123                             } else {
124                                 alias innerFind = ExpressionList!(T[0 .. 2], innerFind!(T[2 .. $]));
125                             }
126                         } else
127                         {
128                             static if(T[0] == Key) {
129                                 alias innerFind = ExpressionList!(Key, Value, T[2 .. $]);
130                             } else {
131                                 alias innerFind = ExpressionList!(T[0 .. 2], innerFind!(T[2 .. $]));
132                             }
133                         }
134                     }
135                 }
136     
137                 alias inner = innerFind!Pairs; 
138             } else {
139                 alias inner = ExpressionList!(inner!(KeyValues[0 .. $/2]), inner!(KeyValues[$/2 .. $]));
140             }
141         }
142         alias set = KeyValueList!(inner!KeyValues);
143     }
144     
145     /// Applies $(B F) template for each pair (key-value).
146     template map(alias F)
147     {
148         alias map = KeyValueList!(staticMap2!(F, Pairs));
149     }
150     
151     private static template getKeys(T...)
152     {
153         static if(T.length == 0) {
154             alias getKeys = ExpressionList!();
155         } else {
156             alias getKeys = ExpressionList!(T[0], getKeys!(T[2 .. $]));
157         }
158     }
159     /// Getting expression list of all keys
160     alias keys = getKeys!Pairs;
161     
162     private static template getValues(T...)
163     {
164         static if(T.length == 0) {
165             alias getValues = ExpressionList!();
166         } else {
167             alias getValues = ExpressionList!(T[1], getValues!(T[2 .. $]));
168         }
169     }
170     /// Getting expression list of all values
171     alias values = getValues!Pairs;
172     
173     /** 
174     *   Filters entries with function or template $(B F), leaving entry only if
175     *   $(B F) returning $(B true).
176     */
177     static template filter(alias F)
178     {
179         alias filter = KeyValueList!(staticFilter2!(F, Pairs));
180     } 
181     
182     /** 
183     *   Filters entries with function or template $(B F) passing only a key from an entry, leaving entry only if
184     *   $(B F) returning $(B true).
185     */
186     static template filterByKey(alias F)
187     {
188         private alias newKeys = staticFilter!(F, keys);
189         private alias newValues = staticMap!(get, newKeys);
190         alias filterByKey = KeyValueList!(staticRobin!(StrictExpressionList!(newKeys, newValues)));
191     }
192 }
193 ///
194 unittest
195 {
196     alias map = KeyValueList!("a", 42, "b", 23);
197     static assert(map.get!"a" == 42);
198     static assert(map.get!("a", "b") == ExpressionList!(42, 23));
199     static assert(map.get!"c".length == 0);
200     
201     alias map2 = KeyValueList!(int, float, float, double, double, 42);
202     static assert(is(map2.get!int == float));
203     static assert(is(map2.get!float == double));
204     static assert(map2.get!double == 42); 
205     
206     static assert(map.has!"a");
207     static assert(map2.has!int);
208     static assert(!map2.has!void);
209     static assert(!map.has!"c");
210     
211     alias map3 = map.set!("c", 4);
212     static assert(map3.get!"c" == 4);
213     alias map4 = map.set!("c", 4, "d", 8);
214     static assert(map4.get!("c", "d") == ExpressionList!(4, 8));
215     alias map5 = map.set!("a", 4);
216     static assert(map5.get!"a" == 4);
217     
218     template inc(string key, int val)
219     {
220         alias inc = ExpressionList!(key, val+1);
221     }
222     
223     alias map6 = map.map!inc;
224     static assert(map6.get!"a" == 43);
225     static assert(map6.get!("a", "b") == ExpressionList!(43, 24));
226     
227     static assert(map.keys == ExpressionList!("a", "b"));
228     static assert(map.values == ExpressionList!(42, 23));
229 }