complete refactoring of low-level array list functions - relates to #619 default tip

Mon, 15 Dec 2025 19:00:51 +0100

author
Mike Becker <universe@uap-core.de>
date
Mon, 15 Dec 2025 19:00:51 +0100
changeset 1608
46d8a8305948
parent 1607
0ecb13118cac

complete refactoring of low-level array list functions - relates to #619

now only the documentation needs to be updated

src/array_list.c file | annotate | diff | comparison | revisions
src/cx/array_list.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
tests/test_list.c file | annotate | diff | comparison | revisions
--- a/src/array_list.c	Sun Dec 14 23:17:48 2025 +0100
+++ b/src/array_list.c	Mon Dec 15 19:00:51 2025 +0100
@@ -58,86 +58,25 @@
     return cap - (cap % alignment) + alignment;
 }
 
-// Default array reallocator
-
-static void *cx_array_default_realloc(
-        void *array,
-        cx_attr_unused size_t old_capacity,
-        size_t new_capacity,
-        size_t elem_size,
-        cx_attr_unused CxArrayReallocator *alloc
-) {
-    size_t n;
-    // LCOV_EXCL_START
-    if (cx_szmul(new_capacity, elem_size, &n)) {
-        errno = EOVERFLOW;
-        return NULL;
-    } // LCOV_EXCL_STOP
-    return cxReallocDefault(array, n);
-}
-
-CxArrayReallocator cx_array_default_reallocator_impl = {
-        cx_array_default_realloc, NULL, NULL
-};
-
-CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl;
-
-// Stack-aware array reallocator
-
-static void *cx_array_advanced_realloc(
-        void *array,
-        size_t old_capacity,
-        size_t new_capacity,
-        size_t elem_size,
-        cx_attr_unused CxArrayReallocator *alloc
-) {
-    // check for overflow
-    size_t n;
-    // LCOV_EXCL_START
-    if (cx_szmul(new_capacity, elem_size, &n)) {
-        errno = EOVERFLOW;
-        return NULL;
-    } // LCOV_EXCL_STOP
-
-    // retrieve the pointer to the actual allocator
-    const CxAllocator *al = alloc->allocator;
-
-    // check if the array is still located on the stack
-    void *newmem;
-    if (array == alloc->stack_ptr) {
-        newmem = cxMalloc(al, n);
-        if (newmem != NULL && array != NULL) {
-            memcpy(newmem, array, old_capacity*elem_size);
-        }
-    } else {
-        newmem = cxRealloc(al, array, n);
-    }
-    return newmem;
-}
-
-struct cx_array_reallocator_s cx_array_reallocator(
-        const struct cx_allocator_s *allocator,
-        const void *stack_ptr
-) {
-    if (allocator == NULL) {
-        allocator = cxDefaultAllocator;
-    }
-    return (struct cx_array_reallocator_s) {
-            cx_array_advanced_realloc,
-            allocator, stack_ptr,
-    };
-}
-
 int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) {
     memset(array, 0, sizeof(CxArray));
     return cx_array_reserve_(allocator, array, elem_size, capacity);
 }
 
+void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size) {
+    array->data = (void*) data;
+    array->capacity = capacity;
+    array->size = size;
+}
+
 int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity) {
     if (cxReallocateArray(allocator, &array->data, capacity, elem_size)) {
         return -1; // LCOV_EXCL_LINE
     }
     array->capacity = capacity;
+    if (array->size > capacity) {
+        array->size = capacity;
+    }
     return 0;
 }
 
@@ -152,22 +91,7 @@
     return 0;
 }
 
-int cx_array_add_(const CxAllocator *allocator, CxArray *array, size_t elem_size, void *element) {
-    if (array->size >= array->capacity) {
-        size_t newcap = cx_array_grow_capacity(array->capacity, array->capacity + 1);
-        if (cxReallocateArray(allocator, &array->data, newcap, elem_size)) {
-            return -1;
-        }
-        array->capacity = newcap;
-    }
-    char *dst = array->data;
-    dst += elem_size * array->size;
-    memcpy(dst, element, elem_size);
-    array->size++;
-    return 0;
-}
-
-int cx_array_insert_array_(const CxAllocator *allocator, CxArray *array,
+int cx_array_insert_(const CxAllocator *allocator, CxArray *array,
         size_t elem_size, size_t index, const void *other, size_t n) {
     // out of bounds and special case check
     if (index > array->size) return -1;
@@ -183,224 +107,74 @@
     }
 
     // determine insert position
-    char *arl_data = array->data;
-    char *insert_pos = arl_data + index * elem_size;
+    char *dst = array->data;
+    dst += index * elem_size;
 
     // do we need to move some elements?
     if (index < array->size) {
         size_t elems_to_move = array->size - index;
-        char *target = insert_pos + n * elem_size;
-        memmove(target, insert_pos, elems_to_move * elem_size);
+        char *target = dst + n * elem_size;
+        memmove(target, dst, elems_to_move * elem_size);
     }
 
     // place the new elements, if any
     // otherwise, this function just reserved the memory (a.k.a emplace)
     if (other != NULL) {
-        memcpy(insert_pos, other, n * elem_size);
+        memcpy(dst, other, n * elem_size);
     }
     array->size += n;
 
     return 0;
 }
 
-CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) {
-    return cxIterator(array->data, elem_size, array->size);
-}
-
-CxIterator cx_array_iterator_ptr_(CxArray *array) {
-    return cxIteratorPtr(array->data, array->size);
-}
-
-void cx_array_free_(const CxAllocator *allocator, CxArray *array) {
-    cxFree(allocator, array->data);
-    array->data = NULL;
-    array->size = array->capacity = 0;
-}
-
-int cx_array_copy(
-        void **target,
-        void *size,
-        void *capacity,
-        unsigned width,
-        size_t index,
-        const void *src,
+int cx_array_insert_sorted_(
+        const CxAllocator *allocator,
+        CxArray *array,
         size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-) {
-    // assert pointers
-    assert(target != NULL);
-    assert(size != NULL);
-    assert(capacity != NULL);
-    assert(src != NULL);
-
-    // default reallocator
-    if (reallocator == NULL) {
-        reallocator = cx_array_default_reallocator;
-    }
-
-    // determine size and capacity
-    size_t oldcap;
-    size_t oldsize;
-    size_t max_size;
-    if (width == 0 || width == sizeof(size_t)) {
-        oldcap = *(size_t*) capacity;
-        oldsize = *(size_t*) size;
-        max_size = SIZE_MAX;
-    } else if (width == sizeof(uint16_t)) {
-        oldcap = *(uint16_t*) capacity;
-        oldsize = *(uint16_t*) size;
-        max_size = UINT16_MAX;
-    } else if (width == sizeof(uint8_t)) {
-        oldcap = *(uint8_t*) capacity;
-        oldsize = *(uint8_t*) size;
-        max_size = UINT8_MAX;
-    }
-#if CX_WORDSIZE == 64
-    else if (width == sizeof(uint32_t)) {
-        oldcap = *(uint32_t*) capacity;
-        oldsize = *(uint32_t*) size;
-        max_size = UINT32_MAX;
-    }
-#endif
-    else {
-        errno = EINVAL;
-        return 1;
-    }
-
-    // assert that the array is allocated when it has capacity
-    assert(*target != NULL || oldcap == 0);
-
-    // check for overflow
-    if (index > max_size || elem_count > max_size - index) {
-        errno = EOVERFLOW;
-        return 1;
-    }
-
-    // check if resize is required
-    const size_t minsize = index + elem_count;
-    const size_t newsize = oldsize < minsize ? minsize : oldsize;
-
-    // reallocate if necessary
-    const size_t newcap = cx_array_grow_capacity(oldcap, newsize);
-    if (newcap > oldcap) {
-        // check if we need to repair the src pointer
-        uintptr_t targetaddr = (uintptr_t) *target;
-        uintptr_t srcaddr = (uintptr_t) src;
-        bool repairsrc = targetaddr <= srcaddr
-                         && srcaddr < targetaddr + oldcap * elem_size;
-
-        // perform reallocation
-        void *newmem = reallocator->realloc(
-                *target, oldcap, newcap, elem_size, reallocator
-        );
-        if (newmem == NULL) {
-            return 1; // LCOV_EXCL_LINE
-        }
-
-        // repair src pointer, if necessary
-        if (repairsrc) {
-            src = ((char *) newmem) + (srcaddr - targetaddr);
-        }
-
-        // store new pointer
-        *target = newmem;
-    }
-
-    // determine target pointer
-    char *start = *target;
-    start += index * elem_size;
-
-    // copy elements and set new size
-    // note: no overflow check here, b/c we cannot get here w/o allocation
-    memmove(start, src, elem_count * elem_size);
-
-    // if any of size or capacity changed, store them back
-    if (newsize != oldsize || newcap != oldcap) {
-        if (width == 0 || width == sizeof(size_t)) {
-            *(size_t*) capacity = newcap;
-            *(size_t*) size = newsize;
-        } else if (width == sizeof(uint16_t)) {
-            *(uint16_t*) capacity = (uint16_t) newcap;
-            *(uint16_t*) size = (uint16_t) newsize;
-        } else if (width == sizeof(uint8_t)) {
-            *(uint8_t*) capacity = (uint8_t) newcap;
-            *(uint8_t*) size = (uint8_t) newsize;
-        }
-#if CX_WORDSIZE == 64
-        else if (width == sizeof(uint32_t)) {
-            *(uint32_t*) capacity = (uint32_t) newcap;
-            *(uint32_t*) size = (uint32_t) newsize;
-        }
-#endif
-    }
-
-    // return successfully
-    return 0;
-}
-
-static int cx_array_insert_sorted_impl(
-        void **target,
-        size_t *size,
-        size_t *capacity,
         cx_compare_func cmp_func,
         const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator,
+        size_t n,
         bool allow_duplicates
 ) {
     // assert pointers
-    assert(target != NULL);
-    assert(size != NULL);
-    assert(capacity != NULL);
+    assert(allocator != NULL);
+    assert(array != NULL);
     assert(cmp_func != NULL);
     assert(sorted_data != NULL);
 
-    // default reallocator
-    if (reallocator == NULL) {
-        reallocator = cx_array_default_reallocator;
-    }
-
     // corner case
-    if (elem_count == 0) return 0;
+    if (n == 0) return 0;
 
     // overflow check
     // LCOV_EXCL_START
-    if (elem_count > SIZE_MAX - *size) {
+    if (n > SIZE_MAX - array->size) {
         errno = EOVERFLOW;
         return 1;
     }
     // LCOV_EXCL_STOP
 
     // store some counts
-    const size_t old_size = *size;
-    const size_t old_capacity = *capacity;
+    const size_t old_size = array->size;
+    const size_t old_capacity = array->capacity;
     // the necessary capacity is the worst case assumption, including duplicates
-    const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + elem_count);
+    const size_t needed_capacity = cx_array_grow_capacity(old_capacity, old_size + n);
 
     // if we need more than we have, try a reallocation
     if (needed_capacity > old_capacity) {
-        void *new_mem = reallocator->realloc(
-                *target, old_capacity, needed_capacity, elem_size, reallocator
-        );
-        if (new_mem == NULL) {
-            // give it up right away, there is no contract
-            // that requires us to insert as much as we can
-            return 1;  // LCOV_EXCL_LINE
+        if (cxReallocateArray(allocator, &array->data, needed_capacity, elem_size)) {
+            return -1; // LCOV_EXCL_LINE
         }
-        *target = new_mem;
-        *capacity = needed_capacity;
+        array->capacity = needed_capacity;
     }
 
     // now we have guaranteed that we can insert everything
-    size_t new_size = old_size + elem_count;
-    *size = new_size;
+    size_t new_size = old_size + n;
+    array->size = new_size;
 
     // declare the source and destination indices/pointers
     size_t si = 0, di = 0;
     const char *src = sorted_data;
-    char *dest = *target;
+    char *dest = array->data;
 
     // find the first insertion point
     di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
@@ -410,12 +184,12 @@
     // we will call it the "buffer" for parked elements
     size_t buf_size = old_size - di;
     size_t bi = new_size - buf_size;
-    char *bptr = ((char *) *target) + bi * elem_size;
+    char *bptr = ((char *) array->data) + bi * elem_size;
     memmove(bptr, dest, buf_size * elem_size);
 
     // while there are both source and buffered elements left,
     // copy them interleaving
-    while (si < elem_count && bi < new_size) {
+    while (si < n && bi < new_size) {
         // determine how many source elements can be inserted.
         // the first element that shall not be inserted is the smallest element
         // that is strictly larger than the first buffered element
@@ -430,7 +204,7 @@
         // than any element in the source and the infimum exists.
         size_t copy_len, bytes_copied;
         copy_len = cx_array_binary_search_inf(
-            src, elem_count - si, elem_size, bptr, cmp_func
+            src, n - si, elem_size, bptr, cmp_func
         );
         copy_len++;
 
@@ -454,7 +228,7 @@
                     skip_len++;
                     copy_len--;
                 }
-                char *last = dest == *target ? NULL : dest - elem_size;
+                char *last = dest == array->data ? NULL : dest - elem_size;
                 // then iterate through the source chunk
                 // and skip all duplicates with the last element in the array
                 size_t more_skipped = 0;
@@ -478,12 +252,12 @@
                 si += skip_len;
                 skip_len += more_skipped;
                 // reduce the actual size by the number of skipped elements
-                *size -= skip_len;
+                array->size -= skip_len;
             }
         }
 
         // when all source elements are in place, we are done
-        if (si >= elem_count) break;
+        if (si >= n) break;
 
         // determine how many buffered elements need to be restored
         copy_len = cx_array_binary_search_sup(
@@ -504,22 +278,22 @@
     }
 
     // still source elements left?
-    if (si < elem_count) {
+    if (si < n) {
         if (allow_duplicates) {
             // duplicates allowed or nothing inserted yet: simply copy everything
-            memcpy(dest, src, elem_size * (elem_count - si));
+            memcpy(dest, src, elem_size * (n - si));
         } else {
             // we must check the remaining source elements one by one
             // to skip the duplicates.
             // Note that no source element can equal the last element in the
             // destination, because that would have created an insertion point
             // and a buffer, s.t. the above loop already handled the duplicates
-            while (si < elem_count) {
+            while (si < n) {
                 // find a chain of elements that can be copied
                 size_t copy_len = 1, skip_len = 0;
                 {
                     const char *left_src = src;
-                    while (si + copy_len + skip_len < elem_count) {
+                    while (si + copy_len + skip_len < n) {
                         const char *right_src = left_src + elem_size;
                         int d = cmp_func(left_src,  right_src);
                         if (d < 0) {
@@ -544,13 +318,13 @@
                 src += bytes_copied + skip_len * elem_size;
                 si += copy_len + skip_len;
                 di += copy_len;
-                *size -= skip_len;
+                array->size -= skip_len;
             }
         }
     }
 
     // buffered elements need to be moved when we skipped duplicates
-    size_t total_skipped = new_size - *size;
+    size_t total_skipped = new_size - array->size;
     if (bi < new_size && total_skipped > 0) {
         // move the remaining buffer to the end of the array
         memmove(dest, bptr, elem_size * (new_size - bi));
@@ -559,34 +333,21 @@
     return 0;
 }
 
-int cx_array_insert_sorted(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-) {
-    return cx_array_insert_sorted_impl(target, size, capacity,
-        cmp_func, sorted_data, elem_size, elem_count, reallocator, true);
+CxIterator cx_array_iterator_(CxArray *array, size_t elem_size) {
+    return cxIterator(array->data, elem_size, array->size);
 }
 
-int cx_array_insert_unique(
-        void **target,
-        size_t *size,
-        size_t *capacity,
-        cx_compare_func cmp_func,
-        const void *sorted_data,
-        size_t elem_size,
-        size_t elem_count,
-        CxArrayReallocator *reallocator
-) {
-    return cx_array_insert_sorted_impl(target, size, capacity,
-        cmp_func, sorted_data, elem_size, elem_count, reallocator, false);
+CxIterator cx_array_iterator_ptr_(CxArray *array) {
+    return cxIteratorPtr(array->data, array->size);
 }
 
+void cx_array_free_(const CxAllocator *allocator, CxArray *array) {
+    cxFree(allocator, array->data);
+    array->data = NULL;
+    array->size = array->capacity = 0;
+}
+
+
 // implementation that finds ANY index
 static size_t cx_array_binary_search_inf_impl(
         const void *arr,
@@ -762,7 +523,6 @@
     struct cx_list_s base;
     void *data;
     size_t capacity;
-    CxArrayReallocator reallocator;
 } cx_array_list;
 
 static void cx_arl_destructor(struct cx_list_s *list) {
@@ -797,8 +557,8 @@
     CxArray wrap = {
         arl->data, list->collection.size, arl->capacity
     };
-    if (cx_array_insert_array_(list->collection.allocator, &wrap,
-        list->collection.elem_size, index, array, n)) {
+    if (cx_array_insert_(list->collection.allocator, &wrap,
+            list->collection.elem_size, index, array, n)) {
         return 0;
     }
     arl->data = wrap.data;
@@ -807,29 +567,41 @@
     return n;
 }
 
+static size_t cx_arl_insert_sorted_impl(
+        struct cx_list_s *list,
+        const void *sorted_data,
+        size_t n,
+        bool allow_duplicates
+) {
+    cx_array_list *arl = (cx_array_list *) list;
+    CxArray wrap = {
+        arl->data, list->collection.size, arl->capacity
+    };
+
+    if (cx_array_insert_sorted_(
+            list->collection.allocator,
+            &wrap,
+            list->collection.elem_size,
+            list->collection.cmpfunc,
+            sorted_data,
+            n,
+            allow_duplicates
+    )) {
+        // array list implementation is "all or nothing"
+        return 0;  // LCOV_EXCL_LINE
+    }
+    arl->data = wrap.data;
+    arl->capacity = wrap.capacity;
+    list->collection.size = wrap.size;
+    return n;
+}
+
 static size_t cx_arl_insert_sorted(
         struct cx_list_s *list,
         const void *sorted_data,
         size_t n
 ) {
-    // get a correctly typed pointer to the list
-    cx_array_list *arl = (cx_array_list *) list;
-
-    if (cx_array_insert_sorted(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            list->collection.cmpfunc,
-            sorted_data,
-            list->collection.elem_size,
-            n,
-            &arl->reallocator
-    )) {
-        // array list implementation is "all or nothing"
-        return 0;  // LCOV_EXCL_LINE
-    } else {
-        return n;
-    }
+    return cx_arl_insert_sorted_impl(list, sorted_data, n, true);
 }
 
 static size_t cx_arl_insert_unique(
@@ -837,24 +609,7 @@
         const void *sorted_data,
         size_t n
 ) {
-    // get a correctly typed pointer to the list
-    cx_array_list *arl = (cx_array_list *) list;
-
-    if (cx_array_insert_unique(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            list->collection.cmpfunc,
-            sorted_data,
-            list->collection.elem_size,
-            n,
-            &arl->reallocator
-    )) {
-        // array list implementation is "all or nothing"
-        return 0;  // LCOV_EXCL_LINE
-    } else {
-        return n;
-    }
+    return cx_arl_insert_sorted_impl(list, sorted_data, n, false);
 }
 
 static void *cx_arl_insert_element(
@@ -933,24 +688,21 @@
         );
     }
 
+    // calculate how many elements would need to be moved
+    size_t remaining = list->collection.size - index - remove;
+
     // short-circuit removal of last elements
-    if (index + remove == list->collection.size) {
+    if (remaining == 0) {
         list->collection.size -= remove;
         return remove;
     }
 
     // just move the elements to the left
-    cx_array_copy(
-            &arl->data,
-            &list->collection.size,
-            &arl->capacity,
-            0,
-            index,
-            ((char *) arl->data) + (index + remove) * list->collection.elem_size,
-            list->collection.elem_size,
-            list->collection.size - index - remove,
-            &arl->reallocator
-    );
+    char *first_remaining = arl->data;
+    first_remaining += (index + remove) * list->collection.elem_size;
+    char *dst_move = arl->data;
+    dst_move += index * list->collection.elem_size;
+    memmove(dst_move, first_remaining, remaining * list->collection.elem_size);
 
     // decrease the size
     list->collection.size -= remove;
@@ -1195,8 +947,5 @@
         return NULL;
     } // LCOV_EXCL_STOP
 
-    // configure the reallocator
-    list->reallocator = cx_array_reallocator(allocator, NULL);
-
     return (CxList *) list;
 }
--- a/src/cx/array_list.h	Sun Dec 14 23:17:48 2025 +0100
+++ b/src/cx/array_list.h	Mon Dec 15 19:00:51 2025 +0100
@@ -65,46 +65,77 @@
 cx_attr_nonnull
 CX_EXPORT int cx_array_init_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
-#define cx_array_init(array, capacity) cx_array_init_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+#define cx_array_init_a(allocator, array, capacity) cx_array_init_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+
+#define cx_array_init(array, capacity) cx_array_init_a(cxDefaultAllocator, array, capacity)
 
-#define cx_array_init_a(allocator, array, capacity) cx_array_init_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+cx_attr_nonnull
+CX_EXPORT void cx_array_init_fixed_(CxArray *array, const void *data, size_t capacity, size_t size);
+
+#define cx_array_init_fixed(array, fixed_size_array, num_initialized) cx_array_init_fixed_((CxArray*)&(array), fixed_size_array, cx_nmemb(fixed_size_array), num_initialized)
 
 cx_attr_nonnull
 CX_EXPORT int cx_array_reserve_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
-#define cx_array_reserve(array, capacity) cx_array_reserve_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+#define cx_array_reserve_a(allocator, array, capacity) cx_array_reserve_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
 
-#define cx_array_reserve_a(allocator, array, capacity) cx_array_reserve_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
+#define cx_array_reserve(array, capacity) cx_array_reserve_a(cxDefaultAllocator, array, capacity)
 
 cx_attr_nonnull
 CX_EXPORT int cx_array_move_to_new_(const CxAllocator *allocator, CxArray *array, size_t elem_size, size_t capacity);
 
-#define cx_array_move_to_new(array, capacity) cx_array_move_to_new_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
-
 #define cx_array_move_to_new_a(allocator, array, capacity) cx_array_move_to_new_(allocator, (CxArray*)&(array), sizeof((array).data[0]), capacity)
 
-cx_attr_nonnull
-CX_EXPORT int cx_array_add_(const CxAllocator *allocator, CxArray *array, size_t elem_size, void *element);
-
-#define cx_array_add(array, element) cx_array_add_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), element)
-
-#define cx_array_add_a(allocator, array, element) cx_array_add_(allocator, (CxArray*)&(array), sizeof((array).data[0]), element)
+#define cx_array_move_to_new(array, capacity) cx_array_move_to_new_a(cxDefaultAllocator, array, capacity)
 
 cx_attr_nonnull_arg(1, 2)
-CX_EXPORT int cx_array_insert_array_(const CxAllocator *allocator, CxArray *array,
+CX_EXPORT int cx_array_insert_(const CxAllocator *allocator, CxArray *array,
         size_t elem_size, size_t index, const void *other, size_t n);
 
-#define cx_array_insert_array(array, index, other, n) \
-        cx_array_insert_array_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), index, other, n)
+#define cx_array_add_a(allocator, array, element) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, element, 1)
+
+#define cx_array_add(array, element) cx_array_add_a(cxDefaultAllocator, array, element)
+
+#define cx_array_insert_a(allocator, array, index, element) \
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, element, 1)
+
+#define cx_array_insert(array, index, element) cx_array_insert_a(cxDefaultAllocator, array, index, element)
 
 #define cx_array_insert_array_a(allocator, array, index, other, n) \
-        cx_array_insert_array_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, other, n)
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), index, other, n)
 
-#define cx_array_add_array(array, other, n) \
-        cx_array_insert_array_(cxDefaultAllocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, other, n)
+#define cx_array_insert_array(array, index, other, n) cx_array_insert_array_a(cxDefaultAllocator, array, index, other, n)
 
 #define cx_array_add_array_a(allocator, array, other, n) \
-        cx_array_insert_array_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, other, n)
+        cx_array_insert_(allocator, (CxArray*)&(array), sizeof((array).data[0]), (array).size, other, n)
+
+#define cx_array_add_array(array, other, n) cx_array_add_array_a(cxDefaultAllocator, array, other, n)
+
+cx_attr_nonnull
+CX_EXPORT int cx_array_insert_sorted_(const CxAllocator *allocator, CxArray *array,
+        size_t elem_size, cx_compare_func cmp_func, const void *sorted_data, size_t n,
+        bool allow_duplicates);
+
+#define cx_array_insert_sorted_a(allocator, array, cmp_func, element) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, element, 1, true)
+
+#define cx_array_insert_sorted(array, cmp_func, element) cx_array_insert_sorted_a(cxDefaultAllocator, array, cmp_func, element)
+
+#define cx_array_insert_sorted_array_a(allocator, array, cmp_func, sorted_data, n) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, sorted_data, n, true)
+
+#define cx_array_insert_sorted_array(array, cmp_func, sorted_data, n) cx_array_insert_sorted_array_a(cxDefaultAllocator, array, cmp_func, sorted_data, n)
+
+#define cx_array_insert_unique_a(allocator, array, cmp_func, element) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, element, 1, false)
+
+#define cx_array_insert_unique(array, cmp_func, element) cx_array_insert_unique_a(cxDefaultAllocator, array, cmp_func, element)
+
+#define cx_array_insert_unique_array_a(allocator, array, cmp_func, sorted_data, n) \
+        cx_array_insert_sorted_(allocator, (CxArray*)&(array), sizeof((array).data[0]), cmp_func, sorted_data, n, false)
+
+#define cx_array_insert_unique_array(array, cmp_func, sorted_data, n) cx_array_insert_unique_array_a(cxDefaultAllocator, array, cmp_func, sorted_data, n)
 
 cx_attr_nodiscard cx_attr_nonnull
 CX_EXPORT CxIterator cx_array_iterator_(CxArray *array, size_t elem_size);
@@ -123,496 +154,7 @@
 
 #define cx_array_free_a(allocator, array) cx_array_free_(allocator, (CxArray*)&(array))
 
-/**
- * Declares variables for an array that can be used with the convenience macros.
- *
- * @par Examples
- * @code
- * // integer array with at most 255 elements
- * CX_ARRAY_DECLARE_SIZED(int, myarray, uint8_t)
- *
- * // array of MyObject* pointers where size and capacity are stored as unsigned int
- * CX_ARRAY_DECLARE_SIZED(MyObject*, objects, unsigned int)
- *
- * // initializing code
- * cx_array_initialize(myarray, 16); // reserve space for 16
- * cx_array_initialize(objects, 100); // reserve space for 100
- * @endcode
- *
- * @param type the type of the data
- * @param name the name of the array
- * @param size_type the type of the size (should be uint8_t, uint16_t, uint32_t, or size_t)
- *
- * @see cx_array_initialize()
- * @see cx_array_simple_copy()
- * @see cx_array_simple_add_sorted()
- * @see cx_array_simple_insert_sorted()
- */
-#define CX_ARRAY_DECLARE_SIZED(type, name, size_type) \
-    type * name; \
-    /** Array size. */ size_type name##_size; \
-    /** Array capacity. */ size_type name##_capacity
 
-/**
- * Declares variables for an array that can be used with the convenience macros.
- *
- * The size and capacity variables will have type @c size_t.
- * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type.
- *
- * @par Examples
- * @code
- * // int array
- * CX_ARRAY_DECLARE(int, myarray)
- *
- * // initializing code
- * cx_array_initialize(myarray, 32); // reserve space for 32
- * @endcode
- *
- * @param type the type of the data
- * @param name the name of the array
- *
- * @see cx_array_initialize()
- * @see cx_array_simple_copy()
- * @see cx_array_simple_add_sorted()
- * @see cx_array_simple_insert_sorted()
- */
-#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t)
-
-/**
- * Initializes an array with the given capacity.
- *
- * The type of the capacity depends on the type used during declaration.
- *
- * @par Examples
- * @code
- * CX_ARRAY_DECLARE_SIZED(int, arr1, uint8_t)
- * CX_ARRAY_DECLARE(int, arr2) // size and capacity are implicitly size_t
- *
- * // initializing code
- * cx_array_initialize(arr1, 500); // error: maximum for uint8_t is 255
- * cx_array_initialize(arr2, 500); // OK
- * @endcode
- *
- *
- * The memory for the array is allocated with the cxDefaultAllocator.
- *
- * @param array the name of the array
- * @param capacity the initial capacity
- * @see cx_array_initialize_a()
- * @see CX_ARRAY_DECLARE_SIZED()
- * @see CX_ARRAY_DECLARE()
- */
-#define cx_array_initialize(array, capacity) \
-        array##_capacity = capacity; \
-        array##_size = 0; \
-        array = cxMallocDefault(sizeof(array[0]) * capacity)
-
-/**
- * Initializes an array with the given capacity using the specified allocator.
- *
- * @par Example
- * @code
- * CX_ARRAY_DECLARE(int, myarray)
- *
- *
- * const CxAllocator *al = // ...
- * cx_array_initialize_a(al, myarray, 128);
- * // ...
- * cxFree(al, myarray); // remember to free with the same allocator
- * @endcode
- *
- * @param allocator (@c CxAllocator*) the allocator
- * @param array the name of the array
- * @param capacity the initial capacity
- * @see cx_array_initialize()
- * @see CX_ARRAY_DECLARE_SIZED()
- * @see CX_ARRAY_DECLARE()
- */
-#define cx_array_initialize_a(allocator, array, capacity) \
-        array##_capacity = capacity; \
-        array##_size = 0; \
-        array = cxMalloc(allocator, sizeof(array[0]) * capacity)
-
-/**
- * Defines a reallocation mechanism for arrays.
- * You can create your own, use cx_array_reallocator(), or
- * use the #cx_array_default_reallocator.
- */
-struct cx_array_reallocator_s {
-    /**
-     * Reallocates space for the given array.
-     *
-     * Implementations are not required to free the original array.
-     * This allows reallocation of static or stack memory by allocating heap memory
-     * and copying the array contents; namely when @c stack_ptr in this struct
-     * is not @c NULL and @p array equals @c stack_ptr.
-     *
-     * @param array the array to reallocate
-     * @param old_capacity the old number of elements
-     * @param new_capacity the new number of elements
-     * @param elem_size the size of each element
-     * @param alloc a reference to this allocator
-     * @return a pointer to the reallocated memory or @c NULL on failure
-     */
-    void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity,
-            size_t elem_size, struct cx_array_reallocator_s *alloc);
-
-    /**
-     * The allocator that shall be used for the reallocations.
-     */
-    const CxAllocator *allocator;
-    /**
-     * Optional pointer to stack memory
-     * if the array is originally located on the stack.
-     */
-    const void *stack_ptr;
-};
-
-/**
- * Typedef for the array reallocator struct.
- */
-typedef struct cx_array_reallocator_s CxArrayReallocator;
-
-/**
- * A default array reallocator that is based on the cxDefaultAllocator.
- */
-CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator;
-
-/**
- * Creates a new array reallocator.
- *
- * When @p allocator is @c NULL, the cxDefaultAllocator will be used.
- *
- * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used
- * @em only for the specific array initially located at @p stack_ptr.
- * When reallocation is needed, the reallocator checks if the array is
- * still located at @p stack_ptr and copies the contents to the heap.
- *
- * @note Invoking this function with both arguments being @c NULL will return a
- * reallocator that behaves like #cx_array_default_reallocator.
- *
- * @param allocator the allocator this reallocator shall be based on
- * @param stack_ptr the address of the array when the array is initially located
- * on the stack or shall not reallocate in place
- * @return an array reallocator
- */
-CX_EXPORT CxArrayReallocator cx_array_reallocator(
-        const struct cx_allocator_s *allocator, const void *stack_ptr);
-/**
- * Copies elements from one array to another.
- *
- * The elements are copied to the @p target array at the specified @p index,
- * overwriting possible elements. The @p index does not need to be in range of
- * the current array @p size. If the new index plus the number of elements added
- * extends the array's size, the remaining @p capacity is used.
- *
- * If the @p capacity is also insufficient to hold the new data, a reallocation
- * attempt is made with the specified @p reallocator.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
- *
- * The @p width in bytes refers to the size and capacity.
- * Both must have the same width.
- * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit
- * architecture. If set to zero, the native word width is used.
- *
- * @note When this function does reallocate the array, it may allocate more
- * space than required to avoid further allocations in the near future.
- *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param width the width in bytes for the @p size and @p capacity or zero for default
- * @param index the index where the copied elements shall be placed
- * @param src the source array
- * @param elem_size the size of one element
- * @param elem_count the number of elements to copy
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
- * @retval zero success
- * @retval non-zero failure
- * @see cx_array_reallocator()
- */
-cx_attr_nonnull_arg(1, 2, 3, 6)
-CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width,
-        size_t index, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
-
-/**
- * Convenience macro that uses cx_array_copy() with a default layout and
- * the specified reallocator.
- *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param index (@c size_t) the index where the copied elements shall be placed
- * @param src (@c void*) the source array
- * @param count (@c size_t) the number of elements to copy
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_copy()
- */
-#define cx_array_simple_copy_a(reallocator, array, index, src, count) \
-    cx_array_copy((void**)&(array), &(array##_size), &(array##_capacity), \
-        sizeof(array##_size), index, src, sizeof((array)[0]), count, \
-        reallocator)
-
-/**
- * Convenience macro that uses cx_array_copy() with a default layout and
- * the default reallocator.
- *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param index (@c size_t) the index where the copied elements shall be placed
- * @param src (@c void*) the source array
- * @param count (@c size_t) the number of elements to copy
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_copy_a()
- */
-#define cx_array_simple_copy(array, index, src, count) \
-    cx_array_simple_copy_a(NULL, array, index, src, count)
-
-/**
- * Inserts a sorted array into another sorted array.
- *
- * If either the target or the source array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
- *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
- *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param cmp_func the compare function for the elements
- * @param src the source array
- * @param elem_size the size of one element
- * @param elem_count the number of elements to insert
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
- * @retval zero success
- * @retval non-zero failure
- */
-cx_attr_nonnull_arg(1, 2, 3, 5)
-CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity,
-        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
-
-/**
- * Inserts an element into a sorted array.
- *
- * If the target array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
- *
- * If the capacity is not enough to hold the new data, a reallocation
- * attempt is made.
- *
- * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
- * It is important, however, that @p size and @p capacity are pointers to
- * variables of the same type.
- *
- * @param target (@c void**) a pointer to the target array
- * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
- * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
- * @param elem_size (@c size_t) the size of one element
- * @param elem (@c void*) a pointer to the element to add
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @retval zero success
- * @retval non-zero failure
- */
-#define cx_array_add_sorted(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
-    cx_array_insert_sorted((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
-
-/**
- * Convenience macro for cx_array_add_sorted() with a default
- * layout and the specified reallocator.
- *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_sorted()
- */
-#define cx_array_simple_add_sorted_a(reallocator, array, elem, cmp_func) \
-    cx_array_add_sorted(&array, &(array##_size), &(array##_capacity), \
-        sizeof((array)[0]), &(elem), cmp_func, reallocator)
-
-/**
- * Convenience macro for cx_array_add_sorted() with a default
- * layout and the default reallocator.
- *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_sorted_a()
- */
-#define cx_array_simple_add_sorted(array, elem, cmp_func) \
-    cx_array_simple_add_sorted_a(NULL, array, elem, cmp_func)
-
-/**
- * Convenience macro for cx_array_insert_sorted() with a default
- * layout and the specified reallocator.
- *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_sorted()
- */
-#define cx_array_simple_insert_sorted_a(reallocator, array, src, n, cmp_func) \
-    cx_array_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \
-        cmp_func, src, sizeof((array)[0]), n, reallocator)
-
-/**
- * Convenience macro for cx_array_insert_sorted() with a default
- * layout and the default reallocator.
- *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_sorted_a()
- */
-#define cx_array_simple_insert_sorted(array, src, n, cmp_func) \
-    cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func)
-
-
-/**
- * Inserts a sorted array into another sorted array, avoiding duplicates.
- *
- * If either the target or the source array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
- *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
- * You can create your own reallocator by hand, use #cx_array_default_reallocator,
- * or use the convenience function cx_array_reallocator() to create a custom reallocator.
- *
- * @param target a pointer to the target array
- * @param size a pointer to the size of the target array
- * @param capacity a pointer to the capacity of the target array
- * @param cmp_func the compare function for the elements
- * @param src the source array
- * @param elem_size the size of one element
- * @param elem_count the number of elements to insert
- * @param reallocator the array reallocator to use
- * (@c NULL defaults to #cx_array_default_reallocator)
- * @retval zero success
- * @retval non-zero failure
- */
-cx_attr_nonnull_arg(1, 2, 3, 5)
-CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity,
-        cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count,
-        CxArrayReallocator *reallocator);
-
-/**
- * Inserts an element into a sorted array if it does not exist.
- *
- * If the target array is not already sorted with respect
- * to the specified @p cmp_func, the behavior is undefined.
- *
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made.
- *
- * The \@ SIZE_TYPE is flexible and can be any unsigned integer type.
- * It is important, however, that @p size and @p capacity are pointers to
- * variables of the same type.
- *
- * @param target (@c void**) a pointer to the target array
- * @param size (@c SIZE_TYPE*) a pointer to the size of the target array
- * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array
- * @param elem_size (@c size_t) the size of one element
- * @param elem (@c void*) a pointer to the element to add
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @retval zero success (also when the element was already present)
- * @retval non-zero failure
- */
-#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \
-    cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator)
-
-/**
- * Convenience macro for cx_array_add_unique() with a default
- * layout and the specified reallocator.
- *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_unique()
- */
-#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \
-    cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \
-        sizeof((array)[0]), &(elem), cmp_func, reallocator)
-
-/**
- * Convenience macro for cx_array_add_unique() with a default
- * layout and the default reallocator.
- *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param elem the element to add (NOT a pointer, address is automatically taken)
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_add_unique_a()
- */
-#define cx_array_simple_add_unique(array, elem, cmp_func) \
-    cx_array_simple_add_unique_a(NULL, array, elem, cmp_func)
-
-/**
- * Convenience macro for cx_array_insert_unique() with a default
- * layout and the specified reallocator.
- *
- * @param reallocator (@c CxArrayReallocator*) the array reallocator to use
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_unique()
- */
-#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \
-    cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \
-        cmp_func, src, sizeof((array)[0]), n, reallocator)
-
-/**
- * Convenience macro for cx_array_insert_unique() with a default
- * layout and the default reallocator.
- *
- * @param array the name of the array (NOT a pointer or alias to the array)
- * @param src (@c void*) pointer to the source array
- * @param n (@c size_t) number of elements in the source array
- * @param cmp_func (@c cx_cmp_func) the compare function for the elements
- * @retval zero success
- * @retval non-zero failure
- * @see CX_ARRAY_DECLARE()
- * @see cx_array_simple_insert_unique_a()
- */
-#define cx_array_simple_insert_unique(array, src, n, cmp_func) \
-    cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func)
 
 /**
  * Searches the largest lower bound in a sorted array.
--- a/src/json.c	Sun Dec 14 23:17:48 2025 +0100
+++ b/src/json.c	Mon Dec 15 19:00:51 2025 +0100
@@ -549,13 +549,9 @@
     memset(json, 0, sizeof(CxJson));
     json->allocator = allocator;
 
-    json->states.data = json->states_internal;
-    json->states.capacity = cx_nmemb(json->states_internal);
+    cx_array_init_fixed(json->states, json->states_internal, 1);
     json->states.data[0] = JP_STATE_VALUE_BEGIN;
-    json->states.size = 1;
-
-    json->vbuf.data = json->vbuf_internal;
-    json->vbuf.capacity = cx_nmemb(json->vbuf_internal);
+    cx_array_init_fixed(json->vbuf, json->vbuf_internal, 0);
 }
 
 void cxJsonDestroy(CxJson *json) {
--- a/tests/test_list.c	Sun Dec 14 23:17:48 2025 +0100
+++ b/tests/test_list.c	Mon Dec 15 19:00:51 2025 +0100
@@ -46,7 +46,6 @@
     arr.data[3] = 7;
     arr.data[4] = 11;
     arr.size = 3;
-    arr.capacity = 5;
     int elem = 8, elem2 = 47;
     int result;
     CX_TEST_DO {
@@ -75,26 +74,62 @@
     cx_array_free(arr);
 }
 
-CX_TEST(test_array_copy_overlap) {
-    CX_ARRAY_DECLARE_SIZED(char, arr, uint8_t);
-    cx_array_initialize(arr, 16);
-    strcpy(arr, "Hello, World!");
+CX_TEST(test_array_reserve) {
+    CX_ARRAY(int, arr);
+    cx_array_init(arr, 5);
+    arr.data[0] = 2;
+    arr.data[1] = 3;
+    arr.data[2] = 5;
+    arr.size = 3;
     CX_TEST_DO {
-        errno = 0;
-        int result = cx_array_simple_copy(arr, 7, arr, 14);
-        CX_TEST_ASSERT(result == 0);
-        CX_TEST_ASSERT(errno == 0);
-        CX_TEST_ASSERT(arr_size == 21);
-        CX_TEST_ASSERT(arr_capacity == 32);
-        CX_TEST_ASSERT(0 == memcmp(arr, "Hello, Hello, World!\0", 21));
+        // extend
+        CX_TEST_ASSERT(0 == cx_array_reserve(arr, 8));
+        CX_TEST_ASSERT(arr.size == 3);
+        CX_TEST_ASSERT(arr.capacity == 8);
+        CX_TEST_ASSERT(arr.data[0] == 2);
+        CX_TEST_ASSERT(arr.data[1] == 3);
+        CX_TEST_ASSERT(arr.data[2] == 5);
+
+        // shrink
+        CX_TEST_ASSERT(0 == cx_array_reserve(arr, 2));
+        CX_TEST_ASSERT(arr.size == 2);
+        CX_TEST_ASSERT(arr.capacity == 2);
+        CX_TEST_ASSERT(arr.data[0] == 2);
+        CX_TEST_ASSERT(arr.data[1] == 3);
     }
-    cxFreeDefault(arr);
+    cx_array_free(arr);
 }
 
-CX_TEST(test_array_reserve) {
+CX_TEST(test_array_move_to_new) {
+    CX_ARRAY(int, arr);
+    int fixed[5] = {2, 4, 6};
+    cx_array_init_fixed(arr, fixed, 3);
     CX_TEST_DO {
-        // TODO: create new test for the new API
+        CX_TEST_ASSERT(arr.data == fixed);
+        CX_TEST_ASSERT(arr.size == 3);
+        CX_TEST_ASSERT(arr.capacity == 5);
+
+        CX_TEST_ASSERT(0 == cx_array_move_to_new(arr, 8));
+
+        CX_TEST_ASSERT(arr.data != fixed);
+        CX_TEST_ASSERT(arr.size == 3);
+        CX_TEST_ASSERT(arr.capacity == 8);
+        CX_TEST_ASSERT(arr.data[0] == 2);
+        CX_TEST_ASSERT(arr.data[1] == 4);
+        CX_TEST_ASSERT(arr.data[2] == 6);
+
+        for (int x = 8 ; x <= 18 ; x+=2) {
+            cx_array_add(arr, &x);
+        }
+
+        CX_TEST_ASSERT(arr.size == 9);
+        CX_TEST_ASSERT(arr.capacity >= 9);
+
+        for (int x = 1 ; x <= 9 ; x++) {
+            CX_TEST_ASSERT(arr.data[x-1] == 2*x);
+        }
     }
+    cx_array_free(arr);
 }
 
 CX_TEST(test_array_insert_sorted) {
@@ -115,50 +150,50 @@
             70, 71, 71, 72, 75, 75, 75, 77, 78, 80, 90, 90, 120, 130
     };
 
-    CX_ARRAY_DECLARE(int, array);
-    cx_array_initialize(array, 4);
+    CX_ARRAY(int, array);
+    cx_array_init(array, 4);
 
     CX_TEST_DO {
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d1, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 1);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d2, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 2);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d3, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 3);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d4, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 4);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d5, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 5);
-        CX_TEST_ASSERT(array_capacity >= 5);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_sorted(array, d6a, 6, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 11);
-        CX_TEST_ASSERT(array_capacity >= 11);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_sorted(array, d7a, 6, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 17);
-        CX_TEST_ASSERT(array_capacity >= 17);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d8, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 18);
-        CX_TEST_ASSERT(array_capacity >= 18);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_sorted(array, d9, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 19);
-        CX_TEST_ASSERT(array_capacity >= 19);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_sorted(array, d10a, 3, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 22);
-        CX_TEST_ASSERT(array_capacity >= 22);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_sorted(array, d11a, 6, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 28);
-        CX_TEST_ASSERT(array_capacity >= 28);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_sorted(array, d12a, 3, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 31);
-        CX_TEST_ASSERT(array_capacity >= 31);
-
-        CX_TEST_ASSERT(0 == memcmp(array, expected, 31 * sizeof(int)));
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d1));
+        CX_TEST_ASSERT(array.size == 1);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d2));
+        CX_TEST_ASSERT(array.size == 2);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d3));
+        CX_TEST_ASSERT(array.size == 3);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d4));
+        CX_TEST_ASSERT(array.size == 4);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d5));
+        CX_TEST_ASSERT(array.size == 5);
+        CX_TEST_ASSERT(array.capacity >= 5);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted_array(array, cx_cmp_int, d6a, 6));
+        CX_TEST_ASSERT(array.size == 11);
+        CX_TEST_ASSERT(array.capacity >= 11);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted_array(array, cx_cmp_int, d7a, 6));
+        CX_TEST_ASSERT(array.size == 17);
+        CX_TEST_ASSERT(array.capacity >= 17);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d8));
+        CX_TEST_ASSERT(array.size == 18);
+        CX_TEST_ASSERT(array.capacity >= 18);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted(array, cx_cmp_int, &d9));
+        CX_TEST_ASSERT(array.size == 19);
+        CX_TEST_ASSERT(array.capacity >= 19);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted_array(array, cx_cmp_int, d10a, 3));
+        CX_TEST_ASSERT(array.size == 22);
+        CX_TEST_ASSERT(array.capacity >= 22);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted_array(array, cx_cmp_int, d11a, 6));
+        CX_TEST_ASSERT(array.size == 28);
+        CX_TEST_ASSERT(array.capacity >= 28);
+        CX_TEST_ASSERT(0 == cx_array_insert_sorted_array(array, cx_cmp_int, d12a, 3));
+        CX_TEST_ASSERT(array.size == 31);
+        CX_TEST_ASSERT(array.capacity >= 31);
+
+        CX_TEST_ASSERT(0 == memcmp(array.data, expected, 31 * sizeof(int)));
     }
-    cxFreeDefault(array);
+    cx_array_free(array);
 }
 
 CX_TEST(test_array_insert_unique) {
@@ -178,47 +213,47 @@
             65, 67, 70, 75, 77, 78, 80, 90, 95, 100, 110
     };
 
-    CX_ARRAY_DECLARE(int, array);
-    cx_array_initialize(array, 4);
+    CX_ARRAY(int, array);
+    cx_array_init(array, 4);
 
     CX_TEST_DO {
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d1, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 1);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d2, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 2);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d3, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 3);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d4, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 4);
-        CX_TEST_ASSERT(array_capacity == 4);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d5, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 5);
-        CX_TEST_ASSERT(array_capacity >= 5);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_unique(array, d6a, 6, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 11);
-        CX_TEST_ASSERT(array_capacity >= 11);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_unique(array, d7a, 6, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 17);
-        CX_TEST_ASSERT(array_capacity >= 17);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d8, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 18);
-        CX_TEST_ASSERT(array_capacity >= 18);
-        CX_TEST_ASSERT(0 == cx_array_simple_add_unique(array, d9, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 18);
-        CX_TEST_ASSERT(array_capacity >= 18);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_unique(array, d10a, 3, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 19);
-        CX_TEST_ASSERT(array_capacity >= 19);
-        CX_TEST_ASSERT(0 == cx_array_simple_insert_unique(array, d11a, 8, cx_cmp_int));
-        CX_TEST_ASSERT(array_size == 22);
-        CX_TEST_ASSERT(array_capacity >= 22);
-
-        CX_TEST_ASSERT(0 == memcmp(array, expected, 22 * sizeof(int)));
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d1));
+        CX_TEST_ASSERT(array.size == 1);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d2));
+        CX_TEST_ASSERT(array.size == 2);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d3));
+        CX_TEST_ASSERT(array.size == 3);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d4));
+        CX_TEST_ASSERT(array.size == 4);
+        CX_TEST_ASSERT(array.capacity == 4);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d5));
+        CX_TEST_ASSERT(array.size == 5);
+        CX_TEST_ASSERT(array.capacity >= 5);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique_array(array, cx_cmp_int, d6a, 6));
+        CX_TEST_ASSERT(array.size == 11);
+        CX_TEST_ASSERT(array.capacity >= 11);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique_array(array, cx_cmp_int, d7a, 6));
+        CX_TEST_ASSERT(array.size == 17);
+        CX_TEST_ASSERT(array.capacity >= 17);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d8));
+        CX_TEST_ASSERT(array.size == 18);
+        CX_TEST_ASSERT(array.capacity >= 18);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique(array, cx_cmp_int, &d9));
+        CX_TEST_ASSERT(array.size == 18);
+        CX_TEST_ASSERT(array.capacity >= 18);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique_array(array, cx_cmp_int, d10a, 3));
+        CX_TEST_ASSERT(array.size == 19);
+        CX_TEST_ASSERT(array.capacity >= 19);
+        CX_TEST_ASSERT(0 == cx_array_insert_unique_array(array, cx_cmp_int, d11a, 8));
+        CX_TEST_ASSERT(array.size == 22);
+        CX_TEST_ASSERT(array.capacity >= 22);
+
+        CX_TEST_ASSERT(0 == memcmp(array.data, expected, 22 * sizeof(int)));
     }
-    cxFreeDefault(array);
+    cx_array_free(array);
 }
 
 CX_TEST(test_array_binary_search) {
@@ -3243,8 +3278,8 @@
     CxTestSuite *suite = cx_test_suite_new("array_list");
 
     cx_test_register(suite, test_array_add);
-    cx_test_register(suite, test_array_copy_overlap);
     cx_test_register(suite, test_array_reserve);
+    cx_test_register(suite, test_array_move_to_new);
     cx_test_register(suite, test_array_insert_sorted);
     cx_test_register(suite, test_array_insert_unique);
     cx_test_register(suite, test_array_binary_search);

mercurial