upgrade ucx newapi

Mon, 22 May 2023 16:17:26 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 22 May 2023 16:17:26 +0200
branch
newapi
changeset 174
0358f1d9c506
parent 173
809581724cc7
child 175
2cb06c231057

upgrade ucx

application/Makefile file | annotate | diff | comparison | revisions
application/main.c file | annotate | diff | comparison | revisions
configure file | annotate | diff | comparison | revisions
make/Makefile.mk file | annotate | diff | comparison | revisions
make/project.xml file | annotate | diff | comparison | revisions
ucx/Makefile file | annotate | diff | comparison | revisions
ucx/allocator.c file | annotate | diff | comparison | revisions
ucx/array_list.c file | annotate | diff | comparison | revisions
ucx/basic_mempool.c file | annotate | diff | comparison | revisions
ucx/buffer.c file | annotate | diff | comparison | revisions
ucx/compare.c file | annotate | diff | comparison | revisions
ucx/cx/allocator.h file | annotate | diff | comparison | revisions
ucx/cx/array_list.h file | annotate | diff | comparison | revisions
ucx/cx/basic_mempool.h file | annotate | diff | comparison | revisions
ucx/cx/buffer.h file | annotate | diff | comparison | revisions
ucx/cx/collection.h file | annotate | diff | comparison | revisions
ucx/cx/common.h file | annotate | diff | comparison | revisions
ucx/cx/compare.h file | annotate | diff | comparison | revisions
ucx/cx/hash_key.h file | annotate | diff | comparison | revisions
ucx/cx/hash_map.h file | annotate | diff | comparison | revisions
ucx/cx/iterator.h file | annotate | diff | comparison | revisions
ucx/cx/linked_list.h file | annotate | diff | comparison | revisions
ucx/cx/list.h file | annotate | diff | comparison | revisions
ucx/cx/map.h file | annotate | diff | comparison | revisions
ucx/cx/mempool.h file | annotate | diff | comparison | revisions
ucx/cx/printf.h file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/cx/tree.h file | annotate | diff | comparison | revisions
ucx/cx/utils.h file | annotate | diff | comparison | revisions
ucx/hash_key.c file | annotate | diff | comparison | revisions
ucx/hash_map.c file | annotate | diff | comparison | revisions
ucx/linked_list.c file | annotate | diff | comparison | revisions
ucx/list.c file | annotate | diff | comparison | revisions
ucx/map.c file | annotate | diff | comparison | revisions
ucx/printf.c file | annotate | diff | comparison | revisions
ucx/string.c file | annotate | diff | comparison | revisions
ucx/szmul.c file | annotate | diff | comparison | revisions
ucx/tree.c file | annotate | diff | comparison | revisions
ucx/utils.c file | annotate | diff | comparison | revisions
ui/Makefile file | annotate | diff | comparison | revisions
ui/common/context.c file | annotate | diff | comparison | revisions
ui/common/context.h file | annotate | diff | comparison | revisions
ui/common/document.c file | annotate | diff | comparison | revisions
ui/common/object.c file | annotate | diff | comparison | revisions
ui/common/objs.mk file | annotate | diff | comparison | revisions
ui/common/properties.c file | annotate | diff | comparison | revisions
ui/common/properties.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/ucx_properties.c file | annotate | diff | comparison | revisions
ui/common/ucx_properties.h file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/container.c file | annotate | diff | comparison | revisions
ui/gtk/display.c file | annotate | diff | comparison | revisions
ui/gtk/dnd.c file | annotate | diff | comparison | revisions
ui/gtk/entry.c file | annotate | diff | comparison | revisions
ui/gtk/image.c file | annotate | diff | comparison | revisions
ui/gtk/menu.c file | annotate | diff | comparison | revisions
ui/gtk/menu.h file | annotate | diff | comparison | revisions
ui/gtk/range.c file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/toolbar.c file | annotate | diff | comparison | revisions
ui/gtk/toolbar.h file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/ui/properties.h file | annotate | diff | comparison | revisions
--- a/application/Makefile	Sat Apr 15 21:06:45 2023 +0200
+++ b/application/Makefile	Mon May 22 16:17:26 2023 +0200
@@ -26,7 +26,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 #
 
-BUILD_ROOT = ../
+BUILD_ROOT = ..
 include ../config.mk
 
 CFLAGS += -I../ui/ -I../ucx
@@ -41,5 +41,5 @@
 	$(LD) -o ../build/bin/mk12$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -luitk -lucx $(LDFLAGS) $(TK_LDFLAGS)
 
 ../build/application/%.$(OBJ_EXT): %.c
-	$(CC) $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
+	$(CC) ../ucx $(CFLAGS) $(TK_CFLAGS) -o $@ -c $<
 
--- a/application/main.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/application/main.c	Mon May 22 16:17:26 2023 +0200
@@ -30,8 +30,8 @@
 #include <stdlib.h>
 
 #include <ui/ui.h>
-#include <ucx/buffer.h>
-#include <ucx/utils.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
 
 typedef struct {
     UiText *text;
--- a/configure	Sat Apr 15 21:06:45 2023 +0200
+++ b/configure	Mon May 22 16:17:26 2023 +0200
@@ -465,8 +465,8 @@
     do
         
 		cat >> $TEMP_DIR/make.mk << __EOF__
-OBJ_EXT = o
-LIB_EXT = a
+OBJ_EXT = .o
+LIB_EXT = .a
 PACKAGE_SCRIPT = package_osx.sh
 
 __EOF__
@@ -488,8 +488,8 @@
     do
         
 		cat >> $TEMP_DIR/make.mk << __EOF__
-OBJ_EXT = o
-LIB_EXT = a
+OBJ_EXT = .o
+LIB_EXT = .a
 PACKAGE_SCRIPT = package_unix.sh
 
 __EOF__
--- a/make/Makefile.mk	Sat Apr 15 21:06:45 2023 +0200
+++ b/make/Makefile.mk	Mon May 22 16:17:26 2023 +0200
@@ -32,7 +32,7 @@
 include config.mk
 
 BUILD_DIRS = build/bin build/lib
-BUILD_DIRS += build/application
+BUILD_DIRS += build/application build/ucx
 BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT)
 
 all: $(BUILD_DIRS) ucx ui application
@@ -41,9 +41,12 @@
 $(BUILD_DIRS):
 	mkdir -p $@
 
-ui: FORCE
+ui: ucx FORCE
 	cd ui; $(MAKE) all
 
+ucx: FORCE
+	cd ucx; $(MAKE) all
+
 application: ui FORCE
 	cd application; $(MAKE)
 
--- a/make/project.xml	Sat Apr 15 21:06:45 2023 +0200
+++ b/make/project.xml	Mon May 22 16:17:26 2023 +0200
@@ -46,13 +46,13 @@
 	</dependency>
 	
 	<dependency platform="macos">
-		<make>OBJ_EXT = o</make>
-		<make>LIB_EXT = a</make>
+		<make>OBJ_EXT = .o</make>
+		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_osx.sh</make>
 	</dependency>
 	<dependency platform="unix" not="macos">
