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 }