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