-		<make>OBJ_EXT = o</make>
-		<make>LIB_EXT = a</make>
+		<make>OBJ_EXT = .o</make>
+		<make>LIB_EXT = .a</make>
 		<make>PACKAGE_SCRIPT = package_unix.sh</make>
 	</dependency>
 	
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/Makefile	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,61 @@
+#
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+#
+# Copyright 2013 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.
+#
+
+BUILD_ROOT = ../
+include ../config.mk
+
+# list of source files
+SRC  = allocator.c
+SRC += array_list.c
+SRC += basic_mempool.c
+SRC += buffer.c
+SRC += compare.c
+SRC += hash_key.c
+SRC += hash_map.c
+SRC += linked_list.c
+SRC += list.c
+SRC += map.c
+SRC += printf.c
+SRC += string.c
+SRC += utils.c
+
+OBJ   = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
+
+UCX_LIB = ../build/lib/libucx$(LIB_EXT)
+
+all: ../build/ucx $(UCX_LIB)
+
+$(UCX_LIB): $(OBJ)
+	$(AR) $(ARFLAGS) $(UCX_LIB) $(OBJ)
+
+../build/ucx:
+	mkdir -p ../build/ucx
+
+../build/ucx/%$(OBJ_EXT): %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/allocator.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,122 @@
+/*
+ * 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/allocator.h"
+
+__attribute__((__malloc__, __alloc_size__(2)))
+static void *cx_malloc_stdlib(
+        __attribute__((__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,
+        void *mem,
+        size_t n
+) {
+    return realloc(mem, n);
+}
+
+__attribute__((__malloc__, __alloc_size__(2, 3)))
+static void *cx_calloc_stdlib(
+        __attribute__((__unused__)) void *d,
+        size_t nelem,
+        size_t n
+) {
+    return calloc(nelem, n);
+}
+
+__attribute__((__nonnull__))
+static void cx_free_stdlib(
+        __attribute__((__unused__)) void *d,
+        void *mem
+) {
+    free(mem);
+}
+
+static cx_allocator_class cx_default_allocator_class = {
+        cx_malloc_stdlib,
+        cx_realloc_stdlib,
+        cx_calloc_stdlib,
+        cx_free_stdlib
+};
+
+struct cx_allocator_s cx_default_allocator = {
+        &cx_default_allocator_class,
+        NULL
+};
+CxAllocator *cxDefaultAllocator = &cx_default_allocator;
+
+// IMPLEMENTATION OF HIGH LEVEL API
+
+void *cxMalloc(
+        CxAllocator const *allocator,
+        size_t n
+) {
+    return allocator->cl->malloc(allocator->data, n);
+}
+
+void *cxRealloc(
+        CxAllocator const *allocator,
+        void *mem,
+        size_t n
+) {
+    return allocator->cl->realloc(allocator->data, mem, n);
+}
+
+int cxReallocate(
+        CxAllocator const *allocator,
+        void **mem,
+        size_t n
+) {
+    void *nmem = allocator->cl->realloc(allocator->data, *mem, n);
+    if (nmem == NULL) {
+        return 1;
+    } else {
+        *mem = nmem;
+        return 0;
+    }
+}
+
+void *cxCalloc(
+        CxAllocator const *allocator,
+        size_t nelem,
+        size_t n
+) {
+    return allocator->cl->calloc(allocator->data, nelem, n);
+}
+
+void cxFree(
+        CxAllocator const *allocator,
+        void *mem
+) {
+    allocator->cl->free(allocator->data, mem);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/array_list.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,547 @@
+/*
+ * 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/array_list.h"
+#include <assert.h>
+#include <string.h>
+
+// LOW LEVEL ARRAY LIST FUNCTIONS
+
+enum cx_array_copy_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        void const *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) {
+    // assert pointers
+    assert(target != NULL);
+    assert(size != NULL);
+    assert(src != NULL);
+
+    // determine capacity
+    size_t cap = capacity == NULL ? *size : *capacity;
+
+    // check if resize is required
+    size_t minsize = index + elem_count;
+    size_t newsize = *size < minsize ? minsize : *size;
+    bool needrealloc = newsize > cap;
+
+    // reallocate if possible
+    if (needrealloc) {
+        // a reallocator and a capacity variable must be available
+        if (reallocator == NULL || capacity == NULL) {
+            return CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED;
+        }
+
+        // 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;
+
+        // calculate new capacity (next number divisible by 16)
+        cap = newsize - (newsize % 16) + 16;
+        assert(cap > newsize);
+
+        // perform reallocation
+        void *newmem = reallocator->realloc(
+                *target, cap, elem_size, reallocator
+        );
+        if (newmem == NULL) {
+            return CX_ARRAY_COPY_REALLOC_FAILED;
+        }
+
+        // repair src pointer, if necessary
+        if (repairsrc) {
+            src = ((char *) newmem) + (srcaddr - targetaddr);
+        }
+
+        // store new pointer and capacity
+        *target = newmem;
+        *capacity = cap;
+    }
+
+    // determine target pointer
+    char *start = *target;
+    start += index * elem_size;
+
+    // copy elements and set new size
+    memmove(start, src, elem_count * elem_size);
+    *size = newsize;
+
+    // return successfully
+    return CX_ARRAY_COPY_SUCCESS;
+}
+
+#ifndef CX_ARRAY_SWAP_SBO_SIZE
+#define CX_ARRAY_SWAP_SBO_SIZE 512
+#endif
+
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+) {
+    assert(arr != NULL);
+
+    // short circuit
+    if (idx1 == idx2) return;
+
+    char sbo_mem[CX_ARRAY_SWAP_SBO_SIZE];
+    void *tmp;
+
+    // decide if we can use the local buffer
+    if (elem_size > CX_ARRAY_SWAP_SBO_SIZE) {
+        tmp = malloc(elem_size);
+        // we don't want to enforce error handling
+        if (tmp == NULL) abort();
+    } else {
+        tmp = sbo_mem;
+    }
+
+    // calculate memory locations
+    char *left = arr, *right = arr;
+    left += idx1 * elem_size;
+    right += idx2 * elem_size;
+
+    // three-way swap
+    memcpy(tmp, left, elem_size);
+    memcpy(left, right, elem_size);
+    memcpy(right, tmp, elem_size);
+
+    // free dynamic memory, if it was needed
+    if (tmp != sbo_mem) {
+        free(tmp);
+    }
+}
+
+// HIGH LEVEL ARRAY LIST FUNCTIONS
+
+typedef struct {
+    struct cx_list_s base;
+    void *data;
+    size_t capacity;
+    struct cx_array_reallocator_s reallocator;
+} cx_array_list;
+
+static void *cx_arl_realloc(
+        void *array,
+        size_t capacity,
+        size_t elem_size,
+        struct cx_array_reallocator_s *alloc
+) {
+    // retrieve the pointer to the list allocator
+    CxAllocator const *al = alloc->ptr1;
+
+    // use the list allocator to reallocate the memory
+    return cxRealloc(al, array, capacity * elem_size);
+}
+
+static void cx_arl_destructor(struct cx_list_s *list) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    char *ptr = arl->data;
+
+    if (list->simple_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+    if (list->advanced_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+
+    cxFree(list->allocator, arl->data);
+    cxFree(list->allocator, list);
+}
+
+static size_t cx_arl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    // out of bounds and special case check
+    if (index > list->size || n == 0) return 0;
+
+    // get a correctly typed pointer to the list
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // do we need to move some elements?
+    if (index < list->size) {
+        char const *first_to_move = (char const *) arl->data;
+        first_to_move += index * list->item_size;
+        size_t elems_to_move = list->size - index;
+        size_t start_of_moved = index + n;
+
+        if (CX_ARRAY_COPY_SUCCESS != cx_array_copy(
+                &arl->data,
+                &list->size,
+                &arl->capacity,
+                start_of_moved,
+                first_to_move,
+                list->item_size,
+                elems_to_move,
+                &arl->reallocator
+        )) {
+            // if moving existing elems is unsuccessful, abort
+            return 0;
+        }
+    }
+
+    // note that if we had to move the elements, the following operation
+    // is guaranteed to succeed, because we have the memory already allocated
+    // therefore, it is impossible to leave this function with an invalid array
+
+    // place the new elements
+    if (CX_ARRAY_COPY_SUCCESS == cx_array_copy(
+            &arl->data,
+            &list->size,
+            &arl->capacity,
+            index,
+            array,
+            list->item_size,
+            n,
+            &arl->reallocator
+    )) {
+        return n;
+    } else {
+        // array list implementation is "all or nothing"
+        return 0;
+    }
+}
+
+static int cx_arl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return 1 != cx_arl_insert_array(list, index, element, 1);
+}
+
+static int cx_arl_insert_iter(
+        struct cx_mut_iterator_s *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    if (iter->index < list->size) {
+        int result = cx_arl_insert_element(
+                list,
+                iter->index + 1 - prepend,
+                elem
+        );
+        if (result == 0 && prepend != 0) {
+            iter->index++;
+            iter->elem_handle = ((char *) iter->elem_handle) + list->item_size;
+        }
+        return result;
+    } else {
+        int result = cx_arl_insert_element(list, list->size, elem);
+        iter->index = list->size;
+        return result;
+    }
+}
+
+static int cx_arl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    cx_array_list *arl = (cx_array_list *) list;
+
+    // out-of-bounds check
+    if (index >= list->size) {
+        return 1;
+    }
+
+    // content destruction
+    cx_invoke_destructor(list, ((char *) arl->data) + index * list->item_size);
+
+    // short-circuit removal of last element
+    if (index == list->size - 1) {
+        list->size--;
+        return 0;
+    }
+
+    // just move the elements starting at index to the left
+    int result = cx_array_copy(
+            &arl->data,
+            &list->size,
+            &arl->capacity,
+            index,
+            ((char *) arl->data) + (index + 1) * list->item_size,
+            list->item_size,
+            list->size - index - 1,
+            &arl->reallocator
+    );
+    if (result == 0) {
+        // decrease the size
+        list->size--;
+    }
+    return result;
+}
+
+static void cx_arl_clear(struct cx_list_s *list) {
+    if (list->size == 0) return;
+
+    cx_array_list *arl = (cx_array_list *) list;
+    char *ptr = arl->data;
+
+    if (list->simple_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_simple_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+    if (list->advanced_destructor) {
+        for (size_t i = 0; i < list->size; i++) {
+            cx_invoke_advanced_destructor(list, ptr);
+            ptr += list->item_size;
+        }
+    }
+
+    memset(arl->data, 0, list->size * list->item_size);
+    list->size = 0;
+}
+
+static int cx_arl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->size || j >= list->size) return 1;
+    cx_array_list *arl = (cx_array_list *) list;
+    cx_array_swap(arl->data, list->item_size, i, j);
+    return 0;
+}
+
+static void *cx_arl_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    if (index < list->size) {
+        cx_array_list const *arl = (cx_array_list const *) list;
+        char *space = arl->data;
+        return space + index * list->item_size;
+    } else {
+        return NULL;
+    }
+}
+
+static ssize_t cx_arl_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    assert(list->cmpfunc != NULL);
+    assert(list->size < SIZE_MAX / 2);
+    char *cur = ((cx_array_list const *) list)->data;
+
+    for (ssize_t i = 0; i < (ssize_t) list->size; i++) {
+        if (0 == list->cmpfunc(elem, cur)) {
+            return i;
+        }
+        cur += list->item_size;
+    }
+
+    return -1;
+}
+
+static void cx_arl_sort(struct cx_list_s *list) {
+    assert(list->cmpfunc != NULL);
+    qsort(((cx_array_list *) list)->data,
+          list->size,
+          list->item_size,
+          list->cmpfunc
+    );
+}
+
+static int cx_arl_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    assert(list->cmpfunc != NULL);
+    if (list->size == other->size) {
+        char const *left = ((cx_array_list const *) list)->data;
+        char const *right = ((cx_array_list const *) other)->data;
+        for (size_t i = 0; i < list->size; i++) {
+            int d = list->cmpfunc(left, right);
+            if (d != 0) {
+                return d;
+            }
+            left += list->item_size;
+            right += other->item_size;
+        }
+        return 0;
+    } else {
+        return list->size < other->size ? -1 : 1;
+    }
+}
+
+static void cx_arl_reverse(struct cx_list_s *list) {
+    if (list->size < 2) return;
+    void *data = ((cx_array_list const *) list)->data;
+    size_t half = list->size / 2;
+    for (size_t i = 0; i < half; i++) {
+        cx_array_swap(data, list->item_size, i, list->size - 1 - i);
+    }
+}
+
+static bool cx_arl_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_list_s const *list = iter->src_handle;
+    return iter->index < list->size;
+}
+
+static void *cx_arl_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle;
+}
+
+static void cx_arl_iter_next(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        struct cx_mut_iterator_s *iter = it;
+        itbase->remove = false;
+        cx_arl_remove(iter->src_handle, iter->index);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index++;
+        iter->elem_handle =
+                ((char *) iter->elem_handle)
+                + ((struct cx_list_s const *) iter->src_handle)->item_size;
+    }
+}
+
+static void cx_arl_iter_prev(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    struct cx_mut_iterator_s *iter = it;
+    cx_array_list *const list = iter->src_handle;
+    if (itbase->remove) {
+        itbase->remove = false;
+        cx_arl_remove(iter->src_handle, iter->index);
+    }
+    iter->index--;
+    if (iter->index < list->base.size) {
+        iter->elem_handle = ((char *) list->data)
+                            + iter->index * list->base.item_size;
+    }
+}
+
+static bool cx_arl_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static struct cx_iterator_s cx_arl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter;
+
+    iter.index = index;
+    iter.src_handle = list;
+    iter.elem_handle = cx_arl_at(list, index);
+    iter.base.valid = cx_arl_iter_valid;
+    iter.base.current = cx_arl_iter_current;
+    iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next;
+    iter.base.flag_removal = cx_arl_iter_flag_rm;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    return iter;
+}
+
+static cx_list_class cx_array_list_class = {
+        cx_arl_destructor,
+        cx_arl_insert_element,
+        cx_arl_insert_array,
+        cx_arl_insert_iter,
+        cx_arl_remove,
+        cx_arl_clear,
+        cx_arl_swap,
+        cx_arl_at,
+        cx_arl_find,
+        cx_arl_sort,
+        cx_arl_compare,
+        cx_arl_reverse,
+        cx_arl_iterator,
+};
+
+CxList *cxArrayListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size,
+        size_t initial_capacity
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_array_list *list = cxCalloc(allocator, 1, sizeof(cx_array_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_array_list_class;
+    list->base.allocator = allocator;
+    list->base.cmpfunc = comparator;
+    list->capacity = initial_capacity;
+
+    if (item_size > 0) {
+        list->base.item_size = item_size;
+    } else {
+        item_size = sizeof(void *);
+        cxListStorePointers((CxList *) list);
+    }
+
+    // allocate the array after the real item_size is known
+    list->data = cxCalloc(allocator, initial_capacity, item_size);
+    if (list->data == NULL) {
+        cxFree(allocator, list);
+        return NULL;
+    }
+
+    // configure the reallocator
+    list->reallocator.realloc = cx_arl_realloc;
+    list->reallocator.ptr1 = (void *) allocator;
+
+    return (CxList *) list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/basic_mempool.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,235 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cx/basic_mempool.h"
+#include "cx/utils.h"
+#include <string.h>
+
+#define of_chk_(n) if (SIZE_MAX - sizeof(cx_destructor_func) < (n)) return NULL
+
+/** Internal structure for denoting pooled memory. */
+typedef struct {
+    /** The destructor. */
+    cx_destructor_func destructor;
+    /**
+     * Access to the first byte of the polled memory.
+     */
+    char c;
+} cx_basic_mempool_memory;
+
+static int cx_basic_mempool_chcap(
+        struct cx_basic_mempool_s *pool,
+        size_t newcap
+) {
+    if (newcap < pool->ndata) {
+        return 1;
+    }
+
+    size_t newcapsz;
+    if (cx_szmul(newcap, sizeof(void *), &newcapsz)) {
+        return 1;
+    }
+
+    void **data = realloc(pool->data, newcapsz);
+    if (data) {
+        pool->data = data;
+        pool->size = newcap;
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+void *cx_malloc_basic_mempool(
+        void *data,
+        size_t n
+) {
+    of_chk_(n);
+    struct cx_basic_mempool_s *pool = data;
+
+    if (pool->ndata >= pool->size) {
+        size_t newcap = pool->size * 2;
+        if (newcap < pool->size || cx_basic_mempool_chcap(pool, newcap)) {
+            return NULL;
+        }
+    }
+
+    cx_basic_mempool_memory *mem = malloc(sizeof(cx_destructor_func) + n);
+    if (mem == NULL) {
+        return NULL;
+    }
+
+    mem->destructor = NULL;
+    pool->data[pool->ndata] = mem;
+    pool->ndata++;
+
+    return &(mem->c);
+}
+
+void *cx_calloc_basic_mempool(
+        void *data,
+        size_t nelem,
+        size_t elsize
+) {
+    size_t msz;
+    if (cx_szmul(nelem, elsize, &msz)) {
+        return NULL;
+    }
+    void *ptr = cx_malloc_basic_mempool(data, msz);
+    if (ptr == NULL) {
+        return NULL;
+    }
+    memset(ptr, 0, nelem * elsize);
+    return ptr;
+}
+
+void *cx_realloc_basic_mempool(
+        void *data,
+        void *ptr,
+        size_t n
+) {
+    of_chk_(n);
+    struct cx_basic_mempool_s *pool = data;
+
+    char *mem = ((char *) ptr) - sizeof(cx_destructor_func);
+    char *newm = (char *) realloc(mem, n + sizeof(cx_destructor_func));
+    if (newm == NULL) {
+        return NULL;
+    }
+    if (mem != newm) {
+        cx_for_n(i, pool->ndata) {
+            if (pool->data[i] == mem) {
+                pool->data[i] = newm;
+                return newm + sizeof(cx_destructor_func);
+            }
+        }
+        abort();
+    } else {
+        return newm + sizeof(cx_destructor_func);
+    }
+}
+
+void cx_free_basic_mempool(
+        void *data,
+        void *ptr
+) {
+    struct cx_basic_mempool_s *pool = data;
+
+    cx_basic_mempool_memory *mem = (cx_basic_mempool_memory *)
+            ((char *) ptr - sizeof(cx_destructor_func));
+    cx_for_n(i, pool->ndata) {
+        if (mem == pool->data[i]) {
+            if (mem->destructor != NULL) {
+                mem->destructor(&(mem->c));
+            }
+            free(mem);
+            size_t last_index = pool->ndata - 1;
+            if (i != last_index) {
+                pool->data[i] = pool->data[last_index];
+                pool->data[last_index] = NULL;
+            }
+            pool->ndata--;
+            return;
+        }
+    }
+    abort();
+}
+
+void cx_basic_mempool_destroy(CxMempool *p) {
+    struct cx_basic_mempool_s *pool = (struct cx_basic_mempool_s *) p;
+    cx_basic_mempool_memory *mem;
+    cx_for_n(i, pool->ndata) {
+        mem = (cx_basic_mempool_memory *) pool->data[i];
+        if (mem) {
+            if (mem->destructor) {
+                mem->destructor(&(mem->c));
+            }
+            free(mem);
+        }
+    }
+    free(pool->data);
+    free((void *) p->allocator);
+    free(pool);
+}
+
+void cx_basic_mempool_set_destr(
+        __attribute__((__unused__)) CxMempool *pool,
+        void *ptr,
+        cx_destructor_func func
+) {
+    *(cx_destructor_func *) ((char *) ptr - sizeof(cx_destructor_func)) = func;
+}
+
+static cx_allocator_class cx_basic_mempool_allocator_class = {
+        cx_malloc_basic_mempool,
+        cx_realloc_basic_mempool,
+        cx_calloc_basic_mempool,
+        cx_free_basic_mempool
+};
+
+static cx_mempool_class cx_basic_mempool_class = {
+        cx_basic_mempool_destroy,
+        cx_basic_mempool_set_destr,
+};
+
+CxMempool *cxBasicMempoolCreate(size_t capacity) {
+    size_t poolsize;
+    if (cx_szmul(capacity, sizeof(void *), &poolsize)) {
+        return NULL;
+    }
+
+    struct cx_basic_mempool_s *pool =
+            malloc(sizeof(struct cx_basic_mempool_s));
+    if (pool == NULL) {
+        return NULL;
+    }
+
+
+    CxAllocator *provided_allocator = malloc(sizeof(CxAllocator));
+    if (!provided_allocator) {
+        free(pool);
+        return NULL;
+    }
+    provided_allocator->cl = &cx_basic_mempool_allocator_class;
+    provided_allocator->data = pool;
+
+    pool->base.cl = &cx_basic_mempool_class;
+    pool->base.allocator = provided_allocator;
+
+    pool->data = malloc(poolsize);
+    if (pool->data == NULL) {
+        free(provided_allocator);
+        free(pool);
+        return NULL;
+    }
+
+    pool->ndata = 0;
+    pool->size = capacity;
+
+    return (CxMempool *) pool;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/buffer.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,415 @@
+/*
+ * 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/buffer.h"
+#include "cx/utils.h"
+
+#include <stdio.h>
+#include <string.h>
+
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+) {
+    if (allocator == NULL) allocator = cxDefaultAllocator;
+    buffer->allocator = allocator;
+    buffer->flags = flags;
+    if (!space) {
+        buffer->bytes = cxMalloc(allocator, capacity);
+        if (buffer->bytes == NULL) {
+            return 1;
+        }
+        buffer->flags |= CX_BUFFER_FREE_CONTENTS;
+    } else {
+        buffer->bytes = space;
+    }
+    buffer->capacity = capacity;
+    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;
+
+    return 0;
+}
+
+void cxBufferDestroy(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
+    }
+}
+
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+) {
+    CxBuffer *buf = cxMalloc(allocator, sizeof(CxBuffer));
+    if (buf == NULL) return NULL;
+    if (0 == cxBufferInit(buf, space, capacity, allocator, flags)) {
+        return buf;
+    } else {
+        cxFree(allocator, buf);
+        return NULL;
+    }
+}
+
+void cxBufferFree(CxBuffer *buffer) {
+    if ((buffer->flags & CX_BUFFER_FREE_CONTENTS) == CX_BUFFER_FREE_CONTENTS) {
+        cxFree(buffer->allocator, buffer->bytes);
+    }
+    cxFree(buffer->allocator, buffer);
+}
+
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+) {
+    size_t npos;
+    switch (whence) {
+        case SEEK_CUR:
+            npos = buffer->pos;
+            break;
+        case SEEK_END:
+            npos = buffer->size;
+            break;
+        case SEEK_SET:
+            npos = 0;
+            break;
+        default:
+            return -1;
+    }
+
+    size_t opos = npos;
+    npos += offset;
+
+    if ((offset > 0 && npos < opos) || (offset < 0 && npos > opos)) {
+        return -1;
+    }
+
+    if (npos >= buffer->size) {
+        return -1;
+    } else {
+        buffer->pos = npos;
+        return 0;
+    }
+
+}
+
+void cxBufferClear(CxBuffer *buffer) {
+    memset(buffer->bytes, 0, buffer->size);
+    buffer->size = 0;
+    buffer->pos = 0;
+}
+
+int cxBufferEof(CxBuffer const *buffer) {
+    return buffer->pos >= buffer->size;
+}
+
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t newcap
+) {
+    if (newcap <= buffer->capacity) {
+        return 0;
+    }
+
+    if (cxReallocate(buffer->allocator,
+                     (void **) &buffer->bytes, newcap) == 0) {
+        buffer->capacity = newcap;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+/**
+ * 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,
+        unsigned char const *space,
+        size_t size,
+        size_t nitems
+) {
+    size_t pos = 0;
+    size_t remaining = nitems;
+    size_t max_items = buffer->flush_blksize / size;
+    while (remaining > 0) {
+        size_t items = remaining > max_items ? max_items : remaining;
+        size_t flushed = buffer->flush_func(
+                space + pos,
+                size, items,
+                buffer->flush_target);
+        if (flushed > 0) {
+            pos += (flushed * size);
+            remaining -= flushed;
+        } else {
+            // if no bytes can be flushed out anymore, we give up
+            break;
+        }
+    }
+    return nitems - remaining;
+}
+
+size_t cxBufferWrite(
+        void const *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    // optimize for easy case
+    if (size == 1 && (buffer->capacity - buffer->pos) >= nitems) {
+        memcpy(buffer->bytes + buffer->pos, ptr, nitems);
+        buffer->pos += nitems;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems;
+    }
+
+    size_t len;
+    size_t nitems_out = nitems;
+    if (cx_szmul(size, nitems, &len)) {
+        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) {
+                perform_flush = true;
+            } else {
+                if (cxBufferMinimumCapacity(buffer, required)) {
+                    return 0;
+                }
+            }
+        } else {
+            if (buffer->flush_blkmax > 0) {
+                perform_flush = true;
+            } else {
+                // truncate data to be written, if we can neither extend nor flush
+                len = buffer->capacity - buffer->pos;
+                if (size > 1) {
+                    len -= len % size;
+                }
+                nitems_out = len / size;
+            }
+        }
+    }
+
+    if (len == 0) {
+        return len;
+    }
+
+    if (perform_flush) {
+        size_t flush_max;
+        if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
+            return 0;
+        }
+        size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
+                           ? buffer->pos
+                           : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
+        if (flush_pos == buffer->pos) {
+            // entire buffer has been flushed, we can reset
+            buffer->size = buffer->pos = 0;
+
+            size_t items_flush; // how many items can also be directly flushed
+            size_t items_keep; // how many items have to be written to the buffer
+
+            items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
+            if (items_flush > 0) {
+                items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
+                // in case we could not flush everything, keep the rest
+            }
+            items_keep = nitems - items_flush;
+            if (items_keep > 0) {
+                // try again with the remaining stuff
+                unsigned char const *new_ptr = ptr;
+                new_ptr += items_flush * size;
+                // report the directly flushed items as written plus the remaining stuff
+                return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
+            } else {
+                // all items have been flushed - report them as written
+                return nitems;
+            }
+        } 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 {
+        memcpy(buffer->bytes + buffer->pos, ptr, len);
+        buffer->pos += len;
+        if (buffer->pos > buffer->size) {
+            buffer->size = buffer->pos;
+        }
+        return nitems_out;
+    }
+
+}
+
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+) {
+    c &= 0xFF;
+    unsigned char const ch = c;
+    if (cxBufferWrite(&ch, 1, 1, buffer) == 1) {
+        return c;
+    } else {
+        return EOF;
+    }
+}
+
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+) {
+    return cxBufferWrite(str, 1, strlen(str), buffer);
+}
+
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+) {
+    size_t len;
+    if (cx_szmul(size, nitems, &len)) {
+        return 0;
+    }
+    if (buffer->pos + len > buffer->size) {
+        len = buffer->size - buffer->pos;
+        if (size > 1) len -= len % size;
+    }
+
+    if (len <= 0) {
+        return len;
+    }
+
+    memcpy(ptr, buffer->bytes + buffer->pos, len);
+    buffer->pos += len;
+
+    return len / size;
+}
+
+int cxBufferGet(CxBuffer *buffer) {
+    if (cxBufferEof(buffer)) {
+        return EOF;
+    } else {
+        int c = buffer->bytes[buffer->pos];
+        buffer->pos++;
+        return c;
+    }
+}
+
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+) {
+    if (shift >= buffer->size) {
+        buffer->pos = buffer->size = 0;
+    } else {
+        memmove(buffer->bytes, buffer->bytes + shift, buffer->size - shift);
+        buffer->size -= shift;
+
+        if (buffer->pos >= shift) {
+            buffer->pos -= shift;
+        } else {
+            buffer->pos = 0;
+        }
+    }
+    return 0;
+}
+
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+) {
+    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 (cxBufferMinimumCapacity(buffer, req_capacity)) {
+                return 1;
+            }
+            movebytes = buffer->size;
+        } else {
+            movebytes = buffer->capacity - shift;
+        }
+    } else {
+        movebytes = buffer->size;
+    }
+
+    memmove(buffer->bytes + shift, buffer->bytes, movebytes);
+    buffer->size = shift + movebytes;
+
+    buffer->pos += shift;
+    if (buffer->pos > buffer->size) {
+        buffer->pos = buffer->size;
+    }
+
+    return 0;
+}
+
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+) {
+    if (shift < 0) {
+        return cxBufferShiftLeft(buffer, (size_t) (-shift));
+    } else if (shift > 0) {
+        return cxBufferShiftRight(buffer, (size_t) shift);
+    } else {
+        return 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/compare.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,201 @@
+/*
+ * 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/compare.h"
+
+#include <math.h>
+
+int cx_cmp_int(void const *i1, void const *i2) {
+    int a = *((const int *) i1);
+    int b = *((const int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longint(void const *i1, void const *i2) {
+    long int a = *((const long int *) i1);
+    long int b = *((const long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_longlong(void const *i1, void const *i2) {
+    long long a = *((const long long *) i1);
+    long long b = *((const long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int16(void const *i1, void const *i2) {
+    int16_t a = *((const int16_t *) i1);
+    int16_t b = *((const int16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int32(void const *i1, void const *i2) {
+    int32_t a = *((const int32_t *) i1);
+    int32_t b = *((const int32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_int64(void const *i1, void const *i2) {
+    int64_t a = *((const int64_t *) i1);
+    int64_t b = *((const int64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint(void const *i1, void const *i2) {
+    unsigned int a = *((const unsigned int *) i1);
+    unsigned int b = *((const unsigned int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulongint(void const *i1, void const *i2) {
+    unsigned long int a = *((const unsigned long int *) i1);
+    unsigned long int b = *((const unsigned long int *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_ulonglong(void const *i1, void const *i2) {
+    unsigned long long a = *((const unsigned long long *) i1);
+    unsigned long long b = *((const unsigned long long *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint16(void const *i1, void const *i2) {
+    uint16_t a = *((const uint16_t *) i1);
+    uint16_t b = *((const uint16_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint32(void const *i1, void const *i2) {
+    uint32_t a = *((const uint32_t *) i1);
+    uint32_t b = *((const uint32_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_uint64(void const *i1, void const *i2) {
+    uint64_t a = *((const uint64_t *) i1);
+    uint64_t b = *((const uint64_t *) i2);
+    if (a == b) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_float(void const *f1, void const *f2) {
+    float a = *((const float *) f1);
+    float b = *((const float *) f2);
+    if (fabsf(a - b) < 1e-6f) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_double(
+        void const *d1,
+        void const *d2
+) {
+    double a = *((const double *) d1);
+    double b = *((const double *) d2);
+    if (fabs(a - b) < 1e-14) {
+        return 0;
+    } else {
+        return a < b ? -1 : 1;
+    }
+}
+
+int cx_cmp_intptr(
+        void const *ptr1,
+        void const *ptr2
+) {
+    intptr_t p1 = *(const intptr_t *) ptr1;
+    intptr_t p2 = *(const intptr_t *) ptr2;
+    if (p1 == p2) {
+        return 0;
+    } else {
+        return p1 < p2 ? -1 : 1;
+    }
+}
+
+int cx_cmp_uintptr(
+        void const *ptr1,
+        void const *ptr2
+) {
+    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;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/allocator.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,225 @@
+/*
+ * 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 allocator.h
+ * Interface for custom allocators.
+ */
+
+#ifndef UCX_ALLOCATOR_H
+#define UCX_ALLOCATOR_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The class definition for an allocator.
+ */
+typedef struct {
+    /**
+     * The allocator's malloc() implementation.
+     */
+    void *(*malloc)(
+            void *data,
+            size_t n
+    );
+
+    /**
+     * The allocator's realloc() implementation.
+     */
+    void *(*realloc)(
+            void *data,
+            void *mem,
+            size_t n
+    )
+    __attribute__((__warn_unused_result__));
+
+    /**
+     * The allocator's calloc() implementation.
+     */
+    void *(*calloc)(
+            void *data,
+            size_t nelem,
+            size_t n
+    );
+
+    /**
+     * The allocator's free() implementation.
+     */
+    void (*free)(
+            void *data,
+            void *mem
+    )
+    __attribute__((__nonnull__));
+} cx_allocator_class;
+
+/**
+ * Structure holding the data for an allocator.
+ */
+struct cx_allocator_s {
+    /**
+     * A pointer to the instance of the allocator class.
+     */
+    cx_allocator_class *cl;
+    /**
+     * A pointer to the data this allocator uses.
+     */
+    void *data;
+};
+
+/**
+ * High-Level type alias for the allocator type.
+ */
+typedef struct cx_allocator_s CxAllocator;
+
+/**
+ * A default allocator using standard library malloc() etc.
+ */
+extern CxAllocator *cxDefaultAllocator;
+
+/**
+ * Function pointer type for destructor functions.
+ *
+ * A destructor function deallocates possible contents and MAY free the memory
+ * pointed to by \p memory. Read the documentation of the respective function
+ * pointer to learn if a destructor SHALL, MAY, or MUST NOT free the memory in that
+ * particular implementation.
+ *
+ * @param memory a pointer to the object to destruct
+  */
+typedef void (*cx_destructor_func)(void *memory) __attribute__((__nonnull__));
+
+/**
+ * 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.
+ *
+ * @param data an optional pointer to custom data
+ * @param memory a pointer to the object to destruct
+  */
+typedef void (*cx_destructor_func2)(
+        void *data,
+        void *memory
+) __attribute__((__nonnull__(2)));
+
+/**
+ * Allocate \p n bytes of memory.
+ *
+ * @param allocator the allocator
+ * @param n the number of bytes
+ * @return a pointer to the allocated memory
+ */
+void *cxMalloc(
+        CxAllocator const *allocator,
+        size_t n
+)
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2)));
+
+/**
+ * Re-allocate the previously allocated block in \p mem, making the new block \p n bytes long.
+ * This function may return the same pointer that was passed to it, if moving the memory
+ * was not necessary.
+ *
+ * \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
+ */
+void *cxRealloc(
+        CxAllocator const *allocator,
+        void *mem,
+        size_t n
+)
+__attribute__((__warn_unused_result__))
+__attribute__((__alloc_size__(3)));
+
+/**
+ * Re-allocate a previously allocated block and changes the pointer in-place, if necessary.
+ * This function acts like cxRealloc() using the pointer pointed to by \p mem.
+ * On success, the pointer is changed to the new location (in case the
+ *
+ * \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
+ */
+int cxReallocate(
+        CxAllocator const *allocator,
+        void **mem,
+        size_t n
+)
+__attribute__((__nonnull__));
+
+/**
+ * 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
+ */
+void *cxCalloc(
+        CxAllocator const *allocator,
+        size_t nelem,
+        size_t n
+)
+__attribute__((__malloc__))
+__attribute__((__alloc_size__(2, 3)));
+
+/**
+ * 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
+ */
+void cxFree(
+        CxAllocator const *allocator,
+        void *mem
+)
+__attribute__((__nonnull__));
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ALLOCATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/array_list.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,194 @@
+/*
+ * 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 array_list.h
+ * \brief Array list implementation.
+ * \details Also provides several low-level functions for custom array list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_ARRAY_LIST_H
+#define UCX_ARRAY_LIST_H
+
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Defines a reallocation mechanism for arrays.
+ */
+struct cx_array_reallocator_s {
+    /**
+     * Re-allocates space for the given array.
+     *
+     * Implementations are not required to free the original array.
+     * This allows re-allocation of static memory by allocating heap memory
+     * and copying the array contents. The information in \p data can keep
+     * track of the state of the memory or other additional allocator info.
+     *
+     * @param array the array to reallocate
+     * @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
+     */
+    void *(*realloc)(
+            void *array,
+            size_t capacity,
+            size_t elem_size,
+            struct cx_array_reallocator_s *alloc
+    );
+
+    /**
+     * Custom data pointer.
+     */
+    void *ptr1;
+    /**
+     * Custom data pointer.
+     */
+    void *ptr2;
+    /**
+     * Custom data integer.
+     */
+    size_t int1;
+    /**
+     * Custom data integer.
+     */
+    size_t int2;
+};
+
+/**
+ * Return codes for cx_array_copy().
+ */
+enum cx_array_copy_result {
+    CX_ARRAY_COPY_SUCCESS,
+    CX_ARRAY_COPY_REALLOC_NOT_SUPPORTED,
+    CX_ARRAY_COPY_REALLOC_FAILED,
+};
+
+/**
+ * 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.
+ *
+ * If the capacity is insufficient to hold the new data, a reallocation
+ * attempt is made, unless the allocator is set to \c NULL, in which case
+ * this function ultimately returns a failure.
+ *
+ * @param target the target array
+ * @param size a pointer to the size of the target array
+ * @param capacity a pointer to the target array's capacity -
+ * \c NULL if only the size shall be used to bound the array
+ * @param index the index where the copied elements shall be placed
+ * @param src the source array
+ * @param elem_size the size of one element
+ * @param elem_count the number of elements to copy
+ * @param reallocator the array re-allocator to use, or \c NULL
+ * if re-allocation shall not happen
+ * @return zero on success, non-zero error code on failure
+ */
+enum cx_array_copy_result cx_array_copy(
+        void **target,
+        size_t *size,
+        size_t *capacity,
+        size_t index,
+        void const *src,
+        size_t elem_size,
+        size_t elem_count,
+        struct cx_array_reallocator_s *reallocator
+) __attribute__((__nonnull__(1, 2, 5)));
+
+
+/**
+ * Swaps two array elements.
+ *
+ * @param arr the array
+ * @param elem_size the element size
+ * @param idx1 index of first element
+ * @param idx2 index of second element
+ */
+void cx_array_swap(
+        void *arr,
+        size_t elem_size,
+        size_t idx1,
+        size_t idx2
+) __attribute__((__nonnull__));
+
+/**
+ * Allocates an array list for storing elements with \p item_size bytes each.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param allocator the allocator for allocating the list memory
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL sort and find functions will not work)
+ * @param item_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+CxList *cxArrayListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size,
+        size_t initial_capacity
+);
+
+/**
+ * Allocates an array list for storing elements with \p item_size bytes each.
+ *
+ * The list will use the cxDefaultAllocator and \em NO compare function.
+ * If you want to call functions that need a compare function, you have to
+ * set it immediately after creation or use cxArrayListCreate().
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param item_size the size of each element in bytes
+ * @param initial_capacity the initial number of elements the array can store
+ * @return the created list
+ */
+#define cxArrayListCreateSimple(item_size, initial_capacity) \
+    cxArrayListCreate(NULL, NULL, item_size, initial_capacity)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_ARRAY_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/basic_mempool.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,76 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * \file basic_mempool.h
+ * \brief Implementation of a basic memory pool.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_BASIC_MEMPOOL_H
+#define UCX_BASIC_MEMPOOL_H
+
+#include "mempool.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Basic array-based memory pool.
+ */
+struct cx_basic_mempool_s {
+    /** Inherit base structure members. */
+    CxMempool base;
+
+    /** List of pointers to pooled memory. */
+    void **data;
+
+    /** Number of pooled memory items. */
+    size_t ndata;
+
+    /** Memory pool size. */
+    size_t size;
+};
+
+/**
+ * Creates a basic array-based memory pool.
+ *
+ * @param capacity the initial capacity of the pool
+ * @return the created memory pool or \c NULL if allocation failed
+ */
+__attribute__((__warn_unused_result__))
+CxMempool *cxBasicMempoolCreate(size_t capacity);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_BASIC_MEMPOOL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/buffer.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,451 @@
+/*
+ * 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 buffer.h
+ *
+ * \brief Advanced buffer implementation.
+ *
+ * Instances of CxBuffer can be used to read from or to write to like one
+ * would do with a stream.
+ *
+ * Some features for convenient use of the buffer
+ * can be enabled. See the documentation of the macro constants for more
+ * information.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_BUFFER_H
+#define UCX_BUFFER_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/**
+ * No buffer features enabled (all flags cleared).
+ */
+#define CX_BUFFER_DEFAULT 0x00
+
+/**
+ * If this flag is enabled, the buffer will automatically free its contents when destroyed.
+ */
+#define CX_BUFFER_FREE_CONTENTS 0x01
+
+/**
+ * If this flag is enabled, the buffer will automatically extends its capacity.
+ */
+#define CX_BUFFER_AUTO_EXTEND 0x02
+
+/** Structure for the UCX buffer data. */
+typedef struct {
+    /** A pointer to the buffer contents. */
+    union {
+        /**
+         * Data is interpreted as text.
+         */
+        char *space;
+        /**
+         * Data is interpreted as binary.
+         */
+        unsigned char *bytes;
+    };
+    /** The allocator to use for automatic memory management. */
+    CxAllocator const *allocator;
+    /** Current position of the buffer. */
+    size_t pos;
+    /** Current capacity (i.e. maximum size) of the buffer. */
+    size_t capacity;
+    /** Current size of the buffer content. */
+    size_t size;
+    /**
+     * The buffer may not extend beyond this threshold before starting to flush.
+     * Default is \c SIZE_MAX (flushing disabled when auto extension is enabled).
+     */
+    size_t flush_threshold;
+    /**
+     * The block size for the elements to flush.
+     * Default is 4096 bytes.
+     */
+    size_t flush_blksize;
+    /**
+     * The maximum number of blocks to flush in one cycle.
+     * Zero disables flushing entirely (this is the default).
+     * Set this to \c SIZE_MAX to flush the entire buffer.
+     *
+     * @attention if the maximum number of blocks multiplied with the block size
+     * is smaller than the expected contents written to this buffer within one write
+     * operation, multiple flush cycles are performed after that write.
+     * That means the total number of blocks flushed after one write to this buffer may
+     * be larger than \c flush_blkmax.
+     */
+    size_t flush_blkmax;
+
+    /**
+     * The write function used for flushing.
+     * If NULL, the flushed content gets discarded.
+     */
+    cx_write_func flush_func;
+
+    /**
+     * The target for \c flush_func.
+     */
+    void *flush_target;
+
+    /**
+     * Flag register for buffer features.
+     * @see #CX_BUFFER_DEFAULT
+     * @see #CX_BUFFER_FREE_CONTENTS
+     * @see #CX_BUFFER_AUTO_EXTEND
+     */
+    int flags;
+} cx_buffer_s;
+
+/**
+ * UCX buffer.
+ */
+typedef cx_buffer_s CxBuffer;
+
+/**
+ * Initializes a fresh buffer.
+ *
+ * \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 buffer the buffer to initialize
+ * @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.
+ * @param flags buffer features (see cx_buffer_s.flags)
+ * @return zero on success, non-zero if a required allocation failed
+ */
+__attribute__((__nonnull__(1)))
+int cxBufferInit(
+        CxBuffer *buffer,
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+);
+
+/**
+ * Allocates and initializes a fresh buffer.
+ *
+ * \note You may provide \c NULL as argument for \p space.
+ * Then this function will allocate the space and enforce
+ * the #CX_BUFFER_FREE_CONTENTS flag.
+ *
+ * @param space pointer to the memory area, or \c NULL to allocate
+ * new memory
+ * @param capacity the capacity of the buffer
+ * @param allocator the allocator to use for allocating the structure and the automatic
+ * memory management within the buffer. If \c NULL, the 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
+ */
+CxBuffer *cxBufferCreate(
+        void *space,
+        size_t capacity,
+        CxAllocator const *allocator,
+        int flags
+);
+
+/**
+ * Destroys the buffer contents.
+ *
+ * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled.
+ * If you want to free the memory of the entire buffer, use cxBufferFree().
+ *
+ * @param buffer the buffer which contents shall be destroyed
+ * @see cxBufferInit()
+ */
+__attribute__((__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().
+ *
+ * @param buffer the buffer to deallocate
+ * @see cxBufferCreate()
+ */
+__attribute__((__nonnull__))
+void cxBufferFree(CxBuffer *buffer);
+
+/**
+ * Shifts the contents of the buffer by the given offset.
+ *
+ * If the offset is positive, the contents are shifted to the right.
+ * If auto extension is enabled, the buffer grows, if necessary.
+ * In case the auto extension fails, this function returns a non-zero value and
+ * no contents are changed.
+ * If auto extension is disabled, the contents that do not fit into the buffer
+ * are discarded.
+ *
+ * If the offset is negative, the contents are shifted to the left where the
+ * first \p shift bytes are discarded.
+ * The new size of the buffer is the old size minus the absolute shift value.
+ * If this value is larger than the buffer size, the buffer is emptied (but
+ * not cleared, see the security note below).
+ *
+ * The buffer position gets shifted alongside with the content but is kept
+ * within the boundaries of the buffer.
+ *
+ * \note For situations where \c off_t is not large enough, there are specialized cxBufferShiftLeft() and
+ * cxBufferShiftRight() functions using a \c size_t as parameter type.
+ *
+ * \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>
+ * for a left shift.
+ *
+ * @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
+ */
+__attribute__((__nonnull__))
+int cxBufferShift(
+        CxBuffer *buffer,
+        off_t shift
+);
+
+/**
+ * Shifts the buffer to the right.
+ * See cxBufferShift() for details.
+ *
+ * @param buffer the buffer
+ * @param shift the shift offset
+ * @return 0 on success, non-zero if a required auto-extension fails
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftRight(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+/**
+ * Shifts the buffer to the left.
+ * See cxBufferShift() for details.
+ *
+ * \note Since a left shift cannot fail due to memory allocation problems, this
+ * function always returns zero.
+ *
+ * @param buffer the buffer
+ * @param shift the positive shift offset
+ * @return always zero
+ * @see cxBufferShift()
+ */
+__attribute__((__nonnull__))
+int cxBufferShiftLeft(
+        CxBuffer *buffer,
+        size_t shift
+);
+
+
+/**
+ * Moves the position of the buffer.
+ *
+ * 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.
+ *
+ * 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).
+ *
+ * @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
+ *
+ */
+__attribute__((__nonnull__))
+int cxBufferSeek(
+        CxBuffer *buffer,
+        off_t offset,
+        int whence
+);
+
+/**
+ * Clears the buffer by resetting the position and deleting the data.
+ *
+ * The data is deleted by zeroing it with a call to memset().
+ *
+ * @param buffer the buffer to be cleared
+ */
+__attribute__((__nonnull__))
+void cxBufferClear(CxBuffer *buffer);
+
+/**
+ * Tests, if the buffer position has exceeded the buffer capacity.
+ *
+ * @param buffer the buffer to test
+ * @return non-zero, if the current buffer position has exceeded the last
+ * available byte of the buffer.
+ */
+__attribute__((__nonnull__))
+int cxBufferEof(CxBuffer const *buffer);
+
+
+/**
+ * Ensures that the buffer has a minimum capacity.
+ *
+ * If the current capacity is not sufficient, the buffer will be extended.
+ *
+ * @param buffer the buffer
+ * @param capacity the minimum required capacity for this buffer
+ * @return 0 on success or a non-zero value on failure
+ */
+__attribute__((__nonnull__))
+int cxBufferMinimumCapacity(
+        CxBuffer *buffer,
+        size_t capacity
+);
+
+/**
+ * Writes data to a CxBuffer.
+ *
+ * If flushing is enabled and the buffer needs to flush, the data is flushed to
+ * the target until the target signals that it cannot take more data by
+ * returning zero via the respective write function. In that case, the remaining
+ * data in this buffer is shifted to the beginning of this buffer so that the
+ * newly available space can be used to append as much data as possible. This
+ * function only stops writing more elements, when the flush target and this
+ * buffer are both incapable of taking more data or all data has been written.
+ * The number returned by this function is the total number of elements that
+ * could be written during the process. It does not necessarily mean that those
+ * elements are still in this buffer, because some of them could have also be
+ * flushed already.
+ *
+ * If automatic flushing is not enabled, the position of the buffer is increased
+ * by the number of bytes written.
+ *
+ * \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
+ */
+__attribute__((__nonnull__))
+size_t cxBufferWrite(
+        void const *ptr,
+        size_t size,
+        size_t nitems,
+        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.
+ *
+ * @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
+ */
+__attribute__((__nonnull__))
+size_t cxBufferRead(
+        void *ptr,
+        size_t size,
+        size_t nitems,
+        CxBuffer *buffer
+);
+
+/**
+ * Writes a character to a buffer.
+ *
+ * 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.
+ *
+ * On successful write, the position of the buffer is increased.
+ *
+ * @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
+ * reached and automatic extension is not enabled or not possible
+ */
+__attribute__((__nonnull__))
+int cxBufferPut(
+        CxBuffer *buffer,
+        int c
+);
+
+/**
+ * Writes a string to a buffer.
+ *
+ * @param buffer the buffer
+ * @param str the zero-terminated string
+ * @return the number of bytes written
+ */
+__attribute__((__nonnull__))
+size_t cxBufferPutString(
+        CxBuffer *buffer,
+        const char *str
+);
+
+/**
+ * Gets a character from a buffer.
+ *
+ * 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
+ */
+__attribute__((__nonnull__))
+int cxBufferGet(CxBuffer *buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_BUFFER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/collection.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,138 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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 collection.h
+ * \brief Common definitions for various collection implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COLLECTION_H
+#define UCX_COLLECTION_H
+
+#include "allocator.h"
+#include "iterator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Special constant used for creating collections that are storing pointers.
+ */
+#define CX_STORE_POINTERS 0
+
+/**
+ * A comparator function comparing two collection elements.
+ */
+typedef int(*cx_compare_func)(
+        void const *left,
+        void const *right
+);
+
+/**
+ * Use this macro to declare common members for a collection structure.
+ */
+#define CX_COLLECTION_MEMBERS \
+    /** \
+     * The allocator to use. \
+     */ \
+    CxAllocator const *allocator; \
+    /** \
+     * The comparator function for the elements. \
+     */ \
+    cx_compare_func cmpfunc; \
+    /** \
+     * The size of each element. \
+     */ \
+    size_t item_size; \
+    /** \
+     * The number of currently stored elements. \
+     */ \
+    size_t size; \
+    /** \
+     * An optional simple destructor for the collection's elements. \
+     * \
+     * @attention Read the documentation of the particular collection implementation \
+     * whether this destructor shall only destroy the contents or also free the memory. \
+     */ \
+    cx_destructor_func simple_destructor; \
+    /** \
+     * An optional advanced destructor for the collection's elements. \
+     * \
+     * @attention Read the documentation of the particular collection implementation \
+     * whether this destructor shall only destroy the contents or also free the memory. \
+     */ \
+    cx_destructor_func2 advanced_destructor; \
+    /** \
+     * The pointer to additional data that is passed to the advanced destructor. \
+     */ \
+    void *destructor_data; \
+    /** \
+     * Indicates if this instance of a collection is supposed to store pointers \
+     * instead of copies of the actual objects. \
+     */ \
+    bool store_pointer;
+
+/**
+ * Invokes the simple destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_simple_destructor(c, e) \
+    (c)->simple_destructor((c)->store_pointer ? (*((void **) (e))) : (e))
+
+/**
+ * Invokes the advanced destructor function for a specific element.
+ *
+ * Usually only used by collection implementations. There should be no need
+ * to invoke this macro manually.
+ *
+ * @param c the collection
+ * @param e the element
+ */
+#define cx_invoke_advanced_destructor(c, e) \
+    (c)->advanced_destructor((c)->destructor_data, \
+    (c)->store_pointer ? (*((void **) (e))) : (e))
+
+
+#define cx_invoke_destructor(c, e) \
+    if ((c)->simple_destructor) cx_invoke_simple_destructor(c,e); \
+    if ((c)->advanced_destructor) cx_invoke_advanced_destructor(c,e)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_COLLECTION_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/common.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,141 @@
+/*
+ * 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 common.h
+ *
+ * \brief Common definitions and feature checks.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ *
+ * \mainpage UAP Common Extensions
+ * Library with common and useful functions, macros and data structures.
+ * <p>
+ * Latest available source:<br>
+ * <a href="https://sourceforge.net/projects/ucx/files/">https://sourceforge.net/projects/ucx/files/</a>
+ * </p>
+ *
+ * <p>
+ * Repositories:<br>
+ * <a href="https://sourceforge.net/p/ucx/code">https://sourceforge.net/p/ucx/code</a>
+ * -&nbsp;or&nbsp;-
+ * <a href="https://develop.uap-core.de/hg/ucx">https://develop.uap-core.de/hg/ucx</a>
+ * </p>
+ *
+ * <h2>LICENCE</h2>
+ *
+ * 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.
+ */
+
+#ifndef UCX_COMMON_H
+#define UCX_COMMON_H
+
+/** Major UCX version as integer constant. */
+#define UCX_VERSION_MAJOR   3
+
+/** Minor UCX version as integer constant. */
+#define UCX_VERSION_MINOR   0
+
+/** Version constant which ensures to increase monotonically. */
+#define UCX_VERSION (((UCX_VERSION_MAJOR)<<16)|UCX_VERSION_MINOR)
+
+// Common Includes
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * Function pointer compatible with fwrite-like functions.
+ */
+typedef size_t (*cx_write_func)(
+        void const *,
+        size_t,
+        size_t,
+        void *
+);
+
+/**
+ * Function pointer compatible with fread-like functions.
+ */
+typedef size_t (*cx_read_func)(
+        void *,
+        size_t,
+        size_t,
+        void *
+);
+
+
+// Compiler specific stuff
+
+#ifndef __GNUC__
+/**
+ * Removes GNU C attributes where they are not supported.
+ */
+#define __attribute__(x)
+#endif
+
+#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 // UCX_COMMON_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/compare.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,220 @@
+/*
+ * 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 compare.h
+ * \brief A collection of simple compare functions.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_COMPARE_H
+#define UCX_COMPARE_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Compares two integers of type int.
+ *
+ * @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
+ */
+int cx_cmp_int(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type long int.
+ *
+ * @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
+ */
+int cx_cmp_longint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type long long.
+ *
+ * @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
+ */
+int cx_cmp_longlong(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int16_t.
+ *
+ * @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
+ */
+int cx_cmp_int16(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int32_t.
+ *
+ * @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
+ */
+int cx_cmp_int32(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type int64_t.
+ *
+ * @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
+ */
+int cx_cmp_int64(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned int.
+ *
+ * @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
+ */
+int cx_cmp_uint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned long int.
+ *
+ * @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
+ */
+int cx_cmp_ulongint(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type unsigned long long.
+ *
+ * @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
+ */
+int cx_cmp_ulonglong(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint16_t.
+ *
+ * @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
+ */
+int cx_cmp_uint16(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint32_t.
+ *
+ * @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
+ */
+int cx_cmp_uint32(void const *i1, void const *i2);
+
+/**
+ * Compares two integers of type uint64_t.
+ *
+ * @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
+ */
+int cx_cmp_uint64(void const *i1, void const *i2);
+
+/**
+ * Compares two real numbers of type float with precision 1e-6f.
+ *
+ * @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
+ */
+
+int cx_cmp_float(void const *f1, void const *f2);
+
+/**
+ * Compares two real numbers of type double with precision 1e-14.
+ *
+ * @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
+ */
+int cx_cmp_double(
+        void const *d1,
+        void const *d2
+);
+
+/**
+ * Compares the integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (intptr_t const*)
+ * @param ptr2 pointer to pointer two (intptr_t const*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_intptr(
+        void const *ptr1,
+        void const *ptr2
+);
+
+/**
+ * Compares the unsigned integer representation of two pointers.
+ *
+ * @param ptr1 pointer to pointer one (uintptr_t const*)
+ * @param ptr2 pointer to pointer two (uintptr_t const*)
+ * @return -1 if *ptr1 is less than *ptr2, 0 if both are equal,
+ * 1 if *ptr1 is greater than *ptr2
+ */
+int cx_cmp_uintptr(
+        void const *ptr1,
+        void const *ptr2
+);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_COMPARE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_key.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,129 @@
+/*
+ * 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 hash_key.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+
+#ifndef UCX_HASH_KEY_H
+#define UCX_HASH_KEY_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for a key within a hash map. */
+struct cx_hash_key_s {
+    /** The key data. */
+    void const *data;
+    /**
+     * The key data length.
+     */
+    size_t len;
+    /** The hash value of the key data. */
+    unsigned hash;
+};
+
+/**
+ * Type for a hash key.
+ */
+typedef struct cx_hash_key_s CxHashKey;
+
+/**
+ * Computes a murmur2 32 bit hash.
+ *
+ * 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.
+ *
+ * @param key the key, the hash shall be computed for
+ */
+void cx_hash_murmur(CxHashKey *key);
+
+/**
+ * Computes a hash key from a string.
+ *
+ * The string needs to be zero-terminated.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_str(char const *str);
+
+/**
+ * Computes a hash key from a byte array.
+ *
+ * @param bytes the array
+ * @param len the length
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key_bytes(
+        unsigned char const *bytes,
+        size_t len
+);
+
+/**
+ * Computes a hash key for an arbitrary object.
+ *
+ * The computation uses the in-memory representation that might not be
+ * the same on different platforms. Therefore, this hash should not be
+ * used for data exchange with different machines.
+ *
+ * @param obj a pointer to an arbitrary object
+ * @param len the length of object in memory
+ * @return the hash key
+ */
+__attribute__((__warn_unused_result__))
+CxHashKey cx_hash_key(
+        void const *obj,
+        size_t len
+);
+
+/**
+ * Computes a hash key from a UCX string.
+ *
+ * @param str the string
+ * @return the hash key
+ */
+#define cx_hash_key_cxstr(str) cx_hash_key((void*)(str).ptr, (str).length)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_KEY_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/hash_map.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,134 @@
+/*
+ * 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 hash_map.h
+ * \brief Hash map implementation.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_HASH_MAP_H
+#define UCX_HASH_MAP_H
+
+#include "map.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Internal structure for an element of a hash map. */
+struct cx_hash_map_element_s;
+
+/**
+ * Internal structure for a hash map.
+ */
+struct cx_hash_map_s {
+    /**
+     * Base structure for maps.
+     */
+    struct cx_map_s base;
+    /**
+     * The buckets of this map, each containing a linked list of elements.
+     */
+    struct cx_hash_map_element_s **buckets;
+    /**
+     * The number of buckets.
+     */
+    size_t bucket_count;
+};
+
+
+/**
+ * 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 item_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 the incremented when the iterator advanced without removal.
+ * In other words, when the iterator is finished, \c index==size .
+ *
+ * @param allocator the allocator to use
+ * @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__))
+CxMap *cxHashMapCreate(
+        CxAllocator const *allocator,
+        size_t itemsize,
+        size_t buckets
+);
+
+/**
+ * Creates a new hash map with a default number of buckets.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created map will be created as if
+ * cxMapStorePointers() was called immediately after creation.
+ *
+ * @note Iterators provided by this hash map implementation provide the remove operation.
+ * The index value of an iterator is the incremented when the iterator advanced without removal.
+ * 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
+ */
+#define cxHashMapCreateSimple(itemsize) \
+    cxHashMapCreate(cxDefaultAllocator, itemsize, 0)
+
+/**
+ * Increases the number of buckets, if necessary.
+ *
+ * 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.
+ *
+ * The rehashing process ensures, that the number of buckets is at least
+ * 2.5 times the element count. So there is enough room for additional
+ * elements without the need of another soon rehashing.
+ *
+ * You can use this function after filling a map to increase access performance.
+ *
+ * @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
+ */
+__attribute__((__nonnull__))
+int cxMapRehash(CxMap *map);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_HASH_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/iterator.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,263 @@
+/*
+ * 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 iterator.h
+ * \brief Interface for iterator implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_ITERATOR_H
+#define UCX_ITERATOR_H
+
+#include "common.h"
+
+/**
+ * The base of mutating and non-mutating iterators.
+ */
+struct cx_iterator_base_s {
+    /**
+     * True iff the iterator points to valid data.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*valid)(void const *);
+
+    /**
+     * Returns a pointer to the current element.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current)(void const *);
+
+    /**
+     * Original implementation in case the function needs to be wrapped.
+     */
+    __attribute__ ((__nonnull__))
+    void *(*current_impl)(void const *);
+
+    /**
+     * Advances the iterator.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    void (*next)(void *);
+
+    /**
+     * Flag current element for removal, if possible.
+     *
+     * When valid returns false, the behavior of this function is undefined.
+     */
+    __attribute__ ((__nonnull__))
+    bool (*flag_removal)(void *);
+
+    /**
+     * Indicates whether this iterator may remove elements.
+     */
+    bool mutating;
+
+    /**
+     * Internal flag for removing the current element when advancing.
+     */
+    bool remove;
+};
+
+/**
+ * Internal iterator struct - use CxMutIterator.
+ */
+struct cx_mut_iterator_s {
+
+    /**
+     * The base properties of this iterator.
+     */
+    struct cx_iterator_base_s base;
+
+    /**
+     * Handle for the current element, if required.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    void *src_handle;
+
+    /**
+     * Field for storing a key-value pair.
+     * May be used by iterators that iterate over k/v-collections.
+     */
+    struct {
+        /**
+         * A pointer to the key.
+         */
+        void const *key;
+        /**
+         * A pointer to the value.
+         */
+        void *value;
+    } kv_data;
+
+    /**
+     * Field for storing a slot number.
+     * May be used by iterators that iterate over multi-bucket collections.
+     */
+    size_t slot;
+
+    /**
+     * If the iterator is position-aware, contains the index of the element in the underlying collection.
+     * Otherwise, this field is usually uninitialized.
+     */
+    size_t index;
+};
+
+/**
+ * Mutating iterator value type.
+ *
+ * An iterator points to a certain element in an (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are mutable through that iterator. However, if the
+ * iterator is based on a collection and the underlying collection is mutated by other means than this iterator
+ * (e.g. elements added or removed), the iterator becomes invalid (regardless of what cxIteratorValid() returns)
+ * and MUST be re-obtained from the collection.
+ *
+ * @see CxIterator
+ */
+typedef struct cx_mut_iterator_s CxMutIterator;
+
+/**
+ * Internal iterator struct - use CxIterator.
+ */
+struct cx_iterator_s {
+
+    /**
+     * The base properties of this iterator.
+     */
+    struct cx_iterator_base_s base;
+
+    /**
+     * Handle for the current element, if required.
+     */
+    void *elem_handle;
+
+    /**
+     * Handle for the source collection, if any.
+     */
+    void const *src_handle;
+
+    /**
+     * Field for storing a key-value pair.
+     * May be used by iterators that iterate over k/v-collections.
+     */
+    struct {
+        /**
+         * A pointer to the key.
+         */
+        void const *key;
+        /**
+         * A pointer to the value.
+         */
+        void *value;
+    } kv_data;
+
+    /**
+     * Field for storing a slot number.
+     * May be used by iterators that iterate over multi-bucket collections.
+     */
+    size_t slot;
+
+    /**
+     * If the iterator is position-aware, contains the index of the element in the underlying collection.
+     * Otherwise, this field is usually uninitialized.
+     */
+    size_t index;
+};
+
+/**
+ * Iterator value type.
+ * An iterator points to a certain element in an (possibly unbounded) chain of elements.
+ * Iterators that are based on collections (which have a defined "first" element), are supposed
+ * to be "position-aware", which means that they keep track of the current index within the collection.
+ *
+ * @note Objects that are pointed to by an iterator are always mutable through that iterator. However,
+ * this iterator cannot mutate the collection itself (add or remove elements) and any mutation of the
+ * collection by other means make this iterator invalid (regardless of what cxIteratorValid() returns).
+ *
+ * @see CxMutIterator
+ */
+typedef struct cx_iterator_s CxIterator;
+
+/**
+ * Checks if the iterator points to valid data.
+ *
+ * This is especially false for past-the-end iterators.
+ *
+ * @param iter the iterator
+ * @return true iff the iterator points to valid data
+ */
+#define cxIteratorValid(iter) (iter).base.valid(&(iter))
+
+/**
+ * Returns a pointer to the current element.
+ *
+ * The behavior is undefined if this iterator is invalid.
+ *
+ * @param iter the iterator
+ * @return a pointer to the current element
+ */
+#define cxIteratorCurrent(iter) (iter).base.current(&iter)
+
+/**
+ * Advances the iterator to the next element.
+ *
+ * @param iter the iterator
+ */
+#define cxIteratorNext(iter) (iter).base.next(&iter)
+
+/**
+ * Flags the current element for removal.
+ *
+ * @param iter the iterator
+ * @return false if this iterator cannot remove the element
+ */
+#define cxIteratorFlagRemoval(iter) (iter).base.flag_removal(&iter)
+
+/**
+ * Loops over an iterator.
+ * @param type the type of the elements
+ * @param elem the name of the iteration variable
+ * @param iter the iterator
+ */
+#define cx_foreach(type, elem, iter) \
+for (type elem; cxIteratorValid(iter) && (elem = (type)cxIteratorCurrent(iter)) != NULL ; cxIteratorNext(iter))
+
+#endif // UCX_ITERATOR_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/linked_list.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,415 @@
+/*
+ * 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 linked_list.h
+ * \brief Linked list implementation.
+ * \details Also provides several low-level functions for custom linked list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LINKED_LIST_H
+#define UCX_LINKED_LIST_H
+
+#include "common.h"
+#include "list.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Set this flag to true, if you want to disable the use of SBO for
+ * linked list swap operations.
+ */
+extern bool CX_DISABLE_LINKED_LIST_SWAP_SBO;
+
+/**
+ * Allocates a linked list for storing elements with \p item_size bytes each.
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param allocator the allocator for allocating the list nodes
+ * (if \c NULL the cxDefaultAllocator will be used)
+ * @param comparator the comparator for the elements
+ * (if \c NULL sort and find functions will not work)
+ * @param item_size the size of each element in bytes
+ * @return the created list
+ */
+CxList *cxLinkedListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size
+);
+
+/**
+ * Allocates a linked list for storing elements with \p item_size bytes each.
+ *
+ * The list will use cxDefaultAllocator and no comparator function. If you want
+ * to call functions that need a comparator, you must either set one immediately
+ * after list creation or use cxLinkedListCreate().
+ *
+ * If \p item_size is CX_STORE_POINTERS, the created list will be created as if
+ * cxListStorePointers() was called immediately after creation.
+ *
+ * @param item_size the size of each element in bytes
+ * @return the created list
+ */
+#define cxLinkedListCreateSimple(item_size) \
+    cxLinkedListCreate(NULL, NULL, item_size)
+
+/**
+ * Finds the node at a certain index.
+ *
+ * This function can be used to start at an arbitrary position within the list.
+ * If the search index is large than the start index, \p loc_advance must denote
+ * the location of some sort of \c next pointer (i.e. a pointer to the next node).
+ * 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
+ * (i.e. a pointer to the previous node).
+ *
+ * @param start a pointer to the start node
+ * @param start_index the start index
+ * @param loc_advance the location of the pointer to advance
+ * @param index the search index
+ * @return the node found at the specified index
+ */
+void *cx_linked_list_at(
+        void const *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the index of an element within a linked list.
+ *
+ * @param start a pointer to the start node
+ * @param loc_advance the location of the pointer to advance
+ * @param loc_data the location of the \c data pointer within your node struct
+ * @param cmp_func a compare function to compare \p elem against the node data
+ * @param elem a pointer to the element to find
+ * @return the index of the element or a negative value if it could not be found
+ */
+ssize_t cx_linked_list_find(
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the first node in a linked 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.
+ *
+ * @param node a pointer to a node in the list
+ * @param loc_prev the location of the \c prev pointer
+ * @return a pointer to the first node
+ */
+void *cx_linked_list_first(
+        void const *node,
+        ptrdiff_t loc_prev
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the last node in a linked list.
+ *
+ * The function starts with the pointer denoted by \p node and traverses the list
+ * along a next pointer whose location within the node struct is
+ * 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
+ * @return a pointer to the last node
+ */
+void *cx_linked_list_last(
+        void const *node,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Finds the predecessor of a node in case it is not linked.
+ *
+ * \remark If \p node is not contained in the list starting with \p begin, the behavior is undefined.
+ *
+ * @param begin the node where to start the search
+ * @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
+ */
+void *cx_linked_list_prev(
+        void const *begin,
+        ptrdiff_t loc_next,
+        void const *node
+) __attribute__((__nonnull__));
+
+/**
+ * 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.
+ *
+ * @param begin a pointer to the begin 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 new_node a pointer to the node that shall be appended
+ */
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Prepends a new node to a linked list.
+ * The node must not be part of any list already.
+ *
+ * \remark One of the pointers \p begin or \p end may be \c NULL, but not both.
+ *
+ * @param begin a pointer to the begin 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 new_node a pointer to the node that shall be prepended
+ */
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Links two nodes.
+ *
+ * @param left the new predecessor of \p right
+ * @param right the new successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Unlinks two nodes.
+ *
+ * If right is not the successor of left, the behavior is undefined.
+ *
+ * @param left the predecessor of \p right
+ * @param right the successor of \p left
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ */
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__));
+
+/**
+ * Inserts a new node after a given node of a linked list.
+ * The new node must not be part of any list already.
+ *
+ * \note If you specify \c NULL as the \p node to insert after, this function needs either the \p begin or
+ * the \p end pointer to determine the start of the list. Then the new node will be prepended to the list.
+ *
+ * @param begin a pointer to the begin node pointer (if your list has one)
+ * @param end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct (negative if your struct does not have one)
+ * @param loc_next the location of a \c next pointer within your node struct (required)
+ * @param node the node after which to insert (\c NULL if you want to prepend the node to the list)
+ * @param new_node a pointer to the node that shall be prepended
+ */
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+) __attribute__((__nonnull__(6)));
+
+/**
+ * Inserts a chain of nodes after a given node of a linked list.
+ * The chain must not be part of any list already.
+ *
+ * If you do not explicitly specify the end of the chain, it will be determined by traversing
+ * the \c next pointer.
+ *
+ * \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 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 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)
+ */
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+) __attribute__((__nonnull__(6)));
+
+/**
+ * Removes a node from the linked list.
+ *
+ * If the node to remove is the begin (resp. end) node of the list and if \p begin (resp. \p end)
+ * 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 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 node to remove
+ */
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) __attribute__((__nonnull__(5)));
+
+
+/**
+ * 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
+ */
+size_t cx_linked_list_size(
+        void const *node,
+        ptrdiff_t loc_next
+);
+
+/**
+ * Sorts a linked list based on a comparison function.
+ *
+ * This function can work with linked lists of the following structure:
+ * \code
+ * typedef struct node node;
+ * struct node {
+ *   node* prev;
+ *   node* next;
+ *   my_payload data;
+ * }
+ * \endcode
+ *
+ * @note This is a recursive function with at most logarithmic recursion depth.
+ *
+ * @param begin a pointer to the begin 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 cmp_func the compare function defining the sort order
+ */
+void cx_linked_list_sort(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) __attribute__((__nonnull__(1, 6)));
+
+
+/**
+ * Compares two lists element wise.
+ *
+ * \note Both list must have the same structure.
+ *
+ * @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 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 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
+ * right list, positive if the left list is larger than the right list, zero if both lists are equal.
+ */
+int cx_linked_list_compare(
+        void const *begin_left,
+        void const *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) __attribute__((__nonnull__(5)));
+
+/**
+ * Reverses the order of the nodes in a linked list.
+ *
+ * @param begin a pointer to the begin node pointer (required)
+ * @param 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)
+ */
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) __attribute__((__nonnull__(1)));
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LINKED_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/list.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,650 @@
+/*
+ * 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 list.h
+ * \brief Interface for list implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_LIST_H
+#define UCX_LIST_H
+
+#include "common.h"
+#include "collection.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * List class type.
+ */
+typedef struct cx_list_class_s cx_list_class;
+
+/**
+ * Structure for holding the base data of a list.
+ */
+struct cx_list_s {
+    CX_COLLECTION_MEMBERS
+    /**
+     * The list class definition.
+     */
+    cx_list_class const *cl;
+    /**
+     * The actual implementation in case the list class is delegating.
+     */
+    cx_list_class const *climpl;
+};
+
+/**
+ * The class definition for arbitrary lists.
+ */
+struct cx_list_class_s {
+    /**
+     * Destructor function.
+     *
+     * Implementations SHALL invoke the content destructor functions if provided
+     * and SHALL deallocate the list memory, if an allocator is provided.
+     */
+    void (*destructor)(struct cx_list_s *list);
+
+    /**
+     * Member function for inserting a single elements.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     */
+    int (*insert_element)(
+            struct cx_list_s *list,
+            size_t index,
+            void const *data
+    );
+
+    /**
+     * Member function for inserting multiple elements.
+     * Implementors SHOULD see to performant implementations for corner cases.
+     */
+    size_t (*insert_array)(
+            struct cx_list_s *list,
+            size_t index,
+            void const *data,
+            size_t n
+    );
+
+    /**
+     * Member function for inserting an element relative to an iterator position.
+     */
+    int (*insert_iter)(
+            struct cx_mut_iterator_s *iter,
+            void const *elem,
+            int prepend
+    );
+
+    /**
+     * Member function for removing an element.
+     */
+    int (*remove)(
+            struct cx_list_s *list,
+            size_t index
+    );
+
+    /**
+     * Member function for removing all elements.
+     */
+    void (*clear)(struct cx_list_s *list);
+
+    /**
+     * Member function for swapping two elements.
+     */
+    int (*swap)(
+            struct cx_list_s *list,
+            size_t i,
+            size_t j
+    );
+
+    /**
+     * Member function for element lookup.
+     */
+    void *(*at)(
+            struct cx_list_s const *list,
+            size_t index
+    );
+
+    /**
+     * Member function for finding an element.
+     */
+    ssize_t (*find)(
+            struct cx_list_s const *list,
+            void const *elem
+    );
+
+    /**
+     * Member function for sorting the list in place.
+     */
+    void (*sort)(struct cx_list_s *list);
+
+    /**
+     * Member function for comparing this list to another list of the same type.
+     */
+    int (*compare)(
+            struct cx_list_s const *list,
+            struct cx_list_s const *other
+    );
+
+    /**
+     * Member function for reversing the order of the items.
+     */
+    void (*reverse)(struct cx_list_s *list);
+
+    /**
+     * Member function for returning an iterator pointing to the specified index.
+     */
+    struct cx_iterator_s (*iterator)(
+            struct cx_list_s const *list,
+            size_t index,
+            bool backward
+    );
+};
+
+/**
+ * Common type for all list implementations.
+ */
+typedef struct cx_list_s CxList;
+
+/**
+ * Advises the list to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this list will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param list the list
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+void cxListStoreObjects(CxList *list);
+
+/**
+ * Advises the list to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty list that already stores copies of
+ * objects is undefined.
+ *
+ * @param list the list
+ * @see cxListStoreObjects()
+ */
+__attribute__((__nonnull__))
+void cxListStorePointers(CxList *list);
+
+/**
+ * Returns true, if this list is storing pointers instead of the actual data.
+ *
+ * @param list
+ * @return true, if this list is storing pointers
+ * @see cxListStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline bool cxListIsStoringPointers(CxList const *list) {
+    return list->store_pointer;
+}
+
+/**
+ * Returns the number of elements currently stored in the list.
+ *
+ * @param list the list
+ * @return the number of currently stored elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListSize(CxList const *list) {
+    return list->size;
+}
+
+/**
+ * Adds an item to the end of the list.
+ *
+ * @param list the list
+ * @param elem a pointer to the element to add
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListAddArray()
+ */
+__attribute__((__nonnull__))
+static inline int cxListAdd(
+        CxList *list,
+        void const *elem
+) {
+    return list->cl->insert_element(list, list->size, elem);
+}
+
+/**
+ * Adds multiple items to the end of the list.
+ *
+ * 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.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListAddArray(
+        CxList *list,
+        void const *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, list->size, array, n);
+}
+
+/**
+ * Inserts an item at the specified index.
+ *
+ * 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
+ * @see cxListInsertAfter()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsert(
+        CxList *list,
+        size_t index,
+        void const *elem
+) {
+    return list->cl->insert_element(list, index, elem);
+}
+
+/**
+ * Inserts multiple items to the list at the specified index.
+ * 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.
+ *
+ * If this list is storing pointers instead of objects \p array is expected to
+ * be an array of pointers.
+ *
+ * @param list the list
+ * @param index the index where to add the elements
+ * @param array a pointer to the elements to add
+ * @param n the number of elements to add
+ * @return the number of added elements
+ */
+__attribute__((__nonnull__))
+static inline size_t cxListInsertArray(
+        CxList *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    return list->cl->insert_array(list, index, array, n);
+}
+
+/**
+ * Inserts an element after the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertBefore()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertAfter(
+        CxMutIterator *iter,
+        void const *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 0);
+}
+
+/**
+ * Inserts an element before the current location of the specified iterator.
+ *
+ * The used iterator remains operational, but all other active iterators should
+ * be considered invalidated.
+ *
+ * If \p iter is not a list iterator, the behavior is undefined.
+ * If \p iter is a past-the-end iterator, the new element gets appended to the list.
+ *
+ * @param iter an iterator
+ * @param elem the element to insert
+ * @return zero on success, non-zero on memory allocation failure
+ * @see cxListInsert()
+ * @see cxListInsertAfter()
+ */
+__attribute__((__nonnull__))
+static inline int cxListInsertBefore(
+        CxMutIterator *iter,
+        void const *elem
+) {
+    return ((struct cx_list_s *) iter->src_handle)->cl->insert_iter(iter, elem, 1);
+}
+
+/**
+ * Removes the element at the specified index.
+ *
+ * If an element destructor function is specified, it is called before
+ * removing the element.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return zero on success, non-zero if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListRemove(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->remove(list, index);
+}
+
+/**
+ * Removes all elements from this list.
+ *
+ * If an element destructor function is specified, it is called for each
+ * element before removing them.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListClear(CxList *list) {
+    list->cl->clear(list);
+}
+
+/**
+ * Swaps two items in the list.
+ *
+ * Implementations should only allocate temporary memory for the swap, if
+ * it is necessary.
+ *
+ * @param list the list
+ * @param i the index of the first element
+ * @param j the index of the second element
+ * @return zero on success, non-zero if one of the indices is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline int cxListSwap(
+        CxList *list,
+        size_t i,
+        size_t j
+) {
+    return list->cl->swap(list, i, j);
+}
+
+/**
+ * Returns a pointer to the element at the specified index.
+ *
+ * @param list the list
+ * @param index the index of the element
+ * @return a pointer to the element or \c NULL if the index is out of bounds
+ */
+__attribute__((__nonnull__))
+static inline void *cxListAt(
+        CxList *list,
+        size_t index
+) {
+    return list->cl->at(list, index);
+}
+
+/**
+ * Returns an iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIteratorAt(
+        CxList const *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, false);
+}
+
+/**
+ * Returns a backwards iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIteratorAt(
+        CxList const *list,
+        size_t index
+) {
+    return list->cl->iterator(list, index, true);
+}
+
+/**
+ * Returns a mutating iterator pointing to the item at the specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns a mutating backwards iterator pointing to the item at the
+ * specified index.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the index is out of range, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @param index the index where the iterator shall point at
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+);
+
+/**
+ * Returns an iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListIterator(CxList const *list) {
+    return list->cl->iterator(list, 0, false);
+}
+
+/**
+ * Returns a mutating iterator pointing to the first item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxMutIterator cxListMutIterator(CxList *list) {
+    return cxListMutIteratorAt(list, 0);
+}
+
+
+/**
+ * Returns a backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxListBackwardsIterator(CxList const *list) {
+    return list->cl->iterator(list, list->size - 1, true);
+}
+
+/**
+ * Returns a mutating backwards iterator pointing to the last item of the list.
+ *
+ * The returned iterator is position-aware.
+ *
+ * If the list is empty, a past-the-end iterator will be returned.
+ *
+ * @param list the list
+ * @return a new iterator
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxMutIterator cxListMutBackwardsIterator(CxList *list) {
+    return cxListMutBackwardsIteratorAt(list, list->size - 1);
+}
+
+/**
+ * Returns the index of the first element that equals \p elem.
+ *
+ * Determining equality is performed by the list's comparator function.
+ *
+ * @param list the list
+ * @param elem the element to find
+ * @return the index of the element or a negative
+ * value when the element is not found
+ */
+__attribute__((__nonnull__))
+static inline ssize_t cxListFind(
+        CxList const *list,
+        void const *elem
+) {
+    return list->cl->find(list, elem);
+}
+
+/**
+ * Sorts the list in place.
+ *
+ * \remark The underlying sort algorithm is implementation defined.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListSort(CxList *list) {
+    list->cl->sort(list);
+}
+
+/**
+ * Reverses the order of the items.
+ *
+ * @param list the list
+ */
+__attribute__((__nonnull__))
+static inline void cxListReverse(CxList *list) {
+    list->cl->reverse(list);
+}
+
+/**
+ * Compares a list to another list of the same type.
+ *
+ * First, the list sizes are compared.
+ * If they match, the lists are compared element-wise.
+ *
+ * @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
+ */
+__attribute__((__nonnull__))
+int cxListCompare(
+        CxList const *list,
+        CxList const *other
+);
+
+/**
+ * Deallocates the memory of the specified list structure.
+ *
+ * Also calls content a destructor function, depending on the configuration
+ * in CxList.content_destructor_type.
+ *
+ * This function itself is a destructor function for the CxList.
+ *
+ * @param list the list which shall be destroyed
+ */
+__attribute__((__nonnull__))
+void cxListDestroy(CxList *list);
+
+/**
+ * A shared instance of an empty list.
+ *
+ * Writing to that list is undefined.
+ */
+extern CxList * const cxEmptyList;
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_LIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/map.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,1129 @@
+/*
+ * 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 map.h
+ * \brief Interface for map implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MAP_H
+#define UCX_MAP_H
+
+#include "common.h"
+#include "collection.h"
+#include "string.h"
+#include "hash_key.h"
+
+#ifdef    __cplusplus
+extern "C" {
+#endif
+
+/** Type for the UCX map. */
+typedef struct cx_map_s CxMap;
+
+/** Type for a map entry. */
+typedef struct cx_map_entry_s CxMapEntry;
+
+/** Type for map class definitions. */
+typedef struct cx_map_class_s cx_map_class;
+
+/** Structure for the UCX map. */
+struct cx_map_s {
+    CX_COLLECTION_MEMBERS
+    /** The map class definition. */
+    cx_map_class *cl;
+};
+
+/**
+ * The type of iterator for a map.
+ */
+enum cx_map_iterator_type {
+    /**
+     * Iterates over key/value pairs.
+     */
+    CX_MAP_ITERATOR_PAIRS,
+    /**
+     * Iterates over keys only.
+     */
+    CX_MAP_ITERATOR_KEYS,
+    /**
+     * Iterates over values only.
+     */
+    CX_MAP_ITERATOR_VALUES
+};
+
+/**
+ * The class definition for arbitrary maps.
+ */
+struct cx_map_class_s {
+    /**
+     * Deallocates the entire memory.
+     */
+    __attribute__((__nonnull__))
+    void (*destructor)(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,
+            void *value
+    );
+
+    /**
+     * Returns an element.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    void *(*get)(
+            CxMap const *map,
+            CxHashKey key
+    );
+
+    /**
+     * Removes an element.
+     */
+    __attribute__((__nonnull__))
+    void *(*remove)(
+            CxMap *map,
+            CxHashKey key,
+            bool destroy
+    );
+
+    /**
+     * Creates an iterator for this map.
+     */
+    __attribute__((__nonnull__, __warn_unused_result__))
+    CxIterator (*iterator)(CxMap const *map, enum cx_map_iterator_type type);
+};
+
+/**
+ * A map entry.
+ */
+struct cx_map_entry_s {
+    /**
+     * A pointer to the key.
+     */
+    CxHashKey const *key;
+    /**
+     * A pointer to the value.
+     */
+    void *value;
+};
+
+extern CxMap *const cxEmptyMap;
+
+/**
+ * Advises the map to store copies of the objects (default mode of operation).
+ *
+ * Retrieving objects from this map will yield pointers to the copies stored
+ * within this list.
+ *
+ * @param map the map
+ * @see cxMapStorePointers()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStoreObjects(CxMap *map) {
+    map->store_pointer = false;
+}
+
+/**
+ * Advises the map to only store pointers to the objects.
+ *
+ * Retrieving objects from this list will yield the original pointers stored.
+ *
+ * @note This function forcibly sets the element size to the size of a pointer.
+ * Invoking this function on a non-empty map that already stores copies of
+ * objects is undefined.
+ *
+ * @param map the map
+ * @see cxMapStoreObjects()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapStorePointers(CxMap *map) {
+    map->store_pointer = true;
+    map->item_size = sizeof(void *);
+}
+
+
+/**
+ * Deallocates the memory of the specified map.
+ *
+ * @param map the map to be destroyed
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDestroy(CxMap *map) {
+    map->cl->destructor(map);
+}
+
+
+/**
+ * Clears a map by removing all elements.
+ *
+ * @param map the map to be cleared
+ */
+__attribute__((__nonnull__))
+static inline void cxMapClear(CxMap *map) {
+    map->cl->clear(map);
+}
+
+
+// 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
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorValues(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+}
+
+/**
+ * Creates a key iterator for a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIteratorKeys(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+}
+
+/**
+ * Creates an iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \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 entries
+ * @see cxMapIteratorKeys()
+ * @see cxMapIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline CxIterator cxMapIterator(CxMap const *map) {
+    return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+}
+
+
+/**
+ * Creates a mutating iterator over the values of a map.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored values
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxMapMutIteratorValues(CxMap *map);
+
+/**
+ * Creates a mutating iterator over the keys of a map.
+ *
+ * The elements of the iterator are keys of type CxHashKey.
+ *
+ * \note An iterator iterates over all elements successively. Therefore the order
+ * highly depends on the map implementation and may change arbitrarily when the contents change.
+ *
+ * @param map the map to create the iterator for
+ * @return an iterator for the currently stored keys
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator cxMapMutIteratorKeys(CxMap *map);
+
+/**
+ * Creates a mutating iterator for a map.
+ *
+ * The elements of the iterator are key/value pairs of type CxMapEntry.
+ *
+ * \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 entries
+ * @see cxMapMutIteratorKeys()
+ * @see cxMapMutIteratorValues()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+CxMutIterator 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__))
+static inline int cxMapPut(
+        CxMap *map,
+        CxHashKey const &key,
+        void *value
+) {
+    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__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxstring const &key,
+        void *value
+) {
+    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__))
+static inline int cxMapPut(
+        CxMap *map,
+        cxmutstr const &key,
+        void *value
+) {
+    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__))
+static inline int cxMapPut(
+        CxMap *map,
+        char const *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        CxHashKey const &key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        cxstring const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        cxmutstr const &key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapGet(
+        CxMap const *map,
+        char const *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * Always invokes the destructor function, if any, on the removed element.
+ * If this map is storing pointers and you just want to retrieve the pointer
+ * without invoking the destructor, use cxMapRemoveAndGet().
+ * If you just want to detach the element from the map without invoking the
+ * destructor or returning the element, use cxMapDetach().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapRemove(
+        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);
+}
+
+/**
+ * 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,
+        char const *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(
+        CxMap *map,
+        cxstring const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        cxmutstr const &key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * In general, you should only use this function if the map does not own
+ * the data and there is a valid reference to the data somewhere else
+ * in the program. In all other cases it is preferable to use
+ * cxMapRemove() or cxMapRemoveAndGet().
+ *
+ * @param map the map
+ * @param key the key
+ * @see cxMapRemove()
+ * @see cxMapRemoveAndGet()
+ */
+__attribute__((__nonnull__))
+static inline void cxMapDetach(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(key), false);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function can be used when the map is storing pointers,
+ * in order to retrieve the pointer from the map without invoking
+ * any destructor function. Sometimes you do not want the pointer
+ * to be returned - in that case (instead of suppressing the "unused
+ * result" warning) you can use cxMapDetach().
+ *
+ * If this map is not storing pointers, this function behaves like
+ * cxMapRemove() and returns \c NULL.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ * @see cxMapStorePointers()
+ * @see cxMapDetach()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->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()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxstring key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->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()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->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()
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cxMapRemoveAndGet(
+        CxMap *map,
+        char const *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
+}
+
+#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
+ */
+__attribute__((__nonnull__))
+static inline int cx_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    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__))
+static inline int cx_map_put_cxstr(
+        CxMap *map,
+        cxstring key,
+        void *value
+) {
+    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__))
+static inline int cx_map_put_mustr(
+        CxMap *map,
+        cxmutstr key,
+        void *value
+) {
+    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__))
+static inline int cx_map_put_str(
+        CxMap *map,
+        char const *key,
+        void *value
+) {
+    return map->cl->put(map, cx_hash_key_str(key), value);
+}
+
+/**
+ * Puts a key/value-pair into the map.
+ *
+ * @param map the map
+ * @param key the key
+ * @param value the value
+ * @return 0 on success, non-zero value on failure
+ */
+#define cxMapPut(map, key, value) _Generic((key), \
+    CxHashKey: cx_map_put,                        \
+    cxstring: cx_map_put_cxstr,                   \
+    cxmutstr: cx_map_put_mustr,                   \
+    char*: cx_map_put_str,                        \
+    char const*: cx_map_put_str)                  \
+    (map, 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__))
+static inline void *cx_map_get(
+        CxMap const *map,
+        CxHashKey key
+) {
+    return map->cl->get(map, key);
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_cxstr(
+        CxMap const *map,
+        cxstring key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_mustr(
+        CxMap const *map,
+        cxmutstr key
+) {
+    return map->cl->get(map, cx_hash_key_cxstr(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_get_str(
+        CxMap const *map,
+        char const *key
+) {
+    return map->cl->get(map, cx_hash_key_str(key));
+}
+
+/**
+ * Retrieves a value by using a key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the value
+ */
+#define cxMapGet(map, key) _Generic((key), \
+    CxHashKey: cx_map_get,                 \
+    cxstring: cx_map_get_cxstr,            \
+    cxmutstr: cx_map_get_mustr,            \
+    char*: cx_map_get_str,                 \
+    char const*: cx_map_get_str)           \
+    (map, key)
+
+/**
+ * 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(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, true);
+}
+
+/**
+ * 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_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), true);
+}
+
+/**
+ * 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.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_remove_str(
+        CxMap *map,
+        char const *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
+ * @see cxMapRemoveAndGet()
+ * @see cxMapDetach()
+ */
+#define cxMapRemove(map, key) _Generic((key), \
+    CxHashKey: cx_map_remove,                 \
+    cxstring: cx_map_remove_cxstr,            \
+    cxmutstr: cx_map_remove_mustr,            \
+    char*: cx_map_remove_str,                 \
+    char const*: cx_map_remove_str)           \
+    (map, key)
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach(
+        CxMap *map,
+        CxHashKey key
+) {
+    (void) map->cl->remove(map, key, false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_cxstr(
+        CxMap *map,
+        cxstring key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    (void) map->cl->remove(map, cx_hash_key_cxstr(key), false);
+}
+
+/**
+ * Detaches a key/value-pair from the map by using the key
+ * without invoking the destructor.
+ *
+ * @param map the map
+ * @param key the key
+ */
+__attribute__((__nonnull__))
+static inline void cx_map_detach_str(
+        CxMap *map,
+        char const *key
+) {
+    (void) map->cl->remove(map, cx_hash_key_str(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()
+ */
+#define cxMapDetach(map, key) _Generic((key), \
+    CxHashKey: cx_map_detach,                 \
+    cxstring: cx_map_detach_cxstr,            \
+    cxmutstr: cx_map_detach_mustr,            \
+    char*: cx_map_detach_str,                 \
+    char const*: cx_map_detach_str)           \
+    (map, key)
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get(
+        CxMap *map,
+        CxHashKey key
+) {
+    return map->cl->remove(map, key, !map->store_pointer);
+}
+
+/**
+ * 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->store_pointer);
+}
+
+/**
+ * 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_mustr(
+        CxMap *map,
+        cxmutstr key
+) {
+    return map->cl->remove(map, cx_hash_key_cxstr(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * @param map the map
+ * @param key the key
+ * @return the stored pointer or \c NULL if either the key is not present
+ * in the map or the map is not storing pointers
+ */
+__attribute__((__nonnull__, __warn_unused_result__))
+static inline void *cx_map_remove_and_get_str(
+        CxMap *map,
+        char const *key
+) {
+    return map->cl->remove(map, cx_hash_key_str(key), !map->store_pointer);
+}
+
+/**
+ * Removes a key/value-pair from the map by using the key.
+ *
+ * This function 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), \
+    CxHashKey: cx_map_remove_and_get,               \
+    cxstring: cx_map_remove_and_get_cxstr,          \
+    cxmutstr: cx_map_remove_and_get_mustr,          \
+    char*: cx_map_remove_and_get_str,               \
+    char const*: cx_map_remove_and_get_str)         \
+    (map, key)
+
+#endif // __cplusplus
+
+#endif // UCX_MAP_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/mempool.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,122 @@
+/*
+ * 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 mempool.h
+ * \brief Interface for memory pool implementations.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_MEMPOOL_H
+#define UCX_MEMPOOL_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Memory pool class type.
+ */
+typedef struct cx_mempool_class_s cx_mempool_class;
+
+/**
+ * The basic structure of a memory pool.
+ * Should be the first member of an actual memory pool implementation.
+ */
+struct cx_mempool_s {
+    /**
+     * The pool class definition.
+     */
+    cx_mempool_class *cl;
+    /**
+     * The provided allocator.
+     */
+    CxAllocator const *allocator;
+};
+
+/**
+ * Common type for all memory pool implementations.
+ */
+typedef struct cx_mempool_s CxMempool;
+
+/**
+ * The class definition for a memory pool.
+ */
+struct cx_mempool_class_s {
+    /** Member function for destroying the pool. */
+    __attribute__((__nonnull__))
+    void (*destroy)(CxMempool *pool);
+
+    /** Member function for setting a destructor. */
+    __attribute__((__nonnull__))
+    void (*set_destructor)(
+            CxMempool *pool,
+            void *memory,
+            cx_destructor_func fnc
+    );
+};
+
+
+/**
+ * Destroys a memory pool including their contents.
+ *
+ * @param pool the memory pool to destroy
+ */
+__attribute__((__nonnull__))
+static inline void cxMempoolDestroy(CxMempool *pool) {
+    pool->cl->destroy(pool);
+}
+
+/**
+ * Sets a destructor function for an allocated memory object.
+ *
+ * If the memory is not managed by the pool, the behavior is undefined.
+ *
+ * @param pool the pool
+ * @param memory the objected allocated in the pool
+ * @param fnc the destructor function
+ */
+__attribute__((__nonnull__))
+static inline void cxMempoolSetDestructor(
+        CxMempool *pool,
+        void *memory,
+        cx_destructor_func fnc
+) {
+    pool->cl->set_destructor(pool, memory, fnc);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_MEMPOOL_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/printf.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,166 @@
+/*
+ * 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 printf.h
+ * \brief Wrapper for write functions with a printf-like interface.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_PRINTF_H
+#define UCX_PRINTF_H
+
+#include "common.h"
+#include "string.h"
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * 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
+ */
+__attribute__((__nonnull__(1, 2, 3), __format__(printf, 3, 4)))
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        ...
+);
+
+/**
+ * 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
+ * @see cx_fprintf()
+ */
+__attribute__((__nonnull__))
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        va_list ap
+);
+
+/**
+ * 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.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return the formatted string
+ * @see cx_strfree_a()
+ */
+__attribute__((__nonnull__(1, 2), __format__(printf, 2, 3)))
+cxmutstr cx_asprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        ...
+);
+
+/**
+ * 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.
+ *
+ * @param fmt format string
+ * @param ... additional arguments
+ * @return 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
+ * the result is written to.
+ *
+ * \note The resulting string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the CxAllocator used for allocating the string
+ * @param fmt format string
+ * @param ap argument list
+ * @return the formatted string
+ * @see cx_asprintf_a()
+ */
+__attribute__((__nonnull__))
+cxmutstr cx_vasprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        va_list ap
+);
+
+/**
+* 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.
+ *
+ * @param fmt format string
+ * @param ap argument list
+ * @return 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.
+ *
+ * @param buffer a pointer to the buffer the data is written to
+ * @param fmt the format string
+ * @param ... additional arguments
+ * @return the total number of bytes written
+ * @see ucx_fprintf()
+ */
+#define cx_bprintf(buffer, fmt, ...) cx_fprintf((CxBuffer*)buffer, \
+    (cx_write_func) cxBufferWrite, fmt, __VA_ARGS__)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_PRINTF_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/string.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,1078 @@
+/*
+ * 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 string.h
+ * \brief Strings that know their length.
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_STRING_H
+#define UCX_STRING_H
+
+#include "common.h"
+#include "allocator.h"
+
+/**
+ * The UCX string structure.
+ */
+struct cx_mutstr_s {
+    /**
+     * A pointer to the string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    char *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * A mutable string.
+ */
+typedef struct cx_mutstr_s cxmutstr;
+
+/**
+ * The UCX string structure for immutable (constant) strings.
+ */
+struct cx_string_s {
+    /**
+     * A pointer to the immutable string.
+     * \note The string is not necessarily \c NULL terminated.
+     * Always use the length.
+     */
+    char const *ptr;
+    /** The length of the string */
+    size_t length;
+};
+
+/**
+ * An immutable string.
+ */
+typedef struct cx_string_s cxstring;
+
+/**
+ * Context for string tokenizing.
+ */
+struct cx_strtok_ctx_s {
+    /**
+     * The string to tokenize.
+     */
+    cxstring str;
+    /**
+     * The primary delimiter.
+     */
+    cxstring delim;
+    /**
+     * Optional array of more delimiters.
+     */
+    cxstring const *delim_more;
+    /**
+     * Length of the array containing more delimiters.
+     */
+    size_t delim_more_count;
+    /**
+     * Position of the currently active token in the source string.
+     */
+    size_t pos;
+    /**
+     * Position of next delimiter in the source string.
+     *
+     * If the tokenizer has not yet returned a token, the content of this field
+     * is undefined. If the tokenizer reached the end of the string, this field
+     * contains the length of the source string.
+     */
+    size_t delim_pos;
+    /**
+     * The position of the next token in the source string.
+     */
+    size_t next_pos;
+    /**
+     * The number of already found tokens.
+     */
+    size_t found;
+    /**
+     * The maximum number of tokens that shall be returned.
+     */
+    size_t limit;
+};
+
+/**
+ * A string tokenizing context.
+ */
+typedef struct cx_strtok_ctx_s CxStrtokCtx;
+
+#ifdef __cplusplus
+extern "C" {
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) cxstring{literal, sizeof(literal) - 1}
+
+#else // __cplusplus
+
+/**
+ * A literal initializer for an UCX string structure.
+ *
+ * The argument MUST be a string (const char*) \em literal.
+ *
+ * @param literal the string literal
+ */
+#define CX_STR(literal) (cxstring){literal, sizeof(literal) - 1}
+
+#endif
+
+
+/**
+ * Wraps a mutable string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \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().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_mutstrn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+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.
+ *
+ * \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 length   the length of the string
+ * @return the wrapped string
+ *
+ * @see cx_mutstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+);
+
+/**
+ * Wraps a string that must be zero-terminated.
+ *
+ * The length is implicitly inferred by using a call to \c strlen().
+ *
+ * \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().
+ *
+ * @param cstring the string to wrap, must be zero-terminated
+ * @return the wrapped string
+ *
+ * @see cx_strn()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxstring cx_str(char const *cstring);
+
+
+/**
+ * Wraps a string that does not need to be zero-terminated.
+ *
+ * The argument may be \c NULL if the length is zero.
+ *
+ * \note the wrapped string will share the specified pointer to the string.
+ * If you do want a copy, use cx_strdup() on the return value of this function.
+ *
+ * If you need to wrap a non-constant string, use cx_mutstrn().
+ *
+ * @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__))
+cxstring cx_strn(
+        char const *cstring,
+        size_t length
+);
+
+/**
+* Casts a mutable string to an immutable string.
+*
+* \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
+*/
+__attribute__((__warn_unused_result__))
+cxstring cx_strcast(cxmutstr str);
+
+/**
+ * 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.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a \c char \c const* you are really supposed to free. If you
+ * encounter such situation, you should double-check your code.
+ *
+ * @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.
+ *
+ * \note There is no implementation for cxstring, because it is unlikely that
+ * you ever have a \c char \c const* you are really supposed to free. If you
+ * encounter such situation, you should double-check your code.
+ *
+ * @param alloc the allocator
+ * @param str the string to free
+ */
+__attribute__((__nonnull__))
+void cx_strfree_a(
+        CxAllocator const *alloc,
+        cxmutstr *str
+);
+
+/**
+ * Returns the accumulated length of all specified strings.
+ *
+ * \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__))
+size_t cx_strlen(
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * 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.
+ * 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
+ * @return the concatenated string
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strcat_ma(
+        CxAllocator const *alloc,
+        cxmutstr str,
+        size_t count,
+        ...
+);
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by the specified allocator.
+ * So developers \em must pass the return value to cx_strfree_a() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * 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
+ */
+#define cx_strcat_a(alloc, count, ...) \
+cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings and returns a new string.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * \note It is guaranteed that there is only one allocation.
+ * 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
+ */
+#define cx_strcat(count, ...) \
+cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__)
+
+/**
+ * Concatenates strings.
+ *
+ * The resulting string will be allocated by standard \c malloc().
+ * So developers \em must pass the return value to cx_strfree() eventually.
+ *
+ * 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.
+ * 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
+ */
+#define cx_strcat_m(str, count, ...) \
+cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__)
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * \attention the new string references the same memory area as the
+ * input string and is usually \em not zero-terminated.
+ * 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
+ *
+ * @see cx_strsubsl()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \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
+ *
+ * @see cx_strsubs()
+ * @see cx_strsubs_m()
+ * @see cx_strsubsl_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * 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.
+ * 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
+ *
+ * @see cx_strsubsl_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+);
+
+/**
+ * Returns a substring starting at the specified location.
+ *
+ * The returned string will be limited to \p length bytes or the number
+ * of bytes available in \p string, whichever is smaller.
+ *
+ * \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
+ *
+ * @see cx_strsubs_m()
+ * @see cx_strsubs()
+ * @see cx_strsubsl()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @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
+ *
+ * @see cx_strchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @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
+ *
+ * @see cx_strchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @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
+ *
+ * @see cx_strrchr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the last occurrence of the
+ * specified character.
+ *
+ * If the string does not contain the character, an empty string is returned.
+ *
+ * @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
+ *
+ * @see cx_strrchr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p 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
+ *               contained
+ * @see cx_strstr_m()
+ */
+__attribute__((__warn_unused_result__))
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+);
+
+/**
+ * Returns a substring starting at the location of the first occurrence of the
+ * specified string.
+ *
+ * If \p haystack does not contain \p needle, an empty string is returned.
+ *
+ * If \p 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
+ *               contained
+ * @see cx_strstr()
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * \note The resulting array contains strings that point to the source
+ * \p string. Use cx_strdup() to get copies.
+ *
+ * @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
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * 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.
+ *
+ * \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
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_a(
+        CxAllocator const *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+);
+
+
+/**
+ * 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.
+ *
+ * @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
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+);
+
+/**
+ * Splits a given string using a delimiter string.
+ *
+ * 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.
+ *
+ * \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
+ * @param delim  the delimiter
+ * @param limit the maximum number of split items
+ * @param output a pointer where the address of the allocated array shall be
+ * written to
+ * @return the actual number of split items
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+size_t cx_strsplit_ma(
+        CxAllocator const *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+);
+
+/**
+ * Compares two strings.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+int cx_strcasecmp(
+        cxstring s1,
+        cxstring s2
+);
+
+/**
+ * Compares two strings.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcmp_p(
+        void const *s1,
+        void const *s2
+);
+
+/**
+ * Compares two strings ignoring case.
+ *
+ * This function has a compatible signature for the use as a cx_compare_func.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+int cx_strcasecmp_p(
+        void const *s1,
+        void const *s2
+);
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup()
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strdup_a(
+        CxAllocator const *allocator,
+        cxstring string
+);
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_a()
+ */
+#define cx_strdup(string) cx_strdup_a(cxDefaultAllocator, string)
+
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by \p allocator.
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param allocator the allocator to use
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_m()
+ */
+#define cx_strdup_ma(allocator, string) cx_strdup_a(allocator, cx_strcast(string))
+
+/**
+ * Creates a duplicate of the specified string.
+ *
+ * The new string will contain a copy allocated by standard
+ * \c malloc(). So developers \em must pass the return value to cx_strfree().
+ *
+ * \note The returned string is guaranteed to be zero-terminated.
+ *
+ * @param string the string to duplicate
+ * @return a duplicate of the string
+ * @see cx_strdup_ma()
+ */
+#define cx_strdup_m(string) cx_strdup_a(cxDefaultAllocator, cx_strcast(string))
+
+/**
+ * Omits leading and trailing spaces.
+ *
+ * \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__))
+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.
+ *
+ * @param string the string that shall be trimmed
+ * @return the trimmed string
+ */
+__attribute__((__warn_unused_result__))
+cxmutstr cx_strtrim_m(cxmutstr string);
+
+/**
+ * Checks, if a string has a specific prefix.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Checks, if a string has a specific prefix, ignoring the case.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+);
+
+/**
+ * Checks, if a string has a specific suffix, ignoring the case.
+ *
+ * @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
+ */
+__attribute__((__warn_unused_result__))
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+);
+
+/**
+ * Converts the string to lower case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strlower(cxmutstr string);
+
+/**
+ * Converts the string to upper case.
+ *
+ * The change is made in-place. If you want a copy, use cx_strdup(), first.
+ *
+ * @param string the string to modify
+ * @see cx_strdup()
+ */
+void cx_strupper(cxmutstr string);
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * 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
+ * @param replmax maximum number of replacements
+ * @return the resulting string after applying the replacements
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+cxmutstr cx_strreplacen_a(
+        CxAllocator const *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+);
+
+/**
+ * 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.
+ *
+ * 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
+ */
+#define cx_strreplacen(str, pattern, replacement, replmax) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ *
+ * 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
+ */
+#define cx_strreplace_a(allocator, str, pattern, replacement) \
+cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Replaces a pattern in a string with another string.
+ *
+ * The pattern is taken literally and is no regular expression.
+ * Replaces at most \p replmax occurrences.
+ *
+ * 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
+ */
+#define cx_strreplace(str, pattern, replacement) \
+cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
+
+/**
+ * Creates a string tokenization context.
+ *
+ * @param str the string to tokenize
+ * @param delim the delimiter (must not be empty)
+ * @param limit the maximum number of tokens that shall be returned
+ * @return a new string tokenization context
+ */
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+* Creates a string tokenization context for a mutable string.
+*
+* @param str the string to tokenize
+* @param delim the delimiter (must not be empty)
+* @param limit the maximum number of tokens that shall be returned
+* @return a new string tokenization context
+*/
+__attribute__((__warn_unused_result__))
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+);
+
+/**
+ * Returns the next token.
+ *
+ * The token will point to the source string.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+);
+
+/**
+ * Returns the next token of a mutable string.
+ *
+ * The token will point to the source string.
+ * If the context was not initialized over a mutable string, modifying
+ * the data of the returned token is undefined behavior.
+ *
+ * @param ctx the tokenization context
+ * @param token a pointer to memory where the next token shall be stored
+ * @return true if successful, false if the limit or the end of the string
+ * has been reached
+ */
+__attribute__((__warn_unused_result__, __nonnull__))
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+);
+
+/**
+ * Defines an array of more delimiters for the specified tokenization context.
+ *
+ * @param ctx the tokenization context
+ * @param delim array of more delimiters
+ * @param count number of elements in the array
+ */
+__attribute__((__nonnull__))
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        cxstring const *delim,
+        size_t count
+);
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //UCX_STRING_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/tree.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,136 @@
+/*
+ * 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 tree.h
+ * \brief Interface for tree implementations.
+ * \author Olaf Wintermann
+ * \author Mike Becker
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_TREE_H
+#define UCX_TREE_H
+
+#include "common.h"
+#include "allocator.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Adds a sibling to the current tree node.
+ *
+ * In case your struct does not have a \p prev or a \p parent pointer,
+ * specify a negative location. The location of the \p next pointer is
+ * mandatory.
+ *
+ * \attention Do not use this function to add siblings in a tree where the
+ * nodes store a pointer to the last sibling because that would not be modified by this function.
+ *
+ * \remark If yo do not provide a location to the parent pointer, a call to this function is
+ * effectively the same as a call to cx_linked_list_add().
+ *
+ * @param node a pointer to the node
+ * @param loc_prev the location of a \c prev pointer within your node struct
+ * @param loc_next the location of a \c next pointer within your node struct
+ * @param loc_parent the location of a \c parent pointer within your node struct
+ * @param new_node the new node that shall be added as a sibling
+ */
+void cx_tree_add_sibling(void *node,
+                         ptrdiff_t loc_prev, ptrdiff_t loc_next,
+                         ptrdiff_t loc_parent,
+                         void *new_node)
+__attribute__((__nonnull__));
+
+/**
+ * Adds a node to the list of children.
+ *
+ * \par Example with a full structure
+ * A full tree node structure may look like this:
+ * \code
+ * typedef struct MyTreeNode MyTreeNode;
+ * struct MyTreeNode {
+ *   MyTreeNode* parent;
+ *   MyTreeNode* first_child;
+ *   MyTreeNode* last_child;
+ *   MyTreeNode* prev_sibling;
+ *   MyTreeNode* next_sibling;
+ *   // ...contents...
+ * }
+ * \endcode
+ * Adding a new child to a node with the above structure can be performed with the following call:
+ * \code
+ * MyTreeNode *node, *child; // given
+ * cx_tree_add_child(&node->first_child, &node->last_child,
+ *                   offsetof(MyTreeNode, prev_sibling), offsetof(MyTreeNode, next_sibling),
+ *                   child, offsetof(MyTreeNode, parent), node);
+ * \endcode
+ *
+ * \par Example with a reduced structure
+ * The minimal reasonable structure with parent pointer looks like this:
+ * \code
+ * typedef struct MyTreeNode MyTreeNode;
+ * struct MyTreeNode {
+ *   MyTreeNode* parent;
+ *   MyTreeNode* children;
+ *   MyTreeNode* next_sibling;
+ *   // ...contents...
+ * }
+ * \endcode
+ * This simplifies the function call to:
+ * \code
+ * MyTreeNode *node, *child; // given
+ * cx_tree_add_child(&node->children, NULL, -1, offsetof(MyTreeNode, next_sibling),
+ *                   child, offsetof(MyTreeNode, parent), node);
+ * \endcode
+ *
+ * \remark If your tree structure does not possess a parent pointer, a call to this function is
+ * effectively the same as a call to cx_linked_list_add().
+ *
+ * @param children_begin a pointer to the begin node pointer (if your list has one)
+ * @param children_end a pointer to the end node pointer (if your list has one)
+ * @param loc_prev the location of a \c prev pointer within your node struct
+ * @param loc_next the location of a \c next pointer within your node struct
+ * @param new_node a pointer to the node that shall be appended
+ * @param loc_parent the location of a \c parent pointer within your node struct
+ * @param parent the parent node
+ */
+void cx_tree_add_child(void **children_begin, void **children_end,
+                       ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node,
+                       ptrdiff_t loc_parent, void *parent)
+__attribute__((__nonnull__ (5)));
+
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // UCX_TREE_H
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/cx/utils.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,196 @@
+/*
+ * 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 utils.h
+ *
+ * \brief General purpose utility functions.
+ *
+ * \author Mike Becker
+ * \author Olaf Wintermann
+ * \version 3.0
+ * \copyright 2-Clause BSD License
+ */
+
+#ifndef UCX_UTILS_H
+#define UCX_UTILS_H
+
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Convenience macro for a for loop that counts from zero to n-1.
+ */
+#define cx_for_n(varname, n) for (size_t varname = 0 ; (varname) < (n) ; (varname)++)
+
+/**
+ * Convenience macro for swapping two pointers.
+ */
+#ifdef __cplusplus
+#define cx_swap_ptr(left, right) do {auto cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#else
+#define cx_swap_ptr(left, right) do {void *cx_tmp_swap_var = left; left = right; right = cx_tmp_swap_var;} while(0)
+#endif
+
+// cx_szmul() definition
+
+#if (__GNUC__ >= 5 || defined(__clang__)) && !defined(CX_NO_SZMUL_BUILTIN)
+#define CX_SZMUL_BUILTIN
+
+/**
+ * Alias for \c __builtin_mul_overflow.
+ *
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) __builtin_mul_overflow(a, b, result)
+
+#else // no GNUC or clang bultin
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+  *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t, where the result should
+ * be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+#define cx_szmul(a, b, result) cx_szmul_impl(a, b, result)
+
+/**
+ * Performs a multiplication of size_t values and checks for overflow.
+ *
+ * This is a custom implementation in case there is no compiler builtin
+ * available.
+ *
+ * @param a first operand
+ * @param b second operand
+ * @param result a pointer to a size_t where the result should be stored
+ * @return zero, if no overflow occurred and the result is correct, non-zero
+ * otherwise
+ */
+int cx_szmul_impl(size_t a, size_t b, size_t *result);
+
+#endif // cx_szmul
+
+
+/**
+ * Reads data from a stream and writes it to another stream.
+ *
+ * @param 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
+ */
+__attribute__((__nonnull__(1, 2, 3, 4)))
+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 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
+ * @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
+ */
+__attribute__((__nonnull__))
+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 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
+ */
+#define cx_stream_copy(src, dest, rfnc, wfnc) \
+    cx_stream_ncopy(src, dest, rfnc, wfnc, SIZE_MAX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // UCX_UTILS_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_key.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,112 @@
+/*
+ * 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/hash_key.h"
+#include <string.h>
+
+void cx_hash_murmur(CxHashKey *key) {
+    unsigned char const *data = key->data;
+    if (data == NULL) {
+        // extension: special value for NULL
+        key->hash = 1574210520u;
+        return;
+    }
+    size_t len = key->len;
+
+    unsigned m = 0x5bd1e995;
+    unsigned r = 24;
+    unsigned h = 25 ^ len;
+    unsigned i = 0;
+    while (len >= 4) {
+        unsigned k = data[i + 0] & 0xFF;
+        k |= (data[i + 1] & 0xFF) << 8;
+        k |= (data[i + 2] & 0xFF) << 16;
+        k |= (data[i + 3] & 0xFF) << 24;
+
+        k *= m;
+        k ^= k >> r;
+        k *= m;
+
+        h *= m;
+        h ^= k;
+
+        i += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+        case 3:
+            h ^= (data[i + 2] & 0xFF) << 16;
+                    __attribute__((__fallthrough__));
+        case 2:
+            h ^= (data[i + 1] & 0xFF) << 8;
+                    __attribute__((__fallthrough__));
+        case 1:
+            h ^= (data[i + 0] & 0xFF);
+            h *= m;
+                    __attribute__((__fallthrough__));
+        default: // do nothing
+            ;
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    key->hash = h;
+}
+
+CxHashKey cx_hash_key_str(char const *str) {
+    CxHashKey key;
+    key.data = str;
+    key.len = str == NULL ? 0 : strlen(str);
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key_bytes(
+        unsigned char const *bytes,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = bytes;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
+
+CxHashKey cx_hash_key(
+        void const *obj,
+        size_t len
+) {
+    CxHashKey key;
+    key.data = obj;
+    key.len = len;
+    cx_hash_murmur(&key);
+    return key;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/hash_map.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,489 @@
+/*
+ * 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/hash_map.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <assert.h>
+
+struct cx_hash_map_element_s {
+    /** A pointer to the next element in the current bucket. */
+    struct cx_hash_map_element_s *next;
+
+    /** The corresponding key. */
+    CxHashKey key;
+
+    /** The value data. */
+    char data[];
+};
+
+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) {
+        struct cx_hash_map_element_s *elem = hash_map->buckets[i];
+        if (elem != NULL) {
+            do {
+                struct cx_hash_map_element_s *next = elem->next;
+                // invoke the destructor
+                cx_invoke_destructor(map, elem->data);
+                // free the key data
+                cxFree(map->allocator, (void *) elem->key.data);
+                // free the node
+                cxFree(map->allocator, elem);
+                // proceed
+                elem = next;
+            } while (elem != NULL);
+
+            // do not leave a dangling pointer
+            hash_map->buckets[i] = NULL;
+        }
+    }
+    map->size = 0;
+}
+
+static void cx_hash_map_destructor(struct cx_map_s *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    // free the buckets
+    cx_hash_map_clear(map);
+    cxFree(map->allocator, hash_map->buckets);
+
+    // free the map structure
+    cxFree(map->allocator, map);
+}
+
+static int cx_hash_map_put(
+        CxMap *map,
+        CxHashKey key,
+        void *value
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    CxAllocator const *allocator = map->allocator;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+
+    while (elm != NULL && elm->key.hash < hash) {
+        prev = elm;
+        elm = elm->next;
+    }
+
+    if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len &&
+        memcmp(elm->key.data, key.data, key.len) == 0) {
+        // overwrite existing element
+        if (map->store_pointer) {
+            memcpy(elm->data, &value, sizeof(void *));
+        } else {
+            memcpy(elm->data, value, map->item_size);
+        }
+    } else {
+        // allocate new element
+        struct cx_hash_map_element_s *e = cxMalloc(
+                allocator,
+                sizeof(struct cx_hash_map_element_s) + map->item_size
+        );
+        if (e == NULL) {
+            return -1;
+        }
+
+        // write the value
+        if (map->store_pointer) {
+            memcpy(e->data, &value, sizeof(void *));
+        } else {
+            memcpy(e->data, value, map->item_size);
+        }
+
+        // copy the key
+        void *kd = cxMalloc(allocator, key.len);
+        if (kd == NULL) {
+            return -1;
+        }
+        memcpy(kd, key.data, key.len);
+        e->key.data = kd;
+        e->key.len = key.len;
+        e->key.hash = hash;
+
+        // insert the element into the linked list
+        if (prev == NULL) {
+            hash_map->buckets[slot] = e;
+        } else {
+            prev->next = e;
+        }
+        e->next = elm;
+
+        // increase the size
+        map->size++;
+    }
+
+    return 0;
+}
+
+static void cx_hash_map_unlink(
+        struct cx_hash_map_s *hash_map,
+        size_t slot,
+        struct cx_hash_map_element_s *prev,
+        struct cx_hash_map_element_s *elm
+) {
+    // unlink
+    if (prev == NULL) {
+        hash_map->buckets[slot] = elm->next;
+    } else {
+        prev->next = elm->next;
+    }
+    // free element
+    cxFree(hash_map->base.allocator, (void *) elm->key.data);
+    cxFree(hash_map->base.allocator, elm);
+    // decrease size
+    hash_map->base.size--;
+}
+
+/**
+ * Helper function to avoid code duplication.
+ *
+ * @param map the map
+ * @param key the key to look up
+ * @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
+ */
+static void *cx_hash_map_get_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool remove,
+        bool destroy
+) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+
+    unsigned hash = key.hash;
+    if (hash == 0) {
+        cx_hash_murmur(&key);
+        hash = key.hash;
+    }
+
+    size_t slot = hash % hash_map->bucket_count;
+    struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+    struct cx_hash_map_element_s *prev = NULL;
+    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);
+                } else {
+                    if (map->store_pointer) {
+                        data = *(void **) elm->data;
+                    } else {
+                        data = elm->data;
+                    }
+                }
+                if (remove) {
+                    cx_hash_map_unlink(hash_map, slot, prev, elm);
+                }
+                return data;
+            }
+        }
+        prev = elm;
+        elm = prev->next;
+    }
+
+    return NULL;
+}
+
+static void *cx_hash_map_get(
+        CxMap const *map,
+        CxHashKey key
+) {
+    // we can safely cast, because we know the map stays untouched
+    return cx_hash_map_get_remove((CxMap *) map, key, false, false);
+}
+
+static void *cx_hash_map_remove(
+        CxMap *map,
+        CxHashKey key,
+        bool destroy
+) {
+    return cx_hash_map_get_remove(map, key, true, destroy);
+}
+
+static void *cx_hash_map_iter_current_entry(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    // struct has to have a compatible signature
+    return (struct cx_map_entry_s *) &(iter->kv_data);
+}
+
+static void *cx_hash_map_iter_current_key(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    return &elm->key;
+}
+
+static void *cx_hash_map_iter_current_value(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    struct cx_hash_map_s const *map = iter->src_handle;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+    if (map->base.store_pointer) {
+        return *(void **) elm->data;
+    } else {
+        return elm->data;
+    }
+}
+
+static bool cx_hash_map_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_hash_map_iter_next(void *it) {
+    struct cx_iterator_s *iter = it;
+    struct cx_hash_map_element_s *elm = iter->elem_handle;
+
+    // remove current element, if asked
+    if (iter->base.remove) {
+        // obtain mutable pointer to the map
+        struct cx_mut_iterator_s *miter = it;
+        struct cx_hash_map_s *map = miter->src_handle;
+
+        // clear the flag
+        iter->base.remove = false;
+
+        // determine the next element
+        struct cx_hash_map_element_s *next = elm->next;
+
+        // search the previous element
+        struct cx_hash_map_element_s *prev = NULL;
+        if (map->buckets[iter->slot] != elm) {
+            prev = map->buckets[iter->slot];
+            while (prev->next != elm) {
+                prev = prev->next;
+            }
+        }
+
+        // destroy
+        cx_invoke_destructor((struct cx_map_s *) map, elm->data);
+
+        // unlink
+        cx_hash_map_unlink(map, iter->slot, prev, elm);
+
+        // advance
+        elm = next;
+    } else {
+        // just advance
+        elm = elm->next;
+        iter->index++;
+    }
+
+    // search the next bucket, if required
+    struct cx_hash_map_s const *map = iter->src_handle;
+    while (elm == NULL && ++iter->slot < map->bucket_count) {
+        elm = map->buckets[iter->slot];
+    }
+
+    // fill the struct with the next element
+    iter->elem_handle = elm;
+    if (elm == NULL) {
+        iter->kv_data.key = NULL;
+        iter->kv_data.value = NULL;
+    } else {
+        iter->kv_data.key = &elm->key;
+        if (map->base.store_pointer) {
+            iter->kv_data.value = *(void **) elm->data;
+        } else {
+            iter->kv_data.value = elm->data;
+        }
+    }
+}
+
+static bool cx_hash_map_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static CxIterator cx_hash_map_iterator(
+        CxMap const *map,
+        enum cx_map_iterator_type type
+) {
+    CxIterator iter;
+
+    iter.src_handle = map;
+    iter.base.valid = cx_hash_map_iter_valid;
+    iter.base.next = cx_hash_map_iter_next;
+
+    switch (type) {
+        case CX_MAP_ITERATOR_PAIRS:
+            iter.base.current = cx_hash_map_iter_current_entry;
+            break;
+        case CX_MAP_ITERATOR_KEYS:
+            iter.base.current = cx_hash_map_iter_current_key;
+            break;
+        case CX_MAP_ITERATOR_VALUES:
+            iter.base.current = cx_hash_map_iter_current_value;
+            break;
+        default:
+            assert(false);
+    }
+
+    iter.base.flag_removal = cx_hash_map_iter_flag_rm;
+    iter.base.remove = false;
+    iter.base.mutating = false;
+
+    iter.slot = 0;
+    iter.index = 0;
+
+    if (map->size > 0) {
+        struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+        struct cx_hash_map_element_s *elm = hash_map->buckets[0];
+        while (elm == NULL) {
+            elm = hash_map->buckets[++iter.slot];
+        }
+        iter.elem_handle = elm;
+        iter.kv_data.key = &elm->key;
+        if (map->store_pointer) {
+            iter.kv_data.value = *(void **) elm->data;
+        } else {
+            iter.kv_data.value = elm->data;
+        }
+    } else {
+        iter.elem_handle = NULL;
+        iter.kv_data.key = NULL;
+        iter.kv_data.value = NULL;
+    }
+
+    return iter;
+}
+
+static cx_map_class cx_hash_map_class = {
+        cx_hash_map_destructor,
+        cx_hash_map_clear,
+        cx_hash_map_put,
+        cx_hash_map_get,
+        cx_hash_map_remove,
+        cx_hash_map_iterator,
+};
+
+CxMap *cxHashMapCreate(
+        CxAllocator const *allocator,
+        size_t itemsize,
+        size_t buckets
+) {
+    if (buckets == 0) {
+        // implementation defined default
+        buckets = 16;
+    }
+
+    struct cx_hash_map_s *map = cxCalloc(allocator, 1,
+                                         sizeof(struct cx_hash_map_s));
+    if (map == NULL) return NULL;
+
+    // initialize hash map members
+    map->bucket_count = buckets;
+    map->buckets = cxCalloc(allocator, buckets,
+                            sizeof(struct cx_hash_map_element_s *));
+    if (map->buckets == NULL) {
+        cxFree(allocator, map);
+        return NULL;
+    }
+
+    // initialize base members
+    map->base.cl = &cx_hash_map_class;
+    map->base.allocator = allocator;
+
+    if (itemsize > 0) {
+        map->base.store_pointer = false;
+        map->base.item_size = itemsize;
+    } else {
+        map->base.store_pointer = true;
+        map->base.item_size = sizeof(void *);
+    }
+
+    return (CxMap *) map;
+}
+
+int cxMapRehash(CxMap *map) {
+    struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map;
+    if (map->size > ((hash_map->bucket_count * 3) >> 2)) {
+
+        size_t new_bucket_count = (map->size * 5) >> 1;
+        struct cx_hash_map_element_s **new_buckets = cxCalloc(
+                map->allocator,
+                new_bucket_count, sizeof(struct cx_hash_map_element_s *)
+        );
+
+        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) {
+            struct cx_hash_map_element_s *elm = hash_map->buckets[slot];
+            while (elm != NULL) {
+                struct cx_hash_map_element_s *next = elm->next;
+                size_t new_slot = elm->key.hash % new_bucket_count;
+
+                // find position where to insert
+                struct cx_hash_map_element_s *bucket_next = new_buckets[new_slot];
+                struct cx_hash_map_element_s *bucket_prev = NULL;
+                while (bucket_next != NULL &&
+                       bucket_next->key.hash < elm->key.hash) {
+                    bucket_prev = bucket_next;
+                    bucket_next = bucket_next->next;
+                }
+
+                // insert
+                if (bucket_prev == NULL) {
+                    elm->next = new_buckets[new_slot];
+                    new_buckets[new_slot] = elm;
+                } else {
+                    bucket_prev->next = elm;
+                    elm->next = bucket_next;
+                }
+
+                // advance
+                elm = next;
+            }
+        }
+
+        // assign result to the map
+        hash_map->bucket_count = new_bucket_count;
+        cxFree(map->allocator, hash_map->buckets);
+        hash_map->buckets = new_buckets;
+    }
+    return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/linked_list.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,927 @@
+/*
+ * 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/linked_list.h"
+#include "cx/utils.h"
+#include <string.h>
+#include <assert.h>
+
+// LOW LEVEL LINKED LIST FUNCTIONS
+
+#define CX_LL_PTR(cur, off) (*(void**)(((char*)(cur))+(off)))
+#define ll_prev(node) CX_LL_PTR(node, loc_prev)
+#define ll_next(node) CX_LL_PTR(node, loc_next)
+#define ll_advance(node) CX_LL_PTR(node, loc_advance)
+#define ll_data(node) (((char*)(node))+loc_data)
+
+void *cx_linked_list_at(
+        void const *start,
+        size_t start_index,
+        ptrdiff_t loc_advance,
+        size_t index
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    size_t i = start_index;
+    void const *cur = start;
+    while (i != index && cur != NULL) {
+        cur = ll_advance(cur);
+        i < index ? i++ : i--;
+    }
+    return (void *) cur;
+}
+
+ssize_t cx_linked_list_find(
+        void const *start,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func,
+        void const *elem
+) {
+    assert(start != NULL);
+    assert(loc_advance >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    void const *node = start;
+    ssize_t index = 0;
+    do {
+        void *current = ll_data(node);
+        if (cmp_func(current, elem) == 0) {
+            return index;
+        }
+        node = ll_advance(node);
+        index++;
+    } while (node != NULL);
+    return -1;
+}
+
+void *cx_linked_list_first(
+        void const *node,
+        ptrdiff_t loc_prev
+) {
+    return cx_linked_list_last(node, loc_prev);
+}
+
+void *cx_linked_list_last(
+        void const *node,
+        ptrdiff_t loc_next
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+
+    void const *cur = node;
+    void const *last;
+    do {
+        last = cur;
+    } while ((cur = ll_next(cur)) != NULL);
+
+    return (void *) last;
+}
+
+void *cx_linked_list_prev(
+        void const *begin,
+        ptrdiff_t loc_next,
+        void const *node
+) {
+    assert(begin != NULL);
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    if (begin == node) return NULL;
+    void const *cur = begin;
+    void const *next;
+    while (1) {
+        next = ll_next(cur);
+        if (next == node) return (void *) cur;
+        cur = next;
+    }
+}
+
+void cx_linked_list_link(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    ll_next(left) = right;
+    if (loc_prev >= 0) {
+        ll_prev(right) = left;
+    }
+}
+
+void cx_linked_list_unlink(
+        void *left,
+        void *right,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert (loc_next >= 0);
+    assert(ll_next(left) == right);
+    ll_next(left) = NULL;
+    if (loc_prev >= 0) {
+        assert(ll_prev(right) == left);
+        ll_prev(right) = NULL;
+    }
+}
+
+void cx_linked_list_add(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    void *last;
+    if (end == NULL) {
+        assert(begin != NULL);
+        last = *begin == NULL ? NULL : cx_linked_list_last(*begin, loc_next);
+    } else {
+        last = *end;
+    }
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
+}
+
+void cx_linked_list_prepend(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, NULL, new_node, new_node);
+}
+
+void cx_linked_list_insert(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *new_node
+) {
+    cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
+}
+
+void cx_linked_list_insert_chain(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node,
+        void *insert_begin,
+        void *insert_end
+) {
+    // find the end of the chain, if not specified
+    if (insert_end == NULL) {
+        insert_end = cx_linked_list_last(insert_begin, loc_next);
+    }
+
+    // determine the successor
+    void *successor;
+    if (node == NULL) {
+        assert(begin != NULL || (end != NULL && loc_prev >= 0));
+        if (begin != NULL) {
+            successor = *begin;
+            *begin = insert_begin;
+        } else {
+            successor = *end == NULL ? NULL : cx_linked_list_first(*end, loc_prev);
+        }
+    } else {
+        successor = ll_next(node);
+        cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
+    }
+
+    if (successor == NULL) {
+        // the list ends with the new chain
+        if (end != NULL) {
+            *end = insert_end;
+        }
+    } else {
+        cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
+    }
+}
+
+void cx_linked_list_remove(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        void *node
+) {
+    assert(node != NULL);
+    assert(loc_next >= 0);
+    assert(loc_prev >= 0 || begin != NULL);
+
+    // find adjacent nodes
+    void *next = ll_next(node);
+    void *prev;
+    if (loc_prev >= 0) {
+        prev = ll_prev(node);
+    } else {
+        prev = cx_linked_list_prev(*begin, loc_next, node);
+    }
+
+    // update next pointer of prev node, or set begin
+    if (prev == NULL) {
+        if (begin != NULL) {
+            *begin = next;
+        }
+    } else {
+        ll_next(prev) = next;
+    }
+
+    // update prev pointer of next node, or set end
+    if (next == NULL) {
+        if (end != NULL) {
+            *end = prev;
+        }
+    } else if (loc_prev >= 0) {
+        ll_prev(next) = prev;
+    }
+}
+
+size_t cx_linked_list_size(
+        void const *node,
+        ptrdiff_t loc_next
+) {
+    assert(loc_next >= 0);
+    size_t size = 0;
+    while (node != NULL) {
+        node = ll_next(node);
+        size++;
+    }
+    return size;
+}
+
+#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
+#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
+#endif
+
+static void cx_linked_list_sort_merge(
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        size_t length,
+        void *ls,
+        void *le,
+        void *re,
+        cx_compare_func cmp_func,
+        void **begin,
+        void **end
+) {
+    void *sbo[CX_LINKED_LIST_SORT_SBO_SIZE];
+    void **sorted = length >= CX_LINKED_LIST_SORT_SBO_SIZE ?
+                    malloc(sizeof(void *) * length) : sbo;
+    if (sorted == NULL) abort();
+    void *rc, *lc;
+
+    lc = ls;
+    rc = le;
+    size_t n = 0;
+    while (lc && lc != le && rc != re) {
+        if (cmp_func(ll_data(lc), ll_data(rc)) <= 0) {
+            sorted[n] = lc;
+            lc = ll_next(lc);
+        } else {
+            sorted[n] = rc;
+            rc = ll_next(rc);
+        }
+        n++;
+    }
+    while (lc && lc != le) {
+        sorted[n] = lc;
+        lc = ll_next(lc);
+        n++;
+    }
+    while (rc && rc != re) {
+        sorted[n] = rc;
+        rc = ll_next(rc);
+        n++;
+    }
+
+    // Update pointer
+    if (loc_prev >= 0) ll_prev(sorted[0]) = NULL;
+    cx_for_n (i, length - 1) {
+        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];
+    if (sorted != sbo) {
+        free(sorted);
+    }
+}
+
+void cx_linked_list_sort( // NOLINT(misc-no-recursion) - purposely recursive function
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+    assert(loc_data >= 0);
+    assert(cmp_func);
+
+    void *lc, *ls, *le, *re;
+
+    // set start node
+    ls = *begin;
+
+    // early exit when this list is empty
+    if (ls == NULL) return;
+
+    // check how many elements are already sorted
+    lc = ls;
+    size_t ln = 1;
+    while (ll_next(lc) != NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) > 0) {
+        lc = ll_next(lc);
+        ln++;
+    }
+    le = ll_next(lc);
+
+    // if first unsorted node is NULL, the list is already completely sorted
+    if (le != NULL) {
+        void *rc;
+        size_t rn = 1;
+        rc = le;
+        // skip already sorted elements
+        while (ll_next(rc) != NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) > 0) {
+            rc = ll_next(rc);
+            rn++;
+        }
+        re = ll_next(rc);
+
+        // {ls,...,le->prev} and {rs,...,re->prev} are sorted - merge them
+        void *sorted_begin, *sorted_end;
+        cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                  ln + rn, ls, le, re, cmp_func,
+                                  &sorted_begin, &sorted_end);
+
+        // Something left? Sort it!
+        size_t remainder_length = cx_linked_list_size(re, loc_next);
+        if (remainder_length > 0) {
+            void *remainder = re;
+            cx_linked_list_sort(&remainder, NULL, loc_prev, loc_next, loc_data, cmp_func);
+
+            // merge sorted list with (also sorted) remainder
+            cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
+                                      ln + rn + remainder_length,
+                                      sorted_begin, remainder, NULL, cmp_func,
+                                      &sorted_begin, &sorted_end);
+        }
+        *begin = sorted_begin;
+        if (end) *end = sorted_end;
+    }
+}
+
+int cx_linked_list_compare(
+        void const *begin_left,
+        void const *begin_right,
+        ptrdiff_t loc_advance,
+        ptrdiff_t loc_data,
+        cx_compare_func cmp_func
+) {
+    void const *left = begin_left, *right = begin_right;
+
+    while (left != NULL && right != NULL) {
+        void const *left_data = ll_data(left);
+        void const *right_data = ll_data(right);
+        int result = cmp_func(left_data, right_data);
+        if (result != 0) return result;
+        left = ll_advance(left);
+        right = ll_advance(right);
+    }
+
+    if (left != NULL) { return 1; }
+    else if (right != NULL) { return -1; }
+    else { return 0; }
+}
+
+void cx_linked_list_reverse(
+        void **begin,
+        void **end,
+        ptrdiff_t loc_prev,
+        ptrdiff_t loc_next
+) {
+    assert(begin != NULL);
+    assert(loc_next >= 0);
+
+    // swap all links
+    void *prev = NULL;
+    void *cur = *begin;
+    while (cur != NULL) {
+        void *next = ll_next(cur);
+
+        ll_next(cur) = prev;
+        if (loc_prev >= 0) {
+            ll_prev(cur) = next;
+        }
+
+        prev = cur;
+        cur = next;
+    }
+
+    // update begin and end
+    if (end != NULL) {
+        *end = *begin;
+    }
+    *begin = prev;
+}
+
+// HIGH LEVEL LINKED LIST IMPLEMENTATION
+
+bool CX_DISABLE_LINKED_LIST_SWAP_SBO = false;
+
+typedef struct cx_linked_list_node cx_linked_list_node;
+struct cx_linked_list_node {
+    cx_linked_list_node *prev;
+    cx_linked_list_node *next;
+    char payload[];
+};
+
+#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
+#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
+#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)
+
+typedef struct {
+    struct cx_list_s base;
+    cx_linked_list_node *begin;
+    cx_linked_list_node *end;
+} cx_linked_list;
+
+static cx_linked_list_node *cx_ll_node_at(
+        cx_linked_list const *list,
+        size_t index
+) {
+    if (index >= list->base.size) {
+        return NULL;
+    } else if (index > list->base.size / 2) {
+        return cx_linked_list_at(list->end, list->base.size - 1, CX_LL_LOC_PREV, index);
+    } else {
+        return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index);
+    }
+}
+
+static int cx_ll_insert_at(
+        struct cx_list_s *list,
+        cx_linked_list_node *node,
+        void const *elem
+) {
+
+    // create the new new_node
+    cx_linked_list_node *new_node = cxMalloc(list->allocator,
+                                             sizeof(cx_linked_list_node) + list->item_size);
+
+    // sortir if failed
+    if (new_node == NULL) return 1;
+
+    // initialize new new_node
+    new_node->prev = new_node->next = NULL;
+    memcpy(new_node->payload, elem, list->item_size);
+
+    // insert
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_insert_chain(
+            (void **) &ll->begin, (void **) &ll->end,
+            CX_LL_LOC_PREV, CX_LL_LOC_NEXT,
+            node, new_node, new_node
+    );
+
+    // increase the size and return
+    list->size++;
+    return 0;
+}
+
+static size_t cx_ll_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    // out-of bounds and corner case check
+    if (index > list->size || n == 0) return 0;
+
+    // find position efficiently
+    cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1);
+
+    // perform first insert
+    if (0 != cx_ll_insert_at(list, node, array)) {
+        return 1;
+    }
+
+    // is there more?
+    if (n == 1) return 1;
+
+    // we now know exactly where we are
+    node = node == NULL ? ((cx_linked_list *) list)->begin : node->next;
+
+    // we can add the remaining nodes and immedately advance to the inserted node
+    char const *source = array;
+    for (size_t i = 1; i < n; i++) {
+        source += list->item_size;
+        if (0 != cx_ll_insert_at(list, node, source)) {
+            return i;
+        }
+        node = node->next;
+    }
+    return n;
+}
+
+static int cx_ll_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return 1 != cx_ll_insert_array(list, index, element, 1);
+}
+
+static int cx_ll_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    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);
+
+    // remove
+    cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                          CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+
+    // adjust size
+    list->size--;
+
+    // free and return
+    cxFree(list->allocator, node);
+
+    return 0;
+}
+
+static void cx_ll_clear(struct cx_list_s *list) {
+    if (list->size == 0) return;
+
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = ll->begin;
+    while (node != NULL) {
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_node *next = node->next;
+        cxFree(list->allocator, node);
+        node = next;
+    }
+    ll->begin = ll->end = NULL;
+    list->size = 0;
+}
+
+#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
+#define CX_LINKED_LIST_SWAP_SBO_SIZE 16
+#endif
+
+static int cx_ll_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    if (i >= list->size || j >= list->size) return 1;
+    if (i == j) return 0;
+
+    // perform an optimized search that finds both elements in one run
+    cx_linked_list *ll = (cx_linked_list *) list;
+    size_t mid = list->size / 2;
+    size_t left, right;
+    if (i < j) {
+        left = i;
+        right = j;
+    } else {
+        left = j;
+        right = i;
+    }
+    cx_linked_list_node *nleft, *nright;
+    if (left < mid && right < mid) {
+        // case 1: both items left from mid
+        nleft = cx_ll_node_at(ll, left);
+        nright = nleft;
+        for (size_t c = left; c < right; c++) {
+            nright = nright->next;
+        }
+    } else if (left >= mid && right >= mid) {
+        // case 2: both items right from mid
+        nright = cx_ll_node_at(ll, right);
+        nleft = nright;
+        for (size_t c = right; c > left; c--) {
+            nleft = nleft->prev;
+        }
+    } else {
+        // case 3: one item left, one item right
+
+        // chose the closest to begin / end
+        size_t closest;
+        size_t other;
+        size_t diff2boundary = list->size - right - 1;
+        if (left <= diff2boundary) {
+            closest = left;
+            other = right;
+            nleft = cx_ll_node_at(ll, left);
+        } else {
+            closest = right;
+            other = left;
+            diff2boundary = left;
+            nright = cx_ll_node_at(ll, right);
+        }
+
+        // is other element closer to us or closer to boundary?
+        if (right - left <= diff2boundary) {
+            // search other element starting from already found element
+            if (closest == left) {
+                nright = nleft;
+                for (size_t c = left; c < right; c++) {
+                    nright = nright->next;
+                }
+            } else {
+                nleft = nright;
+                for (size_t c = right; c > left; c--) {
+                    nleft = nleft->prev;
+                }
+            }
+        } else {
+            // search other element starting at the boundary
+            if (closest == left) {
+                nright = cx_ll_node_at(ll, other);
+            } else {
+                nleft = cx_ll_node_at(ll, other);
+            }
+        }
+    }
+
+    if (list->item_size > CX_LINKED_LIST_SWAP_SBO_SIZE || CX_DISABLE_LINKED_LIST_SWAP_SBO) {
+        cx_linked_list_node *prev = nleft->prev;
+        cx_linked_list_node *next = nright->next;
+        cx_linked_list_node *midstart = nleft->next;
+        cx_linked_list_node *midend = nright->prev;
+
+        if (prev == NULL) {
+            ll->begin = nright;
+        } else {
+            prev->next = nright;
+        }
+        nright->prev = prev;
+        if (midstart == nright) {
+            // special case: both nodes are adjacent
+            nright->next = nleft;
+            nleft->prev = nright;
+        } else {
+            // likely case: a chain is between the two nodes
+            nright->next = midstart;
+            midstart->prev = nright;
+            midend->next = nleft;
+            nleft->prev = midend;
+        }
+        nleft->next = next;
+        if (next == NULL) {
+            ll->end = nleft;
+        } else {
+            next->prev = nleft;
+        }
+    } else {
+        // swap payloads to avoid relinking
+        char buf[CX_LINKED_LIST_SWAP_SBO_SIZE];
+        memcpy(buf, nleft->payload, list->item_size);
+        memcpy(nleft->payload, nright->payload, list->item_size);
+        memcpy(nright->payload, buf, list->item_size);
+    }
+
+    return 0;
+}
+
+static void *cx_ll_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_node *node = cx_ll_node_at(ll, index);
+    return node == NULL ? NULL : node->payload;
+}
+
+static ssize_t cx_ll_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    return cx_linked_list_find(((cx_linked_list *) list)->begin,
+                               CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                               list->cmpfunc, elem);
+}
+
+static void cx_ll_sort(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_sort((void **) &ll->begin, (void **) &ll->end,
+                        CX_LL_LOC_PREV, CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                        list->cmpfunc);
+}
+
+static void cx_ll_reverse(struct cx_list_s *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+    cx_linked_list_reverse((void **) &ll->begin, (void **) &ll->end, CX_LL_LOC_PREV, CX_LL_LOC_NEXT);
+}
+
+static int cx_ll_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    cx_linked_list *left = (cx_linked_list *) list;
+    cx_linked_list *right = (cx_linked_list *) other;
+    return cx_linked_list_compare(left->begin, right->begin,
+                                  CX_LL_LOC_NEXT, CX_LL_LOC_DATA,
+                                  list->cmpfunc);
+}
+
+static bool cx_ll_iter_valid(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    return iter->elem_handle != NULL;
+}
+
+static void cx_ll_iter_next(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        itbase->remove = false;
+        struct cx_mut_iterator_s *iter = it;
+        struct cx_list_s *list = iter->src_handle;
+        cx_linked_list *ll = iter->src_handle;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->size--;
+        cxFree(list->allocator, node);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index++;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->next;
+    }
+}
+
+static void cx_ll_iter_prev(void *it) {
+    struct cx_iterator_base_s *itbase = it;
+    if (itbase->remove) {
+        itbase->remove = false;
+        struct cx_mut_iterator_s *iter = it;
+        struct cx_list_s *list = iter->src_handle;
+        cx_linked_list *ll = iter->src_handle;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+        iter->index--;
+        cx_invoke_destructor(list, node->payload);
+        cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end,
+                              CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node);
+        list->size--;
+        cxFree(list->allocator, node);
+    } else {
+        struct cx_iterator_s *iter = it;
+        iter->index--;
+        cx_linked_list_node *node = iter->elem_handle;
+        iter->elem_handle = node->prev;
+    }
+}
+
+static void *cx_ll_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    cx_linked_list_node *node = iter->elem_handle;
+    return node->payload;
+}
+
+static bool cx_ll_iter_flag_rm(void *it) {
+    struct cx_iterator_base_s *iter = it;
+    if (iter->mutating) {
+        iter->remove = true;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static CxIterator cx_ll_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    CxIterator iter;
+    iter.index = index;
+    iter.src_handle = list;
+    iter.elem_handle = cx_ll_node_at((cx_linked_list const *) list, index);
+    iter.base.valid = cx_ll_iter_valid;
+    iter.base.current = cx_ll_iter_current;
+    iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
+    iter.base.flag_removal = cx_ll_iter_flag_rm;
+    iter.base.mutating = false;
+    iter.base.remove = false;
+    return iter;
+}
+
+static int cx_ll_insert_iter(
+        CxMutIterator *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    cx_linked_list_node *node = iter->elem_handle;
+    if (node != NULL) {
+        assert(prepend >= 0 && prepend <= 1);
+        cx_linked_list_node *choice[2] = {node, node->prev};
+        int result = cx_ll_insert_at(list, choice[prepend], elem);
+        iter->index += prepend * (0 == result);
+        return result;
+    } else {
+        int result = cx_ll_insert_element(list, list->size, elem);
+        iter->index = list->size;
+        return result;
+    }
+}
+
+static void cx_ll_destructor(CxList *list) {
+    cx_linked_list *ll = (cx_linked_list *) list;
+
+    cx_linked_list_node *node = ll->begin;
+    while (node) {
+        cx_invoke_destructor(list, node->payload);
+        void *next = node->next;
+        cxFree(list->allocator, node);
+        node = next;
+    }
+
+    cxFree(list->allocator, list);
+}
+
+static cx_list_class cx_linked_list_class = {
+        cx_ll_destructor,
+        cx_ll_insert_element,
+        cx_ll_insert_array,
+        cx_ll_insert_iter,
+        cx_ll_remove,
+        cx_ll_clear,
+        cx_ll_swap,
+        cx_ll_at,
+        cx_ll_find,
+        cx_ll_sort,
+        cx_ll_compare,
+        cx_ll_reverse,
+        cx_ll_iterator,
+};
+
+CxList *cxLinkedListCreate(
+        CxAllocator const *allocator,
+        cx_compare_func comparator,
+        size_t item_size
+) {
+    if (allocator == NULL) {
+        allocator = cxDefaultAllocator;
+    }
+
+    cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list));
+    if (list == NULL) return NULL;
+
+    list->base.cl = &cx_linked_list_class;
+    list->base.allocator = allocator;
+    list->base.cmpfunc = comparator;
+
+    if (item_size > 0) {
+        list->base.item_size = item_size;
+    } else {
+        cxListStorePointers((CxList *) list);
+    }
+
+    return (CxList *) list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/list.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,342 @@
+/*
+ * 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/list.h"
+
+#include <string.h>
+
+// <editor-fold desc="Store Pointers Functionality">
+
+static _Thread_local cx_compare_func cx_pl_cmpfunc_impl;
+
+static int cx_pl_cmpfunc(
+        void const *l,
+        void const *r
+) {
+    void *const *lptr = l;
+    void *const *rptr = r;
+    void const *left = lptr == NULL ? NULL : *lptr;
+    void const *right = rptr == NULL ? NULL : *rptr;
+    return cx_pl_cmpfunc_impl(left, right);
+}
+
+static void cx_pl_hack_cmpfunc(struct cx_list_s const *list) {
+    // cast away const - this is the hacky thing
+    struct cx_list_s *l = (struct cx_list_s *) list;
+    cx_pl_cmpfunc_impl = l->cmpfunc;
+    l->cmpfunc = cx_pl_cmpfunc;
+}
+
+static void cx_pl_unhack_cmpfunc(struct cx_list_s const *list) {
+    // cast away const - this is the hacky thing
+    struct cx_list_s *l = (struct cx_list_s *) list;
+    l->cmpfunc = cx_pl_cmpfunc_impl;
+}
+
+static void cx_pl_destructor(struct cx_list_s *list) {
+    list->climpl->destructor(list);
+}
+
+static int cx_pl_insert_element(
+        struct cx_list_s *list,
+        size_t index,
+        void const *element
+) {
+    return list->climpl->insert_element(list, index, &element);
+}
+
+static size_t cx_pl_insert_array(
+        struct cx_list_s *list,
+        size_t index,
+        void const *array,
+        size_t n
+) {
+    return list->climpl->insert_array(list, index, array, n);
+}
+
+static int cx_pl_insert_iter(
+        struct cx_mut_iterator_s *iter,
+        void const *elem,
+        int prepend
+) {
+    struct cx_list_s *list = iter->src_handle;
+    return list->climpl->insert_iter(iter, &elem, prepend);
+}
+
+static int cx_pl_remove(
+        struct cx_list_s *list,
+        size_t index
+) {
+    return list->climpl->remove(list, index);
+}
+
+static void cx_pl_clear(struct cx_list_s *list) {
+    list->climpl->clear(list);
+}
+
+static int cx_pl_swap(
+        struct cx_list_s *list,
+        size_t i,
+        size_t j
+) {
+    return list->climpl->swap(list, i, j);
+}
+
+static void *cx_pl_at(
+        struct cx_list_s const *list,
+        size_t index
+) {
+    void **ptr = list->climpl->at(list, index);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static ssize_t cx_pl_find(
+        struct cx_list_s const *list,
+        void const *elem
+) {
+    cx_pl_hack_cmpfunc(list);
+    ssize_t ret = list->climpl->find(list, &elem);
+    cx_pl_unhack_cmpfunc(list);
+    return ret;
+}
+
+static void cx_pl_sort(struct cx_list_s *list) {
+    cx_pl_hack_cmpfunc(list);
+    list->climpl->sort(list);
+    cx_pl_unhack_cmpfunc(list);
+}
+
+static int cx_pl_compare(
+        struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    cx_pl_hack_cmpfunc(list);
+    int ret = list->climpl->compare(list, other);
+    cx_pl_unhack_cmpfunc(list);
+    return ret;
+}
+
+static void cx_pl_reverse(struct cx_list_s *list) {
+    list->climpl->reverse(list);
+}
+
+static void *cx_pl_iter_current(void const *it) {
+    struct cx_iterator_s const *iter = it;
+    void **ptr = iter->base.current_impl(it);
+    return ptr == NULL ? NULL : *ptr;
+}
+
+static struct cx_iterator_s cx_pl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        bool backwards
+) {
+    struct cx_iterator_s iter = list->climpl->iterator(list, index, backwards);
+    iter.base.current_impl = iter.base.current;
+    iter.base.current = cx_pl_iter_current;
+    return iter;
+}
+
+static cx_list_class cx_pointer_list_class = {
+        cx_pl_destructor,
+        cx_pl_insert_element,
+        cx_pl_insert_array,
+        cx_pl_insert_iter,
+        cx_pl_remove,
+        cx_pl_clear,
+        cx_pl_swap,
+        cx_pl_at,
+        cx_pl_find,
+        cx_pl_sort,
+        cx_pl_compare,
+        cx_pl_reverse,
+        cx_pl_iterator,
+};
+
+void cxListStoreObjects(CxList *list) {
+    list->store_pointer = false;
+    if (list->climpl != NULL) {
+        list->cl = list->climpl;
+        list->climpl = NULL;
+    }
+}
+
+void cxListStorePointers(CxList *list) {
+    list->item_size = sizeof(void *);
+    list->store_pointer = true;
+    list->climpl = list->cl;
+    list->cl = &cx_pointer_list_class;
+}
+
+// </editor-fold>
+
+// <editor-fold desc="empty list implementation">
+
+static void cx_emptyl_noop(__attribute__((__unused__)) CxList *list) {
+    // this is a noop, but MUST be implemented
+}
+
+static void *cx_emptyl_at(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        __attribute__((__unused__)) size_t index
+) {
+    return NULL;
+}
+
+static ssize_t cx_emptyl_find(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        __attribute__((__unused__)) void const *elem
+) {
+    return -1;
+}
+
+static int cx_emptyl_compare(
+        __attribute__((__unused__)) struct cx_list_s const *list,
+        struct cx_list_s const *other
+) {
+    if (other->size == 0) return 0;
+    return -1;
+}
+
+static bool cx_emptyl_iter_valid(__attribute__((__unused__)) void const *iter) {
+    return false;
+}
+
+static CxIterator cx_emptyl_iterator(
+        struct cx_list_s const *list,
+        size_t index,
+        __attribute__((__unused__)) bool backwards
+) {
+    CxIterator iter = {0};
+    iter.src_handle = list;
+    iter.index = index;
+    iter.base.valid = cx_emptyl_iter_valid;
+    return iter;
+}
+
+static cx_list_class cx_empty_list_class = {
+        cx_emptyl_noop,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        cx_emptyl_noop,
+        NULL,
+        cx_emptyl_at,
+        cx_emptyl_find,
+        cx_emptyl_noop,
+        cx_emptyl_compare,
+        cx_emptyl_noop,
+        cx_emptyl_iterator,
+};
+
+CxList cx_empty_list = {
+        NULL,
+        NULL,
+        0,
+        0,
+        NULL,
+        NULL,
+        NULL,
+        false,
+        &cx_empty_list_class,
+        NULL
+};
+
+CxList *const cxEmptyList = &cx_empty_list;
+
+// </editor-fold>
+
+void cxListDestroy(CxList *list) {
+    list->cl->destructor(list);
+}
+
+int cxListCompare(
+        CxList const *list,
+        CxList const *other
+) {
+    if (
+        // if one is storing pointers but the other is not
+        (list->store_pointer ^ other->store_pointer) ||
+
+        // if one class is wrapped but the other is not
+        ((list->climpl == NULL) ^ (other->climpl == NULL)) ||
+
+        // if the resolved compare functions are not the same
+        ((list->climpl != NULL ? list->climpl->compare : list->cl->compare) !=
+         (other->climpl != NULL ? other->climpl->compare : other->cl->compare))
+    ) {
+        // lists are definitely different - cannot use internal compare function
+        if (list->size == other->size) {
+            CxIterator left = cxListIterator(list);
+            CxIterator right = cxListIterator(other);
+            for (size_t i = 0; i < list->size; i++) {
+                void *leftValue = cxIteratorCurrent(left);
+                void *rightValue = cxIteratorCurrent(right);
+                int d = list->cmpfunc(leftValue, rightValue);
+                if (d != 0) {
+                    return d;
+                }
+                cxIteratorNext(left);
+                cxIteratorNext(right);
+            }
+            return 0;
+        } else {
+            return list->size < other->size ? -1 : 1;
+        }
+    } else {
+        // lists are compatible
+        return list->cl->compare(list, other);
+    }
+}
+
+CxMutIterator cxListMutIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, false);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxListMutBackwardsIteratorAt(
+        CxList *list,
+        size_t index
+) {
+    CxIterator it = list->cl->iterator(list, index, true);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/map.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,112 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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/map.h"
+#include <string.h>
+
+// <editor-fold desc="empty map implementation">
+
+static void cx_empty_map_noop(__attribute__((__unused__)) CxMap *map) {
+    // this is a noop, but MUST be implemented
+}
+
+static void *cx_empty_map_get(
+        __attribute__((__unused__)) CxMap const *map,
+        __attribute__((__unused__)) CxHashKey key
+) {
+    return NULL;
+}
+
+static bool cx_empty_map_iter_valid(__attribute__((__unused__)) void const *iter) {
+    return false;
+}
+
+static CxIterator cx_empty_map_iterator(
+        struct cx_map_s const *map,
+        __attribute__((__unused__)) enum cx_map_iterator_type type
+) {
+    CxIterator iter = {0};
+    iter.src_handle = map;
+    iter.base.valid = cx_empty_map_iter_valid;
+    return iter;
+}
+
+static struct cx_map_class_s cx_empty_map_class = {
+        cx_empty_map_noop,
+        cx_empty_map_noop,
+        NULL,
+        cx_empty_map_get,
+        NULL,
+        cx_empty_map_iterator
+};
+
+CxMap cx_empty_map = {
+        NULL,
+        NULL,
+        0,
+        0,
+        NULL,
+        NULL,
+        NULL,
+        false,
+        &cx_empty_map_class
+};
+
+CxMap *const cxEmptyMap = &cx_empty_map;
+
+// </editor-fold>
+
+CxMutIterator cxMapMutIteratorValues(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxMapMutIteratorKeys(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
+
+CxMutIterator cxMapMutIterator(CxMap *map) {
+    CxIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS);
+    it.base.mutating = true;
+
+    // we know the iterators share the same memory layout
+    CxMutIterator iter;
+    memcpy(&iter, &it, sizeof(CxMutIterator));
+    return iter;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/printf.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,129 @@
+/*
+ * 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/printf.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#ifndef CX_PRINTF_SBO_SIZE
+#define CX_PRINTF_SBO_SIZE 512
+#endif
+
+int cx_fprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        ...
+) {
+    int ret;
+    va_list ap;
+    va_start(ap, fmt);
+    ret = cx_vfprintf(stream, wfc, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+int cx_vfprintf(
+        void *stream,
+        cx_write_func wfc,
+        char const *fmt,
+        va_list ap
+) {
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret < 0) {
+        return ret;
+    } else if (ret < CX_PRINTF_SBO_SIZE) {
+        return (int) wfc(buf, 1, ret, stream);
+    } else {
+        int len = ret + 1;
+        char *newbuf = malloc(len);
+        if (!newbuf) {
+            return -1;
+        }
+
+        ret = vsnprintf(newbuf, len, fmt, ap2);
+        if (ret > 0) {
+            ret = (int) wfc(newbuf, 1, ret, stream);
+        }
+        free(newbuf);
+    }
+    return ret;
+}
+
+cxmutstr cx_asprintf_a(
+        CxAllocator const *allocator,
+        char const *fmt,
+        ...
+) {
+    va_list ap;
+    cxmutstr ret;
+    va_start(ap, fmt);
+    ret = cx_vasprintf_a(allocator, fmt, ap);
+    va_end(ap);
+    return ret;
+}
+
+cxmutstr cx_vasprintf_a(
+        CxAllocator const *a,
+        char const *fmt,
+        va_list ap
+) {
+    cxmutstr s;
+    s.ptr = NULL;
+    s.length = 0;
+    char buf[CX_PRINTF_SBO_SIZE];
+    va_list ap2;
+    va_copy(ap2, ap);
+    int ret = vsnprintf(buf, CX_PRINTF_SBO_SIZE, fmt, ap);
+    if (ret > 0 && ret < CX_PRINTF_SBO_SIZE) {
+        s.ptr = cxMalloc(a, ret + 1);
+        if (s.ptr) {
+            s.length = (size_t) ret;
+            memcpy(s.ptr, buf, ret);
+            s.ptr[s.length] = '\0';
+        }
+    } else {
+        int len = ret + 1;
+        s.ptr = cxMalloc(a, len);
+        if (s.ptr) {
+            ret = vsnprintf(s.ptr, len, fmt, ap2);
+            if (ret < 0) {
+                free(s.ptr);
+                s.ptr = NULL;
+            } else {
+                s.length = (size_t) ret;
+            }
+        }
+    }
+    return s;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/string.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,785 @@
+/*
+ * 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/string.h"
+#include "cx/utils.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+
+#ifndef _WIN32
+
+#include <strings.h> // for strncasecmp()
+
+#endif // _WIN32
+
+cxmutstr cx_mutstr(char *cstring) {
+    return (cxmutstr) {cstring, strlen(cstring)};
+}
+
+cxmutstr cx_mutstrn(
+        char *cstring,
+        size_t length
+) {
+    return (cxmutstr) {cstring, length};
+}
+
+cxstring cx_str(const char *cstring) {
+    return (cxstring) {cstring, strlen(cstring)};
+}
+
+cxstring cx_strn(
+        const char *cstring,
+        size_t length
+) {
+    return (cxstring) {cstring, length};
+}
+
+cxstring cx_strcast(cxmutstr str) {
+    return (cxstring) {str.ptr, str.length};
+}
+
+void cx_strfree(cxmutstr *str) {
+    free(str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
+}
+
+void cx_strfree_a(
+        CxAllocator const *alloc,
+        cxmutstr *str
+) {
+    cxFree(alloc, str->ptr);
+    str->ptr = NULL;
+    str->length = 0;
+}
+
+size_t cx_strlen(
+        size_t count,
+        ...
+) {
+    if (count == 0) return 0;
+
+    va_list ap;
+    va_start(ap, count);
+    size_t size = 0;
+    cx_for_n(i, count) {
+        cxstring str = va_arg(ap, cxstring);
+        size += str.length;
+    }
+    va_end(ap);
+
+    return size;
+}
+
+cxmutstr cx_strcat_ma(
+        CxAllocator const *alloc,
+        cxmutstr str,
+        size_t count,
+        ...
+) {
+    if (count == 0) return str;
+
+    cxstring *strings = calloc(count, sizeof(cxstring));
+    if (!strings) abort();
+
+    va_list ap;
+    va_start(ap, count);
+
+    // get all args and overall length
+    size_t slen = str.length;
+    cx_for_n(i, count) {
+        cxstring s = va_arg (ap, cxstring);
+        strings[i] = s;
+        slen += s.length;
+    }
+    va_end(ap);
+
+    // reallocate or create new string
+    if (str.ptr == NULL) {
+        str.ptr = cxMalloc(alloc, slen + 1);
+    } else {
+        str.ptr = cxRealloc(alloc, str.ptr, slen + 1);
+    }
+    if (str.ptr == NULL) abort();
+
+    // concatenate strings
+    size_t pos = str.length;
+    str.length = slen;
+    cx_for_n(i, count) {
+        cxstring s = strings[i];
+        memcpy(str.ptr + pos, s.ptr, s.length);
+        pos += s.length;
+    }
+
+    // terminate string
+    str.ptr[str.length] = '\0';
+
+    // free temporary array
+    free(strings);
+
+    return str;
+}
+
+cxstring cx_strsubs(
+        cxstring string,
+        size_t start
+) {
+    return cx_strsubsl(string, start, string.length - start);
+}
+
+cxmutstr cx_strsubs_m(
+        cxmutstr string,
+        size_t start
+) {
+    return cx_strsubsl_m(string, start, string.length - start);
+}
+
+cxstring cx_strsubsl(
+        cxstring string,
+        size_t start,
+        size_t length
+) {
+    if (start > string.length) {
+        return (cxstring) {NULL, 0};
+    }
+
+    size_t rem_len = string.length - start;
+    if (length > rem_len) {
+        length = rem_len;
+    }
+
+    return (cxstring) {string.ptr + start, length};
+}
+
+cxmutstr cx_strsubsl_m(
+        cxmutstr string,
+        size_t start,
+        size_t length
+) {
+    cxstring result = cx_strsubsl(cx_strcast(string), start, length);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    // TODO: improve by comparing multiple bytes at once
+    cx_for_n(i, string.length) {
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
+        }
+    }
+    return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+cxstring cx_strrchr(
+        cxstring string,
+        int chr
+) {
+    chr = 0xFF & chr;
+    size_t i = string.length;
+    while (i > 0) {
+        i--;
+        // TODO: improve by comparing multiple bytes at once
+        if (string.ptr[i] == chr) {
+            return cx_strsubs(string, i);
+        }
+    }
+    return (cxstring) {NULL, 0};
+}
+
+cxmutstr cx_strrchr_m(
+        cxmutstr string,
+        int chr
+) {
+    cxstring result = cx_strrchr(cx_strcast(string), chr);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+#ifndef CX_STRSTR_SBO_SIZE
+#define CX_STRSTR_SBO_SIZE 512
+#endif
+
+cxstring cx_strstr(
+        cxstring haystack,
+        cxstring needle
+) {
+    if (needle.length == 0) {
+        return haystack;
+    }
+
+    // optimize for single-char needles
+    if (needle.length == 1) {
+        return cx_strchr(haystack, *needle.ptr);
+    }
+
+    /*
+     * IMPORTANT:
+     * Our prefix table contains the prefix length PLUS ONE
+     * this is our decision, because we want to use the full range of size_t.
+     * The original algorithm needs a (-1) at one single place,
+     * and we want to avoid that.
+     */
+
+    // local prefix table
+    size_t s_prefix_table[CX_STRSTR_SBO_SIZE];
+
+    // check needle length and use appropriate prefix table
+    // if the pattern exceeds static prefix table, allocate on the heap
+    bool useheap = needle.length >= CX_STRSTR_SBO_SIZE;
+    register size_t *ptable = useheap ? calloc(needle.length + 1,
+                                               sizeof(size_t)) : s_prefix_table;
+
+    // keep counter in registers
+    register size_t i, j;
+
+    // fill prefix table
+    i = 0;
+    j = 0;
+    ptable[i] = j;
+    while (i < needle.length) {
+        while (j >= 1 && needle.ptr[j - 1] != needle.ptr[i]) {
+            j = ptable[j - 1];
+        }
+        i++;
+        j++;
+        ptable[i] = j;
+    }
+
+    // search
+    cxstring result = {NULL, 0};
+    i = 0;
+    j = 1;
+    while (i < haystack.length) {
+        while (j >= 1 && haystack.ptr[i] != needle.ptr[j - 1]) {
+            j = ptable[j - 1];
+        }
+        i++;
+        j++;
+        if (j - 1 == needle.length) {
+            size_t start = i - needle.length;
+            result.ptr = haystack.ptr + start;
+            result.length = haystack.length - start;
+            break;
+        }
+    }
+
+    // if prefix table was allocated on the heap, free it
+    if (ptable != s_prefix_table) {
+        free(ptable);
+    }
+
+    return result;
+}
+
+cxmutstr cx_strstr_m(
+        cxmutstr haystack,
+        cxstring needle
+) {
+    cxstring result = cx_strstr(cx_strcast(haystack), needle);
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+size_t cx_strsplit(
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring *output
+) {
+    // special case: output limit is zero
+    if (limit == 0) return 0;
+
+    // special case: delimiter is empty
+    if (delim.length == 0) {
+        output[0] = string;
+        return 1;
+    }
+
+    // special cases: delimiter is at least as large as the string
+    if (delim.length >= string.length) {
+        // exact match
+        if (cx_strcmp(string, delim) == 0) {
+            output[0] = cx_strn(string.ptr, 0);
+            output[1] = cx_strn(string.ptr + string.length, 0);
+            return 2;
+        } else {
+            // no match possible
+            output[0] = string;
+            return 1;
+        }
+    }
+
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                // copy the current string to the array
+                cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
+                output[n - 1] = item;
+                size_t processed = item.length + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached, copy the _full_ remaining string
+                output[n - 1] = curpos;
+                break;
+            }
+        } else {
+            // no more matches, copy last string
+            output[n - 1] = curpos;
+            break;
+        }
+    }
+
+    return n;
+}
+
+size_t cx_strsplit_a(
+        CxAllocator const *allocator,
+        cxstring string,
+        cxstring delim,
+        size_t limit,
+        cxstring **output
+) {
+    // find out how many splits we're going to make and allocate memory
+    size_t n = 0;
+    cxstring curpos = string;
+    while (1) {
+        ++n;
+        cxstring match = cx_strstr(curpos, delim);
+        if (match.length > 0) {
+            // is the limit reached?
+            if (n < limit) {
+                size_t processed = match.ptr - curpos.ptr + delim.length;
+                curpos.ptr += processed;
+                curpos.length -= processed;
+            } else {
+                // limit reached
+                break;
+            }
+        } else {
+            // no more matches
+            break;
+        }
+    }
+    *output = cxCalloc(allocator, n, sizeof(cxstring));
+    return cx_strsplit(string, delim, n, *output);
+}
+
+size_t cx_strsplit_m(
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr *output
+) {
+    return cx_strsplit(cx_strcast(string),
+                       delim, limit, (cxstring *) output);
+}
+
+size_t cx_strsplit_ma(
+        CxAllocator const *allocator,
+        cxmutstr string,
+        cxstring delim,
+        size_t limit,
+        cxmutstr **output
+) {
+    return cx_strsplit_a(allocator, cx_strcast(string),
+                         delim, limit, (cxstring **) output);
+}
+
+int cx_strcmp(
+        cxstring s1,
+        cxstring s2
+) {
+    if (s1.length == s2.length) {
+        return memcmp(s1.ptr, s2.ptr, s1.length);
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+int cx_strcasecmp(
+        cxstring s1,
+        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
+    } else if (s1.length > s2.length) {
+        return 1;
+    } else {
+        return -1;
+    }
+}
+
+int cx_strcmp_p(
+        void const *s1,
+        void const *s2
+) {
+    cxstring const *left = s1;
+    cxstring const *right = s2;
+    return cx_strcmp(*left, *right);
+}
+
+int cx_strcasecmp_p(
+        void const *s1,
+        void const *s2
+) {
+    cxstring const *left = s1;
+    cxstring const *right = s2;
+    return cx_strcasecmp(*left, *right);
+}
+
+cxmutstr cx_strdup_a(
+        CxAllocator const *allocator,
+        cxstring string
+) {
+    cxmutstr result = {
+            cxMalloc(allocator, string.length + 1),
+            string.length
+    };
+    if (result.ptr == NULL) {
+        result.length = 0;
+        return result;
+    }
+    memcpy(result.ptr, string.ptr, string.length);
+    result.ptr[string.length] = '\0';
+    return result;
+}
+
+cxstring cx_strtrim(cxstring string) {
+    cxstring result = string;
+    // TODO: optimize by comparing multiple bytes at once
+    while (result.length > 0 && isspace(*result.ptr)) {
+        result.ptr++;
+        result.length--;
+    }
+    while (result.length > 0 && isspace(result.ptr[result.length - 1])) {
+        result.length--;
+    }
+    return result;
+}
+
+cxmutstr cx_strtrim_m(cxmutstr string) {
+    cxstring result = cx_strtrim(cx_strcast(string));
+    return (cxmutstr) {(char *) result.ptr, result.length};
+}
+
+bool cx_strprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+    return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
+}
+
+bool cx_strsuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+    return memcmp(string.ptr + string.length - suffix.length,
+                  suffix.ptr, suffix.length) == 0;
+}
+
+bool cx_strcaseprefix(
+        cxstring string,
+        cxstring prefix
+) {
+    if (string.length < prefix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#else
+    return strncasecmp(string.ptr, prefix.ptr, prefix.length) == 0;
+#endif
+}
+
+bool cx_strcasesuffix(
+        cxstring string,
+        cxstring suffix
+) {
+    if (string.length < suffix.length) return false;
+#ifdef _WIN32
+    return _strnicmp(string.ptr+string.length-suffix.length,
+                  suffix.ptr, suffix.length) == 0;
+#else
+    return strncasecmp(string.ptr + string.length - suffix.length,
+                       suffix.ptr, suffix.length) == 0;
+#endif
+}
+
+void cx_strlower(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) tolower(string.ptr[i]);
+    }
+}
+
+void cx_strupper(cxmutstr string) {
+    cx_for_n(i, string.length) {
+        string.ptr[i] = (char) toupper(string.ptr[i]);
+    }
+}
+
+#ifndef CX_STRREPLACE_INDEX_BUFFER_SIZE
+#define CX_STRREPLACE_INDEX_BUFFER_SIZE 64
+#endif
+
+struct cx_strreplace_ibuf {
+    size_t *buf;
+    struct cx_strreplace_ibuf *next;
+    unsigned int len;
+};
+
+static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
+    while (buf) {
+        struct cx_strreplace_ibuf *next = buf->next;
+        free(buf->buf);
+        free(buf);
+        buf = next;
+    }
+}
+
+cxmutstr cx_strreplacen_a(
+        CxAllocator const *allocator,
+        cxstring str,
+        cxstring pattern,
+        cxstring replacement,
+        size_t replmax
+) {
+
+    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
+        return cx_strdup_a(allocator, str);
+
+    // Compute expected buffer length
+    size_t ibufmax = str.length / pattern.length;
+    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
+    if (ibuflen > CX_STRREPLACE_INDEX_BUFFER_SIZE) {
+        ibuflen = CX_STRREPLACE_INDEX_BUFFER_SIZE;
+    }
+
+    // Allocate first index buffer
+    struct cx_strreplace_ibuf *firstbuf, *curbuf;
+    firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
+    if (!firstbuf) return cx_mutstrn(NULL, 0);
+    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
+    if (!firstbuf->buf) {
+        free(firstbuf);
+        return cx_mutstrn(NULL, 0);
+    }
+
+    // Search occurrences
+    cxstring searchstr = str;
+    size_t found = 0;
+    do {
+        cxstring match = cx_strstr(searchstr, pattern);
+        if (match.length > 0) {
+            // Allocate next buffer in chain, if required
+            if (curbuf->len == ibuflen) {
+                struct cx_strreplace_ibuf *nextbuf =
+                        calloc(1, sizeof(struct cx_strreplace_ibuf));
+                if (!nextbuf) {
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
+                }
+                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
+                if (!nextbuf->buf) {
+                    free(nextbuf);
+                    cx_strrepl_free_ibuf(firstbuf);
+                    return cx_mutstrn(NULL, 0);
+                }
+                curbuf->next = nextbuf;
+                curbuf = nextbuf;
+            }
+
+            // Record match index
+            found++;
+            size_t idx = match.ptr - str.ptr;
+            curbuf->buf[curbuf->len++] = idx;
+            searchstr.ptr = match.ptr + pattern.length;
+            searchstr.length = str.length - idx - pattern.length;
+        } else {
+            break;
+        }
+    } while (searchstr.length > 0 && found < replmax);
+
+    // Allocate result string
+    cxmutstr result;
+    {
+        ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
+        size_t rcount = 0;
+        curbuf = firstbuf;
+        do {
+            rcount += curbuf->len;
+            curbuf = curbuf->next;
+        } while (curbuf);
+        result.length = str.length + rcount * adjlen;
+        result.ptr = cxMalloc(allocator, result.length + 1);
+        if (!result.ptr) {
+            cx_strrepl_free_ibuf(firstbuf);
+            return cx_mutstrn(NULL, 0);
+        }
+    }
+
+    // Build result string
+    curbuf = firstbuf;
+    size_t srcidx = 0;
+    char *destptr = result.ptr;
+    do {
+        for (size_t i = 0; i < curbuf->len; i++) {
+            // Copy source part up to next match
+            size_t idx = curbuf->buf[i];
+            size_t srclen = idx - srcidx;
+            if (srclen > 0) {
+                memcpy(destptr, str.ptr + srcidx, srclen);
+                destptr += srclen;
+                srcidx += srclen;
+            }
+
+            // Copy the replacement and skip the source pattern
+            srcidx += pattern.length;
+            memcpy(destptr, replacement.ptr, replacement.length);
+            destptr += replacement.length;
+        }
+        curbuf = curbuf->next;
+    } while (curbuf);
+    memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
+
+    // Result is guaranteed to be zero-terminated
+    result.ptr[result.length] = '\0';
+
+    // Free index buffer
+    cx_strrepl_free_ibuf(firstbuf);
+
+    return result;
+}
+
+CxStrtokCtx cx_strtok(
+        cxstring str,
+        cxstring delim,
+        size_t limit
+) {
+    CxStrtokCtx ctx;
+    ctx.str = str;
+    ctx.delim = delim;
+    ctx.limit = limit;
+    ctx.pos = 0;
+    ctx.next_pos = 0;
+    ctx.delim_pos = 0;
+    ctx.found = 0;
+    ctx.delim_more = NULL;
+    ctx.delim_more_count = 0;
+    return ctx;
+}
+
+CxStrtokCtx cx_strtok_m(
+        cxmutstr str,
+        cxstring delim,
+        size_t limit
+) {
+    return cx_strtok(cx_strcast(str), delim, limit);
+}
+
+bool cx_strtok_next(
+        CxStrtokCtx *ctx,
+        cxstring *token
+) {
+    // abortion criteria
+    if (ctx->found >= ctx->limit || ctx->delim_pos >= ctx->str.length) {
+        return false;
+    }
+
+    // determine the search start
+    cxstring haystack = cx_strsubs(ctx->str, ctx->next_pos);
+
+    // search the next delimiter
+    cxstring delim = cx_strstr(haystack, ctx->delim);
+
+    // if found, make delim capture exactly the delimiter
+    if (delim.length > 0) {
+        delim.length = ctx->delim.length;
+    }
+
+    // if more delimiters are specified, check them now
+    if (ctx->delim_more_count > 0) {
+        cx_for_n(i, ctx->delim_more_count) {
+            cxstring d = cx_strstr(haystack, ctx->delim_more[i]);
+            if (d.length > 0 && (delim.length == 0 || d.ptr < delim.ptr)) {
+                delim.ptr = d.ptr;
+                delim.length = ctx->delim_more[i].length;
+            }
+        }
+    }
+
+    // store the token information and adjust the context
+    ctx->found++;
+    ctx->pos = ctx->next_pos;
+    token->ptr = &ctx->str.ptr[ctx->pos];
+    ctx->delim_pos = delim.length == 0 ?
+                     ctx->str.length : (size_t) (delim.ptr - ctx->str.ptr);
+    token->length = ctx->delim_pos - ctx->pos;
+    ctx->next_pos = ctx->delim_pos + delim.length;
+
+    return true;
+}
+
+bool cx_strtok_next_m(
+        CxStrtokCtx *ctx,
+        cxmutstr *token
+) {
+    return cx_strtok_next(ctx, (cxstring *) token);
+}
+
+void cx_strtok_delim(
+        CxStrtokCtx *ctx,
+        cxstring const *delim,
+        size_t count
+) {
+    ctx->delim_more = delim;
+    ctx->delim_more_count = count;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/szmul.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2023 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.
+ */
+
+int cx_szmul_impl(
+        size_t a,
+        size_t b,
+        size_t *result
+) {
+    if (a == 0 || b == 0) {
+        *result = 0;
+        return 0;
+    }
+    size_t r = a * b;
+    if (r / b == a) {
+        *result = r;
+        return 0;
+    } else {
+        *result = 0;
+        return 1;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/tree.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,52 @@
+/*
+ * 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/tree.h"
+#include "cx/linked_list.h"
+
+#define CX_TR_PTR(cur, off) *((void**)(((char*)(cur))+(off)))
+
+void cx_tree_add_sibling(void *node, ptrdiff_t loc_prev, ptrdiff_t loc_next, ptrdiff_t loc_parent, void *new_node) {
+    cx_linked_list_add(&node, NULL, loc_prev, loc_next, new_node);
+
+    // optional parent link
+    if (loc_parent >= 0) {
+        CX_TR_PTR(new_node, loc_parent) = CX_TR_PTR(node, loc_parent);
+    }
+}
+
+void cx_tree_add_child(void **children_begin, void **children_end,
+                       ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node,
+                       ptrdiff_t loc_parent, void *parent) {
+    cx_linked_list_add(children_begin, children_end, loc_prev, loc_next, new_node);
+
+    // optional parent link
+    if (loc_parent >= 0) {
+        CX_TR_PTR(new_node, loc_parent) = parent;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ucx/utils.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,94 @@
+/*
+ * 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"
+
+#define CX_STREAM_BCOPY_BUF_SIZE 8192
+#define CX_STREAM_COPY_BUF_SIZE 1024
+
+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/Makefile	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/Makefile	Mon May 22 16:17:26 2023 +0200
@@ -33,7 +33,7 @@
 
 include common/objs.mk
 
-UI_LIB = ../build/lib/libuitk.$(LIB_EXT)
+UI_LIB = ../build/lib/libuitk$(LIB_EXT)
 
 include $(TOOLKIT)/objs.mk
 OBJ = $(TOOLKITOBJS) $(COMMONOBJS)
@@ -43,5 +43,5 @@
 include $(TOOLKIT)/Makefile
 
 $(COMMON_OBJPRE)%.o: common/%.c
-	$(CC) -o $@ -c $(CFLAGS) $(TK_CFLAGS) $<
+	$(CC) -o $@ -c -I../ucx/ $(CFLAGS) $(TK_CFLAGS) $<
 
--- a/ui/common/context.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/context.c	Mon May 22 16:17:26 2023 +0200
@@ -32,6 +32,10 @@
 #include <inttypes.h>
 #include <stdarg.h>
 
+#include <cx/array_list.h>
+#include <cx/compare.h>
+#include <cx/basic_mempool.h>
+
 #include "context.h"
 #include "../ui/window.h"
 #include "document.h"
@@ -40,20 +44,24 @@
 static UiContext* global_context;
 
 void uic_init_global_context(void) {
-    UcxMempool *mp = ucx_mempool_new(32);
-    global_context = uic_context(NULL, mp);
+    CxMempool *mp = cxBasicMempoolCreate(32);
+    global_context = uic_context(NULL, mp->allocator);
 }
 
 UiContext* ui_global_context(void) {
     return global_context;
 }
 
-UiContext* uic_context(UiObject *toplevel, UcxMempool *mp) {
-    UiContext *ctx = ucx_mempool_malloc(mp, sizeof(UiContext));
+UiContext* uic_context(UiObject *toplevel, const CxAllocator *a) {
+    UiContext *ctx = cxMalloc(a, sizeof(UiContext));
     memset(ctx, 0, sizeof(UiContext));
-    ctx->mempool = mp;
+    ctx->allocator = a;
     ctx->obj = toplevel;
-    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
+    
+    ctx->documents = cxLinkedListCreate(a, cx_cmp_intptr, CX_STORE_POINTERS);
+    ctx->group_widgets = cxLinkedListCreate(a, NULL, sizeof(UiGroupWidget));
+    ctx->groups = cxArrayListCreate(a, cx_cmp_int, sizeof(int), 32);
     
     ctx->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document2;
@@ -73,8 +81,8 @@
 }
 
 void uic_context_attach_document(UiContext *ctx, void *document) {
-    ctx->documents = ucx_list_append_a(ctx->mempool->allocator, ctx->documents, document);
-    ctx->document = ctx->documents->data;
+    cxListAdd(ctx->documents, document);
+    ctx->document = document;
     
     UiContext *doc_ctx = ui_document_context(document);
     
@@ -82,25 +90,17 @@
     // as any document variable
     UiContext *var_ctx = ctx;
     while(var_ctx) {
-        if(var_ctx->vars_unbound && var_ctx->vars_unbound->count > 0) {
-            UcxMapIterator i = ucx_map_iterator(var_ctx->vars_unbound);
-            UiVar *var;
-            // rmkeys holds all keys, that shall be removed from vars_unbound
-            UcxKey *rmkeys = calloc(var_ctx->vars_unbound->count, sizeof(UcxKey));
-            size_t numkeys = 0;
-            UCX_MAP_FOREACH(key, var, i) {
-                UiVar *docvar = ucx_map_get(doc_ctx->vars, key);
+        if(var_ctx->vars_unbound && var_ctx->vars_unbound->size > 0) {
+            CxIterator i = cxMapIterator(var_ctx->vars_unbound);
+            cx_foreach(CxMapEntry*, entry, i) {
+                UiVar *var = entry->value;
+                UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key);
                 if(docvar) {
                     // bind var to document var
                     uic_copy_binding(var, docvar, TRUE);
-                    rmkeys[numkeys++] = key; // save the key for removal
+                    cxIteratorFlagRemoval(i);
                 }
             }
-            // now that we may have bound some vars to the document,
-            // we can remove them from the unbound map
-            for(size_t k=0;k<numkeys;k++) {
-                ucx_map_remove(var_ctx->vars_unbound, rmkeys[k]);
-            }
         }
         
         var_ctx = ctx->parent;
@@ -108,56 +108,63 @@
 }
 
 static void uic_context_unbind_vars(UiContext *ctx) {
-    UcxMapIterator i = ucx_map_iterator(ctx->vars);
-    UiVar *var;
-    UCX_MAP_FOREACH(key, var, i) {
+    CxIterator i = cxMapIterator(ctx->vars);
+    cx_foreach(CxMapEntry*, entry, i) {
+        UiVar *var = entry->value;
         if(var->from && var->from_ctx) {
             uic_save_var2(var);
             uic_copy_binding(var, var->from, FALSE);
-            ucx_map_put(var->from_ctx->vars_unbound, key, var->from);
+            cxMapPut(var->from_ctx->vars_unbound, *entry->key, var->from);
             var->from_ctx = ctx;
         }
     }
     
-    UCX_FOREACH(elm, ctx->documents) {
-        UiContext *subctx = ui_document_context(elm->data);
-        uic_context_unbind_vars(subctx);
+    if(ctx->documents) {
+        i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
+            uic_context_unbind_vars(subctx);
+        }
     }
 }
 
 void uic_context_detach_document2(UiContext *ctx, void *document) {
     // find the document in the documents list
-    UcxList *doc = NULL;
-    UCX_FOREACH(elm, ctx->documents) {
-        if(elm->data == document) {
-            doc = elm;
-            break;
-        }
-    }
-    if(!doc) {
-        return; // document is not a subdocument of this context
+    ssize_t docIndex = cxListFind(ctx->documents, document);
+    if(docIndex < 0) {
+        return;
     }
     
-    ctx->documents = ucx_list_remove_a(ctx->mempool->allocator, ctx->documents, doc);
-    ctx->document = ctx->documents ? ctx->documents->data : NULL;
+    cxListRemove(ctx->documents, docIndex);
+    ctx->document = cxListAt(ctx->documents, 0);
     
     UiContext *docctx = ui_document_context(document);
     uic_context_unbind_vars(docctx); // unbind all doc/subdoc vars from the parent
 }
 
 void uic_context_detach_all(UiContext *ctx) {
-    UcxList *ls = ucx_list_clone(ctx->documents, NULL, NULL);
-    UCX_FOREACH(elm, ls) {
-        ctx->detach_document2(ctx, elm->data);
+    // copy list
+    CxList *ls = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    CxIterator i = cxListIterator(ctx->documents);
+    cx_foreach(void *, doc, i) {
+        cxListAdd(ls, doc);
     }
-    ucx_list_free(ls);
+    
+    // detach documents
+    i = cxListIterator(ls);
+    cx_foreach(void *, doc, i) {
+        ctx->detach_document2(ctx, doc);
+    }
+    
+    cxListDestroy(ls);
 }
 
-static UiVar* ctx_getvar(UiContext *ctx, UcxKey key) {
-    UiVar *var = ucx_map_get(ctx->vars, key);
+static UiVar* ctx_getvar(UiContext *ctx, CxHashKey key) {
+    UiVar *var = cxMapGet(ctx->vars, key);
     if(!var) {
-        UCX_FOREACH(elm, ctx->documents) {
-            UiContext *subctx = ui_document_context(elm->data);
+        CxIterator i = cxListIterator(ctx->documents);
+        cx_foreach(void *, doc, i) {
+            UiContext *subctx = ui_document_context(doc);
             var = ctx_getvar(subctx, key);
             if(var) {
                 break;
@@ -168,7 +175,7 @@
 }
 
 UiVar* uic_get_var(UiContext *ctx, const char *name) {
-    UcxKey key = ucx_key(name, strlen(name));
+    CxHashKey key = cx_hash_key(name, strlen(name));
     return ctx_getvar(ctx, key);
 }
 
@@ -189,9 +196,9 @@
     var->from_ctx = ctx;
 
     if(!ctx->vars_unbound) {
-        ctx->vars_unbound = ucx_map_new_a(ctx->mempool->allocator, 16);
+        ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16);
     }
-    ucx_map_cstr_put(ctx->vars_unbound, name, var);
+    cxMapPut(ctx->vars_unbound, name, var);
     
     return var;
 }
@@ -358,9 +365,9 @@
     var->value = value;
     var->from = NULL;
     var->from_ctx = ctx;
-    size_t oldcount = ctx->vars->count;
-    ucx_map_cstr_put(ctx->vars, name, var);
-    if(ctx->vars->count != oldcount + 1) {
+    size_t oldcount = ctx->vars->size;
+    cxMapPut(ctx->vars, name, var);
+    if(ctx->vars->size != oldcount + 1) {
         fprintf(stderr, "UiError: var '%s' already exists\n", name);
     }
     
@@ -375,8 +382,7 @@
 }
 
 void uic_remove_bound_var(UiContext *ctx, UiVar *var) {
-    // TODO: implement
-    printf("TODO: implement uic_remove_bound_var\n");
+    // TODO
 }
 
 
@@ -397,8 +403,8 @@
 
 
 void ui_set_group(UiContext *ctx, int group) {
-    if(ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL) == -1) {
-        ctx->groups = ucx_list_append_a(ctx->mempool->allocator, ctx->groups, (void*)(intptr_t)group);
+    if(cxListFind(ctx->groups, &group) == -1) {
+        cxListAdd(ctx->groups, &group);
     }
     
     // enable/disable group widgets
@@ -406,10 +412,9 @@
 }
 
 void ui_unset_group(UiContext *ctx, int group) {
-    int i = ucx_list_find(ctx->groups, (void*)(intptr_t)group, NULL, NULL);
+    int i = cxListFind(ctx->groups, &group);
     if(i != -1) {
-        UcxList *elm = ucx_list_get(ctx->groups, i);
-        ctx->groups = ucx_list_remove_a(ctx->mempool->allocator, ctx->groups, elm);
+        cxListRemove(ctx->groups, i);
     }
     
     // enable/disable group widgets
@@ -417,28 +422,16 @@
 }
 
 int* ui_active_groups(UiContext *ctx, int *ngroups) {
-    if(!ctx->groups) {
-        return NULL;
-    }
-    
-    int nelm = ucx_list_size(ctx->groups);
-    int *groups = calloc(sizeof(int), nelm);
-    
-    int i = 0;
-    UCX_FOREACH(elm, ctx->groups) {
-        groups[i++] = (intptr_t)elm->data;
-    }
-    
-    *ngroups = nelm;
-    return groups;
+    *ngroups = ctx->groups->size;
+    return cxListAt(ctx->groups, 0);
 }
 
 void uic_check_group_widgets(UiContext *ctx) {
     int ngroups = 0;
     int *groups = ui_active_groups(ctx, &ngroups);
     
-    UCX_FOREACH(elm, ctx->group_widgets) {
-        UiGroupWidget *gw = elm->data;
+    CxIterator i = cxListIterator(ctx->group_widgets);
+    cx_foreach(UiGroupWidget *, gw, i) {
         char *check = calloc(1, gw->numgroups);
         
         for(int i=0;i<ngroups;i++) {
@@ -456,64 +449,60 @@
                 break;
             }
         }
+        free(check);
         gw->enable(gw->widget, enable);
     }
-    
-    if(groups) {
-        free(groups);
-    }
 }
 
 void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) {
     // get groups
-    UcxList *groups = NULL;
+    CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
     va_list ap;
     va_start(ap, enable);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        groups = ucx_list_append(groups, (void*)(intptr_t)group);
+        cxListAdd(groups, &group);
     }
     va_end(ap);
     
     uic_add_group_widget(ctx, widget, enable, groups);
     
-    ucx_list_free(groups);
+    cxListDestroy(groups);
 }
 
-void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups) {
-    UcxMempool *mp = ctx->mempool;
-    UiGroupWidget *gw = ucx_mempool_malloc(mp, sizeof(UiGroupWidget));
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups) {
+    const CxAllocator *a = ctx->allocator;
+    UiGroupWidget gw;
     
-    gw->widget = widget;
-    gw->enable = enable;
-    gw->numgroups = ucx_list_size(groups);
-    gw->groups = ucx_mempool_calloc(mp, gw->numgroups, sizeof(int));
-    int i = 0;
-    UCX_FOREACH(elm, groups) {
-        gw->groups[i++] = (intptr_t)elm->data;
+    gw.widget = widget;
+    gw.enable = enable;
+    gw.numgroups = groups->size;
+    gw.groups = cxCalloc(a, gw.numgroups, sizeof(int));
+    
+    // copy groups
+    int *intgroups = cxListAt(groups, 0);
+    if(intgroups) {
+        memcpy(gw.groups, intgroups, gw.numgroups * sizeof(int));
     }
     
-    ctx->group_widgets = ucx_list_append_a(
-            mp->allocator,
-            ctx->group_widgets,
-            gw);
+    cxListAdd(ctx->group_widgets, &gw);
 }
 
 void* ui_malloc(UiContext *ctx, size_t size) {
-    return ctx ? ucx_mempool_malloc(ctx->mempool, size) : NULL;
+    return ctx ? cxMalloc(ctx->allocator, size) : NULL;
 }
 
 void* ui_calloc(UiContext *ctx, size_t nelem, size_t elsize) {
-    return ctx ? ucx_mempool_calloc(ctx->mempool, nelem, elsize) : NULL;
+    return ctx ? cxCalloc(ctx->allocator, nelem, elsize) : NULL;
 }
 
 void ui_free(UiContext *ctx, void *ptr) {
     if(ctx) {
-        ucx_mempool_free(ctx->mempool, ptr);
+        cxFree(ctx->allocator, ptr);
     }
 }
 
 void* ui_realloc(UiContext *ctx, void *ptr, size_t size) {
-    return ctx ? ucx_mempool_realloc(ctx->mempool, ptr, size) : NULL;
+    return ctx ? cxRealloc(ctx->allocator, ptr, size) : NULL;
 }
 
--- a/ui/common/context.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/context.h	Mon May 22 16:17:26 2023 +0200
@@ -30,9 +30,11 @@
 #define	UIC_CONTEXT_H
 
 #include "../ui/toolkit.h"
-#include <ucx/map.h>
-#include <ucx/mempool.h>
-#include <ucx/list.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+#include <cx/mempool.h>
+#include <cx/list.h>
+#include <cx/linked_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
@@ -58,16 +60,16 @@
 struct UiContext {
     UiContext     *parent;
     UiObject      *obj;
-    UcxMempool    *mempool;
+    const CxAllocator *allocator;
     
     void          *document;
-    UcxList       *documents;
+    CxList        *documents;
     
-    UcxMap        *vars; // manually created context vars
-    UcxMap        *vars_unbound; // unbound vars created by widgets
+    CxMap         *vars; // manually created context vars
+    CxMap         *vars_unbound; // unbound vars created by widgets
     
-    UcxList       *groups; // int list
-    UcxList       *group_widgets; // UiGroupWidget* list
+    CxList        *groups; // int list
+    CxList        *group_widgets; // UiGroupWidget list
     
     void (*attach_document)(UiContext *ctx, void *document);
     void (*detach_document2)(UiContext *ctx, void *document); 
@@ -100,7 +102,7 @@
 
 void uic_init_global_context(void);
 
-UiContext* uic_context(UiObject *toplevel, UcxMempool *mp);
+UiContext* uic_context(UiObject *toplevel, const CxAllocator *a);
 UiContext* uic_root_context(UiContext *ctx);
 void uic_context_set_document(UiContext *ctx, void *document); // deprecated
 void uic_context_detach_document(UiContext *ctx); // deprecated
@@ -122,7 +124,7 @@
 void uic_remove_bound_var(UiContext *ctx, UiVar *var);
 
 void uic_check_group_widgets(UiContext *ctx);
-void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, UcxList *groups);
+void uic_add_group_widget(UiContext *ctx, void *widget, ui_enablefunc enable, CxList *groups);
 
 
 #ifdef	__cplusplus
--- a/ui/common/document.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/document.c	Mon May 22 16:17:26 2023 +0200
@@ -32,10 +32,13 @@
 
 #include "document.h"
 
-static UcxMap *documents;
+#include <cx/hash_map.h>
+#include <cx/basic_mempool.h>
+
+static CxMap *documents;
 
 void uic_docmgr_init() {
-    documents = ucx_map_new(32);
+    documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
 }
 
 void ui_set_document(UiObject *obj, void *document) {
@@ -77,15 +80,16 @@
 }
 
 void* ui_document_new(size_t size) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiContext *ctx = ucx_mempool_calloc(mp, 1, sizeof(UiContext));
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    const CxAllocator *a = mp->allocator;
+    UiContext *ctx = cxCalloc(a, 1, sizeof(UiContext));
     ctx->attach_document = uic_context_attach_document;
     ctx->detach_document2 = uic_context_detach_document2;
-    ctx->mempool = mp;
-    ctx->vars = ucx_map_new_a(mp->allocator, 16);
+    ctx->allocator = a;
+    ctx->vars = cxHashMapCreate(a, CX_STORE_POINTERS, 16);
     
-    void *document = ucx_mempool_calloc(mp, 1, size);
-    ucx_map_put(documents, ucx_key(&document, sizeof(void*)), ctx);
+    void *document = cxCalloc(a, 1, size);
+    cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx);
     return document;
 }
 
@@ -95,7 +99,7 @@
 
 UiContext* ui_document_context(void *doc) {
     if(doc) {
-        return ucx_map_get(documents, ucx_key(&doc, sizeof(void*)));
+        return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*)));
     } else {
         return NULL;
     }
--- a/ui/common/object.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/object.c	Mon May 22 16:17:26 2023 +0200
@@ -53,7 +53,7 @@
 UiObject* uic_object_new(UiObject *toplevel, UIWIDGET widget) {
     UiContext *ctx = toplevel->ctx;
     
-    UiObject *newobj = ucx_mempool_calloc(ctx->mempool, 1, sizeof(UiObject));
+    UiObject *newobj = cxCalloc(ctx->allocator, 1, sizeof(UiObject));
     newobj->ctx = ctx;
     newobj->widget = widget;
     
--- a/ui/common/objs.mk	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/objs.mk	Mon May 22 16:17:26 2023 +0200
@@ -34,6 +34,7 @@
 COMMON_OBJ += object.o
 COMMON_OBJ += types.o
 COMMON_OBJ += properties.o
+COMMON_OBJ += ucx_properties.o
 
 TOOLKITOBJS += $(COMMON_OBJ:%=$(COMMON_OBJPRE)%)
 TOOLKITSOURCE += $(COMMON_OBJ:%.o=common/%.c)
--- a/ui/common/properties.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/properties.c	Mon May 22 16:17:26 2023 +0200
@@ -33,12 +33,13 @@
 #include <errno.h>
 
 #include "properties.h"
-#include <ucx/string.h>
-#include <ucx/buffer.h>
-#include <ucx/properties.h>
+#include <cx/string.h>
+#include <cx/buffer.h>
 
-static UiProperties *application_properties;
-static UiProperties *language;
+#include "ucx_properties.h"
+
+static CxMap *application_properties;
+static CxMap *language;
 
 #ifndef UI_COCOA
 
@@ -61,19 +62,20 @@
         return NULL;
     }
     
-    UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     
     // add base dir
     char *homeenv = getenv("HOME");
     if(homeenv == NULL) {
-        ucx_buffer_free(buf);
+        cxBufferDestroy(&buf);
         return NULL;
     }
-    sstr_t home = sstr(homeenv);
+    cxstring home = cx_str(homeenv);
     
-    ucx_buffer_write(home.ptr, 1, home.length, buf);
+    cxBufferWrite(home.ptr, 1, home.length, &buf);
     if(home.ptr[home.length-1] != '/') {
-        ucx_buffer_putc(buf, '/');
+        cxBufferPut(&buf, '/');
     }
     
 #ifdef UI_COCOA
@@ -81,19 +83,19 @@
     ucx_buffer_puts(buf, "Library/Application Support/");
 #else
     // app dir is $HOME/.$APPNAME/
-    ucx_buffer_putc(buf, '.');
+    cxBufferPut(&buf, '.');
 #endif
-    ucx_buffer_puts(buf, appname);
-    ucx_buffer_putc(buf, '/');
+    cxBufferPutString(&buf, appname);
+    cxBufferPut(&buf, '/');
     
     // add file name
     if(name) {
-        ucx_buffer_puts(buf, name);
+        cxBufferPutString(&buf, name);
     }
     
-    char *path = buf->space;
-    free(buf);
-    return path;  
+    cxBufferPut(&buf, 0);
+    
+    return buf.space;
 }
 
 static int ui_mkdir(char *path) {
@@ -105,7 +107,7 @@
 } 
 
 void uic_load_app_properties() {
-    application_properties = ucx_map_new(128);
+    application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128);
     
     if(!ui_appname()) {
         // applications without name cannot load app properties
@@ -167,17 +169,17 @@
 
 
 char* ui_get_property(char *name) {
-    return ucx_map_cstr_get(application_properties, name);
+    return cxMapGet(application_properties, name);
 }
 
 void ui_set_property(char *name, char *value) {
-    ucx_map_cstr_put(application_properties, name, value);
+    cxMapPut(application_properties, name, value);
 }
 
 void ui_set_default_property(char *name, char *value) {
-    char *v = ucx_map_cstr_get(application_properties, name);
+    char *v = cxMapGet(application_properties, name);
     if(!v) {
-        ucx_map_cstr_put(application_properties, name, value);
+        cxMapPut(application_properties, name, value);
     }
 }
 
@@ -186,16 +188,16 @@
 static char* uic_concat_path(const char *base, const char *p, const char *ext) {
     size_t baselen = strlen(base);
     
-    UcxBuffer *buf = ucx_buffer_new(NULL, 32, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer *buf = cxBufferCreate(NULL, 32, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     if(baselen > 0) {
-        ucx_buffer_write(base, 1, baselen, buf);
+        cxBufferWrite(base, 1, baselen, buf);
         if(base[baselen - 1] != '/') {
-            ucx_buffer_putc(buf, '/');
+            cxBufferPut(buf, '/');
         }
     }
-    ucx_buffer_write(p, 1, strlen(p), buf);
+    cxBufferWrite(p, 1, strlen(p), buf);
     if(ext) {
-        ucx_buffer_write(ext, 1, strlen(ext), buf);
+        cxBufferWrite(ext, 1, strlen(ext), buf);
     }
     
     char *str = buf->space;
@@ -263,7 +265,7 @@
 #endif
 
 int uic_load_language_file(const char *path) {
-    UcxMap *lang = ucx_map_new(256);
+    CxMap *lang = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 256);
     
     FILE *file = fopen(path, "r");
     if(!file) {
@@ -276,7 +278,7 @@
     
     fclose(file);
     
-    ucx_map_rehash(lang);
+    cxMapRehash(lang);
     
     language = lang;
     
@@ -292,6 +294,6 @@
     if(!language) {
         return NULL;
     }
-    return ucx_map_cstr_get(language, name);
+    return cxMapGet(language, name);
 }
 
--- a/ui/common/properties.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/properties.h	Mon May 22 16:17:26 2023 +0200
@@ -30,8 +30,8 @@
 #define	UIC_PROPERTIES_H
 
 #include "../ui/properties.h"
-#include <ucx/map.h>
-#include <ucx/list.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
 
 #ifdef	__cplusplus
 extern "C" {
--- a/ui/common/types.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/common/types.c	Mon May 22 16:17:26 2023 +0200
@@ -31,7 +31,8 @@
 #include <string.h>
 #include <stdarg.h>
 
-#include <ucx/list.h>
+#include <cx/list.h>
+#include <cx/array_list.h>
 #include "../ui/tree.h"
 #include "types.h"
 #include "context.h"
@@ -99,7 +100,7 @@
     list->count = ui_list_count;
     list->observers = NULL;
     
-    list->data = NULL;
+    list->data = cxArrayListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS, 32);
     list->iter = NULL;
     
     list->update = NULL;
@@ -113,54 +114,43 @@
 }
 
 void ui_list_free(UiList *list) {
-    ucx_list_free(list->data);
+    cxListDestroy(list->data);
     free(list);
 }
 
 void* ui_list_first(UiList *list) {
-    UcxList *elm = list->data;
-    list->iter = elm;
-    return elm ? elm->data : NULL;
+    list->iter = (void*)(intptr_t)0;
+    return cxListAt(list->data, 0);
 }
 
 void* ui_list_next(UiList *list) {
-    UcxList *elm = list->iter;
+    intptr_t iter = (intptr_t)list->iter;
+    iter++;
+    void *elm = cxListAt(list->data, iter);
     if(elm) {
-        elm = elm->next;
-        if(elm) {
-            list->iter = elm;
-            return elm->data;
-        }
+        list->iter = (void*)iter;
     }
-    return NULL;
+    return elm;
 }
 
 void* ui_list_get(UiList *list, int i) {
-    UcxList *elm = ucx_list_get(list->data, i);
-    if(elm) {
-        list->iter = elm;
-        return elm->data;
-    } else {
-        return NULL;
-    }
+    return cxListAt(list->data, i);
 }
 
 int ui_list_count(UiList *list) {
-    UcxList *elm = list->data;
-    return (int)ucx_list_size(elm);
+    return ((CxList*)list->data)->size;
 }
 
 void ui_list_append(UiList *list, void *data) {
-    list->data = ucx_list_append(list->data, data);
+    cxListAdd(list->data, data);
 }
 
 void ui_list_prepend(UiList *list, void *data) {
-    list->data = ucx_list_prepend(list->data, data);
+    cxListInsert(list->data, 0, data);
 }
 
 void ui_list_clear(UiList *list) {
-    ucx_list_free(list->data);
-    list->data = NULL;
+    cxListClear(list->data);
 }
 
 void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
@@ -183,42 +173,41 @@
     va_list ap;
     va_start(ap, ctx);
     
-    UcxList *cols = NULL;
+    CxList *cols = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(UiColumn), 32);
     int type;
     while((type = va_arg(ap, int)) != -1) {
         char *name = va_arg(ap, char*);
         
-        UiColumn *column = malloc(sizeof(UiColumn));
-        column->type = type;
-        column->name = name;
+        UiColumn column;
+        column.type = type;
+        column.name = name;
         
-        cols = ucx_list_append(cols, column);
+        cxListAdd(cols, &column);
     }
     
     va_end(ap);
     
-    size_t len = ucx_list_size(cols);
+    size_t len = cols->size;
     info->columns = len;
     info->types = ui_calloc(ctx, len, sizeof(UiModelType));
     info->titles = ui_calloc(ctx, len, sizeof(char*));
     
     int i = 0;
-    UCX_FOREACH(elm, cols) {
-        UiColumn *c = elm->data;
+    CxIterator iter = cxListIterator(cols);
+    cx_foreach(UiColumn*, c, iter) {
         info->types[i] = c->type;
         info->titles[i] = c->name;
-        free(c);
         i++;
     }
-    ucx_list_free(cols);
+    cxListDestroy(cols);
     
     return info;
 }
 
 void ui_model_free(UiContext *ctx, UiModel *mi) {
-    ucx_mempool_free(ctx->mempool, mi->types);
-    ucx_mempool_free(ctx->mempool, mi->titles);
-    ucx_mempool_free(ctx->mempool, mi);
+    cxFree(ctx->allocator, mi->types);
+    cxFree(ctx->allocator, mi->titles);
+    cxFree(ctx->allocator, mi);
 }
 
 // types
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.c	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,263 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ucx_properties.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+UcxProperties *ucx_properties_new() {
+    UcxProperties *parser = (UcxProperties*)malloc(
+            sizeof(UcxProperties));
+    if(!parser) {
+        return NULL;
+    }
+    
+    parser->buffer = NULL;
+    parser->buflen = 0;
+    parser->pos = 0;
+    parser->tmp = NULL;
+    parser->tmplen = 0;
+    parser->tmpcap = 0;
+    parser->error = 0;
+    parser->delimiter = '=';
+    parser->comment1 = '#';
+    parser->comment2 = 0;
+    parser->comment3 = 0;   
+    
+    return parser;
+}
+
+void ucx_properties_free(UcxProperties *parser) {
+    if(parser->tmp) {
+        free(parser->tmp);
+    }
+    free(parser);
+}
+
+void ucx_properties_fill(UcxProperties *parser, char *buf, size_t len) {
+    parser->buffer = buf;
+    parser->buflen = len;
+    parser->pos = 0;
+}
+
+static void parser_tmp_append(UcxProperties *parser, char *buf, size_t len) {
+    if(parser->tmpcap - parser->tmplen < len) {
+        size_t newcap = parser->tmpcap + len + 64;
+        parser->tmp = (char*)realloc(parser->tmp, newcap);
+        parser->tmpcap = newcap;
+    }
+    memcpy(parser->tmp + parser->tmplen, buf, len);
+    parser->tmplen += len;
+}
+
+int ucx_properties_next(UcxProperties *parser, cxstring *name, cxstring *value)  {   
+    if(parser->tmplen > 0) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->pos;
+        cxstring str = cx_strn(buf, len);
+        cxstring nl = cx_strchr(str, '\n');
+        if(nl.ptr) {
+            size_t newlen = (size_t)(nl.ptr - buf) + 1;
+            parser_tmp_append(parser, buf, newlen);
+            // the tmp buffer contains exactly one line now
+            
+            char *orig_buf = parser->buffer;
+            size_t orig_len = parser->buflen;
+            
+            parser->buffer = parser->tmp;
+            parser->buflen = parser->tmplen;
+            parser->pos = 0;    
+            parser->tmp = NULL;
+            parser->tmpcap = 0;
+            parser->tmplen = 0;
+            // run ucx_properties_next with the tmp buffer as main buffer
+            int ret = ucx_properties_next(parser, name, value);
+            
+            // restore original buffer
+            parser->tmp = parser->buffer;
+            parser->buffer = orig_buf;
+            parser->buflen = orig_len;
+            parser->pos = newlen;
+            
+            /*
+             * if ret == 0 the tmp buffer contained just space or a comment
+             * we parse again with the original buffer to get a name/value
+             * or a new tmp buffer
+             */
+            return ret ? ret : ucx_properties_next(parser, name, value);
+        } else {
+            parser_tmp_append(parser, buf, len);
+            return 0;
+        }
+    } else if(parser->tmp) {
+        free(parser->tmp);
+        parser->tmp = NULL;
+    }
+    
+    char comment1 = parser->comment1;
+    char comment2 = parser->comment2;
+    char comment3 = parser->comment3;
+    char delimiter = parser->delimiter;
+    
+    // get one line and parse it
+    while(parser->pos < parser->buflen) {
+        char *buf = parser->buffer + parser->pos;
+        size_t len = parser->buflen - parser->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;
+        int has_comment = 0;
+
+        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 = 1;
+                }
+            } 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
+            // store remaining bytes in temporary buffer for next round
+            parser->tmpcap = len + 128;
+            parser->tmp = (char*)malloc(parser->tmpcap);
+            parser->tmplen = len;
+            memcpy(parser->tmp, buf, len);
+            return 0;
+        }
+        
+        cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i);
+        // check line
+        if(delimiter_index == 0) {
+            line = cx_strtrim(line);
+            if(line.length != 0) {
+                parser->error = 1;
+            }
+        } else {
+            cxstring n = cx_strn(buf, delimiter_index);
+            cxstring v = cx_strn(
+                    buf + delimiter_index + 1,
+                    line.length - delimiter_index - 1); 
+            n = cx_strtrim(n);
+            v = cx_strtrim(v);
+            if(n.length != 0 || v.length != 0) {
+                *name = n;
+                *value = v;
+                parser->pos += i + 1;
+                return 1;
+            } else {
+                parser->error = 1;
+            }
+        }
+        
+        parser->pos += i + 1;
+    }
+    
+    return 0;
+}
+
+int ucx_properties2map(UcxProperties *parser, CxMap *map) {
+    cxstring name;
+    cxstring value;
+    while(ucx_properties_next(parser, &name, &value)) {
+        cxmutstr mutvalue = cx_strdup_a(map->allocator, value);
+        if(!mutvalue.ptr) {
+            return 1;
+        }
+        if(cxMapPut(map, cx_hash_key_cxstr(name), mutvalue.ptr)) {
+            cxFree(map->allocator, mutvalue.ptr);
+            return 1;
+        }
+    }
+    if (parser->error) {
+        return parser->error;
+    } else {
+        return 0;
+    }
+}
+
+// buffer size is documented - change doc, when you change bufsize!
+#define UCX_PROPLOAD_BUFSIZE  1024
+int ucx_properties_load(CxMap *map, FILE *file) {
+    UcxProperties *parser = ucx_properties_new();
+    if(!(parser && map && file)) {
+        return 1;
+    }
+    
+    int error = 0;
+    size_t r;
+    char buf[UCX_PROPLOAD_BUFSIZE];
+    while((r = fread(buf, 1, UCX_PROPLOAD_BUFSIZE, file)) != 0) {
+        ucx_properties_fill(parser, buf, r);
+        error = ucx_properties2map(parser, map);
+        if (error) {
+            break;
+        }
+    }
+    ucx_properties_free(parser);
+    return error;
+}
+
+int ucx_properties_store(CxMap *map, FILE *file) {
+    CxIterator iter = cxMapIterator(map);
+    cxstring value;
+    size_t written;
+
+    cx_foreach(CxMapEntry *, v, iter) {
+        value = cx_str(v->value);
+
+        written = 0;
+        written += fwrite(v->key->data, 1, v->key->len, file);
+        written += fwrite(" = ", 1, 3, file);
+        written += fwrite(value.ptr, 1, value.length, file);
+        written += fwrite("\n", 1, 1, file);
+
+        if (written != v->key->len + value.length + 4) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/common/ucx_properties.h	Mon May 22 16:17:26 2023 +0200
@@ -0,0 +1,223 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/**
+ * @file properties.h
+ * 
+ * Load / store utilities for properties files.
+ * 
+ * @author Mike Becker
+ * @author Olaf Wintermann
+ */
+
+#ifndef UCX_PROPERTIES_H
+#define	UCX_PROPERTIES_H
+
+#include <cx/hash_map.h>
+#include <cx/string.h>
+
+#include <stdio.h>
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/**
+ * UcxProperties object for parsing properties data.
+ * Most of the fields are for internal use only. You may configure the
+ * properties parser, e.g. by changing the used delimiter or specifying 
+ * up to three different characters that shall introduce comments.
+ */
+typedef struct {
+    /**
+     * Input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    char   *buffer;
+    
+    /**
+     * Length of the input buffer (don't set manually).
+     * Automatically set by calls to ucx_properties_fill().
+     */
+    size_t buflen;
+    
+    /**
+     * Current buffer position (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t pos;
+    
+    /**
+     * Internal temporary buffer (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    char   *tmp;
+    
+    /**
+     * Internal temporary buffer length (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmplen;
+    
+    /**
+     * Internal temporary buffer capacity (don't set manually).
+     * Used by ucx_properties_next().
+     */
+    size_t tmpcap;
+    
+    /**
+     * Parser error code.
+     * This is always 0 on success and a nonzero value on syntax errors.
+     * The value is set by ucx_properties_next().
+     */
+    int    error;
+    
+    /**
+     * The delimiter that shall be used.
+     * This is '=' by default.
+     */
+    char   delimiter;
+    
+    /**
+     * The first comment character.
+     * This is '#' by default.
+     */
+    char   comment1;
+    
+    /**
+     * The second comment character.
+     * This is not set by default.
+     */
+    char   comment2;
+    
+    /**
+     * The third comment character.
+     * This is not set by default.
+     */
+    char   comment3;
+} UcxProperties;
+
+
+/**
+ * Constructs a new UcxProperties object.
+ * @return a pointer to the new UcxProperties object
+ */
+UcxProperties *ucx_properties_new();
+
+/**
+ * Destroys a UcxProperties object.
+ * @param prop the UcxProperties object to destroy
+ */
+void ucx_properties_free(UcxProperties *prop);
+
+/**
+ * Sets the input buffer for the properties parser.
+ * 
+ * After calling this function, you may parse the data by calling
+ * ucx_properties_next() until it returns 0. The function ucx_properties2map()
+ * is a convenience function that reads as much data as possible by using this
+ * function.
+ * 
+ * 
+ * @param prop the UcxProperties object
+ * @param buf a pointer to the new buffer
+ * @param len the payload length of the buffer
+ * @see ucx_properties_next()
+ * @see ucx_properties2map()
+ */
+void ucx_properties_fill(UcxProperties *prop, char *buf, size_t len);
+
+/**
+ * Retrieves the next key/value-pair.
+ * 
+ * This function returns a nonzero value as long as there are key/value-pairs
+ * found. If no more key/value-pairs are found, you may refill the input buffer
+ * with ucx_properties_fill().
+ * 
+ * <b>Attention:</b> the cxstring.ptr pointers of the output parameters point to
+ * memory within the input buffer of the parser and will get invalid some time.
+ * If you want long term copies of the key/value-pairs, use sstrdup() after
+ * calling this function.
+ * 
+ * @param prop the UcxProperties object
+ * @param name a pointer to the cxstring that shall contain the property name
+ * @param value a pointer to the cxstring that shall contain the property value
+ * @return Nonzero, if a key/value-pair was successfully retrieved
+ * @see ucx_properties_fill()
+ */
+int ucx_properties_next(UcxProperties *prop, cxstring *name, cxstring *value);
+
+/**
+ * Retrieves all available key/value-pairs and puts them into a UcxMap.
+ * 
+ * This is done by successive calls to ucx_properties_next() until no more
+ * key/value-pairs can be retrieved.
+ * 
+ * The memory for the map values is allocated by the map's own allocator.
+ * 
+ * @param prop the UcxProperties object
+ * @param map the target map
+ * @return The UcxProperties.error code (i.e. 0 on success).
+ * @see ucx_properties_fill()
+ * @see UcxMap.allocator
+ */
+int ucx_properties2map(UcxProperties *prop, CxMap *map);
+
+/**
+ * Loads a properties file to a UcxMap.
+ * 
+ * This is a convenience function that reads data from an input
+ * stream until the end of the stream is reached.
+ * 
+ * @param map the map object to write the key/value-pairs to
+ * @param file the <code>FILE*</code> stream to read from
+ * @return 0 on success, or a non-zero value on error
+ * 
+ * @see ucx_properties_fill()
+ * @see ucx_properties2map()
+ */
+int ucx_properties_load(CxMap *map, FILE *file);
+
+/**
+ * Stores a UcxMap to a file.
+ * 
+ * The key/value-pairs are written by using the following format:
+ * 
+ * <code>[key] = [value]\\n</code>
+ * 
+ * @param map the map to store
+ * @param file the <code>FILE*</code> stream to write to
+ * @return 0 on success, or a non-zero value on error
+ */
+int ucx_properties_store(CxMap *map, FILE *file);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* UCX_PROPERTIES_H */
+
--- a/ui/gtk/button.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/button.c	Mon May 22 16:17:26 2023 +0200
@@ -31,7 +31,7 @@
 
 #include "button.h"
 #include "container.h"
-#include <ucx/mempool.h>
+#include <cx/allocator.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
--- a/ui/gtk/container.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/container.c	Mon May 22 16:17:26 2023 +0200
@@ -69,8 +69,8 @@
 
 /* -------------------- Frame Container (deprecated) -------------------- */
 UiContainer* ui_frame_container(UiObject *obj, GtkWidget *frame) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiContainer));
     ct->widget = frame;
