Sun, 05 Jan 2025 22:00:39 +0100
update ucx
--- a/ucx/Makefile Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/Makefile Sun Jan 05 22:00:39 2025 +0100 @@ -26,7 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT = ../ include ../config.mk # list of source files @@ -37,14 +36,16 @@ SRC += compare.c SRC += hash_key.c SRC += hash_map.c +SRC += iterator.c SRC += linked_list.c SRC += list.c SRC += map.c SRC += printf.c SRC += string.c -SRC += utils.c SRC += tree.c -SRC += iterator.c +SRC += streams.c +SRC += properties.c +SRC += json.c OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
--- a/ucx/allocator.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/allocator.c Sun Jan 05 22:00:39 2025 +0100 @@ -28,35 +28,33 @@ #include "cx/allocator.h" -__attribute__((__malloc__, __alloc_size__(2))) +#include <errno.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, + cx_attr_unused void *d, size_t nelem, size_t n ) { return calloc(nelem, n); } -__attribute__((__nonnull__)) static void cx_free_stdlib( - __attribute__((__unused__)) void *d, + cx_attr_unused void *d, void *mem ) { free(mem); @@ -75,20 +73,41 @@ }; CxAllocator *cxDefaultAllocator = &cx_default_allocator; - +#undef 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; } } +#undef cx_reallocatearray +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( @@ -106,6 +125,22 @@ return allocator->cl->realloc(allocator->data, mem, n); } +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); + } +} + +#undef cxReallocate int cxReallocate( const CxAllocator *allocator, void **mem, @@ -113,7 +148,23 @@ ) { void *nmem = allocator->cl->realloc(allocator->data, *mem, n); if (nmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE + } else { + *mem = nmem; + return 0; + } +} + +#undef cxReallocateArray +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;
--- a/ucx/array_list.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/array_list.c Sun Jan 05 22:00:39 2025 +0100 @@ -30,6 +30,7 @@ #include "cx/compare.h" #include <assert.h> #include <string.h> +#include <errno.h> // Default array reallocator @@ -37,65 +38,258 @@ void *array, size_t capacity, size_t elem_size, - __attribute__((__unused__)) struct cx_array_reallocator_s *alloc + cx_attr_unused CxArrayReallocator *alloc ) { - return realloc(array, capacity * elem_size); + size_t n; + if (cx_szmul(capacity, elem_size, &n)) { + errno = EOVERFLOW; + return NULL; + } + return realloc(array, n); } -struct cx_array_reallocator_s cx_array_default_reallocator_impl = { +CxArrayReallocator cx_array_default_reallocator_impl = { cx_array_default_realloc, NULL, NULL, 0, 0 }; -struct cx_array_reallocator_s *cx_array_default_reallocator = &cx_array_default_reallocator_impl; +CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; + +// Stack-aware array reallocator + +static void *cx_array_advanced_realloc( + void *array, + size_t capacity, + size_t elem_size, + cx_attr_unused CxArrayReallocator *alloc +) { + // check for overflow + size_t n; + if (cx_szmul(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, n); + } + } 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_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, 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, 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_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, newcap, elem_size, reallocator ); if (newmem == NULL) { - return CX_ARRAY_REALLOC_FAILED; + return 1; } // repair src pointer, if necessary @@ -103,9 +297,8 @@ src = ((char *) newmem) + (srcaddr - targetaddr); } - // store new pointer and capacity + // store new pointer *target = newmem; - *capacity = cap; } // determine target pointer @@ -113,14 +306,34 @@ 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_SUCCESS; + return 0; } -enum cx_array_result cx_array_insert_sorted( +int cx_array_insert_sorted( void **target, size_t *size, size_t *capacity, @@ -128,7 +341,7 @@ const void *sorted_data, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ) { // assert pointers assert(target != NULL); @@ -136,25 +349,35 @@ assert(capacity != NULL); assert(cmp_func != NULL); assert(sorted_data != NULL); - assert(reallocator != 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 needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation if (needed_capacity > *capacity) { - size_t new_capacity = needed_capacity - (needed_capacity % 16) + 16; + size_t new_capacity = cx_array_align_capacity(needed_capacity, 16, SIZE_MAX); void *new_mem = reallocator->realloc( *target, 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 CX_ARRAY_REALLOC_FAILED; + return 1; // LCOV_EXCL_LINE } *target = new_mem; *capacity = new_capacity; @@ -228,7 +451,7 @@ // still buffer elements left? // don't worry, we already moved them to the correct place - return CX_ARRAY_SUCCESS; + return 0; } size_t cx_array_binary_search_inf( @@ -255,6 +478,9 @@ 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) { @@ -287,10 +513,48 @@ 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 -unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; +const unsigned cx_array_swap_sbo_size = CX_ARRAY_SWAP_SBO_SIZE; void cx_array_swap( void *arr, @@ -337,22 +601,9 @@ 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 - const CxAllocator *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; @@ -394,10 +645,11 @@ size_t elems_to_move = list->collection.size - index; size_t start_of_moved = index + n; - if (CX_ARRAY_SUCCESS != cx_array_copy( + if (cx_array_copy( &arl->data, &list->collection.size, &arl->capacity, + 0, start_of_moved, first_to_move, list->collection.elem_size, @@ -414,20 +666,21 @@ // therefore, it is impossible to leave this function with an invalid array // place the new elements - if (CX_ARRAY_SUCCESS == cx_array_copy( + if (cx_array_copy( &arl->data, &list->collection.size, &arl->capacity, + 0, index, array, list->collection.elem_size, n, &arl->reallocator )) { - return n; - } else { // array list implementation is "all or nothing" return 0; + } else { + return n; } } @@ -439,7 +692,7 @@ // get a correctly typed pointer to the list cx_array_list *arl = (cx_array_list *) list; - if (CX_ARRAY_SUCCESS == cx_array_insert_sorted( + if (cx_array_insert_sorted( &arl->data, &list->collection.size, &arl->capacity, @@ -449,10 +702,10 @@ n, &arl->reallocator )) { - return n; - } else { // array list implementation is "all or nothing" return 0; + } else { + return n; } } @@ -494,45 +747,66 @@ } } -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 + size_t remove; if (index >= list->collection.size) { - return 1; + 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->collection.elem_size); + // easy exit + if (remove == 0) return 0; - // short-circuit removal of last element - if (index == list->collection.size - 1) { - list->collection.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->collection.size, &arl->capacity, + 0, index, - ((char *) arl->data) + (index + 1) * list->collection.elem_size, + ((char *) arl->data) + (index + remove) * list->collection.elem_size, list->collection.elem_size, - list->collection.size - index - 1, + list->collection.size - index - remove, &arl->reallocator ); - // cx_array_copy cannot fail, array cannot grow - assert(result == 0); + // decrease the size + list->collection.size -= remove; - // decrease the size - list->collection.size--; - - return 0; + return remove; } static void cx_arl_clear(struct cx_list_s *list) { @@ -594,10 +868,11 @@ for (ssize_t i = 0; i < (ssize_t) list->collection.size; i++) { if (0 == list->collection.cmpfunc(elem, cur)) { if (remove) { - if (0 == cx_arl_remove(list, i)) { + if (1 == cx_arl_remove(list, i, 1, NULL)) { return i; } else { - return -1; + // should be unreachable + return -1; // LCOV_EXCL_LINE } } else { return i; @@ -664,7 +939,7 @@ struct cx_iterator_s *iter = it; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index); + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } else { iter->index++; iter->elem_handle = @@ -678,7 +953,7 @@ 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); + cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); } iter->index--; if (iter->index < list->base.collection.size) { @@ -754,14 +1029,13 @@ // allocate the array after the real elem_size is known list->data = cxCalloc(allocator, initial_capacity, elem_size); - if (list->data == NULL) { + 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; }
--- a/ucx/buffer.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/buffer.c Sun Jan 05 22:00:39 2025 +0100 @@ -27,10 +27,21 @@ */ #include "cx/buffer.h" -#include "cx/utils.h" #include <stdio.h> #include <string.h> +#include <errno.h> + +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, @@ -39,13 +50,18 @@ 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 { @@ -55,19 +71,27 @@ 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 = malloc(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); } + free(buffer->flush); + memset(buffer, 0, sizeof(CxBuffer)); } CxBuffer *cxBufferCreate( @@ -76,21 +100,26 @@ 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( @@ -117,10 +146,11 @@ npos += offset; if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) { + errno = EOVERFLOW; return -1; } - if (npos >= buffer->size) { + if (npos > buffer->size) { return -1; } else { buffer->pos = npos; @@ -130,7 +160,9 @@ } 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; } @@ -140,7 +172,7 @@ buffer->pos = 0; } -int cxBufferEof(const CxBuffer *buffer) { +bool cxBufferEof(const CxBuffer *buffer) { return buffer->pos >= buffer->size; } @@ -152,48 +184,71 @@ return 0; } - if (cxReallocate(buffer->allocator, + 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( - CxBuffer *buffer, - const unsigned char *space, +static size_t cx_buffer_flush_helper( + const CxBuffer *buffer, size_t size, + const unsigned char *src, 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, size, space, 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( @@ -204,6 +259,7 @@ ) { // 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) { @@ -213,80 +269,69 @@ } size_t len; - size_t nitems_out = nitems; if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; + return 0; + } + if (buffer->pos > SIZE_MAX - len) { + errno = EOVERFLOW; return 0; } size_t required = buffer->pos + len; - if (buffer->pos > required) { - return 0; - } 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 0; // 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 0; } - 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 + // check if we need to copy + if (buffer_copy_on_write(buffer)) return 0; - 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 + // perform the operation + if (perform_flush) { + size_t items_flush; + 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_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems); + if (items_flush == 0) { + // we needed to flush, but could not flush anything + // give up and avoid endless trying + return 0; } - items_keep = nitems - items_flush; - if (items_keep > 0) { - // try again with the remaining stuff - const unsigned char *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; + size_t ritems = nitems - items_flush; + const unsigned char *rest = ptr; + rest += items_flush * size; + return items_flush + cxBufferWrite(rest, size, ritems, buffer); + } else { + items_flush = cx_buffer_flush_impl(buffer, size); + if (items_flush == 0) { + return 0; } - } else if (flush_pos == 0) { - // nothing could be flushed at all, we immediately give up without writing any data - return 0; - } else { - // we were partially successful, we shift left and try again - cxBufferShiftLeft(buffer, flush_pos); return cxBufferWrite(ptr, size, nitems, buffer); } } else { @@ -295,11 +340,24 @@ if (buffer->pos > buffer->size) { buffer->size = buffer->pos; } - return nitems_out; + return nitems; } } +size_t cxBufferAppend( + const void *ptr, + size_t size, + size_t nitems, + CxBuffer *buffer +) { + size_t pos = buffer->pos; + buffer->pos = buffer->size; + size_t written = cxBufferWrite(ptr, size, nitems, buffer); + buffer->pos = pos; + return written; +} + int cxBufferPut( CxBuffer *buffer, int c @@ -313,6 +371,17 @@ } } +int cxBufferTerminate(CxBuffer *buffer) { + bool success = 0 == cxBufferPut(buffer, 0); + if (success) { + buffer->pos--; + buffer->size--; + return 0; + } else { + return -1; + } +} + size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -328,6 +397,7 @@ ) { size_t len; if (cx_szmul(size, nitems, &len)) { + errno = EOVERFLOW; return 0; } if (buffer->pos + len > buffer->size) { @@ -362,6 +432,7 @@ 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; @@ -378,14 +449,18 @@ 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 { @@ -395,8 +470,11 @@ 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) {
--- a/ucx/compare.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/compare.c Sun Jan 05 22:00:39 2025 +0100 @@ -30,9 +30,21 @@ #include <math.h> +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 { @@ -43,6 +55,10 @@ 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 { @@ -53,6 +69,10 @@ 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 { @@ -63,6 +83,10 @@ 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 { @@ -73,6 +97,10 @@ 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 { @@ -83,6 +111,10 @@ 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 { @@ -93,6 +125,10 @@ 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 { @@ -103,6 +139,10 @@ 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 { @@ -113,6 +153,10 @@ 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 { @@ -123,6 +167,10 @@ 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 { @@ -133,6 +181,10 @@ 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 { @@ -143,7 +195,11 @@ 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; @@ -153,7 +209,11 @@ 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; @@ -166,10 +226,14 @@ ) { 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; } } @@ -179,6 +243,10 @@ ) { 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 { @@ -192,11 +260,7 @@ ) { uintptr_t p1 = *(const uintptr_t *) ptr1; uintptr_t p2 = *(const uintptr_t *) ptr2; - if (p1 == p2) { - return 0; - } else { - return p1 < p2 ? -1 : 1; - } + return cx_vcmp_uintptr(p1, p2); } int cx_cmp_ptr(
--- a/ucx/cx/allocator.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/allocator.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file allocator.h + * @file allocator.h * Interface for custom allocators. */ @@ -54,7 +54,6 @@ /** * The allocator's realloc() implementation. */ - __attribute__((__warn_unused_result__)) void *(*realloc)( void *data, void *mem, @@ -73,7 +72,6 @@ /** * The allocator's free() implementation. */ - __attribute__((__nonnull__)) void (*free)( void *data, void *mem @@ -108,76 +106,157 @@ * 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 */ -__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 */ -__attribute__((__nonnull__(2))) typedef void (*cx_destructor_func2)( void *data, void *memory ); /** - * Re-allocate a previously allocated block and changes the pointer in-place, if necessary. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. * - * \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() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard int cx_reallocate( void **mem, size_t n ); /** - * Allocate \p n bytes of memory. + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @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 +int cx_reallocatearray( + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * @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) + +/** + * Re-allocate a previously allocated block and changes the pointer in-place, + * if necessary. + * + * The size is calculated by multiplying @p nemb and @p size. + * + * @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) + +/** + * 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) +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 */ -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2))) +cx_attr_nodiscard +cx_attr_nonnull +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2) void *cxMalloc( const CxAllocator *allocator, size_t n ); /** - * 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. + * 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. * - * \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 */ -__attribute__((__warn_unused_result__)) -__attribute__((__alloc_size__(3))) +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3) void *cxRealloc( const CxAllocator *allocator, void *mem, @@ -185,20 +264,52 @@ ); /** - * 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. + * 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. + * + * 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. + * + * @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 nmemb the number of elements + * @param size the size of each element + * @return a pointer to the re-allocated memory + */ +cx_attr_nodiscard +cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx +cx_attr_allocsize(3, 4) +void *cxReallocArray( + const CxAllocator *allocator, + void *mem, + size_t nmemb, + size_t size +); + +/** + * 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. * - * \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. + * + * @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 */ -__attribute__((__nonnull__)) +cx_attr_nodiscard +cx_attr_nonnull int cxReallocate( const CxAllocator *allocator, void **mem, @@ -206,35 +317,93 @@ ); /** - * Allocate \p nelem elements of \p n bytes each, all initialized to zero. + * 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. + * + * @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) + +/** + * Re-allocate 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 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 +int cxReallocateArray( + const CxAllocator *allocator, + void **mem, + size_t nmemb, + size_t size +); + +/** + * Re-allocate 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 nelem elements of @p n bytes each, all initialized to zero. * * @param allocator the allocator * @param nelem the number of elements * @param n the size of each element in bytes * @return a pointer to the allocated memory */ -__attribute__((__malloc__)) -__attribute__((__alloc_size__(2, 3))) +cx_attr_nonnull_arg(1) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc_ucx +cx_attr_allocsize(2, 3) void *cxCalloc( const CxAllocator *allocator, size_t nelem, size_t 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 - */ -__attribute__((__nonnull__)) -void cxFree( - const CxAllocator *allocator, - void *mem -); - #ifdef __cplusplus } // extern "C" #endif
--- a/ucx/cx/array_list.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/array_list.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,12 +26,11 @@ * 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 - * \copyright 2-Clause BSD License + * @file array_list.h + * @brief Array list implementation. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ @@ -45,30 +44,90 @@ #endif /** - * The maximum item size in an array list that fits into stack buffer when swapped. + * The maximum item size in an array list that fits into stack buffer + * when swapped. */ -extern unsigned cx_array_swap_sbo_size; +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_initialize() * @see cx_array_simple_add_sorted() * @see cx_array_simple_insert_sorted() */ -#define CX_ARRAY_DECLARE(type, name) \ - type * name; \ - size_t name##_size; \ - size_t name##_capacity +#define CX_ARRAY_DECLARE(type, name) CX_ARRAY_DECLARE_SIZED(type, name, size_t) /** - * Initializes an array declared with CX_ARRAY_DECLARE(). + * 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 stdlib malloc(). - * @param array the array + * @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; \ @@ -76,7 +135,36 @@ array = malloc(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 + * + * The memory for the array is allocated with stdlib malloc(). + * @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 { /** @@ -92,8 +180,11 @@ * @param capacity the new capacity (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(4) + cx_attr_allocsize(2, 3) void *(*realloc)( void *array, size_t capacity, @@ -120,125 +211,271 @@ }; /** + * Typedef for the array reallocator struct. + */ +typedef struct cx_array_reallocator_s CxArrayReallocator; + +/** * A default stdlib-based array reallocator. */ -extern struct cx_array_reallocator_s *cx_array_default_reallocator; +extern CxArrayReallocator *cx_array_default_reallocator; + +/** + * Creates a new array reallocator. + * + * When @p allocator is @c NULL, the stdlib default allocator 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 + */ +CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, + const void *stackmem +); /** - * Return codes for array functions. + * 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() */ -enum cx_array_result { - CX_ARRAY_SUCCESS, - CX_ARRAY_REALLOC_NOT_SUPPORTED, - CX_ARRAY_REALLOC_FAILED, -}; +cx_attr_nonnull_arg(1, 2, 3) +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 \p reallocator 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. + * + * 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 (reallocations - * will NOT be supported in that case) + * @param capacity a pointer to the capacity of the target array + * @param width the width in bytes for the @p size and @p capacity or zero for default * @param index the index where the copied elements shall be placed * @param src the source array * @param elem_size the size of one element * @param elem_count the number of elements to copy - * @param reallocator the array reallocator to use, or \c NULL - * if reallocation 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() */ -__attribute__((__nonnull__(1, 2, 5))) -enum cx_array_result cx_array_copy( +cx_attr_nonnull_arg(1, 2, 3, 6) +int cx_array_copy( void **target, - size_t *size, - size_t *capacity, + void *size, + void *capacity, + unsigned width, size_t index, const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + CxArrayReallocator *reallocator ); /** - * Convenience macro that uses cx_array_copy() with a default layout and the default reallocator. + * Convenience macro that uses cx_array_copy() with a default layout and + * the specified reallocator. * - * @param array the name of the array (NOT a pointer to the array) - * @param index the index where the copied elements shall be placed - * @param src the source array - * @param count the number of elements to copy + * @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_copy((void**)&(array), &(array##_size), &(array##_capacity), \ - index, src, sizeof((array)[0]), count, cx_array_default_reallocator) + 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() + */ +#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 containing - * \p size elements, already. The \p capacity must not be \c NULL and point a - * variable holding the current maximum number of elements the array can hold. + * 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, and the optional - * \p reallocator is not \c NULL, an attempt increase the \p capacity is made - * and the new capacity is written back. + * 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. * - * @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 - must not be \c NULL - * @param elem_size the size of one element - * @param elem a pointer to the element to add - * @param reallocator the array reallocator to use, or \c NULL if reallocation shall not happen - * @return zero on success, non-zero error code on failure + * 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, *(size), elem, elem_size, 1, 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 to the array) + * @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_copy(array, array##_size, &(elem), 1) - + 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. + * 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 target array's capacity + * @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 - * @return zero on success, non-zero error code on failure + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) -enum cx_array_result cx_array_insert_sorted( +cx_attr_nonnull_arg(1, 2, 3, 5) +int cx_array_insert_sorted( void **target, size_t *size, size_t *capacity, @@ -246,68 +483,112 @@ const void *src, size_t elem_size, size_t elem_count, - struct cx_array_reallocator_s *reallocator + 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. + * 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. * - * @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 - * @param elem_size the size of one element - * @param elem a pointer to the element to add - * @param reallocator the array reallocator to use - * @return zero on success, non-zero error code on failure + * 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 to the array) + * @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 the compare function for the elements + * @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_add_sorted(&array, &(array##_size), &(array##_capacity), \ - sizeof((array)[0]), &(elem), cmp_func, cx_array_default_reallocator) + 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 to the array) - * @param src pointer to the source array - * @param n number of elements in the source array - * @param cmp_func the compare function for the elements + * @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_insert_sorted((void**)(&array), &(array##_size), &(array##_capacity), \ - cmp_func, src, sizeof((array)[0]), n, cx_array_default_reallocator) - + 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. + * 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 + * 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 + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -315,9 +596,11 @@ * @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 + * @return the index of the largest lower bound, or @p size + * @see cx_array_binary_search_sup() + * @see cx_array_binary_search() */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -329,7 +612,7 @@ /** * Searches an item in a sorted array. * - * If the array is not sorted with respect to the \p cmp_func, the behavior + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -337,38 +620,31 @@ * @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 + * @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() */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search( +cx_attr_nonnull +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; - } -} +); /** * 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. + * 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 + * 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 + * If the array is not sorted with respect to the @p cmp_func, the behavior * is undefined. * * @param arr the array to search @@ -376,26 +652,18 @@ * @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 + * @return the index of the smallest upper bound, or @p size + * @see cx_array_binary_search_inf() + * @see cx_array_binary_search() */ -__attribute__((__nonnull__)) -static inline size_t cx_array_binary_search_sup( +cx_attr_nonnull +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; - } -} +); /** * Swaps two array elements. @@ -405,7 +673,7 @@ * @param idx1 index of first element * @param idx2 index of second element */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_array_swap( void *arr, size_t elem_size, @@ -414,21 +682,24 @@ ); /** - * Allocates an array list for storing elements with \p elem_size bytes each. + * Allocates an array list for storing elements with @p elem_size bytes each. * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation 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, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL, and the list is not storing pointers, sort and find + * (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) CxList *cxArrayListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -437,18 +708,18 @@ ); /** - * Allocates an array list for storing elements with \p elem_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 elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(). * - * @param elem_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(elem_size, initial_capacity) \
--- a/ucx/cx/buffer.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/buffer.h Sun Jan 05 22:00:39 2025 +0100 @@ -27,9 +27,9 @@ */ /** - * \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. @@ -38,9 +38,9 @@ * can be enabled. See the documentation of the macro constants for more * information. * - * \author Mike Becker - * \author Olaf Wintermann - * \copyright 2-Clause BSD License + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License */ #ifndef UCX_BUFFER_H @@ -49,7 +49,7 @@ #include "common.h" #include "allocator.h" -#ifdef __cplusplus +#ifdef __cplusplus extern "C" { #endif @@ -60,16 +60,90 @@ /** * 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 + +/** + * 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 alais 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 { /** @@ -83,6 +157,12 @@ }; /** The allocator to use for automatic memory management. */ 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. */ @@ -90,70 +170,52 @@ /** 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, a default stdlib allocator 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) int cxBufferInit( CxBuffer *buffer, void *space, @@ -163,25 +225,23 @@ ); /** - * 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, - const CxAllocator *allocator, - int flags +cx_attr_nonnull +int cxBufferEnableFlushing( + CxBuffer *buffer, + CxBufferFlushConfig config ); /** @@ -193,22 +253,58 @@ * @param buffer the buffer which contents shall be destroyed * @see cxBufferInit() */ -__attribute__((__nonnull__)) +cx_attr_nonnull 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__)) 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, a default stdlib 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 + */ +cx_attr_malloc +cx_attr_dealloc(cxBufferFree, 1) +cx_attr_nodiscard +CxBuffer *cxBufferCreate( + void *space, + size_t capacity, + const CxAllocator *allocator, + int flags +); + +/** * Shifts the contents of the buffer by the given offset. * * If the offset is positive, the contents are shifted to the right. @@ -219,7 +315,7 @@ * 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). @@ -227,11 +323,11 @@ * 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> @@ -239,9 +335,12 @@ * * @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 int cxBufferShift( CxBuffer *buffer, off_t shift @@ -253,10 +352,11 @@ * * @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 int cxBufferShiftRight( CxBuffer *buffer, size_t shift @@ -266,15 +366,13 @@ * 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 int cxBufferShiftLeft( CxBuffer *buffer, size_t shift @@ -284,23 +382,24 @@ /** * 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 int cxBufferSeek( CxBuffer *buffer, off_t offset, @@ -313,10 +412,13 @@ * 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 void cxBufferClear(CxBuffer *buffer); /** @@ -328,18 +430,20 @@ * @param buffer the buffer to be cleared * @see cxBufferClear() */ -__attribute__((__nonnull__)) +cx_attr_nonnull 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 - * byte of the buffer's contents. + * @retval true if the current buffer position has exceeded the last + * byte of the buffer's contents + * @retval false otherwise */ -__attribute__((__nonnull__)) -int cxBufferEof(const CxBuffer *buffer); +cx_attr_nonnull +cx_attr_nodiscard +bool cxBufferEof(const CxBuffer *buffer); /** @@ -349,9 +453,10 @@ * * @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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxBufferMinimumCapacity( CxBuffer *buffer, size_t capacity @@ -360,6 +465,10 @@ /** * 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 @@ -367,23 +476,23 @@ * 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 number of items that shall be written is larger than the buffer can hold, + * the first items from @c ptr are directly relayed to the flush target, if + * possible. + * The number returned by this function is only the number of elements from + * @c ptr that could be written to either the flush target or the buffer. * - * If automatic flushing is not enabled, the position of the buffer is increased - * by the number of bytes written. - * - * \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 size_t cxBufferWrite( const void *ptr, size_t size, @@ -392,19 +501,104 @@ ); /** + * 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 +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 +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 size_t cxBufferRead( void *ptr, size_t size, @@ -417,30 +611,52 @@ * * 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 int cxBufferPut( CxBuffer *buffer, int c ); /** + * Writes a terminating zero to a buffer at the current position. + * + * On successful write, @em neither the position @em nor the size of the buffer is + * increased. + * + * The purpose of this function is to have the written data ready to be used as + * a C string. + * + * @param buffer the buffer to write to + * @return zero, if the terminator could be written, non-zero otherwise + */ +cx_attr_nonnull +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) size_t cxBufferPutString( CxBuffer *buffer, const char *str @@ -452,9 +668,9 @@ * 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 int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus
--- a/ucx/cx/collection.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/collection.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file collection.h - * \brief Common definitions for various collection implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -96,13 +96,21 @@ /** * 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_BASE struct cx_collection_s collection /** * Sets a simple destructor function for this collection. * - * @param c the collection + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @param destr the destructor function */ #define cxDefineDestructor(c, destr) \ @@ -111,7 +119,7 @@ /** * Sets a simple destructor function for this collection. * - * @param c the collection + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @param destr the destructor function */ #define cxDefineAdvancedDestructor(c, destr, data) \ @@ -124,8 +132,11 @@ * 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)->collection.simple_destructor((c)->collection.store_pointer ? (*((void **) (e))) : (e)) @@ -136,8 +147,11 @@ * 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)->collection.advanced_destructor((c)->collection.destructor_data, \ @@ -150,8 +164,11 @@ * 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)->collection.simple_destructor) cx_invoke_simple_destructor(c,e); \
--- a/ucx/cx/common.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/common.h Sun Jan 05 22:00:39 2025 +0100 @@ -27,15 +27,15 @@ */ /** - * \file common.h + * @file common.h * - * \brief Common definitions and feature checks. + * @brief Common definitions and feature checks. * - * \author Mike Becker - * \author Olaf Wintermann - * \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> @@ -88,7 +88,9 @@ /** 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> @@ -96,7 +98,194 @@ #include <stdint.h> #include <sys/types.h> -#ifndef UCX_TEST_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 + +// --------------------------------------------------------------------------- +// Missing Defines +// --------------------------------------------------------------------------- + +#ifndef SSIZE_MAX // not defined in glibc since C23 and MSVC +#if CX_WORDSIZE == 64 +/** + * The maximum representable value in ssize_t. + */ +#define SSIZE_MAX 0x7fffffffffffffffll +#else +#define SSIZE_MAX 0x7fffffffl +#endif +#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__)) + +#ifndef __clang__ +/** + * 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__) + +#if __STDC_VERSION__ >= 202300L + +/** + * Do not warn about unused variable. + */ +#define cx_attr_unused [[maybe_unused]] + +/** + * Warn about discarded return value. + */ +#define cx_attr_nodiscard [[nodiscard]] + +#else // no C23 + +/** + * 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__)) + +#endif // __STDC_VERSION__ + +// --------------------------------------------------------------------------- +// Useful function pointers +// --------------------------------------------------------------------------- + /** * Function pointer compatible with fwrite-like functions. */ @@ -106,7 +295,6 @@ size_t, void * ); -#endif // UCX_TEST_H /** * Function pointer compatible with fread-like functions. @@ -118,25 +306,71 @@ void * ); +// --------------------------------------------------------------------------- +// Utility macros +// --------------------------------------------------------------------------- -// Compiler specific stuff +/** + * 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 cx_nmemb(arr) (sizeof(arr)/sizeof((arr)[0])) -#ifndef __GNUC__ +// --------------------------------------------------------------------------- +// szmul implementation +// --------------------------------------------------------------------------- + +#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 /** - * Removes GNU C attributes where they are not supported. + * 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 __attribute__(x) +#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 +int cx_szmul_impl(size_t a, size_t b, size_t *result); +#endif // cx_szmul + + +// --------------------------------------------------------------------------- +// Fixes for MSVC incompatibilities +// --------------------------------------------------------------------------- #ifdef _MSC_VER - // fix missing ssize_t definition #include <BaseTsd.h> typedef SSIZE_T ssize_t; // fix missing _Thread_local support #define _Thread_local __declspec(thread) - -#endif +#endif // _MSC_VER #endif // UCX_COMMON_H
--- a/ucx/cx/compare.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/compare.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file compare.h - * \brief A collection of simple compare functions. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -42,199 +42,485 @@ extern "C" { #endif -#ifndef CX_COMPARE_FUNC_DEFINED -#define CX_COMPARE_FUNC_DEFINED /** - * A comparator function comparing two collection elements. + * 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. */ -typedef int(*cx_compare_func)( - const void *left, - const void *right +cx_attr_nonnull +cx_attr_nodiscard +typedef int (*cx_compare_func)( + const void *left, + const void *right ); -#endif // CX_COMPARE_FUNC_DEFINED /** * 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 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_int(const void *i1, const void *i2); /** + * Compares two ints. + * + * @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 +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 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longint(const void *i1, const void *i2); /** + * Compares two long ints. + * + * @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 +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 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_longlong(const void *i1, const void *i2); /** + * Compares twolong long ints. + * + * @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 +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 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 + */ +cx_attr_nodiscard +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 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 + */ +cx_attr_nodiscard +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 */ +cx_attr_nonnull +cx_attr_nodiscard 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 +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 int cx_cmp_uint(const void *i1, const void *i2); /** + * Compares two unsigned ints. + * + * @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 + */ +cx_attr_nodiscard +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 int cx_cmp_ulongint(const void *i1, const void *i2); /** + * Compares two unsigned long ints. + * + * @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 + */ +cx_attr_nodiscard +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 */ +cx_attr_nonnull +cx_attr_nodiscard int cx_cmp_ulonglong(const void *i1, const void *i2); /** + * Compares two unsigned long long ints. + * + * @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 +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 */ +cx_attr_nonnull +cx_attr_nodiscard 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 +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 */ +cx_attr_nonnull +cx_attr_nodiscard 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 +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 */ +cx_attr_nonnull +cx_attr_nodiscard 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 +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 +int cx_cmp_float(const void *f1, const void *f2); -int cx_cmp_float(const void *f1, const void *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 +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( - const void *d1, - const void *d2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_double(const void *d1, const void *d2); + +/** + * Convenience function + * + * @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 +int cx_vcmp_double(double d1, double d2); /** * Compares the integer representation of two pointers. * + * @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*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @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( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +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 +int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** * Compares the unsigned integer representation of two pointers. * + * @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*) - * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal, - * 1 if *ptr1 is greater than *ptr2 + * @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( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +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 +int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); /** * Compares the pointers specified in the arguments without de-referencing. * * @param ptr1 pointer one * @param ptr2 pointer two - * @return -1 if ptr1 is less than ptr2, 0 if both are equal, - * 1 if ptr1 is greater than ptr2 + * @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_ptr( - const void *ptr1, - const void *ptr2 -); +cx_attr_nonnull +cx_attr_nodiscard +int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/hash_key.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/hash_key.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_key.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 */ @@ -38,6 +38,7 @@ #define UCX_HASH_KEY_H #include "common.h" +#include "string.h" #ifdef __cplusplus extern "C" { @@ -61,15 +62,20 @@ 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 void cx_hash_murmur(CxHashKey *key); /** @@ -80,7 +86,8 @@ * @param str the string * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_cstr_arg(1) CxHashKey cx_hash_key_str(const char *str); /** @@ -90,7 +97,8 @@ * @param len the length * @return the hash key */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_access_r(1, 2) CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -107,7 +115,8 @@ * @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) CxHashKey cx_hash_key( const void *obj, size_t len @@ -119,7 +128,18 @@ * @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"
--- a/ucx/cx/hash_map.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/hash_map.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file hash_map.h - * \brief Hash map implementation. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -67,21 +67,24 @@ /** * 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 elem_size is CX_STORE_POINTERS, the created map will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if * cxMapStorePointers() was called immediately after creation. * * @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, a default stdlib allocator 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) CxMap *cxHashMapCreate( const CxAllocator *allocator, size_t itemsize, @@ -91,23 +94,22 @@ /** * Creates a new hash map with a default number of buckets. * - * If \p elem_size is CX_STORE_POINTERS, the created map will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created map will be created as if * cxMapStorePointers() was called immediately after creation. * * @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. * @@ -120,9 +122,10 @@ * @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 int cxMapRehash(CxMap *map);
--- a/ucx/cx/iterator.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/iterator.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file iterator.h - * \brief Interface for iterator implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -38,11 +38,18 @@ #include "common.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Common data for all iterators. + */ struct cx_iterator_base_s { /** * True iff the iterator points to valid data. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull bool (*valid)(const void *); /** @@ -50,13 +57,15 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current)(const void *); /** * Original implementation in case the function needs to be wrapped. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull + cx_attr_nodiscard void *(*current_impl)(const void *); /** @@ -64,7 +73,7 @@ * * When valid returns false, the behavior of this function is undefined. */ - __attribute__ ((__nonnull__)) + cx_attr_nonnull void (*next)(void *); /** * Indicates whether this iterator may remove elements. @@ -86,6 +95,9 @@ * Internal iterator struct - use CxIterator. */ struct cx_iterator_s { + /** + * Inherited common data for all iterators. + */ CX_ITERATOR_BASE; /** @@ -141,7 +153,7 @@ /** * May contain the total number of elements, if known. - * Shall be set to \c SIZE_MAX when the total number is unknown during iteration. + * Shall be set to @c SIZE_MAX when the total number is unknown during iteration. */ size_t elem_count; }; @@ -154,7 +166,7 @@ * 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, - * any concurrent mutation of the collection other than by this iterator makes this iterator invalid + * 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; @@ -165,7 +177,8 @@ * 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)) @@ -176,6 +189,7 @@ * * @param iter the iterator * @return a pointer to the current element + * @see cxIteratorValid() */ #define cxIteratorCurrent(iter) (iter).base.current(&iter) @@ -189,6 +203,8 @@ /** * Flags the current element for removal, if this iterator is mutating. * + * Does nothing for non-mutating iterators. + * * @param iter the iterator */ #define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating @@ -199,11 +215,13 @@ * This is useful for APIs that expect some iterator as an argument. * * @param iter the iterator + * @return (@c CxIterator*) 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 @@ -215,16 +233,21 @@ /** * 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. + * 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 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() */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxIterator( const void *array, size_t elem_size, @@ -238,23 +261,23 @@ * 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 + * 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. + * 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. + * 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 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 + * @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 */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxIterator cxMutIterator( void *array, size_t elem_size, @@ -262,4 +285,48 @@ 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 +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 +CxIterator cxMutIteratorPtr( + void *array, + size_t elem_count, + bool remove_keeps_order +); + +#ifdef __cplusplus +} // extern "C" +#endif + #endif // UCX_ITERATOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/json.h Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,1357 @@ +/* + * 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, iff 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]; + + /** + * Used internally. + */ + bool tokenizer_escape; // TODO: check if it can be replaced with look-behind +}; + +/** + * 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. + */ + 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; +}; + +/** + * 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 +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 +CxJsonWriter cxJsonWriterPretty(bool use_spaces); + +/** + * Writes a JSON value to a buffer or stream. + * + * This function blocks until all data is written or an error when trying + * to write data 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) +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) +void cxJsonInit(CxJson *json, const CxAllocator *allocator); + +/** + * Destroys the json interface. + * + * @param json the json interface + * @see cxJsonInit() + */ +cx_attr_nonnull +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) +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 +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 +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 +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 +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) +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 +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 +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) +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) +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) +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) +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) +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) +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 +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 +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 +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 +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 +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) +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 +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 +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 + */ +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) +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 +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 +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 +CxIterator cxJsonObjIter(const CxJsonValue *value); + +/** + * @copydoc cxJsonObjGet() + */ +cx_attr_nonnull +cx_attr_returns_nonnull +CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); + +#ifdef __cplusplus +} // extern "C" + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { + return cx_json_obj_get_cxstr(value, name); +} + +CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { + return cx_json_obj_get_cxstr(value, cx_strcast(name)); +} + +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 */ +
--- a/ucx/cx/linked_list.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/linked_list.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,12 +26,11 @@ * 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 - * \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 @@ -46,24 +45,28 @@ /** * The maximum item size that uses SBO swap instead of relinking. + * */ -extern unsigned cx_linked_list_swap_sbo_size; +extern const unsigned cx_linked_list_swap_sbo_size; /** - * Allocates a linked list for storing elements with \p elem_size bytes each. + * Allocates a linked list for storing elements with @p elem_size bytes each. * - * If \p elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation 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, a default stdlib allocator will be used) * @param comparator the comparator for the elements - * (if \c NULL, and the list is not storing pointers, sort and find + * (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) CxList *cxLinkedListCreate( const CxAllocator *allocator, cx_compare_func comparator, @@ -71,18 +74,18 @@ ); /** - * Allocates a linked list for storing elements with \p elem_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 elem_size is CX_STORE_POINTERS, the created list will be created as if + * If @p elem_size is CX_STORE_POINTERS, the created list will be created as if * cxListStorePointers() was called immediately after creation and the compare * function will be automatically set to cx_cmp_ptr(). * - * @param elem_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(elem_size) \ cxLinkedListCreate(NULL, NULL, elem_size) @@ -91,11 +94,11 @@ * 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 @@ -104,7 +107,8 @@ * @param index the search index * @return the node found at the specified index */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard void *cx_linked_list_at( const void *start, size_t start_index, @@ -117,12 +121,12 @@ * * @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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find( const void *start, ptrdiff_t loc_advance, @@ -134,16 +138,16 @@ /** * Finds the node containing an element within a linked list. * - * @param result a pointer to the memory where the node pointer (or \c NULL if the element + * @param result a pointer to the memory where the node pointer (or @c NULL if the element * could not be found) shall be stored to * @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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull ssize_t cx_linked_list_find_node( void **result, const void *start, @@ -156,15 +160,16 @@ /** * 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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_first( const void *node, ptrdiff_t loc_prev @@ -173,15 +178,16 @@ /** * 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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_returns_nonnull void *cx_linked_list_last( const void *node, ptrdiff_t loc_next @@ -190,14 +196,14 @@ /** * 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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull void *cx_linked_list_prev( const void *begin, ptrdiff_t loc_next, @@ -208,15 +214,15 @@ * 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 */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_add( void **begin, void **end, @@ -229,15 +235,15 @@ * 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 */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) void cx_linked_list_prepend( void **begin, void **end, @@ -249,12 +255,12 @@ /** * 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) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_link( void *left, void *right, @@ -267,12 +273,12 @@ * * 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) */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_linked_list_unlink( void *left, void *right, @@ -284,17 +290,17 @@ * 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 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 */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert( void **begin, void **end, @@ -309,22 +315,22 @@ * 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) */ -__attribute__((__nonnull__(6))) +cx_attr_nonnull_arg(6) void cx_linked_list_insert_chain( void **begin, void **end, @@ -339,17 +345,17 @@ * 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 + * 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 begin node pointer (required) + * @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 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 */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted( void **begin, void **end, @@ -363,22 +369,22 @@ * 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. + * 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 + * @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 + * 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 begin node pointer (required) + * @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 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 */ -__attribute__((__nonnull__(1, 5, 6))) +cx_attr_nonnull_arg(1, 5, 6) void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -389,39 +395,72 @@ ); /** - * Removes a node from the linked list. + * Removes a chain of nodes 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 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) + * @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 + * to traverse to a former adjacent node in the list, or within the chain. * - * \remark The \c next and \c prev pointers of the removed node are not cleared by this function and may still be used + * @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) +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 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 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 */ -__attribute__((__nonnull__(5))) -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 -); - +) { + 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 */ size_t cx_linked_list_size( const void *node, @@ -432,25 +471,25 @@ * 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 */ -__attribute__((__nonnull__(1, 6))) +cx_attr_nonnull_arg(1, 6) void cx_linked_list_sort( void **begin, void **end, @@ -464,17 +503,17 @@ /** * 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. */ -__attribute__((__nonnull__(5))) +cx_attr_nonnull_arg(5) int cx_linked_list_compare( const void *begin_left, const void *begin_right, @@ -486,12 +525,12 @@ /** * 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) */ -__attribute__((__nonnull__(1))) +cx_attr_nonnull_arg(1) void cx_linked_list_reverse( void **begin, void **end,
--- a/ucx/cx/list.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/list.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file list.h - * \brief Interface for list implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -52,6 +52,9 @@ * Structure for holding the base data of a list. */ struct cx_list_s { + /** + * Common members for collections. + */ CX_COLLECTION_BASE; /** * The list class definition. @@ -71,9 +74,9 @@ * Destructor function. * * Implementations SHALL invoke the content destructor functions if provided - * and SHALL deallocate the list memory. + * 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. @@ -88,6 +91,7 @@ /** * 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)( @@ -118,11 +122,20 @@ ); /** - * 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 ); /** @@ -132,6 +145,7 @@ /** * Member function for swapping two elements. + * * @see cx_list_default_swap() */ int (*swap)( @@ -158,7 +172,8 @@ ); /** - * 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); @@ -166,8 +181,9 @@ /** * Optional member function for comparing this list * to another list of the same type. - * If set to \c NULL, comparison won't be optimized. + * If set to @c NULL, comparison won't be optimized. */ + cx_attr_nonnull int (*compare)( const struct cx_list_s *list, const struct cx_list_s *other @@ -202,7 +218,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_array( struct cx_list_s *list, size_t index, @@ -216,7 +232,7 @@ * This function uses the array insert function to insert consecutive groups * of sorted data. * - * The source data \em must already be sorted wrt. the list's compare function. + * The source data @em must already be sorted wrt. the list's compare function. * * Use this in your own list class if you do not want to implement an optimized * version for your list. @@ -226,7 +242,7 @@ * @param n the number of elements to insert * @return the number of elements actually inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull size_t cx_list_default_insert_sorted( struct cx_list_s *list, const void *sorted_data, @@ -244,7 +260,7 @@ * * @param list the list that shall be sorted */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_list_default_sort(struct cx_list_s *list); /** @@ -256,10 +272,11 @@ * @param list the list in which to swap * @param i index of one element * @param j index of the other element - * @return zero on success, non-zero when indices are out of bounds or memory + * @retval zero success + * @retval non-zero when indices are out of bounds or memory * allocation for the temporary buffer fails */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** @@ -276,7 +293,7 @@ * @param list the list * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStoreObjects(CxList *list); /** @@ -291,7 +308,7 @@ * @param list the list * @see cxListStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxListStorePointers(CxList *list); /** @@ -301,7 +318,7 @@ * @return true, if this list is storing pointers * @see cxListStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxListIsStoringPointers(const CxList *list) { return list->collection.store_pointer; } @@ -312,7 +329,7 @@ * @param list the list * @return the number of currently stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListSize(const CxList *list) { return list->collection.size; } @@ -322,10 +339,11 @@ * * @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() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListAdd( CxList *list, const void *elem @@ -339,9 +357,9 @@ * 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 @@ -349,7 +367,7 @@ * @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, const void *array, @@ -361,17 +379,17 @@ /** * 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() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsert( CxList *list, size_t index, @@ -385,9 +403,10 @@ * * @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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxListInsertSorted( CxList *list, const void *elem @@ -398,15 +417,15 @@ /** * 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 @@ -415,7 +434,7 @@ * @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, @@ -432,9 +451,9 @@ * 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. + * 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 @@ -442,7 +461,7 @@ * @param n the number of elements to add * @return the number of added elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxListInsertSortedArray( CxList *list, const void *array, @@ -457,16 +476,17 @@ * 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( CxIterator *iter, const void *elem @@ -480,16 +500,17 @@ * 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( CxIterator *iter, const void *elem @@ -505,25 +526,95 @@ * * @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 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. + * + * @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 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 element 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. + * + * @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 an element destructor function is specified, it is called for each + * 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->cl->clear(list); } @@ -537,9 +628,11 @@ * @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, @@ -553,11 +646,11 @@ * * @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); @@ -574,7 +667,8 @@ * @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( const CxList *list, size_t index @@ -593,7 +687,8 @@ * @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( const CxList *list, size_t index @@ -612,7 +707,8 @@ * @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 CxIterator cxListMutIteratorAt( CxList *list, size_t index @@ -630,7 +726,8 @@ * @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 CxIterator cxListMutBackwardsIteratorAt( CxList *list, size_t index @@ -646,7 +743,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListIterator(const CxList *list) { return list->cl->iterator(list, 0, false); } @@ -661,7 +759,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutIterator(CxList *list) { return cxListMutIteratorAt(list, 0); } @@ -677,7 +776,8 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListBackwardsIterator(const CxList *list) { return list->cl->iterator(list, list->collection.size - 1, true); } @@ -692,13 +792,14 @@ * @param list the list * @return a new iterator */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxListMutBackwardsIterator(CxList *list) { 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. * @@ -707,7 +808,8 @@ * @return the index of the element or a negative * value when the element is not found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline ssize_t cxListFind( const CxList *list, const void *elem @@ -716,7 +818,7 @@ } /** - * Removes and returns the index of the first element that equals \p elem. + * Removes and returns the index of the first element that equals @p elem. * * Determining equality is performed by the list's comparator function. * @@ -725,7 +827,7 @@ * @return the index of the now removed element or a negative * value when the element is not found or could not be removed */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline ssize_t cxListFindRemove( CxList *list, const void *elem @@ -734,13 +836,13 @@ } /** - * Sorts the list in-place. + * Sorts the list. * - * \remark The underlying sort algorithm is implementation defined. + * @remark The underlying sort algorithm is implementation defined. * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListSort(CxList *list) { list->cl->sort(list); } @@ -750,7 +852,7 @@ * * @param list the list */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxListReverse(CxList *list) { list->cl->reverse(list); } @@ -763,10 +865,14 @@ * * @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 int cxListCompare( const CxList *list, const CxList *other @@ -775,22 +881,21 @@ /** * 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. + * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be destroyed + * @param list the list which shall be freed */ -__attribute__((__nonnull__)) -void cxListDestroy(CxList *list); +void cxListFree(CxList *list); /** * A shared instance of an empty list. * - * Writing to that list is undefined. + * 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. */ -extern CxList * const cxEmptyList; +extern CxList *const cxEmptyList; #ifdef __cplusplus
--- a/ucx/cx/map.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/map.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file map.h - * \brief Interface for map implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -89,19 +89,16 @@ /** * 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, @@ -111,7 +108,6 @@ /** * Returns an element. */ - __attribute__((__nonnull__, __warn_unused_result__)) void *(*get)( const CxMap *map, CxHashKey key @@ -119,18 +115,23 @@ /** * 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)(const CxMap *map, enum cx_map_iterator_type type); }; @@ -151,7 +152,10 @@ /** * A shared instance of an empty map. * - * Writing to that map is undefined. + * Writing to that map is not allowed. + * + * You can use this is a placeholder for initializing CxMap pointers + * for which you do not want to reserve memory right from the beginning. */ extern CxMap *const cxEmptyMap; @@ -164,7 +168,7 @@ * @param map the map * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStoreObjects(CxMap *map) { map->collection.store_pointer = false; } @@ -181,7 +185,7 @@ * @param map the map * @see cxMapStoreObjects() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxMapStorePointers(CxMap *map) { map->collection.store_pointer = true; map->collection.elem_size = sizeof(void *); @@ -194,7 +198,7 @@ * @return true, if this map is storing pointers * @see cxMapStorePointers() */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline bool cxMapIsStoringPointers(const CxMap *map) { return map->collection.store_pointer; } @@ -202,20 +206,21 @@ /** * Deallocates the memory of the specified map. * - * @param map the map to be destroyed + * Also calls the content destructor functions for each element, if specified. + * + * @param map the map to be freed */ -__attribute__((__nonnull__)) -static inline void cxMapDestroy(CxMap *map) { - map->cl->destructor(map); -} +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); } @@ -226,24 +231,22 @@ * @param map the map * @return the number of stored elements */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxMapSize(const CxMap *map) { return map->collection.size; } - -// TODO: set-like map operations (union, intersect, difference) - /** * Creates a value iterator for a map. * - * \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 values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorValues(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); } @@ -253,13 +256,14 @@ * * The elements of the iterator are keys of type 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__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIteratorKeys(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } @@ -269,7 +273,7 @@ * * The elements of the iterator are key/value pairs of type 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 @@ -277,7 +281,8 @@ * @see cxMapIteratorKeys() * @see cxMapIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard static inline CxIterator cxMapIterator(const CxMap *map) { return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); } @@ -286,13 +291,14 @@ /** * Creates a mutating iterator over the values of a map. * - * \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 values */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorValues(CxMap *map); /** @@ -300,13 +306,14 @@ * * The elements of the iterator are keys of type 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__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator cxMapMutIteratorKeys(CxMap *map); /** @@ -314,7 +321,7 @@ * * The elements of the iterator are key/value pairs of type 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 @@ -322,21 +329,13 @@ * @see cxMapMutIteratorKeys() * @see cxMapMutIteratorValues() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull +cx_attr_nodiscard CxIterator 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, @@ -345,16 +344,7 @@ 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, @@ -363,15 +353,7 @@ 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, @@ -380,15 +362,8 @@ 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, const char *key, @@ -397,14 +372,8 @@ 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( const CxMap *map, CxHashKey const &key @@ -412,14 +381,8 @@ 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( const CxMap *map, cxstring const &key @@ -427,14 +390,8 @@ 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( const CxMap *map, cxmutstr const &key @@ -442,14 +399,9 @@ 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( const CxMap *map, const char *key @@ -457,301 +409,86 @@ 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); -} - -/** - * 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( - CxMap *map, - cxstring const &key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); -} - -/** - * 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( - CxMap *map, - cxmutstr const &key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(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( - CxMap *map, - const char *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); -} - -/** - * 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, - 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( +cx_attr_nonnull +static inline int cxMapRemove( CxMap *map, cxstring const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(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 +static inline int cxMapRemove( CxMap *map, cxmutstr const &key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(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_cstr_arg(2) +static inline int cxMapRemove( CxMap *map, const char *key ) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + return map->cl->remove(map, cx_hash_key_str(key), nullptr); } -/** - * 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, - CxHashKey key + CxHashKey key, + void *targetbuf ) { - return map->cl->remove(map, key, !map->collection.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->collection.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->collection.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, - const char *key + const char *key, + void *targetbuf ) { - return map->cl->remove(map, cx_hash_key_str(key), !map->collection.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, @@ -761,14 +498,9 @@ } /** - * 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, @@ -778,14 +510,9 @@ } /** - * 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, @@ -795,14 +522,10 @@ } /** - * 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, const char *key, @@ -814,10 +537,19 @@ /** * 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 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, \ @@ -828,13 +560,10 @@ (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( const CxMap *map, CxHashKey key @@ -843,13 +572,10 @@ } /** - * 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( const CxMap *map, cxstring key @@ -858,13 +584,10 @@ } /** - * 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( const CxMap *map, cxmutstr key @@ -873,13 +596,11 @@ } /** - * 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( const CxMap *map, const char *key @@ -890,9 +611,13 @@ /** * 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, \ @@ -903,74 +628,61 @@ (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); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +static inline int cx_map_remove_cxstr( + CxMap *map, + cxstring key +) { + 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_cxstr( +cx_attr_nonnull +static inline int cx_map_remove_mustr( CxMap *map, - cxstring key + cxmutstr key ) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); + return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); +} + +/** + * @copydoc cxMapRemove() + */ +cx_attr_nonnull +cx_attr_cstr_arg(2) +static inline int cx_map_remove_str( + CxMap *map, + const char *key +) { + return map->cl->remove(map, cx_hash_key_str(key), NULL); } /** * Removes a key/value-pair from the map by using the key. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - (void) map->cl->remove(map, cx_hash_key_cxstr(key), true); -} - -/** - * Removes a key/value-pair from the map by using the key. + * Always invokes the destructors functions, if any, on the removed element. * - * @param map the map - * @param key the key - */ -__attribute__((__nonnull__)) -static inline void cx_map_remove_str( - CxMap *map, - const char *key -) { - (void) map->cl->remove(map, cx_hash_key_str(key), true); -} - -/** - * 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 + * @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, \ @@ -981,177 +693,85 @@ (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 + * @copydoc cxMapRemoveAndGet() */ -__attribute__((__nonnull__)) -static inline void cx_map_detach_cxstr( +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get( CxMap *map, - cxstring key + CxHashKey key, + void *targetbuf ) { - (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); + return map->cl->remove(map, key, targetbuf); } /** - * 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_cxstr( CxMap *map, - const char *key + cxstring key, + void *targetbuf ) { - (void) map->cl->remove(map, cx_hash_key_str(key), false); + return map->cl->remove(map, cx_hash_key_cxstr(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() + * @copydoc 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, \ - const char*: cx_map_detach_str) \ - (map, key) +cx_attr_nonnull +cx_attr_access_w(3) +static inline int cx_map_remove_and_get_mustr( + CxMap *map, + cxmutstr key, + void *targetbuf +) { + 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( +cx_attr_nonnull +cx_attr_access_w(3) +cx_attr_cstr_arg(2) +static inline int cx_map_remove_and_get_str( CxMap *map, - CxHashKey key + const char *key, + void *targetbuf ) { - return map->cl->remove(map, key, !map->collection.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_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.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 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. * - * @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_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), !map->collection.store_pointer); -} - -/** - * Removes a key/value-pair from the map by using the key. + * If this map is storing pointers, the element is the pointer itself + * and not the object it points to. * - * @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 + * @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 + * + * @see cxMapStorePointers() + * @see cxMapRemove() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline void *cx_map_remove_and_get_str( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), !map->collection.store_pointer); -} - -/** - * 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() - */ -#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, \ const char*: cx_map_remove_and_get_str) \ - (map, key) + (map, key, targetbuf) #endif // __cplusplus
--- a/ucx/cx/mempool.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/mempool.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file mempool.h - * \brief Interface for memory pool implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -76,35 +76,33 @@ typedef struct cx_mempool_s CxMempool; /** + * Deallocates a memory pool and frees the managed memory. + * + * @param pool the memory pool to free + */ +void cxMempoolFree(CxMempool *pool); + +/** * Creates an array-based memory pool with a shared destructor function. * * This destructor MUST NOT free the memory. * * @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 destr optional destructor function to use for allocated memory + * @return the created memory pool or @c NULL if allocation failed */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxMempoolFree, 1) CxMempool *cxMempoolCreate(size_t capacity, cx_destructor_func destr); /** * 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 + * @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); -} - -/** - * Destroys a memory pool and frees the managed memory. - * - * @param pool the memory pool to destroy - */ -__attribute__((__nonnull__)) -void cxMempoolDestroy(CxMempool *pool); +#define cxBasicMempoolCreate(capacity) cxMempoolCreate(capacity, NULL) /** * Sets the destructor function for a specific allocated memory object. @@ -115,13 +113,24 @@ * @param memory the object allocated in the pool * @param fnc the destructor function */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cxMempoolSetDestructor( void *memory, cx_destructor_func fnc ); /** + * Removes the destructor function for a specific allocated memory object. + * + * 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 + */ +cx_attr_nonnull +void cxMempoolRemoveDestructor(void *memory); + +/** * Registers foreign memory with this pool. * * The destructor, in contrast to memory allocated by the pool, MUST free the memory. @@ -130,11 +139,12 @@ * If that allocation fails, this function will return non-zero. * * @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 int cxMempoolRegister( CxMempool *pool, void *memory,
--- a/ucx/cx/printf.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/printf.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file printf.h - * \brief Wrapper for write functions with a printf-like interface. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -40,6 +40,14 @@ #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 @@ -48,19 +56,21 @@ /** * The maximum string length that fits into stack memory. */ -extern unsigned const cx_printf_sbo_size; +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) int cx_fprintf( void *stream, cx_write_func wfc, @@ -69,17 +79,18 @@ ); /** - * 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) int cx_vfprintf( void *stream, cx_write_func wfc, @@ -88,10 +99,12 @@ ); /** - * 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 @@ -99,7 +112,9 @@ * @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) cxmutstr cx_asprintf_a( const CxAllocator *allocator, const char *fmt, @@ -107,24 +122,28 @@ ); /** - * 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 @@ -132,7 +151,8 @@ * @return the formatted string * @see cx_asprintf_a() */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_cstr_arg(2) cxmutstr cx_vasprintf_a( const CxAllocator *allocator, const char *fmt, @@ -140,192 +160,232 @@ ); /** -* 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 a pointer to the buffer the data is written to - * @param fmt the format string + * @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 the total number of bytes written - * @see ucx_fprintf() + * @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((CxBuffer*)buffer, \ +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__) /** - * An \c sprintf like function which reallocates the string when the buffer is not large enough. + * 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. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * @param str a pointer to the string buffer - * @param len a pointer to the length of the buffer - * @param fmt the format string + * @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 the length of produced string + * @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. + * 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. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * \attention The original buffer MUST have been allocated with the same allocator! + * @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 ... additional arguments - * @return the length of produced string + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 3, 4), __format__(printf, 4, 5))) -int cx_sprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, ... ); +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_printf(4, 5) +cx_attr_cstr_arg(4) +int cx_sprintf_a( + 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. + * 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. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string, if successful, is guaranteed to be zero-terminated. * - * @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 + * @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. + * 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. + * The size of the buffer will be updated in @p len when necessary. * - * \note The resulting string is guaranteed to be zero-terminated. + * @note The resulting string is guaranteed to be zero-terminated. * - * \attention The original buffer MUST have been allocated with the same allocator! + * @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 + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__)) -int cx_vsprintf_a(CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(4) +cx_attr_access_rw(2) +cx_attr_access_rw(3) +int cx_vsprintf_a( + 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. + * 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 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. + * 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. + * @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. + * @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 a pointer to the buffer - * @param len a pointer to the length of the buffer - * @param str a pointer to the location - * @param fmt the format string + * @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 the length of produced string + * @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. + * 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 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. + * 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. + * @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. + * @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 to the location + * @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 + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__(1, 2, 4, 5), __format__(printf, 5, 6))) -int cx_sprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ... ); +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) +int cx_sprintf_sa( + 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. + * 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 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. + * 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. + * @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. + * @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 a pointer to the buffer - * @param len a pointer to the length of the buffer - * @param str a pointer to the location - * @param fmt the format string - * @param ap argument list - * @return the length of produced string + * @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. + * 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 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. + * 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. + * @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. + * @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 to the location + * @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 + * @return the length of produced string or an error code from stdlib printf implementation */ -__attribute__((__nonnull__)) -int cx_vsprintf_sa(CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap); +cx_attr_nonnull +cx_attr_cstr_arg(5) +int cx_vsprintf_sa( + CxAllocator *alloc, + char *buf, + size_t *len, + char **str, + const char *fmt, + va_list ap +); #ifdef __cplusplus
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/properties.h Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,644 @@ +/* + * 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 character, when appearing at the end of a line, continues that line. + * This is '\' by default. + */ + // char continuation; // TODO: line continuation in properties + + /** + * 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; +}; + +/** + * Typedef for the properties config. + */ +typedef struct cx_properties_config_s CxPropertiesConfig; + +/** + * Default properties configuration. + */ +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 +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 +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) +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 +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 +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 strings allocated + * by #cx_strdup_a(). + * The default stdlib allocator will be used, unless you specify a custom + * allocator in the optional @c data of the sink. + * + * @param map the map that shall consume the k/v-pairs. + * @return the sink + * @see cxPropertiesLoad() + */ +cx_attr_nonnull +cx_attr_nodiscard +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 +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) +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) +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) +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. + * 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_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 +CxPropertiesStatus cxPropertiesLoad( + CxProperties *prop, + CxPropertiesSink sink, + CxPropertiesSource source +); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_PROPERTIES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/streams.h Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,135 @@ +/* + * 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 streams.h + * + * @brief Utility functions for data streams. + * + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_STREAMS_H +#define UCX_STREAMS_H + +#include "common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * 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 + * 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 n the maximum number of bytes that shall be copied. + * If this is larger than @p bufsize, the content is copied over multiple + * iterations. + * @return the total number of bytes copied + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +cx_attr_access_r(1) +cx_attr_access_w(2) +cx_attr_access_w(5) +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * @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 (@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) \ + cx_stream_bncopy(src, dest, rfnc, wfnc, buf, bufsize, SIZE_MAX) + +/** + * Reads data from a stream and writes it to another stream. + * + * 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 n the maximum number of bytes that shall be copied. + * @return total number of bytes copied + */ +cx_attr_nonnull +cx_attr_access_r(1) +cx_attr_access_w(2) +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +); + +/** + * Reads data from a stream and writes it to another stream. + * + * The data is temporarily stored in a stack allocated 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 + * @return total number of bytes copied + */ +#define cx_stream_copy(src, dest, rfnc, wfnc) \ + cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX) + +#ifdef __cplusplus +} +#endif + +#endif // UCX_STREAMS_H
--- a/ucx/cx/string.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/string.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file string.h - * \brief Strings that know their length. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -42,7 +42,7 @@ /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ -extern unsigned const cx_strstr_sbo_size; +extern const unsigned cx_strstr_sbo_size; /** * The UCX string structure. @@ -50,7 +50,7 @@ struct cx_mutstr_s { /** * A pointer to the string. - * \note The string is not necessarily \c NULL terminated. + * @note The string is not necessarily @c NULL terminated. * Always use the length. */ char *ptr; @@ -69,7 +69,7 @@ struct cx_string_s { /** * A pointer to the immutable string. - * \note The string is not necessarily \c NULL terminated. + * @note The string is not necessarily @c NULL terminated. * Always use the length. */ const char *ptr; @@ -148,7 +148,7 @@ /** * 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 */ @@ -160,9 +160,9 @@ /** * 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(). * - * \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(). @@ -172,26 +172,29 @@ * * @see cx_mutstrn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) 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) cxmutstr cx_mutstrn( char *cstring, size_t length @@ -200,9 +203,9 @@ /** * 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. + * @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(). @@ -212,72 +215,112 @@ * * @see cx_strn() */ -__attribute__((__warn_unused_result__, __nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard +cx_attr_cstr_arg(1) 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) cxstring cx_strn( 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; +} +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; +} + /** * 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) \ + (str) +#endif /** - * Passes the pointer in this string to \c free(). + * Passes the pointer in this string to @c free(). * - * 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. * - * \note There is no implementation for cxstring, because it is unlikely that + * @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__)) 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. * - * \note There is no implementation for cxstring, because it is unlikely that + * @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) void cx_strfree_a( const CxAllocator *alloc, cxmutstr *str @@ -285,15 +328,17 @@ /** * 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 size_t cx_strlen( size_t count, ... @@ -303,21 +348,26 @@ * 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 cxmutstr cx_strcat_ma( const CxAllocator *alloc, cxmutstr str, @@ -329,15 +379,19 @@ * 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. * - * \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 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__) @@ -345,15 +399,19 @@ /** * 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 standard @c malloc(). + * 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__) @@ -361,19 +419,23 @@ /** * 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 standard @c malloc(). + * 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__) @@ -381,19 +443,19 @@ /** * 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 cxstring cx_strsubs( cxstring string, size_t start @@ -402,23 +464,23 @@ /** * 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 cxstring cx_strsubsl( cxstring string, size_t start, @@ -428,19 +490,19 @@ /** * 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 cxmutstr cx_strsubs_m( cxmutstr string, size_t start @@ -449,23 +511,23 @@ /** * 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 cxmutstr cx_strsubsl_m( cxmutstr string, size_t start, @@ -480,11 +542,11 @@ * * @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 cxstring cx_strchr( cxstring string, int chr @@ -498,11 +560,11 @@ * * @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 cxmutstr cx_strchr_m( cxmutstr string, int chr @@ -516,11 +578,11 @@ * * @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 cxstring cx_strrchr( cxstring string, int chr @@ -534,11 +596,11 @@ * * @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 cxmutstr cx_strrchr_m( cxmutstr string, int chr @@ -548,19 +610,19 @@ * 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 cxstring cx_strstr( cxstring haystack, cxstring needle @@ -570,19 +632,19 @@ * 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 cxmutstr cx_strstr_m( cxmutstr haystack, cxstring needle @@ -591,16 +653,18 @@ /** * 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 pre-allocated 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) size_t cx_strsplit( cxstring string, cxstring delim, @@ -611,13 +675,13 @@ /** * 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 @@ -627,7 +691,9 @@ * 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) size_t cx_strsplit_a( const CxAllocator *allocator, cxstring string, @@ -640,16 +706,18 @@ /** * 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 pre-allocated 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) size_t cx_strsplit_m( cxmutstr string, cxstring delim, @@ -660,13 +728,13 @@ /** * 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 @@ -676,7 +744,9 @@ * 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) size_t cx_strsplit_ma( const CxAllocator *allocator, cxmutstr string, @@ -690,10 +760,10 @@ * * @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 int cx_strcmp( cxstring s1, cxstring s2 @@ -704,10 +774,10 @@ * * @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 int cx_strcasecmp( cxstring s1, cxstring s2 @@ -720,10 +790,11 @@ * * @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 int cx_strcmp_p( const void *s1, const void *s2 @@ -736,10 +807,11 @@ * * @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 int cx_strcasecmp_p( const void *s1, const void *s2 @@ -749,16 +821,17 @@ /** * 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__)) +cx_attr_nodiscard +cx_attr_nonnull cxmutstr cx_strdup_a( const CxAllocator *allocator, cxstring string @@ -768,12 +841,12 @@ * 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(). + * @c malloc(). 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 + * @param string (@c cxstring) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_a() */ #define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string) @@ -782,13 +855,13 @@ /** * 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 + * @param allocator (@c CxAllocator*) the allocator to use + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_m() */ #define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string)) @@ -797,12 +870,12 @@ * 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(). + * @c malloc(). 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 + * @param string (@c cxmutstr) the string to duplicate + * @return (@c cxmutstr) a duplicate of the string * @see cx_strdup_ma() */ #define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string)) @@ -810,25 +883,25 @@ /** * 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 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 cxmutstr cx_strtrim_m(cxmutstr string); /** @@ -836,10 +909,10 @@ * * @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 bool cx_strprefix( cxstring string, cxstring prefix @@ -850,10 +923,10 @@ * * @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 bool cx_strsuffix( cxstring string, cxstring suffix @@ -864,10 +937,10 @@ * * @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 bool cx_strcaseprefix( cxstring string, cxstring prefix @@ -878,10 +951,10 @@ * * @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 bool cx_strcasesuffix( cxstring string, cxstring suffix @@ -911,9 +984,9 @@ * 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 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, @@ -926,7 +999,8 @@ * @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 cxmutstr cx_strreplacen_a( const CxAllocator *allocator, cxstring str, @@ -939,19 +1013,19 @@ * 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 at most @p replmax occurrences. * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() 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 pattern (@c cxstring) the pattern 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) @@ -961,17 +1035,17 @@ * * The pattern is taken literally and is no regular expression. * - * 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 pattern (@c cxstring) the pattern 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) @@ -980,18 +1054,18 @@ * 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 at most @p replmax occurrences. * - * The returned string will be allocated by \c malloc() and is guaranteed + * The returned string will be allocated by @c malloc() 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 pattern (@c cxstring) the pattern 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) @@ -1004,7 +1078,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok( cxstring str, cxstring delim, @@ -1019,7 +1093,7 @@ * @param limit the maximum number of tokens that shall be returned * @return a new string tokenization context */ -__attribute__((__warn_unused_result__)) +cx_attr_nodiscard CxStrtokCtx cx_strtok_m( cxmutstr str, cxstring delim, @@ -1036,7 +1110,9 @@ * @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) bool cx_strtok_next( CxStrtokCtx *ctx, cxstring *token @@ -1054,7 +1130,9 @@ * @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) bool cx_strtok_next_m( CxStrtokCtx *ctx, cxmutstr *token @@ -1067,13 +1145,407 @@ * @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) void cx_strtok_delim( CxStrtokCtx *ctx, const cxstring *delim, size_t count ); +/* ------------------------------------------------------------------------- * + * string to number conversion functions * + * ------------------------------------------------------------------------- */ + +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtos_lc(cxstring str, short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi_lc(cxstring str, int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtol_lc(cxstring str, long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoll_lc(cxstring str, long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi8_lc(cxstring str, int8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi16_lc(cxstring str, int16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi32_lc(cxstring str, int32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoi64_lc(cxstring str, int64_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtous_lc(cxstring str, unsigned short *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou_lc(cxstring str, unsigned int *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoul_lc(cxstring str, unsigned long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtoull_lc(cxstring str, unsigned long long *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou8_lc(cxstring str, uint8_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou16_lc(cxstring str, uint16_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +int cx_strtou32_lc(cxstring str, uint32_t *output, int base, const char *groupsep); +/** + * @copydoc cx_strtouz_lc() + */ +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +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) +int cx_strtouz_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. + * + * 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 + * @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) +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. + * + * 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 + * @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) +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep); + +#ifndef CX_STR_IMPLEMENTATION +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtos_lc(str, output, base, groupsep) cx_strtos_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi_lc(str, output, base, groupsep) cx_strtoi_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtol_lc(str, output, base, groupsep) cx_strtol_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoll_lc(str, output, base, groupsep) cx_strtoll_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi8_lc(str, output, base, groupsep) cx_strtoi8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi16_lc(str, output, base, groupsep) cx_strtoi16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi32_lc(str, output, base, groupsep) cx_strtoi32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoi64_lc(str, output, base, groupsep) cx_strtoi64_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoz_lc(str, output, base, groupsep) cx_strtoz_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtous_lc(str, output, base, groupsep) cx_strtous_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou_lc(str, output, base, groupsep) cx_strtou_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoul_lc(str, output, base, groupsep) cx_strtoul_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtoull_lc(str, output, base, groupsep) cx_strtoull_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou8_lc(str, output, base, groupsep) cx_strtou8_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou16_lc(str, output, base, groupsep) cx_strtou16_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#define cx_strtou32_lc(str, output, base, groupsep) cx_strtou32_lc(cx_strcast(str), output, base, groupsep) +/** + * @copydoc cx_strtouz_lc() + */ +#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 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_strtouz_lc(str, output, base, groupsep) cx_strtouz_lc(cx_strcast(str), output, base, groupsep) + +/** + * @copydoc cx_strtouz() + */ +#define cx_strtos(str, output, base) cx_strtos_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi(str, output, base) cx_strtoi_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtol(str, output, base) cx_strtol_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoll(str, output, base) cx_strtoll_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi8(str, output, base) cx_strtoi8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi16(str, output, base) cx_strtoi16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi32(str, output, base) cx_strtoi32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoi64(str, output, base) cx_strtoi64_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoz(str, output, base) cx_strtoz_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtous(str, output, base) cx_strtous_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou(str, output, base) cx_strtou_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoul(str, output, base) cx_strtoul_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtoull(str, output, base) cx_strtoull_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou8(str, output, base) cx_strtou8_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou16(str, output, base) cx_strtou16_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou32(str, output, base) cx_strtou32_lc(str, output, base, ",") +/** + * @copydoc cx_strtouz() + */ +#define cx_strtou64(str, output, base) cx_strtou64_lc(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_strtouz_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_strtouz(str, output, base) cx_strtouz_lc(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. + * + * 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 + * @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. + * + * 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 + * @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(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(str, output, '.', ",") + +#endif #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/test.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/test.h Sun Jan 05 22:00:39 2025 +0100 @@ -35,13 +35,13 @@ * * **** IN HEADER FILE: **** * - * <pre> + * <code> * CX_TEST(function_name); * CX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional - * </pre> + * </code> * * **** IN SOURCE FILE: **** - * <pre> + * <code> * CX_TEST_SUBROUTINE(subroutine_name, paramlist) { * // tests with CX_TEST_ASSERT() * } @@ -54,7 +54,7 @@ * } * // cleanup of memory here * } - * </pre> + * </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(). @@ -67,7 +67,8 @@ #ifndef UCX_TEST_H #define UCX_TEST_H -#include <stdlib.h> +#include "common.h" + #include <stdio.h> #include <string.h> #include <setjmp.h> @@ -86,23 +87,10 @@ #define __FUNCTION__ __func__ #endif -// #if !defined(__clang__) && __GNUC__ > 3 #pragma GCC diagnostic ignored "-Wclobbered" #endif -#ifndef UCX_COMMON_H -/** - * Function pointer compatible with fwrite-like functions. - */ -typedef size_t (*cx_write_func)( - const void *, - size_t, - size_t, - void * -); -#endif // UCX_COMMON_H - /** Type for the CxTestSuite. */ typedef struct CxTestSuite CxTestSuite; @@ -148,6 +136,10 @@ * @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) { @@ -161,10 +153,12 @@ } /** - * Destroys a test suite. - * @param suite the test suite to destroy + * 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; @@ -179,8 +173,10 @@ * * @param suite the suite, the test function shall be added to * @param test the test function to register - * @return zero on success or non-zero on failure + * @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) { @@ -205,8 +201,9 @@ * 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 + * @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) { @@ -233,14 +230,14 @@ /** * Runs a test suite and writes the test log to the specified FILE stream. - * @param suite the test suite to run - * @param file the target file to write the output to + * @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 the test suite to run + * @param suite (@c CxTestSuite*) the test suite to run */ #define cx_test_run_stdout(suite) cx_test_run_f(suite, stdout) @@ -255,6 +252,17 @@ /** * 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. */ @@ -272,8 +280,8 @@ * 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 the condition to check - * @param message the message that shall be printed out on failure + * @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; \ @@ -288,7 +296,7 @@ * 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 the condition to check + * @param condition (@c bool) the condition to check */ #define CX_TEST_ASSERT(condition) CX_TEST_ASSERTM(condition, #condition " failed")
--- a/ucx/cx/tree.h Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/cx/tree.h Sun Jan 05 22:00:39 2025 +0100 @@ -26,11 +26,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ /** - * \file tree.h - * \brief Interface for tree implementations. - * \author Mike Becker - * \author Olaf Wintermann - * \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 @@ -138,7 +138,7 @@ */ size_t depth; /** - * The next element in the queue or \c NULL. + * The next element in the queue or @c NULL. */ struct cx_tree_visitor_queue_s *next; }; @@ -209,7 +209,7 @@ * Releases internal memory of the given tree iterator. * @param iter the iterator */ - __attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { free(iter->stack); iter->stack = NULL; @@ -219,7 +219,7 @@ * Releases internal memory of the given tree visitor. * @param visitor the visitor */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { struct cx_tree_visitor_queue_s *q = visitor->queue_next; while (q != NULL) { @@ -233,7 +233,7 @@ * Advises the iterator to skip the subtree below the current node and * also continues the current loop. * - * @param iterator the iterator + * @param iterator (@c CxTreeIterator) the iterator */ #define cxTreeIteratorContinue(iterator) (iterator).skip = true; continue @@ -241,7 +241,7 @@ * Advises the visitor to skip the subtree below the current node and * also continues the current loop. * - * @param visitor the visitor + * @param visitor (@c CxTreeVisitor) the visitor */ #define cxTreeVisitorContinue(visitor) cxTreeIteratorContinue(visitor) @@ -249,7 +249,7 @@ * 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 + * If the parent has children already, the node is @em appended to the list * of all currently existing children. * * @param parent the parent node @@ -258,14 +258,14 @@ * @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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_link( - void *restrict parent, - void *restrict node, + void *parent, + void *node, ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, @@ -283,11 +283,11 @@ * @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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__)) +cx_attr_nonnull void cx_tree_unlink( void *node, ptrdiff_t loc_parent, @@ -298,14 +298,21 @@ ); /** + * 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 + * 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 @@ -321,18 +328,21 @@ * 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 + * 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 @@ -344,10 +354,11 @@ * @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, + * @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); /** @@ -359,11 +370,12 @@ * * 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 + * 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 @@ -373,9 +385,11 @@ * 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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search_data( const void *root, + size_t depth, const void *data, cx_tree_search_data_func sfunc, void **result, @@ -392,11 +406,12 @@ * * 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 + * 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 @@ -406,9 +421,11 @@ * 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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_access_w(5) int cx_tree_search( const void *root, + size_t depth, const void *node, cx_tree_search_func sfunc, void **result, @@ -436,6 +453,7 @@ * @return the new tree iterator * @see cxTreeIteratorDispose() */ +cx_attr_nodiscard CxTreeIterator cx_tree_iterator( void *root, bool visit_on_exit, @@ -461,6 +479,7 @@ * @return the new tree visitor * @see cxTreeVisitorDispose() */ +cx_attr_nodiscard CxTreeVisitor cx_tree_visitor( void *root, ptrdiff_t loc_children, @@ -472,11 +491,12 @@ * 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. + * created node or @c NULL when allocation fails. * - * \note the function may leave the node pointers in the struct uninitialized. + * @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 *); /** @@ -493,11 +513,11 @@ * 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. + * 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. + * 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 @@ -520,12 +540,13 @@ * @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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__(1, 3, 4, 6, 7))) +cx_attr_nonnull_arg(1, 3, 4, 6, 7) +cx_attr_access_w(6) size_t cx_tree_add_iter( struct cx_iterator_base_s *iter, size_t num, @@ -545,11 +566,11 @@ * 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 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 @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 @@ -562,8 +583,8 @@ * 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 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 @@ -573,12 +594,13 @@ * @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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__(1, 4, 5, 7, 8))) +cx_attr_nonnull_arg(1, 4, 5, 7, 8) +cx_attr_access_w(7) size_t cx_tree_add_array( const void *src, size_t num, @@ -599,28 +621,28 @@ * Adds data to a tree. * * An adequate location where to add the new tree node is searched with the - * specified \p sfunc. + * specified @p sfunc. * - * When a location is found, the \p cfunc will be invoked with \p cdata. + * 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 + * 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 + * 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 + * 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 + * 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 + * 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. + * 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(). @@ -635,12 +657,13 @@ * @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 offset in the node struct for the prev 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 */ -__attribute__((__nonnull__(1, 2, 3, 5, 6))) +cx_attr_nonnull_arg(1, 2, 3, 5, 6) +cx_attr_access_w(5) int cx_tree_add( const void *src, cx_tree_search_func sfunc, @@ -704,7 +727,7 @@ /** * A pointer to the root node. * - * Will be \c NULL when \c size is 0. + * Will be @c NULL when @c size is 0. */ void *root; @@ -778,6 +801,10 @@ /** * 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; \ @@ -788,6 +815,11 @@ /** * 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),\ @@ -797,31 +829,13 @@ offsetof(struct cx_tree_node_base_s, next) /** - * Macro for obtaining the node pointer layout for a specific tree. - */ -#define cx_tree_node_layout(tree) \ - (tree)->loc_parent,\ - (tree)->loc_children,\ - (tree)->loc_last_child,\ - (tree)->loc_prev, \ - (tree)->loc_next - -/** * The class definition for arbitrary trees. */ struct cx_tree_class_s { /** - * Destructor function. - * - * Implementations SHALL invoke the node destructor functions if provided - * and SHALL deallocate the tree memory. - */ - void (*destructor)(struct cx_tree_s *); - - /** * Member function for inserting a single element. * - * Implementations SHALL NOT simply invoke \p insert_many as this comes + * Implementations SHALL NOT simply invoke @p insert_many as this comes * with too much overhead. */ int (*insert_element)( @@ -847,21 +861,9 @@ void *(*find)( struct cx_tree_s *tree, const void *subtree, - const void *data + const void *data, + size_t depth ); - - /** - * Member function for creating an iterator for the tree. - */ - CxTreeIterator (*iterator)( - struct cx_tree_s *tree, - bool visit_on_exit - ); - - /** - * Member function for creating a visitor for the tree. - */ - CxTreeVisitor (*visitor)(struct cx_tree_s *tree); }; /** @@ -869,16 +871,80 @@ */ 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 +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() + */ +#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 + */ +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. + * 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 + * @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, a default stdlib allocator 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 @@ -886,13 +952,16 @@ * @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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull_arg(2, 3, 4) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) CxTree *cxTreeCreate( const CxAllocator *allocator, cx_tree_node_create_func create_func, @@ -908,57 +977,51 @@ /** * 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 + * 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 + * @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 - * @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 - * @return the new tree + * @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() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTree *cxTreeCreateSimple( - const CxAllocator *allocator, - cx_tree_node_create_func create_func, - cx_tree_search_func search_func, - cx_tree_search_data_func search_data_func -) { - return cxTreeCreate( - allocator, - create_func, - search_func, - search_data_func, - cx_tree_node_base_layout - ); -} +#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. + * The specified @p allocator will be used for creating the tree struct. * - * \attention This function will create an incompletely defined tree structure + * @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, a default stdlib allocator 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 offset in the node struct for the prev 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() */ -__attribute__((__nonnull__, __warn_unused_result__)) +cx_attr_nonnull_arg(2) +cx_attr_nodiscard +cx_attr_malloc +cx_attr_dealloc(cxTreeFree, 1) CxTree *cxTreeCreateWrapped( const CxAllocator *allocator, void *root, @@ -970,32 +1033,18 @@ ); /** - * Destroys the tree structure. - * - * \attention This function will only invoke the destructor functions - * on the nodes, if specified. - * It will NOT additionally free the nodes with the tree's allocator, because - * that would cause a double-free in most scenarios. - * - * @param tree the tree to destroy - */ -__attribute__((__nonnull__)) -static inline void cxTreeDestroy(CxTree *tree) { - tree->cl->destructor(tree); -} - -/** * Inserts data into the tree. * - * \remark For this function to work, the tree needs specified search and + * @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 - * @return zero on success, non-zero on failure + * @retval zero success + * @retval non-zero failure */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline int cxTreeInsert( CxTree *tree, const void *data @@ -1006,7 +1055,7 @@ /** * Inserts elements provided by an iterator efficiently into the tree. * - * \remark For this function to work, the tree needs specified search and + * @remark For this function to work, the tree needs specified search and * create functions, which might not be available for wrapped trees * (see #cxTreeCreateWrapped()). * @@ -1015,7 +1064,7 @@ * @param n the maximum number of elements to insert * @return the number of elements that could be successfully inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxTreeInsertIter( CxTree *tree, struct cx_iterator_base_s *iter, @@ -1027,7 +1076,7 @@ /** * Inserts an array of data efficiently into the tree. * - * \remark For this function to work, the tree needs specified search and + * @remark For this function to work, the tree needs specified search and * create functions, which might not be available for wrapped trees * (see #cxTreeCreateWrapped()). * @@ -1037,7 +1086,7 @@ * @param n the number of elements in the array * @return the number of elements that could be successfully inserted */ -__attribute__((__nonnull__)) +cx_attr_nonnull static inline size_t cxTreeInsertArray( CxTree *tree, const void *data, @@ -1053,44 +1102,51 @@ /** * Searches the data in the specified tree. * - * \remark For this function to work, the tree needs a specified \c search_data + * @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 + * @return the first matching node, or @c NULL when the data cannot be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxTreeFind( CxTree *tree, const void *data ) { - return tree->cl->find(tree, tree->root, data); + return tree->cl->find(tree, tree->root, data, 0); } /** * Searches the data in the specified subtree. * - * \note When \p subtree_root is not part of the \p tree, the behavior is + * 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 + * @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 - * @return the first matching node, or \c NULL when the data cannot be found + * @param max_depth the maximum search depth + * @return the first matching node, or @c NULL when the data cannot be found */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard static inline void *cxTreeFindInSubtree( CxTree *tree, const void *data, - void *subtree_root + void *subtree_root, + size_t max_depth ) { - return tree->cl->find(tree, subtree_root, data); + return tree->cl->find(tree, subtree_root, data, max_depth); } /** @@ -1100,7 +1156,8 @@ * @param subtree_root the root node of the subtree * @return the number of nodes in the specified subtree */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); /** @@ -1108,9 +1165,10 @@ * * @param tree the tree * @param subtree_root the root node of the subtree - * @return the tree depth including the \p subtree_root + * @return the tree depth including the @p subtree_root */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** @@ -1119,24 +1177,69 @@ * @param tree the tree * @return the tree depth, counting the root as one */ -__attribute__((__nonnull__)) +cx_attr_nonnull +cx_attr_nodiscard 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 sub-tree + * leaving the subtree * @return a tree iterator (depth-first) - * @see cxTreeVisitor() + * @see cxTreeVisit() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeIterator cxTreeIterator( +cx_attr_nonnull +cx_attr_nodiscard +static inline CxTreeIterator cxTreeIterate( CxTree *tree, bool visit_on_exit ) { - return tree->cl->iterator(tree, visit_on_exit); + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); } /** @@ -1144,32 +1247,53 @@ * * @param tree the tree to iterate * @return a tree visitor (a.k.a. breadth-first iterator) - * @see cxTreeIterator() + * @see cxTreeIterate() */ -__attribute__((__nonnull__, __warn_unused_result__)) -static inline CxTreeVisitor cxTreeVisitor(CxTree *tree) { - return tree->cl->visitor(tree); +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 +void cxTreeSetParent( + CxTree *tree, + void *parent, + void *child +); + +/** * Adds a new node to the tree. * - * \attention The node may be externally created, but MUST obey the same rules + * 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() */ -__attribute__((__nonnull__)) -static inline void cxTreeAddChildNode( +cx_attr_nonnull +void cxTreeAddChildNode( CxTree *tree, void *parent, - void *child) { - cx_tree_link(parent, child, cx_tree_node_layout(tree)); - tree->size++; -} + void *child +); /** * Creates a new node and adds it to the tree. @@ -1188,7 +1312,7 @@ * @return zero when the new node was created, non-zero on allocation failure * @see cxTreeInsert() */ -__attribute__((__nonnull__)) +cx_attr_nonnull int cxTreeAddChild( CxTree *tree, void *parent, @@ -1199,13 +1323,15 @@ * 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 #cxTreeRemove() so that those updates can be - * applied when re-linking the children of the removed node. + * 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, @@ -1217,17 +1343,17 @@ * * 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 + * @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 + * @return zero on success, non-zero if @p node is the root node of the tree */ -__attribute__((__nonnull__(1,2))) -int cxTreeRemove( +cx_attr_nonnull_arg(1, 2) +int cxTreeRemoveNode( CxTree *tree, void *node, cx_tree_relink_func relink_func @@ -1238,15 +1364,40 @@ * * 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 + * @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 */ -__attribute__((__nonnull__)) +cx_attr_nonnull 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) +int cxTreeDestroyNode( + CxTree *tree, + void *node, + cx_tree_relink_func relink_func +); + #ifdef __cplusplus } // extern "C" #endif
--- a/ucx/hash_key.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/hash_key.c Sun Jan 05 22:00:39 2025 +0100 @@ -40,7 +40,7 @@ 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;
--- a/ucx/hash_map.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/hash_map.c Sun Jan 05 22:00:39 2025 +0100 @@ -27,10 +27,10 @@ */ #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. */ @@ -45,7 +45,7 @@ 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 { @@ -115,9 +115,7 @@ allocator, 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->collection.store_pointer) { @@ -128,9 +126,7 @@ // 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; @@ -173,17 +169,28 @@ /** * 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; @@ -199,27 +206,31 @@ 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 { + 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( @@ -227,15 +238,17 @@ 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(const void *it) { @@ -346,7 +359,7 @@ iter.base.current = cx_hash_map_iter_current_value; break; default: - assert(false); + assert(false); // LCOV_EXCL_LINE } iter.base.valid = cx_hash_map_iter_valid; @@ -393,6 +406,10 @@ size_t itemsize, size_t buckets ) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + if (buckets == 0) { // implementation defined default buckets = 16; @@ -406,10 +423,10 @@ 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; @@ -431,17 +448,19 @@ if (map->collection.size > ((hash_map->bucket_count * 3) >> 2)) { 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->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;
--- a/ucx/iterator.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/iterator.c Sun Jan 05 22:00:39 2025 +0100 @@ -40,6 +40,11 @@ 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) { @@ -110,3 +115,22 @@ 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 Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/json.c Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,1212 @@ +/* + * 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 "cx/compare.h" + +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <stdio.h> +#include <errno.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 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 (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] != '-' && !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 (!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 (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_start = json->buffer.pos; + + 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_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_start = i; + } + } else { + // finish token + if (ctype != CX_JSON_NO_TOKEN) { + *result = token_create(json, false, token_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 (json->tokenizer_escape) { + json->tokenizer_escape = false; + } else { + if (c == '"') { + *result = token_create(json, true, token_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 == '\\') { + json->tokenizer_escape = true; + } + } + } + } + + if (ttype != CX_JSON_NO_TOKEN) { + // uncompleted token + size_t uncompleted_len = json->buffer.size - token_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_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_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; +} + +static cxmutstr unescape_string(const CxAllocator *a, cxmutstr str) { + // TODO: support more escape sequences + // we know that the unescaped string will be shorter by at least 2 chars + 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 == 't') { + c = '\t'; + } + result.ptr[result.length++] = c; + } else { + if (c == '\\') { + u = true; + } else { + result.ptr[result.length++] = c; + } + } + } + result.ptr[result.length] = 0; + + return result; +} + +static CxJsonValue* create_json_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) { + free(json->states); + } + if (json->vbuf != json->vbuf_internal) { + free(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 (create_json_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 (create_json_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 = create_json_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 = create_json_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 = create_json_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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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 cx_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]); + } + free(values); +} +// LCOV_EXCL_STOP + +int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count) { + CxJsonValue** values = calloc(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) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count) { + CxJsonValue** values = calloc(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) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count) { + CxJsonValue** values = calloc(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) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count) { + CxJsonValue** values = calloc(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) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(values); + return ret; +} + +int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count) { + CxJsonValue** values = calloc(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) { cx_json_arr_free_temp(values, count); return -1; } + } + int ret = cxJsonArrAddValues(arr, values, count); + free(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; + } +} + +static const CxJsonWriter cx_json_writer_default = { + false, + true, + 255, + false, + 4 +}; + +CxJsonWriter cxJsonWriterCompact(void) { + return cx_json_writer_default; +} + +CxJsonWriter cxJsonWriterPretty(bool use_spaces) { + return (CxJsonWriter) { + true, + true, + 255, + use_spaces, + 4 + }; +} + +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[32]; + + // 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); + // TODO: escape the string + actual += wfunc(member->name.ptr, 1, + member->name.length, target); + 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); + // TODO: escape the string + actual += wfunc(value->value.string.ptr, 1, + value->value.string.length, target); + actual += wfunc("\"", 1, 1, target); + expected += 2 + value->value.string.length; + break; + } + case CX_JSON_NUMBER: { + // TODO: locale bullshit + // TODO: formatting settings + snprintf(numbuf, 32, "%g", value->value.number); + size_t len = strlen(numbuf); + actual += wfunc(numbuf, 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 +) { + if (settings == NULL) { + settings = &cx_json_writer_default; + } + assert(target != NULL); + assert(value != NULL); + assert(wfunc != NULL); + + return cx_json_write_rec(target, value, wfunc, settings, 0); +}
--- a/ucx/linked_list.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/linked_list.c Sun Jan 05 22:00:39 2025 +0100 @@ -27,7 +27,6 @@ */ #include "cx/linked_list.h" -#include "cx/utils.h" #include "cx/compare.h" #include <string.h> #include <assert.h> @@ -91,7 +90,7 @@ do { void *current = ll_data(node); if (cmp_func(current, elem) == 0) { - *result = (void*) node; + *result = (void *) node; return index; } node = ll_advance(node); @@ -336,19 +335,22 @@ } } -void cx_linked_list_remove( +size_t cx_linked_list_remove_chain( void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, - void *node + 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); @@ -356,6 +358,12 @@ 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) { @@ -373,6 +381,8 @@ } else if (loc_prev >= 0) { ll_prev(next) = prev; } + + return removed; } size_t cx_linked_list_size( @@ -436,13 +446,13 @@ // 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); } @@ -646,9 +656,7 @@ 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; @@ -660,9 +668,7 @@ const char *source = array; for (size_t i = 1; i < n; i++) { source += list->collection.elem_size; - if (0 != cx_ll_insert_at(list, node, source)) { - return i; - } + if (0 != cx_ll_insert_at(list, node, source)) return i; node = node->next; } return n; @@ -733,30 +739,61 @@ return inserted; } -static int cx_ll_remove( +static size_t cx_ll_remove( struct cx_list_s *list, - size_t index + 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->collection.size--; + 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 and return - cxFree(list->collection.allocator, node); + // 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); - return 0; + // 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 removed; } static void cx_ll_clear(struct cx_list_s *list) { @@ -777,7 +814,7 @@ #ifndef CX_LINKED_LIST_SWAP_SBO_SIZE #define CX_LINKED_LIST_SWAP_SBO_SIZE 128 #endif -unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; +const unsigned cx_linked_list_swap_sbo_size = CX_LINKED_LIST_SWAP_SBO_SIZE; static int cx_ll_swap( struct cx_list_s *list, @@ -798,7 +835,7 @@ 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);
--- a/ucx/list.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/list.c Sun Jan 05 22:00:39 2025 +0100 @@ -59,7 +59,7 @@ } static void cx_pl_destructor(struct cx_list_s *list) { - list->climpl->destructor(list); + list->climpl->deallocate(list); } static int cx_pl_insert_element( @@ -99,11 +99,13 @@ 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) { @@ -210,33 +212,33 @@ // <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__)) const struct cx_list_s *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_remove( - __attribute__((__unused__)) struct cx_list_s *list, - __attribute__((__unused__)) const void *elem, - __attribute__((__unused__)) bool remove + cx_attr_unused struct cx_list_s *list, + cx_attr_unused const void *elem, + cx_attr_unused bool remove ) { return -1; } -static bool cx_emptyl_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_emptyl_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_emptyl_iterator( const struct cx_list_s *list, size_t index, - __attribute__((__unused__)) bool backwards + cx_attr_unused bool backwards ) { CxIterator iter = {0}; iter.src_handle.c = list; @@ -295,10 +297,9 @@ const char *src = data; size_t i = 0; for (; i < n; i++) { - if (0 != invoke_list_func(insert_element, - list, index + i, src + (i * elem_size))) { - return i; - } + if (0 != invoke_list_func( + insert_element, list, index + i, + src + (i * elem_size))) return i; } return i; } @@ -343,9 +344,8 @@ // insert the elements at location si if (ins == 1) { - if (0 != invoke_list_func(insert_element, - list, di, src)) - return inserted; + if (0 != 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; @@ -417,10 +417,6 @@ return 0; } -void cxListDestroy(CxList *list) { - list->cl->destructor(list); -} - int cxListCompare( const CxList *list, const CxList *other @@ -485,3 +481,8 @@ it.base.mutating = true; return it; } + +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); +}
--- a/ucx/map.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/map.c Sun Jan 05 22:00:39 2025 +0100 @@ -31,24 +31,24 @@ // <editor-fold desc="empty map implementation"> -static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) { +static void cx_empty_map_noop(cx_attr_unused CxMap *map) { // this is a noop, but MUST be implemented } static void *cx_empty_map_get( - __attribute__((__unused__)) const CxMap *map, - __attribute__((__unused__)) CxHashKey key + cx_attr_unused const CxMap *map, + cx_attr_unused CxHashKey key ) { return NULL; } -static bool cx_empty_map_iter_valid(__attribute__((__unused__)) const void *iter) { +static bool cx_empty_map_iter_valid(cx_attr_unused const void *iter) { return false; } static CxIterator cx_empty_map_iterator( const struct cx_map_s *map, - __attribute__((__unused__)) enum cx_map_iterator_type type + cx_attr_unused enum cx_map_iterator_type type ) { CxIterator iter = {0}; iter.src_handle.c = map; @@ -100,3 +100,8 @@ it.base.mutating = true; return it; } + +void cxMapFree(CxMap *map) { + if (map == NULL) return; + map->cl->deallocate(map); +}
--- a/ucx/mempool.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/mempool.c Sun Jan 05 22:00:39 2025 +0100 @@ -27,8 +27,9 @@ */ #include "cx/mempool.h" -#include "cx/utils.h" + #include <string.h> +#include <errno.h> struct cx_mempool_memory_s { /** The destructor. */ @@ -45,18 +46,20 @@ 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) { + size_t newmsize; + if (pool->capacity > newcap || cx_szmul(newcap, + sizeof(struct cx_mempool_memory_s*), &newmsize)) { + errno = EOVERFLOW; return NULL; } + struct cx_mempool_memory_s **newdata = realloc(pool->data, newmsize); + 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 (mem == NULL) return NULL; mem->destructor = pool->auto_destr; pool->data[pool->size] = mem; @@ -72,12 +75,11 @@ ) { 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; - } + if (ptr == NULL) return NULL; memset(ptr, 0, nelem * elsize); return ptr; } @@ -93,17 +95,15 @@ mem = (struct cx_mempool_memory_s*)(((char *) ptr) - sizeof(cx_destructor_func)); newm = realloc(mem, n + sizeof(cx_destructor_func)); - if (newm == NULL) { - return NULL; - } + 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); } } - abort(); + abort(); // LCOV_EXCL_LINE } else { return ptr; } @@ -113,12 +113,13 @@ 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)); - 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); @@ -133,12 +134,13 @@ return; } } - abort(); + abort(); // LCOV_EXCL_LINE } -void cxMempoolDestroy(CxMempool *pool) { +void cxMempoolFree(CxMempool *pool) { + if (pool == NULL) return; struct cx_mempool_memory_s *mem; - cx_for_n(i, pool->size) { + for (size_t i = 0; i < pool->size; i++) { mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); @@ -157,6 +159,10 @@ *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func; } +void cxMempoolRemoveDestructor(void *ptr) { + *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = NULL; +} + struct cx_mempool_foreign_mem_s { cx_destructor_func destr; void* mem; @@ -198,35 +204,34 @@ ) { size_t poolsize; if (cx_szmul(capacity, sizeof(struct cx_mempool_memory_s*), &poolsize)) { + errno = EOVERFLOW; return NULL; } struct cx_mempool_s *pool = malloc(sizeof(struct cx_mempool_s)); - if (pool == NULL) { - return NULL; - } + if (pool == NULL) return NULL; CxAllocator *provided_allocator = malloc(sizeof(CxAllocator)); - if (provided_allocator == NULL) { + if (provided_allocator == NULL) { // LCOV_EXCL_START free(pool); return NULL; - } + } // LCOV_EXCL_STOP provided_allocator->cl = &cx_mempool_allocator_class; provided_allocator->data = pool; pool->allocator = provided_allocator; pool->data = malloc(poolsize); - if (pool->data == NULL) { + if (pool->data == NULL) { // LCOV_EXCL_START free(provided_allocator); free(pool); return NULL; - } + } // LCOV_EXCL_STOP pool->size = 0; pool->capacity = capacity; pool->auto_destr = destr; - return (CxMempool *) pool; + return pool; }
--- a/ucx/printf.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/printf.c Sun Jan 05 22:00:39 2025 +0100 @@ -34,7 +34,7 @@ #ifndef CX_PRINTF_SBO_SIZE #define CX_PRINTF_SBO_SIZE 512 #endif -unsigned const cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; +const unsigned cx_printf_sbo_size = CX_PRINTF_SBO_SIZE; int cx_fprintf( void *stream, @@ -69,10 +69,10 @@ } else { int len = ret + 1; char *newbuf = malloc(len); - if (!newbuf) { + if (!newbuf) { // LCOV_EXCL_START va_end(ap2); return -1; - } + } // LCOV_EXCL_STOP ret = vsnprintf(newbuf, len, fmt, ap2); va_end(ap2);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/properties.c Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,406 @@ +/* + * 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; + } + } + // unreachable - either we returned or skipped a blank line + assert(false); + } + + // 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 = 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(src->src); +} + +static int cx_properties_read_init_file( + cx_attr_unused CxProperties *prop, + CxPropertiesSource *src +) { + src->data_ptr = malloc(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 +) { + free(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; + 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) { + status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; + break; + } + + // set the input buffer and read the k/v-pairs + cxPropertiesFill(prop, input); + + CxPropertiesStatus kv_status; + 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/streams.c Sun Jan 05 22:00:39 2025 +0100 @@ -0,0 +1,93 @@ +/* + * 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/streams.h" + +#ifndef CX_STREAM_BCOPY_BUF_SIZE +#define CX_STREAM_BCOPY_BUF_SIZE 8192 +#endif + +#ifndef CX_STREAM_COPY_BUF_SIZE +#define CX_STREAM_COPY_BUF_SIZE 1024 +#endif + +size_t cx_stream_bncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + char *buf, + size_t bufsize, + size_t n +) { + if (n == 0) { + return 0; + } + + char *lbuf; + size_t ncp = 0; + + if (buf) { + if (bufsize == 0) return 0; + lbuf = buf; + } else { + if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; + lbuf = malloc(bufsize); + if (lbuf == NULL) return 0; + } + + size_t r; + size_t rn = bufsize > n ? n : bufsize; + while ((r = rfnc(lbuf, 1, rn, src)) != 0) { + r = wfnc(lbuf, 1, r, dest); + ncp += r; + n -= r; + rn = bufsize > n ? n : bufsize; + if (r == 0 || n == 0) { + break; + } + } + + if (lbuf != buf) { + free(lbuf); + } + + return ncp; +} + +size_t cx_stream_ncopy( + void *src, + void *dest, + cx_read_func rfnc, + cx_write_func wfnc, + size_t n +) { + char buf[CX_STREAM_COPY_BUF_SIZE]; + return cx_stream_bncopy(src, dest, rfnc, wfnc, + buf, CX_STREAM_COPY_BUF_SIZE, n); +}
--- a/ucx/string.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/string.c Sun Jan 05 22:00:39 2025 +0100 @@ -25,19 +25,23 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - +#define CX_STR_IMPLEMENTATION #include "cx/string.h" -#include "cx/utils.h" #include <string.h> #include <stdarg.h> #include <ctype.h> - -#ifndef _WIN32 +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <float.h> -#include <strings.h> // for strncasecmp() - -#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)}; @@ -61,11 +65,8 @@ return (cxstring) {cstring, length}; } -cxstring cx_strcast(cxmutstr str) { - return (cxstring) {str.ptr, str.length}; -} - void cx_strfree(cxmutstr *str) { + if (str == NULL) return; free(str->ptr); str->ptr = NULL; str->length = 0; @@ -75,6 +76,7 @@ const CxAllocator *alloc, cxmutstr *str ) { + if (str == NULL) return; cxFree(alloc, str->ptr); str->ptr = NULL; str->length = 0; @@ -89,8 +91,9 @@ 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); @@ -106,33 +109,59 @@ ) { if (count == 0) return str; - cxstring *strings = calloc(count, sizeof(cxstring)); - if (!strings) abort(); + cxstring strings_stack[8]; + cxstring *strings; + if (count > 8) { + strings = calloc(count, sizeof(cxstring)); + if (strings == NULL) { + return (cxmutstr) {NULL, 0}; + } + } else { + strings = strings_stack; + } va_list ap; va_start(ap, count); // get all args and overall length + bool overflow = false; size_t slen = str.length; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = va_arg (ap, cxstring); strings[i] = s; + if (slen > SIZE_MAX - str.length) overflow = true; slen += s.length; } va_end(ap); + // abort in case of overflow + if (overflow) { + errno = EOVERFLOW; + if (strings != strings_stack) { + free(strings); + } + 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 (str.ptr == NULL) abort(); + if (newstr == NULL) { + if (strings != strings_stack) { + free(strings); + } + return (cxmutstr) {NULL, 0}; + } + str.ptr = newstr; // concatenate strings size_t pos = str.length; str.length = slen; - cx_for_n(i, count) { + for (size_t i = 0; i < count; i++) { cxstring s = strings[i]; memcpy(str.ptr + pos, s.ptr, s.length); pos += s.length; @@ -142,7 +171,9 @@ str.ptr[str.length] = '\0'; // free temporary array - free(strings); + if (strings != strings_stack) { + free(strings); + } return str; } @@ -193,7 +224,7 @@ ) { chr = 0xFF & chr; // TODO: improve by comparing multiple bytes at once - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { if (string.ptr[i] == chr) { return cx_strsubs(string, i); } @@ -236,7 +267,7 @@ #ifndef CX_STRSTR_SBO_SIZE #define CX_STRSTR_SBO_SIZE 512 #endif -unsigned const cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; +const unsigned cx_strstr_sbo_size = CX_STRSTR_SBO_SIZE; cxstring cx_strstr( cxstring haystack, @@ -434,10 +465,14 @@ 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; } } @@ -447,14 +482,14 @@ 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; } } @@ -556,13 +591,13 @@ } void cx_strlower(cxmutstr string) { - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { string.ptr[i] = (char) tolower(string.ptr[i]); } } void cx_strupper(cxmutstr string) { - cx_for_n(i, string.length) { + for (size_t i = 0; i < string.length; i++) { string.ptr[i] = (char) toupper(string.ptr[i]); } } @@ -748,7 +783,7 @@ // 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; @@ -784,3 +819,370 @@ 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; + + // trim already, to search for a sign character + str = cx_strtrim(str); + 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); +} + +int cx_strtoz_lc(cxstring str, ssize_t *output, int base, const char *groupsep) { +#if SSIZE_MAX == INT32_MAX + return cx_strtoi32_lc(str, (int32_t*) output, base, groupsep); +#elif SSIZE_MAX == INT64_MAX + return cx_strtoll_lc(str, (long long*) output, base, groupsep); +#else +#error "unsupported ssize_t size" +#endif +} + +#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 + str = cx_strtrim(str); + 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_strtouz_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; +} + +int cx_strtod_lc(cxstring str, double *output, char decsep, const char *groupsep) { + // TODO: overflow check + // TODO: increase precision + + // trim and check + str = cx_strtrim(str); + 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 (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 (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 (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; +}
--- a/ucx/szmul.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/szmul.c Sun Jan 05 22:00:39 2025 +0100 @@ -26,6 +26,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include "cx/common.h" + +#ifndef CX_SZMUL_BUILTIN int cx_szmul_impl( size_t a, size_t b, @@ -44,3 +47,4 @@ return 1; } } +#endif // CX_SZMUL_BUILTIN
--- a/ucx/tree.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ucx/tree.c Sun Jan 05 22:00:39 2025 +0100 @@ -42,6 +42,13 @@ #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, @@ -51,7 +58,9 @@ ptrdiff_t loc_next ) { tree_parent(node) = NULL; - tree_prev(node) = NULL; + if (loc_prev >= 0) { + tree_prev(node) = NULL; + } tree_next(node) = NULL; tree_children(node) = NULL; if (loc_last_child >= 0) { @@ -60,14 +69,18 @@ } void cx_tree_link( - void *restrict parent, - void *restrict node, + 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) { @@ -80,24 +93,43 @@ tree_last_child(parent) = node; } } else { + void *child; if (loc_last_child >= 0) { - void *child = tree_last_child(parent); - tree_prev(node) = child; - tree_next(child) = node; + child = tree_last_child(parent); tree_last_child(parent) = node; } else { - void *child = tree_children(parent); + 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_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, @@ -108,7 +140,15 @@ ) { if (tree_parent(node) == NULL) return; - void *left = tree_prev(node); + 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); @@ -125,94 +165,95 @@ tree_last_child(parent) = left; } } else { - tree_prev(right) = left; + if (loc_prev >= 0) { + tree_prev(right) = left; + } } tree_parent(node) = NULL; - tree_prev(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 ) { - int ret; + // help avoiding bugs due to uninitialized memory + assert(result != NULL); *result = NULL; - // shortcut: compare root before doing anything else - ret = sfunc(root, node); + // remember return value for best match + int ret = sfunc(root, node); if (ret < 0) { - return ret; - } else if (ret == 0 || tree_children(root) == NULL) { - *result = (void*)root; + // 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; } - // create a working stack - CX_ARRAY_DECLARE(const void *, work); - cx_array_initialize(work, 32); + // 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); - // add the children of root to the working stack - { - void *c = tree_children(root); - while (c != NULL) { - cx_array_simple_add(work, c); - c = tree_next(c); + // 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 = (void *) elem; + ret = 0; + break; + } else if (ret_elem > 0 && ret_elem < ret) { + // new distance is better + *result = elem; + ret = ret_elem; + } else { + // 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); } } - // remember a candidate for adding the data - // also remember the exact return code from sfunc - void *candidate = (void *) root; - int ret_candidate = ret; - - // process the working stack - while (work_size > 0) { - // pop element - const void *elem = work[--work_size]; - - // apply the search function - ret = sfunc(elem, node); + // dispose the iterator as we might have exited the loop early + cxTreeIteratorDispose(&iter); - if (ret == 0) { - // if found, exit the search - *result = (void *) elem; - work_size = 0; - break; - } else if (ret > 0) { - // if children might contain the data, add them to the stack - void *c = tree_children(elem); - while (c != NULL) { - cx_array_simple_add(work, c); - c = tree_next(c); - } - - // remember this node in case no child is suitable - if (ret < ret_candidate) { - candidate = (void *) elem; - ret_candidate = ret; - } - } - } - - // not found, but was there a candidate? - if (ret != 0 && candidate != NULL) { - ret = ret_candidate; - *result = candidate; - } - - // free the working queue and return - free(work); + 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, @@ -221,7 +262,7 @@ ) { // it is basically the same implementation return cx_tree_search( - root, data, + root, depth, data, (cx_tree_search_func) sfunc, result, loc_children, loc_next); @@ -369,7 +410,7 @@ return iter->node; } -__attribute__((__nonnull__)) +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); @@ -531,6 +572,7 @@ void *match = NULL; int result = cx_tree_search( root, + 0, *cnode, sfunc, &match, @@ -595,6 +637,7 @@ cx_tree_add_look_around_retry: result = cx_tree_search( current_node, + 0, new_node, sfunc, &match, @@ -681,37 +724,6 @@ loc_prev, loc_next); } -static void cx_tree_default_destructor(CxTree *tree) { - if (tree->simple_destructor != NULL || tree->advanced_destructor != NULL) { - CxTreeIterator iter = tree->cl->iterator(tree, true); - cx_foreach(void *, node, iter) { - if (iter.exiting) { - if (tree->simple_destructor) { - tree->simple_destructor(node); - } - if (tree->advanced_destructor) { - tree->advanced_destructor(tree->destructor_data, node); - } - } - } - } - cxFree(tree->allocator, tree); -} - -static CxTreeIterator cx_tree_default_iterator( - CxTree *tree, - bool visit_on_exit -) { - return cx_tree_iterator( - tree->root, visit_on_exit, - tree->loc_children, tree->loc_next - ); -} - -static CxTreeVisitor cx_tree_default_visitor(CxTree *tree) { - return cx_tree_visitor(tree->root, tree->loc_children, tree->loc_next); -} - static int cx_tree_default_insert_element( CxTree *tree, const void *data @@ -765,13 +777,15 @@ static void *cx_tree_default_find( CxTree *tree, const void *subtree, - const void *data + 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, @@ -785,12 +799,9 @@ } static cx_tree_class cx_tree_default_class = { - cx_tree_default_destructor, cx_tree_default_insert_element, cx_tree_default_insert_many, - cx_tree_default_find, - cx_tree_default_iterator, - cx_tree_default_visitor + cx_tree_default_find }; CxTree *cxTreeCreate( @@ -804,6 +815,13 @@ 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; @@ -812,6 +830,7 @@ 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; @@ -825,6 +844,14 @@ 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, @@ -834,6 +861,11 @@ 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; @@ -856,6 +888,27 @@ 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, @@ -893,14 +946,16 @@ } size_t cxTreeDepth(CxTree *tree) { - CxTreeVisitor visitor = tree->cl->visitor(tree); + CxTreeVisitor visitor = cx_tree_visitor( + tree->root, tree->loc_children, tree->loc_next + ); while (cxIteratorValid(visitor)) { cxIteratorNext(visitor); } return visitor.depth; } -int cxTreeRemove( +int cxTreeRemoveNode( CxTree *tree, void *node, cx_tree_relink_func relink_func @@ -956,3 +1011,44 @@ 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; + } +}
--- a/ucx/utils.c Sun Jan 05 17:41:39 2025 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/* - * 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/utils.h" - -#ifndef CX_STREAM_BCOPY_BUF_SIZE -#define CX_STREAM_BCOPY_BUF_SIZE 8192 -#endif - -#ifndef CX_STREAM_COPY_BUF_SIZE -#define CX_STREAM_COPY_BUF_SIZE 1024 -#endif - -size_t cx_stream_bncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - char *buf, - size_t bufsize, - size_t n -) { - if (n == 0) { - return 0; - } - - char *lbuf; - size_t ncp = 0; - - if (buf) { - if (bufsize == 0) return 0; - lbuf = buf; - } else { - if (bufsize == 0) bufsize = CX_STREAM_BCOPY_BUF_SIZE; - lbuf = malloc(bufsize); - if (lbuf == NULL) { - return 0; - } - } - - size_t r; - size_t rn = bufsize > n ? n : bufsize; - while ((r = rfnc(lbuf, 1, rn, src)) != 0) { - r = wfnc(lbuf, 1, r, dest); - ncp += r; - n -= r; - rn = bufsize > n ? n : bufsize; - if (r == 0 || n == 0) { - break; - } - } - - if (lbuf != buf) { - free(lbuf); - } - - return ncp; -} - -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -) { - char buf[CX_STREAM_COPY_BUF_SIZE]; - return cx_stream_bncopy(src, dest, rfnc, wfnc, - buf, CX_STREAM_COPY_BUF_SIZE, n); -} - -#ifndef CX_SZMUL_BUILTIN -#include "szmul.c" -#endif
--- a/ui/common/context.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/common/context.c Sun Jan 05 22:00:39 2025 +0100 @@ -164,7 +164,7 @@ ctx->detach_document2(ctx, doc); } - cxListDestroy(ls); + cxListFree(ls); } static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) { @@ -466,7 +466,7 @@ } UIEXPORT void ui_context_destroy(UiContext *ctx) { - cxMempoolDestroy(ctx->mp); + cxMempoolFree(ctx->mp); } @@ -535,7 +535,7 @@ uic_add_group_widget(ctx, widget, enable, groups); - cxListDestroy(groups); + cxListFree(groups); } size_t uic_group_array_size(const int *groups) {
--- a/ui/common/document.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/common/document.c Sun Jan 05 22:00:39 2025 +0100 @@ -110,7 +110,7 @@ if(ctx->close_callback) { ctx->close_callback(&ev, ctx->close_data); } - cxMempoolDestroy(ctx->mp); + cxMempoolFree(ctx->mp); } }
--- a/ui/common/menu.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/common/menu.c Sun Jan 05 22:00:39 2025 +0100 @@ -326,6 +326,6 @@ free_menuitem(m); m = next; } - cxListDestroy(builder->current); + cxListFree(builder->current); free(builder); }
--- a/ui/common/object.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/common/object.c Sun Jan 05 22:00:39 2025 +0100 @@ -87,7 +87,7 @@ ev.intval = 0; obj->ctx->close_callback(&ev, obj->ctx->close_data); } - cxMempoolDestroy(obj->ctx->mp); + cxMempoolFree(obj->ctx->mp); } UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
--- a/ui/common/types.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/common/types.c Sun Jan 05 22:00:39 2025 +0100 @@ -117,7 +117,7 @@ } void ui_list_free(UiList *list) { - cxListDestroy(list->data); + cxListFree(list->data); free(list); } @@ -213,7 +213,7 @@ info->titles[i] = c->name; i++; } - cxListDestroy(cols); + cxListFree(cols); return info; }
--- a/ui/gtk/container.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/gtk/container.c Sun Jan 05 22:00:39 2025 +0100 @@ -971,7 +971,8 @@ int j = 0; while(elm) { CxHashKey key = cx_hash_key(&elm, sizeof(void*)); - UiObject *item_obj = cxMapRemoveAndGet(ct->current_items, key); + UiObject *item_obj = NULL; + cxMapRemoveAndGet(ct->current_items, key, &item_obj); if(item_obj) { g_object_ref(G_OBJECT(item_obj->widget)); BOX_REMOVE(ct->widget, item_obj->widget); @@ -982,7 +983,7 @@ } // ct->current_items only contains elements, that are not in the list - cxMapDestroy(ct->current_items); // calls destructor remove_item + cxMapFree(ct->current_items); // calls destructor remove_item ct->current_items = new_items; // add all items @@ -1021,7 +1022,7 @@ static void destroy_itemlist_container(GtkWidget *w, UiGtkItemListContainer *container) { container->remove_items = FALSE; - cxMapDestroy(container->current_items); + cxMapFree(container->current_items); free(container); }
--- a/ui/gtk/dnd.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/gtk/dnd.c Sun Jan 05 22:00:39 2025 +0100 @@ -191,7 +191,7 @@ } void ui_dnd_free(UiDnD *dnd) { - cxListDestroy(dnd->providers); + cxListFree(dnd->providers); free(dnd); }
--- a/ui/gtk/list.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/gtk/list.c Sun Jan 05 22:00:39 2025 +0100 @@ -1500,14 +1500,14 @@ /* ------------------------------ Source List ------------------------------ */ static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { - cxListDestroy(v->sublists); + cxListFree(v->sublists); free(v); } static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { free(sublist->header); ui_destroy_boundvar(obj->ctx, sublist->var); - cxListDestroy(sublist->widgets); + cxListFree(sublist->widgets); } static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) {
--- a/ui/gtk/menu.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/gtk/menu.c Sun Jan 05 22:00:39 2025 +0100 @@ -131,7 +131,7 @@ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); cxListAddArray(groups, i->groups, i->ngroups); uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups); - cxListDestroy(groups); + cxListFree(groups); } } @@ -464,7 +464,7 @@ CxList *groups = cxArrayListCreateSimple(sizeof(int), i->ngroups); cxListAddArray(groups, i->groups, i->ngroups); uic_add_group_widget(obj->ctx, action, (ui_enablefunc)action_enable, groups); - cxListDestroy(groups); + cxListFree(groups); } if(i->callback != NULL) {
--- a/ui/motif/button.c Sun Jan 05 17:41:39 2025 +0100 +++ b/ui/motif/button.c Sun Jan 05 22:00:39 2025 +0100 @@ -239,7 +239,7 @@ } static void destroy_list(Widget w, CxList *list, XtPointer d) { - cxListDestroy(list); + cxListFree(list); } static void radiobutton_changed(Widget w, UiVarEventData *event, XmToggleButtonCallbackStruct *tb) {