Sidebar s = (Sidebar)widget;
XftFont *xftFont = s->sidebar.font->fonts->font;
CxList *tracks = s->sidebar.window->playlist.tracks;
- size_t numTracks = tracks->size;
+ size_t numTracks = cxListSize(tracks);
int fontheight = xftFont->ascent;
int height = s->sidebar.elmHeight;
#include "settings.h"
#include <cx/buffer.h>
-#include <cx/utils.h>
#include <cx/array_list.h>
static XtAppContext app;
}
void CleanOpenFileArgs(void) {
- cxListDestroy(open_file_arg);
+ cxListFree(open_file_arg);
open_file_arg = NULL;
}
void PlayListInit(MainWindow *win) {
win->playlist.tracks = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 64);
- win->playlist.tracks->simple_destructor = free;
+ win->playlist.tracks->collection.simple_destructor = free;
win->playlist.current_track = -1;
}
void PlayListPlayNext(MainWindow *win, bool force) {
CxList *tracks = win->playlist.tracks;
if(!tracks) return;
- size_t len = tracks->size;
+ size_t len = cxListSize(tracks);
int current = win->playlist.current_track;
if(win->playlist.repeatTrack) {
void PlayListPlayTrack(MainWindow *win, int i) {
CxList *tracks = win->playlist.tracks;
- if(i < tracks->size) {
+ if(i < cxListSize(tracks)) {
char *file = cxListAt(tracks, i);
if(file) {
win->playlist.current_track = i;
cxstring name;
cxstring value;
while(ucx_properties_next(parser, &name, &value)) {
- cxmutstr mutvalue = cx_strdup_a(map->allocator, value);
+ cxmutstr mutvalue = cx_strdup_a(map->collection.allocator, value);
if(!mutvalue.ptr) {
return 1;
}
if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
- cxFree(map->allocator, mutvalue.ptr);
+ cxFree(map->collection.allocator, mutvalue.ptr);
return 1;
}
}
}
int ucx_properties_store(CxMap *map, FILE *file) {
- CxIterator iter = cxMapIterator(map);
+ CxMapIterator iter = cxMapIterator(map);
cxstring value;
size_t written;
#include <cx/hash_map.h>
//include <ucx/properties.h>
#include <cx/buffer.h>
-#include <cx/utils.h>
+#include <cx/streams.h>
#include <cx/printf.h>
#define CONFIG_BASE_DIR ".config"
clearPlaylist = FALSE;
}
PlayListAddFile(win, file);
- PlayListPlayTrack(win, win->playlist.tracks->size-1);
+ PlayListPlayTrack(win, cxListSize(win->playlist.tracks)-1);
free(data);
return 0;
SRC += map.c
SRC += printf.c
SRC += string.c
-SRC += utils.c
+SRC += streams.c
+SRC += tree.c
+SRC += properties.c
+SRC += json.c
OBJ = $(SRC:%.c=../build/ucx/%.$(OBJ_EXT))
+++ /dev/null
-UCX is a library for common data structures, algorithms and string functions.
-
-More informations at: https://develop.uap-core.de/ucx/
-
#include "cx/allocator.h"
-__attribute__((__malloc__, __alloc_size__(2)))
+#include <errno.h>
+#include <string.h>
+
static void *cx_malloc_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
size_t n
) {
return malloc(n);
}
-__attribute__((__warn_unused_result__, __alloc_size__(3)))
static void *cx_realloc_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
void *mem,
size_t n
) {
return realloc(mem, n);
}
-__attribute__((__malloc__, __alloc_size__(2, 3)))
static void *cx_calloc_stdlib(
- __attribute__((__unused__)) void *d,
- size_t nelem,
- size_t n
+ cx_attr_unused void *d,
+ size_t nmemb,
+ size_t size
) {
- return calloc(nelem, n);
+ return calloc(nmemb, size);
}
-__attribute__((__nonnull__))
static void cx_free_stdlib(
- __attribute__((__unused__)) void *d,
+ cx_attr_unused void *d,
void *mem
) {
free(mem);
}
-static cx_allocator_class cx_default_allocator_class = {
+static cx_allocator_class cx_stdlib_allocator_class = {
cx_malloc_stdlib,
cx_realloc_stdlib,
cx_calloc_stdlib,
cx_free_stdlib
};
-struct cx_allocator_s cx_default_allocator = {
- &cx_default_allocator_class,
+struct cx_allocator_s cx_stdlib_allocator = {
+ &cx_stdlib_allocator_class,
NULL
};
-CxAllocator *cxDefaultAllocator = &cx_default_allocator;
-
+const CxAllocator * const cxStdlibAllocator = &cx_stdlib_allocator;
+const CxAllocator * cxDefaultAllocator = cxStdlibAllocator;
-int cx_reallocate(
+int cx_reallocate_(
void **mem,
size_t n
) {
void *nmem = realloc(*mem, n);
if (nmem == NULL) {
- return 1;
+ return 1; // LCOV_EXCL_LINE
} else {
*mem = nmem;
return 0;
}
}
+int cx_reallocatearray_(
+ void **mem,
+ size_t nmemb,
+ size_t size
+) {
+ size_t n;
+ if (cx_szmul(nmemb, size, &n)) {
+ errno = EOVERFLOW;
+ return 1;
+ } else {
+ void *nmem = realloc(*mem, n);
+ if (nmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+ }
+}
+
// IMPLEMENTATION OF HIGH LEVEL API
void *cxMalloc(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
size_t n
) {
return allocator->cl->malloc(allocator->data, n);
}
+void *cxZalloc(
+ const CxAllocator *allocator,
+ size_t n
+) {
+ void *mem = allocator->cl->malloc(allocator->data, n);
+ if (mem != NULL) {
+ memset(mem, 0, n);
+ }
+ return mem;
+}
+
void *cxRealloc(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
void *mem,
size_t n
) {
return allocator->cl->realloc(allocator->data, mem, n);
}
-int cxReallocate(
- CxAllocator const *allocator,
+void *cxReallocArray(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t nmemb,
+ size_t size
+) {
+ size_t n;
+ if (cx_szmul(nmemb, size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ } else {
+ return allocator->cl->realloc(allocator->data, mem, n);
+ }
+}
+
+int cxReallocate_(
+ const CxAllocator *allocator,
void **mem,
size_t n
) {
void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
if (nmem == NULL) {
- return 1;
+ return 1; // LCOV_EXCL_LINE
+ } else {
+ *mem = nmem;
+ return 0;
+ }
+}
+
+int cxReallocateArray_(
+ const CxAllocator *allocator,
+ void **mem,
+ size_t nmemb,
+ size_t size
+) {
+ void *nmem = cxReallocArray(allocator, *mem, nmemb, size);
+ if (nmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
} else {
*mem = nmem;
return 0;
}
void *cxCalloc(
- CxAllocator const *allocator,
- size_t nelem,
- size_t n
+ const CxAllocator *allocator,
+ size_t nmemb,
+ size_t size
) {
- return allocator->cl->calloc(allocator->data, nelem, n);
+ return allocator->cl->calloc(allocator->data, nmemb, size);
}
void cxFree(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
void *mem
) {
allocator->cl->free(allocator->data, mem);
*/
#include "cx/array_list.h"
+#include "cx/compare.h"
#include <assert.h>
#include <string.h>
+#include <errno.h>
+
+// 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;
+ if (cx_szmul(new_capacity, elem_size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ return cxReallocDefault(array, n);
+}
+
+CxArrayReallocator cx_array_default_reallocator_impl = {
+ cx_array_default_realloc, NULL, NULL, 0, 0
+};
+
+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;
+ if (cx_szmul(new_capacity, elem_size, &n)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ // retrieve the pointer to the actual allocator
+ const CxAllocator *al = alloc->ptr1;
+
+ // check if the array is still located on the stack
+ void *newmem;
+ if (array == alloc->ptr2) {
+ 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 *stackmem
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ return (struct cx_array_reallocator_s) {
+ cx_array_advanced_realloc,
+ (void*) allocator, (void*) stackmem,
+ 0, 0
+ };
+}
// LOW LEVEL ARRAY LIST FUNCTIONS
-enum cx_array_copy_result cx_array_copy(
+static size_t cx_array_align_capacity(
+ size_t cap,
+ size_t alignment,
+ size_t max
+) {
+ if (cap > max - alignment) {
+ return cap;
+ } else {
+ return cap - (cap % alignment) + alignment;
+ }
+}
+
+int cx_array_reserve(
+ void **array,
+ void *size,
+ void *capacity,
+ unsigned width,
+ size_t elem_size,
+ size_t elem_count,
+ CxArrayReallocator *reallocator
+) {
+ // assert pointers
+ assert(array != NULL);
+ assert(size != NULL);
+ assert(capacity != 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(*array != NULL || oldcap == 0);
+
+ // check for overflow
+ if (elem_count > max_size - oldsize) {
+ errno = EOVERFLOW;
+ return 1;
+ }
+
+ // determine new capacity
+ size_t newcap = oldsize + elem_count;
+
+ // reallocate if possible
+ if (newcap > oldcap) {
+ // calculate new capacity (next number divisible by 16)
+ newcap = cx_array_align_capacity(newcap, 16, max_size);
+
+ // perform reallocation
+ void *newmem = reallocator->realloc(
+ *array, oldcap, newcap, elem_size, reallocator
+ );
+ if (newmem == NULL) {
+ return 1; // LCOV_EXCL_LINE
+ }
+
+ // store new pointer
+ *array = newmem;
+
+ // store new capacity
+ if (width == 0 || width == sizeof(size_t)) {
+ *(size_t*) capacity = newcap;
+ } else if (width == sizeof(uint16_t)) {
+ *(uint16_t*) capacity = (uint16_t) newcap;
+ } else if (width == sizeof(uint8_t)) {
+ *(uint8_t*) capacity = (uint8_t) newcap;
+ }
+#if CX_WORDSIZE == 64
+ else if (width == sizeof(uint32_t)) {
+ *(uint32_t*) capacity = (uint32_t) newcap;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+int cx_array_copy(
void **target,
- size_t *size,
- size_t *capacity,
+ void *size,
+ void *capacity,
+ unsigned width,
size_t index,
- void const *src,
+ const void *src,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
+ CxArrayReallocator *reallocator
) {
// assert pointers
assert(target != NULL);
assert(size != NULL);
+ assert(capacity != NULL);
assert(src != NULL);
- // determine capacity
- size_t cap = capacity == NULL ? *size : *capacity;
+ // 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
size_t minsize = index + elem_count;
- size_t newsize = *size < minsize ? minsize : *size;
- bool needrealloc = newsize > cap;
+ size_t newsize = oldsize < minsize ? minsize : oldsize;
// reallocate if possible
- if (needrealloc) {
- // a reallocator and a capacity variable must be available
- if (reallocator == NULL || capacity == NULL) {
- return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
- }
-
+ size_t newcap = oldcap;
+ if (newsize > 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 + cap * elem_size;
+ && srcaddr < targetaddr + oldcap * elem_size;
// calculate new capacity (next number divisible by 16)
- cap = newsize - (newsize % 16) + 16;
- assert(cap > newsize);
+ newcap = cx_array_align_capacity(newsize, 16, max_size);
+ assert(newcap > newsize);
// perform reallocation
void *newmem = reallocator->realloc(
- *target, cap, elem_size, reallocator
+ *target, oldcap, newcap, elem_size, reallocator
);
if (newmem == NULL) {
- return CX_ARRAY_COPY_REALLOC_FAILED;
+ return 1;
}
// repair src pointer, if necessary
src = ((char *) newmem) + (srcaddr - targetaddr);
}
- // store new pointer and capacity
+ // store new pointer
*target = newmem;
- *capacity = cap;
}
// determine target pointer
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);
- *size = newsize;
+
+ // 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 CX_ARRAY_COPY_SUCCESS;
+ 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
+) {
+ // assert pointers
+ assert(target != NULL);
+ assert(size != NULL);
+ assert(capacity != 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;
+
+ // overflow check
+ if (elem_count > SIZE_MAX - *size) {
+ errno = EOVERFLOW;
+ return 1;
+ }
+
+ // store some counts
+ size_t old_size = *size;
+ size_t old_capacity = *capacity;
+ size_t needed_capacity = old_size + elem_count;
+
+ // if we need more than we have, try a reallocation
+ if (needed_capacity > old_capacity) {
+ size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX);
+ void *new_mem = reallocator->realloc(
+ *target, old_capacity, new_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
+ }
+ *target = new_mem;
+ *capacity = new_capacity;
+ }
+
+ // now we have guaranteed that we can insert everything
+ size_t new_size = old_size + elem_count;
+ *size = new_size;
+
+ // declare the source and destination indices/pointers
+ size_t si = 0, di = 0;
+ const char *src = sorted_data;
+ char *dest = *target;
+
+ // find the first insertion point
+ di = cx_array_binary_search_sup(dest, old_size, elem_size, src, cmp_func);
+ dest += di * elem_size;
+
+ // move the remaining elements in the array completely to the right
+ // 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;
+ 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) {
+ // determine how many source elements can be inserted
+ size_t copy_len, bytes_copied;
+ copy_len = cx_array_binary_search_sup(
+ src,
+ elem_count - si,
+ elem_size,
+ bptr,
+ cmp_func
+ );
+
+ // copy the source elements
+ bytes_copied = copy_len * elem_size;
+ memcpy(dest, src, bytes_copied);
+ dest += bytes_copied;
+ src += bytes_copied;
+ si += copy_len;
+
+ // when all source elements are in place, we are done
+ if (si >= elem_count) break;
+
+ // determine how many buffered elements need to be restored
+ copy_len = cx_array_binary_search_sup(
+ bptr,
+ new_size - bi,
+ elem_size,
+ src,
+ cmp_func
+ );
+
+ // restore the buffered elements
+ bytes_copied = copy_len * elem_size;
+ memmove(dest, bptr, bytes_copied);
+ dest += bytes_copied;
+ bptr += bytes_copied;
+ bi += copy_len;
+ }
+
+ // still source elements left? simply append them
+ if (si < elem_count) {
+ memcpy(dest, src, elem_size * (elem_count - si));
+ }
+
+ // still buffer elements left?
+ // don't worry, we already moved them to the correct place
+
+ return 0;
+}
+
+size_t cx_array_binary_search_inf(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ // special case: empty array
+ if (size == 0) return 0;
+
+ // declare a variable that will contain the compare results
+ int result;
+
+ // cast the array pointer to something we can use offsets with
+ const char *array = arr;
+
+ // check the first array element
+ result = cmp_func(elem, array);
+ if (result < 0) {
+ return size;
+ } else if (result == 0) {
+ return 0;
+ }
+
+ // special case: there is only one element and that is smaller
+ if (size == 1) return 0;
+
+ // check the last array element
+ result = cmp_func(elem, array + elem_size * (size - 1));
+ if (result >= 0) {
+ return size - 1;
+ }
+
+ // the element is now guaranteed to be somewhere in the list
+ // so start the binary search
+ size_t left_index = 1;
+ size_t right_index = size - 1;
+ size_t pivot_index;
+
+ while (left_index <= right_index) {
+ pivot_index = left_index + (right_index - left_index) / 2;
+ const char *arr_elem = array + pivot_index * elem_size;
+ result = cmp_func(elem, arr_elem);
+ if (result == 0) {
+ // found it!
+ return pivot_index;
+ } else if (result < 0) {
+ // element is smaller than pivot, continue search left
+ right_index = pivot_index - 1;
+ } else {
+ // element is larger than pivot, continue search right
+ left_index = pivot_index + 1;
+ }
+ }
+
+ // report the largest upper bound
+ return result < 0 ? (pivot_index - 1) : pivot_index;
+}
+
+size_t cx_array_binary_search(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t index = cx_array_binary_search_inf(
+ arr, size, elem_size, elem, cmp_func
+ );
+ if (index < size &&
+ cmp_func(((const char *) arr) + index * elem_size, elem) == 0) {
+ return index;
+ } else {
+ return size;
+ }
+}
+
+size_t cx_array_binary_search_sup(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+) {
+ size_t inf = cx_array_binary_search_inf(
+ arr, size, elem_size, elem, cmp_func
+ );
+ if (inf == size) {
+ // no infimum means, first element is supremum
+ return 0;
+ } else if (cmp_func(((const char *) arr) + inf * elem_size, elem) == 0) {
+ return inf;
+ } else {
+ return inf + 1;
+ }
}
#ifndef CX_ARRAY_SWAP_SBO_SIZE
#define CX_ARRAY_SWAP_SBO_SIZE 128
#endif
+const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE;
void cx_array_swap(
void *arr,
// decide if we can use the local buffer
if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
- tmp = malloc(elem_size);
+ tmp = cxMallocDefault(elem_size);
// we don't want to enforce error handling
if (tmp == NULL) abort();
} else {
// free dynamic memory, if it was needed
if (tmp != sbo_mem) {
- free(tmp);
+ cxFreeDefault(tmp);
}
}
struct cx_list_s base;
void *data;
size_t capacity;
- struct cx_array_reallocator_s reallocator;
+ CxArrayReallocator reallocator;
} cx_array_list;
-static void *cx_arl_realloc(
- void *array,
- size_t capacity,
- size_t elem_size,
- struct cx_array_reallocator_s *alloc
-) {
- // retrieve the pointer to the list allocator
- CxAllocator const *al = alloc->ptr1;
-
- // use the list allocator to reallocate the memory
- return cxRealloc(al, array, capacity * elem_size);
-}
-
static void cx_arl_destructor(struct cx_list_s *list) {
cx_array_list *arl = (cx_array_list *) list;
char *ptr = arl->data;
- if (list->simple_destructor) {
- for (size_t i = 0; i < list->size; i++) {
+ if (list->collection.simple_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
cx_invoke_simple_destructor(list, ptr);
- ptr += list->item_size;
+ ptr += list->collection.elem_size;
}
}
- if (list->advanced_destructor) {
- for (size_t i = 0; i < list->size; i++) {
+ if (list->collection.advanced_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
cx_invoke_advanced_destructor(list, ptr);
- ptr += list->item_size;
+ ptr += list->collection.elem_size;
}
}
- cxFree(list->allocator, arl->data);
- cxFree(list->allocator, list);
+ cxFree(list->collection.allocator, arl->data);
+ cxFree(list->collection.allocator, list);
}
static size_t cx_arl_insert_array(
struct cx_list_s *list,
size_t index,
- void const *array,
+ const void *array,
size_t n
) {
// out of bounds and special case check
- if (index > list->size || n == 0) return 0;
+ if (index > list->collection.size || n == 0) return 0;
// get a correctly typed pointer to the list
cx_array_list *arl = (cx_array_list *) list;
- // do we need to move some elements?
- if (index < list->size) {
- char const *first_to_move = (char const *) arl->data;
- first_to_move += index * list->item_size;
- size_t elems_to_move = list->size - index;
- size_t start_of_moved = index + n;
-
- if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
- &arl->data,
- &list->size,
- &arl->capacity,
- start_of_moved,
- first_to_move,
- list->item_size,
- elems_to_move,
- &arl->reallocator
- )) {
- // if moving existing elems is unsuccessful, abort
+ // guarantee enough capacity
+ if (arl->capacity < list->collection.size + n) {
+ size_t new_capacity = list->collection.size + n;
+ new_capacity = new_capacity - (new_capacity % 16) + 16;
+ if (cxReallocateArray(
+ list->collection.allocator,
+ &arl->data, new_capacity,
+ list->collection.elem_size)
+ ) {
return 0;
}
+ arl->capacity = new_capacity;
}
- // note that if we had to move the elements, the following operation
- // is guaranteed to succeed, because we have the memory already allocated
- // therefore, it is impossible to leave this function with an invalid array
+ // determine insert position
+ char *arl_data = arl->data;
+ char *insert_pos = arl_data + index * list->collection.elem_size;
- // place the new elements
- if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
+ // do we need to move some elements?
+ if (index < list->collection.size) {
+ size_t elems_to_move = list->collection.size - index;
+ char *target = insert_pos + n * list->collection.elem_size;
+ memmove(target, insert_pos, elems_to_move * list->collection.elem_size);
+ }
+
+ // place the new elements, if any
+ if (array != NULL) {
+ memcpy(insert_pos, array, n * list->collection.elem_size);
+ }
+ list->collection.size += n;
+
+ 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->size,
+ &list->collection.size,
&arl->capacity,
- index,
- array,
- list->item_size,
+ list->collection.cmpfunc,
+ sorted_data,
+ list->collection.elem_size,
n,
&arl->reallocator
)) {
- return n;
- } else {
// array list implementation is "all or nothing"
return 0;
+ } else {
+ return n;
}
}
-static int cx_arl_insert_element(
+static void *cx_arl_insert_element(
struct cx_list_s *list,
size_t index,
- void const *element
+ const void *element
) {
- return 1 != cx_arl_insert_array(list, index, element, 1);
+ if (cx_arl_insert_array(list, index, element, 1) == 1) {
+ return ((char*)((cx_array_list *) list)->data) + index * list->collection.elem_size;
+ } else {
+ return NULL;
+ }
}
static int cx_arl_insert_iter(
- struct cx_mut_iterator_s *iter,
- void const *elem,
+ struct cx_iterator_s *iter,
+ const void *elem,
int prepend
) {
- struct cx_list_s *list = iter->src_handle;
- if (iter->index < list->size) {
- int result = cx_arl_insert_element(
- list,
- iter->index + 1 - prepend,
- elem
- );
- if (result == 0 && prepend != 0) {
+ struct cx_list_s *list = iter->src_handle.m;
+ if (iter->index < list->collection.size) {
+ if (cx_arl_insert_element(list,
+ iter->index + 1 - prepend, elem) == NULL) {
+ return 1;
+ }
+ iter->elem_count++;
+ if (prepend != 0) {
iter->index++;
- iter->elem_handle = ((char *) iter->elem_handle) + list->item_size;
+ iter->elem_handle = ((char *) iter->elem_handle) + list->collection.elem_size;
}
- return result;
+ return 0;
} else {
- int result = cx_arl_insert_element(list, list->size, elem);
- iter->index = list->size;
- return result;
+ if (cx_arl_insert_element(list, list->collection.size, elem) == NULL) {
+ return 1;
+ }
+ iter->elem_count++;
+ iter->index = list->collection.size;
+ return 0;
}
}
-static int cx_arl_remove(
+static size_t cx_arl_remove(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
cx_array_list *arl = (cx_array_list *) list;
// out-of-bounds check
- if (index >= list->size) {
- return 1;
+ size_t remove;
+ if (index >= list->collection.size) {
+ remove = 0;
+ } else if (index + num > list->collection.size) {
+ remove = list->collection.size - index;
+ } else {
+ remove = num;
}
- // content destruction
- cx_invoke_destructor(list, ((char *) arl->data) + index * list->item_size);
+ // easy exit
+ if (remove == 0) return 0;
- // short-circuit removal of last element
- if (index == list->size - 1) {
- list->size--;
- return 0;
+ // destroy or copy contents
+ if (targetbuf == NULL) {
+ for (size_t idx = index; idx < index + remove; idx++) {
+ cx_invoke_destructor(
+ list,
+ ((char *) arl->data) + idx * list->collection.elem_size
+ );
+ }
+ } else {
+ memcpy(
+ targetbuf,
+ ((char *) arl->data) + index * list->collection.elem_size,
+ remove * list->collection.elem_size
+ );
}
- // just move the elements starting at index to the left
- int result = cx_array_copy(
+ // short-circuit removal of last elements
+ if (index + remove == list->collection.size) {
+ list->collection.size -= remove;
+ return remove;
+ }
+
+ // just move the elements to the left
+ cx_array_copy(
&arl->data,
- &list->size,
+ &list->collection.size,
&arl->capacity,
+ 0,
index,
- ((char *) arl->data) + (index + 1) * list->item_size,
- list->item_size,
- list->size - index - 1,
+ ((char *) arl->data) + (index + remove) * list->collection.elem_size,
+ list->collection.elem_size,
+ list->collection.size - index - remove,
&arl->reallocator
);
- if (result == 0) {
- // decrease the size
- list->size--;
- }
- return result;
+
+ // decrease the size
+ list->collection.size -= remove;
+
+ return remove;
}
static void cx_arl_clear(struct cx_list_s *list) {
- if (list->size == 0) return;
+ if (list->collection.size == 0) return;
cx_array_list *arl = (cx_array_list *) list;
char *ptr = arl->data;
- if (list->simple_destructor) {
- for (size_t i = 0; i < list->size; i++) {
+ if (list->collection.simple_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
cx_invoke_simple_destructor(list, ptr);
- ptr += list->item_size;
+ ptr += list->collection.elem_size;
}
}
- if (list->advanced_destructor) {
- for (size_t i = 0; i < list->size; i++) {
+ if (list->collection.advanced_destructor) {
+ for (size_t i = 0; i < list->collection.size; i++) {
cx_invoke_advanced_destructor(list, ptr);
- ptr += list->item_size;
+ ptr += list->collection.elem_size;
}
}
- memset(arl->data, 0, list->size * list->item_size);
- list->size = 0;
+ memset(arl->data, 0, list->collection.size * list->collection.elem_size);
+ list->collection.size = 0;
}
static int cx_arl_swap(
size_t i,
size_t j
) {
- if (i >= list->size || j >= list->size) return 1;
+ if (i >= list->collection.size || j >= list->collection.size) return 1;
cx_array_list *arl = (cx_array_list *) list;
- cx_array_swap(arl->data, list->item_size, i, j);
+ cx_array_swap(arl->data, list->collection.elem_size, i, j);
return 0;
}
static void *cx_arl_at(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index
) {
- if (index < list->size) {
- cx_array_list const *arl = (cx_array_list const *) list;
+ if (index < list->collection.size) {
+ const cx_array_list *arl = (const cx_array_list *) list;
char *space = arl->data;
- return space + index * list->item_size;
+ return space + index * list->collection.elem_size;
} else {
return NULL;
}
}
-static ssize_t cx_arl_find(
- struct cx_list_s const *list,
- void const *elem
+static size_t cx_arl_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
) {
- assert(list->cmpfunc != NULL);
- assert(list->size < SIZE_MAX / 2);
- char *cur = ((cx_array_list const *) list)->data;
+ assert(list != NULL);
+ assert(list->collection.cmpfunc != NULL);
+ if (list->collection.size == 0) return 0;
+ char *cur = ((const cx_array_list *) list)->data;
+
+ // optimize with binary search, when sorted
+ if (list->collection.sorted) {
+ size_t i = cx_array_binary_search(
+ cur,
+ list->collection.size,
+ list->collection.elem_size,
+ elem,
+ list->collection.cmpfunc
+ );
+ if (remove && i < list->collection.size) {
+ cx_arl_remove(list, i, 1, NULL);
+ }
+ return i;
+ }
- for (ssize_t i = 0; i < (ssize_t) list->size; i++) {
- if (0 == list->cmpfunc(elem, cur)) {
+ // fallback: linear search
+ for (size_t i = 0; i < list->collection.size; i++) {
+ if (0 == list->collection.cmpfunc(elem, cur)) {
+ if (remove) {
+ cx_arl_remove(list, i, 1, NULL);
+ }
return i;
}
- cur += list->item_size;
+ cur += list->collection.elem_size;
}
-
- return -1;
+ return list->collection.size;
}
static void cx_arl_sort(struct cx_list_s *list) {
- assert(list->cmpfunc != NULL);
+ assert(list->collection.cmpfunc != NULL);
qsort(((cx_array_list *) list)->data,
- list->size,
- list->item_size,
- list->cmpfunc
+ list->collection.size,
+ list->collection.elem_size,
+ list->collection.cmpfunc
);
}
static int cx_arl_compare(
- struct cx_list_s const *list,
- struct cx_list_s const *other
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
) {
- assert(list->cmpfunc != NULL);
- if (list->size == other->size) {
- char const *left = ((cx_array_list const *) list)->data;
- char const *right = ((cx_array_list const *) other)->data;
- for (size_t i = 0; i < list->size; i++) {
- int d = list->cmpfunc(left, right);
+ assert(list->collection.cmpfunc != NULL);
+ if (list->collection.size == other->collection.size) {
+ const char *left = ((const cx_array_list *) list)->data;
+ const char *right = ((const cx_array_list *) other)->data;
+ for (size_t i = 0; i < list->collection.size; i++) {
+ int d = list->collection.cmpfunc(left, right);
if (d != 0) {
return d;
}
- left += list->item_size;
- right += other->item_size;
+ left += list->collection.elem_size;
+ right += other->collection.elem_size;
}
return 0;
} else {
- return list->size < other->size ? -1 : 1;
+ return list->collection.size < other->collection.size ? -1 : 1;
}
}
static void cx_arl_reverse(struct cx_list_s *list) {
- if (list->size < 2) return;
- void *data = ((cx_array_list const *) list)->data;
- size_t half = list->size / 2;
+ if (list->collection.size < 2) return;
+ void *data = ((const cx_array_list *) list)->data;
+ size_t half = list->collection.size / 2;
for (size_t i = 0; i < half; i++) {
- cx_array_swap(data, list->item_size, i, list->size - 1 - i);
+ cx_array_swap(data, list->collection.elem_size, i, list->collection.size - 1 - i);
}
}
-static bool cx_arl_iter_valid(void const *it) {
- struct cx_iterator_s const *iter = it;
- struct cx_list_s const *list = iter->src_handle;
- return iter->index < list->size;
+static bool cx_arl_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ const struct cx_list_s *list = iter->src_handle.c;
+ return iter->index < list->collection.size;
}
-static void *cx_arl_iter_current(void const *it) {
- struct cx_iterator_s const *iter = it;
+static void *cx_arl_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
return iter->elem_handle;
}
static void cx_arl_iter_next(void *it) {
- struct cx_iterator_base_s *itbase = it;
- if (itbase->remove) {
- struct cx_mut_iterator_s *iter = it;
- itbase->remove = false;
- cx_arl_remove(iter->src_handle, iter->index);
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
} else {
- struct cx_iterator_s *iter = it;
iter->index++;
iter->elem_handle =
((char *) iter->elem_handle)
- + ((struct cx_list_s const *) iter->src_handle)->item_size;
+ + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size;
}
}
static void cx_arl_iter_prev(void *it) {
- struct cx_iterator_base_s *itbase = it;
- struct cx_mut_iterator_s *iter = it;
- cx_array_list *const list = iter->src_handle;
- if (itbase->remove) {
- itbase->remove = false;
- cx_arl_remove(iter->src_handle, iter->index);
+ struct cx_iterator_s *iter = it;
+ const cx_array_list *list = iter->src_handle.c;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL);
}
iter->index--;
- if (iter->index < list->base.size) {
+ if (iter->index < list->base.collection.size) {
iter->elem_handle = ((char *) list->data)
- + iter->index * list->base.item_size;
+ + iter->index * list->base.collection.elem_size;
}
}
-static bool cx_arl_iter_flag_rm(void *it) {
- struct cx_iterator_base_s *iter = it;
- if (iter->mutating) {
- iter->remove = true;
- return true;
- } else {
- return false;
- }
-}
static struct cx_iterator_s cx_arl_iterator(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index,
bool backwards
) {
struct cx_iterator_s iter;
iter.index = index;
- iter.src_handle = list;
+ iter.src_handle.c = list;
iter.elem_handle = cx_arl_at(list, index);
+ iter.elem_size = list->collection.elem_size;
+ iter.elem_count = list->collection.size;
iter.base.valid = cx_arl_iter_valid;
iter.base.current = cx_arl_iter_current;
iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
- iter.base.flag_removal = cx_arl_iter_flag_rm;
iter.base.remove = false;
iter.base.mutating = false;
cx_arl_destructor,
cx_arl_insert_element,
cx_arl_insert_array,
+ cx_arl_insert_sorted,
cx_arl_insert_iter,
cx_arl_remove,
cx_arl_clear,
cx_arl_swap,
cx_arl_at,
- cx_arl_find,
+ cx_arl_find_remove,
cx_arl_sort,
cx_arl_compare,
cx_arl_reverse,
};
CxList *cxArrayListCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cx_compare_func comparator,
- size_t item_size,
+ size_t elem_size,
size_t initial_capacity
) {
if (allocator == NULL) {
cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
if (list == NULL) return NULL;
-
- list->base.cl = &cx_array_list_class;
- list->base.allocator = allocator;
- list->base.cmpfunc = comparator;
+ cx_list_init((CxList*)list, &cx_array_list_class,
+ allocator, comparator, elem_size);
list->capacity = initial_capacity;
- if (item_size > 0) {
- list->base.item_size = item_size;
- } else {
- item_size = sizeof(void *);
- cxListStorePointers((CxList *) list);
- }
-
- // allocate the array after the real item_size is known
- list->data = cxCalloc(allocator, initial_capacity, item_size);
- if (list->data == NULL) {
+ // allocate the array after the real elem_size is known
+ list->data = cxCalloc(allocator, initial_capacity,
+ list->base.collection.elem_size);
+ if (list->data == NULL) { // LCOV_EXCL_START
cxFree(allocator, list);
return NULL;
- }
+ } // LCOV_EXCL_STOP
// configure the reallocator
- list->reallocator.realloc = cx_arl_realloc;
- list->reallocator.ptr1 = (void *) allocator;
+ list->reallocator = cx_array_reallocator(allocator, NULL);
return (CxList *) list;
}
+++ /dev/null
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "cx/basic_mempool.h"
-#include "cx/utils.h"
-#include <string.h>
-
-#define of_chk_(n) if (SIZE_MAX - sizeof(cx_destructor_func) < (n)) return NULL
-
-/** Internal structure for denoting pooled memory. */
-typedef struct {
- /** The destructor. */
- cx_destructor_func destructor;
- /**
- * Access to the first byte of the polled memory.
- */
- char c;
-} cx_basic_mempool_memory;
-
-static int cx_basic_mempool_chcap(
- struct cx_basic_mempool_s *pool,
- size_t newcap
-) {
- if (newcap < pool->ndata) {
- return 1;
- }
-
- size_t newcapsz;
- if (cx_szmul(newcap, sizeof(void *), &newcapsz)) {
- return 1;
- }
-
- void **data = realloc(pool->data, newcapsz);
- if (data) {
- pool->data = data;
- pool->size = newcap;
- return 0;
- } else {
- return 1;
- }
-}
-
-void *cx_malloc_basic_mempool(
- void *data,
- size_t n
-) {
- of_chk_(n);
- struct cx_basic_mempool_s *pool = data;
-
- if (pool->ndata >= pool->size) {
- size_t newcap = pool->size * 2;
- if (newcap < pool->size || cx_basic_mempool_chcap(pool, newcap)) {
- return NULL;
- }
- }
-
- cx_basic_mempool_memory *mem = malloc(sizeof(cx_destructor_func) + n);
- if (mem == NULL) {
- return NULL;
- }
-
- mem->destructor = NULL;
- pool->data[pool->ndata] = mem;
- pool->ndata++;
-
- return &(mem->c);
-}
-
-void *cx_calloc_basic_mempool(
- void *data,
- size_t nelem,
- size_t elsize
-) {
- size_t msz;
- if (cx_szmul(nelem, elsize, &msz)) {
- return NULL;
- }
- void *ptr = cx_malloc_basic_mempool(data, msz);
- if (ptr == NULL) {
- return NULL;
- }
- memset(ptr, 0, nelem * elsize);
- return ptr;
-}
-
-void *cx_realloc_basic_mempool(
- void *data,
- void *ptr,
- size_t n
-) {
- of_chk_(n);
- struct cx_basic_mempool_s *pool = data;
-
- char *mem = ((char *) ptr) - sizeof(cx_destructor_func);
- char *newm = (char *) realloc(mem, n + sizeof(cx_destructor_func));
- if (newm == NULL) {
- return NULL;
- }
- if (mem != newm) {
- cx_for_n(i, pool->ndata) {
- if (pool->data[i] == mem) {
- pool->data[i] = newm;
- return newm + sizeof(cx_destructor_func);
- }
- }
- abort();
- } else {
- return newm + sizeof(cx_destructor_func);
- }
-}
-
-void cx_free_basic_mempool(
- void *data,
- void *ptr
-) {
- struct cx_basic_mempool_s *pool = data;
-
- cx_basic_mempool_memory *mem = (cx_basic_mempool_memory *)
- ((char *) ptr - sizeof(cx_destructor_func));
- cx_for_n(i, pool->ndata) {
- if (mem == pool->data[i]) {
- if (mem->destructor != NULL) {
- mem->destructor(&(mem->c));
- }
- free(mem);
- size_t last_index = pool->ndata - 1;
- if (i != last_index) {
- pool->data[i] = pool->data[last_index];
- pool->data[last_index] = NULL;
- }
- pool->ndata--;
- return;
- }
- }
- abort();
-}
-
-void cx_basic_mempool_destroy(CxMempool *p) {
- struct cx_basic_mempool_s *pool = (struct cx_basic_mempool_s *) p;
- cx_basic_mempool_memory *mem;
- cx_for_n(i, pool->ndata) {
- mem = (cx_basic_mempool_memory *) pool->data[i];
- if (mem) {
- if (mem->destructor) {
- mem->destructor(&(mem->c));
- }
- free(mem);
- }
- }
- free(pool->data);
- free((void *) p->allocator);
- free(pool);
-}
-
-void cx_basic_mempool_set_destr(
- __attribute__((__unused__)) CxMempool *pool,
- void *ptr,
- cx_destructor_func func
-) {
- *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
-}
-
-static cx_allocator_class cx_basic_mempool_allocator_class = {
- cx_malloc_basic_mempool,
- cx_realloc_basic_mempool,
- cx_calloc_basic_mempool,
- cx_free_basic_mempool
-};
-
-static cx_mempool_class cx_basic_mempool_class = {
- cx_basic_mempool_destroy,
- cx_basic_mempool_set_destr,
-};
-
-CxMempool *cxBasicMempoolCreate(size_t capacity) {
- size_t poolsize;
- if (cx_szmul(capacity, sizeof(void *), &poolsize)) {
- return NULL;
- }
-
- struct cx_basic_mempool_s *pool =
- malloc(sizeof(struct cx_basic_mempool_s));
- if (pool == NULL) {
- return NULL;
- }
-
-
- CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
- if (!provided_allocator) {
- free(pool);
- return NULL;
- }
- provided_allocator->cl = &cx_basic_mempool_allocator_class;
- provided_allocator->data = pool;
-
- pool->base.cl = &cx_basic_mempool_class;
- pool->base.allocator = provided_allocator;
-
- pool->data = malloc(poolsize);
- if (pool->data == NULL) {
- free(provided_allocator);
- free(pool);
- return NULL;
- }
-
- pool->ndata = 0;
- pool->size = capacity;
-
- return (CxMempool *) pool;
-}
*/
#include "cx/buffer.h"
-#include "cx/utils.h"
#include <stdio.h>
#include <string.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#include <Windows.h>
+#include <sysinfoapi.h>
+static unsigned long system_page_size() {
+ static unsigned long ps = 0;
+ if (ps == 0) {
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+ ps = sysinfo.dwPageSize;
+ }
+ return ps;
+}
+#define SYSTEM_PAGE_SIZE system_page_size()
+#else
+#include <unistd.h>
+#define SYSTEM_PAGE_SIZE sysconf(_SC_PAGESIZE)
+#endif
+
+static int buffer_copy_on_write(CxBuffer* buffer) {
+ if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) return 0;
+ void *newspace = cxMalloc(buffer->allocator, buffer->capacity);
+ if (NULL == newspace) return -1;
+ memcpy(newspace, buffer->space, buffer->size);
+ buffer->space = newspace;
+ buffer->flags &= ~CX_BUFFER_COPY_ON_WRITE;
+ buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+ return 0;
+}
int cxBufferInit(
CxBuffer *buffer,
void *space,
size_t capacity,
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
int flags
) {
- if (allocator == NULL) allocator = cxDefaultAllocator;
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ if (flags & CX_BUFFER_COPY_ON_EXTEND) {
+ flags |= CX_BUFFER_AUTO_EXTEND;
+ }
buffer->allocator = allocator;
buffer->flags = flags;
if (!space) {
buffer->bytes = cxMalloc(allocator, capacity);
if (buffer->bytes == NULL) {
- return 1;
+ return -1; // LCOV_EXCL_LINE
}
buffer->flags |= CX_BUFFER_FREE_CONTENTS;
} else {
buffer->size = 0;
buffer->pos = 0;
- buffer->flush_func = NULL;
- buffer->flush_target = NULL;
- buffer->flush_blkmax = 0;
- buffer->flush_blksize = 4096;
- buffer->flush_threshold = SIZE_MAX;
+ buffer->flush = NULL;
+
+ return 0;
+}
+int cxBufferEnableFlushing(
+ CxBuffer *buffer,
+ CxBufferFlushConfig config
+) {
+ buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig));
+ if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE
+ memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig));
return 0;
}
void cxBufferDestroy(CxBuffer *buffer) {
- if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+ if (buffer->flags & CX_BUFFER_FREE_CONTENTS) {
cxFree(buffer->allocator, buffer->bytes);
}
+ cxFreeDefault(buffer->flush);
+ memset(buffer, 0, sizeof(CxBuffer));
}
CxBuffer *cxBufferCreate(
void *space,
size_t capacity,
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
int flags
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
if (buf == NULL) return NULL;
if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
return buf;
} else {
+ // LCOV_EXCL_START
cxFree(allocator, buf);
return NULL;
+ // LCOV_EXCL_STOP
}
}
void cxBufferFree(CxBuffer *buffer) {
- if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
- cxFree(buffer->allocator, buffer->bytes);
- }
- cxFree(buffer->allocator, buffer);
+ if (buffer == NULL) return;
+ const CxAllocator *allocator = buffer->allocator;
+ cxBufferDestroy(buffer);
+ cxFree(allocator, buffer);
}
int cxBufferSeek(
npos = 0;
break;
default:
+ errno = EINVAL;
return -1;
}
npos += offset;
if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+ // to be compliant with fseek() specification
+ // we return EINVAL on underflow
+ errno = EINVAL;
return -1;
}
- if (npos >= buffer->size) {
+ if (npos > buffer->size) {
+ // not compliant with fseek() specification
+ // but this is the better behavior for CxBuffer
+ errno = EINVAL;
return -1;
} else {
buffer->pos = npos;
}
void cxBufferClear(CxBuffer *buffer) {
- memset(buffer->bytes, 0, buffer->size);
+ if (0 == (buffer->flags & CX_BUFFER_COPY_ON_WRITE)) {
+ memset(buffer->bytes, 0, buffer->size);
+ }
+ buffer->size = 0;
+ buffer->pos = 0;
+}
+
+void cxBufferReset(CxBuffer *buffer) {
buffer->size = 0;
buffer->pos = 0;
}
-int cxBufferEof(CxBuffer const *buffer) {
+bool cxBufferEof(const CxBuffer *buffer) {
return buffer->pos >= buffer->size;
}
return 0;
}
- if (cxReallocate(buffer->allocator,
+ unsigned long pagesize = SYSTEM_PAGE_SIZE;
+ // if page size is larger than 64 KB - for some reason - truncate to 64 KB
+ if (pagesize > 65536) pagesize = 65536;
+ if (newcap < pagesize) {
+ // when smaller as one page, map to the next power of two
+ newcap--;
+ newcap |= newcap >> 1;
+ newcap |= newcap >> 2;
+ newcap |= newcap >> 4;
+ // last operation only needed for pages larger 4096 bytes
+ // but if/else would be more expensive than just doing this
+ newcap |= newcap >> 8;
+ newcap++;
+ } else {
+ // otherwise, map to a multiple of the page size
+ newcap -= newcap % pagesize;
+ newcap += pagesize;
+ // note: if newcap is already page aligned,
+ // this gives a full additional page (which is good)
+ }
+
+
+ const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
+ if (buffer->flags & force_copy_flags) {
+ void *newspace = cxMalloc(buffer->allocator, newcap);
+ if (NULL == newspace) return -1;
+ memcpy(newspace, buffer->space, buffer->size);
+ buffer->space = newspace;
+ buffer->capacity = newcap;
+ buffer->flags &= ~force_copy_flags;
+ buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+ return 0;
+ } else if (cxReallocate(buffer->allocator,
(void **) &buffer->bytes, newcap) == 0) {
buffer->capacity = newcap;
return 0;
} else {
- return -1;
+ return -1; // LCOV_EXCL_LINE
}
}
-/**
- * Helps flushing data to the flush target of a buffer.
- *
- * @param buffer the buffer containing the config
- * @param space the data to flush
- * @param size the element size
- * @param nitems the number of items
- * @return the number of items flushed
- */
-static size_t cx_buffer_write_flush_helper(
+void cxBufferShrink(
CxBuffer *buffer,
- unsigned char const *space,
+ size_t reserve
+) {
+ // Ensure buffer is in a reallocatable state
+ const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
+ if (buffer->flags & force_copy_flags) {
+ // do nothing when we are not allowed to reallocate
+ return;
+ }
+
+ // calculate new capacity
+ size_t newCapacity = buffer->size + reserve;
+
+ // If new capacity is smaller than current capacity, resize the buffer
+ if (newCapacity < buffer->capacity) {
+ if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) {
+ buffer->capacity = newCapacity;
+ }
+ }
+}
+
+static size_t cx_buffer_flush_helper(
+ const CxBuffer *buffer,
+ const unsigned char *src,
size_t size,
size_t nitems
) {
- size_t pos = 0;
- size_t remaining = nitems;
- size_t max_items = buffer->flush_blksize / size;
- while (remaining > 0) {
- size_t items = remaining > max_items ? max_items : remaining;
- size_t flushed = buffer->flush_func(
- space + pos,
- size, items,
- buffer->flush_target);
+ // flush data from an arbitrary source
+ // does not need to be the buffer's contents
+ size_t max_items = buffer->flush->blksize / size;
+ size_t fblocks = 0;
+ size_t flushed_total = 0;
+ while (nitems > 0 && fblocks < buffer->flush->blkmax) {
+ fblocks++;
+ size_t items = nitems > max_items ? max_items : nitems;
+ size_t flushed = buffer->flush->wfunc(
+ src, size, items, buffer->flush->target);
if (flushed > 0) {
- pos += (flushed * size);
- remaining -= flushed;
+ flushed_total += flushed;
+ src += flushed * size;
+ nitems -= flushed;
} else {
// if no bytes can be flushed out anymore, we give up
break;
}
}
- return nitems - remaining;
+ return flushed_total;
+}
+
+static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) {
+ // flush the current contents of the buffer
+ unsigned char *space = buffer->bytes;
+ size_t remaining = buffer->pos / size;
+ size_t flushed_total = cx_buffer_flush_helper(
+ buffer, space, size, remaining);
+
+ // shift the buffer left after flushing
+ // IMPORTANT: up to this point, copy on write must have been
+ // performed already, because we can't do error handling here
+ cxBufferShiftLeft(buffer, flushed_total*size);
+
+ return flushed_total;
+}
+
+size_t cxBufferFlush(CxBuffer *buffer) {
+ if (buffer_copy_on_write(buffer)) return 0;
+ return cx_buffer_flush_impl(buffer, 1);
}
size_t cxBufferWrite(
- void const *ptr,
+ const void *ptr,
size_t size,
size_t nitems,
CxBuffer *buffer
) {
// optimize for easy case
if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+ if (buffer_copy_on_write(buffer)) return 0;
memcpy(buffer->bytes + buffer->pos, ptr, nitems);
buffer->pos += nitems;
if (buffer->pos > buffer->size) {
return nitems;
}
- size_t len;
- size_t nitems_out = nitems;
+ size_t len, total_flushed = 0;
+cx_buffer_write_retry:
if (cx_szmul(size, nitems, &len)) {
- return 0;
+ errno = EOVERFLOW;
+ return total_flushed;
}
- size_t required = buffer->pos + len;
- if (buffer->pos > required) {
- return 0;
+ if (buffer->pos > SIZE_MAX - len) {
+ errno = EOVERFLOW;
+ return total_flushed;
}
+ size_t required = buffer->pos + len;
bool perform_flush = false;
if (required > buffer->capacity) {
- if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND && required) {
- if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+ if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
+ if (buffer->flush != NULL && required > buffer->flush->threshold) {
perform_flush = true;
} else {
if (cxBufferMinimumCapacity(buffer, required)) {
- return 0;
+ return total_flushed; // LCOV_EXCL_LINE
}
}
} else {
- if (buffer->flush_blkmax > 0) {
+ if (buffer->flush != NULL) {
perform_flush = true;
} else {
- // truncate data to be written, if we can neither extend nor flush
+ // truncate data, if we can neither extend nor flush
len = buffer->capacity - buffer->pos;
if (size > 1) {
len -= len % size;
}
- nitems_out = len / size;
+ nitems = len / size;
}
}
}
+ // check here and not above because of possible truncation
if (len == 0) {
- return len;
+ return total_flushed;
}
+ // check if we need to copy
+ if (buffer_copy_on_write(buffer)) return 0;
+
+ // perform the operation
if (perform_flush) {
- size_t flush_max;
- if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
- return 0;
- }
- size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
- ? buffer->pos
- : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
- if (flush_pos == buffer->pos) {
- // entire buffer has been flushed, we can reset
- buffer->size = buffer->pos = 0;
-
- size_t items_flush; // how many items can also be directly flushed
- size_t items_keep; // how many items have to be written to the buffer
-
- items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
- if (items_flush > 0) {
- items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
- // in case we could not flush everything, keep the rest
+ size_t items_flushed;
+ if (buffer->pos == 0) {
+ // if we don't have data in the buffer, but are instructed
+ // to flush, it means that we are supposed to relay the data
+ items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems);
+ if (items_flushed == 0) {
+ // we needed to relay data, but could not flush anything
+ // i.e. we have to give up to avoid endless trying
+ return 0;
}
- items_keep = nitems - items_flush;
- if (items_keep > 0) {
- // try again with the remaining stuff
- unsigned char const *new_ptr = ptr;
- new_ptr += items_flush * size;
- // report the directly flushed items as written plus the remaining stuff
- return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
- } else {
- // all items have been flushed - report them as written
- return nitems;
+ nitems -= items_flushed;
+ total_flushed += items_flushed;
+ if (nitems > 0) {
+ ptr = ((unsigned char*)ptr) + items_flushed * size;
+ goto cx_buffer_write_retry;
}
- } else if (flush_pos == 0) {
- // nothing could be flushed at all, we immediately give up without writing any data
- return 0;
+ return total_flushed;
} else {
- // we were partially successful, we shift left and try again
- cxBufferShiftLeft(buffer, flush_pos);
- return cxBufferWrite(ptr, size, nitems, buffer);
+ items_flushed = cx_buffer_flush_impl(buffer, size);
+ if (items_flushed == 0) {
+ // flush target is full, let's try to truncate
+ size_t remaining_space;
+ if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
+ remaining_space = buffer->flush->threshold > buffer->pos
+ ? buffer->flush->threshold - buffer->pos
+ : 0;
+ } else {
+ remaining_space = buffer->capacity > buffer->pos
+ ? buffer->capacity - buffer->pos
+ : 0;
+ }
+ nitems = remaining_space / size;
+ if (nitems == 0) {
+ return total_flushed;
+ }
+ }
+ goto cx_buffer_write_retry;
}
} else {
memcpy(buffer->bytes + buffer->pos, ptr, len);
if (buffer->pos > buffer->size) {
buffer->size = buffer->pos;
}
- return nitems_out;
+ return total_flushed + nitems;
}
+}
+size_t cxBufferAppend(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+) {
+ size_t pos = buffer->pos;
+ size_t append_pos = buffer->size;
+ buffer->pos = append_pos;
+ size_t written = cxBufferWrite(ptr, size, nitems, buffer);
+ // the buffer might have been flushed
+ // we must compute a possible delta for the position
+ // expected: pos = append_pos + written
+ // -> if this is not the case, there is a delta
+ size_t delta = append_pos + written*size - buffer->pos;
+ if (delta > pos) {
+ buffer->pos = 0;
+ } else {
+ buffer->pos = pos - delta;
+ }
+ return written;
}
int cxBufferPut(
}
}
+int cxBufferTerminate(CxBuffer *buffer) {
+ if (0 == cxBufferPut(buffer, 0)) {
+ buffer->size = buffer->pos - 1;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
size_t cxBufferPutString(
CxBuffer *buffer,
const char *str
) {
size_t len;
if (cx_szmul(size, nitems, &len)) {
+ errno = EOVERFLOW;
return 0;
}
if (buffer->pos + len > buffer->size) {
if (shift >= buffer->size) {
buffer->pos = buffer->size = 0;
} else {
+ if (buffer_copy_on_write(buffer)) return -1;
memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
buffer->size -= shift;
CxBuffer *buffer,
size_t shift
) {
+ if (buffer->size > SIZE_MAX - shift) {
+ errno = EOVERFLOW;
+ return -1;
+ }
size_t req_capacity = buffer->size + shift;
size_t movebytes;
// auto extend buffer, if required and enabled
if (buffer->capacity < req_capacity) {
- if ((buffer->flags & CX_BUFFER_AUTO_EXTEND) == CX_BUFFER_AUTO_EXTEND) {
+ if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
if (cxBufferMinimumCapacity(buffer, req_capacity)) {
- return 1;
+ return -1; // LCOV_EXCL_LINE
}
movebytes = buffer->size;
} else {
movebytes = buffer->size;
}
- memmove(buffer->bytes + shift, buffer->bytes, movebytes);
- buffer->size = shift + movebytes;
+ if (movebytes > 0) {
+ if (buffer_copy_on_write(buffer)) return -1;
+ memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+ buffer->size = shift + movebytes;
+ }
buffer->pos += shift;
if (buffer->pos > buffer->size) {
#include <math.h>
-int cx_cmp_int(void const *i1, void const *i2) {
+int cx_vcmp_int(int a, int b) {
+ if (a == b) {
+ return 0;
+ } else {
+ return a < b ? -1 : 1;
+ }
+}
+
+int cx_cmp_int(const void *i1, const void *i2) {
int a = *((const int *) i1);
int b = *((const int *) i2);
+ return cx_vcmp_int(a, b);
+}
+
+int cx_vcmp_longint(long int a, long int b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_longint(void const *i1, void const *i2) {
+int cx_cmp_longint(const void *i1, const void *i2) {
long int a = *((const long int *) i1);
long int b = *((const long int *) i2);
+ return cx_vcmp_longint(a, b);
+}
+
+int cx_vcmp_longlong(long long a, long long b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_longlong(void const *i1, void const *i2) {
+int cx_cmp_longlong(const void *i1, const void *i2) {
long long a = *((const long long *) i1);
long long b = *((const long long *) i2);
+ return cx_vcmp_longlong(a, b);
+}
+
+int cx_vcmp_int16(int16_t a, int16_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_int16(void const *i1, void const *i2) {
+int cx_cmp_int16(const void *i1, const void *i2) {
int16_t a = *((const int16_t *) i1);
int16_t b = *((const int16_t *) i2);
+ return cx_vcmp_int16(a, b);
+}
+
+int cx_vcmp_int32(int32_t a, int32_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_int32(void const *i1, void const *i2) {
+int cx_cmp_int32(const void *i1, const void *i2) {
int32_t a = *((const int32_t *) i1);
int32_t b = *((const int32_t *) i2);
+ return cx_vcmp_int32(a, b);
+}
+
+int cx_vcmp_int64(int64_t a, int64_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_int64(void const *i1, void const *i2) {
+int cx_cmp_int64(const void *i1, const void *i2) {
int64_t a = *((const int64_t *) i1);
int64_t b = *((const int64_t *) i2);
+ return cx_vcmp_int64(a, b);
+}
+
+int cx_vcmp_uint(unsigned int a, unsigned int b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_uint(void const *i1, void const *i2) {
+int cx_cmp_uint(const void *i1, const void *i2) {
unsigned int a = *((const unsigned int *) i1);
unsigned int b = *((const unsigned int *) i2);
+ return cx_vcmp_uint(a, b);
+}
+
+int cx_vcmp_ulongint(unsigned long int a, unsigned long int b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_ulongint(void const *i1, void const *i2) {
+int cx_cmp_ulongint(const void *i1, const void *i2) {
unsigned long int a = *((const unsigned long int *) i1);
unsigned long int b = *((const unsigned long int *) i2);
+ return cx_vcmp_ulongint(a, b);
+}
+
+int cx_vcmp_ulonglong(unsigned long long a, unsigned long long b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_ulonglong(void const *i1, void const *i2) {
+int cx_cmp_ulonglong(const void *i1, const void *i2) {
unsigned long long a = *((const unsigned long long *) i1);
unsigned long long b = *((const unsigned long long *) i2);
+ return cx_vcmp_ulonglong(a, b);
+}
+
+int cx_vcmp_uint16(uint16_t a, uint16_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_uint16(void const *i1, void const *i2) {
+int cx_cmp_uint16(const void *i1, const void *i2) {
uint16_t a = *((const uint16_t *) i1);
uint16_t b = *((const uint16_t *) i2);
+ return cx_vcmp_uint16(a, b);
+}
+
+int cx_vcmp_uint32(uint32_t a, uint32_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_uint32(void const *i1, void const *i2) {
+int cx_cmp_uint32(const void *i1, const void *i2) {
uint32_t a = *((const uint32_t *) i1);
uint32_t b = *((const uint32_t *) i2);
+ return cx_vcmp_uint32(a, b);
+}
+
+int cx_vcmp_uint64(uint64_t a, uint64_t b) {
if (a == b) {
return 0;
} else {
}
}
-int cx_cmp_uint64(void const *i1, void const *i2) {
+int cx_cmp_uint64(const void *i1, const void *i2) {
uint64_t a = *((const uint64_t *) i1);
uint64_t b = *((const uint64_t *) i2);
- if (a == b) {
+ return cx_vcmp_uint64(a, b);
+}
+
+int cx_vcmp_float(float a, float b) {
+ if (fabsf(a - b) < 1e-6f) {
return 0;
} else {
return a < b ? -1 : 1;
}
}
-int cx_cmp_float(void const *f1, void const *f2) {
+int cx_cmp_float(const void *f1, const void *f2) {
float a = *((const float *) f1);
float b = *((const float *) f2);
- if (fabsf(a - b) < 1e-6f) {
+ return cx_vcmp_float(a, b);
+}
+
+int cx_vcmp_double(double a, double b) {
+ if (fabs(a - b) < 1e-14) {
return 0;
} else {
return a < b ? -1 : 1;
}
int cx_cmp_double(
- void const *d1,
- void const *d2
+ const void *d1,
+ const void *d2
) {
double a = *((const double *) d1);
double b = *((const double *) d2);
- if (fabs(a - b) < 1e-14) {
+ return cx_vcmp_double(a, b);
+}
+
+int cx_vcmp_intptr(intptr_t p1, intptr_t p2) {
+ if (p1 == p2) {
return 0;
} else {
- return a < b ? -1 : 1;
+ return p1 < p2 ? -1 : 1;
}
}
int cx_cmp_intptr(
- void const *ptr1,
- void const *ptr2
+ const void *ptr1,
+ const void *ptr2
) {
intptr_t p1 = *(const intptr_t *) ptr1;
intptr_t p2 = *(const intptr_t *) ptr2;
+ return cx_vcmp_intptr(p1, p2);
+}
+
+int cx_vcmp_uintptr(uintptr_t p1, uintptr_t p2) {
if (p1 == p2) {
return 0;
} else {
}
int cx_cmp_uintptr(
- void const *ptr1,
- void const *ptr2
+ const void *ptr1,
+ const void *ptr2
) {
uintptr_t p1 = *(const uintptr_t *) ptr1;
uintptr_t p2 = *(const uintptr_t *) ptr2;
+ return cx_vcmp_uintptr(p1, p2);
+}
+
+int cx_cmp_ptr(
+ const void *ptr1,
+ const void *ptr2
+) {
+ uintptr_t p1 = (uintptr_t) ptr1;
+ uintptr_t p2 = (uintptr_t) ptr2;
if (p1 == p2) {
return 0;
} else {
return p1 < p2 ? -1 : 1;
}
}
-
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file allocator.h
+ * @file allocator.h
* Interface for custom allocators.
*/
void *data,
void *mem,
size_t n
- )
- __attribute__((__warn_unused_result__));
+ );
/**
* The allocator's calloc() implementation.
*/
void *(*calloc)(
void *data,
- size_t nelem,
- size_t n
+ size_t nmemb,
+ size_t size
);
/**
void (*free)(
void *data,
void *mem
- )
- __attribute__((__nonnull__));
+ );
} cx_allocator_class;
/**
typedef struct cx_allocator_s CxAllocator;
/**
- * A default allocator using standard library malloc() etc.
+ * A pre-defined allocator using standard library malloc() etc.
*/
-extern CxAllocator *cxDefaultAllocator;
+cx_attr_export
+extern const CxAllocator * const cxStdlibAllocator;
+
+/**
+ * The default allocator that is used by UCX.
+ * Initialized with cxStdlibAllocator, but you may change it.
+ */
+cx_attr_export
+extern const CxAllocator * cxDefaultAllocator;
/**
* Function pointer type for destructor functions.
*
* A destructor function deallocates possible contents and MAY free the memory
- * pointed to by \p memory. Read the documentation of the respective function
- * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
- * particular implementation.
+ * pointed to by @p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in
+ * that particular implementation.
*
* @param memory a pointer to the object to destruct
*/
-typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__));
+typedef void (*cx_destructor_func)(void *memory);
/**
* Function pointer type for destructor functions.
*
* A destructor function deallocates possible contents and MAY free the memory
- * pointed to by \p memory. Read the documentation of the respective function
- * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
- * particular implementation.
+ * pointed to by @p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in
+ * that particular implementation.
*
* @param data an optional pointer to custom data
* @param memory a pointer to the object to destruct
typedef void (*cx_destructor_func2)(
void *data,
void *memory
-) __attribute__((__nonnull__(2)));
+);
/**
- * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
*
- * \par Error handling
- * \c errno will be set by realloc() on failure.
+ * @par Error handling
+ * @c errno will be set by realloc() on failure.
*
* @param mem pointer to the pointer to allocated block
* @param n the new size in bytes
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocatearray()
*/
-int cx_reallocate(
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_reallocate_(
void **mem,
size_t n
-)
-__attribute__((__nonnull__));
+);
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * The size is calculated by multiplying @p nemb and @p size.
+ *
+ * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure or when the multiplication of
+ * @p nmemb and @p size overflows.
+ *
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocate()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_reallocatearray_(
+ void **mem,
+ size_t nmemb,
+ size_t size
+);
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cx_reallocatearray()
+ */
+#define cx_reallocate(mem, n) cx_reallocate_((void**)(mem), n)
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ *
+ * The size is calculated by multiplying @p nemb and @p size.
+ *
+ * @note This will use stdlib reallocate and @em not the cxDefaultAllocator.
+ *
+ * @par Error handling
+ * @c errno will be set by realloc() on failure or when the multiplication of
+ * @p nmemb and @p size overflows.
+ *
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cx_reallocatearray(mem, nmemb, size) \
+ cx_reallocatearray_((void**)(mem), nmemb, size)
/**
- * Allocate \p n bytes of memory.
+ * Allocates memory and sets every byte to zero.
+ *
+ * @param n (@c size_t) the number of bytes
+ * @return (@c void*) a pointer to the allocated memory
+ */
+#define cx_zalloc(n) calloc(1, n)
+
+/**
+ * Free a block allocated by this allocator.
+ *
+ * @note Freeing a block of a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem a pointer to the block to free
+ */
+cx_attr_nonnull_arg(1)
+cx_attr_export
+void cxFree(
+ const CxAllocator *allocator,
+ void *mem
+);
+
+/**
+ * Allocate @p n bytes of memory.
*
* @param allocator the allocator
* @param n the number of bytes
* @return a pointer to the allocated memory
*/
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_malloc
+cx_attr_dealloc_ucx
+cx_attr_allocsize(2)
+cx_attr_export
void *cxMalloc(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
size_t n
-)
-__attribute__((__malloc__))
-__attribute__((__alloc_size__(2)));
+);
/**
- * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
- * This function may return the same pointer that was passed to it, if moving the memory
- * was not necessary.
+ * Reallocate the previously allocated block in @p mem, making the new block
+ * @p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving
+ * the memory was not necessary.
*
- * \note Re-allocating a block allocated by a different allocator is undefined.
+ * @note Re-allocating a block allocated by a different allocator is undefined.
*
* @param allocator the allocator
* @param mem pointer to the previously allocated block
* @param n the new size in bytes
- * @return a pointer to the re-allocated memory
+ * @return a pointer to the reallocated memory
*/
+cx_attr_nodiscard
+cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx
+cx_attr_allocsize(3)
+cx_attr_export
void *cxRealloc(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
void *mem,
size_t n
-)
-__attribute__((__warn_unused_result__))
-__attribute__((__alloc_size__(3)));
+);
/**
- * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
- * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ * Reallocate the previously allocated block in @p mem, making the new block
+ * @p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving
+ * the memory was not necessary.
*
- * \note Re-allocating a block allocated by a different allocator is undefined.
+ * The size is calculated by multiplying @p nemb and @p size.
+ * If that multiplication overflows, this function returns @c NULL and @c errno
+ * will be set.
*
- * \par Error handling
- * \c errno will be set, if the underlying realloc function does so.
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @param allocator the allocator
+ * @param mem pointer to the previously allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @return a pointer to the reallocated memory
+ */
+cx_attr_nodiscard
+cx_attr_nonnull_arg(1)
+cx_attr_dealloc_ucx
+cx_attr_allocsize(3, 4)
+cx_attr_export
+void *cxReallocArray(
+ const CxAllocator *allocator,
+ void *mem,
+ size_t nmemb,
+ size_t size
+);
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so.
*
* @param allocator the allocator
* @param mem pointer to the pointer to allocated block
* @param n the new size in bytes
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
-int cxReallocate(
- CxAllocator const *allocator,
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
+int cxReallocate_(
+ const CxAllocator *allocator,
void **mem,
size_t n
-)
-__attribute__((__nonnull__));
+);
/**
- * Allocate \p nelem elements of \p n bytes each, all initialized to zero.
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so.
+ *
+ * @param allocator (@c CxAllocator*) the allocator
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param n (@c size_t) the new size in bytes
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cxReallocate(allocator, mem, n) \
+ cxReallocate_(allocator, (void**)(mem), n)
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to
+ * by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so or the
+ * multiplication of @p nmemb and @p size overflows.
*
* @param allocator the allocator
- * @param nelem the number of elements
- * @param n the size of each element in bytes
+ * @param mem pointer to the pointer to allocated block
+ * @param nmemb the number of elements
+ * @param size the size of each element
+ * @retval zero success
+ * @retval non-zero on failure
+ */
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
+int cxReallocateArray_(
+ const CxAllocator *allocator,
+ void **mem,
+ size_t nmemb,
+ size_t size
+);
+
+/**
+ * Reallocate a previously allocated block and changes the pointer in-place,
+ * if necessary.
+ * This function acts like cxReallocArray() using the pointer pointed to
+ * by @p mem.
+ *
+ * @note Re-allocating a block allocated by a different allocator is undefined.
+ *
+ * @par Error handling
+ * @c errno will be set, if the underlying realloc function does so or the
+ * multiplication of @p nmemb and @p size overflows.
+ *
+ * @param allocator (@c CxAllocator*) the allocator
+ * @param mem (@c void**) pointer to the pointer to allocated block
+ * @param nmemb (@c size_t) the number of elements
+ * @param size (@c size_t) the size of each element
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cxReallocateArray(allocator, mem, nmemb, size) \
+ cxReallocateArray_(allocator, (void**) (mem), nmemb, size)
+
+/**
+ * Allocate @p nmemb elements of @p n bytes each, all initialized to zero.
+ *
+ * @param allocator the allocator
+ * @param nmemb the number of elements
+ * @param size the size of each element in bytes
* @return a pointer to the allocated memory
*/
+cx_attr_nonnull_arg(1)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc_ucx
+cx_attr_allocsize(2, 3)
+cx_attr_export
void *cxCalloc(
- CxAllocator const *allocator,
- size_t nelem,
- size_t n
-)
-__attribute__((__malloc__))
-__attribute__((__alloc_size__(2, 3)));
+ const CxAllocator *allocator,
+ size_t nmemb,
+ size_t size
+);
/**
- * Free a block allocated by this allocator.
- *
- * \note Freeing a block of a different allocator is undefined.
+ * Allocate @p n bytes of memory and sets every byte to zero.
*
* @param allocator the allocator
- * @param mem a pointer to the block to free
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
*/
-void cxFree(
- CxAllocator const *allocator,
- void *mem
-)
-__attribute__((__nonnull__));
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_malloc
+cx_attr_dealloc_ucx
+cx_attr_allocsize(2)
+cx_attr_export
+void *cxZalloc(
+ const CxAllocator *allocator,
+ size_t n
+);
+
+/**
+ * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator.
+ */
+#define cxMallocDefault(...) cxMalloc(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxZalloc() with the cxDefaultAllocator.
+ */
+#define cxZallocDefault(...) cxZalloc(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxCalloc() with the cxDefaultAllocator.
+ */
+#define cxCallocDefault(...) cxCalloc(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxRealloc() with the cxDefaultAllocator.
+ */
+#define cxReallocDefault(...) cxRealloc(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxReallocate() with the cxDefaultAllocator.
+ */
+#define cxReallocateDefault(...) cxReallocate(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxReallocateArray() with the cxDefaultAllocator.
+ */
+#define cxReallocateArrayDefault(...) cxReallocateArray(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxReallocArray() with the cxDefaultAllocator.
+ */
+#define cxReallocArrayDefault(...) cxReallocArray(cxDefaultAllocator, __VA_ARGS__)
+/**
+ * Convenience macro that invokes cxFree() with the cxDefaultAllocator.
+ */
+#define cxFreeDefault(...) cxFree(cxDefaultAllocator, __VA_ARGS__)
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file array_list.h
- * \brief Array list implementation.
- * \details Also provides several low-level functions for custom array list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file array_list.h
+ * @brief Array list implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
extern "C" {
#endif
+/**
+ * The maximum item size in an array list that fits into stack buffer
+ * when swapped.
+ */
+cx_attr_export
+extern const unsigned cx_array_swap_sbo_size;
+
+/**
+ * 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_add()
+ * @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 @c size_t type.
+ * 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_add()
+ * @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); // don't forget to free with 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 {
/**
- * Re-allocates space for the given array.
+ * Reallocates space for the given array.
*
* Implementations are not required to free the original array.
- * This allows re-allocation of static memory by allocating heap memory
- * and copying the array contents. The information in \p data can keep
- * track of the state of the memory or other additional allocator info.
+ * This allows reallocation of static memory by allocating heap memory
+ * and copying the array contents. The information in the custom fields of
+ * the referenced allocator can be used to track the state of the memory
+ * or to transport other additional data.
*
* @param array the array to reallocate
- * @param capacity the new capacity (number of elements)
+ * @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
+ * @return a pointer to the reallocated memory or @c NULL on failure
*/
+ cx_attr_nodiscard
+ cx_attr_nonnull_arg(5)
+ cx_attr_allocsize(3, 4)
void *(*realloc)(
void *array,
- size_t capacity,
+ size_t old_capacity,
+ size_t new_capacity,
size_t elem_size,
struct cx_array_reallocator_s *alloc
);
};
/**
- * Return codes for cx_array_copy().
+ * Typedef for the array reallocator struct.
*/
-enum cx_array_copy_result {
- CX_ARRAY_COPY_SUCCESS,
- CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED,
- CX_ARRAY_COPY_REALLOC_FAILED,
-};
+typedef struct cx_array_reallocator_s CxArrayReallocator;
+
+/**
+ * A default array reallocator that is based on the cxDefaultAllocator.
+ */
+cx_attr_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 stackmem is not @c NULL, the reallocator is supposed to be used
+ * @em only for the specific array that is initially located at @p stackmem.
+ * When reallocation is needed, the reallocator checks, if the array is
+ * still located at @p stackmem and copies the contents to the heap.
+ *
+ * @note Invoking this function with both arguments @c NULL will return a
+ * reallocator that behaves like #cx_array_default_reallocator.
+ *
+ * @param allocator the allocator this reallocator shall be based on
+ * @param stackmem the address of the array when the array is initially located
+ * on the stack or shall not reallocated in place
+ * @return an array reallocator
+ */
+cx_attr_export
+CxArrayReallocator cx_array_reallocator(
+ const struct cx_allocator_s *allocator,
+ const void *stackmem
+);
+
+/**
+ * Reserves memory for additional elements.
+ *
+ * This function checks if the @p capacity of the array is sufficient to hold
+ * at least @p size plus @p elem_count elements. If not, a reallocation is
+ * performed 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.
+ *
+ * This function can be useful to replace subsequent calls to cx_array_copy()
+ * with one single cx_array_reserve() and then - after guaranteeing a
+ * sufficient capacity - use simple memmove() or memcpy().
+ *
+ * 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.
+ *
+ * @param array a pointer to the target array
+ * @param size a pointer to the size of the array
+ * @param capacity a pointer to the capacity of the array
+ * @param width the width in bytes for the @p size and @p capacity or zero for default
+ * @param elem_size the size of one element
+ * @param elem_count the number of expected additional elements
+ * @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)
+cx_attr_export
+int cx_array_reserve(
+ void **array,
+ void *size,
+ void *capacity,
+ unsigned width,
+ size_t elem_size,
+ size_t elem_count,
+ CxArrayReallocator *reallocator
+);
/**
* 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
- * would extend the array's size, and \p capacity is not \c NULL, the remaining
- * capacity is used.
+ * 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
+ * would extend the array's size, the remaining @p capacity is used.
*
- * If the capacity is insufficient to hold the new data, a reallocation
- * attempt is made, unless the allocator is set to \c NULL, in which case
- * this function ultimately returns a failure.
+ * 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.
*
- * @param target the target array
+ * 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.
+ *
+ * @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 target array's capacity -
- * \c NULL if only the size shall be used to bound the 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 re-allocator to use, or \c NULL
- * if re-allocation shall not happen
- * @return zero on success, non-zero error code on failure
+ * @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_attr_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)
+
+/**
+ * Convenience macro that uses cx_array_reserve() 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 count (@c size_t) the number of expected @em additional elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_reserve()
+ */
+#define cx_array_simple_reserve_a(reallocator, array, count) \
+ cx_array_reserve((void**)&(array), &(array##_size), &(array##_capacity), \
+ sizeof(array##_size), sizeof((array)[0]), count, \
+ reallocator)
+
+/**
+ * Convenience macro that uses cx_array_reserve() with a default layout and
+ * the default reallocator.
+ *
+ * @param array the name of the array (NOT a pointer or alias to the array)
+ * @param count (@c size_t) the number of expected additional elements
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_reserve_a()
*/
-enum cx_array_copy_result cx_array_copy(
+#define cx_array_simple_reserve(array, count) \
+ cx_array_simple_reserve_a(NULL, array, count)
+
+/**
+ * Adds an element to an array with the possibility of allocating more space.
+ *
+ * The element @p elem is added to the end of the @p target array which contains
+ * @p size elements, already. The @p capacity must point to a variable denoting
+ * the current maximum number of elements the array can hold.
+ *
+ * If the capacity is insufficient to hold the new element, an attempt to
+ * increase the @p capacity is made and the new capacity is written back.
+ *
+ * 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 reallocator (@c CxArrayReallocator*) the array reallocator to use
+ * @retval zero success
+ * @retval non-zero failure
+ */
+#define cx_array_add(target, size, capacity, elem_size, elem, reallocator) \
+ cx_array_copy((void**)(target), size, capacity, sizeof(*(size)), \
+ *(size), elem, elem_size, 1, reallocator)
+
+/**
+ * Convenience macro that uses cx_array_add() 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)
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add()
+ */
+#define cx_array_simple_add_a(reallocator, array, elem) \
+ cx_array_simple_copy_a(reallocator, array, array##_size, &(elem), 1)
+
+/**
+ * Convenience macro that uses cx_array_add() 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)
+ * @retval zero success
+ * @retval non-zero failure
+ * @see CX_ARRAY_DECLARE()
+ * @see cx_array_simple_add_a()
+ */
+#define cx_array_simple_add(array, elem) \
+ cx_array_simple_add_a(cx_array_default_reallocator, array, elem)
+
+/**
+ * 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_attr_export
+int cx_array_insert_sorted(
void **target,
size_t *size,
size_t *capacity,
- size_t index,
- void const *src,
+ cx_compare_func cmp_func,
+ const void *src,
size_t elem_size,
size_t elem_count,
- struct cx_array_reallocator_s *reallocator
-) __attribute__((__nonnull__(1, 2, 5)));
+ 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 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
+ * @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)
+
+/**
+ * Searches the largest lower bound in a sorted array.
+ *
+ * In other words, this function returns the index of the largest element
+ * in @p arr that is less or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
+ *
+ * If @p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the largest lower bound, or @p size
+ * @see cx_array_binary_search_sup()
+ * @see cx_array_binary_search()
+ */
+cx_attr_nonnull
+cx_attr_export
+size_t cx_array_binary_search_inf(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Searches an item in a sorted array.
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the element in the array, or @p size if the element
+ * cannot be found
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search_sup()
+ */
+cx_attr_nonnull
+cx_attr_export
+size_t cx_array_binary_search(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Searches the smallest upper bound in a sorted array.
+ *
+ * In other words, this function returns the index of the smallest element
+ * in @p arr that is greater or equal to @p elem with respect to @p cmp_func.
+ * When no such element exists, @p size is returned.
+ *
+ * If @p elem is contained in the array, this is identical to
+ * #cx_array_binary_search().
+ *
+ * If the array is not sorted with respect to the @p cmp_func, the behavior
+ * is undefined.
+ *
+ * @param arr the array to search
+ * @param size the size of the array
+ * @param elem_size the size of one element
+ * @param elem the element to find
+ * @param cmp_func the compare function
+ * @return the index of the smallest upper bound, or @p size
+ * @see cx_array_binary_search_inf()
+ * @see cx_array_binary_search()
+ */
+cx_attr_nonnull
+cx_attr_export
+size_t cx_array_binary_search_sup(
+ const void *arr,
+ size_t size,
+ size_t elem_size,
+ const void *elem,
+ cx_compare_func cmp_func
+);
/**
* Swaps two array elements.
* @param idx1 index of first element
* @param idx2 index of second element
*/
+cx_attr_nonnull
+cx_attr_export
void cx_array_swap(
void *arr,
size_t elem_size,
size_t idx1,
size_t idx2
-) __attribute__((__nonnull__));
+);
/**
- * Allocates an array list for storing elements with \p item_size bytes each.
+ * Allocates an array list for storing elements with @p elem_size bytes each.
*
- * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
+ * copies of the added elements and the compare function will be automatically set
+ * to cx_cmp_ptr(), if none is given.
*
* @param allocator the allocator for allocating the list memory
- * (if \c NULL the cxDefaultAllocator will be used)
+ * (if @c NULL, the cxDefaultAllocator will be used)
* @param comparator the comparator for the elements
- * (if \c NULL sort and find functions will not work)
- * @param item_size the size of each element in bytes
+ * (if @c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
* @param initial_capacity the initial number of elements the array can store
* @return the created list
*/
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxListFree, 1)
+cx_attr_export
CxList *cxArrayListCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cx_compare_func comparator,
- size_t item_size,
+ size_t elem_size,
size_t initial_capacity
);
/**
- * Allocates an array list for storing elements with \p item_size bytes each.
+ * Allocates an array list for storing elements with @p elem_size bytes each.
*
- * The list will use the cxDefaultAllocator and \em NO compare function.
+ * The list will use the cxDefaultAllocator and @em NO compare function.
* If you want to call functions that need a compare function, you have to
* set it immediately after creation or use cxArrayListCreate().
*
- * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
+ * copies of the added elements and the compare function will be automatically set
+ * to cx_cmp_ptr().
*
- * @param item_size the size of each element in bytes
- * @param initial_capacity the initial number of elements the array can store
+ * @param elem_size (@c size_t) the size of each element in bytes
+ * @param initial_capacity (@c size_t) the initial number of elements the array can store
* @return the created list
*/
-#define cxArrayListCreateSimple(item_size, initial_capacity) \
- cxArrayListCreate(NULL, NULL, item_size, initial_capacity)
+#define cxArrayListCreateSimple(elem_size, initial_capacity) \
+ cxArrayListCreate(NULL, NULL, elem_size, initial_capacity)
#ifdef __cplusplus
} // extern "C"
+++ /dev/null
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-/**
- * \file basic_mempool.h
- * \brief Implementation of a basic memory pool.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
- */
-
-#ifndef UCX_BASIC_MEMPOOL_H
-#define UCX_BASIC_MEMPOOL_H
-
-#include "mempool.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Basic array-based memory pool.
- */
-struct cx_basic_mempool_s {
- /** Inherit base structure members. */
- CxMempool base;
-
- /** List of pointers to pooled memory. */
- void **data;
-
- /** Number of pooled memory items. */
- size_t ndata;
-
- /** Memory pool size. */
- size_t size;
-};
-
-/**
- * Creates a basic array-based memory pool.
- *
- * @param capacity the initial capacity of the pool
- * @return the created memory pool or \c NULL if allocation failed
- */
-__attribute__((__warn_unused_result__))
-CxMempool *cxBasicMempoolCreate(size_t capacity);
-
-#ifdef __cplusplus
-} // extern "C"
-#endif
-
-#endif // UCX_BASIC_MEMPOOL_H
*/
/**
- * \file buffer.h
+ * @file buffer.h
*
- * \brief Advanced buffer implementation.
+ * @brief Advanced buffer implementation.
*
* Instances of CxBuffer can be used to read from or to write to like one
* would do with a stream.
* can be enabled. See the documentation of the macro constants for more
* information.
*
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_BUFFER_H
#include "common.h"
#include "allocator.h"
-#ifdef __cplusplus
+#ifdef __cplusplus
extern "C" {
#endif
/**
* If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ *
+ * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically
+ * set when the copy-on-write operations is performed.
*/
#define CX_BUFFER_FREE_CONTENTS 0x01
/**
- * If this flag is enabled, the buffer will automatically extends its capacity.
+ * If this flag is enabled, the buffer will automatically extend its capacity.
*/
#define CX_BUFFER_AUTO_EXTEND 0x02
+/**
+ * If this flag is enabled, the buffer will allocate new memory when written to.
+ *
+ * The current contents of the buffer will be copied to the new memory and the flag
+ * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically.
+ */
+#define CX_BUFFER_COPY_ON_WRITE 0x04
+
+/**
+ * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation.
+ *
+ * After performing the copy, the flag is automatically cleared.
+ * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why
+ * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled.
+ */
+#define CX_BUFFER_COPY_ON_EXTEND 0x08
+
+/**
+ * Function pointer for cxBufferWrite that is compatible with cx_write_func.
+ * @see cx_write_func
+ */
+#define cxBufferWriteFunc ((cx_write_func) cxBufferWrite)
+/**
+ * Function pointer for cxBufferRead that is compatible with cx_read_func.
+ * @see cx_read_func
+ */
+#define cxBufferReadFunc ((cx_read_func) cxBufferRead)
+
+/**
+ * Configuration for automatic flushing.
+ */
+struct cx_buffer_flush_config_s {
+ /**
+ * The buffer may not extend beyond this threshold before starting to flush.
+ *
+ * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND.
+ * The threshold will be the maximum capacity the buffer is extended to
+ * before flushing.
+ */
+ size_t threshold;
+ /**
+ * The block size for the elements to flush.
+ */
+ size_t blksize;
+ /**
+ * The maximum number of blocks to flush in one cycle.
+ *
+ * @attention while it is guaranteed that cxBufferFlush() will not flush
+ * more blocks, this is not necessarily the case for cxBufferWrite().
+ * After performing a flush cycle, cxBufferWrite() will retry the write
+ * operation and potentially trigger another flush cycle, until the
+ * flush target accepts no more data.
+ */
+ size_t blkmax;
+
+ /**
+ * The target for write function.
+ */
+ void *target;
+
+ /**
+ * The write-function used for flushing.
+ * If NULL, the flushed content gets discarded.
+ */
+ cx_write_func wfunc;
+};
+
+/**
+ * Type alias for the flush configuration struct.
+ *
+ * @code
+ * struct cx_buffer_flush_config_s {
+ * size_t threshold;
+ * size_t blksize;
+ * size_t blkmax;
+ * void *target;
+ * cx_write_func wfunc;
+ * };
+ * @endcode
+ */
+typedef struct cx_buffer_flush_config_s CxBufferFlushConfig;
+
/** Structure for the UCX buffer data. */
-typedef struct {
+struct cx_buffer_s {
/** A pointer to the buffer contents. */
union {
/**
unsigned char *bytes;
};
/** The allocator to use for automatic memory management. */
- CxAllocator const *allocator;
+ const CxAllocator *allocator;
+ /**
+ * Optional flush configuration
+ *
+ * @see cxBufferEnableFlushing()
+ */
+ CxBufferFlushConfig *flush;
/** Current position of the buffer. */
size_t pos;
/** Current capacity (i.e. maximum size) of the buffer. */
size_t capacity;
/** Current size of the buffer content. */
size_t size;
- /**
- * The buffer may not extend beyond this threshold before starting to flush.
- * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
- */
- size_t flush_threshold;
- /**
- * The block size for the elements to flush.
- * Default is 4096 bytes.
- */
- size_t flush_blksize;
- /**
- * The maximum number of blocks to flush in one cycle.
- * Zero disables flushing entirely (this is the default).
- * Set this to \c SIZE_MAX to flush the entire buffer.
- *
- * @attention if the maximum number of blocks multiplied with the block size
- * is smaller than the expected contents written to this buffer within one write
- * operation, multiple flush cycles are performed after that write.
- * That means the total number of blocks flushed after one write to this buffer may
- * be larger than \c flush_blkmax.
- */
- size_t flush_blkmax;
-
- /**
- * The write function used for flushing.
- * If NULL, the flushed content gets discarded.
- */
- cx_write_func flush_func;
-
- /**
- * The target for \c flush_func.
- */
- void *flush_target;
-
/**
* Flag register for buffer features.
* @see #CX_BUFFER_DEFAULT
* @see #CX_BUFFER_FREE_CONTENTS
* @see #CX_BUFFER_AUTO_EXTEND
+ * @see #CX_BUFFER_COPY_ON_WRITE
*/
int flags;
-} cx_buffer_s;
+};
/**
* UCX buffer.
*/
-typedef cx_buffer_s CxBuffer;
+typedef struct cx_buffer_s CxBuffer;
/**
* Initializes a fresh buffer.
*
- * \note You may provide \c NULL as argument for \p space.
+ * You may also provide a read-only @p space, in which case
+ * you will need to cast the pointer, and you should set the
+ * #CX_BUFFER_COPY_ON_WRITE flag.
+ *
+ * You need to set the size manually after initialization, if
+ * you provide @p space which already contains data.
+ *
+ * When you specify stack memory as @p space and decide to use
+ * the auto-extension feature, you @em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
+ *
+ * @note You may provide @c NULL as argument for @p space.
* Then this function will allocate the space and enforce
- * the #CX_BUFFER_FREE_CONTENTS flag.
+ * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying
+ * copy-on-write should be avoided, because the allocated
+ * space will be leaking after the copy-on-write operation.
*
* @param buffer the buffer to initialize
- * @param space pointer to the memory area, or \c NULL to allocate
+ * @param space pointer to the memory area, or @c NULL to allocate
* new memory
* @param capacity the capacity of the buffer
* @param allocator the allocator this buffer shall use for automatic
- * memory management. If \c NULL, the default heap allocator will be used.
+ * memory management
+ * (if @c NULL, the cxDefaultAllocator will be used)
* @param flags buffer features (see cx_buffer_s.flags)
* @return zero on success, non-zero if a required allocation failed
*/
-__attribute__((__nonnull__(1)))
+cx_attr_nonnull_arg(1)
+cx_attr_export
int cxBufferInit(
CxBuffer *buffer,
void *space,
size_t capacity,
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
int flags
);
/**
- * Allocates and initializes a fresh buffer.
+ * Configures the buffer for flushing.
*
- * \note You may provide \c NULL as argument for \p space.
- * Then this function will allocate the space and enforce
- * the #CX_BUFFER_FREE_CONTENTS flag.
+ * Flushing can happen automatically when data is written
+ * to the buffer (see cxBufferWrite()) or manually when
+ * cxBufferFlush() is called.
*
- * @param space pointer to the memory area, or \c NULL to allocate
- * new memory
- * @param capacity the capacity of the buffer
- * @param allocator the allocator to use for allocating the structure and the automatic
- * memory management within the buffer. If \c NULL, the default heap allocator will be used.
- * @param flags buffer features (see cx_buffer_s.flags)
- * @return a pointer to the buffer on success, \c NULL if a required allocation failed
+ * @param buffer the buffer
+ * @param config the flush configuration
+ * @retval zero success
+ * @retval non-zero failure
+ * @see cxBufferFlush()
+ * @see cxBufferWrite()
*/
-CxBuffer *cxBufferCreate(
- void *space,
- size_t capacity,
- CxAllocator const *allocator,
- int flags
+cx_attr_nonnull
+cx_attr_export
+int cxBufferEnableFlushing(
+ CxBuffer *buffer,
+ CxBufferFlushConfig config
);
/**
* @param buffer the buffer which contents shall be destroyed
* @see cxBufferInit()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
void cxBufferDestroy(CxBuffer *buffer);
/**
* Deallocates the buffer.
*
* If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
- * the contents. If you \em only want to destroy the contents, use cxBufferDestroy().
+ * the contents. If you @em only want to destroy the contents, use cxBufferDestroy().
+ *
+ * @remark As with all free() functions, this accepts @c NULL arguments in which
+ * case it does nothing.
*
* @param buffer the buffer to deallocate
* @see cxBufferCreate()
*/
-__attribute__((__nonnull__))
+cx_attr_export
void cxBufferFree(CxBuffer *buffer);
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * You may also provide a read-only @p space, in which case
+ * you will need to cast the pointer, and you should set the
+ * #CX_BUFFER_COPY_ON_WRITE flag.
+ * When you specify stack memory as @p space and decide to use
+ * the auto-extension feature, you @em must use the
+ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
+ * #CX_BUFFER_AUTO_EXTEND flag.
+ *
+ * @note You may provide @c NULL as argument for @p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or @c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer
+ * (if @c NULL, the cxDefaultAllocator will be used)
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return a pointer to the buffer on success, @c NULL if a required allocation failed
+ */
+cx_attr_malloc
+cx_attr_dealloc(cxBufferFree, 1)
+cx_attr_nodiscard
+cx_attr_export
+CxBuffer *cxBufferCreate(
+ void *space,
+ size_t capacity,
+ const CxAllocator *allocator,
+ int flags
+);
+
/**
* Shifts the contents of the buffer by the given offset.
*
* are discarded.
*
* If the offset is negative, the contents are shifted to the left where the
- * first \p shift bytes are discarded.
+ * first @p shift bytes are discarded.
* The new size of the buffer is the old size minus the absolute shift value.
* If this value is larger than the buffer size, the buffer is emptied (but
* not cleared, see the security note below).
* The buffer position gets shifted alongside with the content but is kept
* within the boundaries of the buffer.
*
- * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
- * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a @c size_t as parameter type.
*
- * \attention
- * Security Note: The shifting operation does \em not erase the previously occupied memory cells.
+ * @attention
+ * Security Note: The shifting operation does @em not erase the previously occupied memory cells.
* But you can easily do that manually, e.g. by calling
* <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
* <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
*
* @param buffer the buffer
* @param shift the shift offset (negative means left shift)
- * @return 0 on success, non-zero if a required auto-extension fails
+ * @retval zero success
+ * @retval non-zero if a required auto-extension or copy-on-write fails
+ * @see cxBufferShiftLeft()
+ * @see cxBufferShiftRight()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferShift(
CxBuffer *buffer,
off_t shift
*
* @param buffer the buffer
* @param shift the shift offset
- * @return 0 on success, non-zero if a required auto-extension fails
+ * @retval zero success
+ * @retval non-zero if a required auto-extension or copy-on-write fails
* @see cxBufferShift()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferShiftRight(
CxBuffer *buffer,
size_t shift
* Shifts the buffer to the left.
* See cxBufferShift() for details.
*
- * \note Since a left shift cannot fail due to memory allocation problems, this
- * function always returns zero.
- *
* @param buffer the buffer
* @param shift the positive shift offset
- * @return always zero
+ * @retval zero success
+ * @retval non-zero if the buffer uses copy-on-write and the allocation fails
* @see cxBufferShift()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferShiftLeft(
CxBuffer *buffer,
size_t shift
/**
* Moves the position of the buffer.
*
- * The new position is relative to the \p whence argument.
+ * The new position is relative to the @p whence argument.
*
- * \li \c SEEK_SET marks the start of the buffer.
- * \li \c SEEK_CUR marks the current position.
- * \li \c SEEK_END marks the end of the buffer.
+ * @li @c SEEK_SET marks the start of the buffer.
+ * @li @c SEEK_CUR marks the current position.
+ * @li @c SEEK_END marks the end of the buffer.
*
* With an offset of zero, this function sets the buffer position to zero
- * (\c SEEK_SET), the buffer size (\c SEEK_END) or leaves the buffer position
- * unchanged (\c SEEK_CUR).
+ * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position
+ * unchanged (@c SEEK_CUR).
*
* @param buffer the buffer
- * @param offset position offset relative to \p whence
- * @param whence one of \c SEEK_SET, \c SEEK_CUR or \c SEEK_END
- * @return 0 on success, non-zero if the position is invalid
+ * @param offset position offset relative to @p whence
+ * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END
+ * @retval zero success
+ * @retval non-zero if the position is invalid
*
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferSeek(
CxBuffer *buffer,
off_t offset,
* Clears the buffer by resetting the position and deleting the data.
*
* The data is deleted by zeroing it with a call to memset().
+ * If you do not need that, you can use the faster cxBufferReset().
+ *
+ * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function
+ * will not erase the data and behave exactly as cxBufferReset().
*
* @param buffer the buffer to be cleared
+ * @see cxBufferReset()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
void cxBufferClear(CxBuffer *buffer);
/**
- * Tests, if the buffer position has exceeded the buffer capacity.
+ * Resets the buffer by resetting the position and size to zero.
+ *
+ * The data in the buffer is not deleted. If you need a safe
+ * reset of the buffer, use cxBufferClear().
+ *
+ * @param buffer the buffer to be cleared
+ * @see cxBufferClear()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxBufferReset(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer size.
*
* @param buffer the buffer to test
- * @return non-zero, if the current buffer position has exceeded the last
- * available byte of the buffer.
+ * @retval true if the current buffer position has exceeded the last
+ * byte of the buffer's contents
+ * @retval false otherwise
*/
-__attribute__((__nonnull__))
-int cxBufferEof(CxBuffer const *buffer);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+bool cxBufferEof(const CxBuffer *buffer);
/**
*
* If the current capacity is not sufficient, the buffer will be extended.
*
+ * The new capacity will be a power of two until the system's page size is reached.
+ * Then, the new capacity will be a multiple of the page size.
+ *
* @param buffer the buffer
* @param capacity the minimum required capacity for this buffer
- * @return 0 on success or a non-zero value on failure
+ * @retval zero the capacity was already sufficient or successfully increased
+ * @retval non-zero on allocation failure
+ * @see cxBufferShrink()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferMinimumCapacity(
CxBuffer *buffer,
size_t capacity
);
+/**
+ * Shrinks the capacity of the buffer to fit its current size.
+ *
+ * If @p reserve is larger than zero, the buffer is shrunk to its size plus
+ * the number of reserved bytes.
+ *
+ * If the current capacity is not larger than the size plus the reserved bytes,
+ * nothing happens.
+ *
+ * If the #CX_BUFFER_COPY_ON_WRITE or #CX_BUFFER_COPY_ON_EXTEND flag is set,
+ * this function does nothing.
+ *
+ * @param buffer the buffer
+ * @param reserve the number of bytes that shall remain reserved
+ * @see cxBufferMinimumCapacity()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxBufferShrink(
+ CxBuffer *buffer,
+ size_t reserve
+);
+
/**
* Writes data to a CxBuffer.
*
+ * If automatic flushing is not enabled, the data is simply written into the
+ * buffer at the current position and the position of the buffer is increased
+ * by the number of bytes written.
+ *
* If flushing is enabled and the buffer needs to flush, the data is flushed to
* the target until the target signals that it cannot take more data by
* returning zero via the respective write function. In that case, the remaining
* data in this buffer is shifted to the beginning of this buffer so that the
- * newly available space can be used to append as much data as possible. This
- * function only stops writing more elements, when the flush target and this
+ * newly available space can be used to append as much data as possible.
+ *
+ * This function only stops writing more elements, when the flush target and this
* buffer are both incapable of taking more data or all data has been written.
- * The number returned by this function is the total number of elements that
- * could be written during the process. It does not necessarily mean that those
- * elements are still in this buffer, because some of them could have also be
- * flushed already.
*
- * If automatic flushing is not enabled, the position of the buffer is increased
- * by the number of bytes written.
+ * If, after flushing, the number of items that shall be written still exceeds
+ * the capacity or flush threshold, this function tries to write all items directly
+ * to the flush target, if possible.
+ *
+ * The number returned by this function is the number of elements from
+ * @c ptr that could be written to either the flush target or the buffer
+ * (so it does not include the number of items that had been already in the buffer
+ * in were flushed during the process).
+ *
+ * @attention
+ * When @p size is larger than one and the contents of the buffer are not aligned
+ * with @p size, flushing stops after all complete items have been flushed, leaving
+ * the mis-aligned part in the buffer.
+ * Afterward, this function only writes as many items as possible to the buffer.
*
- * \note The signature is compatible with the fwrite() family of functions.
+ * @note The signature is compatible with the fwrite() family of functions.
*
* @param ptr a pointer to the memory area containing the bytes to be written
* @param size the length of one element
* @param nitems the element count
* @param buffer the CxBuffer to write to
* @return the total count of elements written
+ * @see cxBufferAppend()
+ * @see cxBufferRead()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
size_t cxBufferWrite(
- void const *ptr,
+ const void *ptr,
size_t size,
size_t nitems,
CxBuffer *buffer
);
+/**
+ * Appends data to a CxBuffer.
+ *
+ * The data is always appended to current data within the buffer,
+ * regardless of the current position.
+ * This is especially useful when the buffer is primarily meant for reading
+ * while additional data is added to the buffer occasionally.
+ * Consequently, the position of the buffer is unchanged after this operation.
+ *
+ * @note The signature is compatible with the fwrite() family of functions.
+ *
+ * @param ptr a pointer to the memory area containing the bytes to be written
+ * @param size the length of one element
+ * @param nitems the element count
+ * @param buffer the CxBuffer to write to
+ * @return the total count of elements written
+ * @see cxBufferWrite()
+ * @see cxBufferRead()
+ */
+cx_attr_nonnull
+cx_attr_export
+size_t cxBufferAppend(
+ const void *ptr,
+ size_t size,
+ size_t nitems,
+ CxBuffer *buffer
+);
+
+/**
+ * Performs a single flush-run on the specified buffer.
+ *
+ * Does nothing when the position in the buffer is zero.
+ * Otherwise, the data until the current position minus
+ * one is considered for flushing.
+ * Note carefully that flushing will never exceed the
+ * current @em position, even when the size of the
+ * buffer is larger than the current position.
+ *
+ * One flush run will try to flush @c blkmax many
+ * blocks of size @c blksize until either the @p buffer
+ * has no more data to flush or the write function
+ * used for flushing returns zero.
+ *
+ * The buffer is shifted left for that many bytes
+ * the flush operation has successfully flushed.
+ *
+ * @par Example 1
+ * Assume you have a buffer with size 340 and you are
+ * at position 200. The flush configuration is
+ * @c blkmax=4 and @c blksize=64 .
+ * Assume that the entire flush operation is successful.
+ * All 200 bytes on the left hand-side from the current
+ * position are written.
+ * That means, the size of the buffer is now 140 and the
+ * position is zero.
+ *
+ * @par Example 2
+ * Same as Example 1, but now the @c blkmax is 1.
+ * The size of the buffer is now 276 and the position is 136.
+ *
+ * @par Example 3
+ * Same as Example 1, but now assume the flush target
+ * only accepts 100 bytes before returning zero.
+ * That means, the flush operations manages to flush
+ * one complete block and one partial block, ending
+ * up with a buffer with size 240 and position 100.
+ *
+ * @remark Just returns zero when flushing was not enabled with
+ * cxBufferEnableFlushing().
+ *
+ * @remark When the buffer uses copy-on-write, the memory
+ * is copied first, before attempting any flush.
+ * This is, however, considered an erroneous use of the
+ * buffer, because it does not make much sense to put
+ * readonly data into an UCX buffer for flushing, instead
+ * of writing it directly to the target.
+ *
+ * @param buffer the buffer
+ * @return the number of successfully flushed bytes
+ * @see cxBufferEnableFlushing()
+ */
+cx_attr_nonnull
+cx_attr_export
+size_t cxBufferFlush(CxBuffer *buffer);
+
/**
* Reads data from a CxBuffer.
*
* The position of the buffer is increased by the number of bytes read.
*
- * \note The signature is compatible with the fread() family of functions.
+ * @note The signature is compatible with the fread() family of functions.
*
* @param ptr a pointer to the memory area where to store the read data
* @param size the length of one element
* @param nitems the element count
* @param buffer the CxBuffer to read from
* @return the total number of elements read
+ * @see cxBufferWrite()
+ * @see cxBufferAppend()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
size_t cxBufferRead(
void *ptr,
size_t size,
*
* The least significant byte of the argument is written to the buffer. If the
* end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled,
- * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature is
- * disabled or buffer extension fails, \c EOF is returned.
+ * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature
+ * is disabled or buffer extension fails, @c EOF is returned.
*
* On successful write, the position of the buffer is increased.
*
+ * If you just want to write a null-terminator at the current position, you
+ * should use cxBufferTerminate() instead.
+ *
* @param buffer the buffer to write to
* @param c the character to write
- * @return the byte that has bean written or \c EOF when the end of the stream is
+ * @return the byte that has been written or @c EOF when the end of the stream is
* reached and automatic extension is not enabled or not possible
+ * @see cxBufferTerminate()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferPut(
CxBuffer *buffer,
int c
);
+/**
+ * Writes a terminating zero to a buffer at the current position.
+ *
+ * If successful, sets the size to the current position and advances the position by one.
+ *
+ * The purpose of this function is to have the written data ready to be used as
+ * a C string with the buffer's size being the length of that string.
+ *
+ * @param buffer the buffer to write to
+ * @return zero, if the terminator could be written, non-zero otherwise
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxBufferTerminate(CxBuffer *buffer);
+
/**
* Writes a string to a buffer.
*
+ * This is a convenience function for <code>cxBufferWrite(str, 1, strlen(str), buffer)</code>.
+ *
* @param buffer the buffer
* @param str the zero-terminated string
* @return the number of bytes written
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+cx_attr_export
size_t cxBufferPutString(
CxBuffer *buffer,
const char *str
* The current position of the buffer is increased after a successful read.
*
* @param buffer the buffer to read from
- * @return the character or \c EOF, if the end of the buffer is reached
+ * @return the character or @c EOF, if the end of the buffer is reached
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxBufferGet(CxBuffer *buffer);
#ifdef __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file collection.h
- * \brief Common definitions for various collection implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file collection.h
+ * @brief Common definitions for various collection implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_COLLECTION_H
#include "allocator.h"
#include "iterator.h"
+#include "compare.h"
#ifdef __cplusplus
extern "C" {
#define CX_STORE_POINTERS 0
/**
- * A comparator function comparing two collection elements.
+ * Base attributes of a collection.
*/
-typedef int(*cx_compare_func)(
- void const *left,
- void const *right
-);
+struct cx_collection_s {
+ /**
+ * The allocator to use.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The comparator function for the elements.
+ */
+ cx_compare_func cmpfunc;
+ /**
+ * The size of each element.
+ */
+ size_t elem_size;
+ /**
+ * The number of currently stored elements.
+ */
+ size_t size;
+ /**
+ * An optional simple destructor for the collection's elements.
+ *
+ * @attention Read the documentation of the particular collection implementation
+ * whether this destructor shall only destroy the contents or also free the memory.
+ */
+ cx_destructor_func simple_destructor;
+ /**
+ * An optional advanced destructor for the collection's elements.
+ *
+ * @attention Read the documentation of the particular collection implementation
+ * whether this destructor shall only destroy the contents or also free the memory.
+ */
+ cx_destructor_func2 advanced_destructor;
+ /**
+ * The pointer to additional data that is passed to the advanced destructor.
+ */
+ void *destructor_data;
+ /**
+ * Indicates if this list is supposed to store pointers
+ * instead of copies of the actual objects.
+ */
+ bool store_pointer;
+ /**
+ * Indicates if this collection is guaranteed to be sorted.
+ * Note that the elements can still be sorted, even when the collection is not aware of that.
+ */
+ bool sorted;
+};
/**
* Use this macro to declare common members for a collection structure.
+ *
+ * @par Example Use
+ * @code
+ * struct MyCustomSet {
+ * CX_COLLECTION_BASE;
+ * MySetElements *data;
+ * }
+ * @endcode
*/
-#define CX_COLLECTION_MEMBERS \
- /** \
- * The allocator to use. \
- */ \
- CxAllocator const *allocator; \
- /** \
- * The comparator function for the elements. \
- */ \
- cx_compare_func cmpfunc; \
- /** \
- * The size of each element. \
- */ \
- size_t item_size; \
- /** \
- * The number of currently stored elements. \
- */ \
- size_t size; \
- /** \
- * An optional simple destructor for the collection's elements. \
- * \
- * @attention Read the documentation of the particular collection implementation \
- * whether this destructor shall only destroy the contents or also free the memory. \
- */ \
- cx_destructor_func simple_destructor; \
- /** \
- * An optional advanced destructor for the collection's elements. \
- * \
- * @attention Read the documentation of the particular collection implementation \
- * whether this destructor shall only destroy the contents or also free the memory. \
- */ \
- cx_destructor_func2 advanced_destructor; \
- /** \
- * The pointer to additional data that is passed to the advanced destructor. \
- */ \
- void *destructor_data; \
- /** \
- * Indicates if this instance of a collection is supposed to store pointers \
- * instead of copies of the actual objects. \
- */ \
- bool store_pointer;
+#define CX_COLLECTION_BASE struct cx_collection_s collection
+
+/**
+ * Returns the number of elements currently stored.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @return (@c size_t) the number of currently stored elements
+ */
+#define cxCollectionSize(c) ((c)->collection.size)
+
+/**
+ * Returns the size of one element.
+ *
+ * If #cxCollectionStoresPointers() returns true, this is the size of a pointer.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @return (@c size_t) the size of one element in bytes
+ */
+#define cxCollectionElementSize(c) ((c)->collection.elem_size)
+
+/**
+ * Indicates whether this collection only stores pointers instead of the actual data.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @retval true if this collection stores only pointers to data
+ * @retval false if this collection stores the actual element's data
+ */
+#define cxCollectionStoresPointers(c) ((c)->collection.store_pointer)
+
+/**
+ * Indicates whether the collection can guarantee that the stored elements are currently sorted.
+ *
+ * This may return false even when the elements are sorted.
+ * It is totally up to the implementation of the collection whether it keeps track of the order of its elements.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @retval true if the elements are currently sorted wrt. the collection's compare function
+ * @retval false if the order of elements is unknown
+ */
+#define cxCollectionSorted(c) ((c)->collection.sorted)
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param destr the destructor function
+ */
+#define cxDefineDestructor(c, destr) \
+ (c)->collection.simple_destructor = (cx_destructor_func) destr
+
+/**
+ * Sets a simple destructor function for this collection.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param destr the destructor function
+ */
+#define cxDefineAdvancedDestructor(c, destr, data) \
+ (c)->collection.advanced_destructor = (cx_destructor_func2) destr; \
+ (c)->collection.destructor_data = data
/**
* Invokes the simple destructor function for a specific element.
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_simple_destructor(c, e) \
- (c)->simple_destructor((c)->store_pointer ? (*((void **) (e))) : (e))
+ (c)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e))
/**
* Invokes the advanced destructor function for a specific element.
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_advanced_destructor(c, e) \
- (c)->advanced_destructor((c)->destructor_data, \
- (c)->store_pointer ? (*((void **) (e))) : (e))
+ (c)->collection.advanced_destructor((c)->collection.destructor_data, \
+ (c)->collection.store_pointer ? (*((void **) (e))) : (e))
/**
* Usually only used by collection implementations. There should be no need
* to invoke this macro manually.
*
- * @param c the collection
- * @param e the element
+ * When the collection stores pointers, those pointers are directly passed
+ * to the destructor. Otherwise, a pointer to the element is passed.
+ *
+ * @param c a pointer to a struct that contains #CX_COLLECTION_BASE
+ * @param e the element (the type is @c void* or @c void** depending on context)
*/
#define cx_invoke_destructor(c, e) \
- if ((c)->simple_destructor) cx_invoke_simple_destructor(c,e); \
- if ((c)->advanced_destructor) cx_invoke_advanced_destructor(c,e)
+ if ((c)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
+ if ((c)->collection.advanced_destructor) cx_invoke_advanced_destructor(c,e)
#ifdef __cplusplus
} // extern "C"
*/
/**
- * \file common.h
+ * @file common.h
*
- * \brief Common definitions and feature checks.
+ * @brief Common definitions and feature checks.
*
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*
- * \mainpage UAP Common Extensions
+ * @mainpage UAP Common Extensions
* Library with common and useful functions, macros and data structures.
* <p>
* Latest available source:<br>
* Repositories:<br>
* <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
* - or -
- * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * <a href="https://uap-core.de/hg/ucx">https://uap-core.de/hg/ucx</a>
* </p>
*
* <h2>LICENCE</h2>
#define UCX_VERSION_MAJOR 3
/** Minor UCX version as integer constant. */
-#define UCX_VERSION_MINOR 0
+#define UCX_VERSION_MINOR 1
/** Version constant which ensures to increase monotonically. */
#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
-// Common Includes
+// ---------------------------------------------------------------------------
+// Common includes
+// ---------------------------------------------------------------------------
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
+// ---------------------------------------------------------------------------
+// Architecture Detection
+// ---------------------------------------------------------------------------
+
+#ifndef INTPTR_MAX
+#error Missing INTPTR_MAX definition
+#endif
+#if INTPTR_MAX == INT64_MAX
+/**
+ * The address width in bits on this platform.
+ */
+#define CX_WORDSIZE 64
+#elif INTPTR_MAX == INT32_MAX
+/**
+ * The address width in bits on this platform.
+ */
+#define CX_WORDSIZE 32
+#else
+#error Unknown pointer size or missing size macros!
+#endif
+
+// ---------------------------------------------------------------------------
+// Attribute definitions
+// ---------------------------------------------------------------------------
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+/**
+ * All pointer arguments must be non-NULL.
+ */
+#define cx_attr_nonnull __attribute__((__nonnull__))
+
+/**
+ * The specified pointer arguments must be non-NULL.
+ */
+#define cx_attr_nonnull_arg(...) __attribute__((__nonnull__(__VA_ARGS__)))
+
+/**
+ * The returned value is guaranteed to be non-NULL.
+ */
+#define cx_attr_returns_nonnull __attribute__((__returns_nonnull__))
+
+/**
+ * The attributed function always returns freshly allocated memory.
+ */
+#define cx_attr_malloc __attribute__((__malloc__))
+
+#if !defined(__clang__) && __GNUC__ >= 11
+/**
+ * The pointer returned by the attributed function is supposed to be freed
+ * by @p freefunc.
+ *
+ * @param freefunc the function that shall be used to free the memory
+ * @param freefunc_arg the index of the pointer argument in @p freefunc
+ */
+#define cx_attr_dealloc(freefunc, freefunc_arg) \
+ __attribute__((__malloc__(freefunc, freefunc_arg)))
+#else
+/**
+ * Not supported in clang.
+ */
+#define cx_attr_dealloc(...)
+#endif // __clang__
+
+/**
+ * Shortcut to specify #cxFree() as deallocator.
+ */
+#define cx_attr_dealloc_ucx cx_attr_dealloc(cxFree, 2)
+
+/**
+ * Specifies the parameters from which the allocation size is calculated.
+ */
+#define cx_attr_allocsize(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+
+
+#ifdef __clang__
+/**
+ * No support for @c null_terminated_string_arg in clang or GCC below 14.
+ */
+#define cx_attr_cstr_arg(idx)
+/**
+ * No support for access attribute in clang.
+ */
+#define cx_attr_access(mode, ...)
+#else
+#if __GNUC__ < 10
+/**
+ * No support for access attribute in GCC < 10.
+ */
+#define cx_attr_access(mode, ...)
+#else
+/**
+ * Helper macro to define access macros.
+ */
+#define cx_attr_access(mode, ...) __attribute__((__access__(mode, __VA_ARGS__)))
+#endif // __GNUC__ < 10
+#if __GNUC__ < 14
+/**
+ * No support for @c null_terminated_string_arg in clang or GCC below 14.
+ */
+#define cx_attr_cstr_arg(idx)
+#else
+/**
+ * The specified argument is expected to be a zero-terminated string.
+ *
+ * @param idx the index of the argument
+ */
+#define cx_attr_cstr_arg(idx) \
+ __attribute__((__null_terminated_string_arg__(idx)))
+#endif // __GNUC__ < 14
+#endif // __clang__
+
+
+/**
+ * Specifies that the function will only read through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_r(...) cx_attr_access(__read_only__, __VA_ARGS__)
+
+/**
+ * Specifies that the function will read and write through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_rw(...) cx_attr_access(__read_write__, __VA_ARGS__)
+
+/**
+ * Specifies that the function will only write through the given pointer.
+ *
+ * Takes one or two arguments: the index of the pointer and (optionally) the
+ * index of another argument specifying the maximum number of accessed bytes.
+ */
+#define cx_attr_access_w(...) cx_attr_access(__write_only__, __VA_ARGS__)
+
+/**
+ * Do not warn about unused variable.
+ */
+#define cx_attr_unused __attribute__((__unused__))
+
+/**
+ * Warn about discarded return value.
+ */
+#define cx_attr_nodiscard __attribute__((__warn_unused_result__))
+
+
+// ---------------------------------------------------------------------------
+// MSVC specifics
+// ---------------------------------------------------------------------------
+
+#ifdef _MSC_VER
+// fix missing _Thread_local support
+#define _Thread_local __declspec(thread)
+#endif // _MSC_VER
+
+#if defined(CX_WINDLL_EXPORT)
+#define cx_attr_export __declspec(dllexport)
+#elif defined(CX_WINDLL)
+#define cx_attr_export __declspec(dllimport)
+#else
+/** Only used for building Windows DLLs. */
+#define cx_attr_export
+#endif // CX_WINDLL / CX_WINDLL_EXPORT
+
+// ---------------------------------------------------------------------------
+// Useful function pointers
+// ---------------------------------------------------------------------------
+
/**
* Function pointer compatible with fwrite-like functions.
*/
typedef size_t (*cx_write_func)(
- void const *,
+ const void *,
size_t,
size_t,
void *
void *
);
+// ---------------------------------------------------------------------------
+// Utility macros
+// ---------------------------------------------------------------------------
-// Compiler specific stuff
-
-#ifndef __GNUC__
/**
- * Removes GNU C attributes where they are not supported.
+ * Determines the number of members in a static C array.
+ *
+ * @attention never use this to determine the size of a dynamically allocated
+ * array.
+ *
+ * @param arr the array identifier
+ * @return the number of elements
*/
-#define __attribute__(x)
-#endif
+#define cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0]))
-#ifdef _MSC_VER
-
-// fix missing ssize_t definition
-#include <BaseTsd.h>
-typedef SSIZE_T ssize_t;
+// ---------------------------------------------------------------------------
+// szmul implementation
+// ---------------------------------------------------------------------------
-// fix missing _Thread_local support
-#define _Thread_local __declspec(thread)
+#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
+#define CX_SZMUL_BUILTIN
+#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
+#else // no GNUC or clang bultin
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * @param a (@c size_t) first operand
+ * @param b (@c size_t) second operand
+ * @param result (@c size_t*) a pointer to a variable, where the result should
+ * be stored
+ * @retval zero success
+ * @retval non-zero the multiplication would overflow
+ */
+#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
+/**
+ * Implementation of cx_szmul() when no compiler builtin is available.
+ *
+ * Do not use in application code.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a variable, where the result should
+ * be stored
+ * @retval zero success
+ * @retval non-zero the multiplication would overflow
+ */
+#if __cplusplus
+extern "C"
#endif
+cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result);
+#endif // cx_szmul
+
+
#endif // UCX_COMMON_H
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file compare.h
- * \brief A collection of simple compare functions.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file compare.h
+ * @brief A collection of simple compare functions.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_COMPARE_H
extern "C" {
#endif
+/**
+ * A comparator function comparing two arbitrary values.
+ *
+ * All functions from compare.h with the cx_cmp prefix are
+ * compatible with this signature and can be used as
+ * compare function for collections, or other implementations
+ * that need to be type-agnostic.
+ *
+ * For simple comparisons the cx_vcmp family of functions
+ * can be used, but they are NOT compatible with this function
+ * pointer.
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+typedef int (*cx_compare_func)(
+ const void *left,
+ const void *right
+);
+
/**
* Compares two integers of type int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to integer one
* @param i2 pointer to integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_int(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_int(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int.
+ *
+ * @param i1 integer one
+ * @param i2 integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_int(int i1, int i2);
/**
* Compares two integers of type long int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to long integer one
* @param i2 pointer to long integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_longint(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_longint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long int.
+ *
+ * @param i1 long integer one
+ * @param i2 long integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_longint(long int i1, long int i2);
/**
* Compares two integers of type long long.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to long long one
* @param i2 pointer to long long two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_longlong(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_longlong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type long long.
+ *
+ * @param i1 long long int one
+ * @param i2 long long int two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_longlong(long long int i1, long long int i2);
/**
* Compares two integers of type int16_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int16_t one
* @param i2 pointer to int16_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_int16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @param i1 int16_t one
+ * @param i2 int16_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_int16(void const *i1, void const *i2);
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_int16(int16_t i1, int16_t i2);
/**
* Compares two integers of type int32_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int32_t one
* @param i2 pointer to int32_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_int32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @param i1 int32_t one
+ * @param i2 int32_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_int32(void const *i1, void const *i2);
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_int32(int32_t i1, int32_t i2);
/**
* Compares two integers of type int64_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to int64_t one
* @param i2 pointer to int64_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_int64(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_int64(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @param i1 int64_t one
+ * @param i2 int64_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_int64(int64_t i1, int64_t i2);
/**
* Compares two integers of type unsigned int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned integer one
* @param i2 pointer to unsigned integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_uint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned int.
+ *
+ * @param i1 unsigned integer one
+ * @param i2 unsigned integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uint(void const *i1, void const *i2);
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_uint(unsigned int i1, unsigned int i2);
/**
* Compares two integers of type unsigned long int.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned long integer one
* @param i2 pointer to unsigned long integer two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_ulongint(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long int.
+ *
+ * @param i1 unsigned long integer one
+ * @param i2 unsigned long integer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_ulongint(void const *i1, void const *i2);
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2);
/**
* Compares two integers of type unsigned long long.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to unsigned long long one
* @param i2 pointer to unsigned long long two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_ulonglong(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_ulonglong(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type unsigned long long.
+ *
+ * @param i1 unsigned long long one
+ * @param i2 unsigned long long two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2);
/**
* Compares two integers of type uint16_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint16_t one
* @param i2 pointer to uint16_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uint16(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_uint16(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @param i1 uint16_t one
+ * @param i2 uint16_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_uint16(uint16_t i1, uint16_t i2);
/**
* Compares two integers of type uint32_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint32_t one
* @param i2 pointer to uint32_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uint32(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_uint32(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @param i1 uint32_t one
+ * @param i2 uint32_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_uint32(uint32_t i1, uint32_t i2);
/**
* Compares two integers of type uint64_t.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param i1 pointer to uint64_t one
* @param i2 pointer to uint64_t two
- * @return -1, if *i1 is less than *i2, 0 if both are equal,
- * 1 if *i1 is greater than *i2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uint64(void const *i1, void const *i2);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_uint64(const void *i1, const void *i2);
+
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @param i1 uint64_t one
+ * @param i2 uint64_t two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_uint64(uint64_t i1, uint64_t i2);
/**
* Compares two real numbers of type float with precision 1e-6f.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param f1 pointer to float one
* @param f2 pointer to float two
- * @return -1, if *f1 is less than *f2, 0 if both are equal,
- * 1 if *f1 is greater than *f2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_float(const void *f1, const void *f2);
-int cx_cmp_float(void const *f1, void const *f2);
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @param f1 float one
+ * @param f2 float two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_float(float f1, float f2);
/**
* Compares two real numbers of type double with precision 1e-14.
*
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
* @param d1 pointer to double one
* @param d2 pointer to double two
- * @return -1, if *d1 is less than *d2, 0 if both are equal,
- * 1 if *d1 is greater than *d2
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_double(
- void const *d1,
- void const *d2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_double(const void *d1, const void *d2);
+
+/**
+ * Compares two real numbers of type double with precision 1e-14.
+ *
+ * @param d1 double one
+ * @param d2 double two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_double(double d1, double d2);
/**
* Compares the integer representation of two pointers.
*
- * @param ptr1 pointer to pointer one (intptr_t const*)
- * @param ptr2 pointer to pointer two (intptr_t const*)
- * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
- * 1 if *ptr1 is greater than *ptr2
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
+ * @param ptr1 pointer to pointer one (const intptr_t*)
+ * @param ptr2 pointer to pointer two (const intptr_t*)
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_intptr(
- void const *ptr1,
- void const *ptr2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_intptr(const void *ptr1, const void *ptr2);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2);
/**
* Compares the unsigned integer representation of two pointers.
*
- * @param ptr1 pointer to pointer one (uintptr_t const*)
- * @param ptr2 pointer to pointer two (uintptr_t const*)
- * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
- * 1 if *ptr1 is greater than *ptr2
+ * @note the parameters deliberately have type @c void* to be
+ * compatible with #cx_compare_func without the need of a cast.
+ *
+ * @param ptr1 pointer to pointer one (const uintptr_t*)
+ * @param ptr2 pointer to pointer two (const uintptr_t*)
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
*/
-int cx_cmp_uintptr(
- void const *ptr1,
- void const *ptr2
-);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_uintptr(const void *ptr1, const void *ptr2);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nodiscard
+cx_attr_export
+int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2);
+
+/**
+ * Compares the pointers specified in the arguments without dereferencing.
+ *
+ * @param ptr1 pointer one
+ * @param ptr2 pointer two
+ * @retval -1 if the left argument is less than the right argument
+ * @retval 0 if both arguments are equal
+ * @retval 1 if the left argument is greater than the right argument
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+int cx_cmp_ptr(const void *ptr1, const void *ptr2);
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file hash_key.h
- * \brief Interface for map implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file hash_key.h
+ * @brief Interface for map implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#define UCX_HASH_KEY_H
#include "common.h"
+#include "string.h"
#ifdef __cplusplus
extern "C" {
/** Internal structure for a key within a hash map. */
struct cx_hash_key_s {
/** The key data. */
- void const *data;
+ const void *data;
/**
* The key data length.
*/
typedef struct cx_hash_key_s CxHashKey;
/**
- * Computes a murmur2 32 bit hash.
+ * Computes a murmur2 32-bit hash.
*
- * You need to initialize \c data and \c len in the key struct.
+ * You need to initialize @c data and @c len in the key struct.
* The hash is then directly written to that struct.
*
- * \note If \c data is \c NULL, the hash is defined as 1574210520.
+ * Usually you should not need this function.
+ * Use cx_hash_key(), instead.
+ *
+ * @note If @c data is @c NULL, the hash is defined as 1574210520.
*
* @param key the key, the hash shall be computed for
+ * @see cx_hash_key()
*/
+cx_attr_nonnull
+cx_attr_export
void cx_hash_murmur(CxHashKey *key);
/**
* @param str the string
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
-CxHashKey cx_hash_key_str(char const *str);
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_export
+CxHashKey cx_hash_key_str(const char *str);
/**
* Computes a hash key from a byte array.
* @param len the length
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
+cx_attr_export
CxHashKey cx_hash_key_bytes(
- unsigned char const *bytes,
+ const unsigned char *bytes,
size_t len
);
* @param len the length of object in memory
* @return the hash key
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
+cx_attr_export
CxHashKey cx_hash_key(
- void const *obj,
+ const void *obj,
size_t len
);
* @param str the string
* @return the hash key
*/
-#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+cx_attr_nodiscard
+static inline CxHashKey cx_hash_key_cxstr(cxstring str) {
+ return cx_hash_key(str.ptr, str.length);
+}
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str (@c cxstring or @c cxmutstr) the string
+ * @return (@c CxHashKey) the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str))
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file hash_map.h
- * \brief Hash map implementation.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file hash_map.h
+ * @brief Hash map implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_HASH_MAP_H
/**
* Creates a new hash map with the specified number of buckets.
*
- * If \p buckets is zero, an implementation defined default will be used.
+ * If @p buckets is zero, an implementation defined default will be used.
*
- * If \p item_size is CX_STORE_POINTERS, the created map will be created as if
- * cxMapStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of
+ * copies of the added elements.
*
* @note Iterators provided by this hash map implementation provide the remove operation.
* The index value of an iterator is incremented when the iterator advanced without removal.
- * In other words, when the iterator is finished, \c index==size .
+ * In other words, when the iterator is finished, @c index==size .
*
* @param allocator the allocator to use
+ * (if @c NULL, the cxDefaultAllocator will be used)
* @param itemsize the size of one element
* @param buckets the initial number of buckets in this hash map
* @return a pointer to the new hash map
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxMapFree, 1)
+cx_attr_export
CxMap *cxHashMapCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
size_t itemsize,
size_t buckets
);
/**
* Creates a new hash map with a default number of buckets.
*
- * If \p item_size is CX_STORE_POINTERS, the created map will be created as if
- * cxMapStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created map stores pointers instead of
+ * copies of the added elements.
*
* @note Iterators provided by this hash map implementation provide the remove operation.
* The index value of an iterator is incremented when the iterator advanced without removal.
- * In other words, when the iterator is finished, \c index==size .
+ * In other words, when the iterator is finished, @c index==size .
*
- * @param itemsize the size of one element
- * @return a pointer to the new hash map
+ * @param itemsize (@c size_t) the size of one element
+ * @return (@c CxMap*) a pointer to the new hash map
*/
-#define cxHashMapCreateSimple(itemsize) \
- cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+#define cxHashMapCreateSimple(itemsize) cxHashMapCreate(NULL, itemsize, 0)
/**
* Increases the number of buckets, if necessary.
*
- * The load threshold is \c 0.75*buckets. If the element count exceeds the load
+ * The load threshold is @c 0.75*buckets. If the element count exceeds the load
* threshold, the map will be rehashed. Otherwise, no action is performed and
* this function simply returns 0.
*
* @note If the specified map is not a hash map, the behavior is undefined.
*
* @param map the map to rehash
- * @return zero on success, non-zero if a memory allocation error occurred
+ * @retval zero success
+ * @retval non-zero if a memory allocation error occurred
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxMapRehash(CxMap *map);
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file iterator.h
- * \brief Interface for iterator implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file iterator.h
+ * @brief Interface for iterator implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_ITERATOR_H
#include "common.h"
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/**
- * The base of mutating and non-mutating iterators.
+ * Common data for all iterators.
*/
struct cx_iterator_base_s {
/**
- * True iff the iterator points to valid data.
+ * True if the iterator points to valid data.
*/
- __attribute__ ((__nonnull__))
- bool (*valid)(void const *);
+ bool (*valid)(const void *);
/**
* Returns a pointer to the current element.
*
* When valid returns false, the behavior of this function is undefined.
*/
- __attribute__ ((__nonnull__))
- void *(*current)(void const *);
+ void *(*current)(const void *);
/**
* Original implementation in case the function needs to be wrapped.
*/
- __attribute__ ((__nonnull__))
- void *(*current_impl)(void const *);
+ void *(*current_impl)(const void *);
/**
* Advances the iterator.
*
* When valid returns false, the behavior of this function is undefined.
*/
- __attribute__ ((__nonnull__))
void (*next)(void *);
-
- /**
- * Flag current element for removal, if possible.
- *
- * When valid returns false, the behavior of this function is undefined.
- */
- __attribute__ ((__nonnull__))
- bool (*flag_removal)(void *);
-
/**
* Indicates whether this iterator may remove elements.
*/
bool mutating;
-
/**
* Internal flag for removing the current element when advancing.
*/
};
/**
- * Internal iterator struct - use CxMutIterator.
+ * Convenience type definition for the base structure of an iterator.
+ * @see #CX_ITERATOR_BASE
*/
-struct cx_mut_iterator_s {
-
- /**
- * The base properties of this iterator.
- */
- struct cx_iterator_base_s base;
-
- /**
- * Handle for the current element, if required.
- */
- void *elem_handle;
-
- /**
- * Handle for the source collection, if any.
- */
- void *src_handle;
-
- /**
- * Field for storing a key-value pair.
- * May be used by iterators that iterate over k/v-collections.
- */
- struct {
- /**
- * A pointer to the key.
- */
- void const *key;
- /**
- * A pointer to the value.
- */
- void *value;
- } kv_data;
-
- /**
- * Field for storing a slot number.
- * May be used by iterators that iterate over multi-bucket collections.
- */
- size_t slot;
-
- /**
- * If the iterator is position-aware, contains the index of the element in the underlying collection.
- * Otherwise, this field is usually uninitialized.
- */
- size_t index;
-};
+typedef struct cx_iterator_base_s CxIteratorBase;
/**
- * Mutating iterator value type.
- *
- * An iterator points to a certain element in an (possibly unbounded) chain of elements.
- * Iterators that are based on collections (which have a defined "first" element), are supposed
- * to be "position-aware", which means that they keep track of the current index within the collection.
- *
- * @note Objects that are pointed to by an iterator are mutable through that iterator. However, if the
- * iterator is based on a collection and the underlying collection is mutated by other means than this iterator
- * (e.g. elements added or removed), the iterator becomes invalid (regardless of what cxIteratorValid() returns)
- * and MUST be re-obtained from the collection.
- *
- * @see CxIterator
+ * Declares base attributes for an iterator.
+ * Must be the first member of an iterator structure.
*/
-typedef struct cx_mut_iterator_s CxMutIterator;
+#define CX_ITERATOR_BASE struct cx_iterator_base_s base
/**
* Internal iterator struct - use CxIterator.
*/
struct cx_iterator_s {
-
/**
- * The base properties of this iterator.
+ * Inherited common data for all iterators.
*/
- struct cx_iterator_base_s base;
+ CX_ITERATOR_BASE;
/**
- * Handle for the current element, if required.
+ * Handle for the current element.
*/
void *elem_handle;
/**
* Handle for the source collection, if any.
*/
- void const *src_handle;
-
- /**
- * Field for storing a key-value pair.
- * May be used by iterators that iterate over k/v-collections.
- */
- struct {
+ union {
/**
- * A pointer to the key.
+ * Access for mutating iterators.
*/
- void const *key;
+ void *m;
/**
- * A pointer to the value.
+ * Access for normal iterators.
*/
- void *value;
- } kv_data;
-
- /**
- * Field for storing a slot number.
- * May be used by iterators that iterate over multi-bucket collections.
- */
- size_t slot;
+ const void *c;
+ } src_handle;
/**
* If the iterator is position-aware, contains the index of the element in the underlying collection.
* Otherwise, this field is usually uninitialized.
*/
size_t index;
+
+ /**
+ * The size of an individual element.
+ */
+ size_t elem_size;
+
+ /**
+ * May contain the total number of elements, if known.
+ * Shall be set to @c SIZE_MAX when the total number is unknown during iteration.
+ */
+ size_t elem_count;
};
/**
- * Iterator value type.
+ * Iterator type.
+ *
* An iterator points to a certain element in a (possibly unbounded) chain of elements.
* Iterators that are based on collections (which have a defined "first" element), are supposed
* to be "position-aware", which means that they keep track of the current index within the collection.
*
* @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
- * this iterator cannot mutate the collection itself (add or remove elements) and any mutation of the
- * collection by other means makes this iterator invalid (regardless of what cxIteratorValid() returns).
- *
- * @see CxMutIterator
+ * any concurrent mutation of the collection other than by this iterator makes this iterator invalid,
+ * and it must not be used anymore.
*/
typedef struct cx_iterator_s CxIterator;
/**
* Checks if the iterator points to valid data.
*
- * This is especially false for past-the-end iterators.
- *
* @param iter the iterator
- * @return true iff the iterator points to valid data
+ * @retval true if the iterator points to valid data
+ * @retval false if the iterator already moved past the end
*/
#define cxIteratorValid(iter) (iter).base.valid(&(iter))
*
* @param iter the iterator
* @return a pointer to the current element
+ * @see cxIteratorValid()
*/
#define cxIteratorCurrent(iter) (iter).base.current(&iter)
#define cxIteratorNext(iter) (iter).base.next(&iter)
/**
- * Flags the current element for removal.
+ * Flags the current element for removal, if this iterator is mutating.
+ *
+ * Does nothing for non-mutating iterators.
*
* @param iter the iterator
- * @return false if this iterator cannot remove the element
*/
-#define cxIteratorFlagRemoval(iter) (iter).base.flag_removal(&iter)
+#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating
+
+/**
+ * Obtains a reference to an arbitrary iterator.
+ *
+ * This is useful for APIs that expect some iterator as an argument.
+ *
+ * @param iter the iterator
+ * @return (@c struct @c cx_iterator_base_s*) a pointer to the iterator
+ */
+#define cxIteratorRef(iter) &((iter).base)
/**
* Loops over an iterator.
+ *
* @param type the type of the elements
* @param elem the name of the iteration variable
* @param iter the iterator
#define cx_foreach(type, elem, iter) \
for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter))
+
+/**
+ * Creates an iterator for the specified plain array.
+ *
+ * The @p array can be @c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns @c false.
+ *
+ * This iterator yields the addresses of the array elements.
+ * If you want to iterator over an array of pointers, you can
+ * use cxIteratorPtr() to create an iterator which directly
+ * yields the stored pointers.
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ * @see cxIteratorPtr()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxIterator(
+ const void *array,
+ size_t elem_size,
+ size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain array.
+ *
+ * While the iterator is in use, the array may only be altered by removing
+ * elements through #cxIteratorFlagRemoval(). Every other change to the array
+ * will bring this iterator to an undefined state.
+ *
+ * When @p remove_keeps_order is set to @c false, removing an element will only
+ * move the last element to the position of the removed element, instead of
+ * moving all subsequent elements by one. Usually, when the order of elements is
+ * not important, this parameter should be set to @c false.
+ *
+ * The @p array can be @c NULL in which case the iterator will be immediately
+ * initialized such that #cxIteratorValid() returns @c false.
+ *
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_size the size of one array element
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order @c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxMutIterator(
+ void *array,
+ size_t elem_size,
+ size_t elem_count,
+ bool remove_keeps_order
+);
+
+/**
+ * Creates an iterator for the specified plain pointer array.
+ *
+ * This iterator assumes that every element in the array is a pointer
+ * and yields exactly those pointers during iteration (while in contrast
+ * an iterator created with cxIterator() would return the addresses
+ * of those pointers within the array).
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_count the number of elements in the array
+ * @return an iterator for the specified array
+ * @see cxIterator()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxIteratorPtr(
+ const void *array,
+ size_t elem_count
+);
+
+/**
+ * Creates a mutating iterator for the specified plain pointer array.
+ *
+ * This is the mutating variant of cxIteratorPtr(). See also
+ * cxMutIterator().
+ *
+ * @param array a pointer to the array (can be @c NULL)
+ * @param elem_count the number of elements in the array
+ * @param remove_keeps_order @c true if the order of elements must be preserved
+ * when removing an element
+ * @return an iterator for the specified array
+ * @see cxMutIterator()
+ * @see cxIteratorPtr()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxMutIteratorPtr(
+ void *array,
+ size_t elem_count,
+ bool remove_keeps_order
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
#endif // UCX_ITERATOR_H
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file json.h
+ * @brief Interface for parsing data from JSON files.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_JSON_H
+#define UCX_JSON_H
+
+#include "common.h"
+#include "allocator.h"
+#include "string.h"
+#include "buffer.h"
+#include "array_list.h"
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * The type of the parsed token.
+ */
+enum cx_json_token_type {
+ /**
+ * No complete token parsed, yet.
+ */
+ CX_JSON_NO_TOKEN,
+ /**
+ * The presumed token contains syntactical errors.
+ */
+ CX_JSON_TOKEN_ERROR,
+ /**
+ * A "begin of array" '[' token.
+ */
+ CX_JSON_TOKEN_BEGIN_ARRAY,
+ /**
+ * A "begin of object" '{' token.
+ */
+ CX_JSON_TOKEN_BEGIN_OBJECT,
+ /**
+ * An "end of array" ']' token.
+ */
+ CX_JSON_TOKEN_END_ARRAY,
+ /**
+ * An "end of object" '}' token.
+ */
+ CX_JSON_TOKEN_END_OBJECT,
+ /**
+ * A colon ':' token separating names and values.
+ */
+ CX_JSON_TOKEN_NAME_SEPARATOR,
+ /**
+ * A comma ',' token separating object entries or array elements.
+ */
+ CX_JSON_TOKEN_VALUE_SEPARATOR,
+ /**
+ * A string token.
+ */
+ CX_JSON_TOKEN_STRING,
+ /**
+ * A number token that can be represented as integer.
+ */
+ CX_JSON_TOKEN_INTEGER,
+ /**
+ * A number token that cannot be represented as integer.
+ */
+ CX_JSON_TOKEN_NUMBER,
+ /**
+ * A literal token.
+ */
+ CX_JSON_TOKEN_LITERAL,
+ /**
+ * A white-space token.
+ */
+ CX_JSON_TOKEN_SPACE
+};
+
+/**
+ * The type of some JSON value.
+ */
+enum cx_json_value_type {
+ /**
+ * Reserved.
+ */
+ CX_JSON_NOTHING, // this allows us to always return non-NULL values
+ /**
+ * A JSON object.
+ */
+ CX_JSON_OBJECT,
+ /**
+ * A JSON array.
+ */
+ CX_JSON_ARRAY,
+ /**
+ * A string.
+ */
+ CX_JSON_STRING,
+ /**
+ * A number that contains an integer.
+ */
+ CX_JSON_INTEGER,
+ /**
+ * A number, not necessarily an integer.
+ */
+ CX_JSON_NUMBER,
+ /**
+ * A literal (true, false, null).
+ */
+ CX_JSON_LITERAL
+};
+
+/**
+ * JSON literal types.
+ */
+enum cx_json_literal {
+ /**
+ * The @c null literal.
+ */
+ CX_JSON_NULL,
+ /**
+ * The @c true literal.
+ */
+ CX_JSON_TRUE,
+ /**
+ * The @c false literal.
+ */
+ CX_JSON_FALSE
+};
+
+/**
+ * Type alias for the token type enum.
+ */
+typedef enum cx_json_token_type CxJsonTokenType;
+/**
+ * Type alias for the value type enum.
+ */
+typedef enum cx_json_value_type CxJsonValueType;
+
+/**
+ * Type alias for the JSON parser interface.
+ */
+typedef struct cx_json_s CxJson;
+
+/**
+ * Type alias for the token struct.
+ */
+typedef struct cx_json_token_s CxJsonToken;
+
+/**
+ * Type alias for the JSON value struct.
+ */
+typedef struct cx_json_value_s CxJsonValue;
+
+/**
+ * Type alias for the JSON array struct.
+ */
+typedef struct cx_json_array_s CxJsonArray;
+/**
+ * Type alias for the JSON object struct.
+ */
+typedef struct cx_json_object_s CxJsonObject;
+/**
+ * Type alias for a JSON string.
+ */
+typedef struct cx_mutstr_s CxJsonString;
+/**
+ * Type alias for a number that can be represented as 64-bit signed integer.
+ */
+typedef int64_t CxJsonInteger;
+/**
+ * Type alias for number that is not an integer.
+ */
+typedef double CxJsonNumber;
+/**
+ * Type alias for a JSON literal.
+ */
+typedef enum cx_json_literal CxJsonLiteral;
+
+/**
+ * Type alias for a key/value pair in a JSON object.
+ */
+typedef struct cx_json_obj_value_s CxJsonObjValue;
+
+/**
+ * JSON array structure.
+ */
+struct cx_json_array_s {
+ /**
+ * The array data.
+ */
+ CX_ARRAY_DECLARE(CxJsonValue*, array);
+};
+
+/**
+ * JSON object structure.
+ */
+struct cx_json_object_s {
+ /**
+ * The key/value entries.
+ */
+ CX_ARRAY_DECLARE(CxJsonObjValue, values);
+ /**
+ * The original indices to reconstruct the order in which the members were added.
+ */
+ size_t *indices;
+};
+
+/**
+ * Structure for a key/value entry in a JSON object.
+ */
+struct cx_json_obj_value_s {
+ /**
+ * The key (or name in JSON terminology) of the value.
+ */
+ cxmutstr name;
+ /**
+ * The value.
+ */
+ CxJsonValue *value;
+};
+
+/**
+ * Structure for a JSON value.
+ */
+struct cx_json_value_s {
+ /**
+ * The allocator with which the value was allocated.
+ *
+ * Required for recursively deallocating memory of objects and arrays.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The type of this value.
+ *
+ * Specifies how the @c value union shall be resolved.
+ */
+ CxJsonValueType type;
+ /**
+ * The value data.
+ */
+ union {
+ /**
+ * The array data if type is #CX_JSON_ARRAY.
+ */
+ CxJsonArray array;
+ /**
+ * The object data if type is #CX_JSON_OBJECT.
+ */
+ CxJsonObject object;
+ /**
+ * The string data if type is #CX_JSON_STRING.
+ */
+ CxJsonString string;
+ /**
+ * The integer if type is #CX_JSON_INTEGER.
+ */
+ CxJsonInteger integer;
+ /**
+ * The number if type is #CX_JSON_NUMBER.
+ */
+ CxJsonNumber number;
+ /**
+ * The literal type if type is #CX_JSON_LITERAL.
+ */
+ CxJsonLiteral literal;
+ } value;
+};
+
+/**
+ * Internally used structure for a parsed token.
+ *
+ * You should never need to use this in your code.
+ */
+struct cx_json_token_s {
+ /**
+ * The token type.
+ */
+ CxJsonTokenType tokentype;
+ /**
+ * True, if the @c content must be passed to cx_strfree().
+ */
+ bool allocated;
+ /**
+ * The token text, if any.
+ *
+ * This is not necessarily set when the token type already
+ * uniquely identifies the content.
+ */
+ cxmutstr content;
+};
+
+/**
+ * The JSON parser interface.
+ */
+struct cx_json_s {
+ /**
+ * The allocator used for produced JSON values.
+ */
+ const CxAllocator *allocator;
+ /**
+ * The input buffer.
+ */
+ CxBuffer buffer;
+
+ /**
+ * Used internally.
+ *
+ * Remembers the prefix of the last uncompleted token.
+ */
+ CxJsonToken uncompleted;
+
+ /**
+ * A pointer to an intermediate state of the currently parsed value.
+ *
+ * Never access this value manually.
+ */
+ CxJsonValue *parsed;
+
+ /**
+ * A pointer to an intermediate state of a currently parsed object member.
+ *
+ * Never access this value manually.
+ */
+ CxJsonObjValue uncompleted_member;
+
+ /**
+ * State stack.
+ */
+ CX_ARRAY_DECLARE_SIZED(int, states, unsigned);
+
+ /**
+ * Value buffer stack.
+ */
+ CX_ARRAY_DECLARE_SIZED(CxJsonValue*, vbuf, unsigned);
+
+ /**
+ * Internally reserved memory for the state stack.
+ */
+ int states_internal[8];
+
+ /**
+ * Internally reserved memory for the value buffer stack.
+ */
+ CxJsonValue* vbuf_internal[8];
+};
+
+/**
+ * Status codes for the json interface.
+ */
+enum cx_json_status {
+ /**
+ * Everything is fine.
+ */
+ CX_JSON_NO_ERROR,
+ /**
+ * The input buffer does not contain more data.
+ */
+ CX_JSON_NO_DATA,
+ /**
+ * The input ends unexpectedly.
+ *
+ * Refill the buffer with cxJsonFill() to complete the json data.
+ */
+ CX_JSON_INCOMPLETE_DATA,
+ /**
+ * Not used as a status and never returned by any function.
+ *
+ * You can use this enumerator to check for all "good" status results
+ * by checking if the status is less than @c CX_JSON_OK.
+ *
+ * A "good" status means, that you can refill data and continue parsing.
+ */
+ CX_JSON_OK,
+ /**
+ * The input buffer has never been filled.
+ */
+ CX_JSON_NULL_DATA,
+ /**
+ * Allocating memory for the internal buffer failed.
+ */
+ CX_JSON_BUFFER_ALLOC_FAILED,
+ /**
+ * Allocating memory for a json value failed.
+ */
+ CX_JSON_VALUE_ALLOC_FAILED,
+ /**
+ * A number value is incorrectly formatted.
+ */
+ CX_JSON_FORMAT_ERROR_NUMBER,
+ /**
+ * The tokenizer found something unexpected.
+ */
+ CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN
+};
+
+/**
+ * Typedef for the json status enum.
+ */
+typedef enum cx_json_status CxJsonStatus;
+
+/**
+ * The JSON writer settings.
+ */
+struct cx_json_writer_s {
+ /**
+ * Set true to enable pretty output.
+ */
+ bool pretty;
+ /**
+ * Set false to output the members in the order in which they were added.
+ */
+ bool sort_members;
+ /**
+ * The maximum number of fractional digits in a number value.
+ * The default value is 6 and values larger than 15 are reduced to 15.
+ * Note, that the actual number of digits may be lower, depending on the concrete number.
+ */
+ uint8_t frac_max_digits;
+ /**
+ * Set true to use spaces instead of tab characters.
+ * Indentation is only used in pretty output.
+ */
+ bool indent_space;
+ /**
+ * If @c indent_space is true, this is the number of spaces per tab.
+ * Indentation is only used in pretty output.
+ */
+ uint8_t indent;
+ /**
+ * Set true to enable escaping of the slash character (solidus).
+ */
+ bool escape_slash;
+};
+
+/**
+ * Typedef for the json writer.
+ */
+typedef struct cx_json_writer_s CxJsonWriter;
+
+/**
+ * Creates a default writer configuration for compact output.
+ *
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonWriter cxJsonWriterCompact(void);
+
+/**
+ * Creates a default writer configuration for pretty output.
+ *
+ * @param use_spaces false if you want tabs, true if you want four spaces instead
+ * @return new JSON writer settings
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonWriter cxJsonWriterPretty(bool use_spaces);
+
+/**
+ * Writes a JSON value to a buffer or stream.
+ *
+ * This function blocks until either all data is written, or an error occurs.
+ * The write operation is not atomic in the sense that it might happen
+ * that the data is only partially written when an error occurs with no
+ * way to indicate how much data was written.
+ * To avoid this problem, you can use a CxBuffer as @p target which is
+ * unlikely to fail a write operation and either use the buffer's flush
+ * feature to relay the data or use the data in the buffer manually to
+ * write it to the actual target.
+ *
+ * @param target the buffer or stream where to write to
+ * @param value the value that shall be written
+ * @param wfunc the write function to use
+ * @param settings formatting settings (or @c NULL to use a compact default)
+ * @retval zero success
+ * @retval non-zero when no or not all data could be written
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+cx_attr_export
+int cxJsonWrite(
+ void* target,
+ const CxJsonValue* value,
+ cx_write_func wfunc,
+ const CxJsonWriter* settings
+);
+
+/**
+ * Initializes the json interface.
+ *
+ * @param json the json interface
+ * @param allocator the allocator that shall be used for the produced values
+ * @see cxJsonDestroy()
+ */
+cx_attr_nonnull_arg(1)
+cx_attr_export
+void cxJsonInit(CxJson *json, const CxAllocator *allocator);
+
+/**
+ * Destroys the json interface.
+ *
+ * @param json the json interface
+ * @see cxJsonInit()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxJsonDestroy(CxJson *json);
+
+/**
+ * Destroys and re-initializes the json interface.
+ *
+ * You might want to use this, to reset the parser after
+ * encountering a syntax error.
+ *
+ * @param json the json interface
+ */
+cx_attr_nonnull
+static inline void cxJsonReset(CxJson *json) {
+ const CxAllocator *allocator = json->allocator;
+ cxJsonDestroy(json);
+ cxJsonInit(json, allocator);
+}
+
+/**
+ * Fills the input buffer.
+ *
+ * @remark The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param buf the source buffer
+ * @param len the length of the source buffer
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonFilln(CxJson *json, const char *buf, size_t len);
+
+#ifdef __cplusplus
+} // extern "C"
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+ CxJson *json,
+ cxstring str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+static inline int cxJsonFill(
+ CxJson *json,
+ cxmutstr str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxJsonFill(
+ CxJson *json,
+ const char *str
+) {
+ return cxJsonFilln(json, str, strlen(str));
+}
+
+extern "C" {
+#else // __cplusplus
+/**
+ * Fills the input buffer.
+ *
+ * The JSON interface tries to avoid copying the input data.
+ * When you use this function and cxJsonNext() interleaving,
+ * no copies are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxJsonNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param json the json interface
+ * @param str the source string
+ * @retval zero success
+ * @retval non-zero internal allocation error
+ * @see cxJsonFilln()
+ */
+#define cxJsonFill(json, str) _Generic((str), \
+ cxstring: cx_json_fill_cxstr, \
+ cxmutstr: cx_json_fill_mutstr, \
+ char*: cx_json_fill_str, \
+ const char*: cx_json_fill_str) \
+ (json, str)
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_cxstr(
+ CxJson *json,
+ cxstring str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+static inline int cx_json_fill_mutstr(
+ CxJson *json,
+ cxmutstr str
+) {
+ return cxJsonFilln(json, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxJsonFill()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_json_fill_str(
+ CxJson *json,
+ const char *str
+) {
+ return cxJsonFilln(json, str, strlen(str));
+}
+#endif
+
+/**
+ * Creates a new (empty) JSON object.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON object or @c NULL if allocation fails
+ * @see cxJsonObjPutObj()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator);
+
+/**
+ * Creates a new (empty) JSON array.
+ *
+ * @param allocator the allocator to use
+ * @return the new JSON array or @c NULL if allocation fails
+ * @see cxJsonObjPutArr()
+ * @see cxJsonArrAddValues()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator);
+
+/**
+ * Creates a new JSON number value.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutNumber()
+ * @see cxJsonArrAddNumbers()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num);
+
+/**
+ * Creates a new JSON number value based on an integer.
+ *
+ * @param allocator the allocator to use
+ * @param num the numeric value
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutInteger()
+ * @see cxJsonArrAddIntegers()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateString()
+ * @see cxJsonObjPutString()
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nodiscard
+cx_attr_nonnull_arg(2)
+cx_attr_cstr_arg(2)
+cx_attr_export
+CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str);
+
+/**
+ * Creates a new JSON string.
+ *
+ * @param allocator the allocator to use
+ * @param str the string data
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonCreateCxString()
+ * @see cxJsonObjPutCxString()
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str);
+
+/**
+ * Creates a new JSON literal.
+ *
+ * @param allocator the allocator to use
+ * @param lit the type of literal
+ * @return the new JSON value or @c NULL if allocation fails
+ * @see cxJsonObjPutLiteral()
+ * @see cxJsonArrAddLiterals()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit);
+
+/**
+ * Adds number values to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count);
+
+/**
+ * Adds number values, of which all are integers, to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param num the array of values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddCxStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count);
+
+/**
+ * Adds strings to a JSON array.
+ *
+ * The strings will be copied with the allocator of the array.
+ *
+ * @param arr the JSON array
+ * @param str the array of strings
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ * @see cxJsonArrAddStrings()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count);
+
+/**
+ * Adds literals to a JSON array.
+ *
+ * @param arr the JSON array
+ * @param lit the array of literal types
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count);
+
+/**
+ * Add arbitrary values to a JSON array.
+ *
+ * @attention In contrast to all other add functions, this function adds the values
+ * directly to the array instead of copying them.
+ *
+ * @param arr the JSON array
+ * @param val the values
+ * @param count the number of elements
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count);
+
+/**
+ * Adds or replaces a value within a JSON object.
+ *
+ * The value will be directly added and not copied.
+ *
+ * @note If a value with the specified @p name already exists,
+ * it will be (recursively) freed with its own allocator.
+ *
+ * @param obj the JSON object
+ * @param name the name of the value
+ * @param child the value
+ * @retval zero success
+ * @retval non-zero allocation failure
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child);
+
+/**
+ * Creates a new JSON object and adds it to an existing object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateObj()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON array and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateArr()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name);
+
+/**
+ * Creates a new JSON number and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateNumber()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num);
+
+/**
+ * Creates a new JSON number, based on an integer, and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param num the numeric value
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateInteger()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateString()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(3)
+cx_attr_export
+CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str);
+
+/**
+ * Creates a new JSON string and adds it to an object.
+ *
+ * The string data is copied.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param str the string data
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateCxString()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str);
+
+/**
+ * Creates a new JSON literal and adds it to an object.
+ *
+ * @param obj the target JSON object
+ * @param name the name of the new value
+ * @param lit the type of literal
+ * @return the new value or @c NULL if allocation fails
+ * @see cxJsonObjPut()
+ * @see cxJsonCreateLiteral()
+ */
+cx_attr_nonnull
+cx_attr_export
+CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit);
+
+/**
+ * Recursively deallocates the memory of a JSON value.
+ *
+ * @remark The type of each deallocated value will be changed
+ * to #CX_JSON_NOTHING and values of such type will be skipped
+ * by the de-allocation. That means, this function protects
+ * you from double-frees when you are accidentally freeing
+ * a nested value and then the parent value (or vice versa).
+ *
+ * @param value the value
+ */
+cx_attr_export
+void cxJsonValueFree(CxJsonValue *value);
+
+/**
+ * Tries to obtain the next JSON value.
+ *
+ * Before this function can be called, the input buffer needs
+ * to be filled with cxJsonFill().
+ *
+ * When this function returns #CX_JSON_INCOMPLETE_DATA, you can
+ * add the missing data with another invocation of cxJsonFill()
+ * and then repeat the call to cxJsonNext().
+ *
+ * @param json the json interface
+ * @param value a pointer where the next value shall be stored
+ * @retval CX_JSON_NO_ERROR successfully retrieve the @p value
+ * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from
+ * @retval CX_JSON_INCOMPLETE_DATA an incomplete value was read
+ * and more data needs to be filled
+ * @retval CX_JSON_NULL_DATA the buffer was never initialized
+ * @retval CX_JSON_BUFFER_ALLOC_FAILED allocating internal buffer space failed
+ * @retval CX_JSON_VALUE_ALLOC_FAILED allocating memory for a CxJsonValue failed
+ * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number
+ * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error
+ */
+cx_attr_nonnull
+cx_attr_access_w(2)
+cx_attr_export
+CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value);
+
+/**
+ * Checks if the specified value is a JSON object.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON object
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsObject(const CxJsonValue *value) {
+ return value->type == CX_JSON_OBJECT;
+}
+
+/**
+ * Checks if the specified value is a JSON array.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON array
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsArray(const CxJsonValue *value) {
+ return value->type == CX_JSON_ARRAY;
+}
+
+/**
+ * Checks if the specified value is a string.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a string
+ * @retval false otherwise
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsString(const CxJsonValue *value) {
+ return value->type == CX_JSON_STRING;
+}
+
+/**
+ * Checks if the specified value is a JSON number.
+ *
+ * This function will return true for both floating point and
+ * integer numbers.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON number
+ * @retval false otherwise
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNumber(const CxJsonValue *value) {
+ return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is an integer number.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is an integer number
+ * @retval false otherwise
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsInteger(const CxJsonValue *value) {
+ return value->type == CX_JSON_INTEGER;
+}
+
+/**
+ * Checks if the specified value is a JSON literal.
+ *
+ * JSON literals are @c true, @c false, and @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is a JSON literal
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ * @see cxJsonIsNull()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsLiteral(const CxJsonValue *value) {
+ return value->type == CX_JSON_LITERAL;
+}
+
+/**
+ * Checks if the specified value is a Boolean literal.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is either @c true or @c false
+ * @retval false otherwise
+ * @see cxJsonIsTrue()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsBool(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL;
+}
+
+/**
+ * Checks if the specified value is @c true.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsFalse(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c true
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsFalse()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsTrue(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Checks if the specified value is @c false.
+ *
+ * @remark Be advised, that this is not the same as
+ * testing @c !cxJsonIsTrue(v).
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c false
+ * @retval false otherwise
+ * @see cxJsonIsBool()
+ * @see cxJsonIsTrue()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsFalse(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE;
+}
+
+/**
+ * Checks if the specified value is @c null.
+ *
+ * @param value a pointer to the value
+ * @retval true the value is @c null
+ * @retval false otherwise
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonIsNull(const CxJsonValue *value) {
+ return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL;
+}
+
+/**
+ * Obtains a C string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as C string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline char *cxJsonAsString(const CxJsonValue *value) {
+ return value->value.string.ptr;
+}
+
+/**
+ * Obtains a UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxstring cxJsonAsCxString(const CxJsonValue *value) {
+ return cx_strcast(value->value.string);
+}
+
+/**
+ * Obtains a mutable UCX string from the given JSON value.
+ *
+ * If the @p value is not a string, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as mutable UCX string
+ * @see cxJsonIsString()
+ */
+cx_attr_nonnull
+static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) {
+ return value->value.string;
+}
+
+/**
+ * Obtains a double-precision floating point value from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ */
+cx_attr_nonnull
+static inline double cxJsonAsDouble(const CxJsonValue *value) {
+ if (value->type == CX_JSON_INTEGER) {
+ return (double) value->value.integer;
+ } else {
+ return value->value.number;
+ }
+}
+
+/**
+ * Obtains a 64-bit signed integer from the given JSON value.
+ *
+ * If the @p value is not a JSON number, the behavior is undefined.
+ * If it is a JSON number, but not an integer, the value will be
+ * converted to an integer, possibly losing precision.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsNumber()
+ * @see cxJsonIsInteger()
+ */
+cx_attr_nonnull
+static inline int64_t cxJsonAsInteger(const CxJsonValue *value) {
+ if (value->type == CX_JSON_INTEGER) {
+ return value->value.integer;
+ } else {
+ return (int64_t) value->value.number;
+ }
+}
+
+/**
+ * Obtains a Boolean value from the given JSON value.
+ *
+ * If the @p value is not a JSON literal, the behavior is undefined.
+ * The @c null literal is interpreted as @c false.
+ *
+ * @param value the JSON value
+ * @return the value represented as double
+ * @see cxJsonIsLiteral()
+ */
+cx_attr_nonnull
+static inline bool cxJsonAsBool(const CxJsonValue *value) {
+ return value->value.literal == CX_JSON_TRUE;
+}
+
+/**
+ * Returns the size of a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return the size of the array
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+static inline size_t cxJsonArrSize(const CxJsonValue *value) {
+ return value->value.array.array_size;
+}
+
+/**
+ * Returns an element from a JSON array.
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * This function guarantees to return a value. If the index is
+ * out of bounds, the returned value will be of type
+ * #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON value
+ * @param index the index in the array
+ * @return the value at the specified index
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_export
+CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index);
+
+/**
+ * Returns an iterator over the JSON array elements.
+ *
+ * The iterator yields values of type @c CxJsonValue* .
+ *
+ * If the @p value is not a JSON array, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the array elements
+ * @see cxJsonIsArray()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxJsonArrIter(const CxJsonValue *value);
+
+/**
+ * Returns an iterator over the JSON object members.
+ *
+ * The iterator yields values of type @c CxJsonObjValue* which
+ * contain the name and value of the member.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * @param value the JSON value
+ * @return an iterator over the object members
+ * @see cxJsonIsObject()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxJsonObjIter(const CxJsonValue *value);
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_export
+CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name);
+
+#ifdef __cplusplus
+} // extern "C"
+
+static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) {
+ return cx_json_obj_get_cxstr(value, name);
+}
+
+static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) {
+ return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) {
+ return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+
+extern "C" {
+#else
+/**
+ * Returns a value corresponding to a key in a JSON object.
+ *
+ * If the @p value is not a JSON object, the behavior is undefined.
+ *
+ * This function guarantees to return a JSON value. If the
+ * object does not contain @p name, the returned JSON value
+ * will be of type #CX_JSON_NOTHING, but never @c NULL.
+ *
+ * @param value the JSON object
+ * @param name the key to look up
+ * @return the value corresponding to the key
+ * @see cxJsonIsObject()
+ */
+#define cxJsonObjGet(value, name) _Generic((name), \
+ cxstring: cx_json_obj_get_cxstr, \
+ cxmutstr: cx_json_obj_get_mutstr, \
+ char*: cx_json_obj_get_str, \
+ const char*: cx_json_obj_get_str) \
+ (value, name)
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) {
+ return cx_json_obj_get_cxstr(value, cx_strcast(name));
+}
+
+/**
+ * @copydoc cxJsonObjGet()
+ */
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_cstr_arg(2)
+static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) {
+ return cx_json_obj_get_cxstr(value, cx_str(name));
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_JSON_H */
+
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file linked_list.h
- * \brief Linked list implementation.
- * \details Also provides several low-level functions for custom linked list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file linked_list.h
+ * @brief Linked list implementation.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_LINKED_LIST_H
#endif
/**
- * Set this flag to true, if you want to disable the use of SBO for
- * linked list swap operations.
- */
-extern bool CX_DISABLE_LINKED_LIST_SWAP_SBO;
-
-/**
- * Allocates a linked list for storing elements with \p item_size bytes each.
+ * Allocates a linked list for storing elements with @p elem_size bytes each.
*
- * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
+ * copies of the added elements and the compare function will be automatically set
+ * to cx_cmp_ptr(), if none is given.
*
* @param allocator the allocator for allocating the list nodes
- * (if \c NULL the cxDefaultAllocator will be used)
+ * (if @c NULL, the cxDefaultAllocator will be used)
* @param comparator the comparator for the elements
- * (if \c NULL sort and find functions will not work)
- * @param item_size the size of each element in bytes
+ * (if @c NULL, and the list is not storing pointers, sort and find
+ * functions will not work)
+ * @param elem_size the size of each element in bytes
* @return the created list
*/
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxListFree, 1)
+cx_attr_export
CxList *cxLinkedListCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cx_compare_func comparator,
- size_t item_size
+ size_t elem_size
);
/**
- * Allocates a linked list for storing elements with \p item_size bytes each.
+ * Allocates a linked list for storing elements with @p elem_size bytes each.
*
* The list will use cxDefaultAllocator and no comparator function. If you want
* to call functions that need a comparator, you must either set one immediately
* after list creation or use cxLinkedListCreate().
*
- * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
- * cxListStorePointers() was called immediately after creation.
+ * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of
+ * copies of the added elements and the compare function will be automatically set
+ * to cx_cmp_ptr().
*
- * @param item_size the size of each element in bytes
- * @return the created list
+ * @param elem_size (@c size_t) the size of each element in bytes
+ * @return (@c CxList*) the created list
*/
-#define cxLinkedListCreateSimple(item_size) \
- cxLinkedListCreate(NULL, NULL, item_size)
+#define cxLinkedListCreateSimple(elem_size) \
+ cxLinkedListCreate(NULL, NULL, elem_size)
/**
* Finds the node at a certain index.
*
* This function can be used to start at an arbitrary position within the list.
- * If the search index is large than the start index, \p loc_advance must denote
- * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * If the search index is large than the start index, @p loc_advance must denote
+ * the location of some sort of @c next pointer (i.e. a pointer to the next node).
* But it is also possible that the search index is smaller than the start index
* (e.g. in cases where traversing a list backwards is faster) in which case
- * \p loc_advance must denote the location of some sort of \c prev pointer
+ * @p loc_advance must denote the location of some sort of @c prev pointer
* (i.e. a pointer to the previous node).
*
* @param start a pointer to the start node
* @param index the search index
* @return the node found at the specified index
*/
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
void *cx_linked_list_at(
- void const *start,
+ const void *start,
size_t start_index,
ptrdiff_t loc_advance,
size_t index
-) __attribute__((__nonnull__));
+);
/**
- * Finds the index of an element within a linked list.
+ * Finds the node containing an element within a linked list.
*
* @param start a pointer to the start node
* @param loc_advance the location of the pointer to advance
- * @param loc_data the location of the \c data pointer within your node struct
- * @param cmp_func a compare function to compare \p elem against the node data
+ * @param loc_data the location of the @c data pointer within your node struct
+ * @param cmp_func a compare function to compare @p elem against the node data
* @param elem a pointer to the element to find
- * @return the index of the element or a negative value if it could not be found
+ * @param found_index an optional pointer where the index of the found node
+ * (given that @p start has index 0) is stored
+ * @return the index of the element, if found - unspecified if not found
*/
-ssize_t cx_linked_list_find(
- void const *start,
+cx_attr_nonnull_arg(1, 4, 5)
+cx_attr_export
+void *cx_linked_list_find(
+ const void *start,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func,
- void const *elem
-) __attribute__((__nonnull__));
+ const void *elem,
+ size_t *found_index
+);
/**
* Finds the first node in a linked list.
*
- * The function starts with the pointer denoted by \p node and traverses the list
+ * The function starts with the pointer denoted by @p node and traverses the list
* along a prev pointer whose location within the node struct is
- * denoted by \p loc_prev.
+ * denoted by @p loc_prev.
*
* @param node a pointer to a node in the list
- * @param loc_prev the location of the \c prev pointer
+ * @param loc_prev the location of the @c prev pointer
* @return a pointer to the first node
*/
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_export
void *cx_linked_list_first(
- void const *node,
+ const void *node,
ptrdiff_t loc_prev
-) __attribute__((__nonnull__));
+);
/**
* Finds the last node in a linked list.
*
- * The function starts with the pointer denoted by \p node and traverses the list
+ * The function starts with the pointer denoted by @p node and traverses the list
* along a next pointer whose location within the node struct is
- * denoted by \p loc_next.
+ * denoted by @p loc_next.
*
* @param node a pointer to a node in the list
- * @param loc_next the location of the \c next pointer
+ * @param loc_next the location of the @c next pointer
* @return a pointer to the last node
*/
+cx_attr_nonnull
+cx_attr_returns_nonnull
+cx_attr_export
void *cx_linked_list_last(
- void const *node,
+ const void *node,
ptrdiff_t loc_next
-) __attribute__((__nonnull__));
+);
/**
* Finds the predecessor of a node in case it is not linked.
*
- * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ * @remark If @p node is not contained in the list starting with @p begin, the behavior is undefined.
*
* @param begin the node where to start the search
- * @param loc_next the location of the \c next pointer
+ * @param loc_next the location of the @c next pointer
* @param node the successor of the node to find
- * @return the node or \c NULL if \p node has no predecessor
+ * @return the node or @c NULL if @p node has no predecessor
*/
+cx_attr_nonnull
+cx_attr_export
void *cx_linked_list_prev(
- void const *begin,
+ const void *begin,
ptrdiff_t loc_next,
- void const *node
-) __attribute__((__nonnull__));
+ const void *node
+);
/**
* Adds a new node to a linked list.
* The node must not be part of any list already.
*
- * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param new_node a pointer to the node that shall be appended
*/
+cx_attr_nonnull_arg(5)
+cx_attr_export
void cx_linked_list_add(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *new_node
-) __attribute__((__nonnull__(5)));
+);
/**
* Prepends a new node to a linked list.
* The node must not be part of any list already.
*
- * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ * @remark One of the pointers @p begin or @p end may be @c NULL, but not both.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param new_node a pointer to the node that shall be prepended
*/
+cx_attr_nonnull_arg(5)
+cx_attr_export
void cx_linked_list_prepend(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *new_node
-) __attribute__((__nonnull__(5)));
+);
/**
* Links two nodes.
*
- * @param left the new predecessor of \p right
- * @param right the new successor of \p left
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param left the new predecessor of @p right
+ * @param right the new successor of @p left
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
+cx_attr_nonnull
+cx_attr_export
void cx_linked_list_link(
void *left,
void *right,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
-) __attribute__((__nonnull__));
+);
/**
* Unlinks two nodes.
*
* If right is not the successor of left, the behavior is undefined.
*
- * @param left the predecessor of \p right
- * @param right the successor of \p left
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param left the predecessor of @p right
+ * @param right the successor of @p left
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
+cx_attr_nonnull
+cx_attr_export
void cx_linked_list_unlink(
void *left,
void *right,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
-) __attribute__((__nonnull__));
+);
/**
* Inserts a new node after a given node of a linked list.
* The new node must not be part of any list already.
*
- * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
- * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
+ * the @p end pointer to determine the start of the list. Then the new node will be prepended to the list.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
- * @param new_node a pointer to the node that shall be prepended
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the node after which to insert (@c NULL if you want to prepend the node to the list)
+ * @param new_node a pointer to the node that shall be inserted
*/
+cx_attr_nonnull_arg(6)
+cx_attr_export
void cx_linked_list_insert(
void **begin,
void **end,
ptrdiff_t loc_next,
void *node,
void *new_node
-) __attribute__((__nonnull__(6)));
+);
/**
* Inserts a chain of nodes after a given node of a linked list.
* The chain must not be part of any list already.
*
* If you do not explicitly specify the end of the chain, it will be determined by traversing
- * the \c next pointer.
+ * the @c next pointer.
*
- * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
- * the \p end pointer to determine the start of the list. If only the \p end pointer is specified, you also need
- * to provide a valid \p loc_prev location.
+ * @note If you specify @c NULL as the @p node to insert after, this function needs either the @p begin or
+ * the @p end pointer to determine the start of the list. If only the @p end pointer is specified, you also need
+ * to provide a valid @p loc_prev location.
* Then the chain will be prepended to the list.
*
- * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param begin a pointer to the beginning node pointer (if your list has one)
* @param end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param node the node after which to insert (\c NULL to prepend the chain to the list)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the node after which to insert (@c NULL to prepend the chain to the list)
* @param insert_begin a pointer to the first node of the chain that shall be inserted
* @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined)
*/
+cx_attr_nonnull_arg(6)
+cx_attr_export
void cx_linked_list_insert_chain(
void **begin,
void **end,
void *node,
void *insert_begin,
void *insert_end
-) __attribute__((__nonnull__(6)));
+);
+
+/**
+ * Inserts a node into a sorted linked list.
+ * The new node must not be part of any list already.
+ *
+ * If the list starting with the node pointed to by @p begin is not sorted
+ * already, the behavior is undefined.
+ *
+ * @param begin a pointer to the beginning node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param new_node a pointer to the node that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+cx_attr_nonnull_arg(1, 5, 6)
+cx_attr_export
+void cx_linked_list_insert_sorted(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Inserts a chain of nodes into a sorted linked list.
+ * The chain must not be part of any list already.
+ *
+ * If either the list starting with the node pointed to by @p begin or the list
+ * starting with @p insert_begin is not sorted, the behavior is undefined.
+ *
+ * @attention In contrast to cx_linked_list_insert_chain(), the source chain
+ * will be broken and inserted into the target list so that the resulting list
+ * will be sorted according to @p cmp_func. That means, each node in the source
+ * chain may be re-linked with nodes from the target list.
+ *
+ * @param begin a pointer to the beginning node pointer (required)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param insert_begin a pointer to the first node of the chain that shall be inserted
+ * @param cmp_func a compare function that will receive the node pointers
+ */
+cx_attr_nonnull_arg(1, 5, 6)
+cx_attr_export
+void cx_linked_list_insert_sorted_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *insert_begin,
+ cx_compare_func cmp_func
+);
+
+/**
+ * Removes a chain of nodes from the linked list.
+ *
+ * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end)
+ * addresses are provided, the pointers are adjusted accordingly.
+ *
+ * The following combinations of arguments are valid (more arguments are optional):
+ * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ *
+ * @remark The @c next and @c prev pointers of the removed chain are not cleared by this function and may still be used
+ * to traverse to a former adjacent node in the list, or within the chain.
+ *
+ * @param begin a pointer to the beginning node pointer (optional)
+ * @param end a pointer to the end node pointer (optional)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param node the start node of the chain
+ * @param num the number of nodes to remove
+ * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes)
+ */
+cx_attr_nonnull_arg(5)
+cx_attr_export
+size_t cx_linked_list_remove_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ size_t num
+);
/**
* Removes a node from the linked list.
*
- * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end)
* addresses are provided, the pointers are adjusted accordingly.
*
* The following combinations of arguments are valid (more arguments are optional):
- * \li \p loc_next and \p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
- * \li \p loc_next and \p begin (ancestor node is determined by list traversal, overall O(n) performance)
+ * @li @p loc_next and @p loc_prev (ancestor node is determined by using the prev pointer, overall O(1) performance)
+ * @li @p loc_next and @p begin (ancestor node is determined by list traversal, overall O(n) performance)
*
- * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used
+ * @remark The @c next and @c prev pointers of the removed node are not cleared by this function and may still be used
* to traverse to a former adjacent node in the list.
*
- * @param begin a pointer to the begin node pointer (optional)
+ * @param begin a pointer to the beginning node pointer (optional)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
* @param node the node to remove
*/
-void cx_linked_list_remove(
+cx_attr_nonnull_arg(5)
+static inline void cx_linked_list_remove(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *node
-) __attribute__((__nonnull__(5)));
-
+) {
+ cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1);
+}
/**
- * Determines the size of a linked list starting with \p node.
+ * Determines the size of a linked list starting with @p node.
+ *
* @param node the first node
- * @param loc_next the location of the \c next pointer within the node struct
- * @return the size of the list or zero if \p node is \c NULL
+ * @param loc_next the location of the @c next pointer within the node struct
+ * @return the size of the list or zero if @p node is @c NULL
*/
+cx_attr_nodiscard
+cx_attr_export
size_t cx_linked_list_size(
- void const *node,
+ const void *node,
ptrdiff_t loc_next
);
* Sorts a linked list based on a comparison function.
*
* This function can work with linked lists of the following structure:
- * \code
+ * @code
* typedef struct node node;
* struct node {
* node* prev;
* node* next;
* my_payload data;
* }
- * \endcode
+ * @endcode
*
* @note This is a recursive function with at most logarithmic recursion depth.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if not present)
- * @param loc_next the location of a \c next pointer within your node struct (required)
- * @param loc_data the location of the \c data pointer within your node struct
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if not present)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
+ * @param loc_data the location of the @c data pointer within your node struct
* @param cmp_func the compare function defining the sort order
*/
+cx_attr_nonnull_arg(1, 6)
+cx_attr_export
void cx_linked_list_sort(
void **begin,
void **end,
ptrdiff_t loc_next,
ptrdiff_t loc_data,
cx_compare_func cmp_func
-) __attribute__((__nonnull__(1, 6)));
+);
/**
* Compares two lists element wise.
*
- * \note Both list must have the same structure.
+ * @attention Both list must have the same structure.
*
- * @param begin_left the begin of the left list (\c NULL denotes an empty list)
- * @param begin_right the begin of the right list (\c NULL denotes an empty list)
+ * @param begin_left the beginning of the left list (@c NULL denotes an empty list)
+ * @param begin_right the beginning of the right list (@c NULL denotes an empty list)
* @param loc_advance the location of the pointer to advance
- * @param loc_data the location of the \c data pointer within your node struct
+ * @param loc_data the location of the @c data pointer within your node struct
* @param cmp_func the function to compare the elements
- * @return the first non-zero result of invoking \p cmp_func or: negative if the left list is smaller than the
+ * @return the first non-zero result of invoking @p cmp_func or: negative if the left list is smaller than the
* right list, positive if the left list is larger than the right list, zero if both lists are equal.
*/
+cx_attr_nonnull_arg(5)
+cx_attr_export
int cx_linked_list_compare(
- void const *begin_left,
- void const *begin_right,
+ const void *begin_left,
+ const void *begin_right,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func
-) __attribute__((__nonnull__(5)));
+);
/**
* Reverses the order of the nodes in a linked list.
*
- * @param begin a pointer to the begin node pointer (required)
+ * @param begin a pointer to the beginning node pointer (required)
* @param end a pointer to the end node pointer (optional)
- * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
- * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a @c next pointer within your node struct (required)
*/
+cx_attr_nonnull_arg(1)
+cx_attr_export
void cx_linked_list_reverse(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
-) __attribute__((__nonnull__(1)));
+);
#ifdef __cplusplus
} // extern "C"
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file list.h
- * \brief Interface for list implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file list.h
+ * @brief Interface for list implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_LIST_H
* Structure for holding the base data of a list.
*/
struct cx_list_s {
- CX_COLLECTION_MEMBERS
+ /**
+ * Common members for collections.
+ */
+ CX_COLLECTION_BASE;
/**
* The list class definition.
*/
- cx_list_class const *cl;
+ const cx_list_class *cl;
/**
* The actual implementation in case the list class is delegating.
*/
- cx_list_class const *climpl;
+ const cx_list_class *climpl;
};
/**
* Destructor function.
*
* Implementations SHALL invoke the content destructor functions if provided
- * and SHALL deallocate the list memory, if an allocator is provided.
+ * and SHALL deallocate the entire list memory.
*/
- void (*destructor)(struct cx_list_s *list);
+ void (*deallocate)(struct cx_list_s *list);
/**
* Member function for inserting a single element.
- * Implementors SHOULD see to performant implementations for corner cases.
+ * The data pointer may be @c NULL in which case the function shall only allocate memory.
+ * Returns a pointer to the data of the inserted element.
*/
- int (*insert_element)(
+ void *(*insert_element)(
struct cx_list_s *list,
size_t index,
- void const *data
+ const void *data
);
/**
* Member function for inserting multiple elements.
- * Implementors SHOULD see to performant implementations for corner cases.
+ *
+ * @see cx_list_default_insert_array()
*/
size_t (*insert_array)(
struct cx_list_s *list,
size_t index,
- void const *data,
+ const void *data,
+ size_t n
+ );
+
+ /**
+ * Member function for inserting sorted elements into a sorted list.
+ *
+ * @see cx_list_default_insert_sorted()
+ */
+ size_t (*insert_sorted)(
+ struct cx_list_s *list,
+ const void *sorted_data,
size_t n
);
* Member function for inserting an element relative to an iterator position.
*/
int (*insert_iter)(
- struct cx_mut_iterator_s *iter,
- void const *elem,
+ struct cx_iterator_s *iter,
+ const void *elem,
int prepend
);
/**
- * Member function for removing an element.
+ * Member function for removing elements.
+ *
+ * Implementations SHALL check if @p targetbuf is set and copy the elements
+ * to the buffer without invoking any destructor.
+ * When @p targetbuf is not set, the destructors SHALL be invoked.
+ *
+ * The function SHALL return the actual number of elements removed, which
+ * might be lower than @p num when going out of bounds.
*/
- int (*remove)(
+ size_t (*remove)(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
);
/**
/**
* Member function for swapping two elements.
+ *
+ * @see cx_list_default_swap()
*/
int (*swap)(
struct cx_list_s *list,
* Member function for element lookup.
*/
void *(*at)(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index
);
/**
- * Member function for finding an element.
+ * Member function for finding and optionally removing an element.
*/
- ssize_t (*find)(
- struct cx_list_s const *list,
- void const *elem
+ size_t (*find_remove)(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
);
/**
- * Member function for sorting the list in-place.
+ * Member function for sorting the list.
+ *
+ * @see cx_list_default_sort()
*/
void (*sort)(struct cx_list_s *list);
/**
- * Member function for comparing this list to another list of the same type.
+ * Optional member function for comparing this list
+ * to another list of the same type.
+ * If set to @c NULL, comparison won't be optimized.
*/
+ cx_attr_nonnull
int (*compare)(
- struct cx_list_s const *list,
- struct cx_list_s const *other
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
);
/**
* Member function for returning an iterator pointing to the specified index.
*/
struct cx_iterator_s (*iterator)(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index,
bool backward
);
typedef struct cx_list_s CxList;
/**
- * Advises the list to store copies of the objects (default mode of operation).
+ * A shared instance of an empty list.
*
- * Retrieving objects from this list will yield pointers to the copies stored
- * within this list.
+ * Writing to that list is not allowed.
+ *
+ * You can use this is a placeholder for initializing CxList pointers
+ * for which you do not want to reserve memory right from the beginning.
+ */
+cx_attr_export
+extern CxList *const cxEmptyList;
+
+/**
+ * Default implementation of an array insert.
+ *
+ * This function uses the element insert function for each element of the array.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
*
* @param list the list
- * @see cxListStorePointers()
+ * @param index the index where to insert the data
+ * @param data a pointer to the array of data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
*/
-__attribute__((__nonnull__))
-void cxListStoreObjects(CxList *list);
+cx_attr_nonnull
+cx_attr_export
+size_t cx_list_default_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data,
+ size_t n
+);
/**
- * Advises the list to only store pointers to the objects.
+ * Default implementation of a sorted insert.
+ *
+ * This function uses the array insert function to insert consecutive groups
+ * of sorted data.
*
- * Retrieving objects from this list will yield the original pointers stored.
+ * The source data @em must already be sorted wrt. the list's compare function.
*
- * @note This function forcibly sets the element size to the size of a pointer.
- * Invoking this function on a non-empty list that already stores copies of
- * objects is undefined.
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
*
* @param list the list
- * @see cxListStoreObjects()
+ * @param sorted_data a pointer to the array of pre-sorted data to insert
+ * @param n the number of elements to insert
+ * @return the number of elements actually inserted
*/
-__attribute__((__nonnull__))
-void cxListStorePointers(CxList *list);
+cx_attr_nonnull
+cx_attr_export
+size_t cx_list_default_insert_sorted(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
+);
/**
- * Returns true, if this list is storing pointers instead of the actual data.
+ * Default unoptimized sort implementation.
*
- * @param list
- * @return true, if this list is storing pointers
- * @see cxListStorePointers()
+ * This function will copy all data to an array, sort the array with standard
+ * qsort, and then copy the data back to the list memory.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list that shall be sorted
*/
-__attribute__((__nonnull__))
-static inline bool cxListIsStoringPointers(CxList const *list) {
- return list->store_pointer;
-}
+cx_attr_nonnull
+cx_attr_export
+void cx_list_default_sort(struct cx_list_s *list);
+
+/**
+ * Default unoptimized swap implementation.
+ *
+ * Use this in your own list class if you do not want to implement an optimized
+ * version for your list.
+ *
+ * @param list the list in which to swap
+ * @param i index of one element
+ * @param j index of the other element
+ * @retval zero success
+ * @retval non-zero when indices are out of bounds or memory
+ * allocation for the temporary buffer fails
+ */
+cx_attr_nonnull
+cx_attr_export
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j);
+
+/**
+ * Initializes a list struct.
+ *
+ * Only use this function if you are creating your own list implementation.
+ * The purpose of this function is to be called in the initialization code
+ * of your list, to set certain members correctly.
+ *
+ * This is particularly important when you want your list to support
+ * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list
+ * class accordingly and make sure that you can implement your list as if
+ * it was only storing objects and the wrapper will automatically enable
+ * the feature of storing pointers.
+ *
+ * @par Example
+ *
+ * @code
+ * CxList *myCustomListCreate(
+ * const CxAllocator *allocator,
+ * cx_compare_func comparator,
+ * size_t elem_size
+ * ) {
+ * if (allocator == NULL) {
+ * allocator = cxDefaultAllocator;
+ * }
+ *
+ * MyCustomList *list = cxCalloc(allocator, 1, sizeof(MyCustomList));
+ * if (list == NULL) return NULL;
+ *
+ * // initialize
+ * cx_list_init((CxList*)list, &my_custom_list_class,
+ * allocator, comparator, elem_size);
+ *
+ * // ... some more custom stuff ...
+ *
+ * return (CxList *) list;
+ * }
+ * @endcode
+ *
+ * @param list the list to initialize
+ * @param cl the list class
+ * @param allocator the allocator for the elements
+ * @param comparator a compare function for the elements
+ * @param elem_size the size of one element
+ */
+cx_attr_nonnull_arg(1, 2, 3)
+cx_attr_export
+void cx_list_init(
+ struct cx_list_s *list,
+ struct cx_list_class_s *cl,
+ const struct cx_allocator_s *allocator,
+ cx_compare_func comparator,
+ size_t elem_size
+);
/**
* Returns the number of elements currently stored in the list.
* @param list the list
* @return the number of currently stored elements
*/
-__attribute__((__nonnull__))
-static inline size_t cxListSize(CxList const *list) {
- return list->size;
+cx_attr_nonnull
+static inline size_t cxListSize(const CxList *list) {
+ return list->collection.size;
}
/**
*
* @param list the list
* @param elem a pointer to the element to add
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListAddArray()
+ * @see cxListEmplace()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListAdd(
CxList *list,
- void const *elem
+ const void *elem
) {
- return list->cl->insert_element(list, list->size, elem);
+ list->collection.sorted = false;
+ return list->cl->insert_element(list, list->collection.size, elem) == NULL;
}
/**
* This method is more efficient than invoking cxListAdd() multiple times.
*
* If there is not enough memory to add all elements, the returned value is
- * less than \p n.
+ * less than @p n.
*
- * If this list is storing pointers instead of objects \p array is expected to
+ * If this list is storing pointers instead of objects @p array is expected to
* be an array of pointers.
*
* @param list the list
* @param n the number of elements to add
* @return the number of added elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListAddArray(
CxList *list,
- void const *array,
+ const void *array,
size_t n
) {
- return list->cl->insert_array(list, list->size, array, n);
+ list->collection.sorted = false;
+ return list->cl->insert_array(list, list->collection.size, array, n);
}
/**
* Inserts an item at the specified index.
*
- * If \p index equals the list \c size, this is effectively cxListAdd().
+ * If @p index equals the list @c size, this is effectively cxListAdd().
*
* @param list the list
* @param index the index the element shall have
* @param elem a pointer to the element to add
- * @return zero on success, non-zero on memory allocation failure
- * or when the index is out of bounds
+ * @retval zero success
+ * @retval non-zero memory allocation failure or the index is out of bounds
* @see cxListInsertAfter()
* @see cxListInsertBefore()
+ * @see cxListEmplaceAt()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsert(
CxList *list,
size_t index,
- void const *elem
+ const void *elem
+) {
+ list->collection.sorted = false;
+ return list->cl->insert_element(list, index, elem) == NULL;
+}
+
+/**
+ * Allocates memory for an element at the specified index and returns a pointer to that memory.
+ *
+ * @remark When the list is storing pointers, this will return a @c void**.
+ *
+ * @param list the list
+ * @param index the index where to emplace the element
+ * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds
+ * @see cxListEmplace()
+ * @see cxListInsert()
+ */
+cx_attr_nonnull
+static inline void *cxListEmplaceAt(CxList *list, size_t index) {
+ list->collection.sorted = false;
+ return list->cl->insert_element(list, index, NULL);
+}
+
+
+/**
+ * Allocates memory for an element at the end of the list and returns a pointer to that memory.
+ *
+ * @remark When the list is storing pointers, this will return a @c void**.
+ *
+ * @param list the list
+ * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds
+ * @see cxListEmplaceAt()
+ * @see cxListAdd()
+ */
+cx_attr_nonnull
+static inline void *cxListEmplace(CxList *list) {
+ list->collection.sorted = false;
+ return list->cl->insert_element(list, list->collection.size, NULL);
+}
+
+/**
+ * Inserts an item into a sorted list.
+ *
+ * If the list is not sorted already, the behavior is undefined.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @retval zero success
+ * @retval non-zero memory allocation failure
+ */
+cx_attr_nonnull
+static inline int cxListInsertSorted(
+ CxList *list,
+ const void *elem
) {
- return list->cl->insert_element(list, index, elem);
+ list->collection.sorted = true; // guaranteed by definition
+ const void *data = list->collection.store_pointer ? &elem : elem;
+ return list->cl->insert_sorted(list, data, 1) == 0;
}
/**
* Inserts multiple items to the list at the specified index.
- * If \p index equals the list size, this is effectively cxListAddArray().
+ * If @p index equals the list size, this is effectively cxListAddArray().
*
* This method is usually more efficient than invoking cxListInsert()
* multiple times.
*
* If there is not enough memory to add all elements, the returned value is
- * less than \p n.
+ * less than @p n.
*
- * If this list is storing pointers instead of objects \p array is expected to
+ * If this list is storing pointers instead of objects @p array is expected to
* be an array of pointers.
*
* @param list the list
* @param n the number of elements to add
* @return the number of added elements
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline size_t cxListInsertArray(
CxList *list,
size_t index,
- void const *array,
+ const void *array,
size_t n
) {
+ list->collection.sorted = false;
return list->cl->insert_array(list, index, array, n);
}
+/**
+ * Inserts a sorted array into a sorted list.
+ *
+ * This method is usually more efficient than inserting each element separately,
+ * because consecutive chunks of sorted data are inserted in one pass.
+ *
+ * If there is not enough memory to add all elements, the returned value is
+ * less than @p n.
+ *
+ * If this list is storing pointers instead of objects @p array is expected to
+ * be an array of pointers.
+ *
+ * If the list is not sorted already, the behavior is undefined.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+cx_attr_nonnull
+static inline size_t cxListInsertSortedArray(
+ CxList *list,
+ const void *array,
+ size_t n
+) {
+ list->collection.sorted = true; // guaranteed by definition
+ return list->cl->insert_sorted(list, array, n);
+}
+
/**
* Inserts an element after the current location of the specified iterator.
*
* The used iterator remains operational, but all other active iterators should
* be considered invalidated.
*
- * If \p iter is not a list iterator, the behavior is undefined.
- * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ * If @p iter is not a list iterator, the behavior is undefined.
+ * If @p iter is a past-the-end iterator, the new element gets appended to the list.
*
* @param iter an iterator
* @param elem the element to insert
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListInsert()
* @see cxListInsertBefore()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsertAfter(
- CxMutIterator *iter,
- void const *elem
+ CxIterator *iter,
+ const void *elem
) {
- return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 0);
+ CxList* list = (CxList*)iter->src_handle.m;
+ list->collection.sorted = false;
+ return list->cl->insert_iter(iter, elem, 0);
}
/**
* The used iterator remains operational, but all other active iterators should
* be considered invalidated.
*
- * If \p iter is not a list iterator, the behavior is undefined.
- * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ * If @p iter is not a list iterator, the behavior is undefined.
+ * If @p iter is a past-the-end iterator, the new element gets appended to the list.
*
* @param iter an iterator
* @param elem the element to insert
- * @return zero on success, non-zero on memory allocation failure
+ * @retval zero success
+ * @retval non-zero memory allocation failure
* @see cxListInsert()
* @see cxListInsertAfter()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListInsertBefore(
- CxMutIterator *iter,
- void const *elem
+ CxIterator *iter,
+ const void *elem
) {
- return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 1);
+ CxList* list = (CxList*)iter->src_handle.m;
+ list->collection.sorted = false;
+ return list->cl->insert_iter(iter, elem, 1);
}
/**
*
* @param list the list
* @param index the index of the element
- * @return zero on success, non-zero if the index is out of bounds
+ * @retval zero success
+ * @retval non-zero index out of bounds
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListRemove(
CxList *list,
size_t index
) {
- return list->cl->remove(list, index);
+ return list->cl->remove(list, index, 1, NULL) == 0;
}
/**
- * Removes all elements from this list.
+ * Removes and returns the element at the specified index.
+ *
+ * No destructor is called, and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ * If the list is storing pointers, only the pointer is copied to @p targetbuf.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param targetbuf a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero index out of bounds
+ */
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxListRemoveAndGet(
+ CxList *list,
+ size_t index,
+ void *targetbuf
+) {
+ return list->cl->remove(list, index, 1, targetbuf) == 0;
+}
+
+/**
+ * Removes and returns the first element of the list.
+ *
+ * No destructor is called, and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ * If the list is storing pointers, only the pointer is copied to @p targetbuf.
+ *
+ * @param list the list
+ * @param targetbuf a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero list is empty
+ * @see cxListPopFront()
+ * @see cxListRemoveAndGetLast()
+ */
+cx_attr_nonnull
+cx_attr_access_w(2)
+static inline int cxListRemoveAndGetFirst(
+ CxList *list,
+ void *targetbuf
+) {
+ return list->cl->remove(list, 0, 1, targetbuf) == 0;
+}
+
+/**
+ * Removes and returns the first element of the list.
+ *
+ * Alias for cxListRemoveAndGetFirst().
+ *
+ * No destructor is called, and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ * If the list is storing pointers, only the pointer is copied to @p targetbuf.
+ *
+ * @param list (@c CxList*) the list
+ * @param targetbuf (@c void*) a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero list is empty
+ * @see cxListRemoveAndGetFirst()
+ * @see cxListPop()
+ */
+#define cxListPopFront(list, targetbuf) cxListRemoveAndGetFirst((list), (targetbuf))
+
+
+/**
+ * Removes and returns the last element of the list.
+ *
+ * No destructor is called, and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ * If the list is storing pointers, only the pointer is copied to @p targetbuf.
+ *
+ * @param list the list
+ * @param targetbuf a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero list is empty
+ */
+cx_attr_nonnull
+cx_attr_access_w(2)
+static inline int cxListRemoveAndGetLast(
+ CxList *list,
+ void *targetbuf
+) {
+ // note: index may wrap - member function will catch that
+ return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0;
+}
+
+/**
+ * Removes and returns the last element of the list.
+ *
+ * Alias for cxListRemoveAndGetLast().
+ *
+ * No destructor is called, and instead the element is copied to the
+ * @p targetbuf which MUST be large enough to hold the removed element.
+ * If the list is storing pointers, only the pointer is copied to @p targetbuf.
+ *
+ * @param list (@c CxList*) the list
+ * @param targetbuf (@c void*) a buffer where to copy the element
+ * @retval zero success
+ * @retval non-zero list is empty
+ * @see cxListRemoveAndGetLast()
+ * @see cxListPopFront()
+ */
+#define cxListPop(list, targetbuf) cxListRemoveAndGetLast((list), (targetbuf))
+
+/**
+ * Removes multiple element starting at the specified index.
*
* If an element destructor function is specified, it is called for each
+ * element. It is guaranteed that the destructor is called before removing
+ * the element. However, due to possible optimizations, it is neither guaranteed
+ * that the destructors are invoked for all elements before starting to remove
+ * them, nor that the element is removed immediately after the destructor call
+ * before proceeding to the next element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param num the number of elements to remove
+ * @return the actual number of removed elements
+ */
+cx_attr_nonnull
+static inline size_t cxListRemoveArray(
+ CxList *list,
+ size_t index,
+ size_t num
+) {
+ return list->cl->remove(list, index, num, NULL);
+}
+
+/**
+ * Removes and returns multiple elements starting at the specified index.
+ *
+ * No destructor is called, and instead the elements are copied to the
+ * @p targetbuf which MUST be large enough to hold all removed elements.
+ * If the list is storing pointers, @p targetbuf is expected to be an array of pointers.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @param num the number of elements to remove
+ * @param targetbuf a buffer where to copy the elements
+ * @return the actual number of removed elements
+ */
+cx_attr_nonnull
+cx_attr_access_w(4)
+static inline size_t cxListRemoveArrayAndGet(
+ CxList *list,
+ size_t index,
+ size_t num,
+ void *targetbuf
+) {
+ return list->cl->remove(list, index, num, targetbuf);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If element destructor functions are specified, they are called for each
* element before removing them.
*
* @param list the list
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxListClear(CxList *list) {
+ list->collection.sorted = true; // empty lists are always sorted
list->cl->clear(list);
}
/**
* Swaps two items in the list.
*
- * Implementations should only allocate temporary memory for the swap, if
+ * Implementations should only allocate temporary memory for the swap if
* it is necessary.
*
* @param list the list
* @param i the index of the first element
* @param j the index of the second element
- * @return zero on success, non-zero if one of the indices is out of bounds
+ * @retval zero success
+ * @retval non-zero one of the indices is out of bounds,
+ * or the swap needed extra memory, but allocation failed
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxListSwap(
CxList *list,
size_t i,
size_t j
) {
+ list->collection.sorted = false;
return list->cl->swap(list, i, j);
}
/**
* Returns a pointer to the element at the specified index.
*
+ * If the list is storing pointers, returns the pointer stored at the specified index.
+ *
* @param list the list
* @param index the index of the element
- * @return a pointer to the element or \c NULL if the index is out of bounds
+ * @return a pointer to the element or @c NULL if the index is out of bounds
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void *cxListAt(
- CxList *list,
+ const CxList *list,
size_t index
) {
return list->cl->at(list, index);
}
+/**
+ * Returns a pointer to the first element.
+ *
+ * If the list is storing pointers, returns the first pointer stored in the list.
+ *
+ * @param list the list
+ * @return a pointer to the first element or @c NULL if the list is empty
+ */
+cx_attr_nonnull
+static inline void *cxListFirst(const CxList *list) {
+ return list->cl->at(list, 0);
+}
+
+/**
+ * Returns a pointer to the last element.
+ *
+ * If the list is storing pointers, returns the last pointer stored in the list.
+ *
+ * @param list the list
+ * @return a pointer to the last element or @c NULL if the list is empty
+ */
+cx_attr_nonnull
+static inline void *cxListLast(const CxList *list) {
+ return list->cl->at(list, list->collection.size - 1);
+}
+
+/**
+ * Sets the element at the specified index in the list
+ *
+ * @param list the list to set the element in
+ * @param index the index to set the element at
+ * @param elem element to set
+ * @retval zero on success
+ * @retval non-zero when index is out of bounds
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxListSet(
+ CxList *list,
+ size_t index,
+ const void *elem
+);
+
/**
* Returns an iterator pointing to the item at the specified index.
*
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListIteratorAt(
- CxList const *list,
+ const CxList *list,
size_t index
) {
return list->cl->iterator(list, index, false);
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline CxIterator cxListBackwardsIteratorAt(
- CxList const *list,
+ const CxList *list,
size_t index
) {
return list->cl->iterator(list, index, true);
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-CxMutIterator cxListMutIteratorAt(
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxListMutIteratorAt(
CxList *list,
size_t index
);
* @param index the index where the iterator shall point at
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-CxMutIterator cxListMutBackwardsIteratorAt(
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxIterator cxListMutBackwardsIteratorAt(
CxList *list,
size_t index
);
*
* The returned iterator is position-aware.
*
- * If the list is empty, a past-the-end iterator will be returned.
+ * If the list is empty or @c NULL, a past-the-end iterator will be returned.
*
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxIterator cxListIterator(CxList const *list) {
+cx_attr_nodiscard
+static inline CxIterator cxListIterator(const CxList *list) {
+ if (list == NULL) list = cxEmptyList;
return list->cl->iterator(list, 0, false);
}
*
* The returned iterator is position-aware.
*
- * If the list is empty, a past-the-end iterator will be returned.
+ * If the list is empty or @c NULL, a past-the-end iterator will be returned.
*
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxMutIterator cxListMutIterator(CxList *list) {
+cx_attr_nodiscard
+static inline CxIterator cxListMutIterator(CxList *list) {
+ if (list == NULL) list = cxEmptyList;
return cxListMutIteratorAt(list, 0);
}
*
* The returned iterator is position-aware.
*
- * If the list is empty, a past-the-end iterator will be returned.
+ * If the list is empty or @c NULL, a past-the-end iterator will be returned.
*
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxIterator cxListBackwardsIterator(CxList const *list) {
- return list->cl->iterator(list, list->size - 1, true);
+cx_attr_nodiscard
+static inline CxIterator cxListBackwardsIterator(const CxList *list) {
+ if (list == NULL) list = cxEmptyList;
+ return list->cl->iterator(list, list->collection.size - 1, true);
}
/**
*
* The returned iterator is position-aware.
*
- * If the list is empty, a past-the-end iterator will be returned.
+ * If the list is empty or @c NULL, a past-the-end iterator will be returned.
*
* @param list the list
* @return a new iterator
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxMutIterator cxListMutBackwardsIterator(CxList *list) {
- return cxListMutBackwardsIteratorAt(list, list->size - 1);
+cx_attr_nodiscard
+static inline CxIterator cxListMutBackwardsIterator(CxList *list) {
+ if (list == NULL) list = cxEmptyList;
+ return cxListMutBackwardsIteratorAt(list, list->collection.size - 1);
}
/**
- * Returns the index of the first element that equals \p elem.
+ * Returns the index of the first element that equals @p elem.
*
* Determining equality is performed by the list's comparator function.
*
* @param list the list
* @param elem the element to find
- * @return the index of the element or a negative
- * value when the element is not found
+ * @return the index of the element or the size of the list when the element is not found
+ * @see cxListIndexValid()
+ * @see cxListContains()
*/
-__attribute__((__nonnull__))
-static inline ssize_t cxListFind(
- CxList const *list,
- void const *elem
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline size_t cxListFind(
+ const CxList *list,
+ const void *elem
) {
- return list->cl->find(list, elem);
+ return list->cl->find_remove((CxList*)list, elem, false);
}
/**
- * Sorts the list in-place.
+ * Checks, if the list contains the specified element.
*
- * \remark The underlying sort algorithm is implementation defined.
+ * The elements are compared with the list's comparator function.
*
* @param list the list
+ * @param elem the element to find
+ * @retval true if the element is contained
+ * @retval false if the element is not contained
+ * @see cxListFind()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline bool cxListContains(
+ const CxList* list,
+ const void* elem
+) {
+ return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size;
+}
+
+/**
+ * Checks if the specified index is within bounds.
+ *
+ * @param list the list
+ * @param index the index
+ * @retval true if the index is within bounds
+ * @retval false if the index is out of bounds
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline bool cxListIndexValid(const CxList *list, size_t index) {
+ return index < list->collection.size;
+}
+
+/**
+ * Removes and returns the index of the first element that equals @p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find and remove
+ * @return the index of the now removed element or the list size
+ * when the element is not found or could not be removed
+ * @see cxListIndexValid()
+ */
+cx_attr_nonnull
+static inline size_t cxListFindRemove(
+ CxList *list,
+ const void *elem
+) {
+ return list->cl->find_remove(list, elem, true);
+}
+
+/**
+ * Sorts the list.
+ *
+ * @remark The underlying sort algorithm is implementation defined.
+ *
+ * @param list the list
+ */
+cx_attr_nonnull
static inline void cxListSort(CxList *list) {
+ if (list->collection.sorted) return;
list->cl->sort(list);
+ list->collection.sorted = true;
}
/**
*
* @param list the list
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxListReverse(CxList *list) {
+ // still sorted, but not according to the cmp_func
+ list->collection.sorted = false;
list->cl->reverse(list);
}
*
* @param list the list
* @param other the list to compare to
- * @return zero, if both lists are equal element wise,
- * negative if the first list is smaller, positive if the first list is larger
+ * @retval zero both lists are equal element wise
+ * @retval negative the first list is smaller
+ * or the first non-equal element in the first list is smaller
+ * @retval positive the first list is larger
+ * or the first non-equal element in the first list is larger
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
int cxListCompare(
- CxList const *list,
- CxList const *other
+ const CxList *list,
+ const CxList *other
);
/**
* Deallocates the memory of the specified list structure.
*
- * Also calls content a destructor function, depending on the configuration
- * in CxList.content_destructor_type.
- *
- * This function itself is a destructor function for the CxList.
- *
- * @param list the list which shall be destroyed
- */
-__attribute__((__nonnull__))
-void cxListDestroy(CxList *list);
-
-/**
- * A shared instance of an empty list.
+ * Also calls the content destructor functions for each element, if specified.
*
- * Writing to that list is undefined.
+ * @param list the list which shall be freed
*/
-extern CxList * const cxEmptyList;
+cx_attr_export
+void cxListFree(CxList *list);
#ifdef __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file map.h
- * \brief Interface for map implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file map.h
+ * @brief Interface for map implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_MAP_H
/** Type for a map entry. */
typedef struct cx_map_entry_s CxMapEntry;
+/** Type for a map iterator. */
+typedef struct cx_map_iterator_s CxMapIterator;
+
/** Type for map class definitions. */
typedef struct cx_map_class_s cx_map_class;
/** Structure for the UCX map. */
struct cx_map_s {
- CX_COLLECTION_MEMBERS
+ /**
+ * Base attributes.
+ */
+ CX_COLLECTION_BASE;
/** The map class definition. */
cx_map_class *cl;
};
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+ /**
+ * A pointer to the key.
+ */
+ const CxHashKey *key;
+ /**
+ * A pointer to the value.
+ */
+ void *value;
+};
+
/**
* The type of iterator for a map.
*/
CX_MAP_ITERATOR_VALUES
};
+/**
+ * Internal iterator struct - use CxMapIterator.
+ */
+struct cx_map_iterator_s {
+ /**
+ * Inherited common data for all iterators.
+ */
+ CX_ITERATOR_BASE;
+
+ /**
+ * Handle for the source map.
+ */
+ union {
+ /**
+ * Access for mutating iterators.
+ */
+ CxMap *m;
+ /**
+ * Access for normal iterators.
+ */
+ const CxMap *c;
+ } map;
+
+ /**
+ * Handle for the current element.
+ *
+ * @attention Depends on the map implementation, do not assume a type (better: do not use!).
+ */
+ void *elem;
+
+ /**
+ * Reserved memory for a map entry.
+ *
+ * If a map implementation uses an incompatible layout, the iterator needs something
+ * to point to during iteration which @em is compatible.
+ */
+ CxMapEntry entry;
+
+ /**
+ * Field for storing the current slot number.
+ *
+ * (Used internally)
+ */
+ size_t slot;
+
+ /**
+ * Counts the elements successfully.
+ * It usually does not denote a stable index within the map as it would be for arrays.
+ */
+ size_t index;
+
+ /**
+ * The size of a value stored in this map.
+ */
+ size_t elem_size;
+
+ /**
+ * May contain the total number of elements, if known.
+ * Set to @c SIZE_MAX when the total number is unknown during iteration.
+ *
+ * @remark The UCX implementations of #CxMap always know the number of elements they store.
+ */
+ size_t elem_count;
+
+ /**
+ * The type of this iterator.
+ */
+ enum cx_map_iterator_type type;
+};
+
/**
* The class definition for arbitrary maps.
*/
/**
* Deallocates the entire memory.
*/
- __attribute__((__nonnull__))
- void (*destructor)(struct cx_map_s *map);
+ void (*deallocate)(struct cx_map_s *map);
/**
* Removes all elements.
*/
- __attribute__((__nonnull__))
void (*clear)(struct cx_map_s *map);
/**
* Add or overwrite an element.
*/
- __attribute__((__nonnull__))
int (*put)(
CxMap *map,
CxHashKey key,
/**
* Returns an element.
*/
- __attribute__((__nonnull__, __warn_unused_result__))
void *(*get)(
- CxMap const *map,
+ const CxMap *map,
CxHashKey key
);
/**
* Removes an element.
+ *
+ * Implementations SHALL check if @p targetbuf is set and copy the elements
+ * to the buffer without invoking any destructor.
+ * When @p targetbuf is not set, the destructors SHALL be invoked.
+ *
+ * The function SHALL return zero when the @p key was found and
+ * non-zero, otherwise.
*/
- __attribute__((__nonnull__))
- void *(*remove)(
+ int (*remove)(
CxMap *map,
CxHashKey key,
- bool destroy
+ void *targetbuf
);
/**
* Creates an iterator for this map.
*/
- __attribute__((__nonnull__, __warn_unused_result__))
- CxIterator (*iterator)(CxMap const *map, enum cx_map_iterator_type type);
-};
-
-/**
- * A map entry.
- */
-struct cx_map_entry_s {
- /**
- * A pointer to the key.
- */
- CxHashKey const *key;
- /**
- * A pointer to the value.
- */
- void *value;
+ CxMapIterator (*iterator)(const CxMap *map, enum cx_map_iterator_type type);
};
/**
* A shared instance of an empty map.
*
- * Writing to that map is undefined.
- */
-extern CxMap *const cxEmptyMap;
-
-/**
- * Advises the map to store copies of the objects (default mode of operation).
- *
- * Retrieving objects from this map will yield pointers to the copies stored
- * within this list.
+ * Writing to that map is not allowed.
*
- * @param map the map
- * @see cxMapStorePointers()
+ * You can use this is a placeholder for initializing CxMap pointers
+ * for which you do not want to reserve memory right from the beginning.
*/
-__attribute__((__nonnull__))
-static inline void cxMapStoreObjects(CxMap *map) {
- map->store_pointer = false;
-}
+cx_attr_export
+extern CxMap *const cxEmptyMap;
/**
- * Advises the map to only store pointers to the objects.
- *
- * Retrieving objects from this list will yield the original pointers stored.
+ * Deallocates the memory of the specified map.
*
- * @note This function forcibly sets the element size to the size of a pointer.
- * Invoking this function on a non-empty map that already stores copies of
- * objects is undefined.
+ * Also calls the content destructor functions for each element, if specified.
*
- * @param map the map
- * @see cxMapStoreObjects()
+ * @param map the map to be freed
*/
-__attribute__((__nonnull__))
-static inline void cxMapStorePointers(CxMap *map) {
- map->store_pointer = true;
- map->item_size = sizeof(void *);
-}
-
-
-/**
- * Deallocates the memory of the specified map.
- *
- * @param map the map to be destroyed
- */
-__attribute__((__nonnull__))
-static inline void cxMapDestroy(CxMap *map) {
- map->cl->destructor(map);
-}
+cx_attr_export
+void cxMapFree(CxMap *map);
/**
* Clears a map by removing all elements.
*
+ * Also calls the content destructor functions for each element, if specified.
+ *
* @param map the map to be cleared
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline void cxMapClear(CxMap *map) {
map->cl->clear(map);
}
-
-// TODO: set-like map operations (union, intersect, difference)
+/**
+ * Returns the number of elements in this map.
+ *
+ * @param map the map
+ * @return the number of stored elements
+ */
+cx_attr_nonnull
+static inline size_t cxMapSize(const CxMap *map) {
+ return map->collection.size;
+}
/**
* Creates a value iterator for a map.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * When the map is storing pointers, those pointers are returned.
+ * Otherwise, the iterator iterates over pointers to the memory within the map where the
+ * respective elements are stored.
+ *
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored values
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxIterator cxMapIteratorValues(CxMap const *map) {
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxMapIterator cxMapIteratorValues(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
}
/**
* Creates a key iterator for a map.
*
- * The elements of the iterator are keys of type CxHashKey.
+ * The elements of the iterator are keys of type CxHashKey and the pointer returned
+ * during iterator shall be treated as @c const @c CxHashKey* .
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored keys
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxIterator cxMapIteratorKeys(CxMap const *map) {
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
}
/**
* Creates an iterator for a map.
*
- * The elements of the iterator are key/value pairs of type CxMapEntry.
+ * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned
+ * during iterator shall be treated as @c const @c CxMapEntry* .
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @see cxMapIteratorKeys()
* @see cxMapIteratorValues()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline CxIterator cxMapIterator(CxMap const *map) {
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxMapIterator cxMapIterator(const CxMap *map) {
return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
}
/**
* Creates a mutating iterator over the values of a map.
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * When the map is storing pointers, those pointers are returned.
+ * Otherwise, the iterator iterates over pointers to the memory within the map where the
+ * respective elements are stored.
+ *
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored values
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-CxMutIterator cxMapMutIteratorValues(CxMap *map);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxMapIterator cxMapMutIteratorValues(CxMap *map);
/**
* Creates a mutating iterator over the keys of a map.
*
- * The elements of the iterator are keys of type CxHashKey.
+ * The elements of the iterator are keys of type CxHashKey and the pointer returned
+ * during iterator shall be treated as @c const @c CxHashKey* .
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @return an iterator for the currently stored keys
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-CxMutIterator cxMapMutIteratorKeys(CxMap *map);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxMapIterator cxMapMutIteratorKeys(CxMap *map);
/**
* Creates a mutating iterator for a map.
*
- * The elements of the iterator are key/value pairs of type CxMapEntry.
+ * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned
+ * during iterator shall be treated as @c const @c CxMapEntry* .
*
- * \note An iterator iterates over all elements successively. Therefore the order
+ * @note An iterator iterates over all elements successively. Therefore, the order
* highly depends on the map implementation and may change arbitrarily when the contents change.
*
* @param map the map to create the iterator for
* @see cxMapMutIteratorKeys()
* @see cxMapMutIteratorValues()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-CxMutIterator cxMapMutIterator(CxMap *map);
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxMapIterator cxMapMutIterator(CxMap *map);
#ifdef __cplusplus
} // end the extern "C" block here, because we want to start overloading
-
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
CxHashKey const &key,
return map->cl->put(map, key, value);
}
-
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
cxstring const &key,
return map->cl->put(map, cx_hash_key_cxstr(key), value);
}
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cxMapPut(
CxMap *map,
cxmutstr const &key,
return map->cl->put(map, cx_hash_key_cxstr(key), value);
}
-/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
- */
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
static inline int cxMapPut(
CxMap *map,
- char const *key,
+ const char *key,
void *value
) {
return map->cl->put(map, cx_hash_key_str(key), value);
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
- CxMap const *map,
+ const CxMap *map,
CxHashKey const &key
) {
return map->cl->get(map, key);
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
- CxMap const *map,
+ const CxMap *map,
cxstring const &key
) {
return map->cl->get(map, cx_hash_key_cxstr(key));
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cxMapGet(
- CxMap const *map,
+ const CxMap *map,
cxmutstr const &key
) {
return map->cl->get(map, cx_hash_key_cxstr(key));
}
-/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
- */
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(2)
static inline void *cxMapGet(
- CxMap const *map,
- char const *key
+ const CxMap *map,
+ const char *key
) {
return map->cl->get(map, cx_hash_key_str(key));
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
CxHashKey const &key
) {
- (void) map->cl->remove(map, key, true);
+ return map->cl->remove(map, key, nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
cxstring const &key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+static inline int cxMapRemove(
CxMap *map,
cxmutstr const &key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemoveAndGet()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__))
-static inline void cxMapRemove(
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxMapRemove(
CxMap *map,
- char const *key
+ const char *key
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), true);
+ return map->cl->remove(map, cx_hash_key_str(key), nullptr);
}
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- CxHashKey const &key
-) {
- (void) map->cl->remove(map, key, false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
- CxMap *map,
- cxstring const &key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
- CxMap *map,
- cxmutstr const &key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-__attribute__((__nonnull__))
-static inline void cxMapDetach(
- CxMap *map,
- char const *key
-) {
- (void) map->cl->remove(map, cx_hash_key_str(key), false);
-}
-
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
- *
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- * @see cxMapStorePointers()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cxMapRemoveAndGet(
- CxMap *map,
- CxHashKey key
+ CxHashKey key,
+ void *targetbuf
) {
- return map->cl->remove(map, key, !map->store_pointer);
+ return map->cl->remove(map, key, targetbuf);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
- *
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- * @see cxMapStorePointers()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cxMapRemoveAndGet(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- cxstring key
+ cxstring key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
- *
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- * @see cxMapStorePointers()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cxMapRemoveAndGet(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- cxmutstr key
+ cxmutstr key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
- *
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- * @see cxMapStorePointers()
- * @see cxMapDetach()
- */
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cxMapRemoveAndGet(
+cx_attr_nonnull
+cx_attr_access_w(3)
+cx_attr_cstr_arg(2)
+static inline int cxMapRemoveAndGet(
CxMap *map,
- char const *key
+ const char *key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_str(key), targetbuf);
}
#else // __cplusplus
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put(
CxMap *map,
CxHashKey key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put_cxstr(
CxMap *map,
cxstring key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
static inline int cx_map_put_mustr(
CxMap *map,
cxmutstr key,
}
/**
- * Puts a key/value-pair into the map.
- *
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * @copydoc cxMapPut()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
static inline int cx_map_put_str(
CxMap *map,
- char const *key,
+ const char *key,
void *value
) {
return map->cl->put(map, cx_hash_key_str(key), value);
/**
* Puts a key/value-pair into the map.
*
- * @param map the map
- * @param key the key
- * @param value the value
- * @return 0 on success, non-zero value on failure
+ * A possible existing value will be overwritten.
+ * If destructor functions are specified, they are called for
+ * the overwritten element.
+ *
+ * If this map is storing pointers, the @p value pointer is written
+ * to the map. Otherwise, the memory is copied from @p value with
+ * memcpy().
+ *
+ * The @p key is always copied.
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @param value (@c void*) the value
+ * @retval zero success
+ * @retval non-zero value on memory allocation failure
*/
#define cxMapPut(map, key, value) _Generic((key), \
CxHashKey: cx_map_put, \
cxstring: cx_map_put_cxstr, \
cxmutstr: cx_map_put_mustr, \
char*: cx_map_put_str, \
- char const*: cx_map_put_str) \
+ const char*: cx_map_put_str) \
(map, key, value)
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get(
- CxMap const *map,
+ const CxMap *map,
CxHashKey key
) {
return map->cl->get(map, key);
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get_cxstr(
- CxMap const *map,
+ const CxMap *map,
cxstring key
) {
return map->cl->get(map, cx_hash_key_cxstr(key));
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
static inline void *cx_map_get_mustr(
- CxMap const *map,
+ const CxMap *map,
cxmutstr key
) {
return map->cl->get(map, cx_hash_key_cxstr(key));
}
/**
- * Retrieves a value by using a key.
- *
- * @param map the map
- * @param key the key
- * @return the value
+ * @copydoc cxMapGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(2)
static inline void *cx_map_get_str(
- CxMap const *map,
- char const *key
+ const CxMap *map,
+ const char *key
) {
return map->cl->get(map, cx_hash_key_str(key));
}
/**
* Retrieves a value by using a key.
*
- * @param map the map
- * @param key the key
- * @return the value
+ * If this map is storing pointers, the stored pointer is returned.
+ * Otherwise, a pointer to the element within the map's memory
+ * is returned (which is valid as long as the element stays in the map).
+ *
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @return (@c void*) the value
*/
#define cxMapGet(map, key) _Generic((key), \
CxHashKey: cx_map_get, \
cxstring: cx_map_get_cxstr, \
cxmutstr: cx_map_get_mustr, \
char*: cx_map_get_str, \
- char const*: cx_map_get_str) \
+ const char*: cx_map_get_str) \
(map, key)
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove(
+cx_attr_nonnull
+static inline int cx_map_remove(
CxMap *map,
CxHashKey key
) {
- (void) map->cl->remove(map, key, true);
+ return map->cl->remove(map, key, NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_cxstr(
+cx_attr_nonnull
+static inline int cx_map_remove_cxstr(
CxMap *map,
cxstring key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_mustr(
+cx_attr_nonnull
+static inline int cx_map_remove_mustr(
CxMap *map,
cxmutstr key
) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), NULL);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemove()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_remove_str(
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_map_remove_str(
CxMap *map,
- char const *key
+ const char *key
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), true);
+ return map->cl->remove(map, cx_hash_key_str(key), NULL);
}
/**
* Removes a key/value-pair from the map by using the key.
*
- * Always invokes the destructor function, if any, on the removed element.
- * If this map is storing pointers and you just want to retrieve the pointer
- * without invoking the destructor, use cxMapRemoveAndGet().
- * If you just want to detach the element from the map without invoking the
- * destructor or returning the element, use cxMapDetach().
+ * Always invokes the destructors functions, if any, on the removed element.
*
- * @param map the map
- * @param key the key
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @retval zero success
+ * @retval non-zero the key was not found
+ *
* @see cxMapRemoveAndGet()
- * @see cxMapDetach()
*/
#define cxMapRemove(map, key) _Generic((key), \
CxHashKey: cx_map_remove, \
cxstring: cx_map_remove_cxstr, \
cxmutstr: cx_map_remove_mustr, \
char*: cx_map_remove_str, \
- char const*: cx_map_remove_str) \
+ const char*: cx_map_remove_str) \
(map, key)
/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach(
- CxMap *map,
- CxHashKey key
-) {
- (void) map->cl->remove(map, key, false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach_cxstr(
- CxMap *map,
- cxstring key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
- */
-__attribute__((__nonnull__))
-static inline void cx_map_detach_mustr(
- CxMap *map,
- cxmutstr key
-) {
- (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
-}
-
-/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * @param map the map
- * @param key the key
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__))
-static inline void cx_map_detach_str(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get(
CxMap *map,
- char const *key
+ CxHashKey key,
+ void *targetbuf
) {
- (void) map->cl->remove(map, cx_hash_key_str(key), false);
+ return map->cl->remove(map, key, targetbuf);
}
/**
- * Detaches a key/value-pair from the map by using the key
- * without invoking the destructor.
- *
- * In general, you should only use this function if the map does not own
- * the data and there is a valid reference to the data somewhere else
- * in the program. In all other cases it is preferable to use
- * cxMapRemove() or cxMapRemoveAndGet().
- *
- * @param map the map
- * @param key the key
- * @see cxMapRemove()
- * @see cxMapRemoveAndGet()
- */
-#define cxMapDetach(map, key) _Generic((key), \
- CxHashKey: cx_map_detach, \
- cxstring: cx_map_detach_cxstr, \
- cxmutstr: cx_map_detach_mustr, \
- char*: cx_map_detach_str, \
- char const*: cx_map_detach_str) \
- (map, key)
-
-/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get_cxstr(
CxMap *map,
- CxHashKey key
+ cxstring key,
+ void *targetbuf
) {
- return map->cl->remove(map, key, !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_cxstr(
+cx_attr_nonnull
+cx_attr_access_w(3)
+static inline int cx_map_remove_and_get_mustr(
CxMap *map,
- cxstring key
+ cxmutstr key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf);
}
/**
- * Removes a key/value-pair from the map by using the key.
- *
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
+ * @copydoc cxMapRemoveAndGet()
*/
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_mustr(
+cx_attr_nonnull
+cx_attr_access_w(3)
+cx_attr_cstr_arg(2)
+static inline int cx_map_remove_and_get_str(
CxMap *map,
- cxmutstr key
+ const char *key,
+ void *targetbuf
) {
- return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+ return map->cl->remove(map, cx_hash_key_str(key), targetbuf);
}
/**
* Removes a key/value-pair from the map by using the key.
*
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- */
-__attribute__((__nonnull__, __warn_unused_result__))
-static inline void *cx_map_remove_and_get_str(
- CxMap *map,
- char const *key
-) {
- return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
-}
-
-/**
- * Removes a key/value-pair from the map by using the key.
+ * This function will copy the contents of the removed element
+ * to the target buffer, which must be guaranteed to be large enough
+ * to hold the element (the map's element size).
+ * The destructor functions, if any, will @em not be called.
*
- * This function can be used when the map is storing pointers,
- * in order to retrieve the pointer from the map without invoking
- * any destructor function. Sometimes you do not want the pointer
- * to be returned - in that case (instead of suppressing the "unused
- * result" warning) you can use cxMapDetach().
+ * If this map is storing pointers, the element is the pointer itself
+ * and not the object it points to.
*
- * If this map is not storing pointers, this function behaves like
- * cxMapRemove() and returns \c NULL.
+ * @param map (@c CxMap*) the map
+ * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key
+ * @param targetbuf (@c void*) the buffer where the element shall be copied to
+ * @retval zero success
+ * @retval non-zero the key was not found
*
- * @param map the map
- * @param key the key
- * @return the stored pointer or \c NULL if either the key is not present
- * in the map or the map is not storing pointers
- * @see cxMapStorePointers()
- * @see cxMapDetach()
+ * @see cxMapRemove()
*/
-#define cxMapRemoveAndGet(map, key) _Generic((key), \
+#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \
CxHashKey: cx_map_remove_and_get, \
cxstring: cx_map_remove_and_get_cxstr, \
cxmutstr: cx_map_remove_and_get_mustr, \
char*: cx_map_remove_and_get_str, \
- char const*: cx_map_remove_and_get_str) \
- (map, key)
+ const char*: cx_map_remove_and_get_str) \
+ (map, key, targetbuf)
#endif // __cplusplus
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file mempool.h
- * \brief Interface for memory pool implementations.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file mempool.h
+ * @brief Interface for memory pool implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_MEMPOOL_H
extern "C" {
#endif
-/** Internal structure for pooled memory. */
-struct cx_mempool_memory_s;
+/** A memory block in a simple memory pool. */
+struct cx_mempool_memory_s {
+ /** The destructor. */
+ cx_destructor_func destructor;
+ /** The actual memory. */
+ char c[];
+};
+
+/** A memory block in an advanced memory pool. */
+struct cx_mempool_memory2_s {
+ /** The destructor. */
+ cx_destructor_func2 destructor;
+ /** Data for the destructor. */
+ void *data;
+ /** The actual memory. */
+ char c[];
+};
+
+/** Represents memory that is not allocated by, but registered with a pool. */
+struct cx_mempool_foreign_memory_s {
+ /** The foreign memory. */
+ void* mem;
+ union {
+ /** Simple destructor. */
+ cx_destructor_func destr;
+ /** Advanced destructor. */
+ cx_destructor_func2 destr2;
+ };
+ /** Data for the advanced destructor. */
+ void *destr2_data;
+};
+
+/** Specifies how individual blocks are allocated. */
+enum cx_mempool_type {
+ /**
+ * Allows registration of cx_destructor_func for each memory block.
+ */
+ CX_MEMPOOL_TYPE_SIMPLE,
+ /**
+ * Allows registration of cx_destructor_func2 for each memory block.
+ */
+ CX_MEMPOOL_TYPE_ADVANCED,
+ /**
+ * No individual destructor registration allowed.
+ *
+ * In this mode, no additional memory per block is allocated.
+ */
+ CX_MEMPOOL_TYPE_PURE,
+};
/**
* The basic structure of a memory pool.
* Should be the first member of an actual memory pool implementation.
*/
struct cx_mempool_s {
- /** The provided allocator. */
- CxAllocator const *allocator;
+ /** The used allocator, initialized with the cxDefaultAllocator. */
+ const CxAllocator * const base_allocator;
- /**
- * A destructor that shall be automatically registered for newly allocated memory.
- * This destructor MUST NOT free the memory.
- */
- cx_destructor_func auto_destr;
+ /** The provided allocator. */
+ const CxAllocator *allocator;
/** Array of pooled memory. */
- struct cx_mempool_memory_s **data;
+ void **data;
/** Number of pooled memory items. */
size_t size;
/** Memory pool capacity. */
size_t capacity;
+
+ /** Array of registered memory. */
+ struct cx_mempool_foreign_memory_s *registered;
+
+ /** Number of registered memory items. */
+ size_t registered_size;
+
+ /** Capacity for registered memory. */
+ size_t registered_capacity;
+
+ /**
+ * A destructor that shall be called before deallocating a memory block.
+ * This destructor MUST NOT free the memory itself.
+ *
+ * It is guaranteed that this destructor is called after the individual
+ * destructor of the memory block and before @c destr2.
+ */
+ cx_destructor_func destr;
+
+ /**
+ * A destructor that shall be called before deallocating a memory block.
+ * This destructor MUST NOT free the memory itself.
+ *
+ * It is guaranteed that this destructor is called after the individual
+ * destructor of the memory block and @c destr.
+ */
+ cx_destructor_func2 destr2;
+
+ /**
+ * Additional data for the @c destr2.
+ */
+ void *destr2_data;
};
/**
typedef struct cx_mempool_s CxMempool;
/**
- * Creates an array-based memory pool with a shared destructor function.
+ * Deallocates a memory pool and frees the managed memory.
*
- * This destructor MUST NOT free the memory.
+ * @param pool the memory pool to free
+ */
+cx_attr_export
+void cxMempoolFree(CxMempool *pool);
+
+/**
+ * Creates an array-based memory pool.
+ *
+ * The type determines how much additional memory is allocated per block
+ * to register a destructor function.
*
- * @param capacity the initial capacity of the pool
- * @param destr the destructor function to use for allocated memory
- * @return the created memory pool or \c NULL if allocation failed
+ * @param capacity the initial capacity of the pool (an implementation default if zero)
+ * @param type the type of memory pool
+ * @return the created memory pool or @c NULL if allocation failed
*/
-__attribute__((__warn_unused_result__))
-CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr);
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxMempoolFree, 1)
+cx_attr_export
+CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type);
/**
* Creates a basic array-based memory pool.
*
- * @param capacity the initial capacity of the pool
- * @return the created memory pool or \c NULL if allocation failed
+ * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_SIMPLE.
+ *
+ * @param capacity (@c size_t) the initial capacity of the pool
+ * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed
*/
-__attribute__((__warn_unused_result__))
-static inline CxMempool *cxBasicMempoolCreate(size_t capacity) {
- return cxMempoolCreate(capacity, NULL);
-}
+#define cxMempoolCreateSimple(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_SIMPLE)
/**
- * Destroys a memory pool and frees the managed memory.
+ * Creates a basic array-based memory pool.
+ *
+ * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_ADVANCED.
*
- * @param pool the memory pool to destroy
+ * @param capacity (@c size_t) the initial capacity of the pool
+ * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed
*/
-__attribute__((__nonnull__))
-void cxMempoolDestroy(CxMempool *pool);
+#define cxMempoolCreateAdvanced(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_ADVANCED)
+
+/**
+ * Creates a basic array-based memory pool.
+ *
+ * Convenience macro to create a memory pool of type #CX_MEMPOOL_TYPE_PURE.
+ *
+ * @param capacity (@c size_t) the initial capacity of the pool
+ * @return (@c CxMempool*) the created memory pool or @c NULL if allocation failed
+ */
+#define cxMempoolCreatePure(capacity) cxMempoolCreate(capacity, CX_MEMPOOL_TYPE_PURE)
+
+/**
+ * Sets the global destructor for all memory blocks within the specified pool.
+ *
+ * @param pool the memory pool
+ * @param fnc the destructor that shall be applied to all memory blocks
+ */
+cx_attr_nonnull_arg(1)
+cx_attr_export
+void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc);
+
+/**
+ * Sets the global destructor for all memory blocks within the specified pool.
+ *
+ * @param pool the memory pool
+ * @param fnc the destructor that shall be applied to all memory blocks
+ * @param data additional data for the destructor function
+ */
+cx_attr_nonnull_arg(1)
+cx_attr_export
+void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data);
/**
* Sets the destructor function for a specific allocated memory object.
*
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined.
* If the memory is not managed by a UCX memory pool, the behavior is undefined.
* The destructor MUST NOT free the memory.
*
* @param memory the object allocated in the pool
* @param fnc the destructor function
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
void cxMempoolSetDestructor(
void *memory,
cx_destructor_func fnc
);
+/**
+ * Sets the destructor function for a specific allocated memory object.
+ *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined.
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ * The destructor MUST NOT free the memory.
+ *
+ * @param memory the object allocated in the pool
+ * @param fnc the destructor function
+ * @param data additional data for the destructor function
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxMempoolSetDestructor2(
+ void *memory,
+ cx_destructor_func2 fnc,
+ void *data
+);
+
+/**
+ * Removes the destructor function for a specific allocated memory object.
+ *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_SIMPLE, the behavior is undefined.
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ *
+ * @param memory the object allocated in the pool
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxMempoolRemoveDestructor(void *memory);
+
+/**
+ * Removes the destructor function for a specific allocated memory object.
+ *
+ * If the type of memory pool is not #CX_MEMPOOL_TYPE_ADVANCED, the behavior is undefined.
+ * If the memory is not managed by a UCX memory pool, the behavior is undefined.
+ *
+ * @param memory the object allocated in the pool
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxMempoolRemoveDestructor2(void *memory);
+
/**
* Registers foreign memory with this pool.
*
* The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ * This function can be used with any pool of any type, since destructors for registered memory
+ * are entirely independent of the pool's memory management.
*
- * A small portion of memory will be allocated to register the information in the pool.
- * If that allocation fails, this function will return non-zero.
+ * The destructor for the registered memory will be called after all pooled items have been freed.
*
* @param pool the pool
- * @param memory the object allocated in the pool
+ * @param memory the object to register (MUST NOT be already allocated in the pool)
* @param destr the destructor function
- * @return zero on success, non-zero on failure
+ * @retval zero success
+ * @retval non-zero failure
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_export
int cxMempoolRegister(
CxMempool *pool,
void *memory,
cx_destructor_func destr
);
+
+/**
+ * Registers foreign memory with this pool.
+ *
+ * The destructor, in contrast to memory allocated by the pool, MUST free the memory.
+ * This function can be used with any pool of any type, since destructors for registered memory
+ * are entirely independent of the pool's memory management.
+ *
+ * The destructor for the registered memory will be called after all pooled items have been freed.
+ *
+ * @attention The data pointer MUST NOT be @c NULL.
+ * If you wish to register a destructor without additional data, use cxMempoolRegister().
+ *
+ * @param pool the pool
+ * @param memory the object to register (MUST NOT be already allocated in the pool)
+ * @param destr the destructor function
+ * @param data additional data for the destructor function
+ * @retval zero success
+ * @retval non-zero failure
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxMempoolRegister2(
+ CxMempool *pool,
+ void *memory,
+ cx_destructor_func2 destr,
+ void *data
+);
+
+/**
+ * Transfers all the memory managed by one pool to another.
+ *
+ * The allocator of the source pool will also be transferred and registered with the destination pool
+ * and stays valid, as long as the destination pool is not destroyed.
+ *
+ * The source pool will get a completely new allocator and can be reused or destroyed afterward.
+ *
+ * This function fails when the destination pool has a different type than the source pool.
+ *
+ * @param source the pool to move the memory from
+ * @param dest the pool where to transfer the memory to
+ * @retval zero success
+ * @retval non-zero allocation failure or incompatible pools
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxMempoolTransfer(
+ CxMempool *source,
+ CxMempool *dest
+);
+
+/**
+ * Transfers an object from one pool to another.
+ *
+ * This function fails when the destination pool has a different type than the source pool.
+ *
+ * @attention If the object maintains a reference to the pool's allocator,
+ * you must make sure to update that reference to the allocator of the destination pool.
+ *
+ * @param source the pool to move the memory from
+ * @param dest the pool where to transfer the memory to
+ * @param obj pointer to the object that shall be transferred
+ * @retval zero success
+ * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxMempoolTransferObject(
+ CxMempool *source,
+ CxMempool *dest,
+ const void *obj
+);
+
#ifdef __cplusplus
} // extern "C"
#endif
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file printf.h
- * \brief Wrapper for write functions with a printf-like interface.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file printf.h
+ * @brief Wrapper for write functions with a printf-like interface.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_PRINTF_H
#include "string.h"
#include <stdarg.h>
+/**
+ * Attribute for printf-like functions.
+ * @param fmt_idx index of the format string parameter
+ * @param arg_idx index of the first formatting argument
+ */
+#define cx_attr_printf(fmt_idx, arg_idx) \
+ __attribute__((__format__(printf, fmt_idx, arg_idx)))
+
#ifdef __cplusplus
extern "C" {
#endif
+
+/**
+ * The maximum string length that fits into stack memory.
+ */
+cx_attr_export
+extern const unsigned cx_printf_sbo_size;
+
/**
- * A \c fprintf like function which writes the output to a stream by
+ * A @c fprintf like function which writes the output to a stream by
* using a write_func.
*
* @param stream the stream the data is written to
* @param wfc the write function
* @param fmt format string
* @param ... additional arguments
- * @return the total number of bytes written
+ * @return the total number of bytes written or an error code from stdlib printf implementation
*/
-__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+cx_attr_nonnull_arg(1, 2, 3)
+cx_attr_printf(3, 4)
+cx_attr_cstr_arg(3)
+cx_attr_export
int cx_fprintf(
void *stream,
cx_write_func wfc,
- char const *fmt,
+ const char *fmt,
...
);
/**
- * A \c vfprintf like function which writes the output to a stream by
+ * A @c vfprintf like function which writes the output to a stream by
* using a write_func.
*
* @param stream the stream the data is written to
* @param wfc the write function
* @param fmt format string
* @param ap argument list
- * @return the total number of bytes written
+ * @return the total number of bytes written or an error code from stdlib printf implementation
* @see cx_fprintf()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(3)
+cx_attr_export
int cx_vfprintf(
void *stream,
cx_write_func wfc,
- char const *fmt,
+ const char *fmt,
va_list ap
);
/**
- * A \c asprintf like function which allocates space for a string
+ * A @c asprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
* @param allocator the CxAllocator used for allocating the string
* @param fmt format string
* @return the formatted string
* @see cx_strfree_a()
*/
-__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cx_attr_nonnull_arg(1, 2)
+cx_attr_printf(2, 3)
+cx_attr_cstr_arg(2)
+cx_attr_export
cxmutstr cx_asprintf_a(
- CxAllocator const *allocator,
- char const *fmt,
+ const CxAllocator *allocator,
+ const char *fmt,
...
);
/**
- * A \c asprintf like function which allocates space for a string
+ * A @c asprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
- * @param fmt format string
+ * @param fmt (@c char*) format string
* @param ... additional arguments
- * @return the formatted string
+ * @return (@c cxmutstr) the formatted string
* @see cx_strfree()
*/
#define cx_asprintf(fmt, ...) \
cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__)
/**
-* A \c vasprintf like function which allocates space for a string
+* A @c vasprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was an error, in which case the string's pointer
+ * will be @c NULL.
*
* @param allocator the CxAllocator used for allocating the string
* @param fmt format string
* @return the formatted string
* @see cx_asprintf_a()
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+cx_attr_export
cxmutstr cx_vasprintf_a(
- CxAllocator const *allocator,
- char const *fmt,
+ const CxAllocator *allocator,
+ const char *fmt,
va_list ap
);
/**
-* A \c vasprintf like function which allocates space for a string
+* A @c vasprintf like function which allocates space for a string
* the result is written to.
*
- * \note The resulting string is guaranteed to be zero-terminated.
+ * @note The resulting string is guaranteed to be zero-terminated,
+ * unless there was in error, in which case the string's pointer
+ * will be @c NULL.
*
- * @param fmt format string
- * @param ap argument list
- * @return the formatted string
+ * @param fmt (@c char*) format string
+ * @param ap (@c va_list) argument list
+ * @return (@c cxmutstr) the formatted string
* @see cx_asprintf()
*/
#define cx_vasprintf(fmt, ap) cx_vasprintf_a(cxDefaultAllocator, fmt, ap)
/**
- * A \c printf like function which writes the output to a CxBuffer.
+ * A @c printf like function which writes the output to a CxBuffer.
+ *
+ * @param buffer (@c CxBuffer*) a pointer to the buffer the data is written to
+ * @param fmt (@c char*) the format string
+ * @param ... additional arguments
+ * @return (@c int) the total number of bytes written or an error code from stdlib printf implementation
+ * @see cx_fprintf()
+ * @see cxBufferWrite()
+ */
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \
+ cxBufferWriteFunc, fmt, __VA_ARGS__)
+
+
+/**
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
+ *
+ * @param str (@c char**) a pointer to the string buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param fmt (@c char*) the format string
+ * @param ... additional arguments
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
+ */
+#define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__)
+
+/**
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
+ *
+ * @attention The original buffer MUST have been allocated with the same allocator!
*
- * @param buffer a pointer to the buffer the data is written to
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
* @param fmt the format string
* @param ... additional arguments
- * @return the total number of bytes written
- * @see ucx_fprintf()
+ * @return the length of produced string or an error code from stdlib printf implementation
*/
-#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
- (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
+cx_attr_nonnull_arg(1, 2, 3, 4)
+cx_attr_printf(4, 5)
+cx_attr_cstr_arg(4)
+cx_attr_export
+int cx_sprintf_a(
+ const CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ ...
+);
+
+
+/**
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
+ *
+ * @param str (@c char**) a pointer to the string buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param fmt (@c char*) the format string
+ * @param ap (@c va_list) argument list
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
+ */
+#define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap)
+
+/**
+ * An @c sprintf like function which reallocates the string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * @note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @attention The original buffer MUST have been allocated with the same allocator!
+ *
+ * @param alloc the allocator to use
+ * @param str a pointer to the string buffer
+ * @param len a pointer to the length of the buffer
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string or an error code from stdlib printf implementation
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(4)
+cx_attr_access_rw(2)
+cx_attr_access_rw(3)
+cx_attr_export
+int cx_vsprintf_a(
+ const CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ va_list ap
+);
+
+
+/**
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
+ *
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
+ *
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
+ *
+ * @param buf (@c char*) a pointer to the buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param str (@c char**) a pointer where the location of the result shall be stored
+ * @param fmt (@c char*) the format string
+ * @param ... additional arguments
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
+ */
+#define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__)
+
+/**
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
+ *
+ * @note The resulting string, if successful, is guaranteed to be zero-terminated.
+ *
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer where the location of the result shall be stored
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the length of produced string or an error code from stdlib printf implementation
+ */
+cx_attr_nonnull_arg(1, 2, 4, 5)
+cx_attr_printf(5, 6)
+cx_attr_cstr_arg(5)
+cx_attr_access_rw(2)
+cx_attr_access_rw(3)
+cx_attr_access_rw(4)
+cx_attr_export
+int cx_sprintf_sa(
+ const CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ ...
+);
+
+/**
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
+ *
+ * @note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
+ *
+ * @param buf (@c char*) a pointer to the buffer
+ * @param len (@c size_t*) a pointer to the length of the buffer
+ * @param str (@c char**) a pointer where the location of the result shall be stored
+ * @param fmt (@c char*) the format string
+ * @param ap (@c va_list) argument list
+ * @return (@c int) the length of produced string or an error code from stdlib printf implementation
+ */
+#define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap)
+
+/**
+ * An @c sprintf like function which allocates a new string when the buffer is not large enough.
+ *
+ * The size of the buffer will be updated in @p len when necessary.
+ *
+ * The location of the resulting string will @em always be stored to @p str. When the buffer
+ * was sufficiently large, @p buf itself will be stored to the location of @p str.
+ *
+ * @note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @remark When a new string needed to be allocated, the contents of @p buf will be
+ * poisoned after the call, because this function tries to produce the string in @p buf, first.
+ *
+ * @param alloc the allocator to use
+ * @param buf a pointer to the buffer
+ * @param len a pointer to the length of the buffer
+ * @param str a pointer where the location of the result shall be stored
+ * @param fmt the format string
+ * @param ap argument list
+ * @return the length of produced string or an error code from stdlib printf implementation
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(5)
+cx_attr_export
+int cx_vsprintf_sa(
+ const CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ va_list ap
+);
+
#ifdef __cplusplus
} // extern "C"
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file properties.h
+ * @brief Interface for parsing data from properties files.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define UCX_PROPERTIES_H
+
+#include "common.h"
+#include "string.h"
+#include "map.h"
+#include "buffer.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Configures the expected characters for the properties parser.
+ */
+struct cx_properties_config_s {
+ /**
+ * The key/value delimiter that shall be used.
+ * This is '=' by default.
+ */
+ char delimiter;
+
+ /**
+ * The first comment character.
+ * This is '#' by default.
+ */
+ char comment1;
+
+ /**
+ * The second comment character.
+ * This is not set by default.
+ */
+ char comment2;
+
+ /**
+ * The third comment character.
+ * This is not set by default.
+ */
+ char comment3;
+
+ /*
+ * The character, when appearing at the end of a line, continues that line.
+ * This is '\' by default.
+ */
+ /**
+ * Reserved for future use.
+ */
+ char continuation;
+};
+
+/**
+ * Typedef for the properties config.
+ */
+typedef struct cx_properties_config_s CxPropertiesConfig;
+
+/**
+ * Default properties configuration.
+ */
+cx_attr_export
+extern const CxPropertiesConfig cx_properties_config_default;
+
+/**
+ * Status codes for the properties interface.
+ */
+enum cx_properties_status {
+ /**
+ * Everything is fine.
+ */
+ CX_PROPERTIES_NO_ERROR,
+ /**
+ * The input buffer does not contain more data.
+ */
+ CX_PROPERTIES_NO_DATA,
+ /**
+ * The input ends unexpectedly.
+ *
+ * This either happens when the last line does not terminate with a line
+ * break, or when the input ends with a parsed key but no value.
+ */
+ CX_PROPERTIES_INCOMPLETE_DATA,
+ /**
+ * Not used as a status and never returned by any function.
+ *
+ * You can use this enumerator to check for all "good" status results
+ * by checking if the status is less than @c CX_PROPERTIES_OK.
+ *
+ * A "good" status means, that you can refill data and continue parsing.
+ */
+ CX_PROPERTIES_OK,
+ /**
+ * Input buffer is @c NULL.
+ */
+ CX_PROPERTIES_NULL_INPUT,
+ /**
+ * The line contains a delimiter, but no key.
+ */
+ CX_PROPERTIES_INVALID_EMPTY_KEY,
+ /**
+ * The line contains data, but no delimiter.
+ */
+ CX_PROPERTIES_INVALID_MISSING_DELIMITER,
+ /**
+ * More internal buffer was needed, but could not be allocated.
+ */
+ CX_PROPERTIES_BUFFER_ALLOC_FAILED,
+ /**
+ * Initializing the properties source failed.
+ *
+ * @see cx_properties_read_init_func
+ */
+ CX_PROPERTIES_READ_INIT_FAILED,
+ /**
+ * Reading from a properties source failed.
+ *
+ * @see cx_properties_read_func
+ */
+ CX_PROPERTIES_READ_FAILED,
+ /**
+ * Sinking a k/v-pair failed.
+ *
+ * @see cx_properties_sink_func
+ */
+ CX_PROPERTIES_SINK_FAILED,
+};
+
+/**
+ * Typedef for the properties status enum.
+ */
+typedef enum cx_properties_status CxPropertiesStatus;
+
+/**
+ * Interface for working with properties data.
+ */
+struct cx_properties_s {
+ /**
+ * The configuration.
+ */
+ CxPropertiesConfig config;
+
+ /**
+ * The text input buffer.
+ */
+ CxBuffer input;
+
+ /**
+ * Internal buffer.
+ */
+ CxBuffer buffer;
+};
+
+/**
+ * Typedef for the properties interface.
+ */
+typedef struct cx_properties_s CxProperties;
+
+
+/**
+ * Typedef for a properties sink.
+ */
+typedef struct cx_properties_sink_s CxPropertiesSink;
+
+/**
+ * A function that consumes a k/v-pair in a sink.
+ *
+ * The sink could be e.g. a map and the sink function would be calling
+ * a map function to store the k/v-pair.
+ *
+ * @param prop the properties interface that wants to sink a k/v-pair
+ * @param sink the sink
+ * @param key the key
+ * @param value the value
+ * @retval zero success
+ * @retval non-zero sinking the k/v-pair failed
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_sink_func)(
+ CxProperties *prop,
+ CxPropertiesSink *sink,
+ cxstring key,
+ cxstring value
+);
+
+/**
+ * Defines a sink for k/v-pairs.
+ */
+struct cx_properties_sink_s {
+ /**
+ * The sink object.
+ */
+ void *sink;
+ /**
+ * Optional custom data.
+ */
+ void *data;
+ /**
+ * A function for consuming k/v-pairs into the sink.
+ */
+ cx_properties_sink_func sink_func;
+};
+
+
+/**
+ * Typedef for a properties source.
+ */
+typedef struct cx_properties_source_s CxPropertiesSource;
+
+/**
+ * A function that reads data from a source.
+ *
+ * When the source is depleted, implementations SHALL provide an empty
+ * string in the @p target and return zero.
+ * A non-zero return value is only permitted in case of an error.
+ *
+ * The meaning of the optional parameters is implementation-dependent.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ * @param target a string buffer where the read data shall be stored
+ * @retval zero success
+ * @retval non-zero reading the data failed
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_read_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+);
+
+/**
+ * A function that may initialize additional memory for the source.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ * @retval zero initialization was successful
+ * @retval non-zero otherwise
+ */
+cx_attr_nonnull
+typedef int(*cx_properties_read_init_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src
+);
+
+/**
+ * A function that cleans memory initialized by the read_init_func.
+ *
+ * @param prop the properties interface that wants to read from the source
+ * @param src the source
+ */
+cx_attr_nonnull
+typedef void(*cx_properties_read_clean_func)(
+ CxProperties *prop,
+ CxPropertiesSource *src
+);
+
+/**
+ * Defines a properties source.
+ */
+struct cx_properties_source_s {
+ /**
+ * The source object.
+ *
+ * For example a file stream or a string.
+ */
+ void *src;
+ /**
+ * Optional additional data pointer.
+ */
+ void *data_ptr;
+ /**
+ * Optional size information.
+ */
+ size_t data_size;
+ /**
+ * A function that reads data from the source.
+ */
+ cx_properties_read_func read_func;
+ /**
+ * Optional function that may prepare the source for reading data.
+ */
+ cx_properties_read_init_func read_init_func;
+ /**
+ * Optional function that cleans additional memory allocated by the
+ * read_init_func.
+ */
+ cx_properties_read_clean_func read_clean_func;
+};
+
+/**
+ * Initialize a properties interface.
+ *
+ * @param prop the properties interface
+ * @param config the properties configuration
+ * @see cxPropertiesInitDefault()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config);
+
+/**
+ * Destroys the properties interface.
+ *
+ * @note Even when you are certain that you did not use the interface in a
+ * way that caused a memory allocation, you should call this function anyway.
+ * Future versions of the library might add features that need additional memory
+ * and you really don't want to search the entire code where you might need
+ * add call to this function.
+ *
+ * @param prop the properties interface
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxPropertiesDestroy(CxProperties *prop);
+
+/**
+ * Destroys and re-initializes the properties interface.
+ *
+ * You might want to use this, to reset the parser after
+ * encountering a syntax error.
+ *
+ * @param prop the properties interface
+ */
+cx_attr_nonnull
+static inline void cxPropertiesReset(CxProperties *prop) {
+ CxPropertiesConfig config = prop->config;
+ cxPropertiesDestroy(prop);
+ cxPropertiesInit(prop, config);
+}
+
+/**
+ * Initialize a properties parser with the default configuration.
+ *
+ * @param prop (@c CxProperties*) the properties interface
+ * @see cxPropertiesInit()
+ */
+#define cxPropertiesInitDefault(prop) \
+ cxPropertiesInit(prop, cx_properties_config_default)
+
+/**
+ * Fills the input buffer with data.
+ *
+ * After calling this function, you can parse the data by calling
+ * cxPropertiesNext().
+ *
+ * @remark The properties interface tries to avoid allocations.
+ * When you use this function and cxPropertiesNext() interleaving,
+ * no allocations are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxPropertiesNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param prop the properties interface
+ * @param buf a pointer to the data
+ * @param len the length of the data
+ * @retval zero success
+ * @retval non-zero a memory allocation was necessary but failed
+ * @see cxPropertiesFill()
+ */
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
+int cxPropertiesFilln(
+ CxProperties *prop,
+ const char *buf,
+ size_t len
+);
+
+#ifdef __cplusplus
+} // extern "C"
+cx_attr_nonnull
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ cxstring str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ cxmutstr str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cxPropertiesFill(
+ CxProperties *prop,
+ const char *str
+) {
+ return cxPropertiesFilln(prop, str, strlen(str));
+}
+
+extern "C" {
+#else // __cplusplus
+/**
+ * Fills the input buffer with data.
+ *
+ * After calling this function, you can parse the data by calling
+ * cxPropertiesNext().
+ *
+ * @attention The properties interface tries to avoid allocations.
+ * When you use this function and cxPropertiesNext() interleaving,
+ * no allocations are performed. However, you must not free the
+ * pointer to the data in that case. When you invoke the fill
+ * function more than once before calling cxPropertiesNext(),
+ * the additional data is appended - inevitably leading to
+ * an allocation of a new buffer and copying the previous contents.
+ *
+ * @param prop the properties interface
+ * @param str the text to fill in
+ * @retval zero success
+ * @retval non-zero a memory allocation was necessary but failed
+ * @see cxPropertiesFilln()
+ */
+#define cxPropertiesFill(prop, str) _Generic((str), \
+ cxstring: cx_properties_fill_cxstr, \
+ cxmutstr: cx_properties_fill_mutstr, \
+ char*: cx_properties_fill_str, \
+ const char*: cx_properties_fill_str) \
+ (prop, str)
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+static inline int cx_properties_fill_cxstr(
+ CxProperties *prop,
+ cxstring str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+static inline int cx_properties_fill_mutstr(
+ CxProperties *prop,
+ cxmutstr str
+) {
+ return cxPropertiesFilln(prop, str.ptr, str.length);
+}
+
+/**
+ * @copydoc cxPropertiesFill()
+ */
+cx_attr_nonnull
+cx_attr_cstr_arg(2)
+static inline int cx_properties_fill_str(
+ CxProperties *prop,
+ const char *str
+) {
+ return cxPropertiesFilln(prop, str, strlen(str));
+}
+#endif
+
+/**
+ * Specifies stack memory that shall be used as internal buffer.
+ *
+ * @param prop the properties interface
+ * @param buf a pointer to stack memory
+ * @param capacity the capacity of the stack memory
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxPropertiesUseStack(
+ CxProperties *prop,
+ char *buf,
+ size_t capacity
+);
+
+/**
+ * Retrieves the next key/value-pair.
+ *
+ * This function returns zero as long as there are key/value-pairs found.
+ * If no more key/value-pairs are found, #CX_PROPERTIES_NO_DATA is returned.
+ *
+ * When an incomplete line is encountered, #CX_PROPERTIES_INCOMPLETE_DATA is
+ * returned, and you can add more data with #cxPropertiesFill().
+ *
+ * @remark The incomplete line will be stored in an internal buffer, which is
+ * allocated on the heap, by default. If you want to avoid allocations,
+ * you can specify sufficient space with cxPropertiesUseStack() after
+ * initialization with cxPropertiesInit().
+ *
+ * @attention The returned strings will point into a buffer that might not be
+ * available later. It is strongly recommended to copy the strings for further
+ * use.
+ *
+ * @param prop the properties interface
+ * @param key a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found
+ * @retval CX_PROPERTIES_NO_DATA there is no (more) data in the input buffer
+ * @retval CX_PROPERTIES_INCOMPLETE_DATA the data in the input buffer is incomplete
+ * (fill more data and try again)
+ * @retval CX_PROPERTIES_NULL_INPUT the input buffer was never filled
+ * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key
+ * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter
+ * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxPropertiesStatus cxPropertiesNext(
+ CxProperties *prop,
+ cxstring *key,
+ cxstring *value
+);
+
+/**
+ * Creates a properties sink for an UCX map.
+ *
+ * The values stored in the map will be pointers to freshly allocated,
+ * zero-terminated C strings (@c char*), which means the @p map should have been
+ * created with #CX_STORE_POINTERS.
+ *
+ * The cxDefaultAllocator will be used unless you specify a custom
+ * allocator in the optional @c data field of the returned sink.
+ *
+ * @param map the map that shall consume the k/v-pairs.
+ * @return the sink
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+CxPropertiesSink cxPropertiesMapSink(CxMap *map);
+
+/**
+ * Creates a properties source based on an UCX string.
+ *
+ * @param str the string
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxPropertiesSource cxPropertiesStringSource(cxstring str);
+
+/**
+ * Creates a properties source based on C string with the specified length.
+ *
+ * @param str the string
+ * @param len the length
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
+cx_attr_export
+CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len);
+
+/**
+ * Creates a properties source based on a C string.
+ *
+ * The length will be determined with strlen(), so the string MUST be
+ * zero-terminated.
+ *
+ * @param str the string
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_export
+CxPropertiesSource cxPropertiesCstrSource(const char *str);
+
+/**
+ * Creates a properties source based on an FILE.
+ *
+ * @param file the file
+ * @param chunk_size how many bytes may be read in one operation
+ *
+ * @return the properties source
+ * @see cxPropertiesLoad()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_r(1)
+cx_attr_export
+CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size);
+
+
+/**
+ * Loads properties data from a source and transfers it to a sink.
+ *
+ * This function tries to read as much data from the source as possible.
+ * When the source was completely consumed and at least on k/v-pair was found,
+ * the return value will be #CX_PROPERTIES_NO_ERROR.
+ * When the source was consumed but no k/v-pairs were found, the return value
+ * will be #CX_PROPERTIES_NO_DATA.
+ * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA
+ * is returned. In that case you should call this function again with the same
+ * sink and either an updated source or the same source if the source is able to
+ * yield the missing data.
+ *
+ * The other result codes apply, according to their description.
+ *
+ * @param prop the properties interface
+ * @param sink the sink
+ * @param source the source
+ * @retval CX_PROPERTIES_NO_ERROR (zero) a key/value pair was found
+ * @retval CX_PROPERTIES_READ_INIT_FAILED initializing the source failed
+ * @retval CX_PROPERTIES_READ_FAILED reading from the source failed
+ * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed
+ * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs
+ * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data
+ * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key
+ * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter
+ * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed
+ */
+cx_attr_nonnull
+cx_attr_export
+CxPropertiesStatus cxPropertiesLoad(
+ CxProperties *prop,
+ CxPropertiesSink sink,
+ CxPropertiesSource source
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_PROPERTIES_H
*/
/**
- * \file utils.h
+ * @file streams.h
*
- * \brief General purpose utility functions.
+ * @brief Utility functions for data streams.
*
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
-#ifndef UCX_UTILS_H
-#define UCX_UTILS_H
+#ifndef UCX_STREAMS_H
+#define UCX_STREAMS_H
#include "common.h"
extern "C" {
#endif
-/**
- * Convenience macro for a for loop that counts from zero to n-1.
- */
-#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++)
-
-/**
- * Convenience macro for swapping two pointers.
- */
-#ifdef __cplusplus
-#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
-#else
-#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
-#endif
-
-// cx_szmul() definition
-
-#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
-#define CX_SZMUL_BUILTIN
-
-/**
- * Alias for \c __builtin_mul_overflow.
- *
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
-
-#else // no GNUC or clang bultin
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t, where the result should
- * be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
-
-/**
- * Performs a multiplication of size_t values and checks for overflow.
- *
- * This is a custom implementation in case there is no compiler builtin
- * available.
- *
- * @param a first operand
- * @param b second operand
- * @param result a pointer to a size_t where the result should be stored
- * @return zero, if no overflow occurred and the result is correct, non-zero
- * otherwise
- */
-int cx_szmul_impl(size_t a, size_t b, size_t *result);
-
-#endif // cx_szmul
-
-
/**
* Reads data from a stream and writes it to another stream.
*
* @param dest the destination stream
* @param rfnc the read function
* @param wfnc the write function
- * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * @param buf a pointer to the copy buffer or @c NULL if a buffer
* shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
+ * @param bufsize the size of the copy buffer - if @p buf is @c NULL you can
* set this to zero to let the implementation decide
* @param n the maximum number of bytes that shall be copied.
- * If this is larger than \p bufsize, the content is copied over multiple
+ * If this is larger than @p bufsize, the content is copied over multiple
* iterations.
* @return the total number of bytes copied
*/
-__attribute__((__nonnull__(1, 2, 3, 4)))
+cx_attr_nonnull_arg(1, 2, 3, 4)
+cx_attr_access_r(1)
+cx_attr_access_w(2)
+cx_attr_access_w(5)
+cx_attr_export
size_t cx_stream_bncopy(
void *src,
void *dest,
/**
* Reads data from a stream and writes it to another stream.
*
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
- * @param buf a pointer to the copy buffer or \c NULL if a buffer
+ * @param src (@c void*) the source stream
+ * @param dest (@c void*) the destination stream
+ * @param rfnc (@c cx_read_func) the read function
+ * @param wfnc (@c cx_write_func) the write function
+ * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer
* shall be implicitly created on the heap
- * @param bufsize the size of the copy buffer - if \p buf is \c NULL you can
- * set this to zero to let the implementation decide
+ * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is
+ * @c NULL you can set this to zero to let the implementation decide
* @return total number of bytes copied
*/
#define cx_stream_bcopy(src, dest, rfnc, wfnc, buf, bufsize) \
* @param n the maximum number of bytes that shall be copied.
* @return total number of bytes copied
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_access_r(1)
+cx_attr_access_w(2)
+cx_attr_export
size_t cx_stream_ncopy(
void *src,
void *dest,
*
* The data is temporarily stored in a stack allocated buffer.
*
- * @param src the source stream
- * @param dest the destination stream
- * @param rfnc the read function
- * @param wfnc the write function
+ * @param src (@c void*) the source stream
+ * @param dest (@c void*) the destination stream
+ * @param rfnc (@c cx_read_func) the read function
+ * @param wfnc (@c cx_write_func) the write function
* @return total number of bytes copied
*/
#define cx_stream_copy(src, dest, rfnc, wfnc) \
}
#endif
-#endif // UCX_UTILS_H
+#endif // UCX_STREAMS_H
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file string.h
- * \brief Strings that know their length.
- * \author Mike Becker
- * \author Olaf Wintermann
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file string.h
+ * @brief Strings that know their length.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_STRING_H
#include "common.h"
#include "allocator.h"
+/** Expands a UCX string as printf arguments. */
+#define CX_SFMT(s) (int) (s).length, (s).ptr
+
+/** Format specifier for a UCX string */
+#define CX_PRIstr ".*s"
+
+/**
+ * The maximum length of the "needle" in cx_strstr() that can use SBO.
+ */
+cx_attr_export
+extern const unsigned cx_strstr_sbo_size;
+
/**
* The UCX string structure.
*/
struct cx_mutstr_s {
/**
* A pointer to the string.
- * \note The string is not necessarily \c NULL terminated.
- * Always use the length.
+ * @note The string is not necessarily @c NULL terminated.
*/
char *ptr;
/** The length of the string */
struct cx_string_s {
/**
* A pointer to the immutable string.
- * \note The string is not necessarily \c NULL terminated.
- * Always use the length.
+ * @note The string is not necessarily @c NULL terminated.
*/
- char const *ptr;
+ const char *ptr;
/** The length of the string */
size_t length;
};
/**
* Optional array of more delimiters.
*/
- cxstring const *delim_more;
+ const cxstring *delim_more;
/**
* Length of the array containing more delimiters.
*/
/**
* A literal initializer for an UCX string structure.
*
- * The argument MUST be a string (const char*) \em literal.
+ * The argument MUST be a string (const char*) @em literal.
*
* @param literal the string literal
*/
-#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1}
+#define CX_STR(literal) ((cxstring){literal, sizeof(literal) - 1})
#endif
/**
* Wraps a mutable string that must be zero-terminated.
*
- * The length is implicitly inferred by using a call to \c strlen().
+ * The length is implicitly inferred by using a call to @c strlen().
+ *
+ * When @c NULL is passed, the length will be set to zero.
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a constant string, use cx_str().
*
* @see cx_mutstrn()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_export
cxmutstr cx_mutstr(char *cstring);
/**
* Wraps a string that does not need to be zero-terminated.
*
- * The argument may be \c NULL if the length is zero.
+ * The argument may be @c NULL if the length is zero.
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a constant string, use cx_strn().
*
- * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param cstring the string to wrap (or @c NULL, only if the length is zero)
* @param length the length of the string
* @return the wrapped string
*
* @see cx_mutstr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_rw(1, 2)
+cx_attr_export
cxmutstr cx_mutstrn(
char *cstring,
size_t length
/**
* Wraps a string that must be zero-terminated.
*
- * The length is implicitly inferred by using a call to \c strlen().
+ * The length is implicitly inferred by using a call to @c strlen().
*
- * \note the wrapped string will share the specified pointer to the string.
+ * When @c NULL is passed, the length will be set to zero.
+ *
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a non-constant string, use cx_mutstr().
*
* @see cx_strn()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
-cxstring cx_str(char const *cstring);
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_export
+cxstring cx_str(const char *cstring);
/**
* Wraps a string that does not need to be zero-terminated.
*
- * The argument may be \c NULL if the length is zero.
+ * The argument may be @c NULL if the length is zero.
*
- * \note the wrapped string will share the specified pointer to the string.
+ * @note the wrapped string will share the specified pointer to the string.
* If you do want a copy, use cx_strdup() on the return value of this function.
*
* If you need to wrap a non-constant string, use cx_mutstrn().
*
- * @param cstring the string to wrap (or \c NULL, only if the length is zero)
+ * @param cstring the string to wrap (or @c NULL, only if the length is zero)
* @param length the length of the string
* @return the wrapped string
*
* @see cx_str()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_access_r(1, 2)
+cx_attr_export
cxstring cx_strn(
- char const *cstring,
+ const char *cstring,
size_t length
);
+#ifdef __cplusplus
+} // extern "C"
+cx_attr_nodiscard
+static inline cxstring cx_strcast(cxmutstr str) {
+ return cx_strn(str.ptr, str.length);
+}
+cx_attr_nodiscard
+static inline cxstring cx_strcast(cxstring str) {
+ return str;
+}
+cx_attr_nodiscard
+static inline cxstring cx_strcast(const char *str) {
+ return cx_str(str);
+}
+extern "C" {
+#else
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_m(cxmutstr str) {
+ return (cxstring) {str.ptr, str.length};
+}
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_c(cxstring str) {
+ return str;
+}
+
+/**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_z(const char *str) {
+ return cx_str(str);
+}
+
/**
* Casts a mutable string to an immutable string.
*
-* \note This is not seriously a cast. Instead you get a copy
+* Does nothing for already immutable strings.
+*
+* @note This is not seriously a cast. Instead, you get a copy
* of the struct with the desired pointer type. Both structs still
* point to the same location, though!
*
-* @param str the mutable string to cast
-* @return an immutable copy of the string pointer
+* @param str (@c cxstring or @c cxmutstr) the string to cast
+* @return (@c cxstring) an immutable copy of the string pointer
*/
-__attribute__((__warn_unused_result__))
-cxstring cx_strcast(cxmutstr str);
+#define cx_strcast(str) _Generic((str), \
+ cxmutstr: cx_strcast_m, \
+ cxstring: cx_strcast_c, \
+ const char*: cx_strcast_z, \
+ char *: cx_strcast_z) (str)
+#endif
/**
- * Passes the pointer in this string to \c free().
+ * Passes the pointer in this string to the cxDefaultAllocator's @c free() function.
*
- * The pointer in the struct is set to \c NULL and the length is set to zero.
+ * The pointer in the struct is set to @c NULL and the length is set to zero
+ * which means that this function protects you against double-free.
*
- * \note There is no implementation for cxstring, because it is unlikely that
- * you ever have a \c char \c const* you are really supposed to free. If you
- * encounter such situation, you should double-check your code.
+ * @note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
*
* @param str the string to free
*/
-__attribute__((__nonnull__))
+cx_attr_export
void cx_strfree(cxmutstr *str);
/**
* Passes the pointer in this string to the allocators free function.
*
- * The pointer in the struct is set to \c NULL and the length is set to zero.
+ * The pointer in the struct is set to @c NULL and the length is set to zero
+ * which means that this function protects you against double-free.
*
- * \note There is no implementation for cxstring, because it is unlikely that
- * you ever have a \c char \c const* you are really supposed to free. If you
- * encounter such situation, you should double-check your code.
+ * @note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a <code>const char*</code> you are really supposed to free.
+ * If you encounter such situation, you should double-check your code.
*
* @param alloc the allocator
* @param str the string to free
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull_arg(1)
+cx_attr_export
void cx_strfree_a(
- CxAllocator const *alloc,
+ const CxAllocator *alloc,
cxmutstr *str
);
+/**
+ * Copies a string.
+ *
+ * The memory in the @p dest structure is either allocated or re-allocated to fit the entire
+ * source string, including a zero-terminator.
+ *
+ * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is.
+ *
+ * @param alloc the allocator
+ * @param dest a pointer to the structure where to copy the contents to
+ * @param src the source string
+ *
+ * @retval zero success
+ * @retval non-zero if re-allocation failed
+ */
+cx_attr_nonnull_arg(1)
+cx_attr_export
+int cx_strcpy_a(
+ const CxAllocator *alloc,
+ cxmutstr *dest,
+ cxstring src
+);
+
+
+/**
+ * Copies a string.
+ *
+ * The memory in the @p dest structure is either allocated or re-allocated to fit the entire
+ * source string, including a zero-terminator.
+ *
+ * The string in @p dest is guaranteed to be zero-terminated, regardless of whether @p src is.
+ *
+ * @param dest (@c cxmutstr*) a pointer to the structure where to copy the contents to
+ * @param src (@c cxstring) the source string
+ *
+ * @retval zero success
+ * @retval non-zero if re-allocation failed
+ */
+#define cx_strcpy(dest, src) cx_strcpy_a(cxDefaultAllocator, dest, src)
+
/**
* Returns the accumulated length of all specified strings.
+ *
+ * If this sum overflows, errno is set to EOVERFLOW.
*
- * \attention if the count argument is larger than the number of the
+ * @attention if the count argument is larger than the number of the
* specified strings, the behavior is undefined.
*
* @param count the total number of specified strings
* @param ... all strings
* @return the accumulated length of all strings
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
size_t cx_strlen(
size_t count,
...
* Concatenates strings.
*
* The resulting string will be allocated by the specified allocator.
- * So developers \em must pass the return value to cx_strfree_a() eventually.
+ * So developers @em must pass the return value to cx_strfree_a() eventually.
*
- * If \p str already contains a string, the memory will be reallocated and
+ * If @p str already contains a string, the memory will be reallocated and
* the other strings are appended. Otherwise, new memory is allocated.
*
- * \note It is guaranteed that there is only one allocation.
+ * If memory allocation fails, the pointer in the returned string will
+ * be @c NULL. Depending on the allocator, @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
* @param alloc the allocator to use
* @param str the string the other strings shall be concatenated to
* @param count the number of the other following strings to concatenate
- * @param ... all other strings
+ * @param ... all other UCX strings
* @return the concatenated string
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
cxmutstr cx_strcat_ma(
- CxAllocator const *alloc,
+ const CxAllocator *alloc,
cxmutstr str,
size_t count,
...
* Concatenates strings and returns a new string.
*
* The resulting string will be allocated by the specified allocator.
- * So developers \em must pass the return value to cx_strfree_a() eventually.
+ * So developers @em must pass the return value to cx_strfree_a() eventually.
+ *
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL. Depending on the allocator, @c errno might be set.
*
- * \note It is guaranteed that there is only one allocation.
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param alloc the allocator to use
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param alloc (@c CxAllocator*) the allocator to use
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other UCX strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat_a(alloc, count, ...) \
cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
/**
* Concatenates strings and returns a new string.
*
- * The resulting string will be allocated by standard \c malloc().
- * So developers \em must pass the return value to cx_strfree() eventually.
+ * The resulting string will be allocated by the cxDefaultAllocator.
+ * So developers @em must pass the return value to cx_strfree() eventually.
*
- * \note It is guaranteed that there is only one allocation.
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL and @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other UCX strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat(count, ...) \
cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
/**
* Concatenates strings.
*
- * The resulting string will be allocated by standard \c malloc().
- * So developers \em must pass the return value to cx_strfree() eventually.
+ * The resulting string will be allocated by the cxDefaultAllocator.
+ * So developers @em must pass the return value to cx_strfree() eventually.
*
- * If \p str already contains a string, the memory will be reallocated and
+ * If @p str already contains a string, the memory will be reallocated and
* the other strings are appended. Otherwise, new memory is allocated.
*
- * \note It is guaranteed that there is only one allocation.
+* If memory allocation fails, the pointer in the returned string will
+ * be @c NULL and @c errno might be set.
+ *
+ * @note It is guaranteed that there is only one allocation for the
+ * resulting string.
* It is also guaranteed that the returned string is zero-terminated.
*
- * @param str the string the other strings shall be concatenated to
- * @param count the number of the other following strings to concatenate
- * @param ... all other strings
- * @return the concatenated string
+ * @param str (@c cxmutstr) the string the other strings shall be concatenated to
+ * @param count (@c size_t) the number of the other following strings to concatenate
+ * @param ... all other strings
+ * @return (@c cxmutstr) the concatenated string
*/
#define cx_strcat_m(str, count, ...) \
cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
/**
* Returns a substring starting at the specified location.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubsl()
* @see cx_strsubs_m()
* @see cx_strsubsl_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strsubs(
cxstring string,
size_t start
/**
* Returns a substring starting at the specified location.
*
- * The returned string will be limited to \p length bytes or the number
- * of bytes available in \p string, whichever is smaller.
+ * The returned string will be limited to @p length bytes or the number
+ * of bytes available in @p string, whichever is smaller.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
* @param length the maximum length of the returned string
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubs()
* @see cx_strsubs_m()
* @see cx_strsubsl_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strsubsl(
cxstring string,
size_t start,
/**
* Returns a substring starting at the specified location.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubsl_m()
* @see cx_strsubs()
* @see cx_strsubsl()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strsubs_m(
cxmutstr string,
size_t start
/**
* Returns a substring starting at the specified location.
*
- * The returned string will be limited to \p length bytes or the number
- * of bytes available in \p string, whichever is smaller.
+ * The returned string will be limited to @p length bytes or the number
+ * of bytes available in @p string, whichever is smaller.
*
- * \attention the new string references the same memory area as the
- * input string and is usually \em not zero-terminated.
+ * @attention the new string references the same memory area as the
+ * input string and is usually @em not zero-terminated.
* Use cx_strdup() to get a copy.
*
* @param string input string
* @param start start location of the substring
* @param length the maximum length of the returned string
- * @return a substring of \p string starting at \p start
+ * @return a substring of @p string starting at @p start
*
* @see cx_strsubs_m()
* @see cx_strsubs()
* @see cx_strsubsl()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strsubsl_m(
cxmutstr string,
size_t start,
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the first location of \p chr
+ * @return a substring starting at the first location of @p chr
*
* @see cx_strchr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strchr(
cxstring string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the first location of \p chr
+ * @return a substring starting at the first location of @p chr
*
* @see cx_strchr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strchr_m(
cxmutstr string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the last location of \p chr
+ * @return a substring starting at the last location of @p chr
*
* @see cx_strrchr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strrchr(
cxstring string,
int chr
*
* @param string the string where to locate the character
* @param chr the character to locate
- * @return a substring starting at the last location of \p chr
+ * @return a substring starting at the last location of @p chr
*
* @see cx_strrchr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strrchr_m(
cxmutstr string,
int chr
* Returns a substring starting at the location of the first occurrence of the
* specified string.
*
- * If \p haystack does not contain \p needle, an empty string is returned.
+ * If @p haystack does not contain @p needle, an empty string is returned.
*
- * If \p needle is an empty string, the complete \p haystack is
+ * If @p needle is an empty string, the complete @p haystack is
* returned.
*
* @param haystack the string to be scanned
* @param needle string containing the sequence of characters to match
* @return a substring starting at the first occurrence of
- * \p needle, or an empty string, if the sequence is not
+ * @p needle, or an empty string, if the sequence is not
* contained
* @see cx_strstr_m()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strstr(
cxstring haystack,
cxstring needle
* Returns a substring starting at the location of the first occurrence of the
* specified string.
*
- * If \p haystack does not contain \p needle, an empty string is returned.
+ * If @p haystack does not contain @p needle, an empty string is returned.
*
- * If \p needle is an empty string, the complete \p haystack is
+ * If @p needle is an empty string, the complete @p haystack is
* returned.
*
* @param haystack the string to be scanned
* @param needle string containing the sequence of characters to match
* @return a substring starting at the first occurrence of
- * \p needle, or an empty string, if the sequence is not
+ * @p needle, or an empty string, if the sequence is not
* contained
* @see cx_strstr()
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strstr_m(
cxmutstr haystack,
cxstring needle
/**
* Splits a given string using a delimiter string.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
* @param string the string to split
* @param delim the delimiter
* @param limit the maximum number of split items
- * @param output a pre-allocated array of at least \p limit length
+ * @param output a preallocated array of at least @p limit length
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(4, 3)
+cx_attr_export
size_t cx_strsplit(
cxstring string,
cxstring delim,
/**
* Splits a given string using a delimiter string.
*
- * The array pointed to by \p output will be allocated by \p allocator.
+ * The array pointed to by @p output will be allocated by @p allocator.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
- * \attention If allocation fails, the \c NULL pointer will be written to
- * \p output and the number returned will be zero.
+ * @attention If allocation fails, the @c NULL pointer will be written to
+ * @p output and the number returned will be zero.
*
* @param allocator the allocator to use for allocating the resulting array
* @param string the string to split
* written to
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(5)
+cx_attr_export
size_t cx_strsplit_a(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxstring string,
cxstring delim,
size_t limit,
/**
* Splits a given string using a delimiter string.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
* @param string the string to split
* @param delim the delimiter
* @param limit the maximum number of split items
- * @param output a pre-allocated array of at least \p limit length
+ * @param output a preallocated array of at least @p limit length
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(4, 3)
+cx_attr_export
size_t cx_strsplit_m(
cxmutstr string,
cxstring delim,
/**
* Splits a given string using a delimiter string.
*
- * The array pointed to by \p output will be allocated by \p allocator.
+ * The array pointed to by @p output will be allocated by @p allocator.
*
- * \note The resulting array contains strings that point to the source
- * \p string. Use cx_strdup() to get copies.
+ * @note The resulting array contains strings that point to the source
+ * @p string. Use cx_strdup() to get copies.
*
- * \attention If allocation fails, the \c NULL pointer will be written to
- * \p output and the number returned will be zero.
+ * @attention If allocation fails, the @c NULL pointer will be written to
+ * @p output and the number returned will be zero.
*
* @param allocator the allocator to use for allocating the resulting array
* @param string the string to split
* written to
* @return the actual number of split items
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_access_w(5)
+cx_attr_export
size_t cx_strsplit_ma(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxmutstr string,
cxstring delim,
size_t limit,
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
int cx_strcmp(
cxstring s1,
cxstring s2
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal ignoring case
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal ignoring case
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
int cx_strcasecmp(
cxstring s1,
cxstring s2
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
int cx_strcmp_p(
- void const *s1,
- void const *s2
+ const void *s1,
+ const void *s2
);
/**
*
* @param s1 the first string
* @param s2 the second string
- * @return negative if \p s1 is smaller than \p s2, positive if \p s1 is larger
- * than \p s2, zero if both strings equal ignoring case
+ * @return negative if @p s1 is smaller than @p s2, positive if @p s1 is larger
+ * than @p s2, zero if both strings equal ignoring case
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
int cx_strcasecmp_p(
- void const *s1,
- void const *s2
+ const void *s1,
+ const void *s2
);
/**
* Creates a duplicate of the specified string.
*
- * The new string will contain a copy allocated by \p allocator.
+ * The new string will contain a copy allocated by @p allocator.
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
* @param allocator the allocator to use
* @param string the string to duplicate
* @return a duplicate of the string
* @see cx_strdup()
*/
-__attribute__((__warn_unused_result__, __nonnull__))
-cxmutstr cx_strdup_a(
- CxAllocator const *allocator,
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
+cxmutstr cx_strdup_a_(
+ const CxAllocator *allocator,
cxstring string
);
/**
* Creates a duplicate of the specified string.
*
- * The new string will contain a copy allocated by standard
- * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ * The new string will contain a copy allocated by @p allocator.
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
+ * @param allocator (@c CxAllocator*) the allocator to use
* @param string the string to duplicate
- * @return a duplicate of the string
- * @see cx_strdup_a()
- */
-#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
-
-
-/**
- * Creates a duplicate of the specified string.
- *
- * The new string will contain a copy allocated by \p allocator.
- *
- * \note The returned string is guaranteed to be zero-terminated.
- *
- * @param allocator the allocator to use
- * @param string the string to duplicate
- * @return a duplicate of the string
- * @see cx_strdup_m()
+ * @return (@c cxmutstr) a duplicate of the string
+ * @see cx_strdup()
+ * @see cx_strfree_a()
*/
-#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
+#define cx_strdup_a(allocator, string) \
+ cx_strdup_a_((allocator), cx_strcast(string))
/**
* Creates a duplicate of the specified string.
*
- * The new string will contain a copy allocated by standard
- * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ * The new string will contain a copy allocated by the cxDefaultAllocator.
+ * So developers @em must pass the return value to cx_strfree().
*
- * \note The returned string is guaranteed to be zero-terminated.
+ * @note The returned string is guaranteed to be zero-terminated.
*
* @param string the string to duplicate
- * @return a duplicate of the string
- * @see cx_strdup_ma()
+ * @return (@c cxmutstr) a duplicate of the string
+ * @see cx_strdup_a()
+ * @see cx_strfree()
*/
-#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
+#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
/**
* Omits leading and trailing spaces.
*
- * \note the returned string references the same memory, thus you
- * must \em not free the returned memory.
+ * @note the returned string references the same memory, thus you
+ * must @em not free the returned memory.
*
* @param string the string that shall be trimmed
* @return the trimmed string
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxstring cx_strtrim(cxstring string);
/**
* Omits leading and trailing spaces.
*
- * \note the returned string references the same memory, thus you
- * must \em not free the returned memory.
+ * @note the returned string references the same memory, thus you
+ * must @em not free the returned memory.
*
* @param string the string that shall be trimmed
* @return the trimmed string
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
cxmutstr cx_strtrim_m(cxmutstr string);
/**
*
* @param string the string to check
* @param prefix the prefix the string should have
- * @return \c true, if and only if the string has the specified prefix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
bool cx_strprefix(
cxstring string,
cxstring prefix
*
* @param string the string to check
* @param suffix the suffix the string should have
- * @return \c true, if and only if the string has the specified suffix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
bool cx_strsuffix(
cxstring string,
cxstring suffix
*
* @param string the string to check
* @param prefix the prefix the string should have
- * @return \c true, if and only if the string has the specified prefix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified prefix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
bool cx_strcaseprefix(
cxstring string,
cxstring prefix
*
* @param string the string to check
* @param suffix the suffix the string should have
- * @return \c true, if and only if the string has the specified suffix,
- * \c false otherwise
+ * @return @c true, if and only if the string has the specified suffix,
+ * @c false otherwise
*/
-__attribute__((__warn_unused_result__))
+cx_attr_nodiscard
+cx_attr_export
bool cx_strcasesuffix(
cxstring string,
cxstring suffix
);
/**
- * Converts the string to lower case.
- *
- * The change is made in-place. If you want a copy, use cx_strdup(), first.
- *
- * @param string the string to modify
- * @see cx_strdup()
- */
-void cx_strlower(cxmutstr string);
-
-/**
- * Converts the string to upper case.
- *
- * The change is made in-place. If you want a copy, use cx_strdup(), first.
- *
- * @param string the string to modify
- * @see cx_strdup()
- */
-void cx_strupper(cxmutstr string);
-
-/**
- * Replaces a pattern in a string with another string.
+ * Replaces a string with another string.
*
- * The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces at most @p replmax occurrences.
*
- * The returned string will be allocated by \p allocator and is guaranteed
+ * The returned string will be allocated by @p allocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
*
* @param allocator the allocator to use
* @param str the string where replacements should be applied
- * @param pattern the pattern to search for
+ * @param search the string to search for
* @param replacement the replacement string
* @param replmax maximum number of replacements
* @return the resulting string after applying the replacements
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nodiscard
+cx_attr_nonnull
+cx_attr_export
cxmutstr cx_strreplacen_a(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxstring str,
- cxstring pattern,
+ cxstring search,
cxstring replacement,
size_t replmax
);
/**
- * Replaces a pattern in a string with another string.
+ * Replaces a string with another string.
*
- * The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces at most @p replmax occurrences.
*
- * The returned string will be allocated by \c malloc() and is guaranteed
+ * The returned string will be allocated by the cxDefaultAllocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @param replmax maximum number of replacements
- * @return the resulting string after applying the replacements
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param search (@c cxstring) the string to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @param replmax (@c size_t) maximum number of replacements
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
-#define cx_strreplacen(str, pattern, replacement, replmax) \
-cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
+#define cx_strreplacen(str, search, replacement, replmax) \
+cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax)
/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
+ * Replaces a string with another string.
*
- * The returned string will be allocated by \p allocator and is guaranteed
+ * The returned string will be allocated by @p allocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param allocator the allocator to use
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
+ * @param allocator (@c CxAllocator*) the allocator to use
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param search (@c cxstring) the string to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
-#define cx_strreplace_a(allocator, str, pattern, replacement) \
-cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
+#define cx_strreplace_a(allocator, str, search, replacement) \
+cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX)
/**
- * Replaces a pattern in a string with another string.
- *
- * The pattern is taken literally and is no regular expression.
- * Replaces at most \p replmax occurrences.
+ * Replaces a string with another string.
*
- * The returned string will be allocated by \c malloc() and is guaranteed
+ * The returned string will be allocated by the cxDefaultAllocator and is guaranteed
* to be zero-terminated.
*
* If allocation fails, or the input string is empty,
* the returned string will be empty.
*
- * @param str the string where replacements should be applied
- * @param pattern the pattern to search for
- * @param replacement the replacement string
- * @return the resulting string after applying the replacements
+ * @param str (@c cxstring) the string where replacements should be applied
+ * @param search (@c cxstring) the string to search for
+ * @param replacement (@c cxstring) the replacement string
+ * @return (@c cxmutstr) the resulting string after applying the replacements
*/
-#define cx_strreplace(str, pattern, replacement) \
-cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
+#define cx_strreplace(str, search, replacement) \
+cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX)
/**
* Creates a string tokenization context.
* @param limit the maximum number of tokens that shall be returned
* @return a new string tokenization context
*/
-__attribute__((__warn_unused_result__))
-CxStrtokCtx cx_strtok(
+cx_attr_nodiscard
+cx_attr_export
+CxStrtokCtx cx_strtok_(
cxstring str,
cxstring delim,
size_t limit
);
/**
-* Creates a string tokenization context for a mutable string.
-*
-* @param str the string to tokenize
-* @param delim the delimiter (must not be empty)
-* @param limit the maximum number of tokens that shall be returned
-* @return a new string tokenization context
-*/
-__attribute__((__warn_unused_result__))
-CxStrtokCtx cx_strtok_m(
- cxmutstr str,
- cxstring delim,
- size_t limit
-);
+ * Creates a string tokenization context.
+ *
+ * @param str the string to tokenize
+ * @param delim the delimiter string (must not be empty)
+ * @param limit (@c size_t) the maximum number of tokens that shall be returned
+ * @return (@c CxStrtokCtx) a new string tokenization context
+ */
+#define cx_strtok(str, delim, limit) \
+ cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit))
/**
* Returns the next token.
* @return true if successful, false if the limit or the end of the string
* has been reached
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_w(2)
+cx_attr_export
bool cx_strtok_next(
CxStrtokCtx *ctx,
cxstring *token
* Returns the next token of a mutable string.
*
* The token will point to the source string.
+ *
+ * @attention
* If the context was not initialized over a mutable string, modifying
* the data of the returned token is undefined behavior.
*
* @return true if successful, false if the limit or the end of the string
* has been reached
*/
-__attribute__((__warn_unused_result__, __nonnull__))
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_access_w(2)
+cx_attr_export
bool cx_strtok_next_m(
CxStrtokCtx *ctx,
cxmutstr *token
* @param delim array of more delimiters
* @param count number of elements in the array
*/
-__attribute__((__nonnull__))
+cx_attr_nonnull
+cx_attr_access_r(2, 3)
+cx_attr_export
void cx_strtok_delim(
CxStrtokCtx *ctx,
- cxstring const *delim,
+ const cxstring *delim,
size_t count
);
+/* ------------------------------------------------------------------------- *
+ * string to number conversion functions *
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep);
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep);
+
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export
+int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep);
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou64_lc(str, output, base, groupsep) cx_strtou64_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc_(cx_strcast(str), output, base, groupsep)
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtos(str, output, base) cx_strtos_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi(str, output, base) cx_strtoi_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtol(str, output, base) cx_strtol_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoll(str, output, base) cx_strtoll_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi8(str, output, base) cx_strtoi8_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi16(str, output, base) cx_strtoi16_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi32(str, output, base) cx_strtoi32_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoi64(str, output, base) cx_strtoi64_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoz(str, output, base) cx_strtoz_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtous(str, output, base) cx_strtous_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou(str, output, base) cx_strtou_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoul(str, output, base) cx_strtoul_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtoull(str, output, base) cx_strtoull_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou8(str, output, base) cx_strtou8_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou16(str, output, base) cx_strtou16_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou32(str, output, base) cx_strtou32_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base.
+ * It sets errno to ERANGE when the target datatype is too small.
+ *
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()).
+ *
+ * @param str the string to convert
+ * @param output a pointer to the integer variable where the result shall be stored
+ * @param base 2, 8, 10, or 16
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",")
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc_(cx_strcast(str), output, decsep, groupsep)
+
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ *
+ * @param str the string to convert
+ * @param output a pointer to the double variable where the result shall be stored
+ * @param decsep the decimal separator
+ * @param groupsep each character in this string is treated as group separator and ignored during conversion
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc_(cx_strcast(str), output, decsep, groupsep)
+
+/**
+ * Converts a string to a single precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the float variable where the result shall be stored
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",")
+
+/**
+ * Converts a string to a double precision floating point number.
+ *
+ * The function returns non-zero when conversion is not possible.
+ * In that case the function sets errno to EINVAL when the reason is an invalid character.
+ *
+ * The decimal separator is assumed to be a dot character.
+ * The comma character is treated as group separator and ignored during parsing.
+ * If you want to choose a different format, use cx_strtof_lc().
+ *
+ * @param str the string to convert
+ * @param output a pointer to the double variable where the result shall be stored
+ * @retval zero success
+ * @retval non-zero conversion was not possible
+ */
+#define cx_strtod(str, output) cx_strtod_lc_(cx_strcast(str), output, '.', ",")
#ifdef __cplusplus
} // extern "C"
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @file: test.h
+ *
+ * UCX Test Framework.
+ *
+ * Usage of this test framework:
+ *
+ * **** IN HEADER FILE: ****
+ *
+ * <code>
+ * CX_TEST(function_name);
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional
+ * </code>
+ *
+ * **** IN SOURCE FILE: ****
+ * <code>
+ * CX_TEST_SUBROUTINE(subroutine_name, paramlist) {
+ * // tests with CX_TEST_ASSERT()
+ * }
+ *
+ * CX_TEST(function_name) {
+ * // memory allocation and other stuff here
+ * #CX_TEST_DO {
+ * // tests with CX_TEST_ASSERT() and/or
+ * // calls with CX_TEST_CALL_SUBROUTINE() here
+ * }
+ * // cleanup of memory here
+ * }
+ * </code>
+ *
+ * @attention Do not call own functions within a test, that use
+ * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE().
+ *
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ *
+ */
+
+#ifndef UCX_TEST_H
+#define UCX_TEST_H
+
+#include "common.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <setjmp.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __FUNCTION__
+/**
+ * Alias for the <code>__func__</code> preprocessor macro.
+ * Some compilers use <code>__func__</code> and others use __FUNCTION__.
+ * We use __FUNCTION__ so we define it for those compilers which use
+ * <code>__func__</code>.
+ */
+#define __FUNCTION__ __func__
+#endif
+
+#if !defined(__clang__) && __GNUC__ > 3
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+
+/** Type for the CxTestSuite. */
+typedef struct CxTestSuite CxTestSuite;
+
+/** Pointer to a test function. */
+typedef void(*CxTest)(CxTestSuite *, void *, cx_write_func);
+
+/** Type for the internal list of test cases. */
+typedef struct CxTestSet CxTestSet;
+
+/** Structure for the internal list of test cases. */
+struct CxTestSet {
+
+ /** Test case. */
+ CxTest test;
+
+ /** Pointer to the next list element. */
+ CxTestSet *next;
+};
+
+/**
+ * A test suite containing multiple test cases.
+ */
+struct CxTestSuite {
+
+ /** The number of successful tests after the suite has been run. */
+ unsigned int success;
+
+ /** The number of failed tests after the suite has been run. */
+ unsigned int failure;
+
+ /** The optional name of this test suite. */
+ const char *name;
+
+ /**
+ * Internal list of test cases.
+ * Use cx_test_register() to add tests to this list.
+ */
+ CxTestSet *tests;
+};
+
+/**
+ * Creates a new test suite.
+ * @param name optional name of the suite
+ * @return a new test suite
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_cstr_arg(1)
+cx_attr_malloc
+static inline CxTestSuite* cx_test_suite_new(const char *name) {
+ CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite));
+ if (suite != NULL) {
+ suite->name = name;
+ suite->success = 0;
+ suite->failure = 0;
+ suite->tests = NULL;
+ }
+
+ return suite;
+}
+
+/**
+ * Deallocates a test suite.
+ *
+ * @param suite the test suite to free
+ */
+static inline void cx_test_suite_free(CxTestSuite* suite) {
+ if (suite == NULL) return;
+ CxTestSet *l = suite->tests;
+ while (l != NULL) {
+ CxTestSet *e = l;
+ l = l->next;
+ free(e);
+ }
+ free(suite);
+}
+
+/**
+ * Registers a test function with the specified test suite.
+ *
+ * @param suite the suite, the test function shall be added to
+ * @param test the test function to register
+ * @retval zero success
+ * @retval non-zero failure
+ */
+cx_attr_nonnull
+static inline int cx_test_register(CxTestSuite* suite, CxTest test) {
+ CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet));
+ if (t) {
+ t->test = test;
+ t->next = NULL;
+ if (suite->tests == NULL) {
+ suite->tests = t;
+ } else {
+ CxTestSet *last = suite->tests;
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = t;
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified stream.
+ * @param suite the test suite to run
+ * @param out_target the target buffer or file to write the output to
+ * @param out_writer the write function writing to @p out_target
+ */
+cx_attr_nonnull
+static inline void cx_test_run(CxTestSuite *suite,
+ void *out_target, cx_write_func out_writer) {
+ if (suite->name == NULL) {
+ out_writer("*** Test Suite ***\n", 1, 19, out_target);
+ } else {
+ out_writer("*** Test Suite : ", 1, 17, out_target);
+ out_writer(suite->name, 1, strlen(suite->name), out_target);
+ out_writer(" ***\n", 1, 5, out_target);
+ }
+ suite->success = 0;
+ suite->failure = 0;
+ for (CxTestSet *elem = suite->tests; elem; elem = elem->next) {
+ elem->test(suite, out_target, out_writer);
+ }
+ out_writer("\nAll test completed.\n", 1, 21, out_target);
+ char total[80];
+ int len = snprintf(
+ total, 80,
+ " Total: %u\n Success: %u\n Failure: %u\n\n",
+ suite->success + suite->failure, suite->success, suite->failure
+ );
+ out_writer(total, 1, len, out_target);
+}
+
+/**
+ * Runs a test suite and writes the test log to the specified FILE stream.
+ * @param suite (@c CxTestSuite*) the test suite to run
+ * @param file (@c FILE*) the target file to write the output to
+ */
+#define cx_test_run_f(suite, file) cx_test_run(suite, (void*)file, (cx_write_func)fwrite)
+
+/**
+ * Runs a test suite and writes the test log to stdout.
+ * @param suite (@c CxTestSuite*) the test suite to run
+ */
+#define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout)
+
+/**
+ * Macro for a #CxTest function header.
+ *
+ * Use this macro to declare and/or define a #CxTest function.
+ *
+ * @param name the name of the test function
+ */
+#define CX_TEST(name) void name(CxTestSuite* _suite_,void *_output_, cx_write_func _writefnc_)
+
+/**
+ * Defines the scope of a test.
+ *
+ * @code
+ * CX_TEST(my_test_name) {
+ * // setup code
+ * CX_TEST_DO {
+ * // your tests go here
+ * }
+ * // tear down code
+ * }
+ * @endcode
+ *
+ * @attention Any CX_TEST_ASSERT() calls must be performed in scope of
+ * #CX_TEST_DO.
+ */
+#define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
+ _writefnc_(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\
+ _writefnc_("... ", 1, 4, _output_);\
+ jmp_buf _env_;\
+ for (unsigned int _cx_test_loop_ = 0 ;\
+ _cx_test_loop_ == 0 && !setjmp(_env_);\
+ _writefnc_("success.\n", 1, 9, _output_),\
+ _suite_->success++, _cx_test_loop_++)
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition (@c bool) the condition to check
+ * @param message (@c char*) the message that shall be printed out on failure
+ */
+#define CX_TEST_ASSERTM(condition,message) if (!(condition)) { \
+ const char *_assert_msg_ = message; \
+ _writefnc_(_assert_msg_, 1, strlen(_assert_msg_), _output_); \
+ _writefnc_(".\n", 1, 2, _output_); \
+ _suite_->failure++; \
+ longjmp(_env_, 1);\
+ } (void) 0
+
+/**
+ * Checks a test assertion.
+ * If the assertion is correct, the test carries on. If the assertion is not
+ * correct, the specified message (terminated by a dot and a line break) is
+ * written to the test suites output stream.
+ * @param condition (@c bool) the condition to check
+ */
+#define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
+
+/**
+ * Macro for a test subroutine function header.
+ *
+ * Use this to declare and/or define a subroutine that can be called by using
+ * CX_TEST_CALL_SUBROUTINE().
+ *
+ * @param name the name of the subroutine
+ * @param ... the parameter list
+ *
+ * @see CX_TEST_CALL_SUBROUTINE()
+ */
+#define CX_TEST_SUBROUTINE(name,...) void name(CxTestSuite* _suite_,\
+ void *_output_, cx_write_func _writefnc_, jmp_buf _env_, __VA_ARGS__)
+
+/**
+ * Macro for calling a test subroutine.
+ *
+ * Subroutines declared with CX_TEST_SUBROUTINE() can be called by using this
+ * macro.
+ *
+ * @remark You may <b>only</b> call subroutines within a #CX_TEST_DO block.
+ *
+ * @param name the name of the subroutine
+ * @param ... the argument list
+ *
+ * @see CX_TEST_SUBROUTINE()
+ */
+#define CX_TEST_CALL_SUBROUTINE(name,...) \
+ name(_suite_,_output_,_writefnc_,_env_,__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UCX_TEST_H */
+
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
- * \file tree.h
- * \brief Interface for tree implementations.
- * \author Olaf Wintermann
- * \author Mike Becker
- * \version 3.0
- * \copyright 2-Clause BSD License
+ * @file tree.h
+ * @brief Interface for tree implementations.
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ * @copyright 2-Clause BSD License
*/
#ifndef UCX_TREE_H
#define UCX_TREE_H
#include "common.h"
-#include "allocator.h"
+
+#include "collection.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
- * Adds a sibling to the current tree node.
- *
- * In case your struct does not have a \p prev or a \p parent pointer,
- * specify a negative location. The location of the \p next pointer is
- * mandatory.
- *
- * \attention Do not use this function to add siblings in a tree where the
- * nodes store a pointer to the last sibling because that would not be modified by this function.
- *
- * \remark If yo do not provide a location to the parent pointer, a call to this function is
- * effectively the same as a call to cx_linked_list_add().
- *
- * @param node a pointer to the node
- * @param loc_prev the location of a \c prev pointer within your node struct
- * @param loc_next the location of a \c next pointer within your node struct
- * @param loc_parent the location of a \c parent pointer within your node struct
- * @param new_node the new node that shall be added as a sibling
- */
-void cx_tree_add_sibling(void *node,
- ptrdiff_t loc_prev, ptrdiff_t loc_next,
- ptrdiff_t loc_parent,
- void *new_node)
-__attribute__((__nonnull__));
-
-/**
- * Adds a node to the list of children.
- *
- * \par Example with a full structure
- * A full tree node structure may look like this:
- * \code
- * typedef struct MyTreeNode MyTreeNode;
- * struct MyTreeNode {
- * MyTreeNode* parent;
- * MyTreeNode* first_child;
- * MyTreeNode* last_child;
- * MyTreeNode* prev_sibling;
- * MyTreeNode* next_sibling;
- * // ...contents...
- * }
- * \endcode
- * Adding a new child to a node with the above structure can be performed with the following call:
- * \code
- * MyTreeNode *node, *child; // given
- * cx_tree_add_child(&node->first_child, &node->last_child,
- * offsetof(MyTreeNode, prev_sibling), offsetof(MyTreeNode, next_sibling),
- * child, offsetof(MyTreeNode, parent), node);
- * \endcode
- *
- * \par Example with a reduced structure
- * The minimal reasonable structure with parent pointer looks like this:
- * \code
- * typedef struct MyTreeNode MyTreeNode;
- * struct MyTreeNode {
- * MyTreeNode* parent;
- * MyTreeNode* children;
- * MyTreeNode* next_sibling;
- * // ...contents...
- * }
- * \endcode
- * This simplifies the function call to:
- * \code
- * MyTreeNode *node, *child; // given
- * cx_tree_add_child(&node->children, NULL, -1, offsetof(MyTreeNode, next_sibling),
- * child, offsetof(MyTreeNode, parent), node);
- * \endcode
- *
- * \remark If your tree structure does not possess a parent pointer, a call to this function is
- * effectively the same as a call to cx_linked_list_add().
- *
- * @param children_begin a pointer to the begin node pointer (if your list has one)
- * @param children_end a pointer to the end node pointer (if your list has one)
- * @param loc_prev the location of a \c prev pointer within your node struct
- * @param loc_next the location of a \c next pointer within your node struct
- * @param new_node a pointer to the node that shall be appended
- * @param loc_parent the location of a \c parent pointer within your node struct
+ * A depth-first tree iterator.
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_iterator_s {
+ /**
+ * Base members.
+ */
+ CX_ITERATOR_BASE;
+ /**
+ * Indicates whether the subtree below the current node shall be skipped.
+ */
+ bool skip;
+ /**
+ * Set to true, when the iterator shall visit a node again
+ * when all it's children have been processed.
+ */
+ bool visit_on_exit;
+ /**
+ * True, if this iterator is currently leaving the node.
+ */
+ bool exiting;
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+ /**
+ * Offset in the node struct for the next pointer.
+ */
+ ptrdiff_t loc_next;
+ /**
+ * The total number of distinct nodes that have been passed so far.
+ */
+ size_t counter;
+ /**
+ * The currently observed node.
+ *
+ * This is the same what cxIteratorCurrent() would return.
+ */
+ void *node;
+ /**
+ * Stores a copy of the next pointer of the visited node.
+ * Allows freeing a node on exit without corrupting the iteration.
+ */
+ void *node_next;
+ /**
+ * Internal stack.
+ * Will be automatically freed once the iterator becomes invalid.
+ *
+ * If you want to discard the iterator before, you need to manually
+ * call cxTreeIteratorDispose().
+ */
+ void **stack;
+ /**
+ * Internal capacity of the stack.
+ */
+ size_t stack_capacity;
+ union {
+ /**
+ * Internal stack size.
+ */
+ size_t stack_size;
+ /**
+ * The current depth in the tree.
+ * The node with which the iteration starts has depth 1.
+ */
+ size_t depth;
+ };
+} CxTreeIterator;
+
+/**
+ * An element in a visitor queue.
+ */
+struct cx_tree_visitor_queue_s {
+ /**
+ * The tree node to visit.
+ */
+ void *node;
+ /**
+ * The depth of the node.
+ * The first visited node has depth 1.
+ */
+ size_t depth;
+ /**
+ * The next element in the queue or @c NULL.
+ */
+ struct cx_tree_visitor_queue_s *next;
+};
+
+/**
+ * A breadth-first tree iterator.
+ *
+ * This iterator needs to maintain a visitor queue that will be automatically
+ * freed once the iterator becomes invalid.
+ * If you want to discard the iterator before, you MUST manually call
+ * cxTreeVisitorDispose().
+ *
+ * This iterator is not position-aware in a strict sense, as it does not assume
+ * a particular order of elements in the tree. However, the iterator keeps track
+ * of the number of nodes it has passed in a counter variable.
+ * Each node, regardless of the number of passes, is counted only once.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that
+ * iterator. However, if the
+ * underlying data structure is mutated by other means than this iterator (e.g.
+ * elements added or removed), the iterator becomes invalid (regardless of what
+ * cxIteratorValid() returns).
+ *
+ * @see CxIterator
+ */
+typedef struct cx_tree_visitor_s {
+ /**
+ * Base members.
+ */
+ CX_ITERATOR_BASE;
+ /**
+ * Indicates whether the subtree below the current node shall be skipped.
+ */
+ bool skip;
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+ /**
+ * Offset in the node struct for the next pointer.
+ */
+ ptrdiff_t loc_next;
+ /**
+ * The total number of distinct nodes that have been passed so far.
+ */
+ size_t counter;
+ /**
+ * The currently observed node.
+ *
+ * This is the same what cxIteratorCurrent() would return.
+ */
+ void *node;
+ /**
+ * The current depth in the tree.
+ */
+ size_t depth;
+ /**
+ * The next element in the visitor queue.
+ */
+ struct cx_tree_visitor_queue_s *queue_next;
+ /**
+ * The last element in the visitor queue.
+ */
+ struct cx_tree_visitor_queue_s *queue_last;
+} CxTreeVisitor;
+
+/**
+ * Releases internal memory of the given tree iterator.
+ * @param iter the iterator
+ */
+cx_attr_nonnull
+static inline void cxTreeIteratorDispose(CxTreeIterator *iter) {
+ cxFreeDefault(iter->stack);
+ iter->stack = NULL;
+}
+
+/**
+ * Releases internal memory of the given tree visitor.
+ * @param visitor the visitor
+ */
+cx_attr_nonnull
+static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) {
+ struct cx_tree_visitor_queue_s *q = visitor->queue_next;
+ while (q != NULL) {
+ struct cx_tree_visitor_queue_s *next = q->next;
+ cxFreeDefault(q);
+ q = next;
+ }
+}
+
+/**
+ * Advises the iterator to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param iterator (@c CxTreeIterator) the iterator
+ */
+#define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue
+
+/**
+ * Advises the visitor to skip the subtree below the current node and
+ * also continues the current loop.
+ *
+ * @param visitor (@c CxTreeVisitor) the visitor
+ */
+#define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor)
+
+/**
+ * Links a node to a (new) parent.
+ *
+ * If the node has already a parent, it is unlinked, first.
+ * If the parent has children already, the node is @em appended to the list
+ * of all currently existing children.
+ *
* @param parent the parent node
+ * @param node the node that shall be linked
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_unlink()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cx_tree_link(
+ void *parent,
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Unlinks a node from its parent.
+ *
+ * If the node has no parent, this function does nothing.
+ *
+ * @param node the node that shall be unlinked from its parent
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @see cx_tree_link()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cx_tree_unlink(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Macro that can be used instead of the magic value for infinite search depth.
+ */
+#define CX_TREE_SEARCH_INFINITE_DEPTH 0
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified @p node
+ * contains the given @p data or if one of the children might contain
+ * the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ * Zero means exact match and a positive number is an implementation defined
+ * measure for the distance to an exact match.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param data the data that is searched for
+ *
+ * @return 0 if the node contains the data,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+cx_attr_nonnull
+typedef int (*cx_tree_search_data_func)(const void *node, const void *data);
+
+
+/**
+ * Function pointer for a search function.
+ *
+ * A function of this kind shall check if the specified @p node
+ * contains the same @p data as @p new_node or if one of the children might
+ * contain the data.
+ *
+ * The function should use the returned integer to indicate how close the
+ * match is, where a negative number means that it does not match at all.
+ * Zero means exact match and a positive number is an implementation defined
+ * measure for the distance to an exact match.
+ *
+ * For example if a tree stores file path information, a node that is
+ * describing a parent directory of a filename that is searched, shall
+ * return a positive number to indicate that a child node might contain the
+ * searched item. On the other hand, if the node denotes a path that is not a
+ * prefix of the searched filename, the function would return -1 to indicate
+ * that the search does not need to be continued in that branch.
+ *
+ * @param node the node that is currently investigated
+ * @param new_node a new node with the information which is searched
+ *
+ * @return 0 if @p node contains the same data as @p new_node,
+ * positive if one of the children might contain the data,
+ * negative if neither the node, nor the children contains the data
+ */
+cx_attr_nonnull
+typedef int (*cx_tree_search_func)(const void *node, const void *new_node);
+
+/**
+ * Searches for data in a tree.
+ *
+ * When the data cannot be found exactly, the search function might return a
+ * closest result which might be a good starting point for adding a new node
+ * to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the @p sfunc (meaning: the return value of
+ * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+ * @param depth the maximum depth (zero=indefinite, one=just root)
+ * @param data the data to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+cx_attr_nonnull
+cx_attr_access_w(5)
+cx_attr_export
+int cx_tree_search_data(
+ const void *root,
+ size_t depth,
+ const void *data,
+ cx_tree_search_data_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Searches for a node in a tree.
+ *
+ * When no node with the same data can be found, the search function might
+ * return a closest result which might be a good starting point for adding the
+ * new node to the tree (see also #cx_tree_add()).
+ *
+ * Depending on the tree structure it is not necessarily guaranteed that the
+ * "closest" match is uniquely defined. This function will search for a node
+ * with the best match according to the @p sfunc (meaning: the return value of
+ * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary
+ * node matching the criteria is returned.
+ *
+ * @param root the root node
+* @param depth the maximum depth (zero=indefinite, one=just root)
+ * @param node the node to search for
+ * @param sfunc the search function
+ * @param result where the result shall be stored
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero if the node was found exactly, positive if a node was found that
+ * could contain the node (but doesn't right now), negative if the tree does not
+ * contain any node that might be related to the searched data
+ */
+cx_attr_nonnull
+cx_attr_access_w(5)
+cx_attr_export
+int cx_tree_search(
+ const void *root,
+ size_t depth,
+ const void *node,
+ cx_tree_search_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a depth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree iterator needs to maintain a stack of visited nodes, which is
+ * allocated using the cxDefaultAllocator.
+ * When the iterator becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the iterator becomes
+ * invalid by itself, you MUST call cxTreeIteratorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param visit_on_exit set to true, when the iterator shall visit a node again
+ * after processing all children
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree iterator
+ * @see cxTreeIteratorDispose()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxTreeIterator cx_tree_iterator(
+ void *root,
+ bool visit_on_exit,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a breadth-first iterator for a tree with the specified root node.
+ *
+ * @note A tree visitor needs to maintain a queue of to-be visited nodes, which
+ * is allocated using the cxDefaultAllocator.
+ * When the visitor becomes invalid, this memory is automatically released.
+ * However, if you wish to cancel the iteration before the visitor becomes
+ * invalid by itself, you MUST call cxTreeVisitorDispose() manually to release
+ * the memory.
+ *
+ * @remark The returned iterator does not support cxIteratorFlagRemoval().
+ *
+ * @param root the root node
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree visitor
+ * @see cxTreeVisitorDispose()
+ */
+cx_attr_nodiscard
+cx_attr_export
+CxTreeVisitor cx_tree_visitor(
+ void *root,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Describes a function that creates a tree node from the specified data.
+ * The first argument points to the data the node shall contain and
+ * the second argument may be used for additional data (e.g. an allocator).
+ * Functions of this type shall either return a new pointer to a newly
+ * created node or @c NULL when allocation fails.
+ *
+ * @note the function may leave the node pointers in the struct uninitialized.
+ * The caller is responsible to set them according to the intended use case.
+ */
+cx_attr_nonnull_arg(1)
+typedef void *(*cx_tree_node_create_func)(const void *, void *);
+
+/**
+ * The local search depth for a new subtree when adding multiple elements.
+ * The default value is 3.
+ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to
+ * implement optimized insertion of multiple elements into a tree.
+ */
+cx_attr_export
+extern unsigned int cx_tree_add_look_around_depth;
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, leaving
+ * the iterator in a valid state pointing to the element that could not be
+ * added.
+ * Also, the pointer of the created node will be stored to @p failed.
+ * The integer returned by this function denotes the number of elements obtained
+ * from the @p iter that have been successfully processed.
+ * When all elements could be processed, a @c NULL pointer will be written to
+ * @p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param iter a pointer to an arbitrary iterator
+ * @param num the maximum number of elements to obtain from the iterator
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param root the root node of the tree
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of nodes created and added
+ * @see cx_tree_add()
+ */
+cx_attr_nonnull_arg(1, 3, 4, 6, 7)
+cx_attr_access_w(6)
+cx_attr_export
+size_t cx_tree_add_iter(
+ struct cx_iterator_base_s *iter,
+ size_t num,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Adds multiple elements efficiently to a tree.
+ *
+ * Once an element cannot be added to the tree, this function returns, storing
+ * the pointer of the created node to @p failed.
+ * The integer returned by this function denotes the number of elements from
+ * the @p src array that have been successfully processed.
+ * When all elements could be processed, a @c NULL pointer will be written to
+ * @p failed.
+ *
+ * The advantage of this function compared to multiple invocations of
+ * #cx_tree_add() is that the search for the insert locations is not always
+ * started from the root node.
+ * Instead, the function checks #cx_tree_add_look_around_depth many parent nodes
+ * of the current insert location before starting from the root node again.
+ * When the variable is set to zero, only the last found location is checked
+ * again.
+ *
+ * Refer to the documentation of #cx_tree_add() for more details.
+ *
+ * @param src a pointer to the source data array
+ * @param num the number of elements in the @p src array
+ * @param elem_size the size of each element in the @p src array
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param failed location where the pointer to a failed node shall be stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the number of array elements successfully processed
+ * @see cx_tree_add()
+ */
+cx_attr_nonnull_arg(1, 4, 5, 7, 8)
+cx_attr_access_w(7)
+cx_attr_export
+size_t cx_tree_add_array(
+ const void *src,
+ size_t num,
+ size_t elem_size,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Adds data to a tree.
+ *
+ * An adequate location where to add the new tree node is searched with the
+ * specified @p sfunc.
+ *
+ * When a location is found, the @p cfunc will be invoked with @p cdata.
+ *
+ * The node returned by @p cfunc will be linked into the tree.
+ * When @p sfunc returned a positive integer, the new node will be linked as a
+ * child. The other children (now siblings of the new node) are then checked
+ * with @p sfunc, whether they could be children of the new node and re-linked
+ * accordingly.
+ *
+ * When @p sfunc returned zero and the found node has a parent, the new
+ * node will be added as sibling - otherwise, the new node will be added
+ * as a child.
+ *
+ * When @p sfunc returned a negative value, the new node will not be added to
+ * the tree and this function returns a non-zero value.
+ * The caller should check if @p cnode contains a node pointer and deal with the
+ * node that could not be added.
+ *
+ * This function also returns a non-zero value when @p cfunc tries to allocate
+ * a new node but fails to do so. In that case, the pointer stored to @p cnode
+ * will be @c NULL.
+ *
+ * Multiple elements can be added more efficiently with
+ * #cx_tree_add_array() or #cx_tree_add_iter().
+ *
+ * @param src a pointer to the data
+ * @param sfunc a search function
+ * @param cfunc a node creation function
+ * @param cdata optional additional data
+ * @param cnode the location where a pointer to the new node is stored
+ * @param root the root node of the tree
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return zero when a new node was created and added to the tree,
+ * non-zero otherwise
+ */
+cx_attr_nonnull_arg(1, 2, 3, 5, 6)
+cx_attr_access_w(5)
+cx_attr_export
+int cx_tree_add(
+ const void *src,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **cnode,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+
+/**
+ * Tree class type.
+ */
+typedef struct cx_tree_class_s cx_tree_class;
+
+/**
+ * Base structure that can be used for tree nodes in a #CxTree.
+ */
+struct cx_tree_node_base_s {
+ /**
+ * Pointer to the parent.
+ */
+ struct cx_tree_node_base_s *parent;
+ /**
+ * Pointer to the first child.
+ */
+ struct cx_tree_node_base_s *children;
+ /**
+ * Pointer to the last child.
+ */
+ struct cx_tree_node_base_s *last_child;
+ /**
+ * Pointer to the previous sibling.
+ */
+ struct cx_tree_node_base_s *prev;
+ /**
+ * Pointer to the next sibling.
+ */
+ struct cx_tree_node_base_s *next;
+};
+
+/**
+ * Structure for holding the base data of a tree.
+ */
+struct cx_tree_s {
+ /**
+ * The tree class definition.
+ */
+ const cx_tree_class *cl;
+
+ /**
+ * Allocator to allocate new nodes.
+ */
+ const CxAllocator *allocator;
+
+ /**
+ * A pointer to the root node.
+ *
+ * Will be @c NULL when @c size is 0.
+ */
+ void *root;
+
+ /**
+ * A function to create new nodes.
+ *
+ * Invocations to this function will receive a pointer to this tree
+ * structure as second argument.
+ *
+ * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to.
+ */
+ cx_tree_node_create_func node_create;
+
+ /**
+ * An optional simple destructor for the tree nodes.
+ */
+ cx_destructor_func simple_destructor;
+
+ /**
+ * An optional advanced destructor for the tree nodes.
+ */
+ cx_destructor_func2 advanced_destructor;
+
+ /**
+ * The pointer to additional data that is passed to the advanced destructor.
+ */
+ void *destructor_data;
+
+ /**
+ * A function to compare two nodes.
+ */
+ cx_tree_search_func search;
+
+ /**
+ * A function to compare a node with data.
+ */
+ cx_tree_search_data_func search_data;
+
+ /**
+ * The number of currently stored elements.
+ */
+ size_t size;
+
+ /**
+ * Offset in the node struct for the parent pointer.
+ */
+ ptrdiff_t loc_parent;
+
+ /**
+ * Offset in the node struct for the children linked list.
+ */
+ ptrdiff_t loc_children;
+
+ /**
+ * Optional offset in the node struct for the pointer to the last child
+ * in the linked list (negative if there is no such pointer).
+ */
+ ptrdiff_t loc_last_child;
+
+ /**
+ * Offset in the node struct for the previous sibling pointer.
+ */
+ ptrdiff_t loc_prev;
+
+ /**
+ * Offset in the node struct for the next sibling pointer.
+ */
+ ptrdiff_t loc_next;
+};
+
+/**
+ * Macro to roll out the #cx_tree_node_base_s structure with a custom
+ * node type.
+ *
+ * Must be used as first member in your custom tree struct.
+ *
+ * @param type the data type for the nodes
+ */
+#define CX_TREE_NODE_BASE(type) \
+ type *parent; \
+ type *children;\
+ type *last_child;\
+ type *prev;\
+ type *next
+
+/**
+ * Macro for specifying the layout of a base node tree.
+ *
+ * When your tree uses #CX_TREE_NODE_BASE, you can use this
+ * macro in all tree functions that expect the layout parameters
+ * @c loc_parent, @c loc_children, @c loc_last_child, @c loc_prev,
+ * and @c loc_next.
+ */
+#define cx_tree_node_base_layout \
+ offsetof(struct cx_tree_node_base_s, parent),\
+ offsetof(struct cx_tree_node_base_s, children),\
+ offsetof(struct cx_tree_node_base_s, last_child),\
+ offsetof(struct cx_tree_node_base_s, prev), \
+ offsetof(struct cx_tree_node_base_s, next)
+
+/**
+ * The class definition for arbitrary trees.
+ */
+struct cx_tree_class_s {
+ /**
+ * Member function for inserting a single element.
+ *
+ * Implementations SHALL NOT simply invoke @p insert_many as this comes
+ * with too much overhead.
+ */
+ int (*insert_element)(
+ struct cx_tree_s *tree,
+ const void *data
+ );
+
+ /**
+ * Member function for inserting multiple elements.
+ *
+ * Implementations SHALL avoid to perform a full search in the tree for
+ * every element even though the source data MAY be unsorted.
+ */
+ size_t (*insert_many)(
+ struct cx_tree_s *tree,
+ struct cx_iterator_base_s *iter,
+ size_t n
+ );
+
+ /**
+ * Member function for finding a node.
+ */
+ void *(*find)(
+ struct cx_tree_s *tree,
+ const void *subtree,
+ const void *data,
+ size_t depth
+ );
+};
+
+/**
+ * Common type for all tree implementations.
+ */
+typedef struct cx_tree_s CxTree;
+
+
+/**
+ * Destroys a node and it's subtree.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor, starting with the leaf nodes of the subtree.
+ *
+ * When this function is invoked on the root node of the tree, it destroys the
+ * tree contents, but - in contrast to #cxTreeFree() - not the tree
+ * structure, leaving an empty tree behind.
+ *
+ * @note The destructor function, if any, will @em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @attention This function will not free the memory of the nodes with the
+ * tree's allocator, because that is usually done by the advanced destructor
+ * and would therefore result in a double-free.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ * @see cxTreeFree()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxTreeDestroySubtree(CxTree *tree, void *node);
+
+
+/**
+ * Destroys the tree contents.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor, starting with the leaf nodes of the subtree.
+ *
+ * This is a convenience macro for invoking #cxTreeDestroySubtree() on the
+ * root node of the tree.
+ *
+ * @attention Be careful when calling this function when no destructor function
+ * is registered that actually frees the memory of nodes. In that case you will
+ * need a reference to the (former) root node of the tree somewhere or
+ * otherwise you will be leaking memory.
+ *
+ * @param tree the tree
+ * @see cxTreeDestroySubtree()
*/
-void cx_tree_add_child(void **children_begin, void **children_end,
- ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node,
- ptrdiff_t loc_parent, void *parent)
-__attribute__((__nonnull__ (5)));
+#define cxTreeClear(tree) cxTreeDestroySubtree(tree, tree->root)
+/**
+ * Deallocates the tree structure.
+ *
+ * The destructor functions are invoked for each node, starting with the leaf
+ * nodes.
+ * It is guaranteed that for each node the simple destructor is invoked before
+ * the advanced destructor.
+ *
+ * @attention This function will only invoke the destructor functions
+ * on the nodes.
+ * It will NOT additionally free the nodes with the tree's allocator, because
+ * that would cause a double-free in most scenarios where the advanced
+ * destructor is already freeing the memory.
+ *
+ * @param tree the tree to free
+ */
+cx_attr_export
+void cxTreeFree(CxTree *tree);
+
+/**
+ * Creates a new tree structure based on the specified layout.
+ *
+ * The specified @p allocator will be used for creating the tree struct
+ * and SHALL be used by @p create_func to allocate memory for the nodes.
+ *
+ * @note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator the allocator that shall be used
+ * (if @c NULL, the cxDefaultAllocator will be used)
+ * @param create_func a function that creates new nodes
+ * @param search_func a function that compares two nodes
+ * @param search_data_func a function that compares a node with data
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreateSimple()
+ * @see cxTreeCreateWrapped()
+ */
+cx_attr_nonnull_arg(2, 3, 4)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxTreeFree, 1)
+cx_attr_export
+CxTree *cxTreeCreate(
+ const CxAllocator *allocator,
+ cx_tree_node_create_func create_func,
+ cx_tree_search_func search_func,
+ cx_tree_search_data_func search_data_func,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Creates a new tree structure based on a default layout.
+ *
+ * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first
+ * member (or at least respect the default offsets specified in the tree
+ * struct) and they MUST be allocated with the specified allocator.
+ *
+ * @note This function will also register an advanced destructor which
+ * will free the nodes with the allocator's free() method.
+ *
+ * @param allocator (@c CxAllocator*) the allocator that shall be used
+ * @param create_func (@c cx_tree_node_create_func) a function that creates new nodes
+ * @param search_func (@c cx_tree_search_func) a function that compares two nodes
+ * @param search_data_func (@c cx_tree_search_data_func) a function that compares a node with data
+ * @return (@c CxTree*) the new tree
+ * @see cxTreeCreate()
+ */
+#define cxTreeCreateSimple(\
+ allocator, create_func, search_func, search_data_func \
+) cxTreeCreate(allocator, create_func, search_func, search_data_func, \
+cx_tree_node_base_layout)
+
+/**
+ * Creates a new tree structure based on an existing tree.
+ *
+ * The specified @p allocator will be used for creating the tree struct.
+ *
+ * @attention This function will create an incompletely defined tree structure
+ * where neither the create function, the search function, nor a destructor
+ * will be set. If you wish to use any of this functionality for the wrapped
+ * tree, you need to specify those functions afterwards.
+ *
+ * @param allocator the allocator that was used for nodes of the wrapped tree
+ * (if @c NULL, the cxDefaultAllocator is assumed)
+ * @param root the root node of the tree that shall be wrapped
+ * @param loc_parent offset in the node struct for the parent pointer
+ * @param loc_children offset in the node struct for the children linked list
+ * @param loc_last_child optional offset in the node struct for the pointer to
+ * the last child in the linked list (negative if there is no such pointer)
+ * @param loc_prev optional offset in the node struct for the prev pointer
+ * @param loc_next offset in the node struct for the next pointer
+ * @return the new tree
+ * @see cxTreeCreate()
+ */
+cx_attr_nonnull_arg(2)
+cx_attr_nodiscard
+cx_attr_malloc
+cx_attr_dealloc(cxTreeFree, 1)
+cx_attr_export
+CxTree *cxTreeCreateWrapped(
+ const CxAllocator *allocator,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+);
+
+/**
+ * Inserts data into the tree.
+ *
+ * @remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to insert
+ * @retval zero success
+ * @retval non-zero failure
+ */
+cx_attr_nonnull
+static inline int cxTreeInsert(
+ CxTree *tree,
+ const void *data
+) {
+ return tree->cl->insert_element(tree, data);
+}
+
+/**
+ * Inserts elements provided by an iterator efficiently into the tree.
+ *
+ * @remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param iter the iterator providing the elements
+ * @param n the maximum number of elements to insert
+ * @return the number of elements that could be successfully inserted
+ */
+cx_attr_nonnull
+static inline size_t cxTreeInsertIter(
+ CxTree *tree,
+ CxIteratorBase *iter,
+ size_t n
+) {
+ return tree->cl->insert_many(tree, iter, n);
+}
+
+/**
+ * Inserts an array of data efficiently into the tree.
+ *
+ * @remark For this function to work, the tree needs specified search and
+ * create functions, which might not be available for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the array of data to insert
+ * @param elem_size the size of each element in the array
+ * @param n the number of elements in the array
+ * @return the number of elements that could be successfully inserted
+ */
+cx_attr_nonnull
+static inline size_t cxTreeInsertArray(
+ CxTree *tree,
+ const void *data,
+ size_t elem_size,
+ size_t n
+) {
+ if (n == 0) return 0;
+ if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0;
+ CxIterator iter = cxIterator(data, elem_size, n);
+ return cxTreeInsertIter(tree, cxIteratorRef(iter), n);
+}
+
+/**
+ * Searches the data in the specified tree.
+ *
+ * @remark For this function to work, the tree needs a specified @c search_data
+ * function, which might not be available wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @return the first matching node, or @c NULL when the data cannot be found
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline void *cxTreeFind(
+ CxTree *tree,
+ const void *data
+) {
+ return tree->cl->find(tree, tree->root, data, 0);
+}
+
+/**
+ * Searches the data in the specified subtree.
+ *
+ * When @p max_depth is zero, the depth is not limited.
+ * The @p subtree_root itself is on depth 1 and its children have depth 2.
+ *
+ * @note When @p subtree_root is not part of the @p tree, the behavior is
+ * undefined.
+ *
+ * @remark For this function to work, the tree needs a specified @c search_data
+ * function, which might not be the case for wrapped trees
+ * (see #cxTreeCreateWrapped()).
+ *
+ * @param tree the tree
+ * @param data the data to search for
+ * @param subtree_root the node where to start
+ * @param max_depth the maximum search depth
+ * @return the first matching node, or @c NULL when the data cannot be found
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline void *cxTreeFindInSubtree(
+ CxTree *tree,
+ const void *data,
+ void *subtree_root,
+ size_t max_depth
+) {
+ return tree->cl->find(tree, subtree_root, data, max_depth);
+}
+
+/**
+ * Determines the size of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the number of nodes in the specified subtree
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the depth of the specified subtree.
+ *
+ * @param tree the tree
+ * @param subtree_root the root node of the subtree
+ * @return the tree depth including the @p subtree_root
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root);
+
+/**
+ * Determines the size of the entire tree.
+ *
+ * @param tree the tree
+ * @return the tree size, counting the root as one
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline size_t cxTreeSize(CxTree *tree) {
+ return tree->size;
+}
+
+/**
+ * Determines the depth of the entire tree.
+ *
+ * @param tree the tree
+ * @return the tree depth, counting the root as one
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+cx_attr_export
+size_t cxTreeDepth(CxTree *tree);
+
+/**
+ * Creates a depth-first iterator for the specified tree starting in @p node.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @param tree the tree to iterate
+ * @param node the node where to start
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the subtree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisit()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeIterator cxTreeIterateSubtree(
+ CxTree *tree,
+ void *node,
+ bool visit_on_exit
+) {
+ return cx_tree_iterator(
+ node, visit_on_exit,
+ tree->loc_children, tree->loc_next
+ );
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree starting in @p node.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @param tree the tree to iterate
+ * @param node the node where to start
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterate()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) {
+ return cx_tree_visitor(
+ node, tree->loc_children, tree->loc_next
+ );
+}
+
+/**
+ * Creates a depth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @param visit_on_exit true, if the iterator shall visit a node again when
+ * leaving the subtree
+ * @return a tree iterator (depth-first)
+ * @see cxTreeVisit()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeIterator cxTreeIterate(
+ CxTree *tree,
+ bool visit_on_exit
+) {
+ return cxTreeIterateSubtree(tree, tree->root, visit_on_exit);
+}
+
+/**
+ * Creates a breadth-first iterator for the specified tree.
+ *
+ * @param tree the tree to iterate
+ * @return a tree visitor (a.k.a. breadth-first iterator)
+ * @see cxTreeIterate()
+ */
+cx_attr_nonnull
+cx_attr_nodiscard
+static inline CxTreeVisitor cxTreeVisit(CxTree *tree) {
+ return cxTreeVisitSubtree(tree, tree->root);
+}
+
+/**
+ * Sets the (new) parent of the specified child.
+ *
+ * If the @p child is not already member of the tree, this function behaves
+ * as #cxTreeAddChildNode().
+ *
+ * @param tree the tree
+ * @param parent the (new) parent of the child
+ * @param child the node to add
+ * @see cxTreeAddChildNode()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxTreeSetParent(
+ CxTree *tree,
+ void *parent,
+ void *child
+);
+
+/**
+ * Adds a new node to the tree.
+ *
+ * If the @p child is already member of the tree, the behavior is undefined.
+ * Use #cxTreeSetParent() if you want to move a subtree to another location.
+ *
+ * @attention The node may be externally created, but MUST obey the same rules
+ * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use
+ * the same allocator).
+ *
+ * @param tree the tree
+ * @param parent the parent of the node to add
+ * @param child the node to add
+ * @see cxTreeSetParent()
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxTreeAddChildNode(
+ CxTree *tree,
+ void *parent,
+ void *child
+);
+
+/**
+ * Creates a new node and adds it to the tree.
+ *
+ * With this function you can decide where exactly the new node shall be added.
+ * If you specified an appropriate search function, you may want to consider
+ * leaving this task to the tree by using #cxTreeInsert().
+ *
+ * Be aware that adding nodes at arbitrary locations in the tree might cause
+ * wrong or undesired results when subsequently invoking #cxTreeInsert() and
+ * the invariant imposed by the search function does not hold any longer.
+ *
+ * @param tree the tree
+ * @param parent the parent node of the new node
+ * @param data the data that will be submitted to the create function
+ * @return zero when the new node was created, non-zero on allocation failure
+ * @see cxTreeInsert()
+ */
+cx_attr_nonnull
+cx_attr_export
+int cxTreeAddChild(
+ CxTree *tree,
+ void *parent,
+ const void *data
+);
+
+/**
+ * A function that is invoked when a node needs to be re-linked to a new parent.
+ *
+ * When a node is re-linked, sometimes the contents need to be updated.
+ * This callback is invoked by #cxTreeRemoveNode() and #cxTreeDestroyNode()
+ * so that those updates can be applied when re-linking the children of the
+ * removed node.
+ *
+ * @param node the affected node
+ * @param old_parent the old parent of the node
+ * @param new_parent the new parent of the node
+ */
+cx_attr_nonnull
+typedef void (*cx_tree_relink_func)(
+ void *node,
+ const void *old_parent,
+ const void *new_parent
+);
+
+/**
+ * Removes a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @note The destructor function, if any, will @em not be invoked. That means
+ * you will need to free the removed node by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if @p node is the root node of the tree
+ */
+cx_attr_nonnull_arg(1, 2)
+cx_attr_export
+int cxTreeRemoveNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+);
+
+/**
+ * Removes a node and it's subtree from the tree.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * @note The destructor function, if any, will @em not be invoked. That means
+ * you will need to free the removed subtree by yourself, eventually.
+ *
+ * @param tree the tree
+ * @param node the node to remove
+ */
+cx_attr_nonnull
+cx_attr_export
+void cxTreeRemoveSubtree(CxTree *tree, void *node);
+
+/**
+ * Destroys a node and re-links its children to its former parent.
+ *
+ * If the node is not part of the tree, the behavior is undefined.
+ *
+ * It is guaranteed that the simple destructor is invoked before
+ * the advanced destructor.
+ *
+ * @attention This function will not free the memory of the node with the
+ * tree's allocator, because that is usually done by the advanced destructor
+ * and would therefore result in a double-free.
+ *
+ * @param tree the tree
+ * @param node the node to destroy (must not be the root node)
+ * @param relink_func optional callback to update the content of each re-linked
+ * node
+ * @return zero on success, non-zero if @p node is the root node of the tree
+ */
+cx_attr_nonnull_arg(1, 2)
+cx_attr_export
+int cxTreeDestroyNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+);
#ifdef __cplusplus
} // extern "C"
#endif
-#endif // UCX_TREE_H
-
+#endif //UCX_TREE_H
#include <string.h>
void cx_hash_murmur(CxHashKey *key) {
- unsigned char const *data = key->data;
+ const unsigned char *data = key->data;
if (data == NULL) {
// extension: special value for NULL
key->hash = 1574210520u;
unsigned m = 0x5bd1e995;
unsigned r = 24;
- unsigned h = 25 ^ len;
+ unsigned h = 25 ^ (unsigned) len;
unsigned i = 0;
while (len >= 4) {
unsigned k = data[i + 0] & 0xFF;
key->hash = h;
}
-CxHashKey cx_hash_key_str(char const *str) {
+CxHashKey cx_hash_key_str(const char *str) {
CxHashKey key;
key.data = str;
key.len = str == NULL ? 0 : strlen(str);
}
CxHashKey cx_hash_key_bytes(
- unsigned char const *bytes,
+ const unsigned char *bytes,
size_t len
) {
CxHashKey key;
}
CxHashKey cx_hash_key(
- void const *obj,
+ const void *obj,
size_t len
) {
CxHashKey key;
*/
#include "cx/hash_map.h"
-#include "cx/utils.h"
#include <string.h>
#include <assert.h>
+#include <errno.h>
struct cx_hash_map_element_s {
/** A pointer to the next element in the current bucket. */
static void cx_hash_map_clear(struct cx_map_s *map) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
- cx_for_n(i, hash_map->bucket_count) {
+ for (size_t i = 0; i < hash_map->bucket_count; i++) {
struct cx_hash_map_element_s *elem = hash_map->buckets[i];
if (elem != NULL) {
do {
// invoke the destructor
cx_invoke_destructor(map, elem->data);
// free the key data
- cxFree(map->allocator, (void *) elem->key.data);
+ cxFree(map->collection.allocator, (void *) elem->key.data);
// free the node
- cxFree(map->allocator, elem);
+ cxFree(map->collection.allocator, elem);
// proceed
elem = next;
} while (elem != NULL);
hash_map->buckets[i] = NULL;
}
}
- map->size = 0;
+ map->collection.size = 0;
}
static void cx_hash_map_destructor(struct cx_map_s *map) {
// free the buckets
cx_hash_map_clear(map);
- cxFree(map->allocator, hash_map->buckets);
+ cxFree(map->collection.allocator, hash_map->buckets);
// free the map structure
- cxFree(map->allocator, map);
+ cxFree(map->collection.allocator, map);
}
static int cx_hash_map_put(
void *value
) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
- CxAllocator const *allocator = map->allocator;
+ const CxAllocator *allocator = map->collection.allocator;
unsigned hash = key.hash;
if (hash == 0) {
if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
memcmp(elm->key.data, key.data, key.len) == 0) {
- // overwrite existing element
- if (map->store_pointer) {
+ // overwrite existing element, but call destructors first
+ cx_invoke_destructor(map, elm->data);
+ if (map->collection.store_pointer) {
memcpy(elm->data, &value, sizeof(void *));
} else {
- memcpy(elm->data, value, map->item_size);
+ memcpy(elm->data, value, map->collection.elem_size);
}
} else {
// allocate new element
struct cx_hash_map_element_s *e = cxMalloc(
allocator,
- sizeof(struct cx_hash_map_element_s) + map->item_size
+ sizeof(struct cx_hash_map_element_s) + map->collection.elem_size
);
- if (e == NULL) {
- return -1;
- }
+ if (e == NULL) return -1;
// write the value
- if (map->store_pointer) {
+ if (map->collection.store_pointer) {
memcpy(e->data, &value, sizeof(void *));
} else {
- memcpy(e->data, value, map->item_size);
+ memcpy(e->data, value, map->collection.elem_size);
}
// copy the key
void *kd = cxMalloc(allocator, key.len);
- if (kd == NULL) {
- return -1;
- }
+ if (kd == NULL) return -1;
memcpy(kd, key.data, key.len);
e->key.data = kd;
e->key.len = key.len;
e->next = elm;
// increase the size
- map->size++;
+ map->collection.size++;
}
return 0;
prev->next = elm->next;
}
// free element
- cxFree(hash_map->base.allocator, (void *) elm->key.data);
- cxFree(hash_map->base.allocator, elm);
+ cxFree(hash_map->base.collection.allocator, (void *) elm->key.data);
+ cxFree(hash_map->base.collection.allocator, elm);
// decrease size
- hash_map->base.size--;
+ hash_map->base.collection.size--;
}
/**
* Helper function to avoid code duplication.
*
+ * If @p remove is true, and @p targetbuf is @c NULL, the element
+ * will be destroyed when found.
+ *
+ * If @p remove is true, and @p targetbuf is set, the element will
+ * be copied to that buffer and no destructor function is called.
+ *
+ * If @p remove is false, @p targetbuf must not be non-null and
+ * either the pointer, when the map is storing pointers, is copied
+ * to the target buffer, or a pointer to the stored object will
+ * be copied to the target buffer.
+ *
* @param map the map
* @param key the key to look up
+ * @param targetbuf see description
* @param remove flag indicating whether the looked up entry shall be removed
- * @param destroy flag indicating whether the destructor shall be invoked
- * @return a pointer to the value corresponding to the key or \c NULL
+ * @return zero, if the key was found, non-zero otherwise
*/
-static void *cx_hash_map_get_remove(
+static int cx_hash_map_get_remove(
CxMap *map,
CxHashKey key,
- bool remove,
- bool destroy
+ void *targetbuf,
+ bool remove
) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
while (elm && elm->key.hash <= hash) {
if (elm->key.hash == hash && elm->key.len == key.len) {
if (memcmp(elm->key.data, key.data, key.len) == 0) {
- void *data = NULL;
- if (destroy) {
- cx_invoke_destructor(map, elm->data);
+ if (remove) {
+ if (targetbuf == NULL) {
+ cx_invoke_destructor(map, elm->data);
+ } else {
+ memcpy(targetbuf, elm->data, map->collection.elem_size);
+ }
+ cx_hash_map_unlink(hash_map, slot, prev, elm);
} else {
- if (map->store_pointer) {
+ assert(targetbuf != NULL);
+ void *data = NULL;
+ if (map->collection.store_pointer) {
data = *(void **) elm->data;
} else {
data = elm->data;
}
+ memcpy(targetbuf, &data, sizeof(void *));
}
- if (remove) {
- cx_hash_map_unlink(hash_map, slot, prev, elm);
- }
- return data;
+ return 0;
}
}
prev = elm;
elm = prev->next;
}
- return NULL;
+ return 1;
}
static void *cx_hash_map_get(
- CxMap const *map,
+ const CxMap *map,
CxHashKey key
) {
// we can safely cast, because we know the map stays untouched
- return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+ void *ptr = NULL;
+ int found = cx_hash_map_get_remove((CxMap *) map, key, &ptr, false);
+ return found == 0 ? ptr : NULL;
}
-static void *cx_hash_map_remove(
+static int cx_hash_map_remove(
CxMap *map,
CxHashKey key,
- bool destroy
+ void *targetbuf
) {
- return cx_hash_map_get_remove(map, key, true, destroy);
+ return cx_hash_map_get_remove(map, key, targetbuf, true);
}
-static void *cx_hash_map_iter_current_entry(void const *it) {
- struct cx_iterator_s const *iter = it;
- // struct has to have a compatible signature
- return (struct cx_map_entry_s *) &(iter->kv_data);
+static void *cx_hash_map_iter_current_entry(const void *it) {
+ const CxMapIterator *iter = it;
+ // we have to cast away const, because of the signature
+ return (void*) &iter->entry;
}
-static void *cx_hash_map_iter_current_key(void const *it) {
- struct cx_iterator_s const *iter = it;
- struct cx_hash_map_element_s *elm = iter->elem_handle;
+static void *cx_hash_map_iter_current_key(const void *it) {
+ const CxMapIterator *iter = it;
+ struct cx_hash_map_element_s *elm = iter->elem;
return &elm->key;
}
-static void *cx_hash_map_iter_current_value(void const *it) {
- struct cx_iterator_s const *iter = it;
- struct cx_hash_map_s const *map = iter->src_handle;
- struct cx_hash_map_element_s *elm = iter->elem_handle;
- if (map->base.store_pointer) {
+static void *cx_hash_map_iter_current_value(const void *it) {
+ const CxMapIterator *iter = it;
+ const CxMap *map = iter->map.c;
+ struct cx_hash_map_element_s *elm = iter->elem;
+ if (map->collection.store_pointer) {
return *(void **) elm->data;
} else {
return elm->data;
}
}
-static bool cx_hash_map_iter_valid(void const *it) {
- struct cx_iterator_s const *iter = it;
- return iter->elem_handle != NULL;
+static bool cx_hash_map_iter_valid(const void *it) {
+ const CxMapIterator *iter = it;
+ return iter->elem != NULL;
}
static void cx_hash_map_iter_next(void *it) {
- struct cx_iterator_s *iter = it;
- struct cx_hash_map_element_s *elm = iter->elem_handle;
+ CxMapIterator *iter = it;
+ CxMap *map = iter->map.m;
+ struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map;
+ struct cx_hash_map_element_s *elm = iter->elem;
// remove current element, if asked
if (iter->base.remove) {
- // obtain mutable pointer to the map
- struct cx_mut_iterator_s *miter = it;
- struct cx_hash_map_s *map = miter->src_handle;
// clear the flag
iter->base.remove = false;
// search the previous element
struct cx_hash_map_element_s *prev = NULL;
- if (map->buckets[iter->slot] != elm) {
- prev = map->buckets[iter->slot];
+ if (hmap->buckets[iter->slot] != elm) {
+ prev = hmap->buckets[iter->slot];
while (prev->next != elm) {
prev = prev->next;
}
}
// destroy
- cx_invoke_destructor((struct cx_map_s *) map, elm->data);
+ cx_invoke_destructor(map, elm->data);
// unlink
- cx_hash_map_unlink(map, iter->slot, prev, elm);
+ cx_hash_map_unlink(hmap, iter->slot, prev, elm);
// advance
elm = next;
}
// search the next bucket, if required
- struct cx_hash_map_s const *map = iter->src_handle;
- while (elm == NULL && ++iter->slot < map->bucket_count) {
- elm = map->buckets[iter->slot];
+ while (elm == NULL && ++iter->slot < hmap->bucket_count) {
+ elm = hmap->buckets[iter->slot];
}
-
- // fill the struct with the next element
- iter->elem_handle = elm;
- if (elm == NULL) {
- iter->kv_data.key = NULL;
- iter->kv_data.value = NULL;
- } else {
- iter->kv_data.key = &elm->key;
- if (map->base.store_pointer) {
- iter->kv_data.value = *(void **) elm->data;
+ iter->elem = elm;
+
+ // copy data to a location where the iterator can point to
+ // we need to do it here, because the iterator function call
+ // must not modify the iterator (the parameter is const)
+ if (elm != NULL) {
+ iter->entry.key = &elm->key;
+ if (iter->map.c->collection.store_pointer) {
+ iter->entry.value = *(void **) elm->data;
} else {
- iter->kv_data.value = elm->data;
+ iter->entry.value = elm->data;
}
}
}
-static bool cx_hash_map_iter_flag_rm(void *it) {
- struct cx_iterator_base_s *iter = it;
- if (iter->mutating) {
- iter->remove = true;
- return true;
- } else {
- return false;
- }
-}
-
-static CxIterator cx_hash_map_iterator(
- CxMap const *map,
+static CxMapIterator cx_hash_map_iterator(
+ const CxMap *map,
enum cx_map_iterator_type type
) {
- CxIterator iter;
+ CxMapIterator iter;
- iter.src_handle = map;
- iter.base.valid = cx_hash_map_iter_valid;
- iter.base.next = cx_hash_map_iter_next;
+ iter.map.c = map;
+ iter.elem_count = map->collection.size;
switch (type) {
case CX_MAP_ITERATOR_PAIRS:
+ iter.elem_size = sizeof(CxMapEntry);
iter.base.current = cx_hash_map_iter_current_entry;
break;
case CX_MAP_ITERATOR_KEYS:
+ iter.elem_size = sizeof(CxHashKey);
iter.base.current = cx_hash_map_iter_current_key;
break;
case CX_MAP_ITERATOR_VALUES:
+ iter.elem_size = map->collection.elem_size;
iter.base.current = cx_hash_map_iter_current_value;
break;
default:
- assert(false);
+ assert(false); // LCOV_EXCL_LINE
}
- iter.base.flag_removal = cx_hash_map_iter_flag_rm;
+ iter.base.valid = cx_hash_map_iter_valid;
+ iter.base.next = cx_hash_map_iter_next;
iter.base.remove = false;
iter.base.mutating = false;
iter.slot = 0;
iter.index = 0;
- if (map->size > 0) {
+ if (map->collection.size > 0) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
struct cx_hash_map_element_s *elm = hash_map->buckets[0];
while (elm == NULL) {
elm = hash_map->buckets[++iter.slot];
}
- iter.elem_handle = elm;
- iter.kv_data.key = &elm->key;
- if (map->store_pointer) {
- iter.kv_data.value = *(void **) elm->data;
+ iter.elem = elm;
+ iter.entry.key = &elm->key;
+ if (map->collection.store_pointer) {
+ iter.entry.value = *(void **) elm->data;
} else {
- iter.kv_data.value = elm->data;
+ iter.entry.value = elm->data;
}
} else {
- iter.elem_handle = NULL;
- iter.kv_data.key = NULL;
- iter.kv_data.value = NULL;
+ iter.elem = NULL;
}
return iter;
};
CxMap *cxHashMapCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
size_t itemsize,
size_t buckets
) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
if (buckets == 0) {
// implementation defined default
buckets = 16;
map->bucket_count = buckets;
map->buckets = cxCalloc(allocator, buckets,
sizeof(struct cx_hash_map_element_s *));
- if (map->buckets == NULL) {
+ if (map->buckets == NULL) { // LCOV_EXCL_START
cxFree(allocator, map);
return NULL;
- }
+ } // LCOV_EXCL_STOP
// initialize base members
map->base.cl = &cx_hash_map_class;
- map->base.allocator = allocator;
+ map->base.collection.allocator = allocator;
if (itemsize > 0) {
- map->base.store_pointer = false;
- map->base.item_size = itemsize;
+ map->base.collection.elem_size = itemsize;
} else {
- map->base.store_pointer = true;
- map->base.item_size = sizeof(void *);
+ map->base.collection.elem_size = sizeof(void *);
+ map->base.collection.store_pointer = true;
}
return (CxMap *) map;
int cxMapRehash(CxMap *map) {
struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
- if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
+ if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) {
- size_t new_bucket_count = (map->size * 5) >> 1;
+ size_t new_bucket_count = (map->collection.size * 5) >> 1;
+ if (new_bucket_count < hash_map->bucket_count) {
+ errno = EOVERFLOW;
+ return 1;
+ }
struct cx_hash_map_element_s **new_buckets = cxCalloc(
- map->allocator,
+ map->collection.allocator,
new_bucket_count, sizeof(struct cx_hash_map_element_s *)
);
- if (new_buckets == NULL) {
- return 1;
- }
+ if (new_buckets == NULL) return 1;
// iterate through the elements and assign them to their new slots
- cx_for_n(slot, hash_map->bucket_count) {
+ for (size_t slot = 0; slot < hash_map->bucket_count; slot++) {
struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
while (elm != NULL) {
struct cx_hash_map_element_s *next = elm->next;
// assign result to the map
hash_map->bucket_count = new_bucket_count;
- cxFree(map->allocator, hash_map->buckets);
+ cxFree(map->collection.allocator, hash_map->buckets);
hash_map->buckets = new_buckets;
}
return 0;
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/iterator.h"
+
+#include <string.h>
+
+static bool cx_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->index < iter->elem_count;
+}
+
+static void *cx_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return iter->elem_handle;
+}
+
+static void *cx_iter_current_ptr(const void *it) {
+ const struct cx_iterator_s *iter = it;
+ return *(void**)iter->elem_handle;
+}
+
+static void cx_iter_next_fast(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ iter->elem_count--;
+ // only move the last element when we are not currently aiming
+ // at the last element already
+ if (iter->index < iter->elem_count) {
+ void *last = ((char *) iter->src_handle.m)
+ + iter->elem_count * iter->elem_size;
+ memcpy(iter->elem_handle, last, iter->elem_size);
+ }
+ } else {
+ iter->index++;
+ iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+ }
+}
+
+static void cx_iter_next_slow(void *it) {
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ iter->elem_count--;
+
+ // number of elements to move
+ size_t remaining = iter->elem_count - iter->index;
+ if (remaining > 0) {
+ memmove(
+ iter->elem_handle,
+ ((char *) iter->elem_handle) + iter->elem_size,
+ remaining * iter->elem_size
+ );
+ }
+ } else {
+ iter->index++;
+ iter->elem_handle = ((char *) iter->elem_handle) + iter->elem_size;
+ }
+}
+
+CxIterator cxMutIterator(
+ void *array,
+ size_t elem_size,
+ size_t elem_count,
+ bool remove_keeps_order
+) {
+ CxIterator iter;
+
+ iter.index = 0;
+ iter.src_handle.m = array;
+ iter.elem_handle = array;
+ iter.elem_size = elem_size;
+ iter.elem_count = array == NULL ? 0 : elem_count;
+ iter.base.valid = cx_iter_valid;
+ iter.base.current = cx_iter_current;
+ iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast;
+ iter.base.remove = false;
+ iter.base.mutating = true;
+
+ return iter;
+}
+
+CxIterator cxIterator(
+ const void *array,
+ size_t elem_size,
+ size_t elem_count
+) {
+ CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false);
+ iter.base.mutating = false;
+ return iter;
+}
+
+CxIterator cxMutIteratorPtr(
+ void *array,
+ size_t elem_count,
+ bool remove_keeps_order
+) {
+ CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order);
+ iter.base.current = cx_iter_current_ptr;
+ return iter;
+}
+
+CxIterator cxIteratorPtr(
+ const void *array,
+ size_t elem_count
+) {
+ CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false);
+ iter.base.mutating = false;
+ return iter;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/json.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <inttypes.h>
+
+/*
+ * RFC 8259
+ * https://tools.ietf.org/html/rfc8259
+ */
+
+static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING};
+
+static int json_cmp_objvalue(const void *l, const void *r) {
+ const CxJsonObjValue *left = l;
+ const CxJsonObjValue *right = r;
+ return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name));
+}
+
+static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) {
+ assert(obj->type == CX_JSON_OBJECT);
+ CxJsonObjValue kv_dummy;
+ kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length);
+ size_t index = cx_array_binary_search(
+ obj->value.object.values,
+ obj->value.object.values_size,
+ sizeof(CxJsonObjValue),
+ &kv_dummy,
+ json_cmp_objvalue
+ );
+ if (index == obj->value.object.values_size) {
+ return NULL;
+ } else {
+ return &obj->value.object.values[index];
+ }
+}
+
+static int json_add_objvalue(CxJsonValue *objv, CxJsonObjValue member) {
+ assert(objv->type == CX_JSON_OBJECT);
+ const CxAllocator * const al = objv->allocator;
+ CxJsonObject *obj = &(objv->value.object);
+
+ // determine the index where we need to insert the new member
+ size_t index = cx_array_binary_search_sup(
+ obj->values,
+ obj->values_size,
+ sizeof(CxJsonObjValue),
+ &member, json_cmp_objvalue
+ );
+
+ // is the name already present?
+ if (index < obj->values_size && 0 == json_cmp_objvalue(&member, &obj->values[index])) {
+ // free the original value
+ cx_strfree_a(al, &obj->values[index].name);
+ cxJsonValueFree(obj->values[index].value);
+ // replace the item
+ obj->values[index] = member;
+
+ // nothing more to do
+ return 0;
+ }
+
+ // determine the old capacity and reserve for one more element
+ CxArrayReallocator arealloc = cx_array_reallocator(al, NULL);
+ size_t oldcap = obj->values_capacity;
+ if (cx_array_simple_reserve_a(&arealloc, obj->values, 1)) return 1;
+
+ // check the new capacity, if we need to realloc the index array
+ size_t newcap = obj->values_capacity;
+ if (newcap > oldcap) {
+ if (cxReallocateArray(al, &obj->indices, newcap, sizeof(size_t))) {
+ return 1;
+ }
+ }
+
+ // check if append or insert
+ if (index < obj->values_size) {
+ // move the other elements
+ memmove(
+ &obj->values[index+1],
+ &obj->values[index],
+ (obj->values_size - index) * sizeof(CxJsonObjValue)
+ );
+ // increase indices for the moved elements
+ for (size_t i = 0; i < obj->values_size ; i++) {
+ if (obj->indices[i] >= index) {
+ obj->indices[i]++;
+ }
+ }
+ }
+
+ // insert the element and set the index
+ obj->values[index] = member;
+ obj->indices[obj->values_size] = index;
+ obj->values_size++;
+
+ return 0;
+}
+
+static void token_destroy(CxJsonToken *token) {
+ if (token->allocated) {
+ cx_strfree(&token->content);
+ }
+}
+
+static bool json_isdigit(char c) {
+ // TODO: remove once UCX has public API for this
+ return c >= '0' && c <= '9';
+}
+
+static bool json_isspace(char c) {
+ // TODO: remove once UCX has public API for this
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f';
+}
+
+static int num_isexp(const char *content, size_t length, size_t pos) {
+ if (pos >= length) {
+ return 0;
+ }
+
+ int ok = 0;
+ for (size_t i = pos; i < length; i++) {
+ char c = content[i];
+ if (json_isdigit(c)) {
+ ok = 1;
+ } else if (i == pos) {
+ if (!(c == '+' || c == '-')) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ return ok;
+}
+
+static CxJsonTokenType token_numbertype(const char *content, size_t length) {
+ if (length == 0) return CX_JSON_TOKEN_ERROR;
+
+ if (content[0] != '-' && !json_isdigit(content[0])) {
+ return CX_JSON_TOKEN_ERROR;
+ }
+
+ CxJsonTokenType type = CX_JSON_TOKEN_INTEGER;
+ for (size_t i = 1; i < length; i++) {
+ if (content[i] == '.') {
+ if (type == CX_JSON_TOKEN_NUMBER) {
+ return CX_JSON_TOKEN_ERROR; // more than one decimal separator
+ }
+ type = CX_JSON_TOKEN_NUMBER;
+ } else if (content[i] == 'e' || content[i] == 'E') {
+ return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR;
+ } else if (!json_isdigit(content[i])) {
+ return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep
+ }
+ }
+
+ return type;
+}
+
+static CxJsonToken token_create(CxJson *json, bool isstring, size_t start, size_t end) {
+ cxmutstr str = cx_mutstrn(json->buffer.space + start, end - start);
+ bool allocated = false;
+ if (json->uncompleted.tokentype != CX_JSON_NO_TOKEN) {
+ allocated = true;
+ str = cx_strcat_m(json->uncompleted.content, 1, str);
+ if (str.ptr == NULL) { // LCOV_EXCL_START
+ return (CxJsonToken){CX_JSON_NO_TOKEN, false, {NULL, 0}};
+ } // LCOV_EXCL_STOP
+ }
+ json->uncompleted = (CxJsonToken){0};
+ CxJsonTokenType ttype;
+ if (isstring) {
+ ttype = CX_JSON_TOKEN_STRING;
+ } else {
+ cxstring s = cx_strcast(str);
+ if (!cx_strcmp(s, CX_STR("true")) || !cx_strcmp(s, CX_STR("false"))
+ || !cx_strcmp(s, CX_STR("null"))) {
+ ttype = CX_JSON_TOKEN_LITERAL;
+ } else {
+ ttype = token_numbertype(str.ptr, str.length);
+ }
+ }
+ if (ttype == CX_JSON_TOKEN_ERROR) {
+ if (allocated) {
+ cx_strfree(&str);
+ }
+ return (CxJsonToken){CX_JSON_TOKEN_ERROR, false, {NULL, 0}};
+ }
+ return (CxJsonToken){ttype, allocated, str};
+}
+
+static CxJsonTokenType char2ttype(char c) {
+ switch (c) {
+ case '[': {
+ return CX_JSON_TOKEN_BEGIN_ARRAY;
+ }
+ case '{': {
+ return CX_JSON_TOKEN_BEGIN_OBJECT;
+ }
+ case ']': {
+ return CX_JSON_TOKEN_END_ARRAY;
+ }
+ case '}': {
+ return CX_JSON_TOKEN_END_OBJECT;
+ }
+ case ':': {
+ return CX_JSON_TOKEN_NAME_SEPARATOR;
+ }
+ case ',': {
+ return CX_JSON_TOKEN_VALUE_SEPARATOR;
+ }
+ case '"': {
+ return CX_JSON_TOKEN_STRING;
+ }
+ default: {
+ if (json_isspace(c)) {
+ return CX_JSON_TOKEN_SPACE;
+ }
+ }
+ }
+ return CX_JSON_NO_TOKEN;
+}
+
+static enum cx_json_status token_parse_next(CxJson *json, CxJsonToken *result) {
+ // check if there is data in the buffer
+ if (cxBufferEof(&json->buffer)) {
+ return json->uncompleted.tokentype == CX_JSON_NO_TOKEN ?
+ CX_JSON_NO_DATA : CX_JSON_INCOMPLETE_DATA;
+ }
+
+ // current token type and start index
+ CxJsonTokenType ttype = json->uncompleted.tokentype;
+ size_t token_part_start = json->buffer.pos;
+
+ bool escape_end_of_string = ttype == CX_JSON_TOKEN_STRING
+ && json->uncompleted.content.ptr[json->uncompleted.content.length-1] == '\\';
+
+ for (size_t i = json->buffer.pos; i < json->buffer.size; i++) {
+ char c = json->buffer.space[i];
+ if (ttype != CX_JSON_TOKEN_STRING) {
+ // currently non-string token
+ CxJsonTokenType ctype = char2ttype(c); // start of new token?
+ if (ttype == CX_JSON_NO_TOKEN) {
+ if (ctype == CX_JSON_TOKEN_SPACE) {
+ json->buffer.pos++;
+ continue;
+ } else if (ctype == CX_JSON_TOKEN_STRING) {
+ // begin string
+ ttype = CX_JSON_TOKEN_STRING;
+ token_part_start = i;
+ } else if (ctype != CX_JSON_NO_TOKEN) {
+ // single-char token
+ json->buffer.pos = i + 1;
+ *result = (CxJsonToken){ctype, false, {NULL, 0}};
+ return CX_JSON_NO_ERROR;
+ } else {
+ ttype = CX_JSON_TOKEN_LITERAL; // number or literal
+ token_part_start = i;
+ }
+ } else {
+ // finish token
+ if (ctype != CX_JSON_NO_TOKEN) {
+ *result = token_create(json, false, token_part_start, i);
+ if (result->tokentype == CX_JSON_NO_TOKEN) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ if (result->tokentype == CX_JSON_TOKEN_ERROR) {
+ return CX_JSON_FORMAT_ERROR_NUMBER;
+ }
+ json->buffer.pos = i;
+ return CX_JSON_NO_ERROR;
+ }
+ }
+ } else {
+ // currently inside a string
+ if (escape_end_of_string) {
+ escape_end_of_string = false;
+ } else {
+ if (c == '"') {
+ *result = token_create(json, true, token_part_start, i + 1);
+ if (result->tokentype == CX_JSON_NO_TOKEN) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->buffer.pos = i + 1;
+ return CX_JSON_NO_ERROR;
+ } else if (c == '\\') {
+ escape_end_of_string = true;
+ }
+ }
+ }
+ }
+
+ if (ttype != CX_JSON_NO_TOKEN) {
+ // uncompleted token
+ size_t uncompleted_len = json->buffer.size - token_part_start;
+ if (json->uncompleted.tokentype == CX_JSON_NO_TOKEN) {
+ // current token is uncompleted
+ // save current token content
+ CxJsonToken uncompleted = {
+ ttype, true,
+ cx_strdup(cx_strn(json->buffer.space + token_part_start, uncompleted_len))
+ };
+ if (uncompleted.content.ptr == NULL) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->uncompleted = uncompleted;
+ } else {
+ // previously we also had an uncompleted token
+ // combine the uncompleted token with the current token
+ assert(json->uncompleted.allocated);
+ cxmutstr str = cx_strcat_m(json->uncompleted.content, 1,
+ cx_strn(json->buffer.space + token_part_start, uncompleted_len));
+ if (str.ptr == NULL) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+ json->uncompleted.content = str;
+ }
+ // advance the buffer position - we saved the stuff in the uncompleted token
+ json->buffer.pos += uncompleted_len;
+ }
+
+ return CX_JSON_INCOMPLETE_DATA;
+}
+
+// converts a Unicode codepoint to utf8
+static unsigned codepoint_to_utf8(uint32_t codepoint, char *output_buf) {
+ if (codepoint <= 0x7F) {
+ *output_buf = (char)codepoint;
+ return 1;
+ } else if (codepoint <= 0x7FF) {
+ output_buf[0] = (char)(0xC0 | ((codepoint >> 6) & 0x1F));
+ output_buf[1] = (char)(0x80 | (codepoint & 0x3F));
+ return 2;
+ } else if (codepoint <= 0xFFFF) {
+ output_buf[0] = (char)(0xE0 | ((codepoint >> 12) & 0x0F));
+ output_buf[1] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
+ output_buf[2] = (char)(0x80 | (codepoint & 0x3F));
+ return 3;
+ } else if (codepoint <= 0x10FFFF) {
+ output_buf[0] = (char)(0xF0 | ((codepoint >> 18) & 0x07));
+ output_buf[1] = (char)(0x80 | ((codepoint >> 12) & 0x3F));
+ output_buf[2] = (char)(0x80 | ((codepoint >> 6) & 0x3F));
+ output_buf[3] = (char)(0x80 | (codepoint & 0x3F));
+ return 4;
+ }
+
+ return 0; // LCOV_EXCL_LINE
+}
+
+// converts a utf16 surrogate pair to utf8
+static inline uint32_t utf16pair_to_codepoint(uint16_t c0, uint16_t c1) {
+ return ((c0 - 0xD800) << 10) + (c1 - 0xDC00) + 0x10000;
+}
+
+static unsigned unescape_unicode_string(cxstring str, char *utf8buf) {
+ // str is supposed to start with "\uXXXX" or "\uXXXX\uXXXX"
+ // remaining bytes in the string are ignored (str may be larger!)
+
+ if (str.length < 6 || str.ptr[0] != '\\' || str.ptr[1] != 'u') {
+ return 0;
+ }
+
+ unsigned utf8len = 0;
+ cxstring ustr1 = { str.ptr + 2, 4};
+ uint16_t utf16a, utf16b;
+ if (!cx_strtou16_lc(ustr1, &utf16a, 16, "")) {
+ uint32_t codepoint;
+ if (utf16a < 0xD800 || utf16a > 0xE000) {
+ // character is in the Basic Multilingual Plane
+ // and encoded as a single utf16 char
+ codepoint = utf16a;
+ utf8len = codepoint_to_utf8(codepoint, utf8buf);
+ } else if (utf16a >= 0xD800 && utf16a <= 0xDBFF) {
+ // character is encoded as a surrogate pair
+ // get next 6 bytes
+ if (str.length >= 12) {
+ if (str.ptr[6] == '\\' && str.ptr[7] == 'u') {
+ cxstring ustr2 = { str.ptr+8, 4 };
+ if (!cx_strtou16_lc(ustr2, &utf16b, 16, "")
+ && utf16b >= 0xDC00 && utf16b <= 0xDFFF) {
+ codepoint = utf16pair_to_codepoint(utf16a, utf16b);
+ utf8len = codepoint_to_utf8(codepoint, utf8buf);
+ }
+ }
+ }
+ }
+ }
+ return utf8len;
+}
+
+static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) {
+ // note: this function expects that str contains the enclosing quotes!
+
+ cxmutstr result;
+ result.length = 0;
+ result.ptr = cxMalloc(a, str.length - 1);
+ if (result.ptr == NULL) return result; // LCOV_EXCL_LINE
+
+ bool u = false;
+ for (size_t i = 1; i < str.length - 1; i++) {
+ char c = str.ptr[i];
+ if (u) {
+ u = false;
+ if (c == 'n') {
+ c = '\n';
+ } else if (c == '"') {
+ c = '"';
+ } else if (c == 't') {
+ c = '\t';
+ } else if (c == 'r') {
+ c = '\r';
+ } else if (c == '\\') {
+ c = '\\';
+ } else if (c == '/') {
+ c = '/'; // always unescape, we don't need settings here
+ } else if (c == 'f') {
+ c = '\f';
+ } else if (c == 'b') {
+ c = '\b';
+ } else if (c == 'u') {
+ char utf8buf[4];
+ unsigned utf8len = unescape_unicode_string(
+ cx_strn(str.ptr + i - 1, str.length + 1 - i),
+ utf8buf
+ );
+ if(utf8len > 0) {
+ i += utf8len < 4 ? 4 : 10;
+ // add all bytes from utf8buf except the last char
+ // to the result (last char will be added below)
+ utf8len--;
+ c = utf8buf[utf8len];
+ for (unsigned x = 0; x < utf8len; x++) {
+ result.ptr[result.length++] = utf8buf[x];
+ }
+ } else {
+ // decoding failed, ignore the entire sequence
+ result.ptr[result.length++] = '\\';
+ }
+ } else {
+ // TODO: discuss the behavior for unrecognized escape sequences
+ // most parsers throw an error here - we just ignore it
+ result.ptr[result.length++] = '\\';
+ }
+
+ result.ptr[result.length++] = c;
+ } else {
+ if (c == '\\') {
+ u = true;
+ } else {
+ result.ptr[result.length++] = c;
+ }
+ }
+ }
+ result.ptr[result.length] = 0;
+
+ return result;
+}
+
+static cxmutstr escape_string(cxmutstr str, bool escape_slash) {
+ // note: this function produces the string without enclosing quotes
+ // the reason is that we don't want to allocate memory just for that
+ CxBuffer buf = {0};
+
+ bool all_printable = true;
+ for (size_t i = 0; i < str.length; i++) {
+ unsigned char c = str.ptr[i];
+ bool escape = c < 0x20 || c == '\\' || c == '"'
+ || (escape_slash && c == '/');
+
+ if (all_printable && escape) {
+ size_t capa = str.length + 32;
+ char *space = cxMallocDefault(capa);
+ if (space == NULL) return cx_mutstrn(NULL, 0);
+ cxBufferInit(&buf, space, capa, NULL, CX_BUFFER_AUTO_EXTEND);
+ cxBufferWrite(str.ptr, 1, i, &buf);
+ all_printable = false;
+ }
+ if (escape) {
+ cxBufferPut(&buf, '\\');
+ if (c == '\"') {
+ cxBufferPut(&buf, '\"');
+ } else if (c == '\n') {
+ cxBufferPut(&buf, 'n');
+ } else if (c == '\t') {
+ cxBufferPut(&buf, 't');
+ } else if (c == '\r') {
+ cxBufferPut(&buf, 'r');
+ } else if (c == '\\') {
+ cxBufferPut(&buf, '\\');
+ } else if (c == '/') {
+ cxBufferPut(&buf, '/');
+ } else if (c == '\f') {
+ cxBufferPut(&buf, 'f');
+ } else if (c == '\b') {
+ cxBufferPut(&buf, 'b');
+ } else {
+ char code[6];
+ snprintf(code, sizeof(code), "u%04x", (unsigned int) c);
+ cxBufferPutString(&buf, code);
+ }
+ } else if (!all_printable) {
+ cxBufferPut(&buf, c);
+ }
+ }
+ if (!all_printable) {
+ str = cx_mutstrn(buf.space, buf.size);
+ }
+ cxBufferDestroy(&buf);
+ return str;
+}
+
+static CxJsonValue* json_create_value(CxJson *json, CxJsonValueType type) {
+ CxJsonValue *v = cxCalloc(json->allocator, 1, sizeof(CxJsonValue));
+ if (v == NULL) return NULL; // LCOV_EXCL_LINE
+
+ // initialize the value
+ v->type = type;
+ v->allocator = json->allocator;
+ if (type == CX_JSON_ARRAY) {
+ cx_array_initialize_a(json->allocator, v->value.array.array, 16);
+ if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ } else if (type == CX_JSON_OBJECT) {
+ cx_array_initialize_a(json->allocator, v->value.object.values, 16);
+ v->value.object.indices = cxCalloc(json->allocator, 16, sizeof(size_t));
+ if (v->value.object.values == NULL ||
+ v->value.object.indices == NULL)
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+
+ // add the new value to a possible parent
+ if (json->vbuf_size > 0) {
+ CxJsonValue *parent = json->vbuf[json->vbuf_size - 1];
+ assert(parent != NULL);
+ if (parent->type == CX_JSON_ARRAY) {
+ CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
+ if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ } else if (parent->type == CX_JSON_OBJECT) {
+ // the member was already created after parsing the name
+ assert(json->uncompleted_member.name.ptr != NULL);
+ json->uncompleted_member.value = v;
+ if (json_add_objvalue(parent, json->uncompleted_member)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ json->uncompleted_member.name = (cxmutstr) {NULL, 0};
+ } else {
+ assert(false); // LCOV_EXCL_LINE
+ }
+ }
+
+ // add the new value to the stack, if it is an array or object
+ if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) {
+ CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal);
+ if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
+ goto create_json_value_exit_error; // LCOV_EXCL_LINE
+ }
+ }
+
+ // if currently no value is parsed, this is now the value of interest
+ if (json->parsed == NULL) {
+ json->parsed = v;
+ }
+
+ return v;
+ // LCOV_EXCL_START
+create_json_value_exit_error:
+ cxJsonValueFree(v);
+ return NULL;
+ // LCOV_EXCL_STOP
+}
+
+#define JP_STATE_VALUE_BEGIN 0
+#define JP_STATE_VALUE_END 10
+#define JP_STATE_VALUE_BEGIN_OBJ 1
+#define JP_STATE_OBJ_SEP_OR_CLOSE 11
+#define JP_STATE_VALUE_BEGIN_AR 2
+#define JP_STATE_ARRAY_SEP_OR_CLOSE 12
+#define JP_STATE_OBJ_NAME_OR_CLOSE 5
+#define JP_STATE_OBJ_NAME 6
+#define JP_STATE_OBJ_COLON 7
+
+void cxJsonInit(CxJson *json, const CxAllocator *allocator) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+
+ memset(json, 0, sizeof(CxJson));
+ json->allocator = allocator;
+
+ json->states = json->states_internal;
+ json->states_capacity = cx_nmemb(json->states_internal);
+ json->states[0] = JP_STATE_VALUE_BEGIN;
+ json->states_size = 1;
+
+ json->vbuf = json->vbuf_internal;
+ json->vbuf_capacity = cx_nmemb(json->vbuf_internal);
+}
+
+void cxJsonDestroy(CxJson *json) {
+ cxBufferDestroy(&json->buffer);
+ if (json->states != json->states_internal) {
+ cxFreeDefault(json->states);
+ }
+ if (json->vbuf != json->vbuf_internal) {
+ cxFreeDefault(json->vbuf);
+ }
+ cxJsonValueFree(json->parsed);
+ json->parsed = NULL;
+ if (json->uncompleted_member.name.ptr != NULL) {
+ cx_strfree_a(json->allocator, &json->uncompleted_member.name);
+ json->uncompleted_member = (CxJsonObjValue){{NULL, 0}, NULL};
+ }
+}
+
+int cxJsonFilln(CxJson *json, const char *buf, size_t size) {
+ if (cxBufferEof(&json->buffer)) {
+ // reinitialize the buffer
+ cxBufferDestroy(&json->buffer);
+ cxBufferInit(&json->buffer, (char*) buf, size,
+ NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_COPY_ON_WRITE);
+ json->buffer.size = size;
+ return 0;
+ } else {
+ return size != cxBufferAppend(buf, 1, size, &json->buffer);
+ }
+}
+
+static void json_add_state(CxJson *json, int state) {
+ // we have guaranteed the necessary space with cx_array_simple_reserve()
+ // therefore, we can safely add the state in the simplest way possible
+ json->states[json->states_size++] = state;
+}
+
+#define return_rec(code) \
+ token_destroy(&token); \
+ return code
+
+static enum cx_json_status json_parse(CxJson *json) {
+ // Reserve a pointer for a possibly read value
+ CxJsonValue *vbuf = NULL;
+
+ // grab the next token
+ CxJsonToken token;
+ {
+ enum cx_json_status ret = token_parse_next(json, &token);
+ if (ret != CX_JSON_NO_ERROR) {
+ return ret;
+ }
+ }
+
+ // pop the current state
+ assert(json->states_size > 0);
+ int state = json->states[--json->states_size];
+
+ // guarantee that at least two more states fit on the stack
+ CxArrayReallocator state_realloc = cx_array_reallocator(NULL, json->states_internal);
+ if (cx_array_simple_reserve_a(&state_realloc, json->states, 2)) {
+ return CX_JSON_BUFFER_ALLOC_FAILED; // LCOV_EXCL_LINE
+ }
+
+
+ // 0 JP_STATE_VALUE_BEGIN value begin
+ // 10 JP_STATE_VALUE_END expect value end
+ // 1 JP_STATE_VALUE_BEGIN_OBJ value begin (inside object)
+ // 11 JP_STATE_OBJ_SEP_OR_CLOSE object, expect separator, objclose
+ // 2 JP_STATE_VALUE_BEGIN_AR value begin (inside array)
+ // 12 JP_STATE_ARRAY_SEP_OR_CLOSE array, expect separator or arrayclose
+ // 5 JP_STATE_OBJ_NAME_OR_CLOSE object, expect name or objclose
+ // 6 JP_STATE_OBJ_NAME object, expect name
+ // 7 JP_STATE_OBJ_COLON object, expect ':'
+
+ if (state < 3) {
+ // push expected end state to the stack
+ json_add_state(json, 10 + state);
+ switch (token.tokentype) {
+ case CX_JSON_TOKEN_BEGIN_ARRAY: {
+ if (json_create_value(json, CX_JSON_ARRAY) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ json_add_state(json, JP_STATE_VALUE_BEGIN_AR);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_BEGIN_OBJECT: {
+ if (json_create_value(json, CX_JSON_OBJECT) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ json_add_state(json, JP_STATE_OBJ_NAME_OR_CLOSE);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_STRING: {
+ if ((vbuf = json_create_value(json, CX_JSON_STRING)) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ cxmutstr str = unescape_string(json->allocator, token.content);
+ if (str.ptr == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ vbuf->value.string = str;
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_INTEGER:
+ case CX_JSON_TOKEN_NUMBER: {
+ int type = token.tokentype == CX_JSON_TOKEN_INTEGER ? CX_JSON_INTEGER : CX_JSON_NUMBER;
+ if (NULL == (vbuf = json_create_value(json, type))) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ if (type == CX_JSON_INTEGER) {
+ if (cx_strtoi64(token.content, &vbuf->value.integer, 10)) {
+ return_rec(CX_JSON_FORMAT_ERROR_NUMBER);
+ }
+ } else {
+ if (cx_strtod(token.content, &vbuf->value.number)) {
+ return_rec(CX_JSON_FORMAT_ERROR_NUMBER);
+ }
+ }
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ case CX_JSON_TOKEN_LITERAL: {
+ if ((vbuf = json_create_value(json, CX_JSON_LITERAL)) == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ if (0 == cx_strcmp(cx_strcast(token.content), cx_str("true"))) {
+ vbuf->value.literal = CX_JSON_TRUE;
+ } else if (0 == cx_strcmp(cx_strcast(token.content), cx_str("false"))) {
+ vbuf->value.literal = CX_JSON_FALSE;
+ } else {
+ vbuf->value.literal = CX_JSON_NULL;
+ }
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ default: {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ }
+ } else if (state == JP_STATE_ARRAY_SEP_OR_CLOSE) {
+ // expect ',' or ']'
+ if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) {
+ json_add_state(json, JP_STATE_VALUE_BEGIN_AR);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (token.tokentype == CX_JSON_TOKEN_END_ARRAY) {
+ // discard the array from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ } else if (state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) {
+ if (state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
+ // discard the obj from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ // expect string
+ if (token.tokentype != CX_JSON_TOKEN_STRING) {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+
+ // add new entry
+ cxmutstr name = unescape_string(json->allocator, token.content);
+ if (name.ptr == NULL) {
+ return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
+ }
+ assert(json->uncompleted_member.name.ptr == NULL);
+ json->uncompleted_member.name = name;
+ assert(json->vbuf_size > 0);
+
+ // next state
+ json_add_state(json, JP_STATE_OBJ_COLON);
+ return_rec(CX_JSON_NO_ERROR);
+ }
+ } else if (state == JP_STATE_OBJ_COLON) {
+ // expect ':'
+ if (token.tokentype != CX_JSON_TOKEN_NAME_SEPARATOR) {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ // next state
+ json_add_state(json, JP_STATE_VALUE_BEGIN_OBJ);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (state == JP_STATE_OBJ_SEP_OR_CLOSE) {
+ // expect ',' or '}'
+ if (token.tokentype == CX_JSON_TOKEN_VALUE_SEPARATOR) {
+ json_add_state(json, JP_STATE_OBJ_NAME);
+ return_rec(CX_JSON_NO_ERROR);
+ } else if (token.tokentype == CX_JSON_TOKEN_END_OBJECT) {
+ // discard the obj from the value buffer
+ json->vbuf_size--;
+ return_rec(CX_JSON_NO_ERROR);
+ } else {
+ return_rec(CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN);
+ }
+ } else {
+ // should be unreachable
+ assert(false);
+ return_rec(-1);
+ }
+}
+
+CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value) {
+ // check if buffer has been filled
+ if (json->buffer.space == NULL) {
+ return CX_JSON_NULL_DATA;
+ }
+
+ // initialize output value
+ *value = &cx_json_value_nothing;
+
+ // parse data
+ CxJsonStatus result;
+ do {
+ result = json_parse(json);
+ if (result == CX_JSON_NO_ERROR && json->states_size == 1) {
+ // final state reached
+ assert(json->states[0] == JP_STATE_VALUE_END);
+ assert(json->vbuf_size == 0);
+
+ // write output value
+ *value = json->parsed;
+ json->parsed = NULL;
+
+ // re-initialize state machine
+ json->states[0] = JP_STATE_VALUE_BEGIN;
+
+ return CX_JSON_NO_ERROR;
+ }
+ } while (result == CX_JSON_NO_ERROR);
+
+ // the parser might think there is no data
+ // but when we did not reach the final state,
+ // we know that there must be more to come
+ if (result == CX_JSON_NO_DATA && json->states_size > 1) {
+ return CX_JSON_INCOMPLETE_DATA;
+ }
+
+ return result;
+}
+
+void cxJsonValueFree(CxJsonValue *value) {
+ if (value == NULL || value->type == CX_JSON_NOTHING) return;
+ switch (value->type) {
+ case CX_JSON_OBJECT: {
+ CxJsonObject obj = value->value.object;
+ for (size_t i = 0; i < obj.values_size; i++) {
+ cxJsonValueFree(obj.values[i].value);
+ cx_strfree_a(value->allocator, &obj.values[i].name);
+ }
+ cxFree(value->allocator, obj.values);
+ cxFree(value->allocator, obj.indices);
+ break;
+ }
+ case CX_JSON_ARRAY: {
+ CxJsonArray array = value->value.array;
+ for (size_t i = 0; i < array.array_size; i++) {
+ cxJsonValueFree(array.array[i]);
+ }
+ cxFree(value->allocator, array.array);
+ break;
+ }
+ case CX_JSON_STRING: {
+ cxFree(value->allocator, value->value.string.ptr);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ cxFree(value->allocator, value);
+}
+
+CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_OBJECT;
+ cx_array_initialize_a(allocator, v->value.object.values, 16);
+ if (v->value.object.values == NULL) { // LCOV_EXCL_START
+ cxFree(allocator, v);
+ return NULL;
+ // LCOV_EXCL_STOP
+ }
+ v->value.object.indices = cxCalloc(allocator, 16, sizeof(size_t));
+ if (v->value.object.indices == NULL) { // LCOV_EXCL_START
+ cxFree(allocator, v->value.object.values);
+ cxFree(allocator, v);
+ return NULL;
+ // LCOV_EXCL_STOP
+ }
+ return v;
+}
+
+CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_ARRAY;
+ cx_array_initialize_a(allocator, v->value.array.array, 16);
+ if (v->value.array.array == NULL) { cxFree(allocator, v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_NUMBER;
+ v->value.number = num;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_INTEGER;
+ v->value.integer = num;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char* str) {
+ return cxJsonCreateCxString(allocator, cx_str(str));
+}
+
+CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_STRING;
+ cxmutstr s = cx_strdup_a(allocator, str);
+ if (s.ptr == NULL) { cxFree(allocator, v); return NULL; }
+ v->value.string = s;
+ return v;
+}
+
+CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit) {
+ if (allocator == NULL) allocator = cxDefaultAllocator;
+ CxJsonValue* v = cxMalloc(allocator, sizeof(CxJsonValue));
+ if (v == NULL) return NULL;
+ v->allocator = allocator;
+ v->type = CX_JSON_LITERAL;
+ v->value.literal = lit;
+ return v;
+}
+
+// LCOV_EXCL_START
+// never called as long as malloc() does not return NULL
+static void json_arr_free_temp(CxJsonValue** values, size_t count) {
+ for (size_t i = 0; i < count; i++) {
+ if (values[i] == NULL) break;
+ cxJsonValueFree(values[i]);
+ }
+ cxFreeDefault(values);
+}
+// LCOV_EXCL_STOP
+
+int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) {
+ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateNumber(arr->allocator, num[i]);
+ if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ cxFreeDefault(values);
+ return ret;
+}
+
+int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) {
+ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateInteger(arr->allocator, num[i]);
+ if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ cxFreeDefault(values);
+ return ret;
+}
+
+int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) {
+ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateString(arr->allocator, str[i]);
+ if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ cxFreeDefault(values);
+ return ret;
+}
+
+int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) {
+ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateCxString(arr->allocator, str[i]);
+ if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ cxFreeDefault(values);
+ return ret;
+}
+
+int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) {
+ CxJsonValue** values = cxCallocDefault(count, sizeof(CxJsonValue*));
+ if (values == NULL) return -1;
+ for (size_t i = 0; i < count; i++) {
+ values[i] = cxJsonCreateLiteral(arr->allocator, lit[i]);
+ if (values[i] == NULL) { json_arr_free_temp(values, count); return -1; }
+ }
+ int ret = cxJsonArrAddValues(arr, values, count);
+ cxFreeDefault(values);
+ return ret;
+}
+
+int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count) {
+ CxArrayReallocator value_realloc = cx_array_reallocator(arr->allocator, NULL);
+ assert(arr->type == CX_JSON_ARRAY);
+ return cx_array_simple_copy_a(&value_realloc,
+ arr->value.array.array,
+ arr->value.array.array_size,
+ val, count
+ );
+}
+
+int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child) {
+ cxmutstr k = cx_strdup_a(obj->allocator, name);
+ if (k.ptr == NULL) return -1;
+ CxJsonObjValue kv = {k, child};
+ if (json_add_objvalue(obj, kv)) {
+ cx_strfree_a(obj->allocator, &k);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) {
+ CxJsonValue* v = cxJsonCreateObj(obj->allocator);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name) {
+ CxJsonValue* v = cxJsonCreateArr(obj->allocator);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num) {
+ CxJsonValue* v = cxJsonCreateNumber(obj->allocator, num);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num) {
+ CxJsonValue* v = cxJsonCreateInteger(obj->allocator, num);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str) {
+ CxJsonValue* v = cxJsonCreateString(obj->allocator, str);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str) {
+ CxJsonValue* v = cxJsonCreateCxString(obj->allocator, str);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL; }
+ return v;
+}
+
+CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit) {
+ CxJsonValue* v = cxJsonCreateLiteral(obj->allocator, lit);
+ if (v == NULL) return NULL;
+ if (cxJsonObjPut(obj, name, v)) { cxJsonValueFree(v); return NULL;}
+ return v;
+}
+
+CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index) {
+ if (index >= value->value.array.array_size) {
+ return &cx_json_value_nothing;
+ }
+ return value->value.array.array[index];
+}
+
+CxIterator cxJsonArrIter(const CxJsonValue *value) {
+ return cxIteratorPtr(
+ value->value.array.array,
+ value->value.array.array_size
+ );
+}
+
+CxIterator cxJsonObjIter(const CxJsonValue *value) {
+ return cxIterator(
+ value->value.object.values,
+ sizeof(CxJsonObjValue),
+ value->value.object.values_size
+ );
+}
+
+CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) {
+ CxJsonObjValue *member = json_find_objvalue(value, name);
+ if (member == NULL) {
+ return &cx_json_value_nothing;
+ } else {
+ return member->value;
+ }
+}
+
+CxJsonWriter cxJsonWriterCompact(void) {
+ return (CxJsonWriter) {
+ false,
+ true,
+ 6,
+ false,
+ 4,
+ false
+ };
+}
+
+CxJsonWriter cxJsonWriterPretty(bool use_spaces) {
+ return (CxJsonWriter) {
+ true,
+ true,
+ 6,
+ use_spaces,
+ 4,
+ false
+ };
+}
+
+static int cx_json_writer_indent(
+ void *target,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings,
+ unsigned int depth
+) {
+ if (depth == 0) return 0;
+
+ // determine the width and characters to use
+ const char* indent; // for 32 prepared chars
+ size_t width = depth;
+ if (settings->indent_space) {
+ if (settings->indent == 0) return 0;
+ width *= settings->indent;
+ indent = " ";
+ } else {
+ indent = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+ }
+
+ // calculate the number of write calls and write
+ size_t full = width / 32;
+ size_t remaining = width % 32;
+ for (size_t i = 0; i < full; i++) {
+ if (32 != wfunc(indent, 1, 32, target)) return 1;
+ }
+ if (remaining != wfunc(indent, 1, remaining, target)) return 1;
+
+ return 0;
+}
+
+
+int cx_json_write_rec(
+ void *target,
+ const CxJsonValue *value,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings,
+ unsigned int depth
+) {
+ // keep track of written items
+ // the idea is to reduce the number of jumps for error checking
+ size_t actual = 0, expected = 0;
+
+ // small buffer for number to string conversions
+ char numbuf[40];
+
+ // recursively write the values
+ switch (value->type) {
+ case CX_JSON_OBJECT: {
+ const char *begin_obj = "{\n";
+ if (settings->pretty) {
+ actual += wfunc(begin_obj, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(begin_obj, 1, 1, target);
+ expected++;
+ }
+ depth++;
+ size_t elem_count = value->value.object.values_size;
+ for (size_t look_idx = 0; look_idx < elem_count; look_idx++) {
+ // get the member either via index array or directly
+ size_t elem_idx = settings->sort_members
+ ? look_idx
+ : value->value.object.indices[look_idx];
+ CxJsonObjValue *member = &value->value.object.values[elem_idx];
+ if (settings->sort_members) {
+ depth++;depth--;
+ }
+
+ // possible indentation
+ if (settings->pretty) {
+ if (cx_json_writer_indent(target, wfunc, settings, depth)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+ }
+
+ // the name
+ actual += wfunc("\"", 1, 1, target);
+ cxmutstr name = escape_string(member->name, settings->escape_slash);
+ actual += wfunc(name.ptr, 1, name.length, target);
+ if (name.ptr != member->name.ptr) {
+ cx_strfree(&name);
+ }
+ actual += wfunc("\"", 1, 1, target);
+ const char *obj_name_sep = ": ";
+ if (settings->pretty) {
+ actual += wfunc(obj_name_sep, 1, 2, target);
+ expected += 4 + member->name.length;
+ } else {
+ actual += wfunc(obj_name_sep, 1, 1, target);
+ expected += 3 + member->name.length;
+ }
+
+ // the value
+ if (cx_json_write_rec(target, member->value, wfunc, settings, depth)) return 1;
+
+ // end of object-value
+ if (look_idx < elem_count - 1) {
+ const char *obj_value_sep = ",\n";
+ if (settings->pretty) {
+ actual += wfunc(obj_value_sep, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(obj_value_sep, 1, 1, target);
+ expected++;
+ }
+ } else {
+ if (settings->pretty) {
+ actual += wfunc("\n", 1, 1, target);
+ expected ++;
+ }
+ }
+ }
+ depth--;
+ if (settings->pretty) {
+ if (cx_json_writer_indent(target, wfunc, settings, depth)) return 1;
+ }
+ actual += wfunc("}", 1, 1, target);
+ expected++;
+ break;
+ }
+ case CX_JSON_ARRAY: {
+ actual += wfunc("[", 1, 1, target);
+ expected++;
+ CxIterator iter = cxJsonArrIter(value);
+ cx_foreach(CxJsonValue*, element, iter) {
+ if (cx_json_write_rec(
+ target, element,
+ wfunc, settings, depth)
+ ) return 1;
+
+ if (iter.index < iter.elem_count - 1) {
+ const char *arr_value_sep = ", ";
+ if (settings->pretty) {
+ actual += wfunc(arr_value_sep, 1, 2, target);
+ expected += 2;
+ } else {
+ actual += wfunc(arr_value_sep, 1, 1, target);
+ expected++;
+ }
+ }
+ }
+ actual += wfunc("]", 1, 1, target);
+ expected++;
+ break;
+ }
+ case CX_JSON_STRING: {
+ actual += wfunc("\"", 1, 1, target);
+ cxmutstr str = escape_string(value->value.string, settings->escape_slash);
+ actual += wfunc(str.ptr, 1, str.length, target);
+ if (str.ptr != value->value.string.ptr) {
+ cx_strfree(&str);
+ }
+ actual += wfunc("\"", 1, 1, target);
+ expected += 2 + value->value.string.length;
+ break;
+ }
+ case CX_JSON_NUMBER: {
+ int precision = settings->frac_max_digits;
+ // because of the way how %g is defined, we need to
+ // double the precision and truncate ourselves
+ precision = 1 + (precision > 15 ? 30 : 2 * precision);
+ snprintf(numbuf, 40, "%.*g", precision, value->value.number);
+ char *dot, *exp;
+ unsigned char max_digits;
+ // find the decimal separator and hope that it's one of . or ,
+ dot = strchr(numbuf, '.');
+ if (dot == NULL) {
+ dot = strchr(numbuf, ',');
+ }
+ if (dot == NULL) {
+ // no decimal separator found
+ // output everything until a possible exponent
+ max_digits = 30;
+ dot = numbuf;
+ } else {
+ // found a decimal separator
+ // output everything until the separator
+ // and set max digits to what the settings say
+ size_t len = dot - numbuf;
+ actual += wfunc(numbuf, 1, len, target);
+ expected += len;
+ max_digits = settings->frac_max_digits;
+ if (max_digits > 15) {
+ max_digits = 15;
+ }
+ // locale independent separator
+ if (max_digits > 0) {
+ actual += wfunc(".", 1, 1, target);
+ expected++;
+ }
+ dot++;
+ }
+ // find the exponent
+ exp = strchr(dot, 'e');
+ if (exp == NULL) {
+ // no exponent - output the rest
+ if (max_digits > 0) {
+ size_t len = strlen(dot);
+ if (len > max_digits) {
+ len = max_digits;
+ }
+ actual += wfunc(dot, 1, len, target);
+ expected += len;
+ }
+ } else {
+ // exponent found - truncate the frac digits
+ // and then output the rest
+ if (max_digits > 0) {
+ size_t len = exp - dot - 1;
+ if (len > max_digits) {
+ len = max_digits;
+ }
+ actual += wfunc(dot, 1, len, target);
+ expected += len;
+ }
+ actual += wfunc("e", 1, 1, target);
+ expected++;
+ exp++;
+ size_t len = strlen(exp);
+ actual += wfunc(exp, 1, len, target);
+ expected += len;
+ }
+ break;
+ }
+ case CX_JSON_INTEGER: {
+ snprintf(numbuf, 32, "%" PRIi64, value->value.integer);
+ size_t len = strlen(numbuf);
+ actual += wfunc(numbuf, 1, len, target);
+ expected += len;
+ break;
+ }
+ case CX_JSON_LITERAL: {
+ if (value->value.literal == CX_JSON_TRUE) {
+ actual += wfunc("true", 1, 4, target);
+ expected += 4;
+ } else if (value->value.literal == CX_JSON_FALSE) {
+ actual += wfunc("false", 1, 5, target);
+ expected += 5;
+ } else {
+ actual += wfunc("null", 1, 4, target);
+ expected += 4;
+ }
+ break;
+ }
+ case CX_JSON_NOTHING: {
+ // deliberately supported as an empty string!
+ // users might want to just write the result
+ // of a get operation without testing the value
+ // and therefore this should not blow up
+ break;
+ }
+ default: assert(false); // LCOV_EXCL_LINE
+ }
+
+ return expected != actual;
+}
+
+int cxJsonWrite(
+ void *target,
+ const CxJsonValue *value,
+ cx_write_func wfunc,
+ const CxJsonWriter *settings
+) {
+ assert(target != NULL);
+ assert(value != NULL);
+ assert(wfunc != NULL);
+
+ CxJsonWriter writer_default = cxJsonWriterCompact();
+ if (settings == NULL) {
+ settings = &writer_default;
+ }
+ return cx_json_write_rec(target, value, wfunc, settings, 0);
+}
*/
#include "cx/linked_list.h"
-#include "cx/utils.h"
+#include "cx/compare.h"
#include <string.h>
#include <assert.h>
#define ll_data(node) (((char*)(node))+loc_data)
void *cx_linked_list_at(
- void const *start,
+ const void *start,
size_t start_index,
ptrdiff_t loc_advance,
size_t index
assert(start != NULL);
assert(loc_advance >= 0);
size_t i = start_index;
- void const *cur = start;
+ const void *cur = start;
while (i != index && cur != NULL) {
cur = ll_advance(cur);
i < index ? i++ : i--;
return (void *) cur;
}
-ssize_t cx_linked_list_find(
- void const *start,
+void *cx_linked_list_find(
+ const void *start,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func,
- void const *elem
+ const void *elem,
+ size_t *found_index
) {
assert(start != NULL);
assert(loc_advance >= 0);
assert(loc_data >= 0);
assert(cmp_func);
- void const *node = start;
- ssize_t index = 0;
+ void *node = (void*) start;
+ size_t index = 0;
do {
void *current = ll_data(node);
if (cmp_func(current, elem) == 0) {
- return index;
+ if (found_index != NULL) {
+ *found_index = index;
+ }
+ return node;
}
node = ll_advance(node);
index++;
} while (node != NULL);
- return -1;
+ return NULL;
}
void *cx_linked_list_first(
- void const *node,
+ const void *node,
ptrdiff_t loc_prev
) {
return cx_linked_list_last(node, loc_prev);
}
void *cx_linked_list_last(
- void const *node,
+ const void *node,
ptrdiff_t loc_next
) {
assert(node != NULL);
assert(loc_next >= 0);
- void const *cur = node;
- void const *last;
+ const void *cur = node;
+ const void *last;
do {
last = cur;
} while ((cur = ll_next(cur)) != NULL);
}
void *cx_linked_list_prev(
- void const *begin,
+ const void *begin,
ptrdiff_t loc_next,
- void const *node
+ const void *node
) {
assert(begin != NULL);
assert(node != NULL);
assert(loc_next >= 0);
if (begin == node) return NULL;
- void const *cur = begin;
- void const *next;
+ const void *cur = begin;
+ const void *next;
while (1) {
next = ll_next(cur);
if (next == node) return (void *) cur;
}
}
-void cx_linked_list_remove(
+void cx_linked_list_insert_sorted(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *new_node,
+ cx_compare_func cmp_func
+) {
+ assert(ll_next(new_node) == NULL);
+ cx_linked_list_insert_sorted_chain(
+ begin, end, loc_prev, loc_next, new_node, cmp_func);
+}
+
+void cx_linked_list_insert_sorted_chain(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
- void *node
+ void *insert_begin,
+ cx_compare_func cmp_func
+) {
+ assert(begin != NULL);
+ assert(loc_next >= 0);
+ assert(insert_begin != NULL);
+
+ // track currently observed nodes
+ void *dest_prev = NULL;
+ void *dest = *begin;
+ void *src = insert_begin;
+
+ // special case: list is empty
+ if (dest == NULL) {
+ *begin = src;
+ if (end != NULL) {
+ *end = cx_linked_list_last(src, loc_next);
+ }
+ return;
+ }
+
+ // search the list for insertion points
+ while (dest != NULL && src != NULL) {
+ // compare current list node with source node
+ // if less or equal, skip
+ if (cmp_func(dest, src) <= 0) {
+ dest_prev = dest;
+ dest = ll_next(dest);
+ continue;
+ }
+
+ // determine chain of elements that can be inserted
+ void *end_of_chain = src;
+ void *next_in_chain = ll_next(src);
+ while (next_in_chain != NULL) {
+ // once we become larger than the list elem, break
+ if (cmp_func(dest, next_in_chain) <= 0) {
+ break;
+ }
+ // otherwise, we can insert one more
+ end_of_chain = next_in_chain;
+ next_in_chain = ll_next(next_in_chain);
+ }
+
+ // insert the elements
+ if (dest_prev == NULL) {
+ // new begin
+ *begin = src;
+ } else {
+ cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+ }
+ cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next);
+
+ // continue with next
+ src = next_in_chain;
+ dest_prev = dest;
+ dest = ll_next(dest);
+ }
+
+ // insert remaining items
+ if (src != NULL) {
+ cx_linked_list_link(dest_prev, src, loc_prev, loc_next);
+ }
+
+ // determine new end of list, if requested
+ if (end != NULL) {
+ *end = cx_linked_list_last(
+ dest != NULL ? dest : dest_prev, loc_next);
+ }
+}
+
+size_t cx_linked_list_remove_chain(
+ void **begin,
+ void **end,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next,
+ void *node,
+ size_t num
) {
assert(node != NULL);
assert(loc_next >= 0);
assert(loc_prev >= 0 || begin != NULL);
+ // easy exit
+ if (num == 0) return 0;
+
// find adjacent nodes
- void *next = ll_next(node);
void *prev;
if (loc_prev >= 0) {
prev = ll_prev(node);
prev = cx_linked_list_prev(*begin, loc_next, node);
}
+ void *next = ll_next(node);
+ size_t removed = 1;
+ for (; removed < num && next != NULL ; removed++) {
+ next = ll_next(next);
+ }
+
// update next pointer of prev node, or set begin
if (prev == NULL) {
if (begin != NULL) {
} else if (loc_prev >= 0) {
ll_prev(next) = prev;
}
+
+ return removed;
}
size_t cx_linked_list_size(
- void const *node,
+ const void *node,
ptrdiff_t loc_next
) {
assert(loc_next >= 0);
) {
void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
- malloc(sizeof(void *) * length) : sbo;
+ cxMallocDefault(sizeof(void *) * length) : sbo;
if (sorted == NULL) abort();
void *rc, *lc;
// Update pointer
if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
- cx_for_n (i, length - 1) {
+ for (size_t i = 0 ; i < length - 1; i++) {
cx_linked_list_link(sorted[i], sorted[i + 1], loc_prev, loc_next);
}
ll_next(sorted[length - 1]) = NULL;
*begin = sorted[0];
- *end = sorted[length-1];
+ *end = sorted[length - 1];
if (sorted != sbo) {
- free(sorted);
+ cxFreeDefault(sorted);
}
}
}
int cx_linked_list_compare(
- void const *begin_left,
- void const *begin_right,
+ const void *begin_left,
+ const void *begin_right,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func
) {
- void const *left = begin_left, *right = begin_right;
+ const void *left = begin_left, *right = begin_right;
while (left != NULL && right != NULL) {
- void const *left_data = ll_data(left);
- void const *right_data = ll_data(right);
+ const void *left_data = ll_data(left);
+ const void *right_data = ll_data(right);
int result = cmp_func(left_data, right_data);
if (result != 0) return result;
left = ll_advance(left);
// HIGH LEVEL LINKED LIST IMPLEMENTATION
-bool CX_DISABLE_LINKED_LIST_SWAP_SBO = false;
-
typedef struct cx_linked_list_node cx_linked_list_node;
struct cx_linked_list_node {
cx_linked_list_node *prev;
} cx_linked_list;
static cx_linked_list_node *cx_ll_node_at(
- cx_linked_list const *list,
+ const cx_linked_list *list,
size_t index
) {
- if (index >= list->base.size) {
+ if (index >= list->base.collection.size) {
return NULL;
- } else if (index > list->base.size / 2) {
- return cx_linked_list_at(list->end, list->base.size - 1, CX_LL_LOC_PREV, index);
+ } else if (index > list->base.collection.size / 2) {
+ return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index);
} else {
return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
}
}
+static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) {
+ return cxMalloc(list->collection.allocator,
+ sizeof(cx_linked_list_node) + list->collection.elem_size);
+}
+
static int cx_ll_insert_at(
struct cx_list_s *list,
cx_linked_list_node *node,
- void const *elem
+ const void *elem
) {
// create the new new_node
- cx_linked_list_node *new_node = cxMalloc(list->allocator,
- sizeof(cx_linked_list_node) + list->item_size);
+ cx_linked_list_node *new_node = cx_ll_malloc_node(list);
// sortir if failed
if (new_node == NULL) return 1;
// initialize new new_node
new_node->prev = new_node->next = NULL;
- memcpy(new_node->payload, elem, list->item_size);
+ if (elem != NULL) {
+ memcpy(new_node->payload, elem, list->collection.elem_size);
+ }
// insert
cx_linked_list *ll = (cx_linked_list *) list;
);
// increase the size and return
- list->size++;
+ list->collection.size++;
return 0;
}
static size_t cx_ll_insert_array(
struct cx_list_s *list,
size_t index,
- void const *array,
+ const void *array,
size_t n
) {
// out-of bounds and corner case check
- if (index > list->size || n == 0) return 0;
+ if (index > list->collection.size || n == 0) return 0;
// find position efficiently
cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
// perform first insert
- if (0 != cx_ll_insert_at(list, node, array)) {
- return 1;
- }
+ if (0 != cx_ll_insert_at(list, node, array)) return 1;
// is there more?
if (n == 1) return 1;
// we now know exactly where we are
node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;
- // we can add the remaining nodes and immedately advance to the inserted node
- char const *source = array;
+ // we can add the remaining nodes and immediately advance to the inserted node
+ const char *source = array;
for (size_t i = 1; i < n; i++) {
- source += list->item_size;
- if (0 != cx_ll_insert_at(list, node, source)) {
- return i;
- }
+ source += list->collection.elem_size;
+ if (0 != cx_ll_insert_at(list, node, source)) return i;
node = node->next;
}
return n;
}
-static int cx_ll_insert_element(
+static void *cx_ll_insert_element(
struct cx_list_s *list,
size_t index,
- void const *element
+ const void *element
) {
- return 1 != cx_ll_insert_array(list, index, element, 1);
+ // out-of-bounds check
+ if (index > list->collection.size) return NULL;
+
+ // find position efficiently
+ cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+ // perform first insert
+ if (cx_ll_insert_at(list, node, element)) return NULL;
+
+ // return a pointer to the data of the inserted node
+ if (node == NULL) {
+ return ((cx_linked_list *) list)->begin->payload;
+ } else {
+ return node->next->payload;
+ }
+}
+
+static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func;
+
+static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) {
+ const cx_linked_list_node *left = l;
+ const cx_linked_list_node *right = r;
+ return cx_ll_insert_sorted_cmp_func(left->payload, right->payload);
}
-static int cx_ll_remove(
+static size_t cx_ll_insert_sorted(
struct cx_list_s *list,
- size_t index
+ const void *array,
+ size_t n
+) {
+ // special case
+ if (n == 0) return 0;
+
+ // create a new chain of nodes
+ cx_linked_list_node *chain = cx_ll_malloc_node(list);
+ if (chain == NULL) return 0;
+
+ memcpy(chain->payload, array, list->collection.elem_size);
+ chain->prev = NULL;
+ chain->next = NULL;
+
+ // add all elements from the array to that chain
+ cx_linked_list_node *prev = chain;
+ const char *src = array;
+ size_t inserted = 1;
+ for (; inserted < n; inserted++) {
+ cx_linked_list_node *next = cx_ll_malloc_node(list);
+ if (next == NULL) break;
+ src += list->collection.elem_size;
+ memcpy(next->payload, src, list->collection.elem_size);
+ prev->next = next;
+ next->prev = prev;
+ prev = next;
+ }
+ prev->next = NULL;
+
+ // invoke the low level function
+ cx_linked_list *ll = (cx_linked_list *) list;
+ cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc;
+ cx_linked_list_insert_sorted_chain(
+ (void **) &ll->begin,
+ (void **) &ll->end,
+ CX_LL_LOC_PREV,
+ CX_LL_LOC_NEXT,
+ chain,
+ cx_ll_insert_sorted_cmp_helper
+ );
+
+ // adjust the list metadata
+ list->collection.size += inserted;
+
+ return inserted;
+}
+
+static size_t cx_ll_remove(
+ struct cx_list_s *list,
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = cx_ll_node_at(ll, index);
// out-of-bounds check
- if (node == NULL) return 1;
-
- // element destruction
- cx_invoke_destructor(list, node->payload);
+ if (node == NULL) return 0;
// remove
- cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
- CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ size_t removed = cx_linked_list_remove_chain(
+ (void **) &ll->begin,
+ (void **) &ll->end,
+ CX_LL_LOC_PREV,
+ CX_LL_LOC_NEXT,
+ node,
+ num
+ );
// adjust size
- list->size--;
-
- // free and return
- cxFree(list->allocator, node);
+ list->collection.size -= removed;
+
+ // copy or destroy the removed chain
+ if (targetbuf == NULL) {
+ cx_linked_list_node *n = node;
+ for (size_t i = 0; i < removed; i++) {
+ // element destruction
+ cx_invoke_destructor(list, n->payload);
+
+ // free the node and advance
+ void *next = n->next;
+ cxFree(list->collection.allocator, n);
+ n = next;
+ }
+ } else {
+ char *dest = targetbuf;
+ cx_linked_list_node *n = node;
+ for (size_t i = 0; i < removed; i++) {
+ // copy payload
+ memcpy(dest, n->payload, list->collection.elem_size);
+
+ // advance target buffer
+ dest += list->collection.elem_size;
+
+ // free the node and advance
+ void *next = n->next;
+ cxFree(list->collection.allocator, n);
+ n = next;
+ }
+ }
- return 0;
+ return removed;
}
static void cx_ll_clear(struct cx_list_s *list) {
- if (list->size == 0) return;
+ if (list->collection.size == 0) return;
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = ll->begin;
while (node != NULL) {
cx_invoke_destructor(list, node->payload);
cx_linked_list_node *next = node->next;
- cxFree(list->allocator, node);
+ cxFree(list->collection.allocator, node);
node = next;
}
ll->begin = ll->end = NULL;
- list->size = 0;
+ list->collection.size = 0;
}
-#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
-#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
-#endif
-
static int cx_ll_swap(
struct cx_list_s *list,
size_t i,
size_t j
) {
- if (i >= list->size || j >= list->size) return 1;
+ if (i >= list->collection.size || j >= list->collection.size) return 1;
if (i == j) return 0;
// perform an optimized search that finds both elements in one run
cx_linked_list *ll = (cx_linked_list *) list;
- size_t mid = list->size / 2;
+ size_t mid = list->collection.size / 2;
size_t left, right;
if (i < j) {
left = i;
left = j;
right = i;
}
- cx_linked_list_node *nleft, *nright;
+ cx_linked_list_node *nleft = NULL, *nright = NULL;
if (left < mid && right < mid) {
// case 1: both items left from mid
nleft = cx_ll_node_at(ll, left);
+ assert(nleft != NULL);
nright = nleft;
for (size_t c = left; c < right; c++) {
nright = nright->next;
} else if (left >= mid && right >= mid) {
// case 2: both items right from mid
nright = cx_ll_node_at(ll, right);
+ assert(nright != NULL);
nleft = nright;
for (size_t c = right; c > left; c--) {
nleft = nleft->prev;
// chose the closest to begin / end
size_t closest;
size_t other;
- size_t diff2boundary = list->size - right - 1;
+ size_t diff2boundary = list->collection.size - right - 1;
if (left <= diff2boundary) {
closest = left;
other = right;
}
}
- if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE || CX_DISABLE_LINKED_LIST_SWAP_SBO) {
- cx_linked_list_node *prev = nleft->prev;
- cx_linked_list_node *next = nright->next;
- cx_linked_list_node *midstart = nleft->next;
- cx_linked_list_node *midend = nright->prev;
+ cx_linked_list_node *prev = nleft->prev;
+ cx_linked_list_node *next = nright->next;
+ cx_linked_list_node *midstart = nleft->next;
+ cx_linked_list_node *midend = nright->prev;
- if (prev == NULL) {
- ll->begin = nright;
- } else {
- prev->next = nright;
- }
- nright->prev = prev;
- if (midstart == nright) {
- // special case: both nodes are adjacent
- nright->next = nleft;
- nleft->prev = nright;
- } else {
- // likely case: a chain is between the two nodes
- nright->next = midstart;
- midstart->prev = nright;
- midend->next = nleft;
- nleft->prev = midend;
- }
- nleft->next = next;
- if (next == NULL) {
- ll->end = nleft;
- } else {
- next->prev = nleft;
- }
+ if (prev == NULL) {
+ ll->begin = nright;
+ } else {
+ prev->next = nright;
+ }
+ nright->prev = prev;
+ if (midstart == nright) {
+ // special case: both nodes are adjacent
+ nright->next = nleft;
+ nleft->prev = nright;
} else {
- // swap payloads to avoid relinking
- char buf[CX_LINKED_LIST_SWAP_SBO_SIZE];
- memcpy(buf, nleft->payload, list->item_size);
- memcpy(nleft->payload, nright->payload, list->item_size);
- memcpy(nright->payload, buf, list->item_size);
+ // likely case: a chain is between the two nodes
+ nright->next = midstart;
+ midstart->prev = nright;
+ midend->next = nleft;
+ nleft->prev = midend;
+ }
+ nleft->next = next;
+ if (next == NULL) {
+ ll->end = nleft;
+ } else {
+ next->prev = nleft;
}
return 0;
}
static void *cx_ll_at(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index
) {
cx_linked_list *ll = (cx_linked_list *) list;
return node == NULL ? NULL : node->payload;
}
-static ssize_t cx_ll_find(
- struct cx_list_s const *list,
- void const *elem
+static size_t cx_ll_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
) {
- return cx_linked_list_find(((cx_linked_list *) list)->begin,
- CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
- list->cmpfunc, elem);
+ if (list->collection.size == 0) return 0;
+
+ size_t index;
+ cx_linked_list *ll = ((cx_linked_list *) list);
+ cx_linked_list_node *node = cx_linked_list_find(
+ ll->begin,
+ CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+ list->collection.cmpfunc, elem,
+ &index
+ );
+ if (node == NULL) {
+ return list->collection.size;
+ }
+ if (remove) {
+ cx_invoke_destructor(list, node->payload);
+ cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+ CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
+ }
+ return index;
}
static void cx_ll_sort(struct cx_list_s *list) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
- list->cmpfunc);
+ list->collection.cmpfunc);
}
static void cx_ll_reverse(struct cx_list_s *list) {
}
static int cx_ll_compare(
- struct cx_list_s const *list,
- struct cx_list_s const *other
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
) {
cx_linked_list *left = (cx_linked_list *) list;
cx_linked_list *right = (cx_linked_list *) other;
return cx_linked_list_compare(left->begin, right->begin,
CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
- list->cmpfunc);
+ list->collection.cmpfunc);
}
-static bool cx_ll_iter_valid(void const *it) {
- struct cx_iterator_s const *iter = it;
+static bool cx_ll_iter_valid(const void *it) {
+ const struct cx_iterator_s *iter = it;
return iter->elem_handle != NULL;
}
static void cx_ll_iter_next(void *it) {
- struct cx_iterator_base_s *itbase = it;
- if (itbase->remove) {
- itbase->remove = false;
- struct cx_mut_iterator_s *iter = it;
- struct cx_list_s *list = iter->src_handle;
- cx_linked_list *ll = iter->src_handle;
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ struct cx_list_s *list = iter->src_handle.m;
+ cx_linked_list *ll = iter->src_handle.m;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->next;
cx_invoke_destructor(list, node->payload);
cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
- list->size--;
- cxFree(list->allocator, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
} else {
- struct cx_iterator_s *iter = it;
iter->index++;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->next;
}
static void cx_ll_iter_prev(void *it) {
- struct cx_iterator_base_s *itbase = it;
- if (itbase->remove) {
- itbase->remove = false;
- struct cx_mut_iterator_s *iter = it;
- struct cx_list_s *list = iter->src_handle;
- cx_linked_list *ll = iter->src_handle;
+ struct cx_iterator_s *iter = it;
+ if (iter->base.remove) {
+ iter->base.remove = false;
+ struct cx_list_s *list = iter->src_handle.m;
+ cx_linked_list *ll = iter->src_handle.m;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->prev;
iter->index--;
cx_invoke_destructor(list, node->payload);
cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
- list->size--;
- cxFree(list->allocator, node);
+ list->collection.size--;
+ cxFree(list->collection.allocator, node);
} else {
- struct cx_iterator_s *iter = it;
iter->index--;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->prev;
}
}
-static void *cx_ll_iter_current(void const *it) {
- struct cx_iterator_s const *iter = it;
+static void *cx_ll_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
cx_linked_list_node *node = iter->elem_handle;
return node->payload;
}
-static bool cx_ll_iter_flag_rm(void *it) {
- struct cx_iterator_base_s *iter = it;
- if (iter->mutating) {
- iter->remove = true;
- return true;
- } else {
- return false;
- }
-}
-
static CxIterator cx_ll_iterator(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index,
bool backwards
) {
CxIterator iter;
iter.index = index;
- iter.src_handle = list;
- iter.elem_handle = cx_ll_node_at((cx_linked_list const *) list, index);
+ iter.src_handle.c = list;
+ iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index);
+ iter.elem_size = list->collection.elem_size;
+ iter.elem_count = list->collection.size;
iter.base.valid = cx_ll_iter_valid;
iter.base.current = cx_ll_iter_current;
iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
- iter.base.flag_removal = cx_ll_iter_flag_rm;
iter.base.mutating = false;
iter.base.remove = false;
return iter;
}
static int cx_ll_insert_iter(
- CxMutIterator *iter,
- void const *elem,
+ CxIterator *iter,
+ const void *elem,
int prepend
) {
- struct cx_list_s *list = iter->src_handle;
+ struct cx_list_s *list = iter->src_handle.m;
cx_linked_list_node *node = iter->elem_handle;
if (node != NULL) {
assert(prepend >= 0 && prepend <= 1);
cx_linked_list_node *choice[2] = {node, node->prev};
int result = cx_ll_insert_at(list, choice[prepend], elem);
- iter->index += prepend * (0 == result);
+ if (result == 0) {
+ iter->elem_count++;
+ if (prepend) {
+ iter->index++;
+ }
+ }
return result;
} else {
- int result = cx_ll_insert_element(list, list->size, elem);
- iter->index = list->size;
- return result;
+ if (cx_ll_insert_element(list, list->collection.size, elem) == NULL) {
+ return 1;
+ }
+ iter->elem_count++;
+ iter->index = list->collection.size;
+ return 0;
}
}
while (node) {
cx_invoke_destructor(list, node->payload);
void *next = node->next;
- cxFree(list->allocator, node);
+ cxFree(list->collection.allocator, node);
node = next;
}
- cxFree(list->allocator, list);
+ cxFree(list->collection.allocator, list);
}
static cx_list_class cx_linked_list_class = {
cx_ll_destructor,
cx_ll_insert_element,
cx_ll_insert_array,
+ cx_ll_insert_sorted,
cx_ll_insert_iter,
cx_ll_remove,
cx_ll_clear,
cx_ll_swap,
cx_ll_at,
- cx_ll_find,
+ cx_ll_find_remove,
cx_ll_sort,
cx_ll_compare,
cx_ll_reverse,
};
CxList *cxLinkedListCreate(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cx_compare_func comparator,
- size_t item_size
+ size_t elem_size
) {
if (allocator == NULL) {
allocator = cxDefaultAllocator;
cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
if (list == NULL) return NULL;
-
- list->base.cl = &cx_linked_list_class;
- list->base.allocator = allocator;
- list->base.cmpfunc = comparator;
-
- if (item_size > 0) {
- list->base.item_size = item_size;
- } else {
- cxListStorePointers((CxList *) list);
- }
+ cx_list_init((CxList*)list, &cx_linked_list_class,
+ allocator, comparator, elem_size);
return (CxList *) list;
}
static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
static int cx_pl_cmpfunc(
- void const *l,
- void const *r
+ const void *l,
+ const void *r
) {
void *const *lptr = l;
void *const *rptr = r;
- void const *left = lptr == NULL ? NULL : *lptr;
- void const *right = rptr == NULL ? NULL : *rptr;
+ const void *left = lptr == NULL ? NULL : *lptr;
+ const void *right = rptr == NULL ? NULL : *rptr;
return cx_pl_cmpfunc_impl(left, right);
}
-static void cx_pl_hack_cmpfunc(struct cx_list_s const *list) {
+static void cx_pl_hack_cmpfunc(const struct cx_list_s *list) {
// cast away const - this is the hacky thing
- struct cx_list_s *l = (struct cx_list_s *) list;
+ struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
cx_pl_cmpfunc_impl = l->cmpfunc;
l->cmpfunc = cx_pl_cmpfunc;
}
-static void cx_pl_unhack_cmpfunc(struct cx_list_s const *list) {
+static void cx_pl_unhack_cmpfunc(const struct cx_list_s *list) {
// cast away const - this is the hacky thing
- struct cx_list_s *l = (struct cx_list_s *) list;
+ struct cx_collection_s *l = (struct cx_collection_s*) &list->collection;
l->cmpfunc = cx_pl_cmpfunc_impl;
}
static void cx_pl_destructor(struct cx_list_s *list) {
- list->climpl->destructor(list);
+ list->climpl->deallocate(list);
}
-static int cx_pl_insert_element(
+static void *cx_pl_insert_element(
struct cx_list_s *list,
size_t index,
- void const *element
+ const void *element
) {
return list->climpl->insert_element(list, index, &element);
}
static size_t cx_pl_insert_array(
struct cx_list_s *list,
size_t index,
- void const *array,
+ const void *array,
size_t n
) {
return list->climpl->insert_array(list, index, array, n);
}
+static size_t cx_pl_insert_sorted(
+ struct cx_list_s *list,
+ const void *array,
+ size_t n
+) {
+ cx_pl_hack_cmpfunc(list);
+ size_t result = list->climpl->insert_sorted(list, array, n);
+ cx_pl_unhack_cmpfunc(list);
+ return result;
+}
+
static int cx_pl_insert_iter(
- struct cx_mut_iterator_s *iter,
- void const *elem,
+ struct cx_iterator_s *iter,
+ const void *elem,
int prepend
) {
- struct cx_list_s *list = iter->src_handle;
+ struct cx_list_s *list = iter->src_handle.m;
return list->climpl->insert_iter(iter, &elem, prepend);
}
-static int cx_pl_remove(
+static size_t cx_pl_remove(
struct cx_list_s *list,
- size_t index
+ size_t index,
+ size_t num,
+ void *targetbuf
) {
- return list->climpl->remove(list, index);
+ return list->climpl->remove(list, index, num, targetbuf);
}
static void cx_pl_clear(struct cx_list_s *list) {
}
static void *cx_pl_at(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index
) {
void **ptr = list->climpl->at(list, index);
return ptr == NULL ? NULL : *ptr;
}
-static ssize_t cx_pl_find(
- struct cx_list_s const *list,
- void const *elem
+static size_t cx_pl_find_remove(
+ struct cx_list_s *list,
+ const void *elem,
+ bool remove
) {
cx_pl_hack_cmpfunc(list);
- ssize_t ret = list->climpl->find(list, &elem);
+ size_t ret = list->climpl->find_remove(list, &elem, remove);
cx_pl_unhack_cmpfunc(list);
return ret;
}
}
static int cx_pl_compare(
- struct cx_list_s const *list,
- struct cx_list_s const *other
+ const struct cx_list_s *list,
+ const struct cx_list_s *other
) {
cx_pl_hack_cmpfunc(list);
int ret = list->climpl->compare(list, other);
list->climpl->reverse(list);
}
-static void *cx_pl_iter_current(void const *it) {
- struct cx_iterator_s const *iter = it;
+static void *cx_pl_iter_current(const void *it) {
+ const struct cx_iterator_s *iter = it;
void **ptr = iter->base.current_impl(it);
return ptr == NULL ? NULL : *ptr;
}
static struct cx_iterator_s cx_pl_iterator(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index,
bool backwards
) {
cx_pl_destructor,
cx_pl_insert_element,
cx_pl_insert_array,
+ cx_pl_insert_sorted,
cx_pl_insert_iter,
cx_pl_remove,
cx_pl_clear,
cx_pl_swap,
cx_pl_at,
- cx_pl_find,
+ cx_pl_find_remove,
cx_pl_sort,
cx_pl_compare,
cx_pl_reverse,
cx_pl_iterator,
};
-
-void cxListStoreObjects(CxList *list) {
- list->store_pointer = false;
- if (list->climpl != NULL) {
- list->cl = list->climpl;
- list->climpl = NULL;
- }
-}
-
-void cxListStorePointers(CxList *list) {
- list->item_size = sizeof(void *);
- list->store_pointer = true;
- list->climpl = list->cl;
- list->cl = &cx_pointer_list_class;
-}
-
// </editor-fold>
// <editor-fold desc="empty list implementation">
-static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+static void cx_emptyl_noop(cx_attr_unused CxList *list) {
// this is a noop, but MUST be implemented
}
static void *cx_emptyl_at(
- __attribute__((__unused__)) struct cx_list_s const *list,
- __attribute__((__unused__)) size_t index
+ cx_attr_unused const struct cx_list_s *list,
+ cx_attr_unused size_t index
) {
return NULL;
}
-static ssize_t cx_emptyl_find(
- __attribute__((__unused__)) struct cx_list_s const *list,
- __attribute__((__unused__)) void const *elem
+static size_t cx_emptyl_find_remove(
+ cx_attr_unused struct cx_list_s *list,
+ cx_attr_unused const void *elem,
+ cx_attr_unused bool remove
) {
- return -1;
+ return 0;
}
-static int cx_emptyl_compare(
- __attribute__((__unused__)) struct cx_list_s const *list,
- struct cx_list_s const *other
-) {
- if (other->size == 0) return 0;
- return -1;
-}
-
-static bool cx_emptyl_iter_valid(__attribute__((__unused__)) void const *iter) {
+static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) {
return false;
}
static CxIterator cx_emptyl_iterator(
- struct cx_list_s const *list,
+ const struct cx_list_s *list,
size_t index,
- __attribute__((__unused__)) bool backwards
+ cx_attr_unused bool backwards
) {
CxIterator iter = {0};
- iter.src_handle = list;
+ iter.src_handle.c = list;
iter.index = index;
iter.base.valid = cx_emptyl_iter_valid;
return iter;
NULL,
NULL,
NULL,
+ NULL,
cx_emptyl_noop,
NULL,
cx_emptyl_at,
- cx_emptyl_find,
+ cx_emptyl_find_remove,
cx_emptyl_noop,
- cx_emptyl_compare,
+ NULL,
cx_emptyl_noop,
cx_emptyl_iterator,
};
CxList cx_empty_list = {
+ {
NULL,
NULL,
0,
NULL,
NULL,
false,
- &cx_empty_list_class,
- NULL
+ true,
+ },
+ &cx_empty_list_class,
+ NULL
};
CxList *const cxEmptyList = &cx_empty_list;
// </editor-fold>
-void cxListDestroy(CxList *list) {
- list->cl->destructor(list);
+#define invoke_list_func(name, list, ...) \
+ ((list)->climpl == NULL ? (list)->cl->name : (list)->climpl->name) \
+ (list, __VA_ARGS__)
+
+size_t cx_list_default_insert_array(
+ struct cx_list_s *list,
+ size_t index,
+ const void *data,
+ size_t n
+) {
+ size_t elem_size = list->collection.elem_size;
+ const char *src = data;
+ size_t i = 0;
+ for (; i < n; i++) {
+ if (NULL == invoke_list_func(
+ insert_element, list, index + i,
+ src + (i * elem_size))) return i;
+ }
+ return i;
}
-int cxListCompare(
- CxList const *list,
- CxList const *other
+size_t cx_list_default_insert_sorted(
+ struct cx_list_s *list,
+ const void *sorted_data,
+ size_t n
) {
- if (
- // if one is storing pointers but the other is not
- (list->store_pointer ^ other->store_pointer) ||
+ // corner case
+ if (n == 0) return 0;
+
+ size_t elem_size = list->collection.elem_size;
+ cx_compare_func cmp = list->collection.cmpfunc;
+ const char *src = sorted_data;
- // if one class is wrapped but the other is not
- ((list->climpl == NULL) ^ (other->climpl == NULL)) ||
+ // track indices and number of inserted items
+ size_t di = 0, si = 0, inserted = 0;
- // if the resolved compare functions are not the same
- ((list->climpl != NULL ? list->climpl->compare : list->cl->compare) !=
- (other->climpl != NULL ? other->climpl->compare : other->cl->compare))
- ) {
+ // search the list for insertion points
+ for (; di < list->collection.size; di++) {
+ const void *list_elm = invoke_list_func(at, list, di);
+
+ // compare current list element with first source element
+ // if less or equal, skip
+ if (cmp(list_elm, src) <= 0) {
+ continue;
+ }
+
+ // determine number of consecutive elements that can be inserted
+ size_t ins = 1;
+ const char *next = src;
+ while (++si < n) {
+ next += elem_size;
+ // once we become larger than the list elem, break
+ if (cmp(list_elm, next) <= 0) {
+ break;
+ }
+ // otherwise, we can insert one more
+ ins++;
+ }
+
+ // insert the elements at location si
+ if (ins == 1) {
+ if (NULL == invoke_list_func(
+ insert_element, list, di, src)) return inserted;
+ } else {
+ size_t r = invoke_list_func(insert_array, list, di, src, ins);
+ if (r < ins) return inserted + r;
+ }
+ inserted += ins;
+ di += ins;
+
+ // everything inserted?
+ if (inserted == n) return inserted;
+ src = next;
+ }
+
+ // insert remaining items
+ if (si < n) {
+ inserted += invoke_list_func(insert_array, list, di, src, n - si);
+ }
+
+ return inserted;
+}
+
+void cx_list_default_sort(struct cx_list_s *list) {
+ size_t elem_size = list->collection.elem_size;
+ size_t list_size = list->collection.size;
+ void *tmp = cxMallocDefault(elem_size * list_size);
+ if (tmp == NULL) abort();
+
+ // copy elements from source array
+ char *loc = tmp;
+ for (size_t i = 0; i < list_size; i++) {
+ void *src = invoke_list_func(at, list, i);
+ memcpy(loc, src, elem_size);
+ loc += elem_size;
+ }
+
+ // qsort
+ qsort(tmp, list_size, elem_size,
+ list->collection.cmpfunc);
+
+ // copy elements back
+ loc = tmp;
+ for (size_t i = 0; i < list_size; i++) {
+ void *dest = invoke_list_func(at, list, i);
+ memcpy(dest, loc, elem_size);
+ loc += elem_size;
+ }
+
+ cxFreeDefault(tmp);
+}
+
+int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j) {
+ if (i == j) return 0;
+ if (i >= list->collection.size) return 1;
+ if (j >= list->collection.size) return 1;
+
+ size_t elem_size = list->collection.elem_size;
+
+ void *tmp = cxMallocDefault(elem_size);
+ if (tmp == NULL) return 1;
+
+ void *ip = invoke_list_func(at, list, i);
+ void *jp = invoke_list_func(at, list, j);
+
+ memcpy(tmp, ip, elem_size);
+ memcpy(ip, jp, elem_size);
+ memcpy(jp, tmp, elem_size);
+
+ cxFreeDefault(tmp);
+
+ return 0;
+}
+
+void cx_list_init(
+ struct cx_list_s *list,
+ struct cx_list_class_s *cl,
+ const struct cx_allocator_s *allocator,
+ cx_compare_func comparator,
+ size_t elem_size
+) {
+ list->cl = cl;
+ list->collection.allocator = allocator;
+ list->collection.cmpfunc = comparator;
+ if (elem_size > 0) {
+ list->collection.elem_size = elem_size;
+ } else {
+ list->collection.elem_size = sizeof(void *);
+ if (list->collection.cmpfunc == NULL) {
+ list->collection.cmpfunc = cx_cmp_ptr;
+ }
+ list->collection.store_pointer = true;
+ list->climpl = list->cl;
+ list->cl = &cx_pointer_list_class;
+ }
+}
+
+int cxListCompare(
+ const CxList *list,
+ const CxList *other
+) {
+ bool cannot_optimize = false;
+
+ // if one is storing pointers but the other is not
+ cannot_optimize |= list->collection.store_pointer ^ other->collection.store_pointer;
+
+ // if one class is wrapped but the other is not
+ cannot_optimize |= (list->climpl == NULL) ^ (other->climpl == NULL);
+
+ // if the compare functions do not match or both are NULL
+ if (!cannot_optimize) {
+ cx_compare_func list_cmp = (cx_compare_func) (list->climpl != NULL ?
+ list->climpl->compare : list->cl->compare);
+ cx_compare_func other_cmp = (cx_compare_func) (other->climpl != NULL ?
+ other->climpl->compare : other->cl->compare);
+ cannot_optimize |= list_cmp != other_cmp;
+ cannot_optimize |= list_cmp == NULL;
+ }
+
+ if (cannot_optimize) {
// lists are definitely different - cannot use internal compare function
- if (list->size == other->size) {
- CxIterator left = cxListIterator(list);
- CxIterator right = cxListIterator(other);
- for (size_t i = 0; i < list->size; i++) {
+ if (list->collection.size == other->collection.size) {
+ CxIterator left = list->cl->iterator(list, 0, false);
+ CxIterator right = other->cl->iterator(other, 0, false);
+ for (size_t i = 0; i < list->collection.size; i++) {
void *leftValue = cxIteratorCurrent(left);
void *rightValue = cxIteratorCurrent(right);
- int d = list->cmpfunc(leftValue, rightValue);
+ int d = list->collection.cmpfunc(leftValue, rightValue);
if (d != 0) {
return d;
}
}
return 0;
} else {
- return list->size < other->size ? -1 : 1;
+ return list->collection.size < other->collection.size ? -1 : 1;
}
} else {
// lists are compatible
}
}
-CxMutIterator cxListMutIteratorAt(
+CxIterator cxListMutIteratorAt(
CxList *list,
size_t index
) {
CxIterator it = list->cl->iterator(list, index, false);
it.base.mutating = true;
-
- // we know the iterators share the same memory layout
- CxMutIterator iter;
- memcpy(&iter, &it, sizeof(CxMutIterator));
- return iter;
+ return it;
}
-CxMutIterator cxListMutBackwardsIteratorAt(
+CxIterator cxListMutBackwardsIteratorAt(
CxList *list,
size_t index
) {
CxIterator it = list->cl->iterator(list, index, true);
it.base.mutating = true;
+ return it;
+}
- // we know the iterators share the same memory layout
- CxMutIterator iter;
- memcpy(&iter, &it, sizeof(CxMutIterator));
- return iter;
+void cxListFree(CxList *list) {
+ if (list == NULL) return;
+ list->cl->deallocate(list);
+}
+
+int cxListSet(
+ CxList *list,
+ size_t index,
+ const void *elem
+) {
+ if (index >= list->collection.size) {
+ return 1;
+ }
+
+ if (list->collection.store_pointer) {
+ // For pointer collections, always use climpl
+ void **target = list->climpl->at(list, index);
+ *target = (void *)elem;
+ } else {
+ void *target = list->cl->at(list, index);
+ memcpy(target, elem, list->collection.elem_size);
+ }
+
+ return 0;
}
\r
// <editor-fold desc="empty map implementation">\r
\r
-static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {\r
+static void cx_empty_map_noop(cx_attr_unused CxMap *map) {\r
// this is a noop, but MUST be implemented\r
}\r
\r
static void *cx_empty_map_get(\r
- __attribute__((__unused__)) CxMap const *map,\r
- __attribute__((__unused__)) CxHashKey key\r
+ cx_attr_unused const CxMap *map,\r
+ cx_attr_unused CxHashKey key\r
) {\r
return NULL;\r
}\r
\r
-static bool cx_empty_map_iter_valid(__attribute__((__unused__)) void const *iter) {\r
+static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) {\r
return false;\r
}\r
\r
-static CxIterator cx_empty_map_iterator(\r
- struct cx_map_s const *map,\r
- __attribute__((__unused__)) enum cx_map_iterator_type type\r
+static CxMapIterator cx_empty_map_iterator(\r
+ const struct cx_map_s *map,\r
+ cx_attr_unused enum cx_map_iterator_type type\r
) {\r
- CxIterator iter = {0};\r
- iter.src_handle = map;\r
+ CxMapIterator iter = {0};\r
+ iter.map.c = map;\r
iter.base.valid = cx_empty_map_iter_valid;\r
return iter;\r
}\r
};\r
\r
CxMap cx_empty_map = {\r
+ {\r
NULL,\r
NULL,\r
0,\r
NULL,\r
NULL,\r
false,\r
- &cx_empty_map_class\r
+ true\r
+ },\r
+ &cx_empty_map_class\r
};\r
\r
CxMap *const cxEmptyMap = &cx_empty_map;\r
\r
// </editor-fold>\r
\r
-CxMutIterator cxMapMutIteratorValues(CxMap *map) {\r
- CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);\r
+CxMapIterator cxMapMutIteratorValues(CxMap *map) {\r
+ CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);\r
it.base.mutating = true;\r
-\r
- // we know the iterators share the same memory layout\r
- CxMutIterator iter;\r
- memcpy(&iter, &it, sizeof(CxMutIterator));\r
- return iter;\r
+ return it;\r
}\r
\r
-CxMutIterator cxMapMutIteratorKeys(CxMap *map) {\r
- CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);\r
+CxMapIterator cxMapMutIteratorKeys(CxMap *map) {\r
+ CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);\r
it.base.mutating = true;\r
-\r
- // we know the iterators share the same memory layout\r
- CxMutIterator iter;\r
- memcpy(&iter, &it, sizeof(CxMutIterator));\r
- return iter;\r
+ return it;\r
}\r
\r
-CxMutIterator cxMapMutIterator(CxMap *map) {\r
- CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);\r
+CxMapIterator cxMapMutIterator(CxMap *map) {\r
+ CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);\r
it.base.mutating = true;\r
+ return it;\r
+}\r
\r
- // we know the iterators share the same memory layout\r
- CxMutIterator iter;\r
- memcpy(&iter, &it, sizeof(CxMutIterator));\r
- return iter;\r
+void cxMapFree(CxMap *map) {\r
+ if (map == NULL) return;\r
+ map->cl->deallocate(map);\r
}\r
*/
#include "cx/mempool.h"
-#include "cx/utils.h"
+
#include <string.h>
+#include <errno.h>
-struct cx_mempool_memory_s {
- /** The destructor. */
- cx_destructor_func destructor;
- /** The actual memory. */
- char c[];
-};
+static int cx_mempool_ensure_capacity(
+ struct cx_mempool_s *pool,
+ size_t needed_capacity
+) {
+ if (needed_capacity <= pool->capacity) return 0;
+ size_t newcap = pool->capacity >= 1000 ?
+ pool->capacity + 1000 : pool->capacity * 2;
+ size_t newmsize;
+ // LCOV_EXCL_START
+ if (pool->capacity > newcap
+ || cx_szmul(newcap, sizeof(void*), &newmsize)) {
+ errno = EOVERFLOW;
+ return 1;
+ } // LCOV_EXCL_STOP
+ void **newdata = cxRealloc(pool->base_allocator, pool->data, newmsize);
+ if (newdata == NULL) return 1;
+ pool->data = newdata;
+ pool->capacity = newcap;
+ return 0;
+}
+
+static int cx_mempool_ensure_registered_capacity(
+ struct cx_mempool_s *pool,
+ size_t needed_capacity
+) {
+ if (needed_capacity <= pool->registered_capacity) return 0;
+ // we do not expect so many registrations
+ size_t newcap = pool->registered_capacity + 8;
+ size_t newmsize;
+ // LCOV_EXCL_START
+ if (pool->registered_capacity > newcap || cx_szmul(newcap,
+ sizeof(struct cx_mempool_foreign_memory_s), &newmsize)) {
+ errno = EOVERFLOW;
+ return 1;
+ } // LCOV_EXCL_STOP
+ void *newdata = cxRealloc(pool->base_allocator, pool->registered, newmsize);
+ if (newdata == NULL) return 1;
+ pool->registered = newdata;
+ pool->registered_capacity = newcap;
+ return 0;
+}
-static void *cx_mempool_malloc(
+static void *cx_mempool_malloc_simple(
void *p,
size_t n
) {
struct cx_mempool_s *pool = p;
- if (pool->size >= pool->capacity) {
- size_t newcap = pool->capacity - (pool->capacity % 16) + 16;
- struct cx_mempool_memory_s **newdata = realloc(pool->data, newcap*sizeof(struct cx_mempool_memory_s*));
- if (newdata == NULL) {
- return NULL;
- }
- pool->data = newdata;
- pool->capacity = newcap;
- }
-
- struct cx_mempool_memory_s *mem = malloc(sizeof(cx_destructor_func) + n);
- if (mem == NULL) {
- return NULL;
+ if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+ return NULL; // LCOV_EXCL_LINE
}
- mem->destructor = pool->auto_destr;
+ struct cx_mempool_memory_s *mem =
+ cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory_s) + n);
+ if (mem == NULL) return NULL;
+ mem->destructor = NULL;
pool->data[pool->size] = mem;
pool->size++;
return mem->c;
}
-static void *cx_mempool_calloc(
+static void *cx_mempool_calloc_simple(
void *p,
size_t nelem,
size_t elsize
) {
size_t msz;
if (cx_szmul(nelem, elsize, &msz)) {
+ errno = EOVERFLOW;
return NULL;
}
- void *ptr = cx_mempool_malloc(p, msz);
- if (ptr == NULL) {
- return NULL;
- }
+ void *ptr = cx_mempool_malloc_simple(p, msz);
+ if (ptr == NULL) return NULL;
memset(ptr, 0, nelem * elsize);
return ptr;
}
-static void *cx_mempool_realloc(
+static void cx_mempool_free_simple(
void *p,
- void *ptr,
- size_t n
+ void *ptr
) {
+ if (!ptr) return;
struct cx_mempool_s *pool = p;
- struct cx_mempool_memory_s *mem, *newm;
- mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func));
- newm = realloc(mem, n + sizeof(cx_destructor_func));
+ struct cx_mempool_memory_s *mem =
+ (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s));
- if (newm == NULL) {
+ for (size_t i = 0; i < pool->size; i++) {
+ if (mem == pool->data[i]) {
+ if (mem->destructor) {
+ mem->destructor(mem->c);
+ }
+ if (pool->destr) {
+ pool->destr(mem->c);
+ }
+ if (pool->destr2) {
+ pool->destr2(pool->destr2_data, mem->c);
+ }
+ cxFree(pool->base_allocator, mem);
+ size_t last_index = pool->size - 1;
+ if (i != last_index) {
+ pool->data[i] = pool->data[last_index];
+ pool->data[last_index] = NULL;
+ }
+ pool->size--;
+ return;
+ }
+ }
+ abort(); // LCOV_EXCL_LINE
+}
+
+static void *cx_mempool_realloc_simple(
+ void *p,
+ void *ptr,
+ size_t n
+) {
+ if (ptr == NULL) {
+ return cx_mempool_malloc_simple(p, n);
+ }
+ if (n == 0) {
+ cx_mempool_free_simple(p, ptr);
return NULL;
}
+ struct cx_mempool_s *pool = p;
+
+ const unsigned overhead = sizeof(struct cx_mempool_memory_s);
+ struct cx_mempool_memory_s *mem =
+ (void *) (((char *) ptr) - overhead);
+ struct cx_mempool_memory_s *newm =
+ cxRealloc(pool->base_allocator, mem, n + overhead);
+
+ if (newm == NULL) return NULL;
if (mem != newm) {
- cx_for_n(i, pool->size) {
+ for (size_t i = 0; i < pool->size; i++) {
if (pool->data[i] == mem) {
pool->data[i] = newm;
- return ((char*)newm) + sizeof(cx_destructor_func);
+ return ((char*)newm) + overhead;
}
}
- abort();
+ abort(); // LCOV_EXCL_LINE
} else {
- return ptr;
+ // unfortunately glibc() realloc seems to always move
+ return ptr; // LCOV_EXCL_LINE
+ }
+}
+
+static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) {
+ const bool has_destr = pool->destr;
+ const bool has_destr2 = pool->destr2;
+ for (size_t i = 0; i < pool->size; i++) {
+ struct cx_mempool_memory_s *mem = pool->data[i];
+ if (mem->destructor) {
+ mem->destructor(mem->c);
+ }
+ if (has_destr) {
+ pool->destr(mem->c);
+ }
+ if (has_destr2) {
+ pool->destr2(pool->destr2_data, mem->c);
+ }
+ cxFree(pool->base_allocator, mem);
}
}
-static void cx_mempool_free(
+static cx_allocator_class cx_mempool_simple_allocator_class = {
+ cx_mempool_malloc_simple,
+ cx_mempool_realloc_simple,
+ cx_mempool_calloc_simple,
+ cx_mempool_free_simple
+};
+
+static void *cx_mempool_malloc_advanced(
+ void *p,
+ size_t n
+) {
+ struct cx_mempool_s *pool = p;
+
+ if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+ return NULL; // LCOV_EXCL_LINE
+ }
+
+ struct cx_mempool_memory2_s *mem =
+ cxMalloc(pool->base_allocator, sizeof(struct cx_mempool_memory2_s) + n);
+ if (mem == NULL) return NULL;
+ mem->destructor = NULL;
+ mem->data = NULL;
+ pool->data[pool->size] = mem;
+ pool->size++;
+
+ return mem->c;
+}
+
+static void *cx_mempool_calloc_advanced(
+ void *p,
+ size_t nelem,
+ size_t elsize
+) {
+ size_t msz;
+ if (cx_szmul(nelem, elsize, &msz)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ void *ptr = cx_mempool_malloc_advanced(p, msz);
+ if (ptr == NULL) return NULL;
+ memset(ptr, 0, nelem * elsize);
+ return ptr;
+}
+
+static void cx_mempool_free_advanced(
void *p,
void *ptr
) {
+ if (!ptr) return;
struct cx_mempool_s *pool = p;
- struct cx_mempool_memory_s *mem = (struct cx_mempool_memory_s *)
- ((char *) ptr - sizeof(cx_destructor_func));
+ struct cx_mempool_memory2_s *mem =
+ (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s));
- cx_for_n(i, pool->size) {
+ for (size_t i = 0; i < pool->size; i++) {
if (mem == pool->data[i]) {
if (mem->destructor) {
- mem->destructor(mem->c);
+ mem->destructor(mem->data, mem->c);
+ }
+ if (pool->destr) {
+ pool->destr(mem->c);
}
- free(mem);
+ if (pool->destr2) {
+ pool->destr2(pool->destr2_data, mem->c);
+ }
+ cxFree(pool->base_allocator, mem);
size_t last_index = pool->size - 1;
if (i != last_index) {
pool->data[i] = pool->data[last_index];
return;
}
}
- abort();
+ abort(); // LCOV_EXCL_LINE
+}
+
+static void *cx_mempool_realloc_advanced(
+ void *p,
+ void *ptr,
+ size_t n
+) {
+ if (ptr == NULL) {
+ return cx_mempool_malloc_advanced(p, n);
+ }
+ if (n == 0) {
+ cx_mempool_free_advanced(p, ptr);
+ return NULL;
+ }
+ struct cx_mempool_s *pool = p;
+
+ const unsigned overhead = sizeof(struct cx_mempool_memory2_s);
+ struct cx_mempool_memory2_s *mem =
+ (void *) (((char *) ptr) - overhead);
+ struct cx_mempool_memory2_s *newm =
+ cxRealloc(pool->base_allocator, mem, n + overhead);
+
+ if (newm == NULL) return NULL;
+ if (mem != newm) {
+ for (size_t i = 0; i < pool->size; i++) {
+ if (pool->data[i] == mem) {
+ pool->data[i] = newm;
+ return ((char*)newm) + overhead;
+ }
+ }
+ abort(); // LCOV_EXCL_LINE
+ } else {
+ // unfortunately glibc() realloc seems to always move
+ return ptr; // LCOV_EXCL_LINE
+ }
}
-void cxMempoolDestroy(CxMempool *pool) {
- struct cx_mempool_memory_s *mem;
- cx_for_n(i, pool->size) {
- mem = pool->data[i];
+static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) {
+ const bool has_destr = pool->destr;
+ const bool has_destr2 = pool->destr2;
+ for (size_t i = 0; i < pool->size; i++) {
+ struct cx_mempool_memory2_s *mem = pool->data[i];
if (mem->destructor) {
- mem->destructor(mem->c);
+ mem->destructor(mem->data, mem->c);
+ }
+ if (has_destr) {
+ pool->destr(mem->c);
+ }
+ if (has_destr2) {
+ pool->destr2(pool->destr2_data, mem->c);
}
- free(mem);
+ cxFree(pool->base_allocator, mem);
}
- free(pool->data);
- free((void*) pool->allocator);
- free(pool);
+}
+
+static cx_allocator_class cx_mempool_advanced_allocator_class = {
+ cx_mempool_malloc_advanced,
+ cx_mempool_realloc_advanced,
+ cx_mempool_calloc_advanced,
+ cx_mempool_free_advanced
+};
+
+
+static void *cx_mempool_malloc_pure(
+ void *p,
+ size_t n
+) {
+ struct cx_mempool_s *pool = p;
+
+ if (cx_mempool_ensure_capacity(pool, pool->size + 1)) {
+ return NULL; // LCOV_EXCL_LINE
+ }
+
+ void *mem = cxMalloc(pool->base_allocator, n);
+ if (mem == NULL) return NULL;
+ pool->data[pool->size] = mem;
+ pool->size++;
+
+ return mem;
+}
+
+static void *cx_mempool_calloc_pure(
+ void *p,
+ size_t nelem,
+ size_t elsize
+) {
+ size_t msz;
+ if (cx_szmul(nelem, elsize, &msz)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ void *ptr = cx_mempool_malloc_pure(p, msz);
+ if (ptr == NULL) return NULL;
+ memset(ptr, 0, nelem * elsize);
+ return ptr;
+}
+
+static void cx_mempool_free_pure(
+ void *p,
+ void *ptr
+) {
+ if (!ptr) return;
+ struct cx_mempool_s *pool = p;
+
+ for (size_t i = 0; i < pool->size; i++) {
+ if (ptr == pool->data[i]) {
+ if (pool->destr) {
+ pool->destr(ptr);
+ }
+ if (pool->destr2) {
+ pool->destr2(pool->destr2_data, ptr);
+ }
+ cxFree(pool->base_allocator, ptr);
+ size_t last_index = pool->size - 1;
+ if (i != last_index) {
+ pool->data[i] = pool->data[last_index];
+ pool->data[last_index] = NULL;
+ }
+ pool->size--;
+ return;
+ }
+ }
+ abort(); // LCOV_EXCL_LINE
+}
+
+static void *cx_mempool_realloc_pure(
+ void *p,
+ void *ptr,
+ size_t n
+) {
+ if (ptr == NULL) {
+ return cx_mempool_malloc_pure(p, n);
+ }
+ if (n == 0) {
+ cx_mempool_free_pure(p, ptr);
+ return NULL;
+ }
+ struct cx_mempool_s *pool = p;
+ void *newm = cxRealloc(pool->base_allocator, ptr, n);
+ if (newm == NULL) return NULL;
+ if (ptr != newm) {
+ for (size_t i = 0; i < pool->size; i++) {
+ if (pool->data[i] == ptr) {
+ pool->data[i] = newm;
+ return newm;
+ }
+ }
+ abort(); // LCOV_EXCL_LINE
+ } else {
+ // unfortunately glibc() realloc seems to always move
+ return ptr; // LCOV_EXCL_LINE
+ }
+}
+
+static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) {
+ const bool has_destr = pool->destr;
+ const bool has_destr2 = pool->destr2;
+ for (size_t i = 0; i < pool->size; i++) {
+ void *mem = pool->data[i];
+ if (has_destr) {
+ pool->destr(mem);
+ }
+ if (has_destr2) {
+ pool->destr2(pool->destr2_data, mem);
+ }
+ cxFree(pool->base_allocator, mem);
+ }
+}
+
+static cx_allocator_class cx_mempool_pure_allocator_class = {
+ cx_mempool_malloc_pure,
+ cx_mempool_realloc_pure,
+ cx_mempool_calloc_pure,
+ cx_mempool_free_pure
+};
+
+static void cx_mempool_free_foreign(const struct cx_mempool_s *pool) {
+ for (size_t i = 0; i < pool->registered_size; i++) {
+ struct cx_mempool_foreign_memory_s info = pool->registered[i];
+ if (info.destr2_data == NULL) {
+ if (info.destr) {
+ info.destr(info.mem);
+ }
+ } else {
+ info.destr2(info.destr2_data, info.mem);
+ }
+ }
+}
+
+void cxMempoolFree(CxMempool *pool) {
+ if (pool == NULL) return;
+ if (pool->allocator->cl == &cx_mempool_simple_allocator_class) {
+ cx_mempool_free_all_simple(pool);
+ } else if (pool->allocator->cl == &cx_mempool_advanced_allocator_class) {
+ cx_mempool_free_all_advanced(pool);
+ } else {
+ cx_mempool_free_all_pure(pool);
+ }
+ cx_mempool_free_foreign(pool);
+ cxFree(pool->base_allocator, pool->data);
+ cxFree(pool->base_allocator, pool->registered);
+ cxFree(pool->base_allocator, (void*) pool->allocator);
+ cxFree(pool->base_allocator, pool);
}
void cxMempoolSetDestructor(
*(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
}
-struct cx_mempool_foreign_mem_s {
- cx_destructor_func destr;
- void* mem;
-};
+void cxMempoolSetDestructor2(
+ void *ptr,
+ cx_destructor_func2 func,
+ void *data
+) {
+ struct cx_mempool_memory2_s *info =
+ (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s));
+ info->destructor = func;
+ info->data = data;
+}
+
+void cxMempoolRemoveDestructor(void *ptr) {
+ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL;
+}
-static void cx_mempool_destr_foreign_mem(void* ptr) {
- struct cx_mempool_foreign_mem_s *fm = ptr;
- fm->destr(fm->mem);
+void cxMempoolRemoveDestructor2(void *ptr) {
+ struct cx_mempool_memory2_s *info =
+ (void*)((char *) ptr - sizeof(struct cx_mempool_memory2_s));
+ info->destructor = NULL;
+ info->data = NULL;
}
int cxMempoolRegister(
void *memory,
cx_destructor_func destr
) {
- struct cx_mempool_foreign_mem_s *fm = cx_mempool_malloc(
- pool,
- sizeof(struct cx_mempool_foreign_mem_s)
- );
- if (fm == NULL) return 1;
+ if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) {
+ return 1; // LCOV_EXCL_LINE
+ }
- fm->mem = memory;
- fm->destr = destr;
- *(cx_destructor_func *) ((char *) fm - sizeof(cx_destructor_func)) = cx_mempool_destr_foreign_mem;
+ pool->registered[pool->registered_size++] =
+ (struct cx_mempool_foreign_memory_s) {
+ .mem = memory,
+ .destr = destr,
+ .destr2_data = NULL
+ };
return 0;
}
-static cx_allocator_class cx_mempool_allocator_class = {
- cx_mempool_malloc,
- cx_mempool_realloc,
- cx_mempool_calloc,
- cx_mempool_free
-};
+int cxMempoolRegister2(
+ CxMempool *pool,
+ void *memory,
+ cx_destructor_func2 destr,
+ void *data
+) {
+ if (cx_mempool_ensure_registered_capacity(pool, pool->registered_size + 1)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+
+ pool->registered[pool->registered_size++] =
+ (struct cx_mempool_foreign_memory_s) {
+ .mem = memory,
+ .destr2 = destr,
+ .destr2_data = data
+ };
+
+ return 0;
+}
CxMempool *cxMempoolCreate(
size_t capacity,
- cx_destructor_func destr
+ enum cx_mempool_type type
) {
+ if (capacity == 0) capacity = 16;
size_t poolsize;
- if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) {
+ if (cx_szmul(capacity, sizeof(void*), &poolsize)) {
+ // LCOV_EXCL_START
+ errno = EOVERFLOW;
return NULL;
- }
+ } // LCOV_EXCL_STOP
- struct cx_mempool_s *pool =
- malloc(sizeof(struct cx_mempool_s));
- if (pool == NULL) {
+ CxAllocator *provided_allocator = cxMallocDefault(sizeof(CxAllocator));
+ if (provided_allocator == NULL) { // LCOV_EXCL_START
return NULL;
- }
+ } // LCOV_EXCL_STOP
- CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
- if (provided_allocator == NULL) {
- free(pool);
+ CxMempool *pool = cxCallocDefault(1, sizeof(CxMempool));
+ if (pool == NULL) { // LCOV_EXCL_START
+ cxFreeDefault(provided_allocator);
return NULL;
- }
- provided_allocator->cl = &cx_mempool_allocator_class;
- provided_allocator->data = pool;
+ } // LCOV_EXCL_STOP
+ provided_allocator->data = pool;
+ *((const CxAllocator**)&pool->base_allocator) = cxDefaultAllocator;
pool->allocator = provided_allocator;
+ if (type == CX_MEMPOOL_TYPE_SIMPLE) {
+ provided_allocator->cl = &cx_mempool_simple_allocator_class;
+ } else if (type == CX_MEMPOOL_TYPE_ADVANCED) {
+ provided_allocator->cl = &cx_mempool_advanced_allocator_class;
+ } else {
+ provided_allocator->cl = &cx_mempool_pure_allocator_class;
+ }
- pool->data = malloc(poolsize);
- if (pool->data == NULL) {
- free(provided_allocator);
- free(pool);
+ pool->data = cxMallocDefault(poolsize);
+ if (pool->data == NULL) { // LCOV_EXCL_START
+ cxFreeDefault(provided_allocator);
+ cxFreeDefault(pool);
return NULL;
- }
+ } // LCOV_EXCL_STOP
pool->size = 0;
pool->capacity = capacity;
- pool->auto_destr = destr;
- return (CxMempool *) pool;
+ return pool;
+}
+
+void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc) {
+ pool->destr = fnc;
+}
+
+void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data) {
+ pool->destr2 = fnc;
+ pool->destr2_data = data;
+}
+
+static void cx_mempool_free_transferred_allocator(void *base_al, void *al) {
+ cxFree(base_al, al);
+}
+
+int cxMempoolTransfer(
+ CxMempool *source,
+ CxMempool *dest
+) {
+ // safety checks
+ if (source == dest) return 1;
+ if (source->allocator->cl != dest->allocator->cl) return 1;
+ if (source->base_allocator->cl != dest->base_allocator->cl) return 1;
+
+ // ensure enough capacity in the destination pool
+ if (cx_mempool_ensure_capacity(dest, dest->size + source->size)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+ if (cx_mempool_ensure_registered_capacity(dest,
+ dest->registered_size + source->registered_size)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+
+ // allocate a replacement allocator for the source pool
+ CxAllocator *new_source_allocator =
+ cxMalloc(source->base_allocator, sizeof(CxAllocator));
+ if (new_source_allocator == NULL) { // LCOV_EXCL_START
+ return 1;
+ } // LCOV_EXCL_STOP
+ new_source_allocator->cl = source->allocator->cl;
+ new_source_allocator->data = source;
+
+ // transfer all the data
+ memcpy(&dest->data[dest->size], source->data, sizeof(void*)*source->size);
+ dest->size += source->size;
+
+ // transfer all registered memory
+ memcpy(&dest->registered[dest->registered_size], source->registered,
+ sizeof(struct cx_mempool_foreign_memory_s) * source->size);
+ dest->registered_size += source->registered_size;
+
+ // register the old allocator with the new pool
+ // we have to remove const-ness for this, but that's okay here
+ // also register the base allocator, s.t. the pool knows how to free it
+ CxAllocator *transferred_allocator = (CxAllocator*) source->allocator;
+ transferred_allocator->data = dest;
+ cxMempoolRegister2(dest, transferred_allocator,
+ cx_mempool_free_transferred_allocator, (void*)source->base_allocator);
+
+ // prepare the source pool for re-use
+ source->allocator = new_source_allocator;
+ memset(source->data, 0, source->size * sizeof(void*));
+ memset(source->registered, 0,
+ source->registered_size * sizeof(struct cx_mempool_foreign_memory_s));
+ source->size = 0;
+ source->registered_size = 0;
+
+ return 0;
+}
+
+int cxMempoolTransferObject(
+ CxMempool *source,
+ CxMempool *dest,
+ const void *obj
+) {
+ // safety checks
+ if (source == dest) return 1;
+ if (source->allocator->cl != dest->allocator->cl) return 1;
+ if (source->base_allocator->cl != dest->base_allocator->cl) return 1;
+
+ // search for the object
+ for (size_t i = 0; i < source->size; i++) {
+ struct cx_mempool_memory_s *mem = source->data[i];
+ if (mem->c == obj) {
+ // first, make sure that the dest pool can take the object
+ if (cx_mempool_ensure_capacity(dest, dest->size + 1)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+ // remove from the source pool
+ size_t last_index = source->size - 1;
+ if (i != last_index) {
+ source->data[i] = source->data[last_index];
+ source->data[last_index] = NULL;
+ }
+ source->size--;
+ // add to the target pool
+ dest->data[dest->size++] = mem;
+ return 0;
+ }
+ }
+ // search in the registered objects
+ for (size_t i = 0; i < source->registered_size; i++) {
+ struct cx_mempool_foreign_memory_s *mem = &source->registered[i];
+ if (mem->mem == obj) {
+ // first, make sure that the dest pool can take the object
+ if (cx_mempool_ensure_registered_capacity(dest,
+ dest->registered_size + 1)) {
+ return 1; // LCOV_EXCL_LINE
+ }
+ dest->registered[dest->registered_size++] = *mem;
+ // remove from the source pool
+ size_t last_index = source->registered_size - 1;
+ if (i != last_index) {
+ source->registered[i] = source->registered[last_index];
+ memset(&source->registered[last_index], 0,
+ sizeof(struct cx_mempool_foreign_memory_s));
+ }
+ source->registered_size--;
+ return 0;
+ }
+ }
+ // not found
+ return 1;
}
#ifndef CX_PRINTF_SBO_SIZE
#define CX_PRINTF_SBO_SIZE 512
#endif
+const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE;
int cx_fprintf(
void *stream,
cx_write_func wfc,
- char const *fmt,
+ const char *fmt,
...
) {
int ret;
int cx_vfprintf(
void *stream,
cx_write_func wfc,
- char const *fmt,
+ const char *fmt,
va_list ap
) {
char buf[CX_PRINTF_SBO_SIZE];
va_copy(ap2, ap);
int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
if (ret < 0) {
+ va_end(ap2);
return ret;
} else if (ret < CX_PRINTF_SBO_SIZE) {
+ va_end(ap2);
return (int) wfc(buf, 1, ret, stream);
} else {
int len = ret + 1;
- char *newbuf = malloc(len);
- if (!newbuf) {
+ char *newbuf = cxMallocDefault(len);
+ if (!newbuf) { // LCOV_EXCL_START
+ va_end(ap2);
return -1;
- }
+ } // LCOV_EXCL_STOP
ret = vsnprintf(newbuf, len, fmt, ap2);
+ va_end(ap2);
if (ret > 0) {
ret = (int) wfc(newbuf, 1, ret, stream);
}
- free(newbuf);
+ cxFreeDefault(newbuf);
}
return ret;
}
cxmutstr cx_asprintf_a(
- CxAllocator const *allocator,
- char const *fmt,
+ const CxAllocator *allocator,
+ const char *fmt,
...
) {
va_list ap;
- cxmutstr ret;
va_start(ap, fmt);
- ret = cx_vasprintf_a(allocator, fmt, ap);
+ cxmutstr ret = cx_vasprintf_a(allocator, fmt, ap);
va_end(ap);
return ret;
}
cxmutstr cx_vasprintf_a(
- CxAllocator const *a,
- char const *fmt,
+ const CxAllocator *a,
+ const char *fmt,
va_list ap
) {
cxmutstr s;
va_list ap2;
va_copy(ap2, ap);
int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
- if (ret > 0 && ret < CX_PRINTF_SBO_SIZE) {
+ if (ret >= 0 && ret < CX_PRINTF_SBO_SIZE) {
s.ptr = cxMalloc(a, ret + 1);
if (s.ptr) {
s.length = (size_t) ret;
if (s.ptr) {
ret = vsnprintf(s.ptr, len, fmt, ap2);
if (ret < 0) {
- free(s.ptr);
+ cxFree(a, s.ptr);
s.ptr = NULL;
} else {
s.length = (size_t) ret;
}
}
}
+ va_end(ap2);
return s;
}
+int cx_sprintf_a(
+ const CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ ...
+) {
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = cx_vsprintf_a(alloc, str, len, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int cx_vsprintf_a(
+ const CxAllocator *alloc,
+ char **str,
+ size_t *len,
+ const char *fmt,
+ va_list ap
+) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(*str, *len, fmt, ap);
+ if ((unsigned) ret >= *len) {
+ unsigned newlen = ret + 1;
+ char *ptr = cxRealloc(alloc, *str, newlen);
+ if (ptr) {
+ int newret = vsnprintf(ptr, newlen, fmt, ap2);
+ if (newret < 0) {
+ cxFree(alloc, ptr);
+ } else {
+ *len = newlen;
+ *str = ptr;
+ ret = newret;
+ }
+ }
+ }
+ va_end(ap2);
+ return ret;
+}
+
+int cx_sprintf_sa(
+ const CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ ...
+) {
+ va_list ap;
+ va_start(ap, fmt);
+ int ret = cx_vsprintf_sa(alloc, buf, len, str, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+int cx_vsprintf_sa(
+ const CxAllocator *alloc,
+ char *buf,
+ size_t *len,
+ char **str,
+ const char *fmt,
+ va_list ap
+) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ int ret = vsnprintf(buf, *len, fmt, ap);
+ *str = buf;
+ if ((unsigned) ret >= *len) {
+ unsigned newlen = ret + 1;
+ char *ptr = cxMalloc(alloc, newlen);
+ if (ptr) {
+ int newret = vsnprintf(ptr, newlen, fmt, ap2);
+ if (newret < 0) {
+ cxFree(alloc, ptr);
+ } else {
+ *len = newlen;
+ *str = ptr;
+ ret = newret;
+ }
+ }
+ }
+ va_end(ap2);
+ return ret;
+}
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/properties.h"
+
+#include <assert.h>
+
+const CxPropertiesConfig cx_properties_config_default = {
+ '=',
+ '#',
+ '\0',
+ '\0',
+ '\\',
+};
+
+void cxPropertiesInit(
+ CxProperties *prop,
+ CxPropertiesConfig config
+) {
+ memset(prop, 0, sizeof(CxProperties));
+ prop->config = config;
+}
+
+void cxPropertiesDestroy(CxProperties *prop) {
+ cxBufferDestroy(&prop->input);
+ cxBufferDestroy(&prop->buffer);
+}
+
+int cxPropertiesFilln(
+ CxProperties *prop,
+ const char *buf,
+ size_t len
+) {
+ if (cxBufferEof(&prop->input)) {
+ // destroy a possible previously initialized buffer
+ cxBufferDestroy(&prop->input);
+ cxBufferInit(&prop->input, (void*) buf, len,
+ NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND);
+ prop->input.size = len;
+ } else {
+ if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1;
+ }
+ return 0;
+}
+
+void cxPropertiesUseStack(
+ CxProperties *prop,
+ char *buf,
+ size_t capacity
+) {
+ cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND);
+}
+
+CxPropertiesStatus cxPropertiesNext(
+ CxProperties *prop,
+ cxstring *key,
+ cxstring *value
+) {
+ // check if we have a text buffer
+ if (prop->input.space == NULL) {
+ return CX_PROPERTIES_NULL_INPUT;
+ }
+
+ // a pointer to the buffer we want to read from
+ CxBuffer *current_buffer = &prop->input;
+
+ // check if we have rescued data
+ if (!cxBufferEof(&prop->buffer)) {
+ // check if we can now get a complete line
+ cxstring input = cx_strn(prop->input.space + prop->input.pos,
+ prop->input.size - prop->input.pos);
+ cxstring nl = cx_strchr(input, '\n');
+ if (nl.length > 0) {
+ // we add as much data to the rescue buffer as we need
+ // to complete the line
+ size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1;
+
+ if (cxBufferAppend(input.ptr, 1,
+ len_until_nl, &prop->buffer) < len_until_nl) {
+ return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+ }
+
+ // advance the position in the input buffer
+ prop->input.pos += len_until_nl;
+
+ // we now want to read from the rescue buffer
+ current_buffer = &prop->buffer;
+ } else {
+ // still not enough data, copy input buffer to internal buffer
+ if (cxBufferAppend(input.ptr, 1,
+ input.length, &prop->buffer) < input.length) {
+ return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+ }
+ // reset the input buffer (make way for a re-fill)
+ cxBufferReset(&prop->input);
+ return CX_PROPERTIES_INCOMPLETE_DATA;
+ }
+ }
+
+ char comment1 = prop->config.comment1;
+ char comment2 = prop->config.comment2;
+ char comment3 = prop->config.comment3;
+ char delimiter = prop->config.delimiter;
+
+ // get one line and parse it
+ while (!cxBufferEof(current_buffer)) {
+ const char *buf = current_buffer->space + current_buffer->pos;
+ size_t len = current_buffer->size - current_buffer->pos;
+
+ /*
+ * First we check if we have at least one line. We also get indices of
+ * delimiter and comment chars
+ */
+ size_t delimiter_index = 0;
+ size_t comment_index = 0;
+ bool has_comment = false;
+
+ size_t i = 0;
+ char c = 0;
+ for (; i < len; i++) {
+ c = buf[i];
+ if (c == comment1 || c == comment2 || c == comment3) {
+ if (comment_index == 0) {
+ comment_index = i;
+ has_comment = true;
+ }
+ } else if (c == delimiter) {
+ if (delimiter_index == 0 && !has_comment) {
+ delimiter_index = i;
+ }
+ } else if (c == '\n') {
+ break;
+ }
+ }
+
+ if (c != '\n') {
+ // we don't have enough data for a line, use the rescue buffer
+ assert(current_buffer != &prop->buffer);
+ // make sure that the rescue buffer does not already contain something
+ assert(cxBufferEof(&prop->buffer));
+ if (prop->buffer.space == NULL) {
+ // initialize a rescue buffer, if the user did not provide one
+ cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND);
+ } else {
+ // from a previous rescue there might be already read data
+ // reset the buffer to avoid unnecessary buffer extension
+ cxBufferReset(&prop->buffer);
+ }
+ if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) {
+ return CX_PROPERTIES_BUFFER_ALLOC_FAILED;
+ }
+ // reset the input buffer (make way for a re-fill)
+ cxBufferReset(&prop->input);
+ return CX_PROPERTIES_INCOMPLETE_DATA;
+ }
+
+ cxstring line = has_comment ?
+ cx_strn(buf, comment_index) :
+ cx_strn(buf, i);
+ // check line
+ if (delimiter_index == 0) {
+ // if line is not blank ...
+ line = cx_strtrim(line);
+ // ... either no delimiter found, or key is empty
+ if (line.length > 0) {
+ if (line.ptr[0] == delimiter) {
+ return CX_PROPERTIES_INVALID_EMPTY_KEY;
+ } else {
+ return CX_PROPERTIES_INVALID_MISSING_DELIMITER;
+ }
+ } else {
+ // skip blank line
+ // if it was the rescue buffer, return to the original buffer
+ if (current_buffer == &prop->buffer) {
+ // assert that the rescue buffer really does not contain more data
+ assert(current_buffer->pos + i + 1 == current_buffer->size);
+ // reset the rescue buffer, but don't destroy it!
+ cxBufferReset(&prop->buffer);
+ // continue with the input buffer
+ current_buffer = &prop->input;
+ } else {
+ // if it was the input buffer already, just advance the position
+ current_buffer->pos += i + 1;
+ }
+ continue;
+ }
+ } else {
+ cxstring k = cx_strn(buf, delimiter_index);
+ cxstring val = cx_strn(
+ buf + delimiter_index + 1,
+ line.length - delimiter_index - 1);
+ k = cx_strtrim(k);
+ val = cx_strtrim(val);
+ if (k.length > 0) {
+ *key = k;
+ *value = val;
+ current_buffer->pos += i + 1;
+ assert(current_buffer->pos <= current_buffer->size);
+ return CX_PROPERTIES_NO_ERROR;
+ } else {
+ return CX_PROPERTIES_INVALID_EMPTY_KEY;
+ }
+ }
+ }
+
+ // when we come to this point, all data must have been read
+ assert(cxBufferEof(&prop->buffer));
+ assert(cxBufferEof(&prop->input));
+
+ return CX_PROPERTIES_NO_DATA;
+}
+
+static int cx_properties_sink_map(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSink *sink,
+ cxstring key,
+ cxstring value
+) {
+ CxMap *map = sink->sink;
+ CxAllocator *alloc = sink->data;
+ cxmutstr v = cx_strdup_a(alloc, value);
+ int r = cx_map_put_cxstr(map, key, v.ptr);
+ if (r != 0) cx_strfree_a(alloc, &v);
+ return r;
+}
+
+CxPropertiesSink cxPropertiesMapSink(CxMap *map) {
+ CxPropertiesSink sink;
+ sink.sink = map;
+ sink.data = (void*) cxDefaultAllocator;
+ sink.sink_func = cx_properties_sink_map;
+ return sink;
+}
+
+static int cx_properties_read_string(
+ CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+) {
+ if (prop->input.space == src->src) {
+ // when the input buffer already contains the string
+ // we have nothing more to provide
+ target->length = 0;
+ } else {
+ target->ptr = src->src;
+ target->length = src->data_size;
+ }
+ return 0;
+}
+
+static int cx_properties_read_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src,
+ cxstring *target
+) {
+ target->ptr = src->data_ptr;
+ target->length = fread(src->data_ptr, 1, src->data_size, src->src);
+ return ferror((FILE*)src->src);
+}
+
+static int cx_properties_read_init_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src
+) {
+ src->data_ptr = cxMallocDefault(src->data_size);
+ if (src->data_ptr == NULL) return 1;
+ return 0;
+}
+
+static void cx_properties_read_clean_file(
+ cx_attr_unused CxProperties *prop,
+ CxPropertiesSource *src
+) {
+ cxFreeDefault(src->data_ptr);
+}
+
+CxPropertiesSource cxPropertiesStringSource(cxstring str) {
+ CxPropertiesSource src;
+ src.src = (void*) str.ptr;
+ src.data_size = str.length;
+ src.data_ptr = NULL;
+ src.read_func = cx_properties_read_string;
+ src.read_init_func = NULL;
+ src.read_clean_func = NULL;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) {
+ CxPropertiesSource src;
+ src.src = (void*) str;
+ src.data_size = len;
+ src.data_ptr = NULL;
+ src.read_func = cx_properties_read_string;
+ src.read_init_func = NULL;
+ src.read_clean_func = NULL;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesCstrSource(const char *str) {
+ CxPropertiesSource src;
+ src.src = (void*) str;
+ src.data_size = strlen(str);
+ src.data_ptr = NULL;
+ src.read_func = cx_properties_read_string;
+ src.read_init_func = NULL;
+ src.read_clean_func = NULL;
+ return src;
+}
+
+CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) {
+ CxPropertiesSource src;
+ src.src = file;
+ src.data_size = chunk_size;
+ src.data_ptr = NULL;
+ src.read_func = cx_properties_read_file;
+ src.read_init_func = cx_properties_read_init_file;
+ src.read_clean_func = cx_properties_read_clean_file;
+ return src;
+}
+
+CxPropertiesStatus cxPropertiesLoad(
+ CxProperties *prop,
+ CxPropertiesSink sink,
+ CxPropertiesSource source
+) {
+ assert(source.read_func != NULL);
+ assert(sink.sink_func != NULL);
+
+ // initialize reader
+ if (source.read_init_func != NULL) {
+ if (source.read_init_func(prop, &source)) {
+ return CX_PROPERTIES_READ_INIT_FAILED;
+ }
+ }
+
+ // transfer the data from the source to the sink
+ CxPropertiesStatus status;
+ CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA;
+ bool found = false;
+ while (true) {
+ // read input
+ cxstring input;
+ if (source.read_func(prop, &source, &input)) {
+ status = CX_PROPERTIES_READ_FAILED;
+ break;
+ }
+
+ // no more data - break
+ if (input.length == 0) {
+ if (found) {
+ // something was found, check the last kv_status
+ if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) {
+ status = CX_PROPERTIES_INCOMPLETE_DATA;
+ } else {
+ status = CX_PROPERTIES_NO_ERROR;
+ }
+ } else {
+ // nothing found
+ status = CX_PROPERTIES_NO_DATA;
+ }
+ break;
+ }
+
+ // set the input buffer and read the k/v-pairs
+ cxPropertiesFill(prop, input);
+
+ do {
+ cxstring key, value;
+ kv_status = cxPropertiesNext(prop, &key, &value);
+ if (kv_status == CX_PROPERTIES_NO_ERROR) {
+ found = true;
+ if (sink.sink_func(prop, &sink, key, value)) {
+ kv_status = CX_PROPERTIES_SINK_FAILED;
+ }
+ }
+ } while (kv_status == CX_PROPERTIES_NO_ERROR);
+
+ if (kv_status > CX_PROPERTIES_OK) {
+ status = kv_status;
+ break;
+ }
+ }
+
+ if (source.read_clean_func != NULL) {
+ source.read_clean_func(prop, &source);
+ }
+
+ return status;
+}
* POSSIBILITY OF SUCH DAMAGE.
*/
-#include "cx/utils.h"
+#include "cx/streams.h"
+#include "cx/allocator.h"
#ifndef CX_STREAM_BCOPY_BUF_SIZE
#define CX_STREAM_BCOPY_BUF_SIZE 8192
lbuf = buf;
} else {
if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE;
- lbuf = malloc(bufsize);
- if (lbuf == NULL) {
- return 0;
- }
+ lbuf = cxMallocDefault(bufsize);
+ if (lbuf == NULL) return 0;
}
size_t r;
}
if (lbuf != buf) {
- free(lbuf);
+ cxFreeDefault(lbuf);
}
return ncp;
return cx_stream_bncopy(src, dest, rfnc, wfnc,
buf, CX_STREAM_COPY_BUF_SIZE, n);
}
-
-#ifndef CX_SZMUL_BUILTIN
-#include "szmul.c"
-#endif
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-
#include "cx/string.h"
-#include "cx/utils.h"
#include <string.h>
#include <stdarg.h>
-#include <ctype.h>
-
-#ifndef _WIN32
-
-#include <strings.h> // for strncasecmp()
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <float.h>
-#endif // _WIN32
+#ifdef _WIN32
+#define cx_strcasecmp_impl _strnicmp
+#else
+#include <strings.h>
+#define cx_strcasecmp_impl strncasecmp
+#endif
cxmutstr cx_mutstr(char *cstring) {
- return (cxmutstr) {cstring, strlen(cstring)};
+ return (cxmutstr) {cstring, cstring == NULL ? 0 : strlen(cstring)};
}
cxmutstr cx_mutstrn(
}
cxstring cx_str(const char *cstring) {
- return (cxstring) {cstring, strlen(cstring)};
+ return (cxstring) {cstring, cstring == NULL ? 0 : strlen(cstring)};
}
cxstring cx_strn(
return (cxstring) {cstring, length};
}
-cxstring cx_strcast(cxmutstr str) {
- return (cxstring) {str.ptr, str.length};
-}
-
void cx_strfree(cxmutstr *str) {
- free(str->ptr);
+ if (str == NULL) return;
+ cxFreeDefault(str->ptr);
str->ptr = NULL;
str->length = 0;
}
void cx_strfree_a(
- CxAllocator const *alloc,
+ const CxAllocator *alloc,
cxmutstr *str
) {
+ if (str == NULL) return;
cxFree(alloc, str->ptr);
str->ptr = NULL;
str->length = 0;
}
+int cx_strcpy_a(
+ const CxAllocator *alloc,
+ cxmutstr *dest,
+ cxstring src
+) {
+ if (cxReallocate(alloc, &dest->ptr, src.length + 1)) {
+ return 1;
+ }
+
+ memcpy(dest->ptr, src.ptr, src.length);
+ dest->length = src.length;
+ dest->ptr[dest->length] = '\0';
+
+ return 0;
+}
+
size_t cx_strlen(
size_t count,
...
va_list ap;
va_start(ap, count);
size_t size = 0;
- cx_for_n(i, count) {
+ for (size_t i = 0; i < count; i++) {
cxstring str = va_arg(ap, cxstring);
+ if (size > SIZE_MAX - str.length) errno = EOVERFLOW;
size += str.length;
}
va_end(ap);
}
cxmutstr cx_strcat_ma(
- CxAllocator const *alloc,
+ const CxAllocator *alloc,
cxmutstr str,
size_t count,
...
) {
if (count == 0) return str;
-
- cxstring *strings = calloc(count, sizeof(cxstring));
- if (!strings) abort();
-
va_list ap;
va_start(ap, count);
+ va_list ap2;
+ va_copy(ap2, ap);
- // get all args and overall length
+ // compute overall length
+ bool overflow = false;
size_t slen = str.length;
- cx_for_n(i, count) {
- cxstring s = va_arg (ap, cxstring);
- strings[i] = s;
+ for (size_t i = 0; i < count; i++) {
+ cxstring s = va_arg(ap, cxstring);
+ if (slen > SIZE_MAX - str.length) overflow = true;
slen += s.length;
}
va_end(ap);
+ // abort in case of overflow
+ if (overflow) {
+ va_end(ap2);
+ errno = EOVERFLOW;
+ return (cxmutstr) { NULL, 0 };
+ }
+
// reallocate or create new string
+ char *newstr;
if (str.ptr == NULL) {
- str.ptr = cxMalloc(alloc, slen + 1);
+ newstr = cxMalloc(alloc, slen + 1);
} else {
- str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
+ newstr = cxRealloc(alloc, str.ptr, slen + 1);
+ }
+ if (newstr == NULL) {
+ va_end(ap2);
+ return (cxmutstr) {NULL, 0};
}
- if (str.ptr == NULL) abort();
+ str.ptr = newstr;
// concatenate strings
size_t pos = str.length;
str.length = slen;
- cx_for_n(i, count) {
- cxstring s = strings[i];
+ for (size_t i = 0; i < count; i++) {
+ cxstring s = va_arg(ap2, cxstring);
memcpy(str.ptr + pos, s.ptr, s.length);
pos += s.length;
}
+ va_end(ap2);
// terminate string
str.ptr[str.length] = '\0';
- // free temporary array
- free(strings);
-
return str;
}
cxstring string,
int chr
) {
- chr = 0xFF & chr;
- // TODO: improve by comparing multiple bytes at once
- cx_for_n(i, string.length) {
- if (string.ptr[i] == chr) {
- return cx_strsubs(string, i);
- }
- }
- return (cxstring) {NULL, 0};
+ char *ret = memchr(string.ptr, 0xFF & chr, string.length);
+ if (ret == NULL) return (cxstring) {NULL, 0};
+ return (cxstring) {ret, string.length - (ret - string.ptr)};
}
cxmutstr cx_strchr_m(
}
#ifndef CX_STRSTR_SBO_SIZE
-#define CX_STRSTR_SBO_SIZE 512
+#define CX_STRSTR_SBO_SIZE 128
#endif
+const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE;
cxstring cx_strstr(
cxstring haystack,
// check needle length and use appropriate prefix table
// if the pattern exceeds static prefix table, allocate on the heap
- bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
- register size_t *ptable = useheap ? calloc(needle.length + 1,
- sizeof(size_t)) : s_prefix_table;
+ const bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+ register size_t *ptable = useheap
+ ? cxCallocDefault(needle.length + 1, sizeof(size_t))
+ : s_prefix_table;
// keep counter in registers
register size_t i, j;
}
// if prefix table was allocated on the heap, free it
- if (ptable != s_prefix_table) {
- free(ptable);
+ if (useheap) {
+ cxFreeDefault(ptable);
}
return result;
}
size_t cx_strsplit_a(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxstring string,
cxstring delim,
size_t limit,
}
size_t cx_strsplit_ma(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxmutstr string,
cxstring delim,
size_t limit,
cxstring s2
) {
if (s1.length == s2.length) {
- return memcmp(s1.ptr, s2.ptr, s1.length);
+ return strncmp(s1.ptr, s2.ptr, s1.length);
} else if (s1.length > s2.length) {
+ int r = strncmp(s1.ptr, s2.ptr, s2.length);
+ if (r != 0) return r;
return 1;
} else {
+ int r = strncmp(s1.ptr, s2.ptr, s1.length);
+ if (r != 0) return r;
return -1;
}
}
cxstring s2
) {
if (s1.length == s2.length) {
-#ifdef _WIN32
- return _strnicmp(s1.ptr, s2.ptr, s1.length);
-#else
- return strncasecmp(s1.ptr, s2.ptr, s1.length);
-#endif
+ return cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
} else if (s1.length > s2.length) {
+ int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s2.length);
+ if (r != 0) return r;
return 1;
} else {
+ int r = cx_strcasecmp_impl(s1.ptr, s2.ptr, s1.length);
+ if (r != 0) return r;
return -1;
}
}
int cx_strcmp_p(
- void const *s1,
- void const *s2
+ const void *s1,
+ const void *s2
) {
- cxstring const *left = s1;
- cxstring const *right = s2;
+ const cxstring *left = s1;
+ const cxstring *right = s2;
return cx_strcmp(*left, *right);
}
int cx_strcasecmp_p(
- void const *s1,
- void const *s2
+ const void *s1,
+ const void *s2
) {
- cxstring const *left = s1;
- cxstring const *right = s2;
+ const cxstring *left = s1;
+ const cxstring *right = s2;
return cx_strcasecmp(*left, *right);
}
-cxmutstr cx_strdup_a(
- CxAllocator const *allocator,
+cxmutstr cx_strdup_a_(
+ const CxAllocator *allocator,
cxstring string
) {
cxmutstr result = {
return result;
}
+static bool str_isspace(char c) {
+ // TODO: remove once UCX has public API for this
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f';
+}
+
cxstring cx_strtrim(cxstring string) {
cxstring result = string;
// TODO: optimize by comparing multiple bytes at once
- while (result.length > 0 && isspace(*result.ptr)) {
+ while (result.length > 0 && str_isspace(*result.ptr)) {
result.ptr++;
result.length--;
}
- while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
+ while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) {
result.length--;
}
return result;
#endif
}
-void cx_strlower(cxmutstr string) {
- cx_for_n(i, string.length) {
- string.ptr[i] = (char) tolower(string.ptr[i]);
- }
-}
-
-void cx_strupper(cxmutstr string) {
- cx_for_n(i, string.length) {
- string.ptr[i] = (char) toupper(string.ptr[i]);
- }
-}
-
-#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
-#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
-#endif
-
-struct cx_strreplace_ibuf {
- size_t *buf;
- struct cx_strreplace_ibuf *next;
- unsigned int len;
-};
-
-static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
- while (buf) {
- struct cx_strreplace_ibuf *next = buf->next;
- free(buf->buf);
- free(buf);
- buf = next;
- }
-}
-
cxmutstr cx_strreplacen_a(
- CxAllocator const *allocator,
+ const CxAllocator *allocator,
cxstring str,
- cxstring pattern,
+ cxstring search,
cxstring replacement,
size_t replmax
) {
-
- if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+ // special cases
+ if (search.length == 0 || search.length > str.length || replmax == 0) {
return cx_strdup_a(allocator, str);
-
- // Compute expected buffer length
- size_t ibufmax = str.length / pattern.length;
- size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
- if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
- ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
}
- // Allocate first index buffer
- struct cx_strreplace_ibuf *firstbuf, *curbuf;
- firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
- if (!firstbuf) return cx_mutstrn(NULL, 0);
- firstbuf->buf = calloc(ibuflen, sizeof(size_t));
- if (!firstbuf->buf) {
- free(firstbuf);
- return cx_mutstrn(NULL, 0);
- }
+ size_t in_len = str.length;
+ size_t search_len = search.length;
+ size_t repl_len = replacement.length;
- // Search occurrences
- cxstring searchstr = str;
- size_t found = 0;
- do {
- cxstring match = cx_strstr(searchstr, pattern);
- if (match.length > 0) {
- // Allocate next buffer in chain, if required
- if (curbuf->len == ibuflen) {
- struct cx_strreplace_ibuf *nextbuf =
- calloc(1, sizeof(struct cx_strreplace_ibuf));
- if (!nextbuf) {
- cx_strrepl_free_ibuf(firstbuf);
- return cx_mutstrn(NULL, 0);
- }
- nextbuf->buf = calloc(ibuflen, sizeof(size_t));
- if (!nextbuf->buf) {
- free(nextbuf);
- cx_strrepl_free_ibuf(firstbuf);
- return cx_mutstrn(NULL, 0);
- }
- curbuf->next = nextbuf;
- curbuf = nextbuf;
- }
-
- // Record match index
- found++;
- size_t idx = match.ptr - str.ptr;
- curbuf->buf[curbuf->len++] = idx;
- searchstr.ptr = match.ptr + pattern.length;
- searchstr.length = str.length - idx - pattern.length;
- } else {
- break;
- }
- } while (searchstr.length > 0 && found < replmax);
-
- // Allocate result string
- cxmutstr result;
- {
- ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
- size_t rcount = 0;
- curbuf = firstbuf;
- do {
- rcount += curbuf->len;
- curbuf = curbuf->next;
- } while (curbuf);
- result.length = str.length + rcount * adjlen;
- result.ptr = cxMalloc(allocator, result.length + 1);
- if (!result.ptr) {
- cx_strrepl_free_ibuf(firstbuf);
- return cx_mutstrn(NULL, 0);
- }
+ // first run, count the occurrences
+ // and remember where the first is
+ size_t occurrences = 1;
+ cxstring first = cx_strstr(str, search);
+ if (first.length == 0) {
+ // special case, no replacements
+ return cx_strdup_a(allocator, str);
+ }
+ cxstring tmp = cx_strsubs(first, search_len);
+ while (occurrences < replmax &&
+ (tmp = cx_strstr(tmp, search)).length > 0) {
+ occurrences++;
+ tmp = cx_strsubs(tmp, search_len);
}
- // Build result string
- curbuf = firstbuf;
- size_t srcidx = 0;
- char *destptr = result.ptr;
- do {
- for (size_t i = 0; i < curbuf->len; i++) {
- // Copy source part up to next match
- size_t idx = curbuf->buf[i];
- size_t srclen = idx - srcidx;
- if (srclen > 0) {
- memcpy(destptr, str.ptr + srcidx, srclen);
- destptr += srclen;
- srcidx += srclen;
- }
-
- // Copy the replacement and skip the source pattern
- srcidx += pattern.length;
- memcpy(destptr, replacement.ptr, replacement.length);
- destptr += replacement.length;
- }
- curbuf = curbuf->next;
- } while (curbuf);
- memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
-
- // Result is guaranteed to be zero-terminated
- result.ptr[result.length] = '\0';
+ // calculate necessary memory
+ signed long long diff_len = (signed long long) repl_len - search_len;
+ size_t out_len = in_len + diff_len * occurrences;
+ cxmutstr out = {
+ cxMalloc(allocator, out_len + 1),
+ out_len
+ };
+ if (out.ptr == NULL) return out;
+
+ // second run: perform the replacements
+ // but start where we found the first occurrence
+ const char *inp = str.ptr;
+ tmp = first;
+ char *outp = out.ptr;
+ while (occurrences-- > 0 && (tmp = cx_strstr(tmp, search)).length > 0) {
+ size_t copylen = tmp.ptr - inp;
+ memcpy(outp, inp, copylen);
+ outp += copylen;
+ memcpy(outp, replacement.ptr, repl_len);
+ outp += repl_len;
+ inp += copylen + search_len;
+ tmp = cx_strsubs(tmp, search_len);
+ }
- // Free index buffer
- cx_strrepl_free_ibuf(firstbuf);
+ // add the remaining string
+ size_t copylen = in_len - (inp - str.ptr);
+ memcpy(outp, inp, copylen);
+ out.ptr[out_len] = '\0';
- return result;
+ return out;
}
-CxStrtokCtx cx_strtok(
+CxStrtokCtx cx_strtok_(
cxstring str,
cxstring delim,
size_t limit
return ctx;
}
-CxStrtokCtx cx_strtok_m(
- cxmutstr str,
- cxstring delim,
- size_t limit
-) {
- return cx_strtok(cx_strcast(str), delim, limit);
-}
-
bool cx_strtok_next(
CxStrtokCtx *ctx,
cxstring *token
// if more delimiters are specified, check them now
if (ctx->delim_more_count > 0) {
- cx_for_n(i, ctx->delim_more_count) {
+ for (size_t i = 0; i < ctx->delim_more_count; i++) {
cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
delim.ptr = d.ptr;
void cx_strtok_delim(
CxStrtokCtx *ctx,
- cxstring const *delim,
+ const cxstring *delim,
size_t count
) {
ctx->delim_more = delim;
ctx->delim_more_count = count;
}
+
+#define cx_strtoX_signed_impl(rtype, rmin, rmax) \
+ long long result; \
+ if (cx_strtoll_lc(str, &result, base, groupsep)) { \
+ return -1; \
+ } \
+ if (result < rmin || result > rmax) { \
+ errno = ERANGE; \
+ return -1; \
+ } \
+ *output = (rtype) result; \
+ return 0
+
+int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(short, SHRT_MIN, SHRT_MAX);
+}
+
+int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int, INT_MIN, INT_MAX);
+}
+
+int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(long, LONG_MIN, LONG_MAX);
+}
+
+int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep) {
+ // strategy: parse as unsigned, check range, negate if required
+ bool neg = false;
+ size_t start_unsigned = 0;
+
+ // emptiness check
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // test if we have a negative sign character
+ if (str.ptr[start_unsigned] == '-') {
+ neg = true;
+ start_unsigned++;
+ // must not be followed by positive sign character
+ if (str.length == 1 || str.ptr[start_unsigned] == '+') {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ // now parse the number with strtoull
+ unsigned long long v;
+ cxstring ustr = start_unsigned == 0 ? str
+ : cx_strn(str.ptr + start_unsigned, str.length - start_unsigned);
+ int ret = cx_strtoull_lc(ustr, &v, base, groupsep);
+ if (ret != 0) return ret;
+ if (neg) {
+ if (v - 1 > LLONG_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = -(long long) v;
+ return 0;
+ } else {
+ if (v > LLONG_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = (long long) v;
+ return 0;
+ }
+}
+
+int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int8_t, INT8_MIN, INT8_MAX);
+}
+
+int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int16_t, INT16_MIN, INT16_MAX);
+}
+
+int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep) {
+ cx_strtoX_signed_impl(int32_t, INT32_MIN, INT32_MAX);
+}
+
+int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep) {
+ assert(sizeof(long long) == sizeof(int64_t)); // should be true on all platforms
+ return cx_strtoll_lc(str, (long long*) output, base, groupsep);
+}
+
+#define cx_strtoX_unsigned_impl(rtype, rmax) \
+ uint64_t result; \
+ if (cx_strtou64_lc(str, &result, base, groupsep)) { \
+ return -1; \
+ } \
+ if (result > rmax) { \
+ errno = ERANGE; \
+ return -1; \
+ } \
+ *output = (rtype) result; \
+ return 0
+
+int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned short, USHRT_MAX);
+}
+
+int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned int, UINT_MAX);
+}
+
+int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(unsigned long, ULONG_MAX);
+}
+
+int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep) {
+ // some sanity checks
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!(base == 2 || base == 8 || base == 10 || base == 16)) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (groupsep == NULL) groupsep = "";
+
+ // find the actual start of the number
+ if (str.ptr[0] == '+') {
+ str.ptr++;
+ str.length--;
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+ size_t start = 0;
+
+ // if base is 2 or 16, some leading stuff may appear
+ if (base == 2) {
+ if ((str.ptr[0] | 32) == 'b') {
+ start = 1;
+ } else if (str.ptr[0] == '0' && str.length > 1) {
+ if ((str.ptr[1] | 32) == 'b') {
+ start = 2;
+ }
+ }
+ } else if (base == 16) {
+ if ((str.ptr[0] | 32) == 'x' || str.ptr[0] == '#') {
+ start = 1;
+ } else if (str.ptr[0] == '0' && str.length > 1) {
+ if ((str.ptr[1] | 32) == 'x') {
+ start = 2;
+ }
+ }
+ }
+
+ // check if there are digits left
+ if (start >= str.length) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // now parse the number
+ unsigned long long result = 0;
+ for (size_t i = start; i < str.length; i++) {
+ // ignore group separators
+ if (strchr(groupsep, str.ptr[i])) continue;
+
+ // determine the digit value of the character
+ unsigned char c = str.ptr[i];
+ if (c >= 'a') c = 10 + (c - 'a');
+ else if (c >= 'A') c = 10 + (c - 'A');
+ else if (c >= '0') c = c - '0';
+ else c = 255;
+ if (c >= base) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // now combine the digit with what we already have
+ unsigned long right = (result & 0xff) * base + c;
+ unsigned long long left = (result >> 8) * base + (right >> 8);
+ if (left > (ULLONG_MAX >> 8)) {
+ errno = ERANGE;
+ return -1;
+ }
+ result = (left << 8) + (right & 0xff);
+ }
+
+ *output = result;
+ return 0;
+}
+
+int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint8_t, UINT8_MAX);
+}
+
+int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint16_t, UINT16_MAX);
+}
+
+int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep) {
+ cx_strtoX_unsigned_impl(uint32_t, UINT32_MAX);
+}
+
+int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep) {
+ assert(sizeof(unsigned long long) == sizeof(uint64_t)); // should be true on all platforms
+ return cx_strtoull_lc(str, (unsigned long long*) output, base, groupsep);
+}
+
+int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep) {
+#if SIZE_MAX == UINT32_MAX
+ return cx_strtou32_lc_(str, (uint32_t*) output, base, groupsep);
+#elif SIZE_MAX == UINT64_MAX
+ return cx_strtoull_lc_(str, (unsigned long long *) output, base, groupsep);
+#else
+#error "unsupported size_t size"
+#endif
+}
+
+int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep) {
+ // use string to double and add a range check
+ double d;
+ int ret = cx_strtod_lc_(str, &d, decsep, groupsep);
+ if (ret != 0) return ret;
+ // note: FLT_MIN is the smallest POSITIVE number that can be represented
+ double test = d < 0 ? -d : d;
+ if (test < FLT_MIN || test > FLT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ *output = (float) d;
+ return 0;
+}
+
+static bool str_isdigit(char c) {
+ // TODO: remove once UCX has public API for this
+ return c >= '0' && c <= '9';
+}
+
+int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) {
+ // TODO: overflow check
+ // TODO: increase precision
+
+ // emptiness check
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ double result = 0.;
+ int sign = 1;
+
+ // check if there is a sign
+ if (str.ptr[0] == '-') {
+ sign = -1;
+ str.ptr++;
+ str.length--;
+ } else if (str.ptr[0] == '+') {
+ str.ptr++;
+ str.length--;
+ }
+
+ // there must be at least one char to parse
+ if (str.length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // parse all digits until we find the decsep
+ size_t pos = 0;
+ do {
+ if (str_isdigit(str.ptr[pos])) {
+ result = result * 10 + (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ break;
+ }
+ } while (++pos < str.length);
+
+ // already done?
+ if (pos == str.length) {
+ *output = result * sign;
+ return 0;
+ }
+
+ // is the next char the decsep?
+ if (str.ptr[pos] == decsep) {
+ pos++;
+ // it may end with the decsep, if it did not start with it
+ if (pos == str.length) {
+ if (str.length == 1) {
+ errno = EINVAL;
+ return -1;
+ } else {
+ *output = result * sign;
+ return 0;
+ }
+ }
+ // parse everything until exponent or end
+ double factor = 1.;
+ do {
+ if (str_isdigit(str.ptr[pos])) {
+ factor *= 0.1;
+ result = result + factor * (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ break;
+ }
+ } while (++pos < str.length);
+ }
+
+ // no exponent?
+ if (pos == str.length) {
+ *output = result * sign;
+ return 0;
+ }
+
+ // now the next separator MUST be the exponent separator
+ // and at least one char must follow
+ if ((str.ptr[pos] | 32) != 'e' || str.length <= pos + 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ pos++;
+
+ // check if we have a sign for the exponent
+ double factor = 10.;
+ if (str.ptr[pos] == '-') {
+ factor = .1;
+ pos++;
+ } else if (str.ptr[pos] == '+') {
+ pos++;
+ }
+
+ // at least one digit must follow
+ if (pos == str.length) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // parse the exponent
+ unsigned int exp = 0;
+ do {
+ if (str_isdigit(str.ptr[pos])) {
+ exp = 10 * exp + (str.ptr[pos] - '0');
+ } else if (strchr(groupsep, str.ptr[pos]) == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ } while (++pos < str.length);
+
+ // apply the exponent by fast exponentiation
+ do {
+ if (exp & 1) {
+ result *= factor;
+ }
+ factor *= factor;
+ } while ((exp >>= 1) > 0);
+
+ // store the result and exit
+ *output = result * sign;
+ return 0;
+}
* POSSIBILITY OF SUCH DAMAGE.
*/
+#include "cx/common.h"
+
+#ifndef CX_SZMUL_BUILTIN
int cx_szmul_impl(
size_t a,
size_t b,
*result = 0;
return 1;
}
-}
\ No newline at end of file
+}
+#endif // CX_SZMUL_BUILTIN
--- /dev/null
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/tree.h"
+
+#include "cx/array_list.h"
+
+#include <assert.h>
+
+#define CX_TREE_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define tree_parent(node) CX_TREE_PTR(node, loc_parent)
+#define tree_children(node) CX_TREE_PTR(node, loc_children)
+#define tree_last_child(node) CX_TREE_PTR(node, loc_last_child)
+#define tree_prev(node) CX_TREE_PTR(node, loc_prev)
+#define tree_next(node) CX_TREE_PTR(node, loc_next)
+
+#define cx_tree_ptr_locations \
+ loc_parent, loc_children, loc_last_child, loc_prev, loc_next
+
+#define cx_tree_node_layout(tree) \
+ (tree)->loc_parent,\
+ (tree)->loc_children,\
+ (tree)->loc_last_child,\
+ (tree)->loc_prev, \
+ (tree)->loc_next
+
+static void cx_tree_zero_pointers(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ tree_parent(node) = NULL;
+ if (loc_prev >= 0) {
+ tree_prev(node) = NULL;
+ }
+ tree_next(node) = NULL;
+ tree_children(node) = NULL;
+ if (loc_last_child >= 0) {
+ tree_last_child(node) = NULL;
+ }
+}
+
+void cx_tree_link(
+ void *parent,
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ assert(loc_parent >= 0);
+ assert(loc_children >= 0);
+ assert(loc_next >= 0);
+
+ void *current_parent = tree_parent(node);
+ if (current_parent == parent) return;
+ if (current_parent != NULL) {
+ cx_tree_unlink(node, cx_tree_ptr_locations);
+ }
+
+ if (tree_children(parent) == NULL) {
+ tree_children(parent) = node;
+ if (loc_last_child >= 0) {
+ tree_last_child(parent) = node;
+ }
+ } else {
+ void *child;
+ if (loc_last_child >= 0) {
+ child = tree_last_child(parent);
+ tree_last_child(parent) = node;
+ } else {
+ child = tree_children(parent);
+ void *next;
+ while ((next = tree_next(child)) != NULL) {
+ child = next;
+ }
+ }
+ if (loc_prev >= 0) {
+ tree_prev(node) = child;
+ }
+ tree_next(child) = node;
+ }
+ tree_parent(node) = parent;
+}
+
+static void *cx_tree_node_prev(
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next,
+ const void *node
+) {
+ void *parent = tree_parent(node);
+ void *begin = tree_children(parent);
+ if (begin == node) return NULL;
+ const void *cur = begin;
+ const void *next;
+ while (1) {
+ next = tree_next(cur);
+ if (next == node) return (void *) cur;
+ cur = next;
+ }
+}
+
+void cx_tree_unlink(
+ void *node,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ if (tree_parent(node) == NULL) return;
+
+ assert(loc_children >= 0);
+ assert(loc_next >= 0);
+ assert(loc_parent >= 0);
+ void *left;
+ if (loc_prev >= 0) {
+ left = tree_prev(node);
+ } else {
+ left = cx_tree_node_prev(loc_parent, loc_children, loc_next, node);
+ }
+ void *right = tree_next(node);
+ void *parent = tree_parent(node);
+ assert(left == NULL || tree_children(parent) != node);
+ assert(right == NULL || loc_last_child < 0 ||
+ tree_last_child(parent) != node);
+
+ if (left == NULL) {
+ tree_children(parent) = right;
+ } else {
+ tree_next(left) = right;
+ }
+ if (right == NULL) {
+ if (loc_last_child >= 0) {
+ tree_last_child(parent) = left;
+ }
+ } else {
+ if (loc_prev >= 0) {
+ tree_prev(right) = left;
+ }
+ }
+
+ tree_parent(node) = NULL;
+ tree_next(node) = NULL;
+ if (loc_prev >= 0) {
+ tree_prev(node) = NULL;
+ }
+}
+
+int cx_tree_search(
+ const void *root,
+ size_t depth,
+ const void *node,
+ cx_tree_search_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ // help avoiding bugs due to uninitialized memory
+ assert(result != NULL);
+ *result = NULL;
+
+ // remember return value for best match
+ int ret = sfunc(root, node);
+ if (ret < 0) {
+ // not contained, exit
+ return -1;
+ }
+ *result = (void*) root;
+ // if root is already exact match, exit
+ if (ret == 0) {
+ return 0;
+ }
+
+ // when depth is one, we are already done
+ if (depth == 1) {
+ return ret;
+ }
+
+ // special case: indefinite depth
+ if (depth == 0) {
+ depth = SIZE_MAX;
+ }
+
+ // create an iterator
+ CxTreeIterator iter = cx_tree_iterator(
+ (void*) root, false, loc_children, loc_next
+ );
+
+ // skip root, we already handled it
+ cxIteratorNext(iter);
+
+ // loop through the remaining tree
+ cx_foreach(void *, elem, iter) {
+ // investigate the current node
+ int ret_elem = sfunc(elem, node);
+ if (ret_elem == 0) {
+ // if found, exit the search
+ *result = elem;
+ ret = 0;
+ break;
+ } else if (ret_elem > 0 && ret_elem < ret) {
+ // new distance is better
+ *result = elem;
+ ret = ret_elem;
+ } else if (ret_elem < 0 || ret_elem > ret) {
+ // not contained or distance is worse, skip entire subtree
+ cxTreeIteratorContinue(iter);
+ }
+
+ // when we reached the max depth, skip the subtree
+ if (iter.depth == depth) {
+ cxTreeIteratorContinue(iter);
+ }
+ }
+
+ // dispose the iterator as we might have exited the loop early
+ cxTreeIteratorDispose(&iter);
+
+ assert(ret < 0 || *result != NULL);
+ return ret;
+}
+
+int cx_tree_search_data(
+ const void *root,
+ size_t depth,
+ const void *data,
+ cx_tree_search_data_func sfunc,
+ void **result,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ // it is basically the same implementation
+ return cx_tree_search(
+ root, depth, data,
+ (cx_tree_search_func) sfunc,
+ result,
+ loc_children, loc_next);
+}
+
+static bool cx_tree_iter_valid(const void *it) {
+ const struct cx_tree_iterator_s *iter = it;
+ return iter->node != NULL;
+}
+
+static void *cx_tree_iter_current(const void *it) {
+ const struct cx_tree_iterator_s *iter = it;
+ return iter->node;
+}
+
+static void cx_tree_iter_next(void *it) {
+ struct cx_tree_iterator_s *iter = it;
+ ptrdiff_t const loc_next = iter->loc_next;
+ ptrdiff_t const loc_children = iter->loc_children;
+ // protect us from misuse
+ if (!iter->base.valid(iter)) return;
+
+ void *children;
+
+ // check if we are currently exiting or entering nodes
+ if (iter->exiting) {
+ children = NULL;
+ // skipping on exit is pointless, just clear the flag
+ iter->skip = false;
+ } else {
+ if (iter->skip) {
+ // skip flag is set, pretend that there are no children
+ iter->skip = false;
+ children = NULL;
+ } else {
+ // try to enter the children (if any)
+ children = tree_children(iter->node);
+ }
+ }
+
+ if (children == NULL) {
+ // search for the next node
+ void *next = NULL;
+ cx_tree_iter_search_next:
+ // check if there is a sibling, but only if we are not a (subtree-)root
+ if (iter->exiting) {
+ next = iter->node_next;
+ } else if (iter->depth > 1) {
+ next = tree_next(iter->node);
+ iter->node_next = next;
+ }
+ if (next == NULL) {
+ // no sibling, we are done with this node and exit
+ if (iter->visit_on_exit && !iter->exiting) {
+ // iter is supposed to visit the node again
+ iter->exiting = true;
+ } else {
+ iter->exiting = false;
+ if (iter->depth == 1) {
+ // there is no parent - we have iterated the entire tree
+ // invalidate the iterator and free the node stack
+ iter->node = iter->node_next = NULL;
+ iter->stack_capacity = iter->depth = 0;
+ cxFreeDefault(iter->stack);
+ iter->stack = NULL;
+ } else {
+ // the parent node can be obtained from the top of stack
+ // this way we can avoid the loc_parent in the iterator
+ iter->depth--;
+ iter->node = iter->stack[iter->depth - 1];
+ // retry with the parent node to find a sibling
+ goto cx_tree_iter_search_next;
+ }
+ }
+ } else {
+ if (iter->visit_on_exit && !iter->exiting) {
+ // iter is supposed to visit the node again
+ iter->exiting = true;
+ } else {
+ iter->exiting = false;
+ // move to the sibling
+ iter->counter++;
+ iter->node = next;
+ // new top of stack is the sibling
+ iter->stack[iter->depth - 1] = next;
+ }
+ }
+ } else {
+ // node has children, push the first child onto the stack and enter it
+ cx_array_simple_add(iter->stack, children);
+ iter->node = children;
+ iter->counter++;
+ }
+}
+
+CxTreeIterator cx_tree_iterator(
+ void *root,
+ bool visit_on_exit,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ CxTreeIterator iter;
+ iter.loc_children = loc_children;
+ iter.loc_next = loc_next;
+ iter.visit_on_exit = visit_on_exit;
+
+ // initialize members
+ iter.node_next = NULL;
+ iter.exiting = false;
+ iter.skip = false;
+
+ // assign base iterator functions
+ iter.base.mutating = false;
+ iter.base.remove = false;
+ iter.base.current_impl = NULL;
+ iter.base.valid = cx_tree_iter_valid;
+ iter.base.next = cx_tree_iter_next;
+ iter.base.current = cx_tree_iter_current;
+
+ // visit the root node
+ iter.node = root;
+ if (root != NULL) {
+ iter.stack_capacity = 16;
+ iter.stack = cxMallocDefault(sizeof(void *) * 16);
+ iter.stack[0] = root;
+ iter.counter = 1;
+ iter.depth = 1;
+ } else {
+ iter.stack_capacity = 0;
+ iter.stack = NULL;
+ iter.counter = 0;
+ iter.depth = 0;
+ }
+
+ return iter;
+}
+
+static bool cx_tree_visitor_valid(const void *it) {
+ const struct cx_tree_visitor_s *iter = it;
+ return iter->node != NULL;
+}
+
+static void *cx_tree_visitor_current(const void *it) {
+ const struct cx_tree_visitor_s *iter = it;
+ return iter->node;
+}
+
+cx_attr_nonnull
+static void cx_tree_visitor_enqueue_siblings(
+ struct cx_tree_visitor_s *iter, void *node, ptrdiff_t loc_next) {
+ node = tree_next(node);
+ while (node != NULL) {
+ struct cx_tree_visitor_queue_s *q;
+ q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s));
+ q->depth = iter->queue_last->depth;
+ q->node = node;
+ iter->queue_last->next = q;
+ iter->queue_last = q;
+ node = tree_next(node);
+ }
+ iter->queue_last->next = NULL;
+}
+
+static void cx_tree_visitor_next(void *it) {
+ struct cx_tree_visitor_s *iter = it;
+ // protect us from misuse
+ if (!iter->base.valid(iter)) return;
+
+ ptrdiff_t const loc_next = iter->loc_next;
+ ptrdiff_t const loc_children = iter->loc_children;
+
+ // add the children of the current node to the queue
+ // unless the skip flag is set
+ void *children;
+ if (iter->skip) {
+ iter->skip = false;
+ children = NULL;
+ } else {
+ children = tree_children(iter->node);
+ }
+ if (children != NULL) {
+ struct cx_tree_visitor_queue_s *q;
+ q = cxMallocDefault(sizeof(struct cx_tree_visitor_queue_s));
+ q->depth = iter->depth + 1;
+ q->node = children;
+ if (iter->queue_last == NULL) {
+ assert(iter->queue_next == NULL);
+ iter->queue_next = q;
+ } else {
+ iter->queue_last->next = q;
+ }
+ iter->queue_last = q;
+ cx_tree_visitor_enqueue_siblings(iter, children, loc_next);
+ }
+
+ // check if there is a next node
+ if (iter->queue_next == NULL) {
+ iter->node = NULL;
+ return;
+ }
+
+ // dequeue the next node
+ iter->node = iter->queue_next->node;
+ iter->depth = iter->queue_next->depth;
+ {
+ struct cx_tree_visitor_queue_s *q = iter->queue_next;
+ iter->queue_next = q->next;
+ if (iter->queue_next == NULL) {
+ assert(iter->queue_last == q);
+ iter->queue_last = NULL;
+ }
+ cxFreeDefault(q);
+ }
+
+ // increment the node counter
+ iter->counter++;
+}
+
+CxTreeVisitor cx_tree_visitor(
+ void *root,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_next
+) {
+ CxTreeVisitor iter;
+ iter.loc_children = loc_children;
+ iter.loc_next = loc_next;
+
+ // initialize members
+ iter.skip = false;
+ iter.queue_next = NULL;
+ iter.queue_last = NULL;
+
+ // assign base iterator functions
+ iter.base.mutating = false;
+ iter.base.remove = false;
+ iter.base.current_impl = NULL;
+ iter.base.valid = cx_tree_visitor_valid;
+ iter.base.next = cx_tree_visitor_next;
+ iter.base.current = cx_tree_visitor_current;
+
+ // visit the root node
+ iter.node = root;
+ if (root != NULL) {
+ iter.counter = 1;
+ iter.depth = 1;
+ } else {
+ iter.counter = 0;
+ iter.depth = 0;
+ }
+
+ return iter;
+}
+
+static void cx_tree_add_link_duplicate(
+ void *original, void *duplicate,
+ ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+ void *shared_parent = tree_parent(original);
+ if (shared_parent == NULL) {
+ cx_tree_link(original, duplicate, cx_tree_ptr_locations);
+ } else {
+ cx_tree_link(shared_parent, duplicate, cx_tree_ptr_locations);
+ }
+}
+
+static void cx_tree_add_link_new(
+ void *parent, void *node, cx_tree_search_func sfunc,
+ ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev, ptrdiff_t loc_next
+) {
+ // check the current children one by one,
+ // if they could be children of the new node
+ void *child = tree_children(parent);
+ while (child != NULL) {
+ void *next = tree_next(child);
+
+ if (sfunc(node, child) > 0) {
+ // the sibling could be a child -> re-link
+ cx_tree_link(node, child, cx_tree_ptr_locations);
+ }
+
+ child = next;
+ }
+
+ // add new node as new child
+ cx_tree_link(parent, node, cx_tree_ptr_locations);
+}
+
+int cx_tree_add(
+ const void *src,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **cnode,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ *cnode = cfunc(src, cdata);
+ if (*cnode == NULL) return 1;
+ cx_tree_zero_pointers(*cnode, cx_tree_ptr_locations);
+
+ void *match = NULL;
+ int result = cx_tree_search(
+ root,
+ 0,
+ *cnode,
+ sfunc,
+ &match,
+ loc_children,
+ loc_next
+ );
+
+ if (result < 0) {
+ // node does not fit into the tree - return non-zero value
+ return 1;
+ } else if (result == 0) {
+ // data already found in the tree, link duplicate
+ cx_tree_add_link_duplicate(match, *cnode, cx_tree_ptr_locations);
+ } else {
+ // closest match found, add new node
+ cx_tree_add_link_new(match, *cnode, sfunc, cx_tree_ptr_locations);
+ }
+
+ return 0;
+}
+
+unsigned int cx_tree_add_look_around_depth = 3;
+
+size_t cx_tree_add_iter(
+ struct cx_iterator_base_s *iter,
+ size_t num,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ // erase the failed pointer
+ *failed = NULL;
+
+ // iter not valid? cancel...
+ if (!iter->valid(iter)) return 0;
+
+ size_t processed = 0;
+ void *current_node = root;
+ const void *elem;
+
+ for (void **eptr; processed < num &&
+ iter->valid(iter) && (eptr = iter->current(iter)) != NULL;
+ iter->next(iter)) {
+ elem = *eptr;
+
+ // create the new node
+ void *new_node = cfunc(elem, cdata);
+ if (new_node == NULL) return processed;
+ cx_tree_zero_pointers(new_node, cx_tree_ptr_locations);
+
+ // start searching from current node
+ void *match;
+ int result;
+ unsigned int look_around_retries = cx_tree_add_look_around_depth;
+ cx_tree_add_look_around_retry:
+ result = cx_tree_search(
+ current_node,
+ 0,
+ new_node,
+ sfunc,
+ &match,
+ loc_children,
+ loc_next
+ );
+
+ if (result < 0) {
+ // traverse upwards and try to find better parents
+ void *parent = tree_parent(current_node);
+ if (parent != NULL) {
+ if (look_around_retries > 0) {
+ look_around_retries--;
+ current_node = parent;
+ } else {
+ // look around retries exhausted, start from the root
+ current_node = root;
+ }
+ goto cx_tree_add_look_around_retry;
+ } else {
+ // no parents. so we failed
+ *failed = new_node;
+ return processed;
+ }
+ } else if (result == 0) {
+ // data already found in the tree, link duplicate
+ cx_tree_add_link_duplicate(match, new_node, cx_tree_ptr_locations);
+ // but stick with the original match, in case we needed a new root
+ current_node = match;
+ } else {
+ // closest match found, add new node as child
+ cx_tree_add_link_new(match, new_node, sfunc,
+ cx_tree_ptr_locations);
+ current_node = match;
+ }
+
+ processed++;
+ }
+ return processed;
+}
+
+size_t cx_tree_add_array(
+ const void *src,
+ size_t num,
+ size_t elem_size,
+ cx_tree_search_func sfunc,
+ cx_tree_node_create_func cfunc,
+ void *cdata,
+ void **failed,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ // erase failed pointer
+ *failed = NULL;
+
+ // super special case: zero elements
+ if (num == 0) {
+ return 0;
+ }
+
+ // special case: one element does not need an iterator
+ if (num == 1) {
+ void *node;
+ if (0 == cx_tree_add(
+ src, sfunc, cfunc, cdata, &node, root,
+ loc_parent, loc_children, loc_last_child,
+ loc_prev, loc_next)) {
+ return 1;
+ } else {
+ *failed = node;
+ return 0;
+ }
+ }
+
+ // otherwise, create iterator and hand over to other function
+ CxIterator iter = cxIterator(src, elem_size, num);
+ return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc,
+ cfunc, cdata, failed, root,
+ loc_parent, loc_children, loc_last_child,
+ loc_prev, loc_next);
+}
+
+static int cx_tree_default_insert_element(
+ CxTree *tree,
+ const void *data
+) {
+ void *node;
+ if (tree->root == NULL) {
+ node = tree->node_create(data, tree);
+ if (node == NULL) return 1;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ tree->root = node;
+ tree->size = 1;
+ return 0;
+ }
+ int result = cx_tree_add(data, tree->search, tree->node_create,
+ tree, &node, tree->root, cx_tree_node_layout(tree));
+ if (0 == result) {
+ tree->size++;
+ } else {
+ cxFree(tree->allocator, node);
+ }
+ return result;
+}
+
+static size_t cx_tree_default_insert_many(
+ CxTree *tree,
+ CxIteratorBase *iter,
+ size_t n
+) {
+ size_t ins = 0;
+ if (!iter->valid(iter)) return 0;
+ if (tree->root == NULL) {
+ // use the first element from the iter to create the root node
+ void **eptr = iter->current(iter);
+ void *node = tree->node_create(*eptr, tree);
+ if (node == NULL) return 0;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ tree->root = node;
+ ins = 1;
+ iter->next(iter);
+ }
+ void *failed;
+ ins += cx_tree_add_iter(iter, n, tree->search, tree->node_create,
+ tree, &failed, tree->root, cx_tree_node_layout(tree));
+ tree->size += ins;
+ if (ins < n) {
+ cxFree(tree->allocator, failed);
+ }
+ return ins;
+}
+
+static void *cx_tree_default_find(
+ CxTree *tree,
+ const void *subtree,
+ const void *data,
+ size_t depth
+) {
+ if (tree->root == NULL) return NULL;
+
+ void *found;
+ if (0 == cx_tree_search_data(
+ subtree,
+ depth,
+ data,
+ tree->search_data,
+ &found,
+ tree->loc_children,
+ tree->loc_next
+ )) {
+ return found;
+ } else {
+ return NULL;
+ }
+}
+
+static cx_tree_class cx_tree_default_class = {
+ cx_tree_default_insert_element,
+ cx_tree_default_insert_many,
+ cx_tree_default_find
+};
+
+CxTree *cxTreeCreate(
+ const CxAllocator *allocator,
+ cx_tree_node_create_func create_func,
+ cx_tree_search_func search_func,
+ cx_tree_search_data_func search_data_func,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ assert(create_func != NULL);
+ assert(search_func != NULL);
+ assert(search_data_func != NULL);
+
+ CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+ if (tree == NULL) return NULL;
+
+ tree->cl = &cx_tree_default_class;
+ tree->allocator = allocator;
+ tree->node_create = create_func;
+ tree->search = search_func;
+ tree->search_data = search_data_func;
+ tree->simple_destructor = NULL;
+ tree->advanced_destructor = (cx_destructor_func2) cxFree;
+ tree->destructor_data = (void *) allocator;
+ tree->loc_parent = loc_parent;
+ tree->loc_children = loc_children;
+ tree->loc_last_child = loc_last_child;
+ tree->loc_prev = loc_prev;
+ tree->loc_next = loc_next;
+ tree->root = NULL;
+ tree->size = 0;
+
+ return tree;
+}
+
+void cxTreeFree(CxTree *tree) {
+ if (tree == NULL) return;
+ if (tree->root != NULL) {
+ cxTreeClear(tree);
+ }
+ cxFree(tree->allocator, tree);
+}
+
+CxTree *cxTreeCreateWrapped(
+ const CxAllocator *allocator,
+ void *root,
+ ptrdiff_t loc_parent,
+ ptrdiff_t loc_children,
+ ptrdiff_t loc_last_child,
+ ptrdiff_t loc_prev,
+ ptrdiff_t loc_next
+) {
+ if (allocator == NULL) {
+ allocator = cxDefaultAllocator;
+ }
+ assert(root != NULL);
+
+ CxTree *tree = cxMalloc(allocator, sizeof(CxTree));
+ if (tree == NULL) return NULL;
+
+ tree->cl = &cx_tree_default_class;
+ // set the allocator anyway, just in case...
+ tree->allocator = allocator;
+ tree->node_create = NULL;
+ tree->search = NULL;
+ tree->search_data = NULL;
+ tree->simple_destructor = NULL;
+ tree->advanced_destructor = NULL;
+ tree->destructor_data = NULL;
+ tree->loc_parent = loc_parent;
+ tree->loc_children = loc_children;
+ tree->loc_last_child = loc_last_child;
+ tree->loc_prev = loc_prev;
+ tree->loc_next = loc_next;
+ tree->root = root;
+ tree->size = cxTreeSubtreeSize(tree, root);
+ return tree;
+}
+
+void cxTreeSetParent(
+ CxTree *tree,
+ void *parent,
+ void *child
+) {
+ size_t loc_parent = tree->loc_parent;
+ if (tree_parent(child) == NULL) {
+ tree->size++;
+ }
+ cx_tree_link(parent, child, cx_tree_node_layout(tree));
+}
+
+void cxTreeAddChildNode(
+ CxTree *tree,
+ void *parent,
+ void *child
+) {
+ cx_tree_link(parent, child, cx_tree_node_layout(tree));
+ tree->size++;
+}
+
+int cxTreeAddChild(
+ CxTree *tree,
+ void *parent,
+ const void *data) {
+ void *node = tree->node_create(data, tree);
+ if (node == NULL) return 1;
+ cx_tree_zero_pointers(node, cx_tree_node_layout(tree));
+ cx_tree_link(parent, node, cx_tree_node_layout(tree));
+ tree->size++;
+ return 0;
+}
+
+size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) {
+ CxTreeVisitor visitor = cx_tree_visitor(
+ subtree_root,
+ tree->loc_children,
+ tree->loc_next
+ );
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.counter;
+}
+
+size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root) {
+ CxTreeVisitor visitor = cx_tree_visitor(
+ subtree_root,
+ tree->loc_children,
+ tree->loc_next
+ );
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.depth;
+}
+
+size_t cxTreeDepth(CxTree *tree) {
+ CxTreeVisitor visitor = cx_tree_visitor(
+ tree->root, tree->loc_children, tree->loc_next
+ );
+ while (cxIteratorValid(visitor)) {
+ cxIteratorNext(visitor);
+ }
+ return visitor.depth;
+}
+
+int cxTreeRemoveNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+) {
+ if (node == tree->root) return 1;
+
+ // determine the new parent
+ ptrdiff_t loc_parent = tree->loc_parent;
+ void *new_parent = tree_parent(node);
+
+ // first, unlink from the parent
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+
+ // then relink each child
+ ptrdiff_t loc_children = tree->loc_children;
+ ptrdiff_t loc_next = tree->loc_next;
+ void *child = tree_children(node);
+ while (child != NULL) {
+ // forcibly set the parent to NULL - we do not use the unlink function
+ // because that would unnecessarily modify the children linked list
+ tree_parent(child) = NULL;
+
+ // update contents, if required
+ if (relink_func != NULL) {
+ relink_func(child, node, new_parent);
+ }
+
+ // link to new parent
+ cx_tree_link(new_parent, child, cx_tree_node_layout(tree));
+
+ // proceed to next child
+ child = tree_next(child);
+ }
+
+ // clear the linked list of the removed node
+ tree_children(node) = NULL;
+ ptrdiff_t loc_last_child = tree->loc_last_child;
+ if (loc_last_child >= 0) tree_last_child(node) = NULL;
+
+ // the tree now has one member less
+ tree->size--;
+
+ return 0;
+}
+
+void cxTreeRemoveSubtree(CxTree *tree, void *node) {
+ if (node == tree->root) {
+ tree->root = NULL;
+ tree->size = 0;
+ return;
+ }
+ size_t subtree_size = cxTreeSubtreeSize(tree, node);
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+ tree->size -= subtree_size;
+}
+
+int cxTreeDestroyNode(
+ CxTree *tree,
+ void *node,
+ cx_tree_relink_func relink_func
+) {
+ int result = cxTreeRemoveNode(tree, node, relink_func);
+ if (result == 0) {
+ if (tree->simple_destructor) {
+ tree->simple_destructor(node);
+ }
+ if (tree->advanced_destructor) {
+ tree->advanced_destructor(tree->destructor_data, node);
+ }
+ return 0;
+ } else {
+ return result;
+ }
+}
+
+void cxTreeDestroySubtree(CxTree *tree, void *node) {
+ cx_tree_unlink(node, cx_tree_node_layout(tree));
+ CxTreeIterator iter = cx_tree_iterator(
+ node, true,
+ tree->loc_children, tree->loc_next
+ );
+ cx_foreach(void *, child, iter) {
+ if (iter.exiting) {
+ if (tree->simple_destructor) {
+ tree->simple_destructor(child);
+ }
+ if (tree->advanced_destructor) {
+ tree->advanced_destructor(tree->destructor_data, child);
+ }
+ }
+ }
+ tree->size -= iter.counter;
+ if (node == tree->root) {
+ tree->root = NULL;
+ }
+}