@@ -87,8 +87,8 @@
 
 /* -------------------- Box Container -------------------- */
 UiContainer* ui_box_container(UiObject *obj, GtkWidget *box) {
-    UiBoxContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiBoxContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiBoxContainer));
     ct->container.widget = box;
@@ -118,8 +118,8 @@
 }
 
 UiContainer* ui_grid_container(UiObject *obj, GtkWidget *grid) {
-    UiGridContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiGridContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiGridContainer));
     ct->container.widget = grid;
@@ -202,8 +202,8 @@
 #endif
 
 UiContainer* ui_scrolledwindow_container(UiObject *obj, GtkWidget *scrolledwindow) {
-    UiContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiContainer));
     ct->widget = scrolledwindow;
@@ -223,8 +223,8 @@
 }
 
 UiContainer* ui_tabview_container(UiObject *obj, GtkWidget *tabview) {
-    UiTabViewContainer *ct = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiTabViewContainer *ct = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiTabViewContainer));
     ct->container.widget = tabview;
@@ -408,8 +408,8 @@
     
     if(max <= 0) max = INT_MAX;
     
-    UiPanedContainer *pctn = ucx_mempool_calloc(
-            obj->ctx->mempool,
+    UiPanedContainer *pctn = cxCalloc(
+            obj->ctx->allocator,
             1,
             sizeof(UiPanedContainer));
     pctn->container.widget = paned;
@@ -482,8 +482,9 @@
     left->container = ct1;
     
     UiObject *right = uic_object_new(obj, sidebar);
-    UiContainer *ct2 = ucx_mempool_malloc(
-            obj->ctx->mempool,
+    UiContainer *ct2 = cxCalloc(
+            obj->ctx->allocator,
+            1,
             sizeof(UiContainer));
     ct2->widget = paned;
     ct2->add = ui_split_container_add2;
@@ -557,7 +558,7 @@
     
     UiObject *tab = ui_malloc(view->ctx, sizeof(UiObject));
     tab->widget = NULL; // initialization for uic_context()
-    tab->ctx = uic_context(tab, view->ctx->mempool);
+    tab->ctx = uic_context(tab, view->ctx->allocator);
     tab->ctx->parent = view->ctx;
     tab->ctx->attach_document = uic_context_attach_document;
     tab->ctx->detach_document2 = uic_context_detach_document2;
--- a/ui/gtk/display.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/display.c	Mon May 22 16:17:26 2023 +0200
@@ -31,7 +31,6 @@
 
 #include "display.h"
 #include "container.h"
-#include <ucx/mempool.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
--- a/ui/gtk/dnd.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/dnd.c	Mon May 22 16:17:26 2023 +0200
@@ -31,20 +31,20 @@
 #include <string.h>
 
 #include "dnd.h"
-#include <ucx/buffer.h>
+#include <cx/buffer.h>
 
 #ifdef UI_GTK2LEGACY
 static gboolean selection_data_set_uris(GtkSelectionData *selection_data, char **uris) {
-    UcxBuffer *buf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
+    CxBuffer *buf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND);
     char *uri;
     int i = 0;
     while((uri = uris[i]) != NULL) {
-        ucx_buffer_puts(buf, uri);
-        ucx_buffer_puts(buf, "\r\n");
+        cxBufferPutString(buf, uri);
+        cxBufferPutString(buf, "\r\n");
     }
     GdkAtom type = gdk_atom_intern("text/uri-list", FALSE);
     gtk_selection_data_set(selection_data, type, 8, (guchar*)buf->space, buf->pos);
-    ucx_buffer_free(buf);
+    cxBufferFree(buf);
     return TRUE;
 }
 static char** selection_data_get_uris(GtkSelectionData *selection_data) {
--- a/ui/gtk/entry.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/entry.c	Mon May 22 16:17:26 2023 +0200
@@ -34,7 +34,6 @@
 #include "container.h"
 #include "entry.h"
 
-#include <ucx/mempool.h>
 
 UIWIDGET ui_spinner(UiObject *obj, int step, UiInteger *i) {
     UiVar *var = malloc(sizeof(UiVar));
--- a/ui/gtk/image.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/image.c	Mon May 22 16:17:26 2023 +0200
@@ -29,18 +29,18 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <ucx/map.h>
+#include <cx/map.h>
 
 #include "toolkit.h"
 #include "image.h"
 #include "../common/properties.h"
 
-static UcxMap *image_map;
+static CxMap *image_map;
 
 static GtkIconTheme *icon_theme;
 
 void ui_image_init(void) {
-    image_map = ucx_map_new(8);
+    image_map = cxHashMapCreateSimple(CX_STORE_POINTERS);
     
     icon_theme = gtk_icon_theme_get_default();
 }
@@ -48,7 +48,7 @@
 // **** deprecated functions ****
 
 GdkPixbuf* ui_get_image(const char *name) {
-    UiImage *img = ucx_map_cstr_get(image_map, name);
+    UiImage *img = cxMapGet(image_map, name);
     if(img) {
         return img->pixbuf;
     } else {
@@ -125,7 +125,7 @@
     UiImage *img = malloc(sizeof(UiImage));
     img->pixbuf = pixbuf;
     if(name) {
-        ucx_map_cstr_put(image_map, name, img);
+        cxMapPut(image_map, name, img);
     }
     return img;
 }
--- a/ui/gtk/menu.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/menu.c	Mon May 22 16:17:26 2023 +0200
@@ -38,48 +38,78 @@
 #include "../ui/window.h"
 #include "container.h"
 
-static UcxList *menus;
-static UcxList *current;
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
+static UiMenu *menus_begin;
+static UiMenu *menus_end;
+static CxList *current;
+
+static void add_menu(UiMenu *menu) {
+    cx_linked_list_add(
+            (void**)&menus_begin,
+            (void**)&menus_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            menu);
+}
+
+static void add_item(UiMenuItemI *item) {
+    UiMenu *menu = cxListAt(current, 0);
+    cx_linked_list_add(
+            (void**)&menu->items_begin,
+            (void**)&menu->items_end,
+            offsetof(UiMenu, item.prev),
+            offsetof(UiMenu, item.next),
+            item);
+}
 
 void ui_menu(char *label) {
-    // free current menu hierarchy
-    ucx_list_free(current);
+    if(!current) {
+        current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS);
+    } else {
+        // free current menu hierarchy
+        cxListClear(current);
+    }
     
     // create menu
     UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.prev = NULL;
+    menu->item.next = NULL;
     menu->item.add_to = (ui_menu_add_f)add_menu_widget;
     
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;    
+    menu->label        = label;
+    menu->items_begin  = NULL;
+    menu->items_end    = NULL;
+    menu->parent       = NULL;    
     
-    current = ucx_list_prepend(NULL, menu);
-    menus = ucx_list_append(menus, menu);
-    
+    add_menu(menu);
+    cxListAdd(current, menu);
 }
 
 void ui_submenu(char *label) {
     UiMenu *menu = malloc(sizeof(UiMenu));
+    menu->item.prev = NULL;
+    menu->item.next = NULL;
     menu->item.add_to = (ui_menu_add_f)add_menu_widget;
     
-    menu->label  = label;
-    menu->items  = NULL;
-    menu->parent = NULL;
+    menu->label       = label;
+    menu->items_begin = NULL;
+    menu->items_end   = NULL;
+    menu->parent      = NULL;
     
     // add submenu to current menu
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, menu);
+    add_item((UiMenuItemI*)menu);
     
     // set the submenu to current menu
-    current = ucx_list_prepend(current, menu);
+    cxListInsert(current, 0, menu);
 }
 
 void ui_submenu_end() {
-    if(ucx_list_size(current) < 2) {
+    if(current->size < 2) {
         return;
     }
-    current = ucx_list_remove(current, current);
-    //UcxList *c = current;
+    cxListRemove(current, 0);
 }
 
 void ui_menuitem(char *label, ui_callback f, void *userdata) {
@@ -96,6 +126,8 @@
     }
     
     UiMenuItem *item = malloc(sizeof(UiMenuItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
     item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
     
     item->label = label;
@@ -108,12 +140,14 @@
     va_start(ap, userdata);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+        if(!item->groups) {
+            item->groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 8);
+        }
+        cxListAdd(item->groups, &group);
     }
     va_end(ap);
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
@@ -122,6 +156,8 @@
     }
     
     UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
     item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
     
     item->stockid = stockid;
@@ -134,12 +170,14 @@
     va_start(ap, userdata);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+        if(!item->groups) {
+            item->groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 8);
+        }
+        cxListAdd(item->groups, &group);
     }
     va_end(ap);
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 void ui_menuseparator() {
@@ -148,10 +186,11 @@
     }
     
     UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
