1 module dmdtags.appender;
2 
3 import core.memory: GC;
4 
5 enum initialCapacity = 16;
6 enum growFactor = 2;
7 
8 // Simple extern(C++)-compatible Appender
9 struct Appender(T)
10 {
11 	private static struct Header
12 	{
13 		size_t length;
14 		size_t capacity;
15 		T[0] data; // for alignment
16 	}
17 
18 	private Header* header;
19 
20 	private static size_t allocSize(size_t capacity)
21 	{
22 		import std.experimental.checkedint: checked;
23 
24 		return (Header.sizeof + T.sizeof.checked * capacity).get;
25 	}
26 
27 	private inout(T)[] data() inout
28 	{
29 		if (header is null) {
30 			return null;
31 		} else {
32 			return header.data.ptr[0 .. header.length];
33 		}
34 	}
35 
36 	inout(T)[] opIndex() inout
37 	{
38 		return data;
39 	}
40 
41 	void put(T item)
42 	{
43 		import std.experimental.checkedint: checked;
44 
45 		if (header is null) {
46 			header = cast(Header*) GC.malloc(allocSize(initialCapacity));
47 			header.length = 0;
48 			header.capacity = initialCapacity;
49 		}
50 
51 		assert(header.length <= header.capacity);
52 
53 		if (header.length == header.capacity) {
54 			T[] oldData = data;
55 			Header* oldHeader = header;
56 
57 			immutable size_t newCapacity = (oldHeader.capacity.checked * growFactor).get;
58 			header = cast(Header*) GC.malloc(allocSize(newCapacity));
59 			header.length = oldHeader.length;
60 			header.capacity = newCapacity;
61 
62 			data[] = oldData[];
63 		}
64 
65 		header.length += 1;
66 		data[$ - 1] = item;
67 	}
68 }
69 
70 unittest {
71 	import std.range: iota;
72 	import std.array: array;
73 
74 	Appender!int app;
75 
76 	foreach (i; 0 .. 20) {
77 		app.put(i);
78 	}
79 
80 	assert(app[] == iota(20).array);
81 }