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