+    item->prev = NULL;
+    item->next = NULL;
     item->add_to = (ui_menu_add_f)add_menuseparator_widget;
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 void ui_checkitem(char *label, ui_callback f, void *userdata) {
@@ -160,13 +199,14 @@
     }
     
     UiCheckItem *item = malloc(sizeof(UiCheckItem));
+    item->item.prev = NULL;
+    item->item.next = NULL;
     item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
     item->label = label;
     item->callback = f;
     item->userdata = userdata;
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 void ui_checkitem_nv(char *label, char *vname) {
@@ -175,12 +215,13 @@
     }
     
     UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
+    item->item.prev = NULL;
+    item->item.next = NULL;
     item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
     item->varname = vname;
     item->label = label;
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
@@ -189,29 +230,30 @@
     }
     
     UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
+    item->item.prev = NULL;
+    item->item.next = NULL;
     item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
     item->callback = f;
     item->userdata = userdata;
     item->list = items;
     
-    UiMenu *cm = current->data;
-    cm->items = ucx_list_append(cm->items, item);
+    add_item((UiMenuItemI*)item);
 }
 
 // private menu functions
 GtkWidget *ui_create_menubar(UiObject *obj) {
-    if(menus == NULL) {
+    if(menus_begin == NULL) {
         return NULL;
     }
     
     GtkWidget *mb = gtk_menu_bar_new();
     
-    UcxList *ls = menus;
+    UiMenu *ls = menus_begin;
     while(ls) {
-        UiMenu *menu = ls->data;
+        UiMenu *menu = ls;
         menu->item.add_to(mb, 0, &menu->item, obj);
         
-        ls = ls->next;
+        ls = (UiMenu*)ls->item.next;
     }
     
     return mb;
@@ -224,13 +266,12 @@
     GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
     
-    UcxList *ls = menu->items;
+    UiMenuItemI *it = menu->items_begin;
     int index = 0;
-    while(ls) {
-        UiMenuItemI *i = ls->data;
-        i->add_to(menu_widget, index, i, obj);
+    while(it) {
+        it->add_to(menu_widget, index, it, obj);
         
-        ls = ls->next;
+        it = it->next;
         index++;
     }
     
@@ -360,10 +401,10 @@
 
 void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
     UiMenuItemList *il = (UiMenuItemList*)item;
-    UcxMempool *mp = obj->ctx->mempool;
+    const CxAllocator *a = obj->ctx->allocator;
     
-    UiActiveMenuItemList *ls = ucx_mempool_malloc(
-            mp,
+    UiActiveMenuItemList *ls = cxMalloc(
+            a,
             sizeof(UiActiveMenuItemList));
     
     ls->object = obj;
@@ -519,12 +560,15 @@
     }
     
     // add groups
-    UcxList *groups = NULL;
+    CxList *groups = NULL;
     va_list ap;
     va_start(ap, userdata);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
     }
     va_end(ap);
     
@@ -555,6 +599,7 @@
     
     if(groups) {
         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+        cxListDestroy(groups);
     }
 }
 
@@ -569,12 +614,15 @@
     }
     
     // add groups
