1 module nxt.array_help;
2 
3 import core.internal.traits : Unqual;
4 
5 @safe:
6 
7 /** Returns: statically (stack) allocated array with elements of type `T` of
8  * length `n`.
9  *
10  * For more convenient usage alias it as `s' together with UFCS for the
11  * following convenient notation:
12  *
13  * const x = [1, 2, 3].s;
14  *
15  * TODO: Replace with Phobos `staticArray` when dmd automatically does move for uncopyable `T`.
16  *
17  * TODO: Fix problems discussed here: http://forum.dlang.org/post/otrsanpgmokzpzqmfyvx@forum.dlang.org
18  * TODO: File a bug report: http://forum.dlang.org/post/otrsanpgmokzpzqmfyvx@forum.dlang.org
19  *
20  * TODO: fix compiler so that move kicks in here automatically and remove
21  * special case on `isCopyable`
22  *
23  * See_Also: http://dpaste.dzfl.pl/d0059e6e6c09
24  * See_Also: http://forum.dlang.org/post/oq0cd1$2ji3$1@digitalmars.com
25  */
26 Unqual!T[n] staticArray(T, size_t n)(T[n] x) @trusted
27 {
28 	static if (__traits(isCopyable, T))  /+ TODO: remove when compiler does move for us +/
29 		return x[];
30 	else {					  /+ TODO: remove `move` when compiler does it for us +/
31 		/+ TODO: remove `move` when compiler does it for us: +/
32 		T[n] y = void;		// initialized below
33 		import core.internal.traits : hasElaborateDestructor;
34 		static if (hasElaborateDestructor!T) {
35 			import core.lifetime : move;
36 			/* NOTE: moveEmplaceAll doesn't support uncopyable elements
37 			 * import std.algorithm.mutation : moveEmplaceAll;
38 			 * moveEmplaceAll(x[], y[]);
39 			 */
40 			foreach (const ix, ref value; x)
41 				move(value, y[ix]);
42 		} else {
43 			import core.stdc.string : memcpy;
44 			memcpy(y.ptr, x.ptr, n*T.sizeof); // fast
45 		}
46 		return y;
47 	}
48 }
49 alias s = staticArray;
50 
51 pure @safe unittest {
52 	// assert([].staticArray.ptr == null);
53 	assert([].s.length == 0);
54 }
55 
56 version (none)
57 pure @safe unittest {
58 	import std.array : staticArray;
59 	assert([].staticArray.ptr !is null);
60 	assert([].staticArray.length == 0);
61 }
62 
63 /** Make a static array. */
64 version (none)
65 auto staticArrayAlternative() @safe {
66 	static struct _staticArray {
67 		T[n] s(T, size_t n)(auto ref T[n] values) @safe @property { return values; }
68 
69 		T[0][n] opIndex(size_t n = T.length, T...)(T items) {
70 			typeof(return) arr;
71 			foreach (index,item; items)
72 				arr[index] = item;
73 
74 			return (values) { return values; } (arr);//s!(T[0], n)(arr);
75 		}
76 	}
77 	return _staticArray();
78 }
79 
80 version (unittest) {
81 	static struct US {
82 		this(this) @disable;
83 		int x;
84 		void f() { x = 42; }
85 	}
86 }
87 
88 ///
89 pure nothrow @safe @nogc unittest {
90 	auto a = [1, 2, 3].staticArray;
91 	static assert(is(typeof(a) == int[a.length]));
92 	static assert(is(typeof([1, 2, 3].staticArray) == int[a.length]));
93 
94 	auto b = "hello".s;
95 	static assert(is(typeof(b) == char[5]));
96 
97 	auto x = (cast(ubyte[])[1u, 2u, 3u]).s;
98 	static assert(is(typeof(x) == ubyte[3]));
99 }
100 
101 /// non-copyable element type in static array
102 pure nothrow @safe @nogc unittest {
103 	auto b = [US(42)].s;
104 	static assert(b.length == 1);
105 }
106 
107 ///
108 pure nothrow @safe @nogc unittest {
109 	auto x = [1, 2, 3].staticArray;
110 
111 	static assert(is(typeof(x) == int[x.length]));
112 	static assert(is(typeof([1, 2, 3].staticArray) == int[x.length]));
113 
114 	static assert(!__traits(compiles, {
115 		static int[] doNotDoThat() {
116 			return [1, 2, 3].s;
117 		}
118 	}));
119 }
120 
121 /** Returns: `x` as a static array of unsigned bytes. */
122 @property ubyte[T.sizeof] toUbytes(T)(in T x) @trusted pure nothrow @nogc /+ TODO: endian-dependent +/
123 	=> (cast(ubyte*)(&x))[0 .. x.sizeof];
124 
125 /** Returns: `x` as a static array with elements of type `E`. */
126 @property ref inout(E)[T.sizeof] asN(E, T)(inout ref T x) @trusted pure nothrow @nogc /+ TODO: endian-dependent +/
127 if (T.sizeof % E.sizeof == 0)
128 	=> (cast(E*)(&x))[0 .. x.sizeof];
129 
130 ///
131 pure nothrow @safe @nogc unittest {
132 	immutable ushort x = 17;
133 	auto y = x.asN!ubyte;
134 	version(LittleEndian)
135 		assert(y == [17, 0].s);
136 }
137 
138 /// Number of bytes in a word.
139 private enum wordBytes = size_t.sizeof;
140 
141 /// Number of bits in a word.
142 private enum wordBits = 8*wordBytes;
143 
144 /** Returns: number of words (`size_t`) needed to represent
145  * `bitCount` bits.
146  */
147 static size_t wordCountOfBitCount(size_t bitCount) pure nothrow @safe @nogc
148 	=> ((bitCount / wordBits) +
149 	(bitCount % wordBits != 0 ? 1 : 0));
150 
151 static size_t binBlockBytes(size_t bitCount) pure nothrow @safe @nogc
152 	=> wordBytes*wordCountOfBitCount(bitCount);
153 
154 /** Returns: an uninitialized bit-array containing `bitCount` number of bits. */
155 size_t* makeUninitializedBitArray(alias Allocator)(size_t bitCount) @trusted pure nothrow @nogc
156 	=> cast(typeof(return))Allocator.instance.allocate(binBlockBytes(bitCount));
157 
158 T[] makeArrayZeroed(T, alias Allocator)(size_t numBytes) @trusted pure nothrow @nogc {
159 	import std.experimental.allocator : makeArray;
160 	/+ TODO: activate +/
161 	// static if (__traits(isZeroInit, T) &&
162 	//			__traits(hasMember, Allocator, "allocateZeroed"))
163 	// {
164 	// 	return cast(typeof(return))makeArray!(T)(Allocator.instance, numBytes);
165 	// }
166 	// else
167 	{
168 		import core.stdc.string : memset;
169 		auto result = makeArray!(T)(Allocator.instance, numBytes);
170 		memset(result.ptr, 0, numBytes);
171 		return result;
172 	}
173 }
174 
175 /** Returns: an zero-initialized bit-array containing `bitCount` number of bits. */
176 size_t[] makeBitArrayZeroed(alias Allocator)(size_t bitCount) @trusted pure nothrow @nogc {
177 	version (D_Coverage) {} else pragma(inline, true);
178 	return cast(typeof(return))makeArrayZeroed!(size_t, Allocator)(binBlockBytes(bitCount)); /+ TODO: check aligned allocate +/
179 }
180 
181 /** Returns: `input` reallocated to contain `newBitCount` number of bits. New bits
182  * are default-initialized to zero.
183  */
184 size_t* makeReallocatedBitArrayZeroPadded(alias Allocator)(size_t* input,
185 														   const size_t currentBitCount,
186 														   const size_t newBitCount) @system
187 if (__traits(hasMember, Allocator, "reallocate")) {
188 	assert(currentBitCount < newBitCount, "no use reallocate to same size");
189 
190 	immutable currentWordCount = wordCountOfBitCount(currentBitCount);
191 	immutable newWordCount = wordCountOfBitCount(newBitCount);
192 
193 	auto rawArray = cast(void[])(input[0 .. currentWordCount]);
194 
195 	const ok = Allocator.instance.reallocate(rawArray, newWordCount*wordBytes);
196 	assert(ok, "couldn't reallocate input");
197 	input = cast(size_t*)rawArray.ptr;
198 
199 	// See: https://forum.dlang.org/post/puolgthmxgacveqasqkk@forum.dlang.org
200 	input[currentWordCount .. newWordCount] = 0;
201 
202 	return input;
203 }
204 
205 ///
206 @trusted pure nothrow @nogc unittest {
207 	enum bitCount = 8*size_t.sizeof + 1;
208 
209 	size_t* x = makeUninitializedBitArray!(Allocator)(bitCount);
210 	Allocator.instance.deallocate(cast(void[])(x[0 .. wordCountOfBitCount(bitCount)]));
211 }
212 
213 ///
214 @trusted pure nothrow @nogc unittest {
215 	size_t bitCount = 1;
216 	size_t* y = makeBitArrayZeroed!(Allocator)(bitCount).ptr; // start empty
217 	while (bitCount < 100_000) {
218 		const newBitCount = bitCount * 2;
219 		y = makeReallocatedBitArrayZeroPadded!(Allocator)(y, bitCount, newBitCount);
220 		bitCount = newBitCount;
221 
222 		// check contents
223 		foreach (immutable bitIndex; 0 .. bitCount)
224 			assert(bt(y, bitIndex) == 0);
225 	}
226 	Allocator.instance.deallocate(cast(void[]) (y[0 .. wordCountOfBitCount(bitCount)]));
227 }
228 
229 version (unittest) {
230 	import core.bitop : bt;
231 	import std.experimental.allocator.mallocator : Allocator = Mallocator;
232 }