1 module nxt.variant_arrays;
2 
3 @safe:
4 
5 /** Typed index (reference) into an element in `VariantArrays`.
6  */
7 private struct VariantRef(DefinedTypes...)
8 {
9     @safe:
10 
11     import std.meta : staticIndexOf;
12 
13     alias Kind = ubyte;              // kind code
14     alias Size = size_t;             // size type
15 
16     import nxt.bit_traits : bitsNeeded;
17 
18     /// Used to indicate undefined value.
19     private struct Undefined {}
20 
21     import std.meta : AliasSeq;
22 
23     alias Types = AliasSeq!(Undefined, DefinedTypes);
24 
25     /// Number of bits needed to represent kind.
26     private enum kindBits = bitsNeeded!(Types.length);
27 
28     /** Get number kind of kind type `SomeKind`.
29         TODO make private?
30      */
31     enum nrOfKind(SomeKind) = staticIndexOf!(SomeKind, Types); // TODO cast to ubyte if Types.length is <= 256
32 
33     /// Is `true` iff an index to a `SomeKind`-kind can be stored.
34     enum canReferenceType(SomeKind) = nrOfKind!SomeKind >= 0;
35 
36     /// Comparsion works like for integers.
37     int opCmp(in typeof(this) rhs) const @trusted
38     {
39         version(LDC) pragma(inline, true);
40         if (this.rawWord < rhs.rawWord)
41         {
42             return -1;
43         }
44         if (this.rawWord > rhs.rawWord)
45         {
46             return +1;
47         }
48         return 0;
49     }
50 
51     pragma(inline, true):
52 
53     /// Construct from mutable `that`.
54     this(in typeof(this) that)
55     {
56         rawWord = that.rawWord;
57     }
58     /// Construct from constant `that`.
59     this(typeof(this) that)
60     {
61         rawWord = that.rawWord;
62     }
63 
64     /// Construct.
65     this(Kind kind, Size index) // TODO can ctor inferred by bitfields?
66     {
67         _kindNr = kind;
68         _index = index;
69     }
70 
71     /// Construct from raw word representation `rawWord`.
72     private this(Size rawWord)
73     {
74         this.rawWord = rawWord;
75     }
76 
77     /// Get kindNr.
78     Kind kindNr() const
79     {
80         return _kindNr;
81     }
82 
83     /// Get index.
84     Size index() const
85     {
86         return _index;
87     }
88 
89     /// Cast to `size_t`.
90     size_t opCast(T : size_t)() const
91     {
92         return rawWord;
93     }
94 
95     import core.internal.traits : Unqual;
96 
97     /// Allow cast to unqualified.
98     U opCast(U : Unqual!(typeof(this)))() const
99     {
100         return U(rawWord);
101     }
102 
103     /// The index itself is the hash.
104     hash_t toHash() const @property { return rawWord; }
105     static assert(hash_t.sizeof == rawWord.sizeof);
106 
107     /// Cast to `bool`, meaning 'true' if defined, `false` otherwise.
108     bool opCast(U : bool)() const { return isDefined(); }
109 
110     /// Returns: `true` iff is defined.
111     bool isDefined() const { return rawWord != 0; }
112 
113     /// Returns: `true` iff `this` targets a value of type `SomeKind`.
114     public bool isA(SomeKind)() const
115     {
116         return nrOfKind!(SomeKind) == _kindNr;
117     }
118 
119     @property void toString(scope void delegate(scope const(char)[]) sink) const @trusted
120     {
121         import std.format : formattedWrite;
122         if (isDefined)
123         {
124             sink.formattedWrite!`%s(%s@%s)`(Unqual!(typeof(this)).stringof, _index, _kindNr);
125         }
126         else
127         {
128             sink.formattedWrite!`%s(null)`(Unqual!(typeof(this)).stringof);
129         }
130     }
131 
132     import std.bitmanip : bitfields;
133     union
134     {
135         // TODO big-endian machine
136         mixin(bitfields!(Kind, "_kindNr", kindBits,
137                          Size, "_index", 8*Size.sizeof - kindBits));
138         Size rawWord;           // for comparsion
139     }
140 
141     static assert(this.sizeof == Size.sizeof,
142                   `This should haven't any memory overhead compared to size_t`);
143 }
144 
145 @safe pure nothrow unittest
146 {
147     alias R = VariantRef!(int, float);
148     R r;
149 
150     import std.array : Appender;
151     Appender!(const(R)[]) app;
152 
153     const R x;
154     R mx = x;
155 
156     // TODO app ~= x;
157 
158     const y = [R.init, R.init];
159     // TODO app ~= y;
160 }
161 
162 unittest
163 {
164     alias R = VariantRef!(int, float);
165     R r;
166     import std.conv : to;
167     assert(r.to!string == `VariantRef!(int, float)(null)`);
168 }
169 
170 private mixin template VariantArrayOf(Type)
171 {
172     import nxt.dynamic_array : DynamicArray;
173     DynamicArray!Type store;
174 }
175 
176 /** Stores set of variants.
177 
178     Enables lightweight storage of polymorphic objects.
179 
180     Each element is indexed by a corresponding `VariantRef`.
181  */
182 struct VariantArrays(Types...)
183 {
184     @safe:
185 
186     alias Ref = VariantRef!Types;
187 
188     import nxt.dynamic_array : DynamicArray;
189 
190     /// Returns: array type (as a string) of `Type`.
191     private static immutable(string) arrayTypeStringOfIndex(uint typeIndex)()
192     {
193         pragma(inline, true);
194         return `DynamicArray!(Types[` ~ typeIndex.stringof ~ `])`;
195     }
196 
197     /** Returns: array instance (as a strinng) storing `SomeKind`.
198      * TODO make this a template mixin
199      */
200     private static immutable(string) arrayInstanceString(SomeKind)()
201     if (Ref.canReferenceType!SomeKind)
202     {
203         pragma(inline, true);
204         return `_store` ~ Ref.nrOfKind!(SomeKind).stringof; // previously `SomeKind.mangleof`
205     }
206 
207     /// Make reference to type `SomeKind` at offset `index`.
208     static Ref makeRef(SomeKind)(Ref.Size index)
209     if (Ref.canReferenceType!SomeKind)
210     {
211         pragma(inline, true);
212         return Ref(Ref.nrOfKind!SomeKind, index);
213     }
214 
215     /** Insert `value` at back.
216      */
217     Ref insertBack(SomeKind)(SomeKind value) // TODO add array type overload
218     if (Ref.canReferenceType!SomeKind)
219     {
220         mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
221         const currentIndex = arrayInstance.length;
222         arrayInstance.insertBackMove(value);
223         return Ref(Ref.nrOfKind!SomeKind,
224                    currentIndex);
225     }
226     alias put = insertBack;     // polymorphic `OutputRange` support
227 
228     /** Move (emplace) `value` into back.
229      */
230     Ref insertBackMove(SomeKind)(ref SomeKind value) // TODO add array type overload
231     if (Ref.canReferenceType!SomeKind)
232     {
233         version(DigitalMars) pragma(inline, false); // DMD cannot inline
234         mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
235         const currentIndex = arrayInstance.length;
236         arrayInstance.insertBackMove(value);
237         return Ref(Ref.nrOfKind!SomeKind,
238                    currentIndex);
239     }
240 
241     /// ditto
242     void opOpAssign(string op, SomeKind)(SomeKind value) // TODO add array type overload
243     if (op == "~" &&
244         Ref.canReferenceType!SomeKind)
245     {
246         pragma(inline, true);
247         insertBackMove(value);  // move enables uncopyable types
248     }
249 
250     /// Get reference to element of type `SomeKind` at `index`.
251     scope ref inout(SomeKind) at(SomeKind)(in size_t index) inout return
252     if (Ref.canReferenceType!SomeKind)
253     {
254         pragma(inline, true);
255         mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[index];`);
256     }
257 
258     /// Get reference to element of type `SomeKind` at `ref_`.
259     scope ref inout(SomeKind) at(SomeKind)(in Ref ref_) inout return
260     if (Ref.canReferenceType!SomeKind)
261     {
262         pragma(inline, true);
263         assert(Ref.nrOfKind!SomeKind == ref_._kindNr,
264                "Ref is not of expected template type " ~ SomeKind.stringof);
265         mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[ref_.index];`);
266     }
267 
268     /// Peek at element of type `SomeKind` at `ref_`.
269     scope inout(SomeKind)* peek(SomeKind)(in Ref ref_) inout return @system
270     if (Ref.canReferenceType!SomeKind)
271     {
272         pragma(inline, true);
273         if (Ref.nrOfKind!SomeKind == ref_._kindNr)
274         {
275             return &at!SomeKind(ref_._index);
276         }
277         else
278         {
279             return null;
280         }
281     }
282 
283     /// Constant access to all elements of type `SomeKind`.
284     scope inout(SomeKind)[] allOf(SomeKind)() inout return
285     if (Ref.canReferenceType!SomeKind)
286     {
287         pragma(inline, true);
288         mixin(`return ` ~ arrayInstanceString!SomeKind ~ `[];`);
289     }
290 
291     /// Reserve space for `newCapacity` elements of type `SomeKind`.
292     void reserve(SomeKind)(size_t newCapacity)
293     if (Ref.canReferenceType!SomeKind)
294     {
295         pragma(inline, true);
296         mixin(`alias arrayInstance = ` ~ arrayInstanceString!SomeKind ~ `;`);
297         arrayInstance.reserve(newCapacity);
298     }
299 
300     /** Returns: length of store. */
301     @property size_t length() const
302     {
303         pragma(inline, true);
304         typeof(return) lengthSum = 0;
305         foreach (Type; Types)
306         {
307             mixin(`lengthSum += ` ~ arrayInstanceString!Type ~ `.length;`);
308         }
309         return lengthSum;
310     }
311 
312     /** Check if empty. */
313     @property bool empty() const
314     {
315         pragma(inline, true);
316         return length == 0;
317     }
318 
319 private:
320     static if (false/*__VERSION__ >= 2076*/)
321     {
322         // static foreach (const typeIndex, Type; Types)
323         // {
324         //     // TODO is it better to use?: mixin VariantArrayOf!(Type);
325         //     mixin(arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`);
326         // }
327     }
328     else
329     {
330         mixin({
331                 string s = "";
332                 foreach (const typeIndex, Type; Types)
333                 {
334                     s ~= arrayTypeStringOfIndex!typeIndex ~ ` ` ~ arrayInstanceString!Type ~ `;`;
335                 }
336                 return s;
337             }());
338     }
339 }
340 
341 ///
342 @safe pure nothrow @nogc unittest
343 {
344     import nxt.minimal_fixed_array : MinimalFixedArray;
345 
346     alias Chars(uint capacity) = MinimalFixedArray!(char, capacity);
347     alias Chars7 = Chars!7;
348     alias Chars15 = Chars!15;
349     alias VA = VariantArrays!(ulong,
350                               Chars7,
351                               Chars15);
352 
353     VA data;
354     assert(data.length == 0);
355     assert(data.empty);
356 
357     const i0 = data.put(ulong(13));
358     assert(cast(size_t)i0 == 1);
359 
360     assert(i0.isA!ulong);
361     assert(data.at!ulong(0) == ulong(13));
362     assert(data.length == 1);
363     assert(!data.empty);
364     assert(data.allOf!ulong == [ulong(13)].s);
365 
366     const i1 = data.put(Chars7(`1234567`));
367     assert(cast(size_t)i1 == 2);
368 
369     // same order as in `Types`
370     assert(i0 < i1);
371 
372     assert(i1.isA!(Chars7));
373     assert(data.at!(Chars7)(0) == Chars7(`1234567`));
374     assert(data.allOf!(Chars7) == [Chars7(`1234567`)].s);
375     assert(data.length == 2);
376 
377     const i2 = data.put(Chars15(`123`));
378     assert(cast(size_t)i2 == 3);
379 
380     // same order as in `Types`
381     assert(i0 < i2);
382     assert(i1 < i2);
383 
384     assert(i2.isA!(Chars15));
385     assert(data.at!(Chars15)(0) == Chars15(`123`));
386     // TODO assert(data.allOf!(Chars15) == [Chars15(`123`)].s);
387     assert(data.length == 3);
388 
389     const i3 = data.put(Chars15(`1234`));
390     assert(cast(size_t)i3 == 7);
391 
392     // same order as in `Types`
393     assert(i0 < i3);
394     assert(i1 < i3);
395     assert(i2 < i3);            // same type, i2 added before i3
396 
397     assert(i3.isA!(Chars15));
398     assert(data.at!(Chars15)(1) == Chars15(`1234`));
399     assert(data.allOf!(Chars15) == [Chars15(`123`), Chars15(`1234`)].s);
400     assert(data.length == 4);
401 }
402 
403 version = extraTests;
404 
405 version(extraTests)
406 {
407 static private:
408     alias I = VariantRef!(Rel1, Rel2, Int);
409 
410     // relations
411     struct Rel1 { I[1] args; }
412     struct Rel2 { I[2] args; }
413 
414     alias S = VariantArrays!(Rel1, Rel2,
415                              Int);
416 
417     struct Int { int value; }
418 }
419 
420 ///
421 version(extraTests)
422 @safe pure nothrow @nogc unittest
423 {
424     S s;
425 
426     const S.Ref top = s.put(Rel1(s.put(Rel1(s.put(Rel2([s.put(Int(42)),
427                                                         s.put(Int(43))]))))));
428     assert(s.allOf!Rel1.length == 2);
429     assert(s.allOf!Rel2.length == 1);
430     assert(s.allOf!Int.length == 2);
431     assert(s.length == 5);
432 }
433 
434 /// put and peek
435 version(extraTests)
436 @system pure nothrow @nogc unittest
437 {
438     S s;
439 
440     const n = 10;
441     foreach (const i; 0 .. n)
442     {
443         S.Ref lone = s.put(Int(i));
444         Int* lonePtr = s.peek!Int(lone);
445         assert(lonePtr);
446         assert(*lonePtr == Int(i));
447     }
448     assert(s.length == 10);
449 }
450 
451 version(unittest)
452 {
453     import nxt.array_help : s;
454 }
455 
456 // version = benchmark;
457 
458 version(benchmark)
459 unittest
460 {
461     alias E = uint;
462     immutable n = 5_000_000;
463 
464     import std.stdio : writeln;
465     import std.datetime : MonoTime;
466     import std.meta : AliasSeq;
467     foreach (A; AliasSeq!(VariantArrays!(E)))
468     {
469         A a;
470 
471         immutable before = MonoTime.currTime();
472 
473         foreach (uint i; 0 .. n)
474         {
475             a ~= i;
476         }
477 
478         immutable after = MonoTime.currTime();
479 
480         writeln("Added ", n, " integer nodes into ", A.stringof, " in ", after - before);
481     }
482 
483 }