1 /// General-purpose wrapper/construct types for interacting with types.
2 module tern.typecons.common;
3 
4 import tern.memory : memcpy;
5 import tern.traits;
6 import tern.object : qdup, factory, loadLength;
7 import std.conv;
8 
9 /// Implements all functions of an abstract class with an default/empty function.
10 public class BlackHole(T)
11     if (isAbstractClass!T)
12 {
13     mixin(fullIdentifier!T~" val;
14     alias val this;");
15     static foreach (func; Functions!T)
16     {
17         static if (isAbstractFunction!(getChild!(T, func)))
18         {
19             static if (isNoReturn!(getChild!(T, func)))
20                 mixin(FunctionSignature!(getChild!(T, func))~" { return "~fullIdentifier!(ReturnType!(getChild!(T, func)))~".init; }");
21             else
22                 mixin(FunctionSignature!(getChild!(T, func))~" { }");
23         }
24     }
25 }
26 
27 /// Implements all functions of an abstract class with an assert trap.
28 public class WhiteHole(T)
29     if (isAbstractClass!T)
30 {
31     mixin(fullIdentifier!T~" val;
32     alias val this;");
33     static foreach (func; Functions!T[0..$-5])
34     {
35         static if (isAbstractFunction!(getChild!(T, func)))
36             mixin(FunctionSignature!(getChild!(T, func))~" { assert(0); }");
37     }
38 }
39 
40 /**
41  * Wraps `T` to allow it to be defined as null.  
42  * No, this is not actually an optional, it is literally backed by a pointer and thus *actually* nullable.
43  *
44  * Remarks:
45  *  - `opOpAssign` is not supported for fields of `T`.
46  *  - const Nullable(T) is not supported, but shared Nullable(T) is.
47  */
48 private alias NULL = typeof(null);
49 public struct Nullable(T)
50 {
51     T value;
52     alias value this;
53 
54 public:
55 final:
56     T* ptr;
57 
58     this(T val)
59     {
60         value = val;
61         ptr = &value;
62     }
63 
64     this(NULL val)
65     {
66 
67     }
68 
69     auto opAssign(A)(A ahs)
70     {
71         value = ahs;
72         ptr = &value;
73         return this;
74     }
75 
76     auto opAssign(A)(A ahs) shared
77     {
78         value = ahs;
79         ptr = &value;
80         return this;
81     }
82 
83     A opCast(A)() const
84     {
85         return Nullable!A(cast(A)value);
86     }
87     
88     A opCast(A)() const shared
89     {
90         return Nullable!A(cast(A)value);
91     }
92 
93     auto opUnary(string op)()
94     {
95         static if (op.length == 2)
96             ptr = &value;
97 
98         if (ptr == null)
99             throw new Throwable("Null object reference T.T");
100 
101         return mixin("Nullable!T(cast(T)("~op~"value))");
102     }
103 
104     auto opUnary(string op)() shared
105     {
106         static if (op.length == 2)
107             ptr = &value;
108 
109         if (ptr == null)
110             throw new Throwable("Null object reference T.T");
111 
112         return mixin("Nullable!T(cast(T)("~op~"value))");
113     }
114 
115     auto opEquals(A)(A ahs) const
116     {
117         static if (is(A == NULL))
118             return ptr == null;
119         else
120             return value == ahs;
121     }
122 
123     auto opEquals(A)(A ahs) const shared
124     {
125         static if (is(A == NULL))
126             return ptr == null;
127         else
128             return value == ahs;
129     }
130 
131     int opCmp(A)(A ahs) const
132     {
133         if (ptr == null)
134             throw new Throwable("Null object reference T.T");
135 
136         static if (isScalar!T)
137             return cast(int)(value - ahs);
138         else
139             return mixin("value.opCmp(ahs)");
140     }
141 
142     int opCmp(A)(A ahs) const shared
143     {
144         if (ptr == null)
145             throw new Throwable("Null object reference T.T");
146 
147         static if (isScalar!T)
148             return cast(int)(value - ahs);
149         else
150             return mixin("value.opCmp(ahs)");
151     }
152 
153     auto opOpAssign(string op, R)(R rhs)
154     {
155         if (ptr == null)
156             throw new Throwable("Null object reference T.T");
157 
158         mixin("value "~op~"= rhs;");
159         return this;
160     }
161 
162     auto opOpAssign(string op, R)(R rhs) shared
163     {
164         if (ptr == null)
165             throw new Throwable("Null object reference T.T");
166 
167         mixin("value "~op~"= rhs;");
168         return this;
169     }
170 
171     auto opBinary(string op, R)(const R rhs)
172     {
173         if (ptr == null)
174             throw new Throwable("Null object reference T.T");
175 
176         return mixin("Nullable!T(cast(T)(value "~op~" rhs))");
177     }
178 
179     auto opBinary(string op, R)(const R rhs) shared
180     {
181         if (ptr == null)
182             throw new Throwable("Null object reference T.T");
183 
184         return mixin("Nullable!T(cast(T)(value "~op~" rhs))");
185     }
186 
187     auto opBinaryRight(string op, L)(const L lhs)
188     {
189         if (ptr == null)
190             throw new Throwable("Null object reference T.T");
191 
192         return mixin("Nullable!T(cast(T)(lhs "~op~" value));");
193     }
194 
195     auto opBinaryRight(string op, L)(const L lhs) shared
196     {
197         if (ptr == null)
198             throw new Throwable("Null object reference T.T");
199 
200         return mixin("Nullable!T(cast(T)(lhs "~op~" value));");
201     }
202 
203     template opDispatch(string member) 
204     {
205         template opDispatch(TARGS...) 
206         {
207             auto opDispatch(string member, ARGS...)(ARGS args)
208             {
209                 if (ptr == null)
210                     throw new Throwable("Null object reference T.T");
211 
212                 static if (seqContains!(member, Fields!T))
213                     mixin("return value."~member~" = args[0];");
214                 else static if (seqContains!(member, Functions!T))
215                     mixin("return value."~member~"(args);");
216             }
217 
218             auto opDispatch(string member, ARGS...)(ARGS args) shared
219             {
220                 if (ptr == null)
221                     throw new Throwable("Null object reference T.T");
222 
223                 else static if (seqContains!(member, Functions!T) || 
224                     __traits(compiles, { mixin("return value."~member~"(args);"); }) ||
225                     !__traits(compiles, { mixin("return value."~member~" = args[0];"); }))
226                     mixin("return value."~member~"(args);");
227                 else
228                     mixin("return value."~member~" = args[0];");
229             }
230         }
231     }
232 
233     string toString() const
234     {
235         if (ptr == null)
236             return "null";
237 
238         return to!string(value);
239     }
240 
241     string toString() const shared
242     {
243         if (ptr == null)
244             return "null";
245 
246         return to!string(value);
247     }
248 
249     void nullify()
250     {
251         ptr = null;
252     }
253 
254     void unnullify()
255     {
256         ptr = &value;
257     }
258 }
259 
260 /// Helper function for creating a nullable.
261 Nullable!T nullable(T)(T val)
262 {
263     return Nullable!T(val);
264 }
265 
266 /// Helper function for creating a nullable.
267 Nullable!T nullable(T)(NULL val)
268 {
269     return Nullable!T(null);
270 }
271 
272 /// Highly mutable range wrapper for working with any indexable or sliceable type.
273 public struct Range(T)
274 {
275     T value;
276     alias value this;
277 
278 public:
279 final:
280     this(T val)
281     {
282         value = val.qdup;
283     }
284 
285     auto opAssign(T)(T val)
286     {
287         return value = val.qdup;
288     }
289 
290     void popFront()
291     {
292         value = this[1..$];
293     }
294 
295     void popBack()
296     {
297         value = this[0..$-1];
298     }
299 
300     string toString()
301     {
302         return value.to!string;
303     }
304 
305 @nogc:
306     A opCast(A)()
307     {
308         return cast(A)value;
309     }
310     
311     size_t length()
312     {
313         static if (__traits(compiles, { auto _ = opDollar(); }))
314             return opDollar();
315         static assert("Range!"~T.stringof~" has no length!");
316     }
317 
318     ref auto opIndex(size_t index)
319     {
320         static if (__traits(compiles, { auto _ = value[0]; }))
321             return value[index];
322         static assert("Range!"~T.stringof~" has no indexing!");
323     }
324 
325     auto opIndexAssign(A)(A ahs, size_t index)
326         if (A.sizeof <= ElementType!T.sizeof)
327     {
328         static if (__traits(compiles, { auto _ = (value[0] = ahs); }))
329             return value[index] = ahs;
330         else static if (__traits(compiles, { auto _ = value[0]; }))
331         {
332             memcpy(cast(void*)&ahs, cast(void*)&value[index], A.sizeof);
333             return value[index];
334         }
335         static assert("Range!"~T.stringof~" has no indexing!");
336     }
337 
338     auto opSlice(size_t start, size_t end)
339     {
340         static if (__traits(compiles, { auto _ = value[start..end]; }))
341             return value[start..end];
342         static assert("Range!"~T.stringof~" has no slicing!");
343     }
344 
345     auto opSliceAssign(A)(A ahs, size_t start, size_t end) 
346         if (ElementType!A.sizeof <= ElementType!T.sizeof)
347     {
348         static if (__traits(compiles, { auto _ = (value[start..end] = ahs); }))
349             return value[start..end] = ahs;
350         else static if (__traits(compiles, { auto _ = value[start]; }))
351         {
352             memcpy(cast(void*)ahs.ptr, cast(void*)&value[start], ElementType!A.sizeof * ahs.length);
353             return value[start..end];
354         }
355         return value;
356     }
357 
358     size_t opDollar(size_t DIM : 0)()
359     {
360         static if (__traits(compiles, { auto _ = value.opDollar!DIM; }))
361             return value.opDollar!DIM;
362         else static if (DIM == 0)
363             return opDollar();
364         else
365         {
366             size_t length;
367             foreach (u; value[DIM])
368                 length++;
369             return length;
370         }
371         static assert("Range!"~T.stringof~" has no length!");
372     }
373 
374     size_t opDollar()
375     {
376         static if (__traits(compiles, { auto _ = value.opDollar(); }))
377             return value.opDollar();
378         else static if (__traits(compiles, { auto _ = value.length; }))
379             return value.length;
380         else
381         {
382             size_t length;
383             foreach (u; value)
384                 length++;
385             return length;
386         }
387         static assert("Range!"~T.stringof~" has no length!");
388     }
389 
390     auto front()
391     {
392         return this[0];
393     }
394 
395     auto back()
396     {
397         return this[$-1];
398     }
399 
400     bool empty()
401     {
402         return length == 0;
403     }
404 }