1 
2 /**
3  * Dynamic array implementation.
4  *
5  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/array.d, root/_array.d)
9  * Documentation:  https://dlang.org/phobos/dmd_root_array.html
10  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/array.d
11  */
12 
13 module dmd.root.array;
14 
15 import core.stdc.stdlib : _compare_fp_t;
16 import core.stdc.string;
17 
18 import dmd.root.rmem;
19 import dmd.root.string;
20 
21 // `qsort` is only `nothrow` since 2.081.0
22 private extern(C) void qsort(scope void* base, size_t nmemb, size_t size, _compare_fp_t compar) nothrow @nogc;
23 
24 
25 debug
26 {
27     debug = stomp; // flush out dangling pointer problems by stomping on unused memory
28 }
29 
30 extern (C++) struct Array(T)
31 {
32     size_t length;
33 
34 private:
35     T[] data;
36     enum SMALLARRAYCAP = 1;
37     T[SMALLARRAYCAP] smallarray; // inline storage for small arrays
38 
39 public:
40     /*******************
41      * Params:
42      *  dim = initial length of array
43      */
44     this(size_t dim) pure nothrow
45     {
46         reserve(dim);
47         this.length = dim;
48     }
49 
50     @disable this(this);
51 
52     ~this() pure nothrow
53     {
54         debug (stomp) memset(data.ptr, 0xFF, data.length);
55         if (data.ptr != &smallarray[0])
56             mem.xfree(data.ptr);
57     }
58     ///returns elements comma separated in []
59     extern(D) const(char)[] toString() const
60     {
61         static const(char)[] toStringImpl(alias toStringFunc, Array)(Array* a, bool quoted = false)
62         {
63             const(char)[][] buf = (cast(const(char)[]*)mem.xcalloc((char[]).sizeof, a.length))[0 .. a.length];
64             size_t len = 2; // [ and ]
65             const seplen = quoted ? 3 : 1; // ',' or null terminator and optionally '"'
66             if (a.length == 0)
67                 len += 1; // null terminator
68             else
69             {
70                 foreach (u; 0 .. a.length)
71                 {
72                     buf[u] = toStringFunc(a.data[u]);
73                     len += buf[u].length + seplen;
74                 }
75             }
76             char[] str = (cast(char*)mem.xmalloc_noscan(len))[0..len];
77 
78             str[0] = '[';
79             char* p = str.ptr + 1;
80             foreach (u; 0 .. a.length)
81             {
82                 if (u)
83                     *p++ = ',';
84                 if (quoted)
85                     *p++ = '"';
86                 memcpy(p, buf[u].ptr, buf[u].length);
87                 p += buf[u].length;
88                 if (quoted)
89                     *p++ = '"';
90             }
91             *p++ = ']';
92             *p = 0;
93             assert(p - str.ptr == str.length - 1); // null terminator
94             mem.xfree(buf.ptr);
95             return str[0 .. $-1];
96         }
97 
98         static if (is(typeof(T.init.toString())))
99         {
100             return toStringImpl!(a => a.toString)(&this);
101         }
102         else static if (is(typeof(T.init.toDString())))
103         {
104             return toStringImpl!(a => a.toDString)(&this, true);
105         }
106         else
107         {
108             assert(0);
109         }
110     }
111     ///ditto
112     const(char)* toChars() const
113     {
114         return toString.ptr;
115     }
116 
117     ref Array push(T ptr) return pure nothrow
118     {
119         reserve(1);
120         data[length++] = ptr;
121         return this;
122     }
123 
124     extern (D) ref Array pushSlice(T[] a) return pure nothrow
125     {
126         const oldLength = length;
127         setDim(oldLength + a.length);
128         memcpy(data.ptr + oldLength, a.ptr, a.length * T.sizeof);
129         return this;
130     }
131 
132     ref Array append(typeof(this)* a) return pure nothrow
133     {
134         insert(length, a);
135         return this;
136     }
137 
138     void reserve(size_t nentries) pure nothrow
139     {
140         //printf("Array::reserve: length = %d, data.length = %d, nentries = %d\n", (int)length, (int)data.length, (int)nentries);
141         if (data.length - length < nentries)
142         {
143             if (data.length == 0)
144             {
145                 // Not properly initialized, someone memset it to zero
146                 if (nentries <= SMALLARRAYCAP)
147                 {
148                     data = SMALLARRAYCAP ? smallarray[] : null;
149                 }
150                 else
151                 {
152                     auto p = cast(T*)mem.xmalloc(nentries * T.sizeof);
153                     data = p[0 .. nentries];
154                 }
155             }
156             else if (data.length == SMALLARRAYCAP)
157             {
158                 const allocdim = length + nentries;
159                 auto p = cast(T*)mem.xmalloc(allocdim * T.sizeof);
160                 memcpy(p, smallarray.ptr, length * T.sizeof);
161                 data = p[0 .. allocdim];
162             }
163             else
164             {
165                 /* Increase size by 1.5x to avoid excessive memory fragmentation
166                  */
167                 auto increment = length / 2;
168                 if (nentries > increment)       // if 1.5 is not enough
169                     increment = nentries;
170                 const allocdim = length + increment;
171                 debug (stomp)
172                 {
173                     // always move using allocate-copy-stomp-free
174                     auto p = cast(T*)mem.xmalloc(allocdim * T.sizeof);
175                     memcpy(p, data.ptr, length * T.sizeof);
176                     memset(data.ptr, 0xFF, data.length * T.sizeof);
177                     mem.xfree(data.ptr);
178                 }
179                 else
180                     auto p = cast(T*)mem.xrealloc(data.ptr, allocdim * T.sizeof);
181                 data = p[0 .. allocdim];
182             }
183 
184             debug (stomp)
185             {
186                 if (length < data.length)
187                     memset(data.ptr + length, 0xFF, (data.length - length) * T.sizeof);
188             }
189             else
190             {
191                 if (mem.isGCEnabled)
192                     if (length < data.length)
193                         memset(data.ptr + length, 0xFF, (data.length - length) * T.sizeof);
194             }
195         }
196     }
197 
198     void remove(size_t i) pure nothrow @nogc
199     {
200         if (length - i - 1)
201             memmove(data.ptr + i, data.ptr + i + 1, (length - i - 1) * T.sizeof);
202         length--;
203         debug (stomp) memset(data.ptr + length, 0xFF, T.sizeof);
204     }
205 
206     void insert(size_t index, typeof(this)* a) pure nothrow
207     {
208         if (a)
209         {
210             size_t d = a.length;
211             reserve(d);
212             if (length != index)
213                 memmove(data.ptr + index + d, data.ptr + index, (length - index) * T.sizeof);
214             memcpy(data.ptr + index, a.data.ptr, d * T.sizeof);
215             length += d;
216         }
217     }
218 
219     void insert(size_t index, T ptr) pure nothrow
220     {
221         reserve(1);
222         memmove(data.ptr + index + 1, data.ptr + index, (length - index) * T.sizeof);
223         data[index] = ptr;
224         length++;
225     }
226 
227     void setDim(size_t newdim) pure nothrow
228     {
229         if (length < newdim)
230         {
231             reserve(newdim - length);
232         }
233         length = newdim;
234     }
235 
236     size_t find(T ptr) const nothrow pure
237     {
238         foreach (i; 0 .. length)
239             if (data[i] is ptr)
240                 return i;
241         return size_t.max;
242     }
243 
244     bool contains(T ptr) const nothrow pure
245     {
246         return find(ptr) != size_t.max;
247     }
248 
249     ref inout(T) opIndex(size_t i) inout nothrow pure
250     {
251         return data[i];
252     }
253 
254     inout(T)* tdata() inout pure nothrow @nogc @trusted
255     {
256         return data.ptr;
257     }
258 
259     Array!T* copy() const pure nothrow
260     {
261         auto a = new Array!T();
262         a.setDim(length);
263         memcpy(a.data.ptr, data.ptr, length * T.sizeof);
264         return a;
265     }
266 
267     void shift(T ptr) pure nothrow
268     {
269         reserve(1);
270         memmove(data.ptr + 1, data.ptr, length * T.sizeof);
271         data[0] = ptr;
272         length++;
273     }
274 
275     void zero() nothrow pure @nogc
276     {
277         data[0 .. length] = T.init;
278     }
279 
280     T pop() nothrow pure @nogc
281     {
282         debug (stomp)
283         {
284             assert(length);
285             auto result = data[length - 1];
286             remove(length - 1);
287             return result;
288         }
289         else
290             return data[--length];
291     }
292 
293     extern (D) inout(T)[] opSlice() inout nothrow pure @nogc
294     {
295         return data[0 .. length];
296     }
297 
298     extern (D) inout(T)[] opSlice(size_t a, size_t b) inout nothrow pure @nogc
299     {
300         assert(a <= b && b <= length);
301         return data[a .. b];
302     }
303 
304     /**
305      * Sort the elements of an array
306      *
307      * This function relies on `qsort`.
308      *
309      * Params:
310      *   pred = Predicate to use. Should be a function of type
311      *          `int function(scope const T*  e1, scope const T* e2) nothrow`.
312      *          The return value of this function should follow the
313      *          usual C rule: `e1 >= e2 ? (e1 > e2) : -1`.
314      *          The function can have D linkage.
315      *
316      * Returns:
317      *   A reference to this, for easy chaining.
318      */
319     extern(D) ref typeof(this) sort (alias pred) () nothrow
320     {
321         if (this.length < 2)
322             return this;
323         qsort(this.data.ptr, this.length, T.sizeof, &arraySortWrapper!(T, pred));
324         return this;
325     }
326 
327     /// Ditto, but use `opCmp` by default
328     extern(D) ref typeof(this) sort () () nothrow
329         if (is(typeof(this.data[0].opCmp(this.data[1])) : int))
330     {
331         return this.sort!(function (scope const(T)* pe1, scope const(T)* pe2) => pe1.opCmp(*pe2));
332     }
333 
334     alias opDollar = length;
335     alias dim = length;
336 }
337 
338 unittest
339 {
340     // Test for objects implementing toString()
341     static struct S
342     {
343         int s = -1;
344         string toString() const
345         {
346             return "S";
347         }
348     }
349     auto array = Array!S(4);
350     assert(array.toString() == "[S,S,S,S]");
351     array.setDim(0);
352     assert(array.toString() == "[]");
353 
354     // Test for toDString()
355     auto strarray = Array!(const(char)*)(2);
356     strarray[0] = "hello";
357     strarray[1] = "world";
358     auto str = strarray.toString();
359     assert(str == `["hello","world"]`);
360     // Test presence of null terminator.
361     assert(str.ptr[str.length] == '\0');
362 }
363 
364 unittest
365 {
366     auto array = Array!double(4);
367     array.shift(10);
368     array.push(20);
369     array[2] = 15;
370     assert(array[0] == 10);
371     assert(array.find(10) == 0);
372     assert(array.find(20) == 5);
373     assert(!array.contains(99));
374     array.remove(1);
375     assert(array.length == 5);
376     assert(array[1] == 15);
377     assert(array.pop() == 20);
378     assert(array.length == 4);
379     array.insert(1, 30);
380     assert(array[1] == 30);
381     assert(array[2] == 15);
382 }
383 
384 unittest
385 {
386     auto arrayA = Array!int(0);
387     int[3] buf = [10, 15, 20];
388     arrayA.pushSlice(buf);
389     assert(arrayA[] == buf[]);
390     auto arrayPtr = arrayA.copy();
391     assert(arrayPtr);
392     assert((*arrayPtr)[] == arrayA[]);
393     assert(arrayPtr.tdata != arrayA.tdata);
394 
395     arrayPtr.setDim(0);
396     int[2] buf2 = [100, 200];
397     arrayPtr.pushSlice(buf2);
398 
399     arrayA.append(arrayPtr);
400     assert(arrayA[3..$] == buf2[]);
401     arrayA.insert(0, arrayPtr);
402     assert(arrayA[] == [100, 200, 10, 15, 20, 100, 200]);
403 
404     arrayA.zero();
405     foreach(e; arrayA)
406         assert(e == 0);
407 }
408 
409 /**
410  * Exposes the given root Array as a standard D array.
411  * Params:
412  *  array = the array to expose.
413  * Returns:
414  *  The given array exposed to a standard D array.
415  */
416 @property inout(T)[] peekSlice(T)(inout(Array!T)* array) pure nothrow @nogc
417 {
418     return array ? (*array)[] : null;
419 }
420 
421 /**
422  * Splits the array at $(D index) and expands it to make room for $(D length)
423  * elements by shifting everything past $(D index) to the right.
424  * Params:
425  *  array = the array to split.
426  *  index = the index to split the array from.
427  *  length = the number of elements to make room for starting at $(D index).
428  */
429 void split(T)(ref Array!T array, size_t index, size_t length) pure nothrow
430 {
431     if (length > 0)
432     {
433         auto previousDim = array.length;
434         array.setDim(array.length + length);
435         for (size_t i = previousDim; i > index;)
436         {
437             i--;
438             array[i + length] = array[i];
439         }
440     }
441 }
442 unittest
443 {
444     auto array = Array!int();
445     array.split(0, 0);
446     assert([] == array[]);
447     array.push(1).push(3);
448     array.split(1, 1);
449     array[1] = 2;
450     assert([1, 2, 3] == array[]);
451     array.split(2, 3);
452     array[2] = 8;
453     array[3] = 20;
454     array[4] = 4;
455     assert([1, 2, 8, 20, 4, 3] == array[]);
456     array.split(0, 0);
457     assert([1, 2, 8, 20, 4, 3] == array[]);
458     array.split(0, 1);
459     array[0] = 123;
460     assert([123, 1, 2, 8, 20, 4, 3] == array[]);
461     array.split(0, 3);
462     array[0] = 123;
463     array[1] = 421;
464     array[2] = 910;
465     assert([123, 421, 910, 123, 1, 2, 8, 20, 4, 3] == (&array).peekSlice());
466 }
467 
468 /**
469  * Reverse an array in-place.
470  * Params:
471  *      a = array
472  * Returns:
473  *      reversed a[]
474  */
475 T[] reverse(T)(T[] a) pure nothrow @nogc @safe
476 {
477     if (a.length > 1)
478     {
479         const mid = (a.length + 1) >> 1;
480         foreach (i; 0 .. mid)
481         {
482             T e = a[i];
483             a[i] = a[$ - 1 - i];
484             a[$ - 1 - i] = e;
485         }
486     }
487     return a;
488 }
489 
490 unittest
491 {
492     int[] a1 = [];
493     assert(reverse(a1) == []);
494     int[] a2 = [2];
495     assert(reverse(a2) == [2]);
496     int[] a3 = [2,3];
497     assert(reverse(a3) == [3,2]);
498     int[] a4 = [2,3,4];
499     assert(reverse(a4) == [4,3,2]);
500     int[] a5 = [2,3,4,5];
501     assert(reverse(a5) == [5,4,3,2]);
502 }
503 
504 unittest
505 {
506     //test toString/toChars.  Identifier is a simple object that has a usable .toString
507     import dmd.identifier : Identifier;
508     import core.stdc.string : strcmp;
509 
510     auto array = Array!Identifier();
511     array.push(new Identifier("id1"));
512     array.push(new Identifier("id2"));
513 
514     string expected = "[id1,id2]";
515     assert(array.toString == expected);
516     assert(strcmp(array.toChars, expected.ptr) == 0);
517 }
518 
519 /// Predicate to wrap a D function passed to `qsort`
520 private template arraySortWrapper(T, alias fn)
521 {
522     pragma(mangle, "arraySortWrapper_" ~ T.mangleof ~ "_" ~ fn.mangleof)
523     extern(C) int arraySortWrapper(scope const void* e1, scope const void* e2) nothrow
524     {
525         return fn(cast(const(T*))e1, cast(const(T*))e2);
526     }
527 }
528 
529 // Test sorting
530 unittest
531 {
532     Array!(const(char)*) strings;
533     strings.push("World");
534     strings.push("Foo");
535     strings.push("baguette");
536     strings.push("Avocado");
537     strings.push("Hello");
538     // Newer frontend versions will work with (e1, e2) and infer the type
539     strings.sort!(function (scope const char** e1, scope const char** e2) => strcmp(*e1, *e2));
540     assert(strings[0] == "Avocado");
541     assert(strings[1] == "Foo");
542     assert(strings[2] == "Hello");
543     assert(strings[3] == "World");
544     assert(strings[4] == "baguette");
545 
546     /// opCmp automatically supported
547     static struct MyStruct
548     {
549         int a;
550 
551         int opCmp(const ref MyStruct other) const nothrow
552         {
553             // Reverse order
554             return other.a - this.a;
555         }
556     }
557 
558     Array!MyStruct arr1;
559     arr1.push(MyStruct(2));
560     arr1.push(MyStruct(4));
561     arr1.push(MyStruct(256));
562     arr1.push(MyStruct(42));
563     arr1.sort();
564     assert(arr1[0].a == 256);
565     assert(arr1[1].a == 42);
566     assert(arr1[2].a == 4);
567     assert(arr1[3].a == 2);
568 
569     /// But only if user defined
570     static struct OtherStruct
571     {
572         int a;
573 
574         static int pred (scope const OtherStruct* pe1, scope const OtherStruct* pe2)
575             nothrow @nogc pure @safe
576         {
577             return pe1.a - pe2.a;
578         }
579     }
580 
581     static assert (!is(typeof(Array!(OtherStruct).init.sort())));
582     static assert (!is(typeof(Array!(OtherStruct).init.sort!pred)));
583 }