-    UcxList *groups = NULL;
+    CxList *groups = NULL;
     va_list ap;
     va_start(ap, userdata);
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        ucx_list_append(groups, (void*)(intptr_t)group);
+        if(!groups) {
+            groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16);
+        }
+        cxListAdd(groups, &group);
     }
     va_end(ap);
     
@@ -605,5 +653,6 @@
     
     if(groups) {
         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
+        cxListDestroy(groups);
     }
 }
--- a/ui/gtk/menu.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/menu.h	Mon May 22 16:17:26 2023 +0200
@@ -30,7 +30,7 @@
 #define	MENU_H
 
 #include "../ui/menu.h"
-#include <ucx/list.h>
+#include <cx/list.h>
 #include "toolkit.h"
 
 #ifdef	__cplusplus
@@ -50,13 +50,16 @@
 typedef GtkWidget*(*ui_menu_add_f)(GtkWidget *, int, UiMenuItemI*, UiObject*);
     
 struct UiMenuItemI {
+    UiMenuItemI    *prev;
+    UiMenuItemI    *next;
     ui_menu_add_f  add_to;
 };
 
 struct UiMenu {
     UiMenuItemI    item;
     char           *label;
-    UcxList        *items;
+    UiMenuItemI    *items_begin;
+    UiMenuItemI    *items_end;
     UiMenu         *parent;
 };
 
