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 }