From c070723bc8b9b60255605c409b7a43329d27b4cd Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 18 Feb 2022 08:34:50 +0100 Subject: [PATCH] Add sc_vector Adapt vlc_vector [1], that I initially wrote while implementing the VLC playlist [2]. Change the implementation to use "statement expressions" [3], which are forbidden in VLC because "non-standard", but: - they are supported by gcc and clang; - they are already used in the scrcpy codebase; - they avoid implementation hacks (VLC_VECTOR_FAILFLAG_); - they allow a better API (sc_vector_index_of() may return the result without an output parameter). PR #3035 [1]: https://code.videolan.org/videolan/vlc/-/blob/0857947abaed9c89810cd96353aaa1b7e6ba3b0d/include/vlc_vector.h [2]: https://blog.rom1v.com/2019/05/a-new-core-playlist-for-vlc-4 [3]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html --- app/meson.build | 3 + app/src/util/vector.h | 539 ++++++++++++++++++++++++++++++++++++++++ app/tests/test_vector.c | 421 +++++++++++++++++++++++++++++++ 3 files changed, 963 insertions(+) create mode 100644 app/src/util/vector.h create mode 100644 app/tests/test_vector.c diff --git a/app/meson.build b/app/meson.build index 6449439a..6255bcbc 100644 --- a/app/meson.build +++ b/app/meson.build @@ -282,6 +282,9 @@ if get_option('buildtype') == 'debug' 'src/util/str.c', 'src/util/strbuf.c', ]], + ['test_vector', [ + 'tests/test_vector.c', + ]], ] foreach t : tests diff --git a/app/src/util/vector.h b/app/src/util/vector.h new file mode 100644 index 00000000..a08fa9d6 --- /dev/null +++ b/app/src/util/vector.h @@ -0,0 +1,539 @@ +#ifndef SC_VECTOR_H +#define SC_VECTOR_H + +#include "common.h" + +#include +#include + +// Adapted from vlc_vector: +// + +/** + * Vector struct body + * + * A vector is a dynamic array, managed by the sc_vector_* helpers. + * + * It is generic over the type of its items, so it is implemented as macros. + * + * To use a vector, a new type must be defined: + * + * struct vec_int SC_VECTOR(int); + * + * The struct may be anonymous: + * + * struct SC_VECTOR(const char *) names; + * + * Vector size is accessible via `vec.size`, and items are intended to be + * accessed directly, via `vec.data[i]`. + * + * Functions and macros having name ending with '_' are private. + */ +#define SC_VECTOR(type) \ +{ \ + size_t cap; \ + size_t size; \ + type *data; \ +} + +/** + * Static initializer for a vector + */ +#define SC_VECTOR_INITIALIZER { 0, 0, NULL } + +/** + * Initialize an empty vector + */ +#define sc_vector_init(pv) \ +({ \ + (pv)->cap = 0; \ + (pv)->size = 0; \ + (pv)->data = NULL; \ +}) + +/** + * Destroy a vector + * + * The vector may not be used anymore unless sc_vector_init() is called. + */ +#define sc_vector_destroy(pv) \ + free((pv)->data) + +/** + * Clear a vector + * + * Remove all items from the vector. + */ +#define sc_vector_clear(pv) \ +({ \ + sc_vector_destroy(pv); \ + sc_vector_init(pv);\ +}) + +/** + * The minimal allocation size, in number of items + * + * Private. + */ +#define SC_VECTOR_MINCAP_ ((size_t) 10) + +static inline size_t +sc_vector_min_(size_t a, size_t b) +{ + return a < b ? a : b; +} + +static inline size_t +sc_vector_max_(size_t a, size_t b) +{ + return a > b ? a : b; +} + +static inline size_t +sc_vector_clamp_(size_t x, size_t min, size_t max) +{ + return sc_vector_max_(min, sc_vector_min_(max, x)); +} + +/** + * Realloc data and update vector fields + * + * On reallocation success, update the vector capacity (*pcap) and size + * (*psize), and return the reallocated data. + * + * On reallocation failure, return NULL without any change. + * + * Private. + * + * \param ptr the current `data` field of the vector to realloc + * \param count the requested capacity, in number of items + * \param size the size of one item + * \param pcap a pointer to the `cap` field of the vector [IN/OUT] + * \param psize a pointer to the `size` field of the vector [IN/OUT] + * \return the new ptr on success, NULL on error + */ +static inline void * +sc_vector_reallocdata_(void *ptr, size_t count, size_t size, + size_t *restrict pcap, size_t *restrict psize) +{ + void *p = realloc(ptr, count * size); + if (!p) { + return NULL; + } + + *pcap = count; + *psize = sc_vector_min_(*psize, count); + return p; +} + +#define sc_vector_realloc_(pv, newcap) \ +({ \ + void *p = sc_vector_reallocdata_((pv)->data, newcap, sizeof(*(pv)->data), \ + &(pv)->cap, &(pv)->size); \ + if (p) { \ + (pv)->data = p; \ + } \ + (bool) p; \ +}); + +#define sc_vector_resize_(pv, newcap) \ +({ \ + bool ok; \ + if ((pv)->cap == (newcap)) { \ + ok = true; \ + } else if ((newcap) > 0) { \ + ok = sc_vector_realloc_(pv, (newcap)); \ + } else { \ + sc_vector_clear(pv); \ + ok = true; \ + } \ + ok; \ +}) + +static inline size_t +sc_vector_growsize_(size_t value) +{ + /* integer multiplication by 1.5 */ + return value + (value >> 1); +} + +/* SIZE_MAX/2 to fit in ssize_t, and so that cap*1.5 does not overflow. */ +#define sc_vector_max_cap_(pv) (SIZE_MAX / 2 / sizeof(*(pv)->data)) + +/** + * Increase the capacity of the vector to at least `mincap` + * + * \param pv a pointer to the vector + * \param mincap (size_t) the requested capacity + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_reserve(pv, mincap) \ +({ \ + bool ok; \ + /* avoid to allocate tiny arrays (< SC_VECTOR_MINCAP_) */ \ + size_t mincap_ = sc_vector_max_(mincap, SC_VECTOR_MINCAP_); \ + if (mincap_ <= (pv)->cap) { \ + /* nothing to do */ \ + ok = true; \ + } else if (mincap_ <= sc_vector_max_cap_(pv)) { \ + /* not too big */ \ + size_t newsize = sc_vector_growsize_((pv)->cap); \ + newsize = sc_vector_clamp_(newsize, mincap_, sc_vector_max_cap_(pv)); \ + ok = sc_vector_realloc_(pv, newsize); \ + } else { \ + ok = false; \ + } \ + ok; \ +}) + +#define sc_vector_shrink_to_fit(pv) \ + /* decreasing the size may not fail */ \ + (void) sc_vector_resize_(pv, (pv)->size) + +/** + * Resize the vector down automatically + * + * Shrink only when necessary (in practice when cap > (size+5)*1.5) + * + * \param pv a pointer to the vector + */ +#define sc_vector_autoshrink(pv) \ +({ \ + bool must_shrink = \ + /* do not shrink to tiny size */ \ + (pv)->cap > SC_VECTOR_MINCAP_ && \ + /* no need to shrink */ \ + (pv)->cap >= sc_vector_growsize_((pv)->size + 5); \ + if (must_shrink) { \ + size_t newsize = sc_vector_max_((pv)->size + 5, SC_VECTOR_MINCAP_); \ + sc_vector_resize_(pv, newsize); \ + } \ +}) + +#define sc_vector_check_same_ptr_type_(a, b) \ + (void) ((a) == (b)) /* warn on type mismatch */ + +/** + * Push an item at the end of the vector + * + * The amortized complexity is O(1). + * + * \param pv a pointer to the vector + * \param item the item to append + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_push(pv, item) \ +({ \ + bool ok = sc_vector_reserve(pv, (pv)->size + 1); \ + if (ok) { \ + (pv)->data[(pv)->size++] = (item); \ + } \ + ok; \ +}) + +/** + * Append `count` items at the end of the vector + * + * \param pv a pointer to the vector + * \param items the items array to append + * \param count the number of items in the array + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_push_all(pv, items, count) \ + sc_vector_push_all_(pv, items, (size_t) count) + +#define sc_vector_push_all_(pv, items, count) \ +({ \ + sc_vector_check_same_ptr_type_((pv)->data, items); \ + bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \ + if (ok) { \ + memcpy(&(pv)->data[(pv)->size], items, (count) * sizeof(*(pv)->data)); \ + (pv)->size += count; \ + } \ + ok; \ +}) + +/** + * Insert an hole of size `count` to the given index + * + * The items in range [index; size-1] will be moved. The items in the hole are + * left uninitialized. + * + * \param pv a pointer to the vector + * \param index the index where the hole is to be inserted + * \param count the number of items in the hole + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert_hole(pv, index, count) \ + sc_vector_insert_hole_(pv, (size_t) index, (size_t) count); + +#define sc_vector_insert_hole_(pv, index, count) \ +({ \ + bool ok = sc_vector_reserve(pv, (pv)->size + (count)); \ + if (ok) { \ + if ((index) < (pv)->size) { \ + memmove(&(pv)->data[(index) + (count)], \ + &(pv)->data[(index)], \ + ((pv)->size - (index)) * sizeof(*(pv)->data)); \ + } \ + (pv)->size += count; \ + } \ + ok; \ +}) + +/** + * Insert an item at the given index + * + * The items in range [index; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index where the item is to be inserted + * \param item the item to append + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert(pv, index, item) \ + sc_vector_insert_(pv, (size_t) index, (size_t) item); + +#define sc_vector_insert_(pv, index, item) \ +({ \ + bool ok = sc_vector_insert_hole_(pv, index, 1); \ + if (ok) { \ + (pv)->data[index] = (item); \ + } \ + ok; \ +}) + +/** + * Insert `count` items at the given index + * + * The items in range [index; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index where the items are to be inserted + * \param items the items array to append + * \param count the number of items in the array + * \retval true if no allocation failed + * \retval false on allocation failure (the vector is left untouched) + */ +#define sc_vector_insert_all(pv, index, items, count) \ + sc_vector_insert_all_(pv, (size_t) index, items, (size_t) count) + +#define sc_vector_insert_all_(pv, index, items, count) \ +({ \ + sc_vector_check_same_ptr_type_((pv)->data, items); \ + bool ok = sc_vector_insert_hole_(pv, index, count); \ + if (ok) { \ + memcpy(&(pv)->data[index], items, count * sizeof(*(pv)->data)); \ + } \ + ok; \ +}) + +/** Reverse a char array in place */ +static inline void +sc_char_array_reverse(char *array, size_t len) +{ + for (size_t i = 0; i < len / 2; ++i) + { + char c = array[i]; + array[i] = array[len - i - 1]; + array[len - i - 1] = c; + } +} + +/** + * Right-rotate a (char) array in place + * + * For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with + * distance 4 will result in {5, 6, 1, 2, 3, 4}. + * + * Private. + */ +static inline void +sc_char_array_rotate_left(char *array, size_t len, size_t distance) +{ + sc_char_array_reverse(array, distance); + sc_char_array_reverse(&array[distance], len - distance); + sc_char_array_reverse(array, len); +} + +/** + * Right-rotate a (char) array in place + * + * For example, left-rotating a char array containing {1, 2, 3, 4, 5, 6} with + * distance 2 will result in {5, 6, 1, 2, 3, 4}. + * + * Private. + */ +static inline void +sc_char_array_rotate_right(char *array, size_t len, size_t distance) +{ + sc_char_array_rotate_left(array, len, len - distance); +} + +/** + * Move items in a (char) array in place + * + * Move slice [index, count] to target. + */ +static inline void +sc_char_array_move(char *array, size_t idx, size_t count, size_t target) +{ + if (idx < target) { + sc_char_array_rotate_left(&array[idx], target - idx + count, count); + } else { + sc_char_array_rotate_right(&array[target], idx - target + count, count); + } +} + +/** + * Move a slice of items to a given target index + * + * The items in range [index; count] will be moved so that the *new* position + * of the first item is `target`. + * + * \param pv a pointer to the vector + * \param index the index of the first item to move + * \param count the number of items to move + * \param target the new index of the moved slice + */ +#define sc_vector_move_slice(pv, index, count, target) \ + sc_vector_move_slice_(pv, (size_t) index, count, (size_t) target); + +#define sc_vector_move_slice_(pv, index, count, target) \ +({ \ + sc_char_array_move((char *) (pv)->data, \ + (index) * sizeof(*(pv)->data), \ + (count) * sizeof(*(pv)->data), \ + (target) * sizeof(*(pv)->data)); \ +}) + +/** + * Move an item to a given target index + * + * The items will be moved so that its *new* position is `target`. + * + * \param pv a pointer to the vector + * \param index the index of the item to move + * \param target the new index of the moved item + */ +#define sc_vector_move(pv, index, target) \ + sc_vector_move_slice(pv, index, 1, target) + +/** + * Remove a slice of items, without shrinking the array + * + * If you have no good reason to use the _noshrink() version, use + * sc_vector_remove_slice() instead. + * + * The items in range [index+count; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of the first item to remove + * \param count the number of items to remove + */ +#define sc_vector_remove_slice_noshrink(pv, index, count) \ + sc_vector_remove_slice_noshrink_(pv, (size_t) index, (size_t) count) + +#define sc_vector_remove_slice_noshrink_(pv, index, count) \ +({ \ + if ((index) + (count) < (pv)->size) { \ + memmove(&(pv)->data[index], \ + &(pv)->data[(index) + (count)], \ + ((pv)->size - (index) - (count)) * sizeof(*(pv)->data)); \ + } \ + (pv)->size -= count; \ +}) + +/** + * Remove a slice of items + * + * The items in range [index+count; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of the first item to remove + * \param count the number of items to remove + */ +#define sc_vector_remove_slice(pv, index, count) \ +({ \ + sc_vector_remove_slice_noshrink(pv, index, count); \ + sc_vector_autoshrink(pv); \ +}) + +/** + * Remove an item, without shrinking the array + * + * If you have no good reason to use the _noshrink() version, use + * sc_vector_remove() instead. + * + * The items in range [index+1; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_remove_noshrink(pv, index) \ + sc_vector_remove_slice_noshrink(pv, index, 1) + +/** + * Remove an item + * + * The items in range [index+1; size-1] will be moved. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_remove(pv, index) \ +({ \ + sc_vector_remove_noshrink(pv, index); \ + sc_vector_autoshrink(pv); \ +}) + +/** + * Remove an item + * + * The removed item is replaced by the last item of the vector. + * + * This does not preserve ordering, but is O(1). This is useful when the order + * of items is not meaningful. + * + * \param pv a pointer to the vector + * \param index the index of item to remove + */ +#define sc_vector_swap_remove(pv, index) \ + sc_vector_swap_remove_(pv, (size_t) index); + +#define sc_vector_swap_remove_(pv, index) \ +({ \ + (pv)->data[index] = (pv)->data[(pv)->size-1]; \ + (pv)->size--; \ +}); + +/** + * Return the index of an item + * + * Iterate over all items to find a given item. + * + * Use only for vectors of primitive types or pointers. + * + * Return the index, or -1 if not found. + * + * \param pv a pointer to the vector + * \param item the item to find (compared with ==) + */ +#define sc_vector_index_of(pv, item) \ +({ \ + ssize_t idx = -1; \ + for (size_t i = 0; i < (pv)->size; ++i) { \ + if ((pv)->data[i] == (item)) { \ + idx = (ssize_t) i; \ + break; \ + } \ + } \ + idx; \ +}) + +#endif diff --git a/app/tests/test_vector.c b/app/tests/test_vector.c new file mode 100644 index 00000000..7ca09989 --- /dev/null +++ b/app/tests/test_vector.c @@ -0,0 +1,421 @@ +#include "common.h" + +#include + +#include "util/vector.h" + +static void test_vector_insert_remove(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_push(&vec, 42); + assert(ok); + assert(vec.data[0] == 42); + assert(vec.size == 1); + + ok = sc_vector_push(&vec, 37); + assert(ok); + assert(vec.size == 2); + assert(vec.data[0] == 42); + assert(vec.data[1] == 37); + + ok = sc_vector_insert(&vec, 1, 100); + assert(ok); + assert(vec.size == 3); + assert(vec.data[0] == 42); + assert(vec.data[1] == 100); + assert(vec.data[2] == 37); + + ok = sc_vector_push(&vec, 77); + assert(ok); + assert(vec.size == 4); + assert(vec.data[0] == 42); + assert(vec.data[1] == 100); + assert(vec.data[2] == 37); + assert(vec.data[3] == 77); + + sc_vector_remove(&vec, 1); + assert(vec.size == 3); + assert(vec.data[0] == 42); + assert(vec.data[1] == 37); + assert(vec.data[2] == 77); + + sc_vector_clear(&vec); + assert(vec.size == 0); + + sc_vector_destroy(&vec); +} + +static void test_vector_push_array(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + ok = sc_vector_push_all(&vec, items, 8); + + assert(ok); + assert(vec.size == 13); + assert(vec.data[0] == 3); + assert(vec.data[1] == 14); + assert(vec.data[2] == 15); + assert(vec.data[3] == 92); + assert(vec.data[4] == 65); + assert(vec.data[5] == 1); + assert(vec.data[6] == 2); + assert(vec.data[7] == 3); + assert(vec.data[8] == 4); + assert(vec.data[9] == 5); + assert(vec.data[10] == 6); + assert(vec.data[11] == 7); + assert(vec.data[12] == 8); + + sc_vector_destroy(&vec); +} + +static void test_vector_insert_array(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + int items[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + ok = sc_vector_insert_all(&vec, 3, items, 8); + assert(ok); + assert(vec.size == 13); + assert(vec.data[0] == 3); + assert(vec.data[1] == 14); + assert(vec.data[2] == 15); + assert(vec.data[3] == 1); + assert(vec.data[4] == 2); + assert(vec.data[5] == 3); + assert(vec.data[6] == 4); + assert(vec.data[7] == 5); + assert(vec.data[8] == 6); + assert(vec.data[9] == 7); + assert(vec.data[10] == 8); + assert(vec.data[11] == 92); + assert(vec.data[12] == 65); + + sc_vector_destroy(&vec); +} + +static void test_vector_remove_slice(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + for (int i = 0; i < 100; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + assert(vec.size == 100); + + sc_vector_remove_slice(&vec, 32, 60); + assert(vec.size == 40); + assert(vec.data[31] == 31); + assert(vec.data[32] == 92); + + sc_vector_destroy(&vec); +} + +static void test_vector_swap_remove(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_push(&vec, 3); assert(ok); + ok = sc_vector_push(&vec, 14); assert(ok); + ok = sc_vector_push(&vec, 15); assert(ok); + ok = sc_vector_push(&vec, 92); assert(ok); + ok = sc_vector_push(&vec, 65); assert(ok); + assert(vec.size == 5); + + sc_vector_swap_remove(&vec, 1); + assert(vec.size == 4); + assert(vec.data[0] == 3); + assert(vec.data[1] == 65); + assert(vec.data[2] == 15); + assert(vec.data[3] == 92); + + sc_vector_destroy(&vec); +} + +static void test_vector_index_of(void) { + struct SC_VECTOR(int) vec; + sc_vector_init(&vec); + + bool ok; + + for (int i = 0; i < 10; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + ssize_t idx; + + idx = sc_vector_index_of(&vec, 0); + assert(idx == 0); + + idx = sc_vector_index_of(&vec, 1); + assert(idx == 1); + + idx = sc_vector_index_of(&vec, 4); + assert(idx == 4); + + idx = sc_vector_index_of(&vec, 9); + assert(idx == 9); + + idx = sc_vector_index_of(&vec, 12); + assert(idx == -1); + + sc_vector_destroy(&vec); +} + +static void test_vector_grow() { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + for (int i = 0; i < 50; ++i) + { + ok = sc_vector_push(&vec, i); /* append */ + assert(ok); + } + + assert(vec.cap >= 50); + assert(vec.size == 50); + + for (int i = 0; i < 25; ++i) + { + ok = sc_vector_insert(&vec, 20, i); /* insert in the middle */ + assert(ok); + } + + assert(vec.cap >= 75); + assert(vec.size == 75); + + for (int i = 0; i < 25; ++i) + { + ok = sc_vector_insert(&vec, 0, i); /* prepend */ + assert(ok); + } + + assert(vec.cap >= 100); + assert(vec.size == 100); + + for (int i = 0; i < 50; ++i) + sc_vector_remove(&vec, 20); /* remove from the middle */ + + assert(vec.cap >= 50); + assert(vec.size == 50); + + for (int i = 0; i < 25; ++i) + sc_vector_remove(&vec, 0); /* remove from the head */ + + assert(vec.cap >= 25); + assert(vec.size == 25); + + for (int i = 24; i >=0; --i) + sc_vector_remove(&vec, i); /* remove from the tail */ + + assert(vec.size == 0); + + sc_vector_destroy(&vec); +} + +static void test_vector_exp_growth(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + size_t oldcap = vec.cap; + int realloc_count = 0; + bool ok; + for (int i = 0; i < 10000; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + if (vec.cap != oldcap) + { + realloc_count++; + oldcap = vec.cap; + } + } + + /* Test speciically for an expected growth factor of 1.5. In practice, the + * result is even lower (19) due to the first alloc of size 10 */ + assert(realloc_count <= 23); /* ln(10000) / ln(1.5) ~= 23 */ + + realloc_count = 0; + for (int i = 9999; i >= 0; --i) + { + sc_vector_remove(&vec, i); + if (vec.cap != oldcap) + { + realloc_count++; + oldcap = vec.cap; + } + } + + assert(realloc_count <= 23); /* same expectations for removals */ + assert(realloc_count > 0); /* sc_vector_remove() must autoshrink */ + + sc_vector_destroy(&vec); +} + +static void test_vector_reserve(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_reserve(&vec, 800); + assert(ok); + assert(vec.cap >= 800); + assert(vec.size == 0); + + size_t initial_cap = vec.cap; + + for (int i = 0; i < 800; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + assert(vec.cap == initial_cap); /* no realloc */ + } + + sc_vector_destroy(&vec); +} + +static void test_vector_shrink_to_fit(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + bool ok; + + ok = sc_vector_reserve(&vec, 800); + assert(ok); + for (int i = 0; i < 250; ++i) + { + ok = sc_vector_push(&vec, i); + assert(ok); + } + + assert(vec.cap >= 800); + assert(vec.size == 250); + + sc_vector_shrink_to_fit(&vec); + assert(vec.cap == 250); + assert(vec.size == 250); + + sc_vector_destroy(&vec); +} + +static void test_vector_move(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 7; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move item at 1 so that its new position is 4 */ + sc_vector_move(&vec, 1, 4); + + assert(vec.size == 7); + assert(vec.data[0] == 0); + assert(vec.data[1] == 2); + assert(vec.data[2] == 3); + assert(vec.data[3] == 4); + assert(vec.data[4] == 1); + assert(vec.data[5] == 5); + assert(vec.data[6] == 6); + + sc_vector_destroy(&vec); +} + +static void test_vector_move_slice_forward(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 10; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move slice {2, 3, 4, 5} so that its new position is 5 */ + sc_vector_move_slice(&vec, 2, 4, 5); + + assert(vec.size == 10); + assert(vec.data[0] == 0); + assert(vec.data[1] == 1); + assert(vec.data[2] == 6); + assert(vec.data[3] == 7); + assert(vec.data[4] == 8); + assert(vec.data[5] == 2); + assert(vec.data[6] == 3); + assert(vec.data[7] == 4); + assert(vec.data[8] == 5); + assert(vec.data[9] == 9); + + sc_vector_destroy(&vec); +} + +static void test_vector_move_slice_backward(void) { + struct SC_VECTOR(int) vec = SC_VECTOR_INITIALIZER; + + for (int i = 0; i < 10; ++i) + { + bool ok = sc_vector_push(&vec, i); + assert(ok); + } + + /* move slice {5, 6, 7} so that its new position is 2 */ + sc_vector_move_slice(&vec, 5, 3, 2); + + assert(vec.size == 10); + assert(vec.data[0] == 0); + assert(vec.data[1] == 1); + assert(vec.data[2] == 5); + assert(vec.data[3] == 6); + assert(vec.data[4] == 7); + assert(vec.data[5] == 2); + assert(vec.data[6] == 3); + assert(vec.data[7] == 4); + assert(vec.data[8] == 8); + assert(vec.data[9] == 9); + + sc_vector_destroy(&vec); +} + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + test_vector_insert_remove(); + test_vector_push_array(); + test_vector_insert_array(); + test_vector_remove_slice(); + test_vector_swap_remove(); + test_vector_move(); + test_vector_move_slice_forward(); + test_vector_move_slice_backward(); + test_vector_index_of(); + test_vector_grow(); + test_vector_exp_growth(); + test_vector_reserve(); + test_vector_shrink_to_fit(); + return 0; +}