@@ -65,7 +68,7 @@
     ui_callback    callback;
     char           *label;
     void           *userdata;
-    UcxList        *groups;
+    CxList         *groups;
 };
 
 struct UiStMenuItem {
@@ -73,7 +76,7 @@
     ui_callback    callback;
     char           *stockid;
     void           *userdata;
-    UcxList        *groups;
+    CxList         *groups;
 };
 
 struct UiCheckItem {
--- a/ui/gtk/range.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/range.c	Mon May 22 16:17:26 2023 +0200
@@ -31,7 +31,6 @@
 
 #include "range.h"
 #include "container.h"
-#include <ucx/mempool.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
--- a/ui/gtk/text.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/text.c	Mon May 22 16:17:26 2023 +0200
@@ -34,6 +34,8 @@
 #include "container.h"
 
 
+#include "../common/types.h"
+
 static void selection_handler(
         GtkTextBuffer *buf,
         GtkTextIter *location,
@@ -304,19 +306,19 @@
     }
     
     if(mgr->cur) {
-        UcxList *elm = mgr->cur->next;
+        UiTextBufOp *elm = mgr->cur->next;
         if(elm) {
             mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
             while(elm) {
                 elm->prev = NULL;   
-                UcxList *next = elm->next;
-                ui_free_textbuf_op(elm->data);
-                free(elm);
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
                 elm = next;
             }
         }
         
-        UiTextBufOp *last_op = mgr->cur->data;
+        UiTextBufOp *last_op = mgr->cur;
         if(
             last_op->type == UI_TEXTBUF_INSERT &&
             ui_check_insertstr(last_op->text, last_op->len, text, length) == 0)
@@ -341,15 +343,22 @@
     dpstr[length] = 0;
     
     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
     op->type = UI_TEXTBUF_INSERT;
     op->start = gtk_text_iter_get_offset(location);
     op->end = op->start+length;
     op->len = length;
     op->text = dpstr;
     
-    UcxList *elm = ucx_list_append(NULL, op);
-    mgr->cur = elm;
-    mgr->begin = ucx_list_concat(mgr->begin, elm);
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
 }
 
 void ui_textbuf_delete(
@@ -369,14 +378,14 @@
     }
     
     if(mgr->cur) {
-        UcxList *elm = mgr->cur->next;
+        UiTextBufOp *elm = mgr->cur->next;
         if(elm) {
             mgr->cur->next = NULL;
+            mgr->end = mgr->cur;
             while(elm) {
                 elm->prev = NULL;   
-                UcxList *next = elm->next;
-                ui_free_textbuf_op(elm->data);
-                free(elm);
+                UiTextBufOp *next = elm->next;
+                ui_free_textbuf_op(elm);
                 elm = next;
             }
         }
@@ -385,6 +394,8 @@
     char *text = gtk_text_buffer_get_text(value->obj, start, end, FALSE);
     
     UiTextBufOp *op = malloc(sizeof(UiTextBufOp));
+    op->prev = NULL;
+    op->next = NULL;
     op->type = UI_TEXTBUF_DELETE;
     op->start = gtk_text_iter_get_offset(start);
     op->end = gtk_text_iter_get_offset(end);
@@ -395,20 +406,39 @@
     dpstr[op->len] = 0;
     op->text = dpstr;
     
-    UcxList *elm = ucx_list_append(NULL, op);
-    mgr->cur = elm;
-    mgr->begin = ucx_list_concat(mgr->begin, elm);
+    cx_linked_list_add(
+            (void**)&mgr->begin,
+            (void**)&mgr->end,
+            offsetof(UiTextBufOp, prev),
+            offsetof(UiTextBufOp, next),
+            op);
+    
+    mgr->cur = op;
 }
 
 UiUndoMgr* ui_create_undomgr() {
     UiUndoMgr *mgr = malloc(sizeof(UiUndoMgr));
     mgr->begin = NULL;
+    mgr->end = NULL;
     mgr->cur = NULL;
     mgr->length = 0;
     mgr->event = 1;
     return mgr;
 }
 
+void ui_destroy_undomgr(UiUndoMgr *mgr) {
+    UiTextBufOp *op = mgr->begin;
+    while(op) {
+        UiTextBufOp *nextOp = op->next;
+        if(op->text) {
+            free(op->text);
+        }
+        free(op);
+        op = nextOp;
+    }
+    free(mgr);
+}
+
 void ui_free_textbuf_op(UiTextBufOp *op) {
     if(op->text) {
         free(op->text);
@@ -440,7 +470,7 @@
     UiUndoMgr *mgr = value->undomgr;
     
     if(mgr->cur) {
-        UiTextBufOp *op = mgr->cur->data;
+        UiTextBufOp *op = mgr->cur;
         mgr->event = 0;
         switch(op->type) {
             case UI_TEXTBUF_INSERT: {
@@ -468,7 +498,7 @@
 void ui_text_redo(UiText *value) {
     UiUndoMgr *mgr = value->undomgr;
     
-    UcxList *elm = NULL;
+    UiTextBufOp *elm = NULL;
     if(mgr->cur) {
         if(mgr->cur->next) {
             elm = mgr->cur->next;
@@ -478,7 +508,7 @@
     }
     
     if(elm) {
-        UiTextBufOp *op = elm->data;
+        UiTextBufOp *op = elm;
         mgr->event = 0;
         switch(op->type) {
             case UI_TEXTBUF_INSERT: {
@@ -580,6 +610,10 @@
 
 void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
     if(textfield->var) {
+        UiText *text = textfield->var->value;
+        if(text->undomgr) {
+            ui_destroy_undomgr(text->undomgr);
+        }
         ui_destroy_boundvar(textfield->ctx, textfield->var);
     }
     free(textfield);
--- a/ui/gtk/text.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/text.h	Mon May 22 16:17:26 2023 +0200
@@ -31,7 +31,7 @@
 
 #include "../ui/text.h"
 #include "toolkit.h"
-#include <ucx/list.h>
+#include <cx/linked_list.h>
 #include "../common/context.h"
 
 #ifdef	__cplusplus
@@ -40,19 +40,24 @@
 
 #define UI_TEXTBUF_INSERT 0
 #define UI_TEXTBUF_DELETE 1
-typedef struct UiTextBufOp {
+    
+typedef struct UiTextBufOp UiTextBufOp;
+struct UiTextBufOp {
+    UiTextBufOp *prev;
+    UiTextBufOp *next;
     int  type; // UI_TEXTBUF_INSERT, UI_TEXTBUF_DELETE
     int  start;
     int  end;
     int  len;
     char *text;
-} UiTextBufOp;
+};
 
 typedef struct UiUndoMgr {
-    UcxList *begin;
-    UcxList *cur;
-    int     length;
-    int     event;
+    UiTextBufOp  *begin;
+    UiTextBufOp  *end;
+    UiTextBufOp  *cur;
+    int          length;
+    int          event;
 } UiUndoMgr;
 
 typedef struct UiTextArea {
@@ -95,6 +100,7 @@
         GtkTextIter *end,
         void *data);
 UiUndoMgr* ui_create_undomgr();
+void ui_destroy_undomgr(UiUndoMgr *mgr);
 void ui_free_textbuf_op(UiTextBufOp *op);
 int ui_check_insertstr(char *oldstr, int oldlen, char *newstr, int newlen);
 
--- a/ui/gtk/toolbar.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/toolbar.c	Mon May 22 16:17:26 2023 +0200
@@ -34,14 +34,18 @@
 #include "button.h"
 #include "image.h"
 #include "tree.h"
-#include <ucx/mempool.h>
+#include <cx/basic_mempool.h>
+#include <cx/hash_map.h>
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
 #include "../common/context.h"
 
-static UcxMap *toolbar_items;
-static UcxList *defaults;
+static CxMap *toolbar_items;
+static CxList *defaults;
 
 void ui_toolbar_init() {
-    toolbar_items = ucx_map_new(16);
+    toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    defaults = cxLinkedListCreateSimple(CX_STORE_POINTERS);
 }
 
 void ui_toolitem(char *name, char *label, ui_callback f, void *udata) {
@@ -80,7 +84,7 @@
     item->isimportant = 0;
     item->groups = NULL;
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 void ui_toolitem_vstgr(
@@ -102,10 +106,13 @@
     // add groups
     int group;
     while((group = va_arg(ap, int)) != -1) {
-        item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
+        if(!item->groups) {
+            item->groups = cxArrayListCreateSimple(sizeof(int), 16);
+        }
+        cxListAdd(item->groups, &group);
     }
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 void ui_toolitem_toggle(const char *name, const char *label, const char *img, UiInteger *i) {
@@ -119,7 +126,7 @@
     item->value = i;
     item->var = NULL;
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 void ui_toolitem_toggle_st(const char *name, const char *stockid, UiInteger *i) {
@@ -133,7 +140,7 @@
     item->value = i;
     item->var = NULL;
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 void ui_toolitem_toggle_nv(const char *name, const char *label, const char *img, const char *intvar) {
@@ -147,7 +154,7 @@
     item->value = NULL;
     item->var = intvar;
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 void ui_toolitem_toggle_stnv(const char *name, const char *stockid, const char *intvar) {
@@ -161,7 +168,7 @@
     item->value = NULL;
     item->var = intvar;
     
-    ucx_map_cstr_put(toolbar_items, name, item);
+    cxMapPut(toolbar_items, name, item);
 }
 
 
@@ -184,7 +191,7 @@
     cb->callback = f;
     cb->userdata = udata;
     
-    ucx_map_cstr_put(toolbar_items, name, cb);
+    cxMapPut(toolbar_items, name, cb);
 }
 
 void ui_toolbar_combobox_str(
@@ -210,13 +217,13 @@
     cb->callback = f;
     cb->userdata = udata;
     
-    ucx_map_cstr_put(toolbar_items, name, cb);
+    cxMapPut(toolbar_items, name, cb);
 }
 
 
 void ui_toolbar_add_default(char *name) {
     char *s = strdup(name);
-    defaults = ucx_list_append(defaults, s);
+    cxListAdd(defaults, s);
 }
 
 GtkWidget* ui_create_toolbar(UiObject *obj) {
@@ -232,14 +239,15 @@
 #endif
     
     GtkToolbar *tb = GTK_TOOLBAR(toolbar);
-    UCX_FOREACH(elm, defaults) {
-        UiToolItemI *item = ucx_map_cstr_get(toolbar_items, elm->data);
+    CxIterator i = cxListIterator(defaults);
+    cx_foreach(char *, def, i) {
+        UiToolItemI *item = cxMapGet(toolbar_items, def);
         if(item) {
             item->add_to(tb, item, obj);
-        } else if(!strcmp(elm->data, "@separator")) {
+        } else if(!strcmp(def, "@separator")) {
             gtk_toolbar_insert(tb, gtk_separator_tool_item_new(), -1);
         } else {
-            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", elm->data);
+            fprintf(stderr, "UI Error: Unknown toolbar item: %s\n", def);
         }
     }
     
@@ -258,8 +266,8 @@
     }
     
     if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
                 sizeof(UiEventData));
         event->obj = obj;
         event->userdata = item->userdata;
@@ -287,8 +295,8 @@
     }
     
     if(item->callback) {
-        UiEventData *event = ucx_mempool_malloc(
-                obj->ctx->mempool,
+        UiEventData *event = cxMalloc(
+                obj->ctx->allocator,
                 sizeof(UiEventData));
         event->obj = obj;
         event->userdata = item->userdata;
@@ -349,8 +357,8 @@
     
     // register event
     // the event func will call the UiInteger observer callbacks
-    UiEventData *event = ucx_mempool_malloc(
-            obj->ctx->mempool,
+    UiEventData *event = cxMalloc(
+            obj->ctx->allocator,
             sizeof(UiEventData));
     event->obj = obj;
     event->userdata = var;
--- a/ui/gtk/toolbar.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/toolbar.h	Mon May 22 16:17:26 2023 +0200
@@ -30,8 +30,8 @@
 #define	TOOLBAR_H
 
 #include "../ui/toolbar.h"
-#include <ucx/map.h>
-#include <ucx/list.h>
+#include <cx/map.h>
+#include <cx/list.h>
 
 #include "model.h"
 #include "tree.h"
@@ -61,7 +61,7 @@
     ui_callback    callback;
     void           *userdata;
     const char     *varname;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
@@ -71,7 +71,7 @@
     ui_callback    callback;
     void           *userdata;
     const char     *varname;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
@@ -82,7 +82,7 @@
     const char     *stockid;
     UiInteger      *value;
     const char     *var;
-    UcxList        *groups;
+    CxList         *groups;
     int            isimportant;
 };
 
--- a/ui/gtk/toolkit.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/toolkit.c	Mon May 22 16:17:26 2023 +0200
@@ -38,7 +38,9 @@
 #include "../common/document.h"
 #include "../common/properties.h"
 
-#include <ucx/utils.h>
+#include <cx/utils.h>
+#include <cx/string.h>
+#include <cx/printf.h>
 
 #include <pthread.h>
 
@@ -118,7 +120,7 @@
 
 void ui_main() {
 #ifndef UI_GTK2
-    sstr_t appid = ucx_sprintf(
+    cxmutstr appid = cx_asprintf(
             "ui.%s",
             application_name ? application_name : "application1");
     
@@ -246,10 +248,14 @@
 }
 
 void ui_destroy_boundvar(UiContext *ctx, UiVar *var) {
+    uic_unbind_var(var);
+    
     if(var->type == UI_VAR_SPECIAL) {
         free(var);
     } else {
-        uic_remove_bound_var(ctx, var);
+        ui_free(var->from_ctx, var);
+        // TODO: free or unbound
+        //uic_remove_bound_var(ctx, var);
     }
 }
 
--- a/ui/gtk/window.c	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/gtk/window.c	Mon May 22 16:17:26 2023 +0200
@@ -34,6 +34,8 @@
 #include "../ui/properties.h"
 #include "../common/context.h"
 
+#include <cx/basic_mempool.h>
+
 #include "menu.h"
 #include "toolbar.h"
 #include "container.h"
@@ -66,8 +68,8 @@
 }
 
 static UiObject* create_window(char *title, void *window_data, UiBool simple) {
-    UcxMempool *mp = ucx_mempool_new(256);
-    UiObject *obj = ucx_mempool_calloc(mp, 1, sizeof(UiObject)); 
+    CxMempool *mp = cxBasicMempoolCreate(256);
+    UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); 
     
 #ifndef UI_GTK2
     obj->widget = gtk_application_window_new(ui_get_application());
@@ -76,7 +78,7 @@
 #endif
     
     
-    obj->ctx = uic_context(obj, mp);
+    obj->ctx = uic_context(obj, mp->allocator);
     obj->window = window_data;
     
     if(title != NULL) {
--- a/ui/ui/properties.h	Sat Apr 15 21:06:45 2023 +0200
+++ b/ui/ui/properties.h	Mon May 22 16:17:26 2023 +0200
@@ -35,7 +35,7 @@
 extern "C" {
 #endif
 
-typedef struct UcxMap UiProperties;
+typedef struct CxMap UiProperties;
     
 char* ui_getappdir();
 char* ui_configfile(char *name);

mercurial