1 module dmocks.dynamic; 2 3 import std.conv; 4 import std.traits; 5 6 /++ 7 + This is a very very simple class for storing a variable regardless of it's size and type 8 +/ 9 abstract class Dynamic 10 { 11 // toHash, toString and opEquals are also part of this class 12 // but i'm not sure how to express that in code so this comment has to be enough:) 13 14 /// returns stored typeinfo 15 abstract TypeInfo type(); 16 /// converts stored value to given "to" type and returns 1el array of target type vals or null when conversion failed 17 abstract void[] convertTo(TypeInfo to); 18 19 /// returns true if variable held by dynamic is convertible to given type 20 bool canConvertTo(TypeInfo to) 21 { 22 return type==to || convertTo(to) !is null; 23 } 24 } 25 26 /// returns stored value if type T is precisely the type of variable stored, variable stored can be implicitly to that type 27 T get(T)(Dynamic d) 28 { 29 import std.exception : enforce; 30 import std.format : format; 31 32 // in addition to init property requirement disallow user defined value types which can have alias this to null-able type 33 static if (!is(T==union) && !is(T==struct) && is(typeof(T.init is null))) 34 { 35 if (d.type == typeid(typeof(null))) 36 return null; 37 } 38 if (d.type == typeid(T)) 39 return ((cast(DynamicT!T)d).data()); 40 41 void[] convertResult = d.convertTo(typeid(T)); 42 43 enforce(convertResult, format!"Cannot convert stored value of type '%s' to '%s'!"(d.type, T.stringof)); 44 45 return (cast(T*)convertResult)[0]; 46 } 47 48 /// a helper function for creating Dynamic obhects 49 Dynamic dynamic(T)(auto ref T t) 50 { 51 return new DynamicT!T(t); 52 } 53 54 class DynamicT(T) : Dynamic 55 { 56 private T _data; 57 this(T t) 58 { 59 _data = t; 60 } 61 62 /// 63 override TypeInfo type() 64 { 65 return typeid(T); 66 } 67 68 /// 69 override string toString() 70 { 71 static if (is(typeof(_data) == class) && !hasFunctionAttributes!(typeof(_data).toString, "const")) 72 { 73 return (cast(Unqual!(typeof(_data))) _data).toString(); 74 } 75 else 76 { 77 return _data.to!string; 78 } 79 } 80 81 /// two dynamics are equal when they store same type and the values pass opEquals 82 override bool opEquals(Object object) 83 { 84 auto dyn = cast(DynamicT!T)object; 85 if (dyn is null) 86 return false; 87 if (dyn.type != type) 88 return false; 89 90 return _data == dyn._data; 91 } 92 93 /// 94 override size_t toHash() 95 { 96 return typeid(T).getHash(&_data); 97 } 98 99 /// 100 T data() 101 { 102 return _data; 103 } 104 105 /// 106 override void[] convertTo(TypeInfo to) 107 { 108 foreach(target;ImplicitConversionTargets!(T)) 109 { 110 if (typeid(target) == to) 111 { 112 auto ret = new target[1]; 113 ret[0] = cast(target)_data; 114 return ret; 115 } 116 } 117 118 return null; 119 } 120 } 121 122 version (unittest) 123 { 124 class A 125 { 126 } 127 128 class B : A 129 { 130 } 131 132 struct C 133 { 134 } 135 136 struct D 137 { 138 private C _c; 139 alias _c this; 140 } 141 } 142 143 unittest 144 { 145 auto d = dynamic(6); 146 assert(d.toString == "6"); 147 assert(d.type.toString == "int"); 148 auto e = dynamic(6); 149 assert(e == d); 150 assert(e.get!int == 6); 151 } 152 153 unittest 154 { 155 auto d = dynamic(new B); 156 assert(d.get!A !is null); 157 assert(d.get!B !is null); 158 } 159 160 unittest 161 { 162 auto d = dynamic(null); 163 assert(d.get!A is null); 164 } 165 166 unittest 167 { 168 int[5] a; 169 auto d = dynamic(a); 170 assert(d.get!(int[5]) == [0,0,0,0,0]); 171 } 172 173 unittest 174 { 175 float f; 176 auto d = dynamic(f); 177 bool errored = false; 178 179 try 180 { 181 int i = d.get!int; 182 } 183 catch (Exception) 184 { 185 errored = true; 186 } 187 188 assert(errored); 189 } 190 191 /+ ImplicitConversionTargets doesn't include alias thises 192 unittest 193 { 194 auto d = dynamic(D()); 195 d.get!C; 196 d.get!D; 197 } 198 +/ 199 200 @("supports const object with non-const toString") 201 unittest 202 { 203 static assert(is(typeof(dynamic(new const Object)))); 204 static assert(is(typeof(dynamic(new immutable Object)))); 205 }