1 module beard.variant; 2 3 import beard.fold_left : foldLeft2; 4 import beard.contains : contains; 5 import beard.child_of : ChildOf; 6 import beard.io; 7 import std.c..string : memcpy; 8 import std.typetuple : staticIndexOf, allSatisfy; 9 import std.traits : Unqual; 10 11 private template maxSize(size_t _size) { 12 enum size = _size; 13 template add(U) { 14 alias maxSize!(_size > U.sizeof ? _size : U.sizeof) add; 15 } 16 } 17 18 class BadVariantCopy : Throwable { 19 this(string error) { super(error); } 20 } 21 22 // may store any of T or be empty. 23 // I would prefer only allowing empty if void is in T and creating an object 24 // of the first type when default initialising. 25 // Unfortunately D does not allow default constructors for structs :( 26 struct Variant(T...) { 27 alias T types; 28 enum size = foldLeft2!(maxSize!0u, T).size; 29 enum n_types = T.length; 30 31 void opAssign(U)(auto ref U rhs) { 32 static if (contains!(U, T)) { 33 // copying object references like this is okay 34 static if (is(T == class) && is(T == shared)) 35 memcpy(&value_, cast(const(void*)) &rhs, rhs.sizeof); 36 else 37 memcpy(&value_, &rhs, rhs.sizeof); 38 idx_ = staticIndexOf!(U, T); 39 } 40 else static if (is(U == Variant)) { 41 this.value_ = rhs.value_; 42 this.idx_ = rhs.idx_; 43 } 44 else static if (isVariant!U) { 45 struct copyVariant { 46 void opCall(T)(T t) { 47 static if (contains!(T, types)) 48 *dest = t; 49 else throw new BadVariantCopy( 50 "cannot store type source variant holds"); 51 } 52 53 void empty() { dest.reset(); } 54 55 this(Variant *v) { dest = v; } 56 Variant *dest; 57 } 58 59 rhs.apply(copyVariant(&this)); 60 } 61 else static assert(false, "invalid variant type"); 62 } 63 64 void printTo(S)(int indent, S stream) { 65 struct variantPrint { 66 void opCall(T)(T t) { printIndented(stream_, indent_, t); } 67 void empty() { printIndented(stream_, indent_, "<empty>"); } 68 69 this(S s, int indent) { stream_ = s; indent_ = indent; } 70 S stream_; 71 int indent_; 72 } 73 74 apply(variantPrint(stream, indent)); 75 } 76 77 // helper for creating forwarding array mixins 78 private static string makeFwd(uint idx)() { 79 static if (idx < T.length + 1) 80 return (idx ? "," : "[") ~ 81 "&fwd!" ~ idx.stringof ~ makeFwd!(idx + 1); 82 else 83 return "]"; 84 } 85 86 private auto applyStruct(F)(ref F f) { 87 alias typeof(f.opCall(T[0])) return_type; 88 89 static return_type fwd(uint i)(ref Variant t, ref F f) { 90 static if (i < T.length) 91 return f.opCall(t.as!(T[i])()); 92 else 93 return f.empty(); 94 } 95 96 static return_type function(ref Variant, ref F)[T.length + 1] forwarders = 97 mixin(makeFwd!0()); 98 99 return forwarders[this.idx_](this, f); 100 } 101 102 private static auto callMatching(A, F...)(auto ref A a, F f) { 103 // TODO: use something other than compiles which can 104 // hide genuine errors 105 static if (! F.length) { 106 static assert(false, "no matching function"); 107 } 108 else static if (__traits(compiles, f[0](a))) { 109 return f[0](a); 110 } 111 else static if (__traits(compiles, f[0].opCall(a))) { 112 return f[0].opCall(a); 113 } 114 else { 115 return callMatching(a, f[1..$]); 116 } 117 } 118 119 private static auto callEmpty(F...)(F f) { 120 static if (! F.length) { 121 static assert(false, "no matching function for empty"); 122 } 123 else static if (__traits(compiles, f[0]())) { 124 return f[0](); 125 } 126 else static if (__traits(compiles, f[0].empty())) { 127 return f[0].empty(); 128 } 129 else { 130 return callEmpty(f[1..$]); 131 } 132 } 133 134 private class None {}; 135 private template GetReturnType(T) { 136 static if(is(T return_type == return)) 137 alias return_type GetReturnType; 138 // static else if(__traits(compiles, T(...)) 139 // alias ... GetReturnType; 140 else 141 alias None GetReturnType; 142 } 143 144 // Helper for apply when using many function parameters. 145 private auto applyFunctions(F...)(F f) { 146 alias GetReturnType!(F[0]) return_type; 147 static if(! is(return_type : None)) { 148 static return_type fwd(uint i)(ref Variant t, F f) { 149 static if (i < T.length) { 150 alias T[i] ArgType; 151 return callMatching(t.as!ArgType, f); 152 } 153 else 154 return callEmpty(f); 155 } 156 157 static return_type function(ref Variant, F)[T.length + 1] forwarders = 158 mixin(makeFwd!0()); 159 160 return forwarders[this.idx_](this, f); 161 } 162 else { 163 static assert(false, "incorrect arguments"); 164 } 165 } 166 167 // This calls directly through a compile time constructed vtable. 168 // See the examples in test/variant.d, it's not as complicated as 169 // it seems. 170 auto apply(F...)(auto ref F f) { 171 static if (F.length == 1 && __traits(hasMember, f[0], "opCall")) 172 return applyStruct(f[0]); 173 else 174 return applyFunctions(f); 175 } 176 177 // Unsafe cast to a value, use apply to do this safely. 178 ref T as(T)() { return * cast(T*) &value_; } 179 180 // If U is a base class of all possible storable types, then return 181 // it. If the variant is empty the reference this returns will be garbage. 182 ref U base(U)() { 183 static assert(allSatisfy!(ChildOf!U.Eval, types), 184 "not a common base class"); 185 return as!U; 186 } 187 188 // Test if the variant is currently storing type U directly (not via 189 // superclass relationship). 190 bool isType(U)() { 191 static if (contains!(U, types)) 192 return idx_ == staticIndexOf!(U, types); 193 else 194 static assert(false, "type not in variant"); 195 } 196 197 // Test if the variant is empty. 198 bool empty() @property const { return idx_ >= T.length; } 199 200 // Make variant empty. 201 void reset() { 202 // run destructor on existing class? 203 idx_ = n_types; 204 } 205 206 //////////////////////////////////////////////////////////////////////// 207 this(U)(U rhs) { this = rhs; } 208 209 private: 210 union { 211 ubyte[size] value_; 212 // mark the region as a pointer to stop objects being garbage collected 213 static if (size >= (void*).sizeof) 214 void* p[size / (void*).sizeof]; 215 } 216 217 uint idx_ = n_types; 218 } 219 220 template isVariant(T) { 221 // d won't allow enum isVariant = is(...); 222 static if (is(Unqual!T Unused : Variant!U, U...)) 223 enum isVariant = true; 224 else 225 enum isVariant = false; 226 } 227 // vim:ts=4 sw=4