Sat, 08 Nov 2025 23:06:11 +0100
update ucx
--- a/dav/Makefile Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/Makefile Sat Nov 08 23:06:11 2025 +0100 @@ -28,7 +28,36 @@ include ../config.mk -include obj.mk +DAV_SRC = main.c +DAV_SRC += config.c +DAV_SRC += optparser.c +DAV_SRC += error.c +DAV_SRC += assistant.c +DAV_SRC += tar.c +DAV_SRC += system.c +DAV_SRC += libxattr.c +DAV_SRC += finfo.c +DAV_SRC += connect.c + +SYNC_SRC = sync.c +SYNC_SRC += config.c +SYNC_SRC += scfg.c +SYNC_SRC += sopt.c +SYNC_SRC += db.c +SYNC_SRC += error.c +SYNC_SRC += assistant.c +SYNC_SRC += libxattr.c +SYNC_SRC += finfo.c +SYNC_SRC += tags.c +SYNC_SRC += system.c +SYNC_SRC += connect.c + +XATTR_SRC = xattrtool.c +XATTR_SRC += libxattr.c + +DAV_OBJ = $(DAV_SRC:%.c=../build/tool/%$(OBJ_EXT)) +SYNC_OBJ = $(SYNC_SRC:%.c=../build/tool/%$(OBJ_EXT)) +XATTR_OBJ = $(XATTR_SRC:%.c=../build/tool/%$(OBJ_EXT)) DAV_BIN = ../build/bin/dav$(APP_EXT) DAV_SYNC_BIN = ../build/bin/dav-sync$(APP_EXT) @@ -36,13 +65,13 @@ all: ../build/bin/dav ../build/bin/dav-sync ../build/bin/xattrtool -$(DAV_BIN): $(DAV_MAIN_OBJ) $(DAV_OBJ) $(COMMON_OBJ) ../build/lib/libidav$(LIB_EXT) - $(CC) -o $(DAV_BIN) $(DAV_MAIN_OBJ) $(DAV_OBJ) $(COMMON_OBJ) \ +$(DAV_BIN): $(DAV_OBJ) ../build/lib/libidav$(LIB_EXT) + $(CC) -o $(DAV_BIN) $(DAV_OBJ) \ ../build/lib/libidav$(LIB_EXT) ../build/lib/libucx$(LIB_EXT) \ $(LDFLAGS) $(DAV_LDFLAGS) -$(DAV_SYNC_BIN): $(SYNC_MAIN_OBJ) $(SYNC_OBJ) $(COMMON_OBJ) ../build/lib/libidav$(LIB_EXT) - $(CC) -o $(DAV_SYNC_BIN) $(SYNC_MAIN_OBJ) $(COMMON_OBJ) $(SYNC_OBJ) \ +$(DAV_SYNC_BIN): $(SYNC_OBJ) ../build/lib/libidav$(LIB_EXT) + $(CC) -o $(DAV_SYNC_BIN) $(SYNC_OBJ) \ ../build/lib/libidav$(LIB_EXT) ../build/lib/libucx$(LIB_EXT) \ $(LDFLAGS) $(DAV_LDFLAGS)
--- a/dav/config.c Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/config.c Sat Nov 08 23:06:11 2025 +0100 @@ -31,7 +31,6 @@ #include <string.h> #include <sys/types.h> #include <cx/hash_map.h> -#include <cx/streams.h> #include <errno.h> #include <libxml/tree.h> @@ -42,6 +41,8 @@ #include <libidav/config.h> #include <libidav/pwdstore.h> +#include <cx/streams.h> + #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b)
--- a/dav/main.c Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/main.c Sat Nov 08 23:06:11 2025 +0100 @@ -127,7 +127,7 @@ int dav_main(int argc, char **argv) { if(argc < 2) { fprintf(stderr, "Missing command\n"); - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); return -1; } @@ -136,7 +136,7 @@ char *cmd = argv[1]; CmdArgs *args = cmd_parse_args(argc - 2, argv + 2); if(!args) { - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); return -1; } if(cmd_getoption(args, "noinput")) { @@ -247,7 +247,7 @@ } else if(!strcasecmp(cmd, "complete")) { ret = cmd_complete(args); } else { - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); } } @@ -298,7 +298,7 @@ return NULL; } -void dav_sync_print_usage(char *cmd) { +void print_usage(char *cmd) { fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd); fprintf(stderr, "Commands:\n");
--- a/dav/main.h Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/main.h Sat Nov 08 23:06:11 2025 +0100 @@ -59,7 +59,7 @@ typedef int(*getfunc)(DavCfgRepository *, GetResource *, CmdArgs *, void *); -void dav_sync_print_usage(char *cmd); +void print_usage(char *cmd); int update_progress(DavResource *res, int64_t total, int64_t now, Progress *p); void download_progress(DavResource *res, int64_t total, int64_t now, void *data);
--- a/dav/obj.mk Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. -# -# Copyright 2025 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. -# - -DAV_MAIN_SRC = main.c - -SYNC_MAIN_SRC = sync_main.c - -COMMON_SRC = config.c -COMMON_SRC += error.c -COMMON_SRC += assistant.c -COMMON_SRC += libxattr.c -COMMON_SRC += finfo.c -COMMON_SRC += connect.c -COMMON_SRC += system.c -COMMON_SRC += optparser.c - -DAV_SRC = tar.c - -SYNC_SRC += sync.c -SYNC_SRC += syncdir.c -SYNC_SRC += scfg.c -SYNC_SRC += db.c -SYNC_SRC += tags.c -SYNC_SRC += pull.c -SYNC_SRC += push.c - -XATTR_SRC = xattrtool.c -XATTR_SRC += libxattr.c - -DAV_OBJ = $(DAV_SRC:%.c=../build/tool/%$(OBJ_EXT)) -SYNC_OBJ = $(SYNC_SRC:%.c=../build/tool/%$(OBJ_EXT)) -COMMON_OBJ = $(COMMON_SRC:%.c=../build/tool/%$(OBJ_EXT)) -XATTR_OBJ = $(XATTR_SRC:%.c=../build/tool/%$(OBJ_EXT)) - -DAV_MAIN_OBJ = $(DAV_MAIN_SRC:%.c=../build/tool/%$(OBJ_EXT)) -SYNC_MAIN_OBJ = $(SYNC_MAIN_SRC:%.c=../build/tool/%$(OBJ_EXT))
--- a/dav/optparser.c Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/optparser.c Sat Nov 08 23:06:11 2025 +0100 @@ -31,6 +31,7 @@ #include <string.h> #include "optparser.h" +#include "sopt.h" #include <cx/hash_map.h> @@ -215,86 +216,6 @@ return a; } -CmdArgs* cmd_parse_sync_args(int argc, char **argv) { - CmdArgs *a = malloc(sizeof(CmdArgs)); - a->options = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - a->argv = calloc(argc, sizeof(char*)); - a->argc = 0; - - const char *NOARG = ""; - - char *option = NULL; - char optchar = 0; - for(int i=0;i<argc;i++) { - char *arg = argv[i]; - size_t len = strlen(arg); - if(len > 1 && arg[0] == '-') { - for(int c=1;c<len;c++) { - switch(arg[c]) { - default: { - fprintf(stderr, "Unknown option -%c\n\n", arg[c]); - cmd_args_free(a); - return NULL; - } - case 'c': { - cmd_map_put(a->options, "conflict", NOARG); - break; - } - case 'l': { - cmd_map_put(a->options, "lock", NOARG); - break; - } - case 'd': { - cmd_map_put(a->options, "nolock", NOARG); - break; - } - case 'r': { - cmd_map_put(a->options, "remove", NOARG); - break; - } - case 'v': { - cmd_map_put(a->options, "verbose", NOARG); - break; - } - case 's': { - option = "syncdir"; - optchar = 's'; - break; - } - case 't': { - option = "tags"; - optchar = 't'; - break; - } - case 'V': { - option = "version"; - optchar = 'V'; - break; - } - case 'R': { - cmd_map_put(a->options, "restore-removed", NOARG); - break; - } - case 'M': { - cmd_map_put(a->options, "restore-modified", NOARG); - break; - } - case 'S': { - cmd_map_put(a->options, "snapshot", NOARG); - } - } - } - } else if(option) { - cmd_map_put(a->options, option, arg); - option = NULL; - } else { - a->argv[a->argc++] = arg; - } - } - - return a; -} - char* cmd_getoption(CmdArgs *arg, const char *name) { return cxMapGet(arg->options, cx_hash_key_str(name)); }
--- a/dav/optparser.h Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/optparser.h Sat Nov 08 23:06:11 2025 +0100 @@ -37,7 +37,6 @@ #endif CmdArgs* cmd_parse_args(int argc, char **argv); -CmdArgs* cmd_parse_sync_args(int argc, char **argv); char* cmd_getoption(CmdArgs *arg, const char *name); void cmd_args_free(CmdArgs *args);
--- a/dav/pull.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,287 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "pull.h" -#include "syncdir.h" - -#include <libidav/utils.h> -#include <errno.h> -#include <string.h> - -/* - * strcmp version that works with NULL pointers - */ -static int nullstrcmp(const char *s1, const char *s2) { - if(!s1 && s2) { - return -1; - } - if(s1 && !s2) { - return 1; - } - if(!s1 && !s2) { - return 0; - } - return strcmp(s1, s2); -} - -static char* nullstrdup(const char *s) { - return s ? strdup(s) : NULL; -} - - - -RemoteChangeType resource_get_remote_change( - CmdArgs *a, - DavResource *res, - SyncDirectory *dir, - SyncDatabase *db) -{ - DavBool update_db = FALSE; - - char *etag = dav_get_string_property(res, "D:getetag"); - if(!etag) { - fprintf(stderr, "Error: resource %s has no etag\n", res->path); - return REMOTE_NO_CHANGE; - } - char *hash = sync_get_content_hash(res); - - DavBool issplit = dav_get_property(res, "idav:split") ? TRUE : FALSE; - if(issplit) { - util_remove_trailing_pathseparator(res->path); - } - DavBool iscollection = res->iscollection && !issplit; - - RemoteChangeType type = cmd_getoption(a, "conflict") ? - REMOTE_CHANGE_MODIFIED : REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; - - LocalResource *local = cxMapGet(db->resources, dav_resource_path_key(res)); - char *local_path = syncdir_create_local_path(dir, res->path); - - char *link = SYNC_SYMLINK(dir) ? - dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL; - - SYS_STAT s; - DavBool exists = 1; - if(sys_stat(local_path, &s)) { - if(errno != ENOENT) { - fprintf(stderr, "Cannot stat file: %s\n", local_path); - free(local_path); - return REMOTE_NO_CHANGE; - } - exists = 0; - } - - RemoteChangeType ret = REMOTE_NO_CHANGE; - if(iscollection) { - if(!exists) { - ret = REMOTE_CHANGE_MKDIR; - } else if(local && S_ISDIR(s.st_mode)) { - local->isdirectory = 1; // make sure isdirectory is set - } else { - // set change to REMOTE_CHANGE_MKDIR, which will fail later - ret = REMOTE_CHANGE_MKDIR; - } - } else if(local) { - DavBool nochange = FALSE; - if(SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) { - ret = REMOTE_CHANGE_LINK; - nochange = TRUE; - - if(local->link_target) { - LocalResource *local2 = local_resource_new(dir, db, local->path); - if(type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) { - ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; - } - local_resource_free(local2); - - if(!nullstrcmp(link, local->link_target)) { - ret = REMOTE_NO_CHANGE; - update_db = TRUE; - } - } - } else if(issplit && local->hash && hash) { - if(!strcmp(local->hash, hash)) { - // resource is already up-to-date on the client - nochange = TRUE; - } - } else if(local->etag) { - cxstring e = cx_str(etag); - if(cx_strprefix(e, CX_STR("W/"))) { - e = cx_strsubs(e, 2); - } - if(!strcmp(e.ptr, local->etag)) { - // resource is already up-to-date on the client - nochange = TRUE; - } - } - - if(!nochange) { - if(!(exists && s.st_mtime != local->last_modified)) { - type = REMOTE_CHANGE_MODIFIED; - } - ret = type; - } - } else if(link) { - // new file is a link - ret = REMOTE_CHANGE_LINK; - - if(exists && type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) { - // a file with the same name already exists - // if it is a link, compare the targets - LocalResource *local2 = local_resource_new(dir, db, res->path); - if(local2) { - if(local2->link_target) { - if(strcmp(link, local2->link_target)) { - ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; - } - } else { - ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; - } - - local_resource_free(local2); - } - } - - } else if(exists) { - ret = type; - } else { - ret = REMOTE_CHANGE_NEW; - } - - // if hashing is enabled we can compare the hash of the remote file - // with the local file to test if a file is really modified - char *update_hash = NULL; - if (!iscollection && - !link && - (ret == REMOTE_CHANGE_MODIFIED || ret == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) && - exists && - hash && - !dir->pull_skip_hashing) - { - // because rehashing a file is slow, there is a config element for - // disabling this (pull-skip-hashing) - char *local_hash = util_file_hash(local_path); - if(local_hash) { - if(!strcmp(hash, local_hash)) { - ret = REMOTE_NO_CHANGE; - update_db = TRUE; - update_hash = local_hash; - - // if local already exists, update the hash here - // because it is possible that there are metadata updates - // and in this case the db will updated later and needs - // the current hash - if(local) { - if(local->hash) { - free(local->hash); - } - local->hash = local_hash; - } - } else { - free(local_hash); - } - } - } - - // if a file is not modified, check if the metadata has changed - while(ret == REMOTE_NO_CHANGE && local) { - // check if tags have changed - if(dir->tagconfig) { - DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_PROPS_NS, "tags"); - CxList *remote_tags = NULL; - if(tagsprop) { - remote_tags = parse_dav_xml_taglist(tagsprop); - } - char *remote_hash = create_tags_hash(remote_tags); - if(nullstrcmp(remote_hash, local->remote_tags_hash)) { - ret = REMOTE_CHANGE_METADATA; - } - if(remote_hash) { - free(remote_hash); - } - free_taglist(remote_tags); - - if(ret == REMOTE_CHANGE_METADATA) { - break; - } - } - - // check if extended attributes have changed - if((dir->metadata & FINFO_XATTR) == FINFO_XATTR) { - DavXmlNode *xattr = dav_get_property_ns(res, DAV_PROPS_NS, "xattributes"); - char *xattr_hash = get_xattr_hash(xattr); - if(nullstrcmp(xattr_hash, local->xattr_hash)) { - ret = REMOTE_CHANGE_METADATA; - break; - } - } - - // check if finfo has changed - DavXmlNode *finfo = dav_get_property_ns(res, DAV_PROPS_NS, "finfo"); - if((dir->metadata & FINFO_MODE) == FINFO_MODE) { - FileInfo f; - finfo_get_values(finfo, &f); - if(f.mode_set && f.mode != local->mode) { - ret = REMOTE_CHANGE_METADATA; - break; - } - } - - break; - } - - // if update_db is set, a file was modified on the server, but we already - // have the file content, but we need to update the db - if(ret == REMOTE_NO_CHANGE && update_db) { - if(!local) { - local = calloc(1, sizeof(LocalResource)); - local->path = strdup(res->path); - - cxMapPut(db->resources, cx_hash_key_str(local->path), local); - } - - // update local res - SYS_STAT statdata; - if(!sys_stat(local_path, &statdata)) { - sync_set_metadata_from_stat(local, &statdata); - } else { - fprintf(stderr, "stat failed for file: %s : %s", local_path, strerror(errno)); - } - local_resource_set_etag(local, etag); - if(!local->hash) { - local->hash = update_hash; - } // else: hash already updated - if(link) { - free(local->link_target); - local->link_target = link; - } - } - - free(local_path); - return ret; -}
--- a/dav/pull.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 PULL_H -#define PULL_H - -#include "sync.h" - -#ifdef __cplusplus -extern "C" { -#endif - - - - -#ifdef __cplusplus -} -#endif - -#endif /* PULL_H */ -
--- a/dav/push.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "push.h" -
--- a/dav/push.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 PUSH_H -#define PUSH_H - -#ifdef __cplusplus -extern "C" { -#endif - - - - -#ifdef __cplusplus -} -#endif - -#endif /* PUSH_H */ -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dav/sopt.c Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sopt.h" + +#include <cx/hash_map.h> + +void cmd_args_free(CmdArgs *args) { + if(args) { + cxMapFree(args->options); + free(args->argv); + free(args); + } +} + +static void cmd_map_put(CxMap *map, const char *key, const void *value) { + cxMapPut(map, key, (void*)value); +} + +CmdArgs* cmd_parse_args(int argc, char **argv) { + CmdArgs *a = malloc(sizeof(CmdArgs)); + a->options = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + a->argv = calloc(argc, sizeof(char*)); + a->argc = 0; + + const char *NOARG = ""; + + char *option = NULL; + char optchar = 0; + for(int i=0;i<argc;i++) { + char *arg = argv[i]; + size_t len = strlen(arg); + if(len > 1 && arg[0] == '-') { + for(int c=1;c<len;c++) { + switch(arg[c]) { + default: { + fprintf(stderr, "Unknown option -%c\n\n", arg[c]); + cmd_args_free(a); + return NULL; + } + case 'c': { + cmd_map_put(a->options, "conflict", NOARG); + break; + } + case 'l': { + cmd_map_put(a->options, "lock", NOARG); + break; + } + case 'd': { + cmd_map_put(a->options, "nolock", NOARG); + break; + } + case 'r': { + cmd_map_put(a->options, "remove", NOARG); + break; + } + case 'v': { + cmd_map_put(a->options, "verbose", NOARG); + break; + } + case 's': { + option = "syncdir"; + optchar = 's'; + break; + } + case 't': { + option = "tags"; + optchar = 't'; + break; + } + case 'V': { + option = "version"; + optchar = 'V'; + break; + } + case 'R': { + cmd_map_put(a->options, "restore-removed", NOARG); + break; + } + case 'M': { + cmd_map_put(a->options, "restore-modified", NOARG); + break; + } + case 'S': { + cmd_map_put(a->options, "snapshot", NOARG); + } + } + } + } else if(option) { + cmd_map_put(a->options, option, arg); + option = NULL; + } else { + a->argv[a->argc++] = arg; + } + } + + return a; +} + +char* cmd_getoption(CmdArgs *arg, const char *name) { + return cxMapGet(arg->options, cx_hash_key_str(name)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dav/sopt.h Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2018 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 OPTPARSER_H +#define OPTPARSER_H + +#include <cx/map.h> +#include "opt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* ArgBool; + +void cmd_args_free(CmdArgs *args); +CmdArgs* cmd_parse_args(int argc, char **argv); +char* cmd_getoption(CmdArgs *arg, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* OPTPARSER_H */ +
--- a/dav/sync.c Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/sync.c Sat Nov 08 23:06:11 2025 +0100 @@ -39,7 +39,6 @@ #include <cx/list.h> #include <cx/hash_map.h> #include <cx/printf.h> -#include <cx/streams.h> #ifndef _WIN32 // unix includes @@ -59,16 +58,17 @@ #include <libidav/session.h> +#include <cx/streams.h> + #include "sync.h" #include "config.h" -#include "optparser.h" +#include "sopt.h" #include "error.h" #include "assistant.h" #include "libxattr.h" #include "tags.h" #include "connect.h" -#include "syncdir.h" #include "system.h" @@ -202,19 +202,24 @@ return s ? strdup(s) : NULL; } - -// TODO: remove, ucx supports creating iterators for NULL maps +static void nullfree(void *p) { + if(p) { + free(p); + } +} + + + static CxMapIterator mapIteratorValues(CxMap *map) { return cxMapIteratorValues(map ? map : cxEmptyMap); } -// TODO: remove + static CxIterator listIterator(CxList *list) { return cxListIterator(list ? list : cxEmptyList); } typedef void*(*clonefunc)(void *elm, void *userdata); -// TODO: remove when ucx has a clone func static CxMap* mapClone(const CxAllocator *a, CxMap *map, clonefunc clone, void *userdata) { CxMap *newmap = cxHashMapCreate( a, @@ -237,6 +242,45 @@ return newmap; } +int dav_sync_main(int argc, char **argv); + +#ifdef _WIN32 +static char* wchar2utf8(const wchar_t *wstr, size_t wlen) { + size_t maxlen = wlen * 4; + char *ret = malloc(maxlen + 1); + int ret_len = WideCharToMultiByte( + CP_UTF8, + 0, + wstr, + wlen, + ret, + maxlen, + NULL, + NULL); + ret[ret_len] = 0; + return ret; +} + +int wmain(int argc, wchar_t **argv) { + char **argv_utf8 = calloc(argc, sizeof(char*)); + for(int i=0;i<argc;i++) { + argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i])); + } + + int ret = dav_sync_main(argc, argv_utf8); + + for(int i=0;i<argc;i++) { + free(argv_utf8[i]); + } + free(argv_utf8); + + return ret; +} +#else +int main(int argc, char **argv) { + return dav_sync_main(argc, argv); +} +#endif int dav_sync_main(int argc, char **argv) { orig_argc = argc; @@ -244,14 +288,14 @@ if(argc < 2) { fprintf(stderr, "Missing command\n"); - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); return -1; } char *cmd = argv[1]; - CmdArgs *args = cmd_parse_sync_args(argc - 2, argv + 2); + CmdArgs *args = cmd_parse_args(argc - 2, argv + 2); if(!args) { - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); return -1; } int ret = EXIT_FAILURE; @@ -344,7 +388,7 @@ || !strcmp(cmd, "check-repositories")) { ret = cmd_check_repositories(args); } else { - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); } } @@ -363,7 +407,7 @@ return ret; } -void dav_sync_print_usage(char *cmd) { +void print_usage(char *cmd) { fprintf(stderr, "Usage: %s command [options] arguments...\n\n", cmd); fprintf(stderr, "Commands:\n"); @@ -448,6 +492,14 @@ #endif +static char* create_local_path(SyncDirectory *dir, const char *path) { + char *local_path = util_concat_path(dir->path, path); + size_t local_path_len = strlen(local_path); + if(local_path[local_path_len-1] == '/') { + local_path[local_path_len-1] = '\0'; + } + return local_path; +} static int res_matches_filter(Filter *filter, char *res_path) { // include/exclude filter @@ -631,7 +683,6 @@ return 1; } -// TODO: remove: use dav_resource_map void res2map(DavResource *root, CxMap *map) { CxList *stack = cxLinkedListCreateSimple(CX_STORE_POINTERS); cxListInsert(stack, 0, root->children); @@ -651,6 +702,18 @@ cxListFree(stack); } +static CxHashKey resource_path_key(DavResource *res) { + CxHashKey key = { NULL, 0, 0 }; + if(res && res->path) { + cxstring res_path = cx_str(res->path); + if(res_path.length > 0 && res_path.ptr[res_path.length-1] == '/') { + res_path.length--; + } + key = cx_hash_key(res_path.ptr, res_path.length); + } + return key; +} + int cmd_pull(CmdArgs *a, DavBool incoming) { if(a->argc != 1) { fprintf(stderr, "Too %s arguments\n", a->argc < 1 ? "few" : "many"); @@ -715,7 +778,7 @@ DavBool locked = FALSE; DavResource *root = dav_resource_new(sn, "/"); root->iscollection = TRUE; - if((dir->lockpull || cmd_getoption(a, "lock")) && !cmd_getoption(a, "nolock")) { + if((dir->lockpush || cmd_getoption(a, "lock")) && !cmd_getoption(a, "nolock")) { if(dav_lock_t(root, dir->lock_timeout)) { log_resource_error(sn, "/"); dav_session_destroy(sn); @@ -863,7 +926,7 @@ // remove every server resource from dbres // all remaining elements are the resources that are removed // on the server - cxMapRemove(dbres, dav_resource_path_key(res)); + cxMapRemove(dbres, resource_path_key(res)); if(!dav_get_property_ns(res, DAV_NS, "split") && res->children) { cxListInsert(stack, 0, res->children); @@ -912,7 +975,7 @@ if(SYNC_HASHING(dir)) { // check for moved/copied files SYS_STAT s; - CxIterator mut_iter = cxListMutIterator(res_new); + CxIterator mut_iter = cxListIterator(res_new); cx_foreach(DavResource *, res, mut_iter) { if(dav_get_property_ns(res, DAV_PROPS_NS, "link")) { continue; @@ -1011,11 +1074,11 @@ break; } - LocalResource *local = cxMapGet(db->resources, dav_resource_path_key(res)); + LocalResource *local = cxMapGet(db->resources, resource_path_key(res)); if(local) { log_printf("update: %s\n", res->path); char *res_path = resource_local_path(res); - char *local_path = syncdir_create_local_path(dir, res->path); + char *local_path = create_local_path(dir, res->path); free(res_path); if(sync_store_metadata(dir, local_path, local, res)) { fprintf(stderr, "Metadata update failed: %s\n", res->path); @@ -1113,6 +1176,237 @@ return ret; } +RemoteChangeType resource_get_remote_change( + CmdArgs *a, + DavResource *res, + SyncDirectory *dir, + SyncDatabase *db) +{ + DavBool update_db = FALSE; + + char *etag = dav_get_string_property(res, "D:getetag"); + if(!etag) { + fprintf(stderr, "Error: resource %s has no etag\n", res->path); + return REMOTE_NO_CHANGE; + } + char *hash = sync_get_content_hash(res); + + DavBool issplit = dav_get_property(res, "idav:split") ? TRUE : FALSE; + if(issplit) { + util_remove_trailing_pathseparator(res->path); + } + DavBool iscollection = res->iscollection && !issplit; + + RemoteChangeType type = cmd_getoption(a, "conflict") ? + REMOTE_CHANGE_MODIFIED : REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; + + LocalResource *local = cxMapGet(db->resources, resource_path_key(res)); + char *local_path = create_local_path(dir, res->path); + + char *link = SYNC_SYMLINK(dir) ? + dav_get_string_property_ns(res, DAV_PROPS_NS, "link") : NULL; + + SYS_STAT s; + DavBool exists = 1; + if(sys_stat(local_path, &s)) { + if(errno != ENOENT) { + fprintf(stderr, "Cannot stat file: %s\n", local_path); + free(local_path); + return REMOTE_NO_CHANGE; + } + exists = 0; + } + + RemoteChangeType ret = REMOTE_NO_CHANGE; + if(iscollection) { + if(!exists) { + ret = REMOTE_CHANGE_MKDIR; + } else if(local && S_ISDIR(s.st_mode)) { + local->isdirectory = 1; // make sure isdirectory is set + } else { + // set change to REMOTE_CHANGE_MKDIR, which will fail later + ret = REMOTE_CHANGE_MKDIR; + } + } else if(local) { + DavBool nochange = FALSE; + if(SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) { + ret = REMOTE_CHANGE_LINK; + nochange = TRUE; + + if(local->link_target) { + LocalResource *local2 = local_resource_new(dir, db, local->path); + if(type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) { + ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; + } + local_resource_free(local2); + + if(!nullstrcmp(link, local->link_target)) { + ret = REMOTE_NO_CHANGE; + update_db = TRUE; + } + } + } else if(issplit && local->hash && hash) { + if(!strcmp(local->hash, hash)) { + // resource is already up-to-date on the client + nochange = TRUE; + } + } else if(local->etag) { + cxstring e = cx_str(etag); + if(cx_strprefix(e, CX_STR("W/"))) { + e = cx_strsubs(e, 2); + } + if(!strcmp(e.ptr, local->etag)) { + // resource is already up-to-date on the client + nochange = TRUE; + } + } + + if(!nochange) { + if(!(exists && s.st_mtime != local->last_modified)) { + type = REMOTE_CHANGE_MODIFIED; + } + ret = type; + } + } else if(link) { + // new file is a link + ret = REMOTE_CHANGE_LINK; + + if(exists && type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) { + // a file with the same name already exists + // if it is a link, compare the targets + LocalResource *local2 = local_resource_new(dir, db, res->path); + if(local2) { + if(local2->link_target) { + if(strcmp(link, local2->link_target)) { + ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; + } + } else { + ret = REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED; + } + + local_resource_free(local2); + } + } + + } else if(exists) { + ret = type; + } else { + ret = REMOTE_CHANGE_NEW; + } + + // if hashing is enabled we can compare the hash of the remote file + // with the local file to test if a file is really modified + char *update_hash = NULL; + if (!iscollection && + !link && + (ret == REMOTE_CHANGE_MODIFIED || ret == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) && + exists && + hash && + !dir->pull_skip_hashing) + { + // because rehashing a file is slow, there is a config element for + // disabling this (pull-skip-hashing) + char *local_hash = util_file_hash(local_path); + if(local_hash) { + if(!strcmp(hash, local_hash)) { + ret = REMOTE_NO_CHANGE; + update_db = TRUE; + update_hash = local_hash; + + // if local already exists, update the hash here + // because it is possible that there are metadata updates + // and in this case the db will updated later and needs + // the current hash + if(local) { + if(local->hash) { + free(local->hash); + } + local->hash = local_hash; + } + } else { + free(local_hash); + } + } + } + + // if a file is not modified, check if the metadata has changed + while(ret == REMOTE_NO_CHANGE && local) { + // check if tags have changed + if(dir->tagconfig) { + DavXmlNode *tagsprop = dav_get_property_ns(res, DAV_PROPS_NS, "tags"); + CxList *remote_tags = NULL; + if(tagsprop) { + remote_tags = parse_dav_xml_taglist(tagsprop); + } + char *remote_hash = create_tags_hash(remote_tags); + if(nullstrcmp(remote_hash, local->remote_tags_hash)) { + ret = REMOTE_CHANGE_METADATA; + } + if(remote_hash) { + free(remote_hash); + } + free_taglist(remote_tags); + + if(ret == REMOTE_CHANGE_METADATA) { + break; + } + } + + // check if extended attributes have changed + if((dir->metadata & FINFO_XATTR) == FINFO_XATTR) { + DavXmlNode *xattr = dav_get_property_ns(res, DAV_PROPS_NS, "xattributes"); + char *xattr_hash = get_xattr_hash(xattr); + if(nullstrcmp(xattr_hash, local->xattr_hash)) { + ret = REMOTE_CHANGE_METADATA; + break; + } + } + + // check if finfo has changed + DavXmlNode *finfo = dav_get_property_ns(res, DAV_PROPS_NS, "finfo"); + if((dir->metadata & FINFO_MODE) == FINFO_MODE) { + FileInfo f; + finfo_get_values(finfo, &f); + if(f.mode_set && f.mode != local->mode) { + ret = REMOTE_CHANGE_METADATA; + break; + } + } + + break; + } + + // if update_db is set, a file was modified on the server, but we already + // have the file content, but we need to update the db + if(ret == REMOTE_NO_CHANGE && update_db) { + if(!local) { + local = calloc(1, sizeof(LocalResource)); + local->path = strdup(res->path); + + cxMapPut(db->resources, cx_hash_key_str(local->path), local); + } + + // update local res + SYS_STAT statdata; + if(!sys_stat(local_path, &statdata)) { + sync_set_metadata_from_stat(local, &statdata); + } else { + fprintf(stderr, "stat failed for file: %s : %s", local_path, strerror(errno)); + } + local_resource_set_etag(local, etag); + if(!local->hash) { + local->hash = update_hash; + } // else: hash already updated + if(link) { + nullfree(local->link_target); + local->link_target = link; + } + } + + free(local_path); + return ret; +} + void sync_set_metadata_from_stat(LocalResource *local, SYS_STAT *s) { local->last_modified = s->st_mtime; local->mode = s->st_mode & 07777; @@ -1351,10 +1645,10 @@ char *local_path; if(link) { char *res_path = resource_local_path(res); - local_path = syncdir_create_local_path(dir, res_path); + local_path = create_local_path(dir, res_path); free(res_path); } else { - local_path = syncdir_create_local_path(dir, path); + local_path = create_local_path(dir, path); } char *etag = dav_get_string_property(res, "D:getetag"); @@ -1539,7 +1833,7 @@ // TODO: use resource_local_path return value (necessary for creating links on windows) //char *res_path = resource_local_path(res); - char *local_path = syncdir_create_local_path(dir, res->path); + char *local_path = create_local_path(dir, res->path); //free(res_path); log_printf("get: %s\n", res->path); @@ -1598,7 +1892,7 @@ } int sync_remove_local_resource(SyncDirectory *dir, LocalResource *res) { - char *local_path = syncdir_create_local_path(dir, res->path); + char *local_path = create_local_path(dir, res->path); SYS_STAT s; if(sys_stat(local_path, &s)) { free(local_path); @@ -1630,7 +1924,7 @@ int sync_remove_local_directory(SyncDirectory *dir, LocalResource *res) { int ret = 0; - char *local_path = syncdir_create_local_path(dir, res->path); + char *local_path = create_local_path(dir, res->path); log_printf("delete: %s\n", res->path); if(rmdir(local_path)) { @@ -1649,7 +1943,7 @@ } void rename_conflict_file(SyncDirectory *dir, SyncDatabase *db, char *path, DavBool copy) { - char *local_path = syncdir_create_local_path(dir, path); + char *local_path = create_local_path(dir, path); char *parent = util_parent_path(local_path); renamefunc fn = copy ? copy_file : sys_rename; @@ -1970,7 +2264,7 @@ if(SYNC_STORE_HASH(dir)) { // calculate hashes of all new files and check if a file // was moved or is a copy - CxIterator mut_iter = cxListMutIterator(ls_new); + CxIterator mut_iter = cxListIterator(ls_new); cx_foreach(LocalResource *, local, mut_iter) { if(local->isdirectory || local->link_target) { continue; @@ -2136,9 +2430,9 @@ if(dav_exists(res)) { log_printf("conflict: %s\n", local->path); local->last_modified = 0; - free(local->etag); + nullfree(local->etag); local->etag = NULL; - free(local->hash); + nullfree(local->hash); local->hash = NULL; local->skipped = TRUE; sync_conflict++; @@ -2214,9 +2508,9 @@ } else if(cdt && changed) { log_printf("conflict: %s\n", local_res->path); local_res->last_modified = 0; - free(local_res->etag); + nullfree(local_res->etag); local_res->etag = NULL; - free(local_res->hash); + nullfree(local_res->hash); local_res->hash = NULL; local_res->skipped = TRUE; sync_conflict++; @@ -2455,7 +2749,7 @@ } } - char *file_path = syncdir_create_local_path(dir, resource->path); + char *file_path = create_local_path(dir, resource->path); SYS_STAT s; if(sys_stat(file_path, &s)) { if(errno == ENOENT) { @@ -2568,7 +2862,7 @@ // download the resource if(!sync_shutdown) { if(resource->isdirectory) { - char *local_path = syncdir_create_local_path(dir, res->path); + char *local_path = create_local_path(dir, res->path); if(sys_mkdir(local_path) && errno != EEXIST) { log_error( "Cannot create directory %s: %s", @@ -2583,7 +2877,7 @@ LocalResource *lr = cxMapGet(db->resources, cx_hash_key_str(res->path)); if(lr) { lr->last_modified = 0; - free(lr->hash); + nullfree(lr->hash); lr->hash = NULL; } // else should not happen } @@ -2756,7 +3050,7 @@ char *p = cxListAt(stack, 0); cxListRemove(stack, 0); - char *local_path = syncdir_create_local_path(dir, p); + char *local_path = create_local_path(dir, p); SYS_DIR local_dir = sys_opendir(local_path); if(!local_dir) { @@ -2796,7 +3090,7 @@ LocalResource* local_resource_new(SyncDirectory *dir, SyncDatabase *db, char *path) { - char *file_path = syncdir_create_local_path(dir, path); + char *file_path = create_local_path(dir, path); SYS_STAT s; if(sys_lstat(file_path, &s)) { log_error("Cannot stat file %s: %s\n", file_path, strerror(errno)); @@ -3017,7 +3311,7 @@ // check if xattr have changed if((dir->metadata & FINFO_XATTR) == FINFO_XATTR) { - char *path = syncdir_create_local_path(dir, local_resource_path(db_res)); + char *path = create_local_path(dir, local_resource_path(db_res)); XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir); // test if xattr are added, removed or changed if((db_res->xattr_hash && !xattr) || @@ -3152,7 +3446,7 @@ int local_resource_load_metadata(SyncDirectory *dir, LocalResource *res) { // currently only xattr needed if((dir->metadata & FINFO_XATTR) == FINFO_XATTR) { - char *path = syncdir_create_local_path(dir, local_resource_path(res)); + char *path = create_local_path(dir, local_resource_path(res)); XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir); res->xattr = xattr; free(path); @@ -3485,7 +3779,7 @@ } if(!store_tags) { - free(local->remote_tags_hash); + nullfree(local->remote_tags_hash); local->remote_tags_hash = remote_hash; return 0; } @@ -3576,7 +3870,7 @@ CxBuffer *buf = NULL; if(dir->tagconfig->store == TAG_STORE_XATTR) { ssize_t tag_length = 0; - char *local_path = syncdir_create_local_path(dir, local_resource_path(res)); + char *local_path = create_local_path(dir, local_resource_path(res)); char* tag_data = xattr_get( local_path, dir->tagconfig->xattr_name, @@ -4104,7 +4398,7 @@ LocalResource *local, int *counter) { - char *local_path = syncdir_create_local_path(dir, local_resource_path(local)); + char *local_path = create_local_path(dir, local_resource_path(local)); SYS_STAT s; if(sys_stat(local_path, &s)) { @@ -4275,7 +4569,7 @@ DavBool copy, int *counter) { - char *local_path = syncdir_create_local_path(dir, local->path); + char *local_path = create_local_path(dir, local->path); SYS_STAT s; if(sys_stat(local_path, &s)) { @@ -4458,8 +4752,8 @@ free_taglist(tags); tags = new_tags; - free(tags_hash); - free(new_remote_hash); + nullfree(tags_hash); + nullfree(new_remote_hash); tags_hash = create_tags_hash(tags); new_remote_hash = nullstrdup(tags_hash); @@ -4467,7 +4761,7 @@ } } } - free(remote_hash); + nullfree(remote_hash); if(dir->tagconfig->local_format == TAG_FORMAT_CSV) { // csv tag lists don't have colors, so we have to add @@ -4552,7 +4846,7 @@ CxMapIterator i = cxMapIteratorValues(db->conflict); cx_foreach(LocalResource *, res, i) { - char *path = syncdir_create_local_path(dir, res->path); + char *path = create_local_path(dir, res->path); SYS_STAT s; if(sys_stat(path, &s)) { if(errno == ENOENT) { @@ -4675,7 +4969,7 @@ CxMapIterator i = cxMapIteratorValues(db->conflict); cx_foreach(LocalResource*, res, i) { log_printf("delete: %s\n", res->path); - char *path = syncdir_create_local_path(dir, res->path); + char *path = create_local_path(dir, res->path); if(sys_unlink(path)) { if(errno != ENOENT) { log_error("unlink: %s", strerror(errno));
--- a/dav/sync.h Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/sync.h Sat Nov 08 23:06:11 2025 +0100 @@ -36,7 +36,7 @@ #include "scfg.h" #include "config.h" -#include "optparser.h" +#include "sopt.h" #include "system.h" #include "version.h" @@ -92,9 +92,7 @@ RemoteChangeType type; } RemoteChange; -void dav_sync_print_usage(char *cmd); - -int dav_sync_main(int argc, char **argv); +void print_usage(char *cmd); #ifndef _WIN32 pthread_t start_sighandler(pthread_mutex_t *mutex) ;
--- a/dav/sync_main.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "sync_main.h" - -#include <stdlib.h> - - -#include "system.h" - -#ifndef _WIN32 -// unix includes -#include <unistd.h> -#include <pthread.h> -#else -//windows includes - -#endif - -#ifdef _WIN32 -static char* wchar2utf8(const wchar_t *wstr, size_t wlen) { - size_t maxlen = wlen * 4; - char *ret = malloc(maxlen + 1); - int ret_len = WideCharToMultiByte( - CP_UTF8, - 0, - wstr, - wlen, - ret, - maxlen, - NULL, - NULL); - ret[ret_len] = 0; - return ret; -} - -int wmain(int argc, wchar_t **argv) { - char **argv_utf8 = calloc(argc, sizeof(char*)); - for(int i=0;i<argc;i++) { - argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i])); - } - - int ret = dav_sync_main(argc, argv_utf8); - - for(int i=0;i<argc;i++) { - free(argv_utf8[i]); - } - free(argv_utf8); - - return ret; -} -#else -int main(int argc, char **argv) { - return dav_sync_main(argc, argv); -} -#endif
--- a/dav/sync_main.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 SYNC_MAIN_H -#define SYNC_MAIN_H - -#include "sync.h" - -#ifdef __cplusplus -extern "C" { -#endif - - - - -#ifdef __cplusplus -} -#endif - -#endif /* SYNC_MAIN_H */ -
--- a/dav/syncdir.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "syncdir.h" - -#include <string.h> -#include <libidav/utils.h> - -char* syncdir_create_local_path(SyncDirectory *dir, const char *path) { - char *local_path = util_concat_path(dir->path, path); - size_t local_path_len = strlen(local_path); - if(local_path[local_path_len-1] == '/') { - local_path[local_path_len-1] = '\0'; - } - return local_path; -}
--- a/dav/syncdir.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 SYNCDIR_H -#define SYNCDIR_H - -#include "sync.h" - -#ifdef __cplusplus -extern "C" { -#endif - -char* syncdir_create_local_path(SyncDirectory *dir, const char *path); - - -#ifdef __cplusplus -} -#endif - -#endif /* SYNCDIR_H */ -
--- a/dav/xattrtool.c Tue Oct 14 21:02:26 2025 +0200 +++ b/dav/xattrtool.c Sat Nov 08 23:06:11 2025 +0100 @@ -46,7 +46,7 @@ int attrtool_set(int argc, char **argv); int attrtool_rm(int argc, char **argv); -void dav_sync_print_usage(char *cmd) { +void print_usage(char *cmd) { fprintf(stderr, "usage %s:\n", cmd); fprintf(stderr, " list <file>\n"); fprintf(stderr, " listvalues <file>\n"); @@ -97,7 +97,7 @@ int xattr_tool_main(int argc, char **argv) { if(argc < 3) { - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); return 1; } @@ -113,7 +113,7 @@ return attrtool_rm(argc, argv); } else { fprintf(stderr, "Unknown command\n"); - dav_sync_print_usage(argv[0]); + print_usage(argv[0]); } return 1; @@ -157,7 +157,7 @@ int attrtool_get(int argc, char **argv, int raw) { if(argc < 4) { fprintf(stderr, "Too few arguments\n"); - dav_sync_print_usage(argv[1]); + print_usage(argv[1]); return 1; } @@ -185,7 +185,7 @@ int attrtool_set(int argc, char **argv) { if(argc < 4) { fprintf(stderr, "Too few arguments\n"); - dav_sync_print_usage(argv[1]); + print_usage(argv[1]); return 1; } @@ -206,7 +206,7 @@ int attrtool_rm(int argc, char **argv) { if(argc < 4) { fprintf(stderr, "Too few arguments\n"); - dav_sync_print_usage(argv[1]); + print_usage(argv[1]); return 1; }
--- a/libidav/pwdstore.c Tue Oct 14 21:02:26 2025 +0200 +++ b/libidav/pwdstore.c Sat Nov 08 23:06:11 2025 +0100 @@ -34,8 +34,9 @@ #include <stdlib.h> #include <string.h> +#include <cx/streams.h> + #include <cx/hash_map.h> -#include <cx/streams.h> #ifdef _WIN32 #include <winsock.h> @@ -263,7 +264,7 @@ } static void remove_list_entries(PwdStore *s, const char *id) { - CxIterator i = cxListMutIterator(s->locations); + CxIterator i = cxListIterator(s->locations); cx_foreach(PwdIndexEntry*, ie, i) { if(!strcmp(ie->id, id)) { cxIteratorFlagRemoval(i); @@ -271,7 +272,7 @@ break; } } - i = cxListMutIterator(s->noloc); + i = cxListIterator(s->noloc); cx_foreach(PwdIndexEntry*, ie, i) { if(!strcmp(ie->id, id)) { cxIteratorFlagRemoval(i);
--- a/libidav/resource.c Tue Oct 14 21:02:26 2025 +0200 +++ b/libidav/resource.c Sat Nov 08 23:06:11 2025 +0100 @@ -280,47 +280,6 @@ return resource->href; } -CxHashKey dav_resource_path_key(DavResource *res) { - CxHashKey key = { NULL, 0, 0 }; - if(res && res->path) { - cxstring res_path = cx_str(res->path); - if(res_path.length > 0 && res_path.ptr[res_path.length-1] == '/') { - res_path.length--; - } - key = cx_hash_key(res_path.ptr, res_path.length); - } - return key; -} - -CxTreeIterator dav_resource_iterator(DavResource *res) { - return cx_tree_iterator(res, FALSE, offsetof(DavResource, children), offsetof(DavResource, next)); -} - -CxTreeVisitor dav_resource_visitor(DavResource *res) { - return cx_tree_visitor(res, offsetof(DavResource, children), offsetof(DavResource, next)); -} - -CxMap* dav_resource_map(DavResource *res) { - CxMap *map = cxHashMapCreate(res->session->mp->allocator, CX_STORE_POINTERS, 16); - if(!map) { - return NULL; - } - CxTreeIterator i = dav_resource_iterator(res); - int error = 0; - cx_foreach(DavResource *, res, i) { - if(cxMapPut(map, res->path, res)) { - error = 1; - } - } - if(!error) { - cxMapRehash(map); - return map; - } else { - cxMapFree(map); - return NULL; - } -} - void resource_add_prop(DavResource *res, const char *ns, const char *name, DavXmlNode *val) { DavSession *sn = res->session; @@ -1639,7 +1598,7 @@ property->value = n->children ? dav_convert_xml(sn, n->children) : NULL; cxmutstr propkey = dav_property_key(property->ns->name, property->name); - cxMapPut(map, cx_hash_key_cxstr(propkey), property); + cxMapPut(map, propkey, property); cx_strfree(&propkey); } n = n->next;
--- a/libidav/webdav.c Tue Oct 14 21:02:26 2025 +0200 +++ b/libidav/webdav.c Sat Nov 08 23:06:11 2025 +0100 @@ -36,7 +36,6 @@ #include "session.h" #include "methods.h" #include <cx/buffer.h> -#include <cx/printf.h> #include <cx/linked_list.h> #include <cx/hash_map.h> #include <cx/compare.h>
--- a/libidav/webdav.h Tue Oct 14 21:02:26 2025 +0200 +++ b/libidav/webdav.h Sat Nov 08 23:06:11 2025 +0100 @@ -36,7 +36,6 @@ #include <cx/linked_list.h> #include <cx/string.h> #include <cx/buffer.h> -#include <cx/tree.h> #include <curl/curl.h> #include <libxml/tree.h> @@ -338,12 +337,6 @@ char* dav_resource_get_href(DavResource *resource); -CxHashKey dav_resource_path_key(DavResource *res); - -CxTreeIterator dav_resource_iterator(DavResource *res); -CxTreeVisitor dav_resource_visitor(DavResource *res); -CxMap* dav_resource_map(DavResource *res); - DavResource* dav_create_child(DavResource *parent, char *name); int dav_delete(DavResource *res); int dav_create(DavResource *res);
--- a/test/Makefile Tue Oct 14 21:02:26 2025 +0200 +++ b/test/Makefile Sat Nov 08 23:06:11 2025 +0100 @@ -28,16 +28,11 @@ include ../config.mk -include ../dav/obj.mk - TEST_SRC = main.c -TEST_SRC += helper.c +TEST_SRC += test.c TEST_SRC += base64.c TEST_SRC += crypto.c TEST_SRC += utils.c -TEST_SRC += test_resource.c - -TEST_SRC += test_finfo.c TEST_OBJ = $(TEST_SRC:%.c=../build/test/%$(OBJ_EXT)) @@ -46,8 +41,8 @@ all: $(TEST_TARGET) -$(TEST_TARGET): $(TEST_OBJ) $(DAV_OBJ) $(SYNC_OBJ) $(COMMON_OBJ) ../build/lib/libidav$(LIB_EXT) - $(CC) -o $(TEST_TARGET) $(TEST_OBJ) $(DAV_OBJ) $(SYNC_OBJ) $(COMMON_OBJ) \ +$(TEST_TARGET): $(TEST_OBJ) ../build/lib/libidav$(LIB_EXT) + $(CC) -o $(TEST_TARGET) $(TEST_OBJ) \ ../build/lib/libidav$(LIB_EXT) ../build/lib/libucx$(LIB_EXT) \ $(LDFLAGS) $(DAV_LDFLAGS)
--- a/test/base64.c Tue Oct 14 21:02:26 2025 +0200 +++ b/test/base64.c Sat Nov 08 23:06:11 2025 +0100 @@ -35,18 +35,20 @@ #include <cx/string.h> #include <libidav/utils.h> -CX_TEST(test_util_base64decode) { +UCX_TEST(test_util_base64decode) { char *s1 = util_base64decode("YWJj"); char *s2 = util_base64decode("aGVsbG8gd29ybGQ="); char *s3 = util_base64decode("MA=="); char *s4 = util_base64decode("MHh4MQ=="); - CX_TEST_DO { - CX_TEST_ASSERT(!strcmp(s1, "abc")); - CX_TEST_ASSERT(!strcmp(s2, "hello world")); - CX_TEST_ASSERT(!strcmp(s3, "0")); - CX_TEST_ASSERT(!strcmp(s4, "0xx1")); - } + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(!strcmp(s1, "abc"), "s1 wrong"); + UCX_TEST_ASSERT(!strcmp(s2, "hello world"), "s2 wrong"); + UCX_TEST_ASSERT(!strcmp(s3, "0"), "s3 wrong"); + UCX_TEST_ASSERT(!strcmp(s4, "0xx1"), "s4 wrong"); + + UCX_TEST_END; free(s1); free(s2); @@ -54,7 +56,7 @@ free(s4); } -CX_TEST(test_util_base64decode_len) { +UCX_TEST(test_util_base64decode_len) { int len1, len2, len3, len4, len5, len6; char *s1 = util_base64decode_len("aGVsbG8=", &len1); // 5 char *s2 = util_base64decode_len("MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5", &len2); // 30 @@ -63,25 +65,27 @@ char *s5 = util_base64decode_len("YWJjLS0tLS0tM3g=", &len5); // 11 char *s6 = util_base64decode_len("YWJjZGVmZy4uLjs7MGhlbGxv", &len6); // 18 - CX_TEST_DO { - CX_TEST_ASSERT(!strcmp(s1, "hello")); - CX_TEST_ASSERT(len1 == 5); - - CX_TEST_ASSERT(!strcmp(s2, "012345678901234567890123456789")); - CX_TEST_ASSERT(len2 == 30); - - CX_TEST_ASSERT(!strcmp(s3, ".")); - CX_TEST_ASSERT(len3 == 1); - - CX_TEST_ASSERT(!strcmp(s4, "lol")); - CX_TEST_ASSERT(len4 == 3); - - CX_TEST_ASSERT(!strcmp(s5, "abc------3x")); - CX_TEST_ASSERT(len5 == 11); - - CX_TEST_ASSERT(!strcmp(s6, "abcdefg...;;0hello")); - CX_TEST_ASSERT(len6 == 18); - } + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(!strcmp(s1, "hello"), "s1 wrong"); + UCX_TEST_ASSERT(len1 == 5, "len1 wrong"); + + UCX_TEST_ASSERT(!strcmp(s2, "012345678901234567890123456789"), "s2 wrong"); + UCX_TEST_ASSERT(len2 == 30, "len2 wrong"); + + UCX_TEST_ASSERT(!strcmp(s3, "."), "s3 wrong"); + UCX_TEST_ASSERT(len3 == 1, "len3 wrong"); + + UCX_TEST_ASSERT(!strcmp(s4, "lol"), "s4 wrong"); + UCX_TEST_ASSERT(len4 == 3, "len4 wrong"); + + UCX_TEST_ASSERT(!strcmp(s5, "abc------3x"), "s5 wrong"); + UCX_TEST_ASSERT(len5 == 11, "len5 wrong"); + + UCX_TEST_ASSERT(!strcmp(s6, "abcdefg...;;0hello"), "s6 failed"); + UCX_TEST_ASSERT(len6 == 18, "len6 wrong"); + + UCX_TEST_END; free(s1); free(s2); @@ -91,7 +95,7 @@ free(s6); } -CX_TEST(test_util_base64encode) { +UCX_TEST(test_util_base64encode) { char *str1 = "hello world"; char *str2 = "test string"; char *str3 = "01234567890123456789012345678901234567890123456789"; @@ -123,19 +127,21 @@ char *b9 = util_base64encode(str9, strlen(str9)); char *b10 = util_base64encode(str10, strlen(str10)); - CX_TEST_DO { - CX_TEST_ASSERT(!strcmp(b1, "aGVsbG8gd29ybGQ=")); - CX_TEST_ASSERT(!strcmp(b2, "dGVzdCBzdHJpbmc=")); - CX_TEST_ASSERT(!strcmp(b3, "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODk=")); - CX_TEST_ASSERT(!strcmp(b4, "AAwDMgJuPwE=")); - - CX_TEST_ASSERT(!strcmp(b5, "YQ==")); - CX_TEST_ASSERT(!strcmp(b6, "YWI=")); - CX_TEST_ASSERT(!strcmp(b7, "YWJj")); - CX_TEST_ASSERT(!strcmp(b8, "YWJjZA==")); - CX_TEST_ASSERT(!strcmp(b9, "YWJjZGU=")); - CX_TEST_ASSERT(!strcmp(b10, "YWJjZGVm")); - } + UCX_TEST_BEGIN; + + UCX_TEST_ASSERT(!strcmp(b1, "aGVsbG8gd29ybGQ="), "b1 failed"); + UCX_TEST_ASSERT(!strcmp(b2, "dGVzdCBzdHJpbmc="), "b2 failed"); + UCX_TEST_ASSERT(!strcmp(b3, "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODk="), "b3 failed"); + UCX_TEST_ASSERT(!strcmp(b4, "AAwDMgJuPwE="), "b4 failed"); + + UCX_TEST_ASSERT(!strcmp(b5, "YQ=="), "b5 failed"); + UCX_TEST_ASSERT(!strcmp(b6, "YWI="), "b6 failed"); + UCX_TEST_ASSERT(!strcmp(b7, "YWJj"), "b7 failed"); + UCX_TEST_ASSERT(!strcmp(b8, "YWJjZA=="), "b8 failed"); + UCX_TEST_ASSERT(!strcmp(b9, "YWJjZGU="), "b9 failed"); + UCX_TEST_ASSERT(!strcmp(b10, "YWJjZGVm"), "b10 failed"); + + UCX_TEST_END; free(b1); free(b2);
--- a/test/base64.h Tue Oct 14 21:02:26 2025 +0200 +++ b/test/base64.h Sat Nov 08 23:06:11 2025 +0100 @@ -29,15 +29,15 @@ #ifndef BASE64_H #define BASE64_H -#include <cx/test.h> +#include "test.h" #ifdef __cplusplus extern "C" { #endif -CX_TEST(test_util_base64decode); -CX_TEST(test_util_base64decode_len); -CX_TEST(test_util_base64encode); +UCX_TEST(test_util_base64decode); +UCX_TEST(test_util_base64decode_len); +UCX_TEST(test_util_base64encode); #ifdef __cplusplus
--- a/test/crypto.c Tue Oct 14 21:02:26 2025 +0200 +++ b/test/crypto.c Sat Nov 08 23:06:11 2025 +0100 @@ -191,138 +191,146 @@ } } -CX_TEST(test_util_decrypt_str_k) { +UCX_TEST(test_util_decrypt_str_k) { create_keys(); DavContext *ctx = dav_context_new(); DavSession *sn = dav_session_new(ctx, "http://localhost/"); - CX_TEST_DO { - for(int k=0;k<2;k++) { - for(int i=0;i<20;i++) { - char *c1; - char *c2; - if(k == 0) { - c1 = cryptkey0aes128strings[i]; - c2 = cryptkey0aes256strings[i]; - } else { - c1 = cryptkey1aes128strings[i]; - c2 = cryptkey1aes256strings[i]; - } - - char *s128 = util_decrypt_str_k(sn, c1, &keys128[k]); - char *s256 = util_decrypt_str_k(sn, c2, &keys256[k]); - - CX_TEST_ASSERT(!strcmp(s128, strings[i])); - CX_TEST_ASSERT(!strcmp(s256, strings[i])); + UCX_TEST_BEGIN; + + for(int k=0;k<2;k++) { + for(int i=0;i<20;i++) { + char *c1; + char *c2; + if(k == 0) { + c1 = cryptkey0aes128strings[i]; + c2 = cryptkey0aes256strings[i]; + } else { + c1 = cryptkey1aes128strings[i]; + c2 = cryptkey1aes256strings[i]; } + + char *s128 = util_decrypt_str_k(sn, c1, &keys128[k]); + char *s256 = util_decrypt_str_k(sn, c2, &keys256[k]); + + UCX_TEST_ASSERT(!strcmp(s128, strings[i]), "s128 wrong"); + UCX_TEST_ASSERT(!strcmp(s256, strings[i]), "s256 wrong"); } } + UCX_TEST_END; + dav_context_destroy(ctx); } -CX_TEST(test_util_encrypt_str_k) { +UCX_TEST(test_util_encrypt_str_k) { DavContext *ctx = dav_context_new(); DavSession *sn = dav_session_new(ctx, "http://localhost/"); - CX_TEST_DO { - for(int k=0;k<16;k++) { - for(int i=0;i<20;i++) { - char *aes128 = util_encrypt_str_k(sn, strings[i], &keys128[k]); - char *aes256 = util_encrypt_str_k(sn, strings[i], &keys256[k]); - - char *dec1 = util_decrypt_str_k(sn, aes128, &keys128[k]); - char *dec2 = util_decrypt_str_k(sn, aes256, &keys256[k]); - - CX_TEST_ASSERT(!strcmp(dec1, strings[i])); - CX_TEST_ASSERT(!strcmp(dec2, strings[i])); - } + UCX_TEST_BEGIN; + + for(int k=0;k<16;k++) { + for(int i=0;i<20;i++) { + char *aes128 = util_encrypt_str_k(sn, strings[i], &keys128[k]); + char *aes256 = util_encrypt_str_k(sn, strings[i], &keys256[k]); + + char *dec1 = util_decrypt_str_k(sn, aes128, &keys128[k]); + char *dec2 = util_decrypt_str_k(sn, aes256, &keys256[k]); + + UCX_TEST_ASSERT(!strcmp(dec1, strings[i]), "aes128 encrypt failed"); + UCX_TEST_ASSERT(!strcmp(dec2, strings[i]), "aes256 encrypt failed"); } } + + UCX_TEST_END; } -CX_TEST(test_crypto_buffer) { - CX_TEST_DO { - for(int i=0;i<32;i++) { - DavKey *key = i < 16 ? &keys256[i] : &keys128[i%16]; - - for(int j=0;j<20;j++) { - CxBuffer *content = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - cxBufferPutString(content, strings[j]); - content->pos = 0; - - CxBuffer *enc = aes_encrypt_buffer(content, key); - CX_TEST_ASSERT(enc->size >= content->size + 16); - - char *base64 = util_base64encode(enc->space, enc->size); - size_t plainlen = 0; - char *plain = aes_decrypt(base64, &plainlen, key); - - CX_TEST_ASSERT(plain); - CX_TEST_ASSERT(plainlen == content->size); - CX_TEST_ASSERT(!memcmp(plain, content->space, plainlen)); - - CxBuffer *dec = aes_decrypt_buffer(enc, key); - CX_TEST_ASSERT(dec->size == content->size); - - CX_TEST_ASSERT(!memcmp(content->space, dec->space, dec->size)); - - cxBufferFree(content); - cxBufferFree(enc); - cxBufferFree(dec); - free(base64); - free(plain); - } +UCX_TEST(test_crypto_buffer) { + UCX_TEST_BEGIN; + + for(int i=0;i<32;i++) { + DavKey *key = i < 16 ? &keys256[i] : &keys128[i%16]; + + for(int j=0;j<20;j++) { + CxBuffer *content = cxBufferCreate(NULL, 256, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cxBufferPutString(content, strings[j]); + content->pos = 0; + + CxBuffer *enc = aes_encrypt_buffer(content, key); + UCX_TEST_ASSERT(enc->size >= content->size + 16, "aes_encrypt_buffer failed"); + + char *base64 = util_base64encode(enc->space, enc->size); + size_t plainlen = 0; + char *plain = aes_decrypt(base64, &plainlen, key); + + UCX_TEST_ASSERT(plain, "aes_decrypt failed"); + UCX_TEST_ASSERT(plainlen == content->size, "aes_decrypt: wrong length"); + UCX_TEST_ASSERT(!memcmp(plain, content->space, plainlen), "aes_decrypt: wrong content"); + + CxBuffer *dec = aes_decrypt_buffer(enc, key); + UCX_TEST_ASSERT(dec->size == content->size, "aes_decrypt_buffer failed"); + + UCX_TEST_ASSERT(!memcmp(content->space, dec->space, dec->size), "decrypted buffer has wrong content"); + + cxBufferFree(content); + cxBufferFree(enc); + cxBufferFree(dec); + free(base64); + free(plain); } } + + UCX_TEST_END; } -CX_TEST(test_crypto_stream) { +UCX_TEST(test_crypto_stream) { CxBuffer *data = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); CxBuffer *cbuf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); CxBuffer *pbuf = cxBufferCreate(NULL, 1024, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - CX_TEST_DO { - for(int i=0;i<32;i++) { - DavKey *key = i < 16 ? &keys256[i] : &keys128[i%16]; - for(int j=0;j<20;j++) { - data->pos = 0; - data->size = 0; - size_t slen = strlen(strings[j]); - cxBufferWrite(strings[j], 1, slen, data); - cxBufferSeek(data, 0, SEEK_SET); + UCX_TEST_BEGIN; + + for(int i=0;i<32;i++) { + DavKey *key = i < 16 ? &keys256[i] : &keys128[i%16]; + for(int j=0;j<20;j++) { + data->pos = 0; + data->size = 0; + size_t slen = strlen(strings[j]); + cxBufferWrite(strings[j], 1, slen, data); + cxBufferSeek(data, 0, SEEK_SET); - cbuf->pos = 0; - cbuf->size = 0; - pbuf->pos = 0; - pbuf->size = 0; + cbuf->pos = 0; + cbuf->size = 0; + pbuf->pos = 0; + pbuf->size = 0; - AESEncrypter *enc = aes_encrypter_new(key, data, (dav_read_func)cxBufferRead, NULL); - char buf[1024]; - size_t r = 0; - while((r = aes_read(buf, 1, 1024, enc)) != 0) { - cxBufferWrite(buf, 1, r, cbuf); - } - aes_encrypter_close(enc); + AESEncrypter *enc = aes_encrypter_new(key, data, (dav_read_func)cxBufferRead, NULL); + char buf[1024]; + size_t r = 0; + while((r = aes_read(buf, 1, 1024, enc)) != 0) { + cxBufferWrite(buf, 1, r, cbuf); + } + aes_encrypter_close(enc); - AESDecrypter *dec = aes_decrypter_new(key, pbuf, (dav_write_func)cxBufferWrite); - aes_write(cbuf->space, 1, cbuf->pos, dec); - aes_decrypter_shutdown(dec); - aes_decrypter_close(dec); - - CX_TEST_ASSERT(slen == pbuf->pos); - CX_TEST_ASSERT(!memcmp(strings[j], pbuf->space, slen)); + AESDecrypter *dec = aes_decrypter_new(key, pbuf, (dav_write_func)cxBufferWrite); + aes_write(cbuf->space, 1, cbuf->pos, dec); + aes_decrypter_shutdown(dec); + aes_decrypter_close(dec); - data->pos = 0; - CxBuffer *enc2 = aes_encrypt_buffer(data, key); - CxBuffer *dec2 = aes_decrypt_buffer(enc2, key); - - CX_TEST_ASSERT(dec2->size == data->size); - CX_TEST_ASSERT(!memcmp(strings[j], dec2->space, dec2->size)); - } + UCX_TEST_ASSERT(slen == pbuf->pos, "wrong length after enc-dec"); + UCX_TEST_ASSERT(!memcmp(strings[j], pbuf->space, slen), "wrong content after enc-dec"); + + data->pos = 0; + CxBuffer *enc2 = aes_encrypt_buffer(data, key); + CxBuffer *dec2 = aes_decrypt_buffer(enc2, key); + + UCX_TEST_ASSERT(dec2->size == data->size, "dec2 has wrong size"); + UCX_TEST_ASSERT(!memcmp(strings[j], dec2->space, dec2->size), "dec2 has wrong content"); } } + + UCX_TEST_END; } static char *pws[] = { @@ -409,50 +417,51 @@ }; -CX_TEST(test_dav_pw2key) { +UCX_TEST(test_dav_pw2key) { - CX_TEST_DO { + + UCX_TEST_BEGIN; - for(int p=0;p<4;p++) { - for(int s=0;s<4;s++) { - DavKey *keys[4]; - keys[0] = dav_pw2key( - pws[p], - salt[s], - saltlen[s], - DAV_PWFUNC_PBKDF2_SHA256, - DAV_KEY_AES128); - keys[1] = dav_pw2key( - pws[p], - salt[s], - saltlen[s], - DAV_PWFUNC_PBKDF2_SHA256, - DAV_KEY_AES256); - keys[2] = dav_pw2key( - pws[p], - salt[s], - saltlen[s], - DAV_PWFUNC_PBKDF2_SHA512, - DAV_KEY_AES128); - keys[3] = dav_pw2key( - pws[p], - salt[s], - saltlen[s], - DAV_PWFUNC_PBKDF2_SHA512, - DAV_KEY_AES256); - - for(int i=0;i<4;i++) { - DavKey *key = keys[i]; - - int index = 16*p + 4*s + i; - int keylen = index % 2 == 0 ? 16 : 32; - - CX_TEST_ASSERT(key); - CX_TEST_ASSERT(keylen == key->length); - CX_TEST_ASSERT(!memcmp(key->data, pwgenkeys[index], keylen)); - } + for(int p=0;p<4;p++) { + for(int s=0;s<4;s++) { + DavKey *keys[4]; + keys[0] = dav_pw2key( + pws[p], + salt[s], + saltlen[s], + DAV_PWFUNC_PBKDF2_SHA256, + DAV_KEY_AES128); + keys[1] = dav_pw2key( + pws[p], + salt[s], + saltlen[s], + DAV_PWFUNC_PBKDF2_SHA256, + DAV_KEY_AES256); + keys[2] = dav_pw2key( + pws[p], + salt[s], + saltlen[s], + DAV_PWFUNC_PBKDF2_SHA512, + DAV_KEY_AES128); + keys[3] = dav_pw2key( + pws[p], + salt[s], + saltlen[s], + DAV_PWFUNC_PBKDF2_SHA512, + DAV_KEY_AES256); + + for(int i=0;i<4;i++) { + DavKey *key = keys[i]; + + int index = 16*p + 4*s + i; + int keylen = index % 2 == 0 ? 16 : 32; + + UCX_TEST_ASSERT(key, "no key"); + UCX_TEST_ASSERT(keylen == key->length, "wrong key length"); + UCX_TEST_ASSERT(!memcmp(key->data, pwgenkeys[index], keylen), "wrong key data"); } } + } - } + UCX_TEST_END; }
--- a/test/crypto.h Tue Oct 14 21:02:26 2025 +0200 +++ b/test/crypto.h Sat Nov 08 23:06:11 2025 +0100 @@ -29,17 +29,17 @@ #ifndef CRYPTO_H #define CRYPTO_H -#include <cx/test.h> +#include "test.h" #ifdef __cplusplus extern "C" { #endif -CX_TEST(test_util_decrypt_str_k); -CX_TEST(test_util_encrypt_str_k); -CX_TEST(test_crypto_buffer); -CX_TEST(test_crypto_stream); -CX_TEST(test_dav_pw2key); +UCX_TEST(test_util_decrypt_str_k); +UCX_TEST(test_util_encrypt_str_k); +UCX_TEST(test_crypto_buffer); +UCX_TEST(test_crypto_stream); +UCX_TEST(test_dav_pw2key); #ifdef __cplusplus
--- a/test/helper.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "helper.h" - - -DavSession* test_session(DavContext *ctx) { - return dav_session_new(ctx, "http://localhost:8182/webdav/"); -}
--- a/test/helper.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 TEST_HELPER_H -#define TEST_HELPER_H - -#include <libidav/webdav.h> - -#ifdef __cplusplus -extern "C" { -#endif - -DavSession* test_session(DavContext *ctx); - - -#ifdef __cplusplus -} -#endif - -#endif /* TEST_HELPER_H */ -
--- a/test/main.c Tue Oct 14 21:02:26 2025 +0200 +++ b/test/main.c Sat Nov 08 23:06:11 2025 +0100 @@ -29,14 +29,12 @@ #include <stdio.h> #include <stdlib.h> -#include <cx/test.h> +#include "test.h" #include "base64.h" #include "crypto.h" #include "utils.h" -#include "test_resource.h" - int test_main(int argc, char **argv); #ifdef _WIN32 @@ -53,24 +51,21 @@ printf("libidav tests\n"); printf("-------------\n\n"); - CxTestSuite* suite = cx_test_suite_new("libidav"); + UcxTestSuite* suite = ucx_test_suite_new(); - cx_test_register(suite, test_util_base64decode); - cx_test_register(suite, test_util_base64decode_len); - cx_test_register(suite, test_util_base64encode); - cx_test_register(suite, test_util_decrypt_str_k); - cx_test_register(suite, test_util_encrypt_str_k); - cx_test_register(suite, test_crypto_buffer); - cx_test_register(suite, test_crypto_stream); - cx_test_register(suite, test_dav_pw2key); - cx_test_register(suite, test_util_parse_creationdate); + ucx_test_register(suite, test_util_base64decode); + ucx_test_register(suite, test_util_base64decode_len); + ucx_test_register(suite, test_util_base64encode); + ucx_test_register(suite, test_util_decrypt_str_k); + ucx_test_register(suite, test_util_encrypt_str_k); + ucx_test_register(suite, test_crypto_buffer); + ucx_test_register(suite, test_crypto_stream); + ucx_test_register(suite, test_dav_pw2key); + ucx_test_register(suite, test_util_parse_creationdate); - cx_test_register(suite, test_resource_iterator_visitor); - cx_test_register(suite, test_resource_map); - - cx_test_run(suite, stdout, (cx_write_func)fwrite); + ucx_test_run(suite, stdout); fflush(stdout); - cx_test_suite_free(suite); + ucx_test_suite_free(suite); return 0; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/test.c Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,91 @@ +/* + * 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 "test.h" + +UcxTestSuite* ucx_test_suite_new() { + UcxTestSuite* suite = (UcxTestSuite*) malloc(sizeof(UcxTestSuite)); + if (suite != NULL) { + suite->success = 0; + suite->failure = 0; + suite->tests = NULL; + } + + return suite; +} + +void ucx_test_suite_free(UcxTestSuite* suite) { + UcxTestList *l = suite->tests; + while (l != NULL) { + UcxTestList *e = l; + l = l->next; + free(e); + } + free(suite); +} + +int ucx_test_register(UcxTestSuite* suite, UcxTest test) { + if (suite->tests) { + UcxTestList *newelem = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (newelem) { + newelem->test = test; + newelem->next = NULL; + + UcxTestList *last = suite->tests; + while (last->next) { + last = last->next; + } + last->next = newelem; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } else { + suite->tests = (UcxTestList*) malloc(sizeof(UcxTestList)); + if (suite->tests) { + suite->tests->test = test; + suite->tests->next = NULL; + + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } + } +} + +void ucx_test_run(UcxTestSuite* suite, FILE* output) { + suite->success = 0; + suite->failure = 0; + for (UcxTestList* elem = suite->tests ; elem ; elem = elem->next) { + elem->test(suite, output); + } + fwrite("\nAll test completed.\n", 1, 21, output); + fprintf(output, " Total: %u\n Success: %u\n Failure: %u\n", + suite->success+suite->failure, suite->success, suite->failure); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/test.h Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file: test.h + * + * UCX Test Framework. + * + * Usage of this test framework: + * + * **** IN HEADER FILE: **** + * + * <pre> + * UCX_TEST(function_name); + * UCX_TEST_SUBROUTINE(subroutine_name, paramlist); // optional + * </pre> + * + * **** IN SOURCE FILE: **** + * <pre> + * UCX_TEST_SUBROUTINE(subroutine_name, paramlist) { + * // tests with UCX_TEST_ASSERT() + * } + * + * UCX_TEST(function_name) { + * // memory allocation and other stuff here + * #UCX_TEST_BEGIN + * // tests with UCX_TEST_ASSERT() and/or + * // calls with UCX_TEST_CALL_SUBROUTINE() here + * #UCX_TEST_END + * // cleanup of memory here + * } + * </pre> + * + * <b>Note:</b> if a test fails, a longjump is performed + * back to the #UCX_TEST_BEGIN macro! + * + * <b>Attention:</b> Do not call own functions within a test, that use + * UCX_TEST_ASSERT() macros and are not defined by using UCX_TEST_SUBROUTINE(). + * + * + * @author Mike Becker + * @author Olaf Wintermann + * + */ + +#ifndef UCX_21_TEST_H +#define UCX_21_TEST_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __FUNCTION__ + +/** + * Alias for the <code>__func__</code> preprocessor macro. + * Some compilers use <code>__func__</code> and others use __FUNCTION__. + * We use __FUNCTION__ so we define it for those compilers which use + * <code>__func__</code>. + */ +#define __FUNCTION__ __func__ +#endif + +/** Type for the UcxTestSuite. */ +typedef struct UcxTestSuite UcxTestSuite; + +/** Pointer to a test function. */ +typedef void(*UcxTest)(UcxTestSuite*,FILE*); + +/** Type for the internal list of test cases. */ +typedef struct UcxTestList UcxTestList; + +/** Structure for the internal list of test cases. */ +struct UcxTestList { + + /** Test case. */ + UcxTest test; + + /** Pointer to the next list element. */ + UcxTestList *next; +}; + +/** + * A test suite containing multiple test cases. + */ +struct UcxTestSuite { + + /** The number of successful tests after the suite has been run. */ + unsigned int success; + + /** The number of failed tests after the suite has been run. */ + unsigned int failure; + + /** + * Internal list of test cases. + * Use ucx_test_register() to add tests to this list. + */ + UcxTestList *tests; +}; + +/** + * Creates a new test suite. + * @return a new test suite + */ +UcxTestSuite* ucx_test_suite_new(); + +/** + * Destroys a test suite. + * @param suite the test suite to destroy + */ +void ucx_test_suite_free(UcxTestSuite* suite); + +/** + * Registers a test function with the specified test suite. + * + * @param suite the suite, the test function shall be added to + * @param test the test function to register + * @return <code>EXIT_SUCCESS</code> on success or + * <code>EXIT_FAILURE</code> on failure + */ +int ucx_test_register(UcxTestSuite* suite, UcxTest test); + +/** + * Runs a test suite and writes the test log to the specified stream. + * @param suite the test suite to run + * @param outstream the stream the log shall be written to + */ +void ucx_test_run(UcxTestSuite* suite, FILE* outstream); + +/** + * Macro for a #UcxTest function header. + * + * Use this macro to declare and/or define a #UcxTest function. + * + * @param name the name of the test function + */ +#define UCX_TEST(name) void name(UcxTestSuite* _suite_,FILE *_output_) + +/** + * Marks the begin of a test. + * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>after</b> + * #UCX_TEST_BEGIN. + * + * @see #UCX_TEST_END + */ +#define UCX_TEST_BEGIN fwrite("Running ", 1, 8, _output_);\ + fwrite(__FUNCTION__, 1, strlen(__FUNCTION__), _output_);\ + fwrite("... ", 1, 4, _output_);\ + jmp_buf _env_; \ + if (!setjmp(_env_)) { + +/** + * Checks a test assertion. + * If the assertion is correct, the test carries on. If the assertion is not + * correct, the specified message (terminated by a dot and a line break) is + * written to the test suites output stream. + * @param condition the condition to check + * @param message the message that shall be printed out on failure + */ +#define UCX_TEST_ASSERT(condition,message) if (!(condition)) { \ + fwrite(message".\n", 1, 2+strlen(message), _output_); \ + _suite_->failure++; \ + longjmp(_env_, 1);\ + } + +/** + * Macro for a test subroutine function header. + * + * Use this to declare and/or define a subroutine that can be called by using + * UCX_TEST_CALL_SUBROUTINE(). + * + * @param name the name of the subroutine + * @param ... the parameter list + * + * @see UCX_TEST_CALL_SUBROUTINE() + */ +#define UCX_TEST_SUBROUTINE(name,...) void name(UcxTestSuite* _suite_,\ + FILE *_output_, jmp_buf _env_, __VA_ARGS__) + +/** + * Macro for calling a test subroutine. + * + * Subroutines declared with UCX_TEST_SUBROUTINE() can be called by using this + * macro. + * + * <b>Note:</b> You may <b>only</b> call subroutines within a #UCX_TEST_BEGIN- + * #UCX_TEST_END-block. + * + * @param name the name of the subroutine + * @param ... the argument list + * + * @see UCX_TEST_SUBROUTINE() + */ +#define UCX_TEST_CALL_SUBROUTINE(name,...) \ + name(_suite_,_output_,_env_,__VA_ARGS__); + +/** + * Marks the end of a test. + * <b>Note:</b> Any UCX_TEST_ASSERT() calls must be performed <b>before</b> + * #UCX_TEST_END. + * + * @see #UCX_TEST_BEGIN + */ +#define UCX_TEST_END fwrite("success.\n", 1, 9, _output_); _suite_->success++;} + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_21_TEST_H */ +
--- a/test/test_finfo.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "test_finfo.h" -
--- a/test/test_finfo.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 TEST_FINFO_H -#define TEST_FINFO_H - -#include <cx/test.h> - -#ifdef __cplusplus -extern "C" { -#endif - - - - -#ifdef __cplusplus -} -#endif - -#endif /* TEST_FINFO_H */ -
--- a/test/test_resource.c Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,166 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 "test_resource.h" - -#include "helper.h" - - -CX_TEST(test_resource_iterator_visitor) { - DavContext *ctx = dav_context_new(); - DavSession *sn = test_session(ctx); - CX_TEST_DO { - DavResource *root = dav_resource_new(sn, "/"); - DavResource *c1 = dav_resource_new(sn, "/child1"); - DavResource *c2 = dav_resource_new(sn, "/child2"); - DavResource *c1c1 = dav_resource_new(sn, "/child1/sub1"); - DavResource *c1c2 = dav_resource_new(sn, "/child1/sub2"); - DavResource *c1c2c1 = dav_resource_new(sn, "/child2/sub2/x1"); - DavResource *c1c3 = dav_resource_new(sn, "/child1/sub3"); - DavResource *c3 = dav_resource_new(sn, "/child3"); - - root->children = c1; - c1->next = c2; c2->prev = c1; - c2->next = c3; c3->prev = c2; - - c1->parent = root; - c2->parent = root; - c3->parent = root; - - c1->children = c1c1; - c1c1->next = c1c2; c1c2->prev = c1c1; - c1c2->next = c1c3; c1c3->prev = c1c2; - - c1c1->parent = c1; - c1c2->parent = c1; - c1c3->parent = c1; - - c1c2->children = c1c2c1; c1c2c1->parent = c1c2; - - DavResource *resources[] = {root, c1, c2, c3, c1c1, c1c2, c1c3, c1c2c1 }; - - // Iterator Test - CxTreeIterator i = dav_resource_iterator(root); - int iterated = 0; - cx_foreach(DavResource *, res, i) { - for(int r=0;r<8;r++) { - if(res == resources[r]) { - res->exists = 1; - break; - } - } - iterated++; - } - CX_TEST_ASSERT(iterated == 8); - - for(int ir=0;ir<8;ir++) { - CX_TEST_ASSERT(resources[ir]->exists); - resources[ir]->exists = 0; // reset for next test - } - - // Visitor Test - CxTreeVisitor v = dav_resource_visitor(root); - int visited = 0; - cx_foreach(DavResource *, res, v) { - for(int r=0;r<8;r++) { - if(res == resources[r]) { - res->exists = 1; - break; - } - } - visited++; - } - CX_TEST_ASSERT(visited == 8); - - for(int vr=0;vr<8;vr++) { - CX_TEST_ASSERT(resources[vr]->exists); - resources[vr]->exists = 0; // reset for next test - } - - - // partial test - i = dav_resource_iterator(c1); - iterated = 0; - cx_foreach(DavResource *, res, i) { - iterated++; - } - CX_TEST_ASSERT(iterated == 7); - - v = dav_resource_visitor(c1); - visited = 0; - cx_foreach(DavResource *, res, v) { - visited++; - } - CX_TEST_ASSERT(visited == 7); - } - dav_context_destroy(ctx); -} - -CX_TEST(test_resource_map) { - DavContext *ctx = dav_context_new(); - DavSession *sn = test_session(ctx); - CX_TEST_DO { - DavResource *root = dav_resource_new(sn, "/"); - DavResource *c1 = dav_resource_new(sn, "/child1"); - DavResource *c2 = dav_resource_new(sn, "/child2"); - DavResource *c1c1 = dav_resource_new(sn, "/child1/sub1"); - DavResource *c1c2 = dav_resource_new(sn, "/child1/sub2"); - DavResource *c2c2c1 = dav_resource_new(sn, "/child2/sub2/x1"); - DavResource *c1c3 = dav_resource_new(sn, "/child1/sub3"); - DavResource *c3 = dav_resource_new(sn, "/child3"); - - root->children = c1; - c1->next = c2; c2->prev = c1; - c2->next = c3; c3->prev = c2; - - c1->parent = root; - c2->parent = root; - c3->parent = root; - - c1->children = c1c1; - c1c1->next = c1c2; c1c2->prev = c1c1; - c1c2->next = c1c3; c1c3->prev = c1c2; - - c1c1->parent = c1; - c1c2->parent = c1; - c1c3->parent = c1; - - c1c2->children = c2c2c1; c2c2c1->parent = c1c2; - - CxMap *map = dav_resource_map(root); - CX_TEST_ASSERT(cxMapGet(map, "/") == root); - CX_TEST_ASSERT(cxMapGet(map, "/child1") == c1); - CX_TEST_ASSERT(cxMapGet(map, "/child2") == c2); - CX_TEST_ASSERT(cxMapGet(map, "/child3") == c3); - CX_TEST_ASSERT(cxMapGet(map, "/child1/sub1") == c1c1); - CX_TEST_ASSERT(cxMapGet(map, "/child1/sub2") == c1c2); - CX_TEST_ASSERT(cxMapGet(map, "/child1/sub3") == c1c3); - CX_TEST_ASSERT(cxMapGet(map, "/child2/sub2/x1") == c2c2c1); - } - dav_context_destroy(ctx); -} \ No newline at end of file
--- a/test/test_resource.h Tue Oct 14 21:02:26 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2025 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 TEST_RESOURCE_H -#define TEST_RESOURCE_H - -#include <cx/test.h> - -#ifdef __cplusplus -extern "C" { -#endif - -CX_TEST(test_resource_iterator_visitor); -CX_TEST(test_resource_map); - - -#ifdef __cplusplus -} -#endif - -#endif /* TEST_RESOURCE_H */ -
--- a/test/utils.c Tue Oct 14 21:02:26 2025 +0200 +++ b/test/utils.c Sat Nov 08 23:06:11 2025 +0100 @@ -26,28 +26,33 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "utils.h" +#include "test.h" + #include <libidav/utils.h> -CX_TEST(test_util_parse_creationdate) { - CX_TEST_DO { - time_t t1 = util_parse_creationdate("2012-11-29T21:35:36Z"); - time_t t2 = util_parse_creationdate("2023-09-17T13:03:00+02:00"); - - struct tm *tm = gmtime(&t1); - CX_TEST_ASSERT(tm->tm_year == 112); - CX_TEST_ASSERT(tm->tm_mon == 10); - CX_TEST_ASSERT(tm->tm_mday == 29); - CX_TEST_ASSERT(tm->tm_hour == 21); - CX_TEST_ASSERT(tm->tm_min == 35); - CX_TEST_ASSERT(tm->tm_sec == 36); - - tm = gmtime(&t2); - CX_TEST_ASSERT(tm->tm_year == 123); - CX_TEST_ASSERT(tm->tm_mon == 8); - CX_TEST_ASSERT(tm->tm_mday == 17); - CX_TEST_ASSERT(tm->tm_hour == 11); - CX_TEST_ASSERT(tm->tm_min == 03); - CX_TEST_ASSERT(tm->tm_sec == 0); - } +UCX_TEST(test_util_parse_creationdate) { + UCX_TEST_BEGIN; + + time_t t1 = util_parse_creationdate("2012-11-29T21:35:36Z"); + time_t t2 = util_parse_creationdate("2023-09-17T13:03:00+02:00"); + + struct tm *tm = gmtime(&t1); + UCX_TEST_ASSERT(tm->tm_year == 112, "t1: wrong year"); + UCX_TEST_ASSERT(tm->tm_mon == 10, "t1: wrong month"); + UCX_TEST_ASSERT(tm->tm_mday == 29, "t1: wrong day"); + UCX_TEST_ASSERT(tm->tm_hour == 21, "t1: wrong hour"); + UCX_TEST_ASSERT(tm->tm_min == 35, "t1: wrong minute"); + UCX_TEST_ASSERT(tm->tm_sec == 36, "t1: wrong second"); + + tm = gmtime(&t2); + UCX_TEST_ASSERT(tm->tm_year == 123, "t2: wrong year"); + UCX_TEST_ASSERT(tm->tm_mon == 8, "t2: wrong month"); + UCX_TEST_ASSERT(tm->tm_mday == 17, "t2: wrong day"); + UCX_TEST_ASSERT(tm->tm_hour == 11, "t2: wrong hour"); + UCX_TEST_ASSERT(tm->tm_min == 03, "t2: wrong minute"); + UCX_TEST_ASSERT(tm->tm_sec == 0, "t2: wrong second"); + + UCX_TEST_END; + + }
--- a/test/utils.h Tue Oct 14 21:02:26 2025 +0200 +++ b/test/utils.h Sat Nov 08 23:06:11 2025 +0100 @@ -29,13 +29,13 @@ #ifndef TEST_UTILS_H #define TEST_UTILS_H -#include <cx/test.h> +#include "test.h" #ifdef __cplusplus extern "C" { #endif -CX_TEST(test_util_parse_creationdate); +UCX_TEST(test_util_parse_creationdate); #ifdef __cplusplus
--- a/ucx/Makefile Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/Makefile Sat Nov 08 23:06:11 2025 +0100 @@ -46,6 +46,7 @@ SRC += streams.c SRC += properties.c SRC += json.c +SRC += kv_list.c OBJ = $(SRC:%.c=../build/ucx/%$(OBJ_EXT))
--- a/ucx/allocator.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/allocator.c Sat Nov 08 23:06:11 2025 +0100 @@ -73,7 +73,7 @@ NULL }; const CxAllocator * const cxStdlibAllocator = &cx_stdlib_allocator; -const CxAllocator * cxDefaultAllocator = cxStdlibAllocator; +const CxAllocator * cxDefaultAllocator = &cx_stdlib_allocator; int cx_reallocate_( void **mem,
--- a/ucx/array_list.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/array_list.c Sat Nov 08 23:06:11 2025 +0100 @@ -50,7 +50,7 @@ } CxArrayReallocator cx_array_default_reallocator_impl = { - cx_array_default_realloc, NULL, NULL, 0, 0 + cx_array_default_realloc, NULL, NULL }; CxArrayReallocator *cx_array_default_reallocator = &cx_array_default_reallocator_impl; @@ -72,11 +72,11 @@ } // retrieve the pointer to the actual allocator - const CxAllocator *al = alloc->ptr1; + const CxAllocator *al = alloc->allocator; // check if the array is still located on the stack void *newmem; - if (array == alloc->ptr2) { + if (array == alloc->stack_ptr) { newmem = cxMalloc(al, n); if (newmem != NULL && array != NULL) { memcpy(newmem, array, old_capacity*elem_size); @@ -89,20 +89,27 @@ struct cx_array_reallocator_s cx_array_reallocator( const struct cx_allocator_s *allocator, - const void *stackmem + const void *stack_ptr ) { if (allocator == NULL) { allocator = cxDefaultAllocator; } return (struct cx_array_reallocator_s) { cx_array_advanced_realloc, - (void*) allocator, (void*) stackmem, - 0, 0 + allocator, stack_ptr, }; } // LOW LEVEL ARRAY LIST FUNCTIONS +/** + * Increases the capacity until it is a multiple of a some alignment or reaches the maximum. + * + * @param cap the required capacity (must not be larger than @p max) + * @param alignment the alignment + * @param max the maximum capacity + * @return the aligned capacity + */ static size_t cx_array_align_capacity( size_t cap, size_t alignment, @@ -291,7 +298,7 @@ *target, oldcap, newcap, elem_size, reallocator ); if (newmem == NULL) { - return 1; + return 1; // LCOV_EXCL_LINE } // repair src pointer, if necessary @@ -335,7 +342,7 @@ return 0; } -int cx_array_insert_sorted( +static int cx_array_insert_sorted_impl( void **target, size_t *size, size_t *capacity, @@ -343,7 +350,8 @@ const void *sorted_data, size_t elem_size, size_t elem_count, - CxArrayReallocator *reallocator + CxArrayReallocator *reallocator, + bool allow_duplicates ) { // assert pointers assert(target != NULL); @@ -369,6 +377,7 @@ // store some counts size_t old_size = *size; size_t old_capacity = *capacity; + // the necessary capacity is the worst case assumption, including duplicates size_t needed_capacity = old_size + elem_count; // if we need more than we have, try a reallocation @@ -418,13 +427,60 @@ bptr, cmp_func ); + // binary search gives us the smallest index; + // we also want to include equal elements here + while (si + copy_len < elem_count + && cmp_func(bptr, src+copy_len*elem_size) == 0) { + copy_len++; + } // copy the source elements - bytes_copied = copy_len * elem_size; - memcpy(dest, src, bytes_copied); - dest += bytes_copied; - src += bytes_copied; - si += copy_len; + if (copy_len > 0) { + if (allow_duplicates) { + // we can copy the entire chunk + bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied; + si += copy_len; + di += copy_len; + } else { + // first, check the end of the source chunk + // for being a duplicate of the bptr + const char *end_of_src = src + (copy_len - 1) * elem_size; + size_t skip_len = 0; + while (copy_len > 0 && cmp_func(bptr, end_of_src) == 0) { + end_of_src -= elem_size; + skip_len++; + copy_len--; + } + char *last = dest == *target ? NULL : dest - elem_size; + // then iterate through the source chunk + // and skip all duplicates with the last element in the array + size_t more_skipped = 0; + for (unsigned j = 0; j < copy_len; j++) { + if (last != NULL && cmp_func(last, src) == 0) { + // duplicate - skip + src += elem_size; + si++; + more_skipped++; + } else { + memcpy(dest, src, elem_size); + src += elem_size; + last = dest; + dest += elem_size; + si++; + di++; + } + } + // skip the previously identified elements as well + src += skip_len * elem_size; + si += skip_len; + skip_len += more_skipped; + // reduce the actual size by the number of skipped elements + *size -= skip_len; + } + } // when all source elements are in place, we are done if (si >= elem_count) break; @@ -443,20 +499,103 @@ memmove(dest, bptr, bytes_copied); dest += bytes_copied; bptr += bytes_copied; + di += copy_len; bi += copy_len; } - // still source elements left? simply append them + // still source elements left? if (si < elem_count) { - memcpy(dest, src, elem_size * (elem_count - si)); + if (allow_duplicates) { + // duplicates allowed or nothing inserted yet: simply copy everything + memcpy(dest, src, elem_size * (elem_count - si)); + } else { + if (dest != *target) { + // skip all source elements that equal the last element + char *last = dest - elem_size; + while (si < elem_count) { + if (last != NULL && cmp_func(last, src) == 0) { + src += elem_size; + si++; + (*size)--; + } else { + break; + } + } + } + // we must check the elements in the chunk one by one + while (si < elem_count) { + // find a chain of elements that can be copied + size_t copy_len = 1, skip_len = 0; + { + const char *left_src = src; + while (si + copy_len < elem_count) { + const char *right_src = left_src + elem_size; + int d = cmp_func(left_src, right_src); + if (d < 0) { + if (skip_len > 0) { + // new larger element found; + // handle it in the next cycle + break; + } + left_src += elem_size; + copy_len++; + } else if (d == 0) { + left_src += elem_size; + skip_len++; + } else { + break; + } + } + } + size_t bytes_copied = copy_len * elem_size; + memcpy(dest, src, bytes_copied); + dest += bytes_copied; + src += bytes_copied + skip_len * elem_size; + si += copy_len + skip_len; + di += copy_len; + *size -= skip_len; + } + } } - // still buffer elements left? - // don't worry, we already moved them to the correct place + // buffered elements need to be moved when we skipped duplicates + size_t total_skipped = new_size - *size; + if (bi < new_size && total_skipped > 0) { + // move the remaining buffer to the end of the array + memmove(dest, bptr, elem_size * (new_size - bi)); + } return 0; } +int cx_array_insert_sorted( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, true); +} + +int cx_array_insert_unique( + void **target, + size_t *size, + size_t *capacity, + cx_compare_func cmp_func, + const void *sorted_data, + size_t elem_size, + size_t elem_count, + CxArrayReallocator *reallocator +) { + return cx_array_insert_sorted_impl(target, size, capacity, + cmp_func, sorted_data, elem_size, elem_count, reallocator, false); +} + size_t cx_array_binary_search_inf( const void *arr, size_t size, @@ -502,6 +641,13 @@ result = cmp_func(elem, arr_elem); if (result == 0) { // found it! + // check previous elements; + // when they are equal, report the smallest index + arr_elem -= elem_size; + while (pivot_index > 0 && cmp_func(elem, arr_elem) == 0) { + pivot_index--; + arr_elem -= elem_size; + } return pivot_index; } else if (result < 0) { // element is smaller than pivot, continue search left @@ -644,7 +790,7 @@ // guarantee enough capacity if (arl->capacity < list->collection.size + n) { size_t new_capacity = list->collection.size + n; - new_capacity = new_capacity - (new_capacity % 16) + 16; + new_capacity = cx_array_align_capacity(new_capacity, 16, SIZE_MAX); if (cxReallocateArray( list->collection.allocator, &arl->data, new_capacity, @@ -700,6 +846,31 @@ } } +static size_t cx_arl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + // get a correctly typed pointer to the list + cx_array_list *arl = (cx_array_list *) list; + + if (cx_array_insert_unique( + &arl->data, + &list->collection.size, + &arl->capacity, + list->collection.cmpfunc, + sorted_data, + list->collection.elem_size, + n, + &arl->reallocator + )) { + // array list implementation is "all or nothing" + return 0; + } else { + return n; + } +} + static void *cx_arl_insert_element( struct cx_list_s *list, size_t index, @@ -717,7 +888,7 @@ const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle.m; + struct cx_list_s *list = iter->src_handle; if (iter->index < list->collection.size) { if (cx_arl_insert_element(list, iter->index + 1 - prepend, elem) == NULL) { @@ -928,7 +1099,7 @@ static bool cx_arl_iter_valid(const void *it) { const struct cx_iterator_s *iter = it; - const struct cx_list_s *list = iter->src_handle.c; + const struct cx_list_s *list = iter->src_handle; return iter->index < list->collection.size; } @@ -941,23 +1112,25 @@ struct cx_iterator_s *iter = it; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + cx_arl_remove(iter->src_handle, iter->index, 1, NULL); + iter->elem_count--; } else { iter->index++; iter->elem_handle = ((char *) iter->elem_handle) - + ((const struct cx_list_s *) iter->src_handle.c)->collection.elem_size; + + ((const struct cx_list_s *) iter->src_handle)->collection.elem_size; } } static void cx_arl_iter_prev(void *it) { struct cx_iterator_s *iter = it; - const cx_array_list *list = iter->src_handle.c; if (iter->base.remove) { iter->base.remove = false; - cx_arl_remove(iter->src_handle.m, iter->index, 1, NULL); + cx_arl_remove(iter->src_handle, iter->index, 1, NULL); + iter->elem_count--; } iter->index--; + cx_array_list *list = iter->src_handle; if (iter->index < list->base.collection.size) { iter->elem_handle = ((char *) list->data) + iter->index * list->base.collection.elem_size; @@ -973,7 +1146,7 @@ struct cx_iterator_s iter; iter.index = index; - iter.src_handle.c = list; + iter.src_handle = (void*)list; iter.elem_handle = cx_arl_at(list, index); iter.elem_size = list->collection.elem_size; iter.elem_count = list->collection.size; @@ -981,7 +1154,7 @@ iter.base.current = cx_arl_iter_current; iter.base.next = backwards ? cx_arl_iter_prev : cx_arl_iter_next; iter.base.remove = false; - iter.base.mutating = false; + iter.base.allow_remove = true; return iter; } @@ -991,6 +1164,7 @@ cx_arl_insert_element, cx_arl_insert_array, cx_arl_insert_sorted, + cx_arl_insert_unique, cx_arl_insert_iter, cx_arl_remove, cx_arl_clear,
--- a/ucx/compare.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/compare.c Sat Nov 08 23:06:11 2025 +0100 @@ -198,6 +198,20 @@ return cx_vcmp_uint64(a, b); } +int cx_vcmp_size(size_t a, size_t b) { + if (a == b) { + return 0; + } else { + return a < b ? -1 : 1; + } +} + +int cx_cmp_size(const void *i1, const void *i2) { + size_t a = *((const size_t *) i1); + size_t b = *((const size_t *) i2); + return cx_vcmp_size(a, b); +} + int cx_vcmp_float(float a, float b) { if (fabsf(a - b) < 1e-6f) { return 0;
--- a/ucx/cx/allocator.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/allocator.h Sat Nov 08 23:06:11 2025 +0100 @@ -46,36 +46,22 @@ /** * The allocator's malloc() implementation. */ - void *(*malloc)( - void *data, - size_t n - ); + void *(*malloc)(void *data, size_t n); /** * The allocator's realloc() implementation. */ - void *(*realloc)( - void *data, - void *mem, - size_t n - ); + void *(*realloc)(void *data, void *mem, size_t n); /** * The allocator's calloc() implementation. */ - void *(*calloc)( - void *data, - size_t nmemb, - size_t size - ); + void *(*calloc)(void *data, size_t nmemb, size_t size); /** * The allocator's free() implementation. */ - void (*free)( - void *data, - void *mem - ); + void (*free)(void *data, void *mem); } cx_allocator_class; /** @@ -100,15 +86,13 @@ /** * A pre-defined allocator using standard library malloc() etc. */ -cx_attr_export -extern const CxAllocator * const cxStdlibAllocator; +CX_EXPORT extern const CxAllocator * const cxStdlibAllocator; /** * The default allocator that is used by UCX. * Initialized with cxStdlibAllocator, but you may change it. */ -cx_attr_export -extern const CxAllocator * cxDefaultAllocator; +CX_EXPORT extern const CxAllocator * cxDefaultAllocator; /** * Function pointer type for destructor functions. @@ -133,10 +117,33 @@ * @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 -); +typedef void (*cx_destructor_func2)(void *data, void *memory); + + +/** + * Function pointer type for clone functions. + * + * A clone function is supposed to create a deep copy of the memory pointed to + * by the @p source pointer. + * If the @p target pointer is non-null, the clone function is supposed to store + * the copy into that memory region. + * Otherwise, the clone function shall use the specified @p allocator to create + * a new object. + * + * The return value of a clone function is always a pointer to the target + * memory region, or @c NULL if any allocation failed. + * A clone function SHOULD NOT fail for any other reason than an allocation + * failure. + * + * @param target the target memory or @c NULL, if memory shall be allocated + * @param source the source memory + * @param allocator the allocator that shall be used + * @param data optional additional data + * @return either the specified @p target, a pointer to the allocated memory, + * or @c NULL, if any error occurred + */ +typedef void*(cx_clone_func)(void *target, const void *source, + const CxAllocator *allocator, void *data); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -153,13 +160,8 @@ * @retval non-zero failure * @see cx_reallocatearray() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_reallocate_( - void **mem, - size_t n -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_reallocate_(void **mem, size_t n); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -180,14 +182,8 @@ * @retval non-zero failure * @see cx_reallocate() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_reallocatearray_( - void **mem, - size_t nmemb, - size_t size -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_reallocatearray_(void **mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -244,11 +240,7 @@ * @param mem a pointer to the block to free */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxFree( - const CxAllocator *allocator, - void *mem -); +CX_EXPORT void cxFree(const CxAllocator *allocator, void *mem); /** * Allocate @p n bytes of memory. @@ -257,21 +249,14 @@ * @param n the number of bytes * @return a pointer to the allocated memory */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2) -cx_attr_export -void *cxMalloc( - const CxAllocator *allocator, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) +CX_EXPORT void *cxMalloc(const CxAllocator *allocator, size_t n); /** * Reallocate the previously allocated block in @p mem, making the new block * @p n bytes long. - * This function may return the same pointer that was passed to it, if moving + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -281,25 +266,18 @@ * @param n the new size in bytes * @return a pointer to the reallocated memory */ -cx_attr_nodiscard -cx_attr_nonnull_arg(1) -cx_attr_dealloc_ucx -cx_attr_allocsize(3) -cx_attr_export -void *cxRealloc( - const CxAllocator *allocator, - void *mem, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx cx_attr_allocsize(3) +CX_EXPORT void *cxRealloc(const CxAllocator *allocator, void *mem, size_t n); /** * Reallocate the previously allocated block in @p mem, making the new block * @p n bytes long. - * This function may return the same pointer that was passed to it, if moving + * This function may return the same pointer passed to it if moving * the memory was not necessary. * * The size is calculated by multiplying @p nemb and @p size. - * If that multiplication overflows, this function returns @c NULL and @c errno + * If that multiplication overflows, this function returns @c NULL, and @c errno * will be set. * * @note Re-allocating a block allocated by a different allocator is undefined. @@ -310,17 +288,10 @@ * @param size the size of each element * @return a pointer to the reallocated memory */ -cx_attr_nodiscard -cx_attr_nonnull_arg(1) -cx_attr_dealloc_ucx -cx_attr_allocsize(3, 4) -cx_attr_export -void *cxReallocArray( - const CxAllocator *allocator, - void *mem, - size_t nmemb, - size_t size -); +cx_attr_nodiscard cx_attr_nonnull_arg(1) +cx_attr_dealloc_ucx cx_attr_allocsize(3, 4) +CX_EXPORT void *cxReallocArray(const CxAllocator *allocator, + void *mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -330,7 +301,7 @@ * @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. + * @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 @@ -338,14 +309,8 @@ * @retval zero success * @retval non-zero failure */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cxReallocate_( - const CxAllocator *allocator, - void **mem, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cxReallocate_(const CxAllocator *allocator, void **mem, size_t n); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -355,7 +320,7 @@ * @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. + * @c errno will be set if the underlying realloc function does so. * * @param allocator (@c CxAllocator*) the allocator * @param mem (@c void**) pointer to the pointer to allocated block @@ -385,15 +350,9 @@ * @retval zero success * @retval non-zero on failure */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cxReallocateArray_( - const CxAllocator *allocator, - void **mem, - size_t nmemb, - size_t size -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cxReallocateArray_(const CxAllocator *allocator, + void **mem, size_t nmemb, size_t size); /** * Reallocate a previously allocated block and changes the pointer in-place, @@ -425,17 +384,9 @@ * @param size the size of each element in bytes * @return a pointer to the allocated memory */ -cx_attr_nonnull_arg(1) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2, 3) -cx_attr_export -void *cxCalloc( - const CxAllocator *allocator, - size_t nmemb, - size_t size -); +cx_attr_nonnull_arg(1) cx_attr_nodiscard +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2, 3) +CX_EXPORT void *cxCalloc(const CxAllocator *allocator, size_t nmemb, size_t size); /** * Allocate @p n bytes of memory and sets every byte to zero. @@ -444,16 +395,9 @@ * @param n the number of bytes * @return a pointer to the allocated memory */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_malloc -cx_attr_dealloc_ucx -cx_attr_allocsize(2) -cx_attr_export -void *cxZalloc( - const CxAllocator *allocator, - size_t n -); +cx_attr_nodiscard cx_attr_nonnull +cx_attr_malloc cx_attr_dealloc_ucx cx_attr_allocsize(2) +CX_EXPORT void *cxZalloc(const CxAllocator *allocator, size_t n); /** * Convenience macro that invokes cxMalloc() with the cxDefaultAllocator.
--- a/ucx/cx/array_list.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/array_list.h Sat Nov 08 23:06:11 2025 +0100 @@ -44,11 +44,10 @@ #endif /** - * The maximum item size in an array list that fits into stack buffer - * when swapped. + * The maximum item size in an array list that fits into + * a stack buffer when swapped. */ -cx_attr_export -extern const unsigned cx_array_swap_sbo_size; +CX_EXPORT extern const unsigned cx_array_swap_sbo_size; /** * Declares variables for an array that can be used with the convenience macros. @@ -84,7 +83,7 @@ /** * Declares variables for an array that can be used with the convenience macros. * - * The size and capacity variables will have @c size_t type. + * The size and capacity variables will have type @c size_t. * Use #CX_ARRAY_DECLARE_SIZED() to specify a different type. * * @par Examples @@ -147,7 +146,7 @@ * const CxAllocator *al = // ... * cx_array_initialize_a(al, myarray, 128); * // ... - * cxFree(al, myarray); // don't forget to free with same allocator + * cxFree(al, myarray); // remember to free with the same allocator * @endcode * * @param allocator (@c CxAllocator*) the allocator @@ -172,10 +171,9 @@ * Reallocates space for the given array. * * Implementations are not required to free the original array. - * This allows reallocation of static memory by allocating heap memory - * and copying the array contents. The information in the custom fields of - * the referenced allocator can be used to track the state of the memory - * or to transport other additional data. + * This allows reallocation of static or stack memory by allocating heap memory + * and copying the array contents; namely when @c stack_ptr in this struct + * is not @c NULL and @p array equals @c stack_ptr. * * @param array the array to reallocate * @param old_capacity the old number of elements @@ -184,33 +182,18 @@ * @param alloc a reference to this allocator * @return a pointer to the reallocated memory or @c NULL on failure */ - cx_attr_nodiscard - cx_attr_nonnull_arg(5) - cx_attr_allocsize(3, 4) - void *(*realloc)( - void *array, - size_t old_capacity, - size_t new_capacity, - size_t elem_size, - struct cx_array_reallocator_s *alloc - ); + void *(*realloc)( void *array, size_t old_capacity, size_t new_capacity, + size_t elem_size, struct cx_array_reallocator_s *alloc); /** - * Custom data pointer. + * The allocator that shall be used for the reallocations. */ - void *ptr1; - /** - * Custom data pointer. - */ - void *ptr2; + const CxAllocator *allocator; /** - * Custom data integer. + * Optional pointer to stack memory + * if the array is originally located on the stack. */ - size_t int1; - /** - * Custom data integer. - */ - size_t int2; + const void *stack_ptr; }; /** @@ -221,32 +204,28 @@ /** * A default array reallocator that is based on the cxDefaultAllocator. */ -cx_attr_export -extern CxArrayReallocator *cx_array_default_reallocator; +CX_EXPORT extern CxArrayReallocator *cx_array_default_reallocator; /** * Creates a new array reallocator. * * When @p allocator is @c NULL, the cxDefaultAllocator will be used. * - * When @p stackmem is not @c NULL, the reallocator is supposed to be used - * @em only for the specific array that is initially located at @p stackmem. - * When reallocation is needed, the reallocator checks, if the array is - * still located at @p stackmem and copies the contents to the heap. + * When @p stack_ptr is not @c NULL, the reallocator is supposed to be used + * @em only for the specific array initially located at @p stack_ptr. + * When reallocation is needed, the reallocator checks if the array is + * still located at @p stack_ptr and copies the contents to the heap. * - * @note Invoking this function with both arguments @c NULL will return a + * @note Invoking this function with both arguments being @c NULL will return a * reallocator that behaves like #cx_array_default_reallocator. * * @param allocator the allocator this reallocator shall be based on - * @param stackmem the address of the array when the array is initially located - * on the stack or shall not reallocated in place + * @param stack_ptr the address of the array when the array is initially located + * on the stack or shall not reallocate in place * @return an array reallocator */ -cx_attr_export -CxArrayReallocator cx_array_reallocator( - const struct cx_allocator_s *allocator, - const void *stackmem -); +CX_EXPORT CxArrayReallocator cx_array_reallocator( + const struct cx_allocator_s *allocator, const void *stack_ptr); /** * Reserves memory for additional elements. @@ -263,7 +242,7 @@ * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param array a pointer to the target array @@ -279,16 +258,9 @@ * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -int cx_array_reserve( - void **array, - void *size, - void *capacity, - unsigned width, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_reserve(void **array, void *size, void *capacity, + unsigned width, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Copies elements from one array to another. @@ -296,7 +268,7 @@ * The elements are copied to the @p target array at the specified @p index, * overwriting possible elements. The @p index does not need to be in range of * the current array @p size. If the new index plus the number of elements added - * would extend the array's size, the remaining @p capacity is used. + * extends the array's size, the remaining @p capacity is used. * * If the @p capacity is also insufficient to hold the new data, a reallocation * attempt is made with the specified @p reallocator. @@ -305,7 +277,7 @@ * * The @p width in bytes refers to the size and capacity. * Both must have the same width. - * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64 bit + * Supported are 0, 1, 2, and 4, as well as 8 if running on a 64-bit * architecture. If set to zero, the native word width is used. * * @param target a pointer to the target array @@ -323,18 +295,9 @@ * @see cx_array_reallocator() */ cx_attr_nonnull_arg(1, 2, 3, 6) -cx_attr_export -int cx_array_copy( - void **target, - void *size, - void *capacity, - unsigned width, - size_t index, - const void *src, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_copy(void **target, void *size, void *capacity, unsigned width, + size_t index, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Convenience macro that uses cx_array_copy() with a default layout and @@ -482,17 +445,9 @@ * @retval non-zero failure */ cx_attr_nonnull_arg(1, 2, 3, 5) -cx_attr_export -int cx_array_insert_sorted( - void **target, - size_t *size, - size_t *capacity, - cx_compare_func cmp_func, - const void *src, - size_t elem_size, - size_t elem_count, - CxArrayReallocator *reallocator -); +CX_EXPORT int cx_array_insert_sorted(void **target, size_t *size, size_t *capacity, + cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); /** * Inserts an element into a sorted array. @@ -500,7 +455,7 @@ * If the target array is not already sorted with respect * to the specified @p cmp_func, the behavior is undefined. * - * If the capacity is insufficient to hold the new data, a reallocation + * If the capacity is not enough to hold the new data, a reallocation * attempt is made. * * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. @@ -586,6 +541,127 @@ #define cx_array_simple_insert_sorted(array, src, n, cmp_func) \ cx_array_simple_insert_sorted_a(NULL, array, src, n, cmp_func) + +/** + * Inserts a sorted array into another sorted array, avoiding duplicates. + * + * If either the target or the source array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * You can create your own reallocator by hand, use #cx_array_default_reallocator, + * or use the convenience function cx_array_reallocator() to create a custom reallocator. + * + * @param target a pointer to the target array + * @param size a pointer to the size of the target array + * @param capacity a pointer to the capacity of the target array + * @param cmp_func the compare function for the elements + * @param src the source array + * @param elem_size the size of one element + * @param elem_count the number of elements to insert + * @param reallocator the array reallocator to use + * (@c NULL defaults to #cx_array_default_reallocator) + * @retval zero success + * @retval non-zero failure + */ +cx_attr_nonnull_arg(1, 2, 3, 5) +CX_EXPORT int cx_array_insert_unique(void **target, size_t *size, size_t *capacity, + cx_compare_func cmp_func, const void *src, size_t elem_size, size_t elem_count, + CxArrayReallocator *reallocator); + +/** + * Inserts an element into a sorted array if it does not exist. + * + * If the target array is not already sorted with respect + * to the specified @p cmp_func, the behavior is undefined. + * + * If the capacity is insufficient to hold the new data, a reallocation + * attempt is made. + * + * The \@ SIZE_TYPE is flexible and can be any unsigned integer type. + * It is important, however, that @p size and @p capacity are pointers to + * variables of the same type. + * + * @param target (@c void**) a pointer to the target array + * @param size (@c SIZE_TYPE*) a pointer to the size of the target array + * @param capacity (@c SIZE_TYPE*) a pointer to the capacity of the target array + * @param elem_size (@c size_t) the size of one element + * @param elem (@c void*) a pointer to the element to add + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @retval zero success (also when the element was already present) + * @retval non-zero failure + */ +#define cx_array_add_unique(target, size, capacity, elem_size, elem, cmp_func, reallocator) \ + cx_array_insert_unique((void**)(target), size, capacity, cmp_func, elem, elem_size, 1, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique() + */ +#define cx_array_simple_add_unique_a(reallocator, array, elem, cmp_func) \ + cx_array_add_unique(&array, &(array##_size), &(array##_capacity), \ + sizeof((array)[0]), &(elem), cmp_func, reallocator) + +/** + * Convenience macro for cx_array_add_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param elem the element to add (NOT a pointer, address is automatically taken) + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_add_unique_a() + */ +#define cx_array_simple_add_unique(array, elem, cmp_func) \ + cx_array_simple_add_unique_a(NULL, array, elem, cmp_func) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the specified reallocator. + * + * @param reallocator (@c CxArrayReallocator*) the array reallocator to use + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique() + */ +#define cx_array_simple_insert_unique_a(reallocator, array, src, n, cmp_func) \ + cx_array_insert_unique((void**)(&array), &(array##_size), &(array##_capacity), \ + cmp_func, src, sizeof((array)[0]), n, reallocator) + +/** + * Convenience macro for cx_array_insert_unique() with a default + * layout and the default reallocator. + * + * @param array the name of the array (NOT a pointer or alias to the array) + * @param src (@c void*) pointer to the source array + * @param n (@c size_t) number of elements in the source array + * @param cmp_func (@c cx_cmp_func) the compare function for the elements + * @retval zero success + * @retval non-zero failure + * @see CX_ARRAY_DECLARE() + * @see cx_array_simple_insert_unique_a() + */ +#define cx_array_simple_insert_unique(array, src, n, cmp_func) \ + cx_array_simple_insert_unique_a(NULL, array, src, n, cmp_func) + /** * Searches the largest lower bound in a sorted array. * @@ -609,14 +685,8 @@ * @see cx_array_binary_search() */ cx_attr_nonnull -cx_attr_export -size_t cx_array_binary_search_inf( - const void *arr, - size_t size, - size_t elem_size, - const void *elem, - cx_compare_func cmp_func -); +CX_EXPORT size_t cx_array_binary_search_inf(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func cmp_func); /** * Searches an item in a sorted array. @@ -635,14 +705,8 @@ * @see cx_array_binary_search_sup() */ cx_attr_nonnull -cx_attr_export -size_t cx_array_binary_search( - const void *arr, - size_t size, - size_t elem_size, - const void *elem, - cx_compare_func cmp_func -); +CX_EXPORT size_t cx_array_binary_search(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func cmp_func); /** * Searches the smallest upper bound in a sorted array. @@ -667,37 +731,25 @@ * @see cx_array_binary_search() */ cx_attr_nonnull -cx_attr_export -size_t cx_array_binary_search_sup( - const void *arr, - size_t size, - size_t elem_size, - const void *elem, - cx_compare_func cmp_func -); +CX_EXPORT size_t cx_array_binary_search_sup(const void *arr, size_t size, + size_t elem_size, const void *elem, cx_compare_func cmp_func); /** * Swaps two array elements. * * @param arr the array * @param elem_size the element size - * @param idx1 index of first element - * @param idx2 index of second element + * @param idx1 index of the first element + * @param idx2 index of the second element */ cx_attr_nonnull -cx_attr_export -void cx_array_swap( - void *arr, - size_t elem_size, - size_t idx1, - size_t idx2 -); +CX_EXPORT void cx_array_swap(void *arr, size_t elem_size, size_t idx1, size_t idx2); /** * Allocates an array list for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * copies of the added elements, and the compare function will be automatically set * to cx_cmp_ptr(), if none is given. * * @param allocator the allocator for allocating the list memory @@ -712,13 +764,8 @@ cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) -cx_attr_export -CxList *cxArrayListCreate( - const CxAllocator *allocator, - cx_compare_func comparator, - size_t elem_size, - size_t initial_capacity -); +CX_EXPORT CxList *cxArrayListCreate(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size, size_t initial_capacity); /** * Allocates an array list for storing elements with @p elem_size bytes each.
--- a/ucx/cx/buffer.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/buffer.h Sat Nov 08 23:06:11 2025 +0100 @@ -62,7 +62,7 @@ * If this flag is enabled, the buffer will automatically free its contents when destroyed. * * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically - * set when the copy-on-write operations is performed. + * set when the copy-on-write operation is performed. */ #define CX_BUFFER_FREE_CONTENTS 0x01 @@ -74,7 +74,7 @@ /** * If this flag is enabled, the buffer will allocate new memory when written to. * - * The current contents of the buffer will be copied to the new memory and the flag + * The current contents of the buffer will be copied to the new memory, and the flag * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically. */ #define CX_BUFFER_COPY_ON_WRITE 0x04 @@ -127,7 +127,7 @@ size_t blkmax; /** - * The target for write function. + * The target for the write function. */ void *target; @@ -202,7 +202,7 @@ * you will need to cast the pointer, and you should set the * #CX_BUFFER_COPY_ON_WRITE flag. * - * You need to set the size manually after initialization, if + * You need to set the size manually after initialization if * you provide @p space which already contains data. * * When you specify stack memory as @p space and decide to use @@ -210,7 +210,7 @@ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying * copy-on-write should be avoided, because the allocated @@ -227,14 +227,8 @@ * @return zero on success, non-zero if a required allocation failed */ cx_attr_nonnull_arg(1) -cx_attr_export -int cxBufferInit( - CxBuffer *buffer, - void *space, - size_t capacity, - const CxAllocator *allocator, - int flags -); +CX_EXPORT int cxBufferInit(CxBuffer *buffer, void *space, size_t capacity, + const CxAllocator *allocator, int flags); /** * Configures the buffer for flushing. @@ -251,11 +245,7 @@ * @see cxBufferWrite() */ cx_attr_nonnull -cx_attr_export -int cxBufferEnableFlushing( - CxBuffer *buffer, - CxBufferFlushConfig config -); +CX_EXPORT int cxBufferEnableFlushing(CxBuffer *buffer, CxBufferFlushConfig config); /** * Destroys the buffer contents. @@ -267,8 +257,7 @@ * @see cxBufferInit() */ cx_attr_nonnull -cx_attr_export -void cxBufferDestroy(CxBuffer *buffer); +CX_EXPORT void cxBufferDestroy(CxBuffer *buffer); /** * Deallocates the buffer. @@ -276,14 +265,10 @@ * 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(). * - * @remark As with all free() functions, this accepts @c NULL arguments in which - * case it does nothing. - * * @param buffer the buffer to deallocate * @see cxBufferCreate() */ -cx_attr_export -void cxBufferFree(CxBuffer *buffer); +CX_EXPORT void cxBufferFree(CxBuffer *buffer); /** * Allocates and initializes a fresh buffer. @@ -296,7 +281,7 @@ * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the * #CX_BUFFER_AUTO_EXTEND flag. * - * @note You may provide @c NULL as argument for @p space. + * @note You may provide @c NULL as the argument for @p space. * Then this function will allocate the space and enforce * the #CX_BUFFER_FREE_CONTENTS flag. * @@ -309,16 +294,9 @@ * @param flags buffer features (see cx_buffer_s.flags) * @return a pointer to the buffer on success, @c NULL if a required allocation failed */ -cx_attr_malloc -cx_attr_dealloc(cxBufferFree, 1) -cx_attr_nodiscard -cx_attr_export -CxBuffer *cxBufferCreate( - void *space, - size_t capacity, - const CxAllocator *allocator, - int flags -); +cx_attr_malloc cx_attr_dealloc(cxBufferFree, 1) cx_attr_nodiscard +CX_EXPORT CxBuffer *cxBufferCreate(void *space, size_t capacity, + const CxAllocator *allocator, int flags); /** * Shifts the contents of the buffer by the given offset. @@ -327,8 +305,8 @@ * 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. + * When the 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. @@ -336,15 +314,15 @@ * 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 + * The buffer position gets shifted alongside 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. + * cxBufferShiftRight() functions using a @c size_t as the 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 + * But you can do that manually 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. @@ -357,11 +335,7 @@ * @see cxBufferShiftRight() */ cx_attr_nonnull -cx_attr_export -int cxBufferShift( - CxBuffer *buffer, - off_t shift -); +CX_EXPORT int cxBufferShift(CxBuffer *buffer, off_t shift); /** * Shifts the buffer to the right. @@ -374,11 +348,7 @@ * @see cxBufferShift() */ cx_attr_nonnull -cx_attr_export -int cxBufferShiftRight( - CxBuffer *buffer, - size_t shift -); +CX_EXPORT int cxBufferShiftRight(CxBuffer *buffer, size_t shift); /** * Shifts the buffer to the left. @@ -391,11 +361,7 @@ * @see cxBufferShift() */ cx_attr_nonnull -cx_attr_export -int cxBufferShiftLeft( - CxBuffer *buffer, - size_t shift -); +CX_EXPORT int cxBufferShiftLeft(CxBuffer *buffer, size_t shift); /** @@ -419,12 +385,7 @@ * */ cx_attr_nonnull -cx_attr_export -int cxBufferSeek( - CxBuffer *buffer, - off_t offset, - int whence -); +CX_EXPORT int cxBufferSeek(CxBuffer *buffer, off_t offset, int whence); /** * Clears the buffer by resetting the position and deleting the data. @@ -439,8 +400,7 @@ * @see cxBufferReset() */ cx_attr_nonnull -cx_attr_export -void cxBufferClear(CxBuffer *buffer); +CX_EXPORT void cxBufferClear(CxBuffer *buffer); /** * Resets the buffer by resetting the position and size to zero. @@ -452,8 +412,7 @@ * @see cxBufferClear() */ cx_attr_nonnull -cx_attr_export -void cxBufferReset(CxBuffer *buffer); +CX_EXPORT void cxBufferReset(CxBuffer *buffer); /** * Tests, if the buffer position has exceeded the buffer size. @@ -463,10 +422,8 @@ * byte of the buffer's contents * @retval false otherwise */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -bool cxBufferEof(const CxBuffer *buffer); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxBufferEof(const CxBuffer *buffer); /** @@ -484,11 +441,7 @@ * @see cxBufferShrink() */ cx_attr_nonnull -cx_attr_export -int cxBufferMinimumCapacity( - CxBuffer *buffer, - size_t capacity -); +CX_EXPORT int cxBufferMinimumCapacity(CxBuffer *buffer, size_t capacity); /** * Shrinks the capacity of the buffer to fit its current size. @@ -507,17 +460,13 @@ * @see cxBufferMinimumCapacity() */ cx_attr_nonnull -cx_attr_export -void cxBufferShrink( - CxBuffer *buffer, - size_t reserve -); +CX_EXPORT void cxBufferShrink(CxBuffer *buffer, size_t reserve); /** * Writes data to a CxBuffer. * * If automatic flushing is not enabled, the data is simply written into the - * buffer at the current position and the position of the buffer is increased + * buffer at the current position, and the position of the buffer is increased * by the number of bytes written. * * If flushing is enabled and the buffer needs to flush, the data is flushed to @@ -526,7 +475,7 @@ * 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 + * 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. * * If, after flushing, the number of items that shall be written still exceeds @@ -534,14 +483,14 @@ * to the flush target, if possible. * * The number returned by this function is the number of elements from - * @c ptr that could be written to either the flush target or the buffer - * (so it does not include the number of items that had been already in the buffer - * in were flushed during the process). + * @c ptr that could be written to either the flush target or the buffer. + * That means it does @em not include the number of items that were already in + * the buffer and were also flushed during the process. * * @attention * When @p size is larger than one and the contents of the buffer are not aligned * with @p size, flushing stops after all complete items have been flushed, leaving - * the mis-aligned part in the buffer. + * the misaligned part in the buffer. * Afterward, this function only writes as many items as possible to the buffer. * * @note The signature is compatible with the fwrite() family of functions. @@ -555,13 +504,8 @@ * @see cxBufferRead() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferWrite( - const void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferWrite(const void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Appends data to a CxBuffer. @@ -583,13 +527,8 @@ * @see cxBufferRead() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferAppend( - const void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferAppend(const void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Performs a single flush-run on the specified buffer. @@ -614,19 +553,19 @@ * at position 200. The flush configuration is * @c blkmax=4 and @c blksize=64 . * Assume that the entire flush operation is successful. - * All 200 bytes on the left hand-side from the current + * All 200 bytes on the left-hand-side from the current * position are written. - * That means, the size of the buffer is now 140 and the + * That means the size of the buffer is now 140 and the * position is zero. * * @par Example 2 * Same as Example 1, but now the @c blkmax is 1. - * The size of the buffer is now 276 and the position is 136. + * The size of the buffer is now 276, and the position is 136. * * @par Example 3 * Same as Example 1, but now assume the flush target * only accepts 100 bytes before returning zero. - * That means, the flush operations manages to flush + * That means the flush operation manages to flush * one complete block and one partial block, ending * up with a buffer with size 240 and position 100. * @@ -636,8 +575,8 @@ * @remark When the buffer uses copy-on-write, the memory * is copied first, before attempting any flush. * This is, however, considered an erroneous use of the - * buffer, because it does not make much sense to put - * readonly data into an UCX buffer for flushing, instead + * buffer because it makes little sense to put + * readonly data into an UCX buffer for flushing instead * of writing it directly to the target. * * @param buffer the buffer @@ -645,8 +584,7 @@ * @see cxBufferEnableFlushing() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferFlush(CxBuffer *buffer); +CX_EXPORT size_t cxBufferFlush(CxBuffer *buffer); /** * Reads data from a CxBuffer. @@ -664,13 +602,8 @@ * @see cxBufferAppend() */ cx_attr_nonnull -cx_attr_export -size_t cxBufferRead( - void *ptr, - size_t size, - size_t nitems, - CxBuffer *buffer -); +CX_EXPORT size_t cxBufferRead(void *ptr, size_t size, + size_t nitems, CxBuffer *buffer); /** * Writes a character to a buffer. @@ -678,9 +611,9 @@ * 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. + * is disabled or the buffer extension fails, @c EOF is returned. * - * On successful write, the position of the buffer is increased. + * On successful writing, the position of the buffer is increased. * * If you just want to write a null-terminator at the current position, you * should use cxBufferTerminate() instead. @@ -688,15 +621,11 @@ * @param buffer the buffer to write to * @param c the character to write * @return the byte that has been written or @c EOF when the end of the stream is - * reached and automatic extension is not enabled or not possible + * reached, and automatic extension is not enabled or not possible * @see cxBufferTerminate() */ cx_attr_nonnull -cx_attr_export -int cxBufferPut( - CxBuffer *buffer, - int c -); +CX_EXPORT int cxBufferPut(CxBuffer *buffer, int c); /** * Writes a terminating zero to a buffer at the current position. @@ -710,8 +639,7 @@ * @return zero, if the terminator could be written, non-zero otherwise */ cx_attr_nonnull -cx_attr_export -int cxBufferTerminate(CxBuffer *buffer); +CX_EXPORT int cxBufferTerminate(CxBuffer *buffer); /** * Writes a string to a buffer. @@ -722,13 +650,8 @@ * @param str the zero-terminated string * @return the number of bytes written */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -cx_attr_export -size_t cxBufferPutString( - CxBuffer *buffer, - const char *str -); +cx_attr_nonnull cx_attr_cstr_arg(2) +CX_EXPORT size_t cxBufferPutString(CxBuffer *buffer, const char *str); /** * Gets a character from a buffer. @@ -739,8 +662,7 @@ * @return the character or @c EOF, if the end of the buffer is reached */ cx_attr_nonnull -cx_attr_export -int cxBufferGet(CxBuffer *buffer); +CX_EXPORT int cxBufferGet(CxBuffer *buffer); #ifdef __cplusplus }
--- a/ucx/cx/collection.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/collection.h Sat Nov 08 23:06:11 2025 +0100 @@ -142,14 +142,24 @@ /** * Indicates whether the collection can guarantee that the stored elements are currently sorted. * - * This may return false even when the elements are sorted. - * It is totally up to the implementation of the collection whether it keeps track of the order of its elements. + * This may return @c false even when the elements are sorted. + * It is totally up to the implementation of the collection when to check if the elements are sorted. + * It is usually a good practice to establish this property as an invariant that does not need + * to be re-checked on certain operations. * * @param c a pointer to a struct that contains #CX_COLLECTION_BASE * @retval true if the elements are currently sorted wrt. the collection's compare function * @retval false if the order of elements is unknown */ -#define cxCollectionSorted(c) ((c)->collection.sorted) +#define cxCollectionSorted(c) ((c)->collection.sorted || (c)->collection.size == 0) + +/** + * Sets the compare function for a collection. + * + * @param c a pointer to a struct that contains #CX_COLLECTION_BASE + * @param func (@c cx_compare_func) the compare function that shall be used by @c c + */ +#define cxCollectionCompareFunc(c, func) (c)->collection.cmpfunc = (func) /** * Sets a simple destructor function for this collection.
--- a/ucx/cx/common.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/common.h Sat Nov 08 23:06:11 2025 +0100 @@ -49,7 +49,7 @@ * <a href="https://uap-core.de/hg/ucx">https://uap-core.de/hg/ucx</a> * </p> * - * <h2>LICENCE</h2> + * <h2>LICENSE</h2> * * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. * @@ -189,7 +189,7 @@ */ #define cx_attr_cstr_arg(idx) /** - * No support for access attribute in clang. + * No support for the access attribute in clang. */ #define cx_attr_access(mode, ...) #else @@ -265,15 +265,32 @@ #define _Thread_local __declspec(thread) #endif // _MSC_VER +// --------------------------------------------------------------------------- +// Exported and inlined functions +// --------------------------------------------------------------------------- + #if defined(CX_WINDLL_EXPORT) -#define cx_attr_export __declspec(dllexport) +#define CX_EXPORT __declspec(dllexport) #elif defined(CX_WINDLL) -#define cx_attr_export __declspec(dllimport) +#define CX_EXPORT __declspec(dllimport) #else /** Only used for building Windows DLLs. */ -#define cx_attr_export +#define CX_EXPORT #endif // CX_WINDLL / CX_WINDLL_EXPORT +#ifdef __GNUC__ +/** + * Declares a function to be inlined. + */ +#define CX_INLINE __attribute__((always_inline)) static inline +#else +#define CX_INLINE static inline +#endif +/** + * Declares a compatibility function for C++ builds. + */ +#define CX_CPPDECL static inline + // --------------------------------------------------------------------------- // Useful function pointers // --------------------------------------------------------------------------- @@ -281,22 +298,12 @@ /** * Function pointer compatible with fwrite-like functions. */ -typedef size_t (*cx_write_func)( - const void *, - size_t, - size_t, - void * -); +typedef size_t (*cx_write_func)(const void*, size_t, size_t, void*); /** * Function pointer compatible with fread-like functions. */ -typedef size_t (*cx_read_func)( - void *, - size_t, - size_t, - void * -); +typedef size_t (*cx_read_func)(void*, size_t, size_t, void*); // --------------------------------------------------------------------------- // Utility macros @@ -348,9 +355,7 @@ #if __cplusplus extern "C" #endif -cx_attr_export int cx_szmul_impl(size_t a, size_t b, size_t *result); +CX_EXPORT int cx_szmul_impl(size_t a, size_t b, size_t *result); #endif // cx_szmul - - #endif // UCX_COMMON_H
--- a/ucx/cx/compare.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/compare.h Sat Nov 08 23:06:11 2025 +0100 @@ -47,20 +47,14 @@ * * All functions from compare.h with the cx_cmp prefix are * compatible with this signature and can be used as - * compare function for collections, or other implementations + * compare function for collections or other implementations * that need to be type-agnostic. * * For simple comparisons the cx_vcmp family of functions * can be used, but they are NOT compatible with this function * pointer. */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -typedef int (*cx_compare_func)( - const void *left, - const void *right -); +typedef int (*cx_compare_func)(const void *left, const void *right); /** * Compares two integers of type int. @@ -74,10 +68,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int(const void *i1, const void *i2); /** * Compares two integers of type int. @@ -89,8 +81,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int(int i1, int i2); +CX_EXPORT int cx_vcmp_int(int i1, int i2); /** * Compares two integers of type long int. @@ -104,10 +95,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_longint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_longint(const void *i1, const void *i2); /** * Compares two integers of type long int. @@ -119,8 +108,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_longint(long int i1, long int i2); +CX_EXPORT int cx_vcmp_longint(long int i1, long int i2); /** * Compares two integers of type long long. @@ -134,10 +122,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_longlong(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_longlong(const void *i1, const void *i2); /** * Compares two integers of type long long. @@ -149,8 +135,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_longlong(long long int i1, long long int i2); +CX_EXPORT int cx_vcmp_longlong(long long int i1, long long int i2); /** * Compares two integers of type int16_t. @@ -164,10 +149,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int16(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int16(const void *i1, const void *i2); /** * Compares two integers of type int16_t. @@ -179,8 +162,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int16(int16_t i1, int16_t i2); +CX_EXPORT int cx_vcmp_int16(int16_t i1, int16_t i2); /** * Compares two integers of type int32_t. @@ -194,10 +176,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int32(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int32(const void *i1, const void *i2); /** * Compares two integers of type int32_t. @@ -209,8 +189,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int32(int32_t i1, int32_t i2); +CX_EXPORT int cx_vcmp_int32(int32_t i1, int32_t i2); /** * Compares two integers of type int64_t. @@ -224,10 +203,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_int64(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_int64(const void *i1, const void *i2); /** * Compares two integers of type int64_t. @@ -239,8 +216,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_int64(int64_t i1, int64_t i2); +CX_EXPORT int cx_vcmp_int64(int64_t i1, int64_t i2); /** * Compares two integers of type unsigned int. @@ -254,10 +230,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint(const void *i1, const void *i2); /** * Compares two integers of type unsigned int. @@ -269,8 +243,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint(unsigned int i1, unsigned int i2); +CX_EXPORT int cx_vcmp_uint(unsigned int i1, unsigned int i2); /** * Compares two integers of type unsigned long int. @@ -284,10 +257,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ulongint(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ulongint(const void *i1, const void *i2); /** * Compares two integers of type unsigned long int. @@ -299,8 +270,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); +CX_EXPORT int cx_vcmp_ulongint(unsigned long int i1, unsigned long int i2); /** * Compares two integers of type unsigned long long. @@ -314,10 +284,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ulonglong(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ulonglong(const void *i1, const void *i2); /** * Compares two integers of type unsigned long long. @@ -329,8 +297,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); +CX_EXPORT int cx_vcmp_ulonglong(unsigned long long int i1, unsigned long long int i2); /** * Compares two integers of type uint16_t. @@ -344,10 +311,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint16(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint16(const void *i1, const void *i2); /** * Compares two integers of type uint16_t. @@ -359,8 +324,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint16(uint16_t i1, uint16_t i2); +CX_EXPORT int cx_vcmp_uint16(uint16_t i1, uint16_t i2); /** * Compares two integers of type uint32_t. @@ -374,10 +338,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint32(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint32(const void *i1, const void *i2); /** * Compares two integers of type uint32_t. @@ -389,8 +351,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint32(uint32_t i1, uint32_t i2); +CX_EXPORT int cx_vcmp_uint32(uint32_t i1, uint32_t i2); /** * Compares two integers of type uint64_t. @@ -404,10 +365,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uint64(const void *i1, const void *i2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uint64(const void *i1, const void *i2); /** * Compares two integers of type uint64_t. @@ -419,8 +378,34 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uint64(uint64_t i1, uint64_t i2); +CX_EXPORT int cx_vcmp_uint64(uint64_t i1, uint64_t i2); + +/** + * Compares two integers of type size_t. + * + * @note the parameters deliberately have type @c void* to be + * compatible with #cx_compare_func without the need of a cast. + * + * @param i1 pointer to size_t one + * @param i2 pointer to size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_size(const void *i1, const void *i2); + +/** + * Compares two integers of type size_t. + * + * @param i1 size_t one + * @param i2 size_t two + * @retval -1 if the left argument is less than the right argument + * @retval 0 if both arguments are equal + * @retval 1 if the left argument is greater than the right argument + */ +cx_attr_nodiscard +CX_EXPORT int cx_vcmp_size(size_t i1, size_t i2); /** * Compares two real numbers of type float with precision 1e-6f. @@ -434,10 +419,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_float(const void *f1, const void *f2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_float(const void *f1, const void *f2); /** * Compares two real numbers of type float with precision 1e-6f. @@ -449,8 +432,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_float(float f1, float f2); +CX_EXPORT int cx_vcmp_float(float f1, float f2); /** * Compares two real numbers of type double with precision 1e-14. @@ -464,10 +446,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_double(const void *d1, const void *d2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_double(const void *d1, const void *d2); /** * Compares two real numbers of type double with precision 1e-14. @@ -479,8 +459,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_double(double d1, double d2); +CX_EXPORT int cx_vcmp_double(double d1, double d2); /** * Compares the integer representation of two pointers. @@ -494,10 +473,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_intptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_intptr(const void *ptr1, const void *ptr2); /** * Compares the integer representation of two pointers. @@ -509,8 +486,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); +CX_EXPORT int cx_vcmp_intptr(intptr_t ptr1, intptr_t ptr2); /** * Compares the unsigned integer representation of two pointers. @@ -524,10 +500,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_uintptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_uintptr(const void *ptr1, const void *ptr2); /** * Compares the unsigned integer representation of two pointers. @@ -539,8 +513,7 @@ * @retval 1 if the left argument is greater than the right argument */ cx_attr_nodiscard -cx_attr_export -int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); +CX_EXPORT int cx_vcmp_uintptr(uintptr_t ptr1, uintptr_t ptr2); /** * Compares the pointers specified in the arguments without dereferencing. @@ -551,10 +524,8 @@ * @retval 0 if both arguments are equal * @retval 1 if the left argument is greater than the right argument */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cx_cmp_ptr(const void *ptr1, const void *ptr2); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cx_cmp_ptr(const void *ptr1, const void *ptr2); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/hash_key.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/hash_key.h Sat Nov 08 23:06:11 2025 +0100 @@ -46,14 +46,17 @@ /** Internal structure for a key within a hash map. */ struct cx_hash_key_s { - /** The key data. */ + /** + * The key data. + * May be NULL when the hash is collision-free. + */ const void *data; /** * The key data length. */ size_t len; /** The hash value of the key data. */ - unsigned hash; + uint64_t hash; }; /** @@ -76,8 +79,45 @@ * @see cx_hash_key() */ cx_attr_nonnull -cx_attr_export -void cx_hash_murmur(CxHashKey *key); +CX_EXPORT void cx_hash_murmur(CxHashKey *key); + +/** + * Mixes up a 32-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +CX_EXPORT uint32_t cx_hash_u32(uint32_t x); + +/** + * Mixes up a 64-bit integer to be used as a hash. + * + * This function produces no collisions and has a good statistical distribution. + * + * @param x the integer + * @return the hash + */ +CX_EXPORT uint64_t cx_hash_u64(uint64_t x); + +/** + * Computes a hash key from a 32-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_u32(uint32_t x); + +/** + * Computes a hash key from a 64-bit integer. + * + * @param x the integer + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_u64(uint64_t x); /** * Computes a hash key from a string. @@ -87,10 +127,22 @@ * @param str the string * @return the hash key */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -CxHashKey cx_hash_key_str(const char *str); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxHashKey cx_hash_key_str(const char *str); + +/** + * Computes a hash key from a string. + * + * Use this function when the string is represented + * as an unsigned char array. + * + * The string needs to be zero-terminated. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxHashKey cx_hash_key_ustr(const unsigned char *str); /** * Computes a hash key from a byte array. @@ -99,13 +151,8 @@ * @param len the length * @return the hash key */ -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -CxHashKey cx_hash_key_bytes( - const unsigned char *bytes, - size_t len -); +cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT CxHashKey cx_hash_key_bytes(const unsigned char *bytes, size_t len); /** * Computes a hash key for an arbitrary object. @@ -115,16 +162,21 @@ * used for data exchange with different machines. * * @param obj a pointer to an arbitrary object - * @param len the length of object in memory + * @param len the length of the object in memory * @return the hash key */ cx_attr_nodiscard cx_attr_access_r(1, 2) -cx_attr_export -CxHashKey cx_hash_key( - const void *obj, - size_t len -); +CX_EXPORT CxHashKey cx_hash_key(const void *obj, size_t len); + +/** + * Computes a hash key from a UCX string. + * + * @param str the string + * @return the hash key + */ +cx_attr_nodiscard +CX_EXPORT CxHashKey cx_hash_key_cxstr(cxstring str); /** * Computes a hash key from a UCX string. @@ -133,20 +185,91 @@ * @return the hash key */ cx_attr_nodiscard -static inline CxHashKey cx_hash_key_cxstr(cxstring str) { - return cx_hash_key(str.ptr, str.length); -} +CX_EXPORT CxHashKey cx_hash_key_mutstr(cxmutstr str); /** - * Computes a hash key from a UCX string. + * The identity function for the CX_HASH_KEY() macro. + * You should never need to use this manually. + * + * @param key the key + * @return a copy of the key + */ +cx_attr_nodiscard +CX_INLINE CxHashKey cx_hash_key_identity(CxHashKey key) { + return key; +} + +#ifndef __cplusplus +/** + * Creates a hash key from any of the supported types with implicit length. + * + * Does nothing when passing a CxHashkey. + * + * Supported types are UCX strings, zero-terminated C strings, + * and 32-bit or 64-bit unsigned integers. * - * @param str (@c cxstring or @c cxmutstr) the string - * @return (@c CxHashKey) the hash key + * @param key the key data + * @returns the @c CxHashKey */ -#define cx_hash_key_cxstr(str) cx_hash_key_cxstr(cx_strcast(str)) +#define CX_HASH_KEY(key) _Generic((key), \ + CxHashKey: cx_hash_key_identity, \ + cxstring: cx_hash_key_cxstr, \ + cxmutstr: cx_hash_key_mutstr, \ + char*: cx_hash_key_str, \ + const char*: cx_hash_key_str, \ + unsigned char*: cx_hash_key_ustr, \ + const unsigned char*: cx_hash_key_ustr, \ + uint32_t: cx_hash_key_u32, \ + uint64_t: cx_hash_key_u64) \ + (key) +#endif // __cplusplus + +/** + * Compare function for hash keys. + * + * The pointers are untyped to be compatible with the cx_compare_func signature. + * + * @param left (@c CxHashKey*) the first key + * @param right (@c CxHashKey*) the second key + * @return zero when the keys equal, non-zero when they differ + */ +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_hash_key_cmp(const void *left, const void *right); #ifdef __cplusplus } // extern "C" + +// ---------------------------------------------------------- +// Overloads of CX_HASH_KEY (the C++ version of a _Generic) +// ---------------------------------------------------------- + +CX_CPPDECL CxHashKey CX_HASH_KEY(CxHashKey key) { + return key; +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(cxstring str) { + return cx_hash_key_cxstr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(cxmutstr str) { + return cx_hash_key_mutstr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(const char *str) { + return cx_hash_key_str(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(const unsigned char *str) { + return cx_hash_key_ustr(str); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(uint32_t key) { + return cx_hash_key_u32(key); +} + +CX_CPPDECL CxHashKey CX_HASH_KEY(uint64_t key) { + return cx_hash_key_u64(key); +} #endif #endif // UCX_HASH_KEY_H
--- a/ucx/cx/hash_map.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/hash_map.h Sat Nov 08 23:06:11 2025 +0100 @@ -73,7 +73,8 @@ * copies of the added elements. * * @note Iterators provided by this hash map implementation provide the remove operation. - * The index value of an iterator is incremented when the iterator advanced without removal. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param allocator the allocator to use @@ -82,15 +83,9 @@ * @param buckets the initial number of buckets in this hash map * @return a pointer to the new hash map */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxMapFree, 1) -cx_attr_export -CxMap *cxHashMapCreate( - const CxAllocator *allocator, - size_t itemsize, - size_t buckets -); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) +CX_EXPORT CxMap *cxHashMapCreate(const CxAllocator *allocator, + size_t itemsize, size_t buckets); /** * Creates a new hash map with a default number of buckets. @@ -99,7 +94,8 @@ * copies of the added elements. * * @note Iterators provided by this hash map implementation provide the remove operation. - * The index value of an iterator is incremented when the iterator advanced without removal. + * The index value of an iterator is incremented when the iterator advanced without + * removing an entry. * In other words, when the iterator is finished, @c index==size . * * @param itemsize (@c size_t) the size of one element @@ -111,10 +107,10 @@ * 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 + * 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 + * 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. * @@ -127,8 +123,7 @@ * @retval non-zero if a memory allocation error occurred */ cx_attr_nonnull -cx_attr_export -int cxMapRehash(CxMap *map); +CX_EXPORT int cxMapRehash(CxMap *map); #ifdef __cplusplus
--- a/ucx/cx/iterator.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/iterator.h Sat Nov 08 23:06:11 2025 +0100 @@ -50,7 +50,10 @@ * True if the iterator points to valid data. */ bool (*valid)(const void *); - + /** + * Original implementation in case the function needs to be wrapped. + */ + bool (*valid_impl)(const void *); /** * Returns a pointer to the current element. * @@ -62,7 +65,6 @@ * Original implementation in case the function needs to be wrapped. */ void *(*current_impl)(const void *); - /** * Advances the iterator. * @@ -70,9 +72,13 @@ */ void (*next)(void *); /** + * Original implementation in case the function needs to be wrapped. + */ + void (*next_impl)(void *); + /** * Indicates whether this iterator may remove elements. */ - bool mutating; + bool allow_remove; /** * Internal flag for removing the current element when advancing. */ @@ -108,16 +114,7 @@ /** * Handle for the source collection, if any. */ - union { - /** - * Access for mutating iterators. - */ - void *m; - /** - * Access for normal iterators. - */ - const void *c; - } src_handle; + void *src_handle; /** * If the iterator is position-aware, contains the index of the element in the underlying collection. @@ -141,11 +138,11 @@ * Iterator type. * * An iterator points to a certain element in a (possibly unbounded) chain of elements. - * Iterators that are based on collections (which have a defined "first" element), are supposed + * 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, - * any concurrent mutation of the collection other than by this iterator makes this iterator invalid, + * any concurrent mutation of the collection other than by this iterator makes this iterator obsolete, * and it must not be used anymore. */ typedef struct cx_iterator_s CxIterator; @@ -178,13 +175,12 @@ #define cxIteratorNext(iter) (iter).base.next(&iter) /** - * Flags the current element for removal, if this iterator is mutating. - * - * Does nothing for non-mutating iterators. + * Flags the current element for removal if the iterator allows it. * * @param iter the iterator + * @return @c true if removal is allowed, @c false otherwise */ -#define cxIteratorFlagRemoval(iter) (iter).base.remove |= (iter).base.mutating +#define cxIteratorFlagRemoval(iter) ((iter).base.remove = (iter).base.allow_remove) /** * Obtains a reference to an arbitrary iterator. @@ -210,7 +206,7 @@ /** * Creates an iterator for the specified plain array. * - * The @p array can be @c NULL in which case the iterator will be immediately + * The @p array can be @c NULL, in which case the iterator will be immediately * initialized such that #cxIteratorValid() returns @c false. * * This iterator yields the addresses of the array elements. @@ -218,23 +214,6 @@ * use cxIteratorPtr() to create an iterator which directly * yields the stored pointers. * - * @param array a pointer to the array (can be @c NULL) - * @param elem_size the size of one array element - * @param elem_count the number of elements in the array - * @return an iterator for the specified array - * @see cxIteratorPtr() - */ -cx_attr_nodiscard -cx_attr_export -CxIterator cxIterator( - const void *array, - size_t elem_size, - size_t elem_count -); - -/** - * Creates a mutating iterator for the specified plain array. - * * While the iterator is in use, the array may only be altered by removing * elements through #cxIteratorFlagRemoval(). Every other change to the array * will bring this iterator to an undefined state. @@ -244,67 +223,45 @@ * moving all subsequent elements by one. Usually, when the order of elements is * not important, this parameter should be set to @c false. * - * The @p array can be @c NULL in which case the iterator will be immediately - * initialized such that #cxIteratorValid() returns @c false. - * - * * @param array a pointer to the array (can be @c NULL) * @param elem_size the size of one array element * @param elem_count the number of elements in the array * @param remove_keeps_order @c true if the order of elements must be preserved * when removing an element * @return an iterator for the specified array + * @see cxIteratorPtr() */ cx_attr_nodiscard -cx_attr_export -CxIterator cxMutIterator( - void *array, - size_t elem_size, - size_t elem_count, - bool remove_keeps_order -); +CX_EXPORT CxIterator cxIterator(const void *array, + size_t elem_size, size_t elem_count, bool remove_keeps_order); /** * Creates an iterator for the specified plain pointer array. * * This iterator assumes that every element in the array is a pointer - * and yields exactly those pointers during iteration (while in contrast - * an iterator created with cxIterator() would return the addresses - * of those pointers within the array). + * and yields exactly those pointers during iteration (on the other + * hand, an iterator created with cxIterator() would return the + * addresses of those pointers within the array). * - * @param array a pointer to the array (can be @c NULL) - * @param elem_count the number of elements in the array - * @return an iterator for the specified array - * @see cxIterator() - */ -cx_attr_nodiscard -cx_attr_export -CxIterator cxIteratorPtr( - const void *array, - size_t elem_count -); - -/** - * Creates a mutating iterator for the specified plain pointer array. + * While the iterator is in use, the array may only be altered by removing + * elements through #cxIteratorFlagRemoval(). Every other change to the array + * will bring this iterator to an undefined state. * - * This is the mutating variant of cxIteratorPtr(). See also - * cxMutIterator(). + * When @p remove_keeps_order is set to @c false, removing an element will only + * move the last element to the position of the removed element, instead of + * moving all subsequent elements by one. Usually, when the order of elements is + * not important, this parameter should be set to @c false. * * @param array a pointer to the array (can be @c NULL) * @param elem_count the number of elements in the array * @param remove_keeps_order @c true if the order of elements must be preserved * when removing an element * @return an iterator for the specified array - * @see cxMutIterator() - * @see cxIteratorPtr() + * @see cxIterator() */ cx_attr_nodiscard -cx_attr_export -CxIterator cxMutIteratorPtr( - void *array, - size_t elem_count, - bool remove_keeps_order -); +CX_EXPORT CxIterator cxIteratorPtr(const void *array, size_t elem_count, + bool remove_keeps_order); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/json.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/json.h Sat Nov 08 23:06:11 2025 +0100 @@ -90,11 +90,11 @@ */ CX_JSON_TOKEN_STRING, /** - * A number token that can be represented as integer. + * A number token that can be represented as an integer. */ CX_JSON_TOKEN_INTEGER, /** - * A number token that cannot be represented as integer. + * A number token that cannot be represented as an integer. */ CX_JSON_TOKEN_NUMBER, /** @@ -196,11 +196,11 @@ */ typedef struct cx_mutstr_s CxJsonString; /** - * Type alias for a number that can be represented as 64-bit signed integer. + * Type alias for a number that can be represented as a 64-bit signed integer. */ typedef int64_t CxJsonInteger; /** - * Type alias for number that is not an integer. + * Type alias for a number that is not an integer. */ typedef double CxJsonNumber; /** @@ -272,27 +272,27 @@ */ union { /** - * The array data if type is #CX_JSON_ARRAY. + * The array data if the type is #CX_JSON_ARRAY. */ CxJsonArray array; /** - * The object data if type is #CX_JSON_OBJECT. + * The object data if the type is #CX_JSON_OBJECT. */ CxJsonObject object; /** - * The string data if type is #CX_JSON_STRING. + * The string data if the type is #CX_JSON_STRING. */ CxJsonString string; /** - * The integer if type is #CX_JSON_INTEGER. + * The integer if the type is #CX_JSON_INTEGER. */ CxJsonInteger integer; /** - * The number if type is #CX_JSON_NUMBER. + * The number if the type is #CX_JSON_NUMBER. */ CxJsonNumber number; /** - * The literal type if type is #CX_JSON_LITERAL. + * The literal type if the type is #CX_JSON_LITERAL. */ CxJsonLiteral literal; } value; @@ -377,7 +377,7 @@ }; /** - * Status codes for the json interface. + * Status codes for the JSON interface. */ enum cx_json_status { /** @@ -391,7 +391,7 @@ /** * The input ends unexpectedly. * - * Refill the buffer with cxJsonFill() to complete the json data. + * Refill the buffer with cxJsonFill() to complete the JSON data. */ CX_JSON_INCOMPLETE_DATA, /** @@ -400,7 +400,7 @@ * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_JSON_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_JSON_OK, /** @@ -412,7 +412,7 @@ */ CX_JSON_BUFFER_ALLOC_FAILED, /** - * Allocating memory for a json value failed. + * Allocating memory for a JSON value failed. */ CX_JSON_VALUE_ALLOC_FAILED, /** @@ -426,7 +426,7 @@ }; /** - * Typedef for the json status enum. + * Typedef for the JSON status enum. */ typedef enum cx_json_status CxJsonStatus; @@ -445,7 +445,7 @@ /** * The maximum number of fractional digits in a number value. * The default value is 6 and values larger than 15 are reduced to 15. - * Note, that the actual number of digits may be lower, depending on the concrete number. + * Note that the actual number of digits may be lower, depending on the concrete number. */ uint8_t frac_max_digits; /** @@ -465,7 +465,7 @@ }; /** - * Typedef for the json writer. + * Typedef for the JSON writer. */ typedef struct cx_json_writer_s CxJsonWriter; @@ -475,8 +475,7 @@ * @return new JSON writer settings */ cx_attr_nodiscard -cx_attr_export -CxJsonWriter cxJsonWriterCompact(void); +CX_EXPORT CxJsonWriter cxJsonWriterCompact(void); /** * Creates a default writer configuration for pretty output. @@ -485,8 +484,7 @@ * @return new JSON writer settings */ cx_attr_nodiscard -cx_attr_export -CxJsonWriter cxJsonWriterPretty(bool use_spaces); +CX_EXPORT CxJsonWriter cxJsonWriterPretty(bool use_spaces); /** * Writes a JSON value to a buffer or stream. @@ -496,9 +494,8 @@ * that the data is only partially written when an error occurs with no * way to indicate how much data was written. * To avoid this problem, you can use a CxBuffer as @p target which is - * unlikely to fail a write operation and either use the buffer's flush - * feature to relay the data or use the data in the buffer manually to - * write it to the actual target. + * unlikely to fail a write operation. You can, for example, use the buffer's flush + * feature to relay the data. * * @param target the buffer or stream where to write to * @param value the value that shall be written @@ -508,49 +505,38 @@ * @retval non-zero when no or not all data could be written */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -int cxJsonWrite( - void* target, - const CxJsonValue* value, - cx_write_func wfunc, - const CxJsonWriter* settings -); +CX_EXPORT int cxJsonWrite(void* target, const CxJsonValue* value, + cx_write_func wfunc, const CxJsonWriter* settings); /** - * Initializes the json interface. + * Initializes the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @param allocator the allocator that shall be used for the produced values * @see cxJsonDestroy() */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxJsonInit(CxJson *json, const CxAllocator *allocator); +CX_EXPORT void cxJsonInit(CxJson *json, const CxAllocator *allocator); /** - * Destroys the json interface. + * Destroys the JSON interface. * - * @param json the json interface + * @param json the JSON interface * @see cxJsonInit() */ cx_attr_nonnull -cx_attr_export -void cxJsonDestroy(CxJson *json); +CX_EXPORT void cxJsonDestroy(CxJson *json); /** - * Destroys and re-initializes the json interface. + * Destroys and re-initializes the JSON interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * - * @param json the json interface + * @param json the JSON interface */ cx_attr_nonnull -static inline void cxJsonReset(CxJson *json) { - const CxAllocator *allocator = json->allocator; - cxJsonDestroy(json); - cxJsonInit(json, allocator); -} +CX_EXPORT void cxJsonReset(CxJson *json); /** * Fills the input buffer. @@ -563,48 +549,30 @@ * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param buf the source buffer * @param len the length of the source buffer * @retval zero success * @retval non-zero internal allocation error * @see cxJsonFill() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonFilln(CxJson *json, const char *buf, size_t len); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonFilln(CxJson *json, const char *buf, size_t len); + -#ifdef __cplusplus -} // extern "C" - +/** + * Internal function, do not use. + * + * @param json the JSON interface + * @param str the string + * @retval zero success + * @retval non-zero internal allocation error + */ cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxstring str -) { +CX_INLINE int cx_json_fill(CxJson *json, cxstring str) { return cxJsonFilln(json, str.ptr, str.length); } -cx_attr_nonnull -static inline int cxJsonFill( - CxJson *json, - cxmutstr str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxJsonFill( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer. * @@ -616,53 +584,13 @@ * the additional data is appended - inevitably leading to * an allocation of a new buffer and copying the previous contents. * - * @param json the json interface + * @param json the JSON interface * @param str the source string * @retval zero success * @retval non-zero internal allocation error * @see cxJsonFilln() */ -#define cxJsonFill(json, str) _Generic((str), \ - cxstring: cx_json_fill_cxstr, \ - cxmutstr: cx_json_fill_mutstr, \ - char*: cx_json_fill_str, \ - const char*: cx_json_fill_str) \ - (json, str) - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_cxstr( - CxJson *json, - cxstring str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -static inline int cx_json_fill_mutstr( - CxJson *json, - cxmutstr str -) { - return cxJsonFilln(json, str.ptr, str.length); -} - -/** - * @copydoc cxJsonFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_json_fill_str( - CxJson *json, - const char *str -) { - return cxJsonFilln(json, str, strlen(str)); -} -#endif +#define cxJsonFill(json, str) cx_json_fill(json, cx_strcast(str)) /** * Creates a new (empty) JSON object. @@ -673,8 +601,7 @@ * @see cxJsonArrAddValues() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); +CX_EXPORT CxJsonValue* cxJsonCreateObj(const CxAllocator* allocator); /** * Creates a new (empty) JSON array. @@ -685,8 +612,7 @@ * @see cxJsonArrAddValues() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); +CX_EXPORT CxJsonValue* cxJsonCreateArr(const CxAllocator* allocator); /** * Creates a new JSON number value. @@ -698,8 +624,7 @@ * @see cxJsonArrAddNumbers() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); +CX_EXPORT CxJsonValue* cxJsonCreateNumber(const CxAllocator* allocator, double num); /** * Creates a new JSON number value based on an integer. @@ -711,8 +636,7 @@ * @see cxJsonArrAddIntegers() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); +CX_EXPORT CxJsonValue* cxJsonCreateInteger(const CxAllocator* allocator, int64_t num); /** * Creates a new JSON string. @@ -724,11 +648,8 @@ * @see cxJsonObjPutString() * @see cxJsonArrAddStrings() */ -cx_attr_nodiscard -cx_attr_nonnull_arg(2) -cx_attr_cstr_arg(2) -cx_attr_export -CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); +cx_attr_nodiscard cx_attr_nonnull_arg(2) cx_attr_cstr_arg(2) +CX_EXPORT CxJsonValue* cxJsonCreateString(const CxAllocator* allocator, const char *str); /** * Creates a new JSON string. @@ -741,8 +662,7 @@ * @see cxJsonArrAddCxStrings() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); +CX_EXPORT CxJsonValue* cxJsonCreateCxString(const CxAllocator* allocator, cxstring str); /** * Creates a new JSON literal. @@ -754,8 +674,7 @@ * @see cxJsonArrAddLiterals() */ cx_attr_nodiscard -cx_attr_export -CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cxJsonCreateLiteral(const CxAllocator* allocator, CxJsonLiteral lit); /** * Adds number values to a JSON array. @@ -766,10 +685,8 @@ * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddNumbers(CxJsonValue* arr, const double* num, size_t count); /** * Adds number values, of which all are integers, to a JSON array. @@ -780,10 +697,8 @@ * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddIntegers(CxJsonValue* arr, const int64_t* num, size_t count); /** * Adds strings to a JSON array. @@ -797,10 +712,8 @@ * @retval non-zero allocation failure * @see cxJsonArrAddCxStrings() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddStrings(CxJsonValue* arr, const char* const* str, size_t count); /** * Adds strings to a JSON array. @@ -814,10 +727,8 @@ * @retval non-zero allocation failure * @see cxJsonArrAddStrings() */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddCxStrings(CxJsonValue* arr, const cxstring* str, size_t count); /** * Adds literals to a JSON array. @@ -828,10 +739,8 @@ * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddLiterals(CxJsonValue* arr, const CxJsonLiteral* lit, size_t count); /** * Add arbitrary values to a JSON array. @@ -845,10 +754,8 @@ * @retval zero success * @retval non-zero allocation failure */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxJsonArrAddValues(CxJsonValue* arr, CxJsonValue* const* val, size_t count); /** * Adds or replaces a value within a JSON object. @@ -865,8 +772,7 @@ * @retval non-zero allocation failure */ cx_attr_nonnull -cx_attr_export -int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); +CX_EXPORT int cxJsonObjPut(CxJsonValue* obj, cxstring name, CxJsonValue* child); /** * Creates a new JSON object and adds it to an existing object. @@ -878,8 +784,7 @@ * @see cxJsonCreateObj() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name); /** * Creates a new JSON array and adds it to an object. @@ -891,8 +796,7 @@ * @see cxJsonCreateArr() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); +CX_EXPORT CxJsonValue* cxJsonObjPutArr(CxJsonValue* obj, cxstring name); /** * Creates a new JSON number and adds it to an object. @@ -905,8 +809,7 @@ * @see cxJsonCreateNumber() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); +CX_EXPORT CxJsonValue* cxJsonObjPutNumber(CxJsonValue* obj, cxstring name, double num); /** * Creates a new JSON number, based on an integer, and adds it to an object. @@ -919,8 +822,7 @@ * @see cxJsonCreateInteger() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); +CX_EXPORT CxJsonValue* cxJsonObjPutInteger(CxJsonValue* obj, cxstring name, int64_t num); /** * Creates a new JSON string and adds it to an object. @@ -934,10 +836,8 @@ * @see cxJsonObjPut() * @see cxJsonCreateString() */ -cx_attr_nonnull -cx_attr_cstr_arg(3) -cx_attr_export -CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); +cx_attr_nonnull cx_attr_cstr_arg(3) +CX_EXPORT CxJsonValue* cxJsonObjPutString(CxJsonValue* obj, cxstring name, const char* str); /** * Creates a new JSON string and adds it to an object. @@ -952,8 +852,7 @@ * @see cxJsonCreateCxString() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); +CX_EXPORT CxJsonValue* cxJsonObjPutCxString(CxJsonValue* obj, cxstring name, cxstring str); /** * Creates a new JSON literal and adds it to an object. @@ -966,22 +865,20 @@ * @see cxJsonCreateLiteral() */ cx_attr_nonnull -cx_attr_export -CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); +CX_EXPORT CxJsonValue* cxJsonObjPutLiteral(CxJsonValue* obj, cxstring name, CxJsonLiteral lit); /** * Recursively deallocates the memory of a JSON value. * * @remark The type of each deallocated value will be changed - * to #CX_JSON_NOTHING and values of such type will be skipped - * by the de-allocation. That means, this function protects + * to #CX_JSON_NOTHING, and values of such a type will be skipped + * by the deallocation. That means this function protects * you from double-frees when you are accidentally freeing * a nested value and then the parent value (or vice versa). * * @param value the value */ -cx_attr_export -void cxJsonValueFree(CxJsonValue *value); +CX_EXPORT void cxJsonValueFree(CxJsonValue *value); /** * Tries to obtain the next JSON value. @@ -993,7 +890,7 @@ * add the missing data with another invocation of cxJsonFill() * and then repeat the call to cxJsonNext(). * - * @param json the json interface + * @param json the JSON interface * @param value a pointer where the next value shall be stored * @retval CX_JSON_NO_ERROR successfully retrieve the @p value * @retval CX_JSON_NO_DATA there is no (more) data in the buffer to read from @@ -1005,10 +902,8 @@ * @retval CX_JSON_FORMAT_ERROR_NUMBER the JSON text contains an illegally formatted number * @retval CX_JSON_FORMAT_ERROR_UNEXPECTED_TOKEN JSON syntax error */ -cx_attr_nonnull -cx_attr_access_w(2) -cx_attr_export -CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); /** * Checks if the specified value is a JSON object. @@ -1018,7 +913,7 @@ * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsObject(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsObject(const CxJsonValue *value) { return value->type == CX_JSON_OBJECT; } @@ -1030,7 +925,7 @@ * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsArray(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsArray(const CxJsonValue *value) { return value->type == CX_JSON_ARRAY; } @@ -1042,14 +937,14 @@ * @retval false otherwise */ cx_attr_nonnull -static inline bool cxJsonIsString(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsString(const CxJsonValue *value) { return value->type == CX_JSON_STRING; } /** * Checks if the specified value is a JSON number. * - * This function will return true for both floating point and + * This function will return true for both floating-point and * integer numbers. * * @param value a pointer to the value @@ -1058,7 +953,7 @@ * @see cxJsonIsInteger() */ cx_attr_nonnull -static inline bool cxJsonIsNumber(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsNumber(const CxJsonValue *value) { return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; } @@ -1071,7 +966,7 @@ * @see cxJsonIsNumber() */ cx_attr_nonnull -static inline bool cxJsonIsInteger(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsInteger(const CxJsonValue *value) { return value->type == CX_JSON_INTEGER; } @@ -1088,7 +983,7 @@ * @see cxJsonIsNull() */ cx_attr_nonnull -static inline bool cxJsonIsLiteral(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsLiteral(const CxJsonValue *value) { return value->type == CX_JSON_LITERAL; } @@ -1102,14 +997,14 @@ * @see cxJsonIsFalse() */ cx_attr_nonnull -static inline bool cxJsonIsBool(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsBool(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; } /** * Checks if the specified value is @c true. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsFalse(v). * * @param value a pointer to the value @@ -1119,14 +1014,14 @@ * @see cxJsonIsFalse() */ cx_attr_nonnull -static inline bool cxJsonIsTrue(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsTrue(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; } /** * Checks if the specified value is @c false. * - * @remark Be advised, that this is not the same as + * @remark Be advised that this is different from * testing @c !cxJsonIsTrue(v). * * @param value a pointer to the value @@ -1136,7 +1031,7 @@ * @see cxJsonIsTrue() */ cx_attr_nonnull -static inline bool cxJsonIsFalse(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsFalse(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; } @@ -1149,7 +1044,7 @@ * @see cxJsonIsLiteral() */ cx_attr_nonnull -static inline bool cxJsonIsNull(const CxJsonValue *value) { +CX_INLINE bool cxJsonIsNull(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; } @@ -1162,11 +1057,8 @@ * @return the value represented as C string * @see cxJsonIsString() */ -cx_attr_nonnull -cx_attr_returns_nonnull -static inline char *cxJsonAsString(const CxJsonValue *value) { - return value->value.string.ptr; -} +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT char *cxJsonAsString(const CxJsonValue *value); /** * Obtains a UCX string from the given JSON value. @@ -1178,9 +1070,7 @@ * @see cxJsonIsString() */ cx_attr_nonnull -static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { - return cx_strcast(value->value.string); -} +CX_EXPORT cxstring cxJsonAsCxString(const CxJsonValue *value); /** * Obtains a mutable UCX string from the given JSON value. @@ -1192,12 +1082,10 @@ * @see cxJsonIsString() */ cx_attr_nonnull -static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { - return value->value.string; -} +CX_EXPORT cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value); /** - * Obtains a double-precision floating point value from the given JSON value. + * Obtains a double-precision floating-point value from the given JSON value. * * If the @p value is not a JSON number, the behavior is undefined. * @@ -1206,13 +1094,7 @@ * @see cxJsonIsNumber() */ cx_attr_nonnull -static inline double cxJsonAsDouble(const CxJsonValue *value) { - if (value->type == CX_JSON_INTEGER) { - return (double) value->value.integer; - } else { - return value->value.number; - } -} +CX_EXPORT double cxJsonAsDouble(const CxJsonValue *value); /** * Obtains a 64-bit signed integer from the given JSON value. @@ -1227,13 +1109,7 @@ * @see cxJsonIsInteger() */ cx_attr_nonnull -static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { - if (value->type == CX_JSON_INTEGER) { - return value->value.integer; - } else { - return (int64_t) value->value.number; - } -} +CX_EXPORT int64_t cxJsonAsInteger(const CxJsonValue *value); /** * Obtains a Boolean value from the given JSON value. @@ -1246,7 +1122,7 @@ * @see cxJsonIsLiteral() */ cx_attr_nonnull -static inline bool cxJsonAsBool(const CxJsonValue *value) { +CX_INLINE bool cxJsonAsBool(const CxJsonValue *value) { return value->value.literal == CX_JSON_TRUE; } @@ -1260,7 +1136,7 @@ * @see cxJsonIsArray() */ cx_attr_nonnull -static inline size_t cxJsonArrSize(const CxJsonValue *value) { +CX_INLINE size_t cxJsonArrSize(const CxJsonValue *value) { return value->value.array.array_size; } @@ -1278,10 +1154,8 @@ * @return the value at the specified index * @see cxJsonIsArray() */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); /** * Removes an element from a JSON array. @@ -1297,8 +1171,7 @@ * @see cxJsonIsArray() */ cx_attr_nonnull -cx_attr_export -CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index); +CX_EXPORT CxJsonValue *cxJsonArrRemove(CxJsonValue *value, size_t index); /** * Returns an iterator over the JSON array elements. @@ -1311,10 +1184,8 @@ * @return an iterator over the array elements * @see cxJsonIsArray() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxIterator cxJsonArrIter(const CxJsonValue *value); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxIterator cxJsonArrIter(const CxJsonValue *value); /** * Returns an iterator over the JSON object members. @@ -1328,55 +1199,18 @@ * @return an iterator over the object members * @see cxJsonIsObject() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxIterator cxJsonObjIter(const CxJsonValue *value); - -/** - * @copydoc cxJsonObjGet() - */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxIterator cxJsonObjIter(const CxJsonValue *value); /** - * @copydoc cxJsonObjRemove() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key */ -cx_attr_nonnull -cx_attr_export -CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name); - -#ifdef __cplusplus -} // extern "C" - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxstring name) { - return cx_json_obj_get_cxstr(value, name); -} - -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name); -static inline CxJsonValue *cxJsonObjGet(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} - -static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxstring name) { - return cx_json_obj_remove_cxstr(value, name); -} - -static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, cxmutstr name) { - return cx_json_obj_remove_cxstr(value, cx_strcast(name)); -} - -static inline CxJsonValue *cxJsonObjRemove(CxJsonValue *value, const char *name) { - return cx_json_obj_remove_cxstr(value, cx_str(name)); -} - -extern "C" { -#else /** * Returns a value corresponding to a key in a JSON object. * @@ -1391,31 +1225,16 @@ * @return the value corresponding to the key * @see cxJsonIsObject() */ -#define cxJsonObjGet(value, name) _Generic((name), \ - cxstring: cx_json_obj_get_cxstr, \ - cxmutstr: cx_json_obj_get_mutstr, \ - char*: cx_json_obj_get_str, \ - const char*: cx_json_obj_get_str) \ - (value, name) +#define cxJsonObjGet(value, name) cx_json_obj_get(value, cx_strcast(name)) /** - * @copydoc cxJsonObjGet() + * Internal function, do not use. + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key or @c NULL when the key is not part of the object */ cx_attr_nonnull -cx_attr_returns_nonnull -static inline CxJsonValue *cx_json_obj_get_mutstr(const CxJsonValue *value, cxmutstr name) { - return cx_json_obj_get_cxstr(value, cx_strcast(name)); -} - -/** - * @copydoc cxJsonObjGet() - */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_cstr_arg(2) -static inline CxJsonValue *cx_json_obj_get_str(const CxJsonValue *value, const char *name) { - return cx_json_obj_get_cxstr(value, cx_str(name)); -} +CX_EXPORT CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name); /** * Removes and returns a value corresponding to a key in a JSON object. @@ -1430,30 +1249,7 @@ * @return the value corresponding to the key or @c NULL when the key is not part of the object * @see cxJsonIsObject() */ -#define cxJsonObjRemove(value, name) _Generic((name), \ - cxstring: cx_json_obj_remove_cxstr, \ - cxmutstr: cx_json_obj_remove_mutstr, \ - char*: cx_json_obj_remove_str, \ - const char*: cx_json_obj_remove_str) \ - (value, name) - -/** - * @copydoc cxJsonObjRemove() - */ -cx_attr_nonnull -static inline CxJsonValue *cx_json_obj_remove_mutstr(CxJsonValue *value, cxmutstr name) { - return cx_json_obj_remove_cxstr(value, cx_strcast(name)); -} - -/** - * @copydoc cxJsonObjRemove() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline CxJsonValue *cx_json_obj_remove_str(CxJsonValue *value, const char *name) { - return cx_json_obj_remove_cxstr(value, cx_str(name)); -} -#endif +#define cxJsonObjRemove(value, name) cx_json_obj_remove(value, cx_strcast(name)) #ifdef __cplusplus }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/cx/kv_list.h Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,260 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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 kv_list.h + * @brief Linked list implementation with key/value-lookup. + * @author Mike Becker + * @author Olaf Wintermann + * @copyright 2-Clause BSD License + */ + +#ifndef UCX_KV_LIST_H +#define UCX_KV_LIST_H + +#include "common.h" +#include "list.h" +#include "map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @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, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @return the created list + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +CX_EXPORT CxList *cxKvListCreate(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size); + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. + * + * This function creates the list with cxKvListCreate() and immediately applies + * cxKvListAsMap(). If you want to use the returned object as a list, you can call + * cxKvListAsList() later. + * + * @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, and the list is not storing pointers, sort and find + * functions will not work) + * @param elem_size the size of each element in bytes + * @return the created list wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMapFree, 1) +CX_EXPORT CxMap *cxKvListCreateAsMap(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size); + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * The list will use cxDefaultAllocator and no comparator function. If you want + * to call functions that need a comparator, you must either set one immediately + * after list creation or use cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * After creating the list, it can also be used as a map after converting the pointer + * to a CxMap pointer with cxKvListAsMap(). + * When you want to use the list interface again, you can also convert the map pointer back + * with cxKvListAsList(). + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxList*) the created list + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateSimple(elem_size) cxKvListCreate(NULL, NULL, elem_size) + +/** + * Allocates a linked list with a lookup-map for storing elements with @p elem_size bytes each. + * + * The list will use cxDefaultAllocator and no comparator function. If you want + * to call functions that need a comparator, you must either set one immediately + * after list creation or use cxKvListCreate(). + * + * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr(). + * + * This macro behaves as if the list was created with cxKvListCreateSimple() and + * immediately followed up by cxKvListAsMap(). + * If you want to use the returned object as a list, you can call cxKvListAsList() later. + * + * @param elem_size (@c size_t) the size of each element in bytes + * @return (@c CxMap*) the created list wrapped into the CxMap interface + * @see cxKvListAsMap() + * @see cxKvListAsList() + */ +#define cxKvListCreateAsMapSimple(elem_size) cxKvListCreateAsMap(NULL, NULL, elem_size) + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param map a map pointer that was returned by a call to cxKvListAsMap() + * @return the original list pointer + */ +cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxList *cxKvListAsList(CxMap *map); + +/** + * Converts a map pointer belonging to a key-value-List back to the original list pointer. + * + * @param list a list created by cxKvListCreate() or cxKvListCreateSimple() + * @return a map pointer that lets you use the list as if it was a map + */ +cx_attr_nodiscard cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT CxMap *cxKvListAsMap(CxList *list); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list the list + * @param index the index of the element in the list + * @param key the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListSetKey() + */ +cx_attr_nonnull +CX_EXPORT int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key); + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list the list + * @param index the index the inserted element shall have + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see cxKvListInsert() + */ +cx_attr_nonnull +CX_EXPORT int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value); + +/** + * Sets or updates the key of a list item. + * + * This is, for example, useful when you have inserted an element using the CxList interface, + * and now you want to associate this element with a key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index of the element in the list + * @param key (any supported key type) the key + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListSetKey(list, index, key) cx_kv_list_set_key(list, index, CX_HASH_KEY(key)) + +/** + * Inserts an item into the list at the specified index and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param index (@c size_t) the index the inserted element shall have + * @param key (any supported key type) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure or the index is out of bounds + * @see CX_HASH_KEY() + */ +#define cxKvListInsert(list, index, key, value) cx_kv_list_insert(list, index, CX_HASH_KEY(key), value) + + +/** + * Removes the key of a list item. + * + * This can be useful if you want to explicitly remove an item from the lookup map. + * + * If no key is associated with the item, nothing happens, and this function returns zero. + * + * @param list the list + * @param index the index of the element in the list + * @retval zero success + * @retval non-zero the index is out of bounds + */ +cx_attr_nonnull +CX_EXPORT int cxKvListRemoveKey(CxList *list, size_t index); + +/** + * Returns the key of a list item. + * + * @param list the list + * @param index the index of the element in the list + * @return a pointer to the key or @c NULL when the index is out of bounds or the item does not have a key + */ +cx_attr_nonnull +CX_EXPORT const CxHashKey *cxKvListGetKey(CxList *list, size_t index); + +/** + * Adds an item into the list and associates it with the specified key. + * + * @param list (@c CxList*) the list + * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param value (@c void*) the value + * @retval zero success + * @retval non-zero memory allocation failure + */ +#define cxKvListAdd(list, key, value) cxKvListInsert(list, (list)->collection.size, key, value) + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // UCX_KV_LIST_H
--- a/ucx/cx/linked_list.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/linked_list.h Sat Nov 08 23:06:11 2025 +0100 @@ -44,11 +44,43 @@ #endif /** + * Metadata for a linked list. + */ +typedef struct cx_linked_list_s { + /** Base members. */ + struct cx_list_s base; + /** + * Location of the prev pointer (mandatory). + */ + off_t loc_prev; + /** + * Location of the next pointer (mandatory). + */ + off_t loc_next; + /** + * Location of the payload (mandatory). + */ + off_t loc_data; + /** + * Additional bytes to allocate @em behind the payload (e.g. for metadata). + */ + size_t extra_data_len; + /** + * Pointer to the first node. + */ + void *begin; + /** + * Pointer to the last node. + */ + void *end; +} cx_linked_list; + +/** * Allocates a linked list for storing elements with @p elem_size bytes each. * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set - * to cx_cmp_ptr(), if none is given. + * copies of the added elements, and the compare function will be automatically set + * to cx_cmp_ptr() if none is given. * * @param allocator the allocator for allocating the list nodes * (if @c NULL, the cxDefaultAllocator will be used) @@ -58,15 +90,9 @@ * @param elem_size the size of each element in bytes * @return the created list */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxListFree, 1) -cx_attr_export -CxList *cxLinkedListCreate( - const CxAllocator *allocator, - cx_compare_func comparator, - size_t elem_size -); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxListFree, 1) +CX_EXPORT CxList *cxLinkedListCreate(const CxAllocator *allocator, + cx_compare_func comparator, size_t elem_size); /** * Allocates a linked list for storing elements with @p elem_size bytes each. @@ -76,25 +102,25 @@ * after list creation or use cxLinkedListCreate(). * * If @p elem_size is #CX_STORE_POINTERS, the created list stores pointers instead of - * copies of the added elements and the compare function will be automatically set + * copies of the added elements, and the compare function will be automatically set * to cx_cmp_ptr(). * * @param elem_size (@c size_t) the size of each element in bytes * @return (@c CxList*) the created list */ #define cxLinkedListCreateSimple(elem_size) \ - cxLinkedListCreate(NULL, NULL, elem_size) + cxLinkedListCreate(NULL, NULL, elem_size) /** * Finds the node at a certain index. * * This function can be used to start at an arbitrary position within the list. - * If the search index is large than the start index, @p loc_advance must denote - * the location of some sort of @c next pointer (i.e. a pointer to the next node). + * If the search index is larger than the start index, @p loc_advance must denote + * the location of a @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). + * (e.g., in cases where traversing a list backwards is faster). + * In that case @p loc_advance must denote the location of a @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 @@ -102,15 +128,9 @@ * @param index the search index * @return the node found at the specified index */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -void *cx_linked_list_at( - const void *start, - size_t start_index, - ptrdiff_t loc_advance, - size_t index -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cx_linked_list_at(const void *start,size_t start_index, + ptrdiff_t loc_advance, size_t index); /** * Finds the node containing an element within a linked list. @@ -122,18 +142,12 @@ * @param elem a pointer to the element to find * @param found_index an optional pointer where the index of the found node * (given that @p start has index 0) is stored - * @return the index of the element, if found - unspecified if not found + * @return a pointer to the found node or @c NULL if no matching node was found */ cx_attr_nonnull_arg(1, 4, 5) -cx_attr_export -void *cx_linked_list_find( - const void *start, - ptrdiff_t loc_advance, - ptrdiff_t loc_data, - cx_compare_func cmp_func, - const void *elem, - size_t *found_index -); +CX_EXPORT void *cx_linked_list_find(const void *start, ptrdiff_t loc_advance, + ptrdiff_t loc_data, cx_compare_func cmp_func, const void *elem, + size_t *found_index); /** * Finds the first node in a linked list. @@ -146,13 +160,8 @@ * @param loc_prev the location of the @c prev pointer * @return a pointer to the first node */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -void *cx_linked_list_first( - const void *node, - ptrdiff_t loc_prev -); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT void *cx_linked_list_first(const void *node, ptrdiff_t loc_prev); /** * Finds the last node in a linked list. @@ -165,13 +174,8 @@ * @param loc_next the location of the @c next pointer * @return a pointer to the last node */ -cx_attr_nonnull -cx_attr_returns_nonnull -cx_attr_export -void *cx_linked_list_last( - const void *node, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_returns_nonnull +CX_EXPORT void *cx_linked_list_last(const void *node, ptrdiff_t loc_next); /** * Finds the predecessor of a node in case it is not linked. @@ -184,16 +188,11 @@ * @return the node or @c NULL if @p node has no predecessor */ cx_attr_nonnull -cx_attr_export -void *cx_linked_list_prev( - const void *begin, - ptrdiff_t loc_next, - const void *node -); +CX_EXPORT void *cx_linked_list_prev(const void *begin, ptrdiff_t loc_next, const void *node); /** * Adds a new node to a linked list. - * The node must not be part of any list already. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -204,18 +203,11 @@ * @param new_node a pointer to the node that shall be appended */ cx_attr_nonnull_arg(5) -cx_attr_export -void cx_linked_list_add( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node -); +CX_EXPORT void cx_linked_list_add(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node); /** * Prepends a new node to a linked list. - * The node must not be part of any list already. + * The node must not be part of any list yet. * * @remark One of the pointers @p begin or @p end may be @c NULL, but not both. * @@ -226,14 +218,7 @@ * @param new_node a pointer to the node that shall be prepended */ cx_attr_nonnull_arg(5) -cx_attr_export -void cx_linked_list_prepend( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node -); +CX_EXPORT void cx_linked_list_prepend(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node); /** * Links two nodes. @@ -244,13 +229,7 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull -cx_attr_export -void cx_linked_list_link( - void *left, - void *right, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_linked_list_link(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Unlinks two nodes. @@ -263,17 +242,11 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull -cx_attr_export -void cx_linked_list_unlink( - void *left, - void *right, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_linked_list_unlink(void *left, void *right, ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Inserts a new node after a given node of a linked list. - * The new node must not be part of any list already. + * The new node must not be part of any list yet. * * @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. @@ -286,19 +259,12 @@ * @param new_node a pointer to the node that shall be inserted */ cx_attr_nonnull_arg(6) -cx_attr_export -void cx_linked_list_insert( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node, - void *new_node -); +CX_EXPORT void cx_linked_list_insert(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, void *new_node); /** * Inserts a chain of nodes after a given node of a linked list. - * The chain must not be part of any list already. + * The chain must not be part of any list yet. * * If you do not explicitly specify the end of the chain, it will be determined by traversing * the @c next pointer. @@ -317,20 +283,12 @@ * @param insert_end a pointer to the last node of the chain (or NULL if the last node shall be determined) */ cx_attr_nonnull_arg(6) -cx_attr_export -void cx_linked_list_insert_chain( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node, - void *insert_begin, - void *insert_end -); +CX_EXPORT 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); /** * Inserts a node into a sorted linked list. - * The new node must not be part of any list already. + * The new node must not be part of any list yet. * * If the list starting with the node pointed to by @p begin is not sorted * already, the behavior is undefined. @@ -343,26 +301,19 @@ * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) -cx_attr_export -void cx_linked_list_insert_sorted( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *new_node, - cx_compare_func cmp_func -); +CX_EXPORT void cx_linked_list_insert_sorted(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func); /** * Inserts a chain of nodes into a sorted linked list. - * The chain must not be part of any list already. + * The chain must not be part of any list yet. * * If either the list starting with the node pointed to by @p begin or the list * starting with @p insert_begin is not sorted, the behavior is undefined. * * @attention In contrast to cx_linked_list_insert_chain(), the source chain * will be broken and inserted into the target list so that the resulting list - * will be sorted according to @p cmp_func. That means, each node in the source + * will be sorted according to @p cmp_func. That means each node in the source * chain may be re-linked with nodes from the target list. * * @param begin a pointer to the beginning node pointer (required) @@ -373,20 +324,55 @@ * @param cmp_func a compare function that will receive the node pointers */ cx_attr_nonnull_arg(1, 5, 6) -cx_attr_export -void cx_linked_list_insert_sorted_chain( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *insert_begin, - cx_compare_func cmp_func -); +CX_EXPORT void cx_linked_list_insert_sorted_chain(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func); + +/** + * Inserts a node into a sorted linked list if no other node with the same value already exists. + * The new node must not be part of any list yet. + * + * If the list starting with the node pointed to by @p begin is not sorted + * already, the behavior is undefined. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param new_node a pointer to the node that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @retval zero when the node was inserted + * @retval non-zero when a node with the same value already exists + */ +cx_attr_nonnull_arg(1, 5, 6) +CX_EXPORT int cx_linked_list_insert_unique(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *new_node, cx_compare_func cmp_func); + +/** + * Inserts a chain of nodes into a sorted linked list, avoiding duplicates. + * The chain must not be part of any list yet. + * + * If either the list starting with the node pointed to by @p begin or the list + * starting with @p insert_begin is not sorted, the behavior is undefined. + * + * @attention In contrast to cx_linked_list_insert_sorted(), not all nodes of the + * chain might be added. This function returns a new chain consisting of all the duplicates. + * + * @param begin a pointer to the beginning node pointer (required) + * @param end a pointer to the end node pointer (if your list has one) + * @param loc_prev the location of a @c prev pointer within your node struct (negative if your struct does not have one) + * @param loc_next the location of a @c next pointer within your node struct (required) + * @param insert_begin a pointer to the first node of the chain that shall be inserted + * @param cmp_func a compare function that will receive the node pointers + * @return a pointer to a new chain with all duplicates that were not inserted (or @c NULL when there were no duplicates) + */ +cx_attr_nonnull_arg(1, 5, 6) cx_attr_nodiscard +CX_EXPORT void *cx_linked_list_insert_unique_chain(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *insert_begin, cx_compare_func cmp_func); /** * Removes a chain of nodes from the linked list. * - * If one of the nodes to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If one of the nodes to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -405,20 +391,13 @@ * @return the actual number of nodes that were removed (can be less when the list did not have enough nodes) */ cx_attr_nonnull_arg(5) -cx_attr_export -size_t cx_linked_list_remove_chain( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node, - size_t num -); +CX_EXPORT size_t cx_linked_list_remove_chain(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node, size_t num); /** * Removes a node from the linked list. * - * If the node to remove is the beginning (resp. end) node of the list and if @p begin (resp. @p end) + * If the node to remove is the beginning (resp. end) node of the list, and if @p begin (resp. @p end) * addresses are provided, the pointers are adjusted accordingly. * * The following combinations of arguments are valid (more arguments are optional): @@ -435,15 +414,8 @@ * @param node the node to remove */ cx_attr_nonnull_arg(5) -static inline void cx_linked_list_remove( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - void *node -) { - cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); -} +CX_EXPORT void cx_linked_list_remove(void **begin, void **end, + ptrdiff_t loc_prev, ptrdiff_t loc_next, void *node); /** * Determines the size of a linked list starting with @p node. @@ -453,11 +425,7 @@ * @return the size of the list or zero if @p node is @c NULL */ cx_attr_nodiscard -cx_attr_export -size_t cx_linked_list_size( - const void *node, - ptrdiff_t loc_next -); +CX_EXPORT size_t cx_linked_list_size(const void *node, ptrdiff_t loc_next); /** * Sorts a linked list based on a comparison function. @@ -482,21 +450,14 @@ * @param cmp_func the compare function defining the sort order */ cx_attr_nonnull_arg(1, 6) -cx_attr_export -void cx_linked_list_sort( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next, - ptrdiff_t loc_data, - cx_compare_func cmp_func -); +CX_EXPORT 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); /** * Compares two lists element wise. * - * @attention Both list must have the same structure. + * @attention Both lists must have the same structure. * * @param begin_left the beginning of the left list (@c NULL denotes an empty list) * @param begin_right the beginning of the right list (@c NULL denotes an empty list) @@ -507,14 +468,8 @@ * right list, positive if the left list is larger than the right list, zero if both lists are equal. */ cx_attr_nonnull_arg(5) -cx_attr_export -int cx_linked_list_compare( - const void *begin_left, - const void *begin_right, - ptrdiff_t loc_advance, - ptrdiff_t loc_data, - cx_compare_func cmp_func -); +CX_EXPORT int cx_linked_list_compare(const void *begin_left, const void *begin_right, + ptrdiff_t loc_advance, ptrdiff_t loc_data, cx_compare_func cmp_func); /** * Reverses the order of the nodes in a linked list. @@ -525,13 +480,7 @@ * @param loc_next the location of a @c next pointer within your node struct (required) */ cx_attr_nonnull_arg(1) -cx_attr_export -void cx_linked_list_reverse( - void **begin, - void **end, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_linked_list_reverse(void **begin, void **end, ptrdiff_t loc_prev, ptrdiff_t loc_next); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/list.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/list.h Sat Nov 08 23:06:11 2025 +0100 @@ -80,46 +80,41 @@ /** * Member function for inserting a single element. - * The data pointer may be @c NULL in which case the function shall only allocate memory. + * The data pointer may be @c NULL, in which case the function shall only allocate memory. * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - void *(*insert_element)( - struct cx_list_s *list, - size_t index, - const void *data - ); + void *(*insert_element)(struct cx_list_s *list, size_t index, const void *data); /** * Member function for inserting multiple elements. * + * The data pointer may be @c NULL, in which case the function shall only allocate memory. + * Returns the number of successfully inserted or allocated elements. + * * @see cx_list_default_insert_array() */ - size_t (*insert_array)( - struct cx_list_s *list, - size_t index, - const void *data, - size_t n - ); + size_t (*insert_array)(struct cx_list_s *list, size_t index, const void *data, size_t n); /** * Member function for inserting sorted elements into a sorted list. + * Returns the number of successfully inserted elements. * * @see cx_list_default_insert_sorted() */ - size_t (*insert_sorted)( - struct cx_list_s *list, - const void *sorted_data, - size_t n - ); + size_t (*insert_sorted)(struct cx_list_s *list, const void *sorted_data, size_t n); + + /** + * Member function for inserting multiple elements if they do not exist. + * Implementations shall return the number of successfully processed elements + * (including those which were not added because they are already contained). + * @see cx_list_default_insert_unique() + */ + size_t (*insert_unique)(struct cx_list_s *list, const void *sorted_data, size_t n); /** * Member function for inserting an element relative to an iterator position. */ - int (*insert_iter)( - struct cx_iterator_s *iter, - const void *elem, - int prepend - ); + int (*insert_iter)(struct cx_iterator_s *iter, const void *elem, int prepend); /** * Member function for removing elements. @@ -131,12 +126,7 @@ * The function SHALL return the actual number of elements removed, which * might be lower than @p num when going out of bounds. */ - size_t (*remove)( - struct cx_list_s *list, - size_t index, - size_t num, - void *targetbuf - ); + size_t (*remove)(struct cx_list_s *list, size_t index, size_t num, void *targetbuf); /** * Member function for removing all elements. @@ -148,28 +138,17 @@ * * @see cx_list_default_swap() */ - int (*swap)( - struct cx_list_s *list, - size_t i, - size_t j - ); + int (*swap)(struct cx_list_s *list, size_t i, size_t j); /** * Member function for element lookup. */ - void *(*at)( - const struct cx_list_s *list, - size_t index - ); + void *(*at)(const struct cx_list_s *list, size_t index); /** * Member function for finding and optionally removing an element. */ - size_t (*find_remove)( - struct cx_list_s *list, - const void *elem, - bool remove - ); + size_t (*find_remove)(struct cx_list_s *list, const void *elem, bool remove); /** * Member function for sorting the list. @@ -181,12 +160,9 @@ /** * Optional member function for comparing this list * to another list of the same type. - * If set to @c NULL, comparison won't be optimized. + * If set to @c NULL, the comparison won't be optimized. */ - int (*compare)( - const struct cx_list_s *list, - const struct cx_list_s *other - ); + int (*compare)(const struct cx_list_s *list, const struct cx_list_s *other); /** * Member function for reversing the order of the items. @@ -196,11 +172,7 @@ /** * Member function for returning an iterator pointing to the specified index. */ - struct cx_iterator_s (*iterator)( - const struct cx_list_s *list, - size_t index, - bool backward - ); + struct cx_iterator_s (*iterator)(const struct cx_list_s *list, size_t index, bool backward); }; /** @@ -213,11 +185,10 @@ * * Writing to that list is not allowed. * - * You can use this is a placeholder for initializing CxList pointers + * You can use this as a placeholder for initializing CxList pointers * for which you do not want to reserve memory right from the beginning. */ -cx_attr_export -extern CxList *const cxEmptyList; +CX_EXPORT extern CxList *const cxEmptyList; /** * Default implementation of an array insert. @@ -234,13 +205,8 @@ * @return the number of elements actually inserted */ cx_attr_nonnull -cx_attr_export -size_t cx_list_default_insert_array( - struct cx_list_s *list, - size_t index, - const void *data, - size_t n -); +CX_EXPORT size_t cx_list_default_insert_array(struct cx_list_s *list, + size_t index, const void *data, size_t n); /** * Default implementation of a sorted insert. @@ -259,12 +225,28 @@ * @return the number of elements actually inserted */ cx_attr_nonnull -cx_attr_export -size_t cx_list_default_insert_sorted( - struct cx_list_s *list, - const void *sorted_data, - size_t n -); +CX_EXPORT size_t cx_list_default_insert_sorted(struct cx_list_s *list, + const void *sorted_data, size_t n); + +/** + * Default implementation of an array insert where only elements are inserted when they don't exist in the list. + * + * This function is similar to cx_list_default_insert_sorted(), except it skips elements that are already in the list. + * + * @note The return value of this function denotes the number of elements from the @p sorted_data that are definitely + * contained in the list after completing the call. It is @em not the number of elements that were newly inserted. + * That means, when no error occurred, the return value should be @p n. + * + * Use this in your own list class if you do not want to implement an optimized version for your list. + * + * @param list the list + * @param sorted_data a pointer to the array of pre-sorted data to insert + * @param n the number of elements to insert + * @return the number of elements from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +CX_EXPORT size_t cx_list_default_insert_unique(struct cx_list_s *list, + const void *sorted_data, size_t n); /** * Default unoptimized sort implementation. @@ -278,8 +260,7 @@ * @param list the list that shall be sorted */ cx_attr_nonnull -cx_attr_export -void cx_list_default_sort(struct cx_list_s *list); +CX_EXPORT void cx_list_default_sort(struct cx_list_s *list); /** * Default unoptimized swap implementation. @@ -295,20 +276,19 @@ * allocation for the temporary buffer fails */ cx_attr_nonnull -cx_attr_export -int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); +CX_EXPORT int cx_list_default_swap(struct cx_list_s *list, size_t i, size_t j); /** * Initializes a list struct. * * Only use this function if you are creating your own list implementation. * The purpose of this function is to be called in the initialization code - * of your list, to set certain members correctly. + * of your list to set certain members correctly. * * This is particularly important when you want your list to support * #CX_STORE_POINTERS as @p elem_size. This function will wrap the list * class accordingly and make sure that you can implement your list as if - * it was only storing objects and the wrapper will automatically enable + * it was only storing objects, and the wrapper will automatically enable * the feature of storing pointers. * * @par Example @@ -343,14 +323,9 @@ * @param elem_size the size of one element */ cx_attr_nonnull_arg(1, 2, 3) -cx_attr_export -void cx_list_init( - struct cx_list_s *list, - struct cx_list_class_s *cl, - const struct cx_allocator_s *allocator, - cx_compare_func comparator, - size_t elem_size -); +CX_EXPORT void cx_list_init(struct cx_list_s *list, + struct cx_list_class_s *cl, const struct cx_allocator_s *allocator, + cx_compare_func comparator, size_t elem_size); /** * Returns the number of elements currently stored in the list. @@ -359,9 +334,7 @@ * @return the number of currently stored elements */ cx_attr_nonnull -static inline size_t cxListSize(const CxList *list) { - return list->collection.size; -} +CX_EXPORT size_t cxListSize(const CxList *list); /** * Adds an item to the end of the list. @@ -374,13 +347,7 @@ * @see cxListEmplace() */ cx_attr_nonnull -static inline int cxListAdd( - CxList *list, - const void *elem -) { - list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, elem) == NULL; -} +CX_EXPORT int cxListAdd(CxList *list, const void *elem); /** * Adds multiple items to the end of the list. @@ -390,28 +357,22 @@ * 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 + * 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 + * @see cxListEmplaceArray() */ cx_attr_nonnull -static inline size_t cxListAddArray( - CxList *list, - const void *array, - size_t n -) { - list->collection.sorted = false; - return list->cl->insert_array(list, list->collection.size, array, n); -} +CX_EXPORT size_t cxListAddArray(CxList *list, const void *array, size_t n); /** * Inserts an item at the specified index. * - * If @p index equals the list @c size, this is effectively cxListAdd(). + * If the @p index equals the list @c size, this is effectively cxListAdd(). * * @param list the list * @param index the index the element shall have @@ -423,14 +384,7 @@ * @see cxListEmplaceAt() */ cx_attr_nonnull -static inline int cxListInsert( - CxList *list, - size_t index, - const void *elem -) { - list->collection.sorted = false; - return list->cl->insert_element(list, index, elem) == NULL; -} +CX_EXPORT int cxListInsert(CxList *list, size_t index, const void *elem); /** * Allocates memory for an element at the specified index and returns a pointer to that memory. @@ -441,14 +395,11 @@ * @param index the index where to emplace the element * @return a pointer to the allocated memory; @c NULL when the operation fails, or the index is out-of-bounds * @see cxListEmplace() + * @see cxListEmplaceArrayAt() * @see cxListInsert() */ cx_attr_nonnull -static inline void *cxListEmplaceAt(CxList *list, size_t index) { - list->collection.sorted = false; - return list->cl->insert_element(list, index, NULL); -} - +CX_EXPORT void *cxListEmplaceAt(CxList *list, size_t index); /** * Allocates memory for an element at the end of the list and returns a pointer to that memory. @@ -461,10 +412,46 @@ * @see cxListAdd() */ cx_attr_nonnull -static inline void *cxListEmplace(CxList *list) { - list->collection.sorted = false; - return list->cl->insert_element(list, list->collection.size, NULL); -} +CX_EXPORT void *cxListEmplace(CxList *list); + +/** + * Allocates memory for multiple elements and returns an iterator. + * + * The iterator will only iterate over the successfully allocated elements. + * The @c elem_count attribute is set to that number, and the @c index attribute + * will range from zero to @c elem_count minus one. + * + * @remark When the list is storing pointers, the iterator will iterate over + * the @c void** elements. + * + * @param list the list + * @param index the index where to insert the new data + * @param n the number of elements for which to allocate the memory + * @return an iterator, iterating over the new memory + * @see cxListEmplaceAt() + * @see cxListInsertArray() + */ +cx_attr_nonnull +CX_EXPORT CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n); + +/** + * Allocates memory for multiple elements and returns an iterator. + * + * The iterator will only iterate over the successfully allocated elements. + * The @c elem_count attribute is set to that number, and the @c index attribute + * will range from zero to @c elem_count minus one. + * + * @remark When the list is storing pointers, the iterator will iterate over + * the @c void** elements. + * + * @param list the list + * @param n the number of elements for which to allocate the memory + * @return an iterator, iterating over the new memory + * @see cxListEmplace() + * @see cxListAddArray() + */ +cx_attr_nonnull +CX_EXPORT CxIterator cxListEmplaceArray(CxList *list, size_t n); /** * Inserts an item into a sorted list. @@ -477,18 +464,27 @@ * @retval non-zero memory allocation failure */ cx_attr_nonnull -static inline int cxListInsertSorted( - CxList *list, - const void *elem -) { - list->collection.sorted = true; // guaranteed by definition - const void *data = list->collection.store_pointer ? &elem : elem; - return list->cl->insert_sorted(list, data, 1) == 0; -} +CX_EXPORT int cxListInsertSorted(CxList *list, const void *elem); + +/** + * Inserts an item into a list if it does not exist. + * + * If the list is not sorted already, this function will check all elements + * and append the new element when it was not found. + * It is strongly recommended to use this function only on sorted lists, where + * the element, if it is not contained, is inserted at the correct position. + * + * @param list the list + * @param elem a pointer to the element to add + * @retval zero success (also when the element was already in the list) + * @retval non-zero memory allocation failure + */ +cx_attr_nonnull +CX_EXPORT int cxListInsertUnique(CxList *list, const void *elem); /** * Inserts multiple items to the list at the specified index. - * If @p index equals the list size, this is effectively cxListAddArray(). + * If the @p index equals the list size, this is effectively cxListAddArray(). * * This method is usually more efficient than invoking cxListInsert() * multiple times. @@ -496,7 +492,7 @@ * 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 + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * @param list the list @@ -504,28 +500,21 @@ * @param array a pointer to the elements to add * @param n the number of elements to add * @return the number of added elements + * @see cxListEmplaceArrayAt() */ cx_attr_nonnull -static inline size_t cxListInsertArray( - CxList *list, - size_t index, - const void *array, - size_t n -) { - list->collection.sorted = false; - return list->cl->insert_array(list, index, array, n); -} +CX_EXPORT size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n); /** * Inserts a sorted array into a sorted list. * - * This method is usually more efficient than inserting each element separately, + * This method is usually more efficient than inserting each element separately * because consecutive chunks of sorted data are inserted in one pass. * * If there is not enough memory to add all elements, the returned value is * less than @p n. * - * If this list is storing pointers instead of objects @p array is expected to + * If this list is storing pointers instead of objects, @p array is expected to * be an array of pointers. * * If the list is not sorted already, the behavior is undefined. @@ -536,14 +525,42 @@ * @return the number of added elements */ cx_attr_nonnull -static inline size_t cxListInsertSortedArray( - CxList *list, - const void *array, - size_t n -) { - list->collection.sorted = true; // guaranteed by definition - return list->cl->insert_sorted(list, array, n); -} +CX_EXPORT size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n); + +/** + * Inserts an array into a list, skipping duplicates. + * + * The @p list does not need to be sorted (in contrast to cxListInsertSortedArray()). + * But it is strongly recommended to use this function only on sorted lists, + * because otherwise it will fall back to an inefficient algorithm which inserts + * all elements one by one. + * If the @p list is not sorted, the @p array also does not need to be sorted. + * But when the @p list is sorted, the @p array must also be sorted. + * + * This method is usually more efficient than inserting each element separately + * because consecutive chunks of sorted data are inserted in one pass. + * + * If there is not enough memory to add all elements, the returned value is + * less than @p n. + * + * @note The return value of this function denotes the number of elements + * from the @p sorted_data that are definitely contained in the list after + * completing the call. It is @em not the number of elements that were newly + * inserted. That means, when no error occurred, the return value should + * be @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 + * + * @return the number of elements from the @p sorted_data that are definitely present in the list after this call + */ +cx_attr_nonnull +CX_EXPORT size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n); /** * Inserts an element after the current location of the specified iterator. @@ -562,14 +579,7 @@ * @see cxListInsertBefore() */ cx_attr_nonnull -static inline int cxListInsertAfter( - CxIterator *iter, - const void *elem -) { - CxList* list = (CxList*)iter->src_handle.m; - list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 0); -} +CX_EXPORT int cxListInsertAfter(CxIterator *iter, const void *elem); /** * Inserts an element before the current location of the specified iterator. @@ -588,14 +598,7 @@ * @see cxListInsertAfter() */ cx_attr_nonnull -static inline int cxListInsertBefore( - CxIterator *iter, - const void *elem -) { - CxList* list = (CxList*)iter->src_handle.m; - list->collection.sorted = false; - return list->cl->insert_iter(iter, elem, 1); -} +CX_EXPORT int cxListInsertBefore(CxIterator *iter, const void *elem); /** * Removes the element at the specified index. @@ -609,12 +612,7 @@ * @retval non-zero index out of bounds */ cx_attr_nonnull -static inline int cxListRemove( - CxList *list, - size_t index -) { - return list->cl->remove(list, index, 1, NULL) == 0; -} +CX_EXPORT int cxListRemove(CxList *list, size_t index); /** * Removes and returns the element at the specified index. @@ -629,15 +627,8 @@ * @retval zero success * @retval non-zero index out of bounds */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxListRemoveAndGet( - CxList *list, - size_t index, - void *targetbuf -) { - return list->cl->remove(list, index, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(3) +CX_EXPORT int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf); /** * Removes and returns the first element of the list. @@ -649,18 +640,12 @@ * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListPopFront() * @see cxListRemoveAndGetLast() */ -cx_attr_nonnull -cx_attr_access_w(2) -static inline int cxListRemoveAndGetFirst( - CxList *list, - void *targetbuf -) { - return list->cl->remove(list, 0, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT int cxListRemoveAndGetFirst(CxList *list, void *targetbuf); /** * Removes and returns the first element of the list. @@ -674,7 +659,7 @@ * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetFirst() * @see cxListPop() */ @@ -691,17 +676,10 @@ * @param list the list * @param targetbuf a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty */ -cx_attr_nonnull -cx_attr_access_w(2) -static inline int cxListRemoveAndGetLast( - CxList *list, - void *targetbuf -) { - // note: index may wrap - member function will catch that - return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; -} +cx_attr_nonnull cx_attr_access_w(2) +CX_EXPORT int cxListRemoveAndGetLast(CxList *list, void *targetbuf); /** * Removes and returns the last element of the list. @@ -715,7 +693,7 @@ * @param list (@c CxList*) the list * @param targetbuf (@c void*) a buffer where to copy the element * @retval zero success - * @retval non-zero list is empty + * @retval non-zero the list is empty * @see cxListRemoveAndGetLast() * @see cxListPopFront() */ @@ -737,13 +715,7 @@ * @return the actual number of removed elements */ cx_attr_nonnull -static inline size_t cxListRemoveArray( - CxList *list, - size_t index, - size_t num -) { - return list->cl->remove(list, index, num, NULL); -} +CX_EXPORT size_t cxListRemoveArray(CxList *list, size_t index, size_t num); /** * Removes and returns multiple elements starting at the specified index. @@ -758,16 +730,8 @@ * @param targetbuf a buffer where to copy the elements * @return the actual number of removed elements */ -cx_attr_nonnull -cx_attr_access_w(4) -static inline size_t cxListRemoveArrayAndGet( - CxList *list, - size_t index, - size_t num, - void *targetbuf -) { - return list->cl->remove(list, index, num, targetbuf); -} +cx_attr_nonnull cx_attr_access_w(4) +CX_EXPORT size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf); /** * Removes all elements from this list. @@ -778,10 +742,7 @@ * @param list the list */ cx_attr_nonnull -static inline void cxListClear(CxList *list) { - list->collection.sorted = true; // empty lists are always sorted - list->cl->clear(list); -} +CX_EXPORT void cxListClear(CxList *list); /** * Swaps two items in the list. @@ -797,14 +758,7 @@ * or the swap needed extra memory, but allocation failed */ cx_attr_nonnull -static inline int cxListSwap( - CxList *list, - size_t i, - size_t j -) { - list->collection.sorted = false; - return list->cl->swap(list, i, j); -} +CX_EXPORT int cxListSwap(CxList *list, size_t i, size_t j); /** * Returns a pointer to the element at the specified index. @@ -816,12 +770,7 @@ * @return a pointer to the element or @c NULL if the index is out of bounds */ cx_attr_nonnull -static inline void *cxListAt( - const CxList *list, - size_t index -) { - return list->cl->at(list, index); -} +CX_EXPORT void *cxListAt(const CxList *list, size_t index); /** * Returns a pointer to the first element. @@ -832,9 +781,7 @@ * @return a pointer to the first element or @c NULL if the list is empty */ cx_attr_nonnull -static inline void *cxListFirst(const CxList *list) { - return list->cl->at(list, 0); -} +CX_EXPORT void *cxListFirst(const CxList *list); /** * Returns a pointer to the last element. @@ -845,12 +792,13 @@ * @return a pointer to the last element or @c NULL if the list is empty */ cx_attr_nonnull -static inline void *cxListLast(const CxList *list) { - return list->cl->at(list, list->collection.size - 1); -} +CX_EXPORT void *cxListLast(const CxList *list); /** - * Sets the element at the specified index in the list + * Sets the element at the specified index in the list. + * + * This overwrites the element in-place without calling any destructor + * on the overwritten element. * * @param list the list to set the element in * @param index the index to set the element at @@ -859,12 +807,7 @@ * @retval non-zero when index is out of bounds */ cx_attr_nonnull -cx_attr_export -int cxListSet( - CxList *list, - size_t index, - const void *elem -); +CX_EXPORT int cxListSet(CxList *list, size_t index, const void *elem); /** * Returns an iterator pointing to the item at the specified index. @@ -878,13 +821,7 @@ * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListIteratorAt( - const CxList *list, - size_t index -) { - if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, index, false); -} +CX_EXPORT CxIterator cxListIteratorAt(const CxList *list, size_t index); /** * Returns a backwards iterator pointing to the item at the specified index. @@ -898,50 +835,7 @@ * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListBackwardsIteratorAt( - const CxList *list, - size_t index -) { - if (list == NULL) list = cxEmptyList; - 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 or @p list is @c NULL, 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 - */ -cx_attr_nodiscard -cx_attr_export -CxIterator 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 or @p list is @c NULL, 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 - */ -cx_attr_nodiscard -cx_attr_export -CxIterator cxListMutBackwardsIteratorAt( - CxList *list, - size_t index -); +CX_EXPORT CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index); /** * Returns an iterator pointing to the first item of the list. @@ -954,27 +848,7 @@ * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListIterator(const CxList *list) { - if (list == NULL) list = cxEmptyList; - 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 or @c NULL, a past-the-end iterator will be returned. - * - * @param list the list - * @return a new iterator - */ -cx_attr_nodiscard -static inline CxIterator cxListMutIterator(CxList *list) { - if (list == NULL) list = cxEmptyList; - return cxListMutIteratorAt(list, 0); -} - +CX_EXPORT CxIterator cxListIterator(const CxList *list); /** * Returns a backwards iterator pointing to the last item of the list. @@ -987,26 +861,7 @@ * @return a new iterator */ cx_attr_nodiscard -static inline CxIterator cxListBackwardsIterator(const CxList *list) { - if (list == NULL) list = cxEmptyList; - return list->cl->iterator(list, list->collection.size - 1, true); -} - -/** - * Returns a mutating backwards iterator pointing to the last item of the list. - * - * The returned iterator is position-aware. - * - * If the list is empty or @c NULL, a past-the-end iterator will be returned. - * - * @param list the list - * @return a new iterator - */ -cx_attr_nodiscard -static inline CxIterator cxListMutBackwardsIterator(CxList *list) { - if (list == NULL) list = cxEmptyList; - return cxListMutBackwardsIteratorAt(list, list->collection.size - 1); -} +CX_EXPORT CxIterator cxListBackwardsIterator(const CxList *list); /** * Returns the index of the first element that equals @p elem. @@ -1019,17 +874,11 @@ * @see cxListIndexValid() * @see cxListContains() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline size_t cxListFind( - const CxList *list, - const void *elem -) { - return list->cl->find_remove((CxList*)list, elem, false); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxListFind(const CxList *list, const void *elem); /** - * Checks, if the list contains the specified element. + * Checks if the list contains the specified element. * * The elements are compared with the list's comparator function. * @@ -1039,14 +888,8 @@ * @retval false if the element is not contained * @see cxListFind() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline bool cxListContains( - const CxList* list, - const void* elem -) { - return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxListContains(const CxList* list, const void* elem); /** * Checks if the specified index is within bounds. @@ -1056,11 +899,8 @@ * @retval true if the index is within bounds * @retval false if the index is out of bounds */ -cx_attr_nonnull -cx_attr_nodiscard -static inline bool cxListIndexValid(const CxList *list, size_t index) { - return index < list->collection.size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT bool cxListIndexValid(const CxList *list, size_t index); /** * Removes and returns the index of the first element that equals @p elem. @@ -1074,12 +914,7 @@ * @see cxListIndexValid() */ cx_attr_nonnull -static inline size_t cxListFindRemove( - CxList *list, - const void *elem -) { - return list->cl->find_remove(list, elem, true); -} +CX_EXPORT size_t cxListFindRemove(CxList *list, const void *elem); /** * Sorts the list. @@ -1089,11 +924,7 @@ * @param list the list */ cx_attr_nonnull -static inline void cxListSort(CxList *list) { - if (list->collection.sorted) return; - list->cl->sort(list); - list->collection.sorted = true; -} +CX_EXPORT void cxListSort(CxList *list); /** * Reverses the order of the items. @@ -1101,11 +932,7 @@ * @param list the list */ cx_attr_nonnull -static inline void cxListReverse(CxList *list) { - // still sorted, but not according to the cmp_func - list->collection.sorted = false; - list->cl->reverse(list); -} +CX_EXPORT void cxListReverse(CxList *list); /** * Compares a list to another list of the same type. @@ -1116,28 +943,116 @@ * @param list the list * @param other the list to compare to * @retval zero both lists are equal element wise - * @retval negative the first list is smaller + * @retval negative the first list is smaller, * or the first non-equal element in the first list is smaller * @retval positive the first list is larger * or the first non-equal element in the first list is larger */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -int cxListCompare( - const CxList *list, - const CxList *other -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT int cxListCompare(const CxList *list, const CxList *other); /** * Deallocates the memory of the specified list structure. * * Also calls the content destructor functions for each element, if specified. * - * @param list the list which shall be freed + * @param list the list that shall be freed + */ +CX_EXPORT void cxListFree(CxList *list); + + +/** + * Performs a deep clone of one list into another. + * + * If the destination list already contains elements, the cloned elements + * are appended to that list. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination list also uses this + * destructor function. + * + * @param dst the destination list + * @param src the source list + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListUnion() + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxListClone(CxList *dst, const CxList *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are not present in another list. + * + * If the @p minuend does not contain duplicates, this is equivalent to adding + * the set difference to the destination list. + * + * This function is optimized for the case when both the @p minuend and the + * @p subtrahend are sorted. + * + * @param dst the destination list + * @param minuend the list to subtract elements from + * @param subtrahend the elements that shall be subtracted + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred */ -cx_attr_export -void cxListFree(CxList *list); +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones elements from a list only if they are also present in another list. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * + * If the destination list already contains elements, the intersection is appended + * to that list. + * + * @param dst the destination list + * @param src the list to clone the elements from + * @param other the list to check the elements for existence + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListIntersection(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Performs a deep clone of one list into another, skipping duplicates. + * + * This function is optimized for the case when both the @p src and the + * @p other list are sorted. + * In that case, the union will also be sorted. + * + * If the destination list already contains elements, the union is appended + * to that list. In that case the destination is not necessarily sorted. + * + * @param dst the destination list + * @param src the primary source list + * @param other the other list, where elements are only cloned from + * when they are not in @p src + * @param clone_func the clone function for the elements + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + * @see cxListClone() + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxListUnion(CxList *dst, const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); #ifdef __cplusplus
--- a/ucx/cx/map.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/map.h Sat Nov 08 23:06:11 2025 +0100 @@ -41,6 +41,11 @@ #include "string.h" #include "hash_key.h" +#ifndef UCX_LIST_H +// forward-declare CxList +typedef struct cx_list_s CxList; +#endif + #ifdef __cplusplus extern "C" { #endif @@ -111,16 +116,7 @@ /** * Handle for the source map. */ - union { - /** - * Access for mutating iterators. - */ - CxMap *m; - /** - * Access for normal iterators. - */ - const CxMap *c; - } map; + CxMap *map; /** * Handle for the current element. @@ -189,19 +185,12 @@ * shall only allocate memory instead of adding an existing value to the map. * Returns a pointer to the allocated memory or @c NULL if allocation fails. */ - void *(*put)( - CxMap *map, - CxHashKey key, - void *value - ); + void *(*put)(CxMap *map, CxHashKey key, void *value); /** * Returns an element. */ - void *(*get)( - const CxMap *map, - CxHashKey key - ); + void *(*get)(const CxMap *map, CxHashKey key); /** * Removes an element. @@ -213,11 +202,7 @@ * The function SHALL return zero when the @p key was found and * non-zero, otherwise. */ - int (*remove)( - CxMap *map, - CxHashKey key, - void *targetbuf - ); + int (*remove)(CxMap *map, CxHashKey key, void *targetbuf); /** * Creates an iterator for this map. @@ -230,11 +215,10 @@ * * Writing to that map is not allowed. * - * You can use this is a placeholder for initializing CxMap pointers + * You can use this as a placeholder for initializing CxMap pointers * for which you do not want to reserve memory right from the beginning. */ -cx_attr_export -extern CxMap *const cxEmptyMap; +CX_EXPORT extern CxMap *const cxEmptyMap; /** * Deallocates the memory of the specified map. @@ -243,8 +227,7 @@ * * @param map the map to be freed */ -cx_attr_export -void cxMapFree(CxMap *map); +CX_EXPORT void cxMapFree(CxMap *map); /** @@ -255,9 +238,7 @@ * @param map the map to be cleared */ cx_attr_nonnull -static inline void cxMapClear(CxMap *map) { - map->cl->clear(map); -} +CX_EXPORT void cxMapClear(CxMap *map); /** * Returns the number of elements in this map. @@ -266,9 +247,7 @@ * @return the number of stored elements */ cx_attr_nonnull -static inline size_t cxMapSize(const CxMap *map) { - return map->collection.size; -} +CX_EXPORT size_t cxMapSize(const CxMap *map); /** * Creates a value iterator for a map. @@ -284,15 +263,12 @@ * @return an iterator for the currently stored values */ cx_attr_nodiscard -static inline CxMapIterator cxMapIteratorValues(const CxMap *map) { - if (map == NULL) map = cxEmptyMap; - return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); -} +CX_EXPORT CxMapIterator cxMapIteratorValues(const CxMap *map); /** * Creates a key iterator for a map. * - * The elements of the iterator are keys of type CxHashKey and the pointer returned + * The elements of the iterator are keys of type CxHashKey, and the pointer returned * during iterator shall be treated as @c const @c CxHashKey* . * * @note An iterator iterates over all elements successively. Therefore, the order @@ -302,15 +278,12 @@ * @return an iterator for the currently stored keys */ cx_attr_nodiscard -static inline CxMapIterator cxMapIteratorKeys(const CxMap *map) { - if (map == NULL) map = cxEmptyMap; - return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); -} +CX_EXPORT CxMapIterator cxMapIteratorKeys(const CxMap *map); /** * Creates an iterator for a map. * - * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned + * The elements of the iterator are key/value pairs of type CxMapEntry, and the pointer returned * during iterator shall be treated as @c const @c CxMapEntry* . * * @note An iterator iterates over all elements successively. Therefore, the order @@ -322,296 +295,30 @@ * @see cxMapIteratorValues() */ cx_attr_nodiscard -static inline CxMapIterator cxMapIterator(const CxMap *map) { - if (map == NULL) map = cxEmptyMap; - return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); -} - - -/** - * Creates a mutating iterator over the values of a map. - * - * When the map is storing pointers, those pointers are returned. - * Otherwise, the iterator iterates over pointers to the memory within the map where the - * respective elements are stored. - * - * @note An iterator iterates over all elements successively. Therefore, the order - * highly depends on the map implementation and may change arbitrarily when the contents change. - * - * @param map the map to create the iterator for (can be @c NULL) - * @return an iterator for the currently stored values - */ -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIteratorValues(CxMap *map); - -/** - * Creates a mutating iterator over the keys of a map. - * - * The elements of the iterator are keys of type CxHashKey and the pointer returned - * during iterator shall be treated as @c const @c CxHashKey* . - * - * @note An iterator iterates over all elements successively. Therefore, the order - * highly depends on the map implementation and may change arbitrarily when the contents change. - * - * @param map the map to create the iterator for (can be @c NULL) - * @return an iterator for the currently stored keys - */ -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIteratorKeys(CxMap *map); +CX_EXPORT CxMapIterator cxMapIterator(const CxMap *map); /** - * Creates a mutating iterator for a map. + * Puts a key/value-pair into the map. * - * The elements of the iterator are key/value pairs of type CxMapEntry and the pointer returned - * during iterator shall be treated as @c const @c CxMapEntry* . - * - * @note An iterator iterates over all elements successively. Therefore, the order - * highly depends on the map implementation and may change arbitrarily when the contents change. + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. * - * @param map the map to create the iterator for (can be @c NULL) - * @return an iterator for the currently stored entries - * @see cxMapMutIteratorKeys() - * @see cxMapMutIteratorValues() - */ -cx_attr_nodiscard -cx_attr_export -CxMapIterator cxMapMutIterator(CxMap *map); - -#ifdef __cplusplus -} // end the extern "C" block here, because we want to start overloading -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - CxHashKey const &key, - void *value -) { - return map->cl->put(map, key, value) == NULL; -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxstring const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; -} - -cx_attr_nonnull -static inline int cxMapPut( - CxMap *map, - cxmutstr const &key, - void *value -) { - return map->cl->put(map, cx_hash_key_cxstr(key), value) == NULL; -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapPut( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value) == NULL; -} - -cx_attr_nonnull -static inline void *cxMapEmplace( - CxMap *map, - CxHashKey const &key -) { - return map->cl->put(map, key, NULL); -} - -cx_attr_nonnull -static inline void *cxMapEmplace( - CxMap *map, - cxstring const &key -) { - return map->cl->put(map, cx_hash_key_cxstr(key), NULL); -} - -cx_attr_nonnull -static inline void *cxMapEmplace( - CxMap *map, - cxmutstr const &key -) { - return map->cl->put(map, cx_hash_key_cxstr(key), NULL); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline void *cxMapEmplace( - CxMap *map, - const char *key -) { - return map->cl->put(map, cx_hash_key_str(key), NULL); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - CxHashKey const &key -) { - return map->cl->get(map, key); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxstring const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxMapGet( - const CxMap *map, - cxmutstr const &key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cxMapGet( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - CxHashKey const &key -) { - return map->cl->remove(map, key, nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxstring const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -static inline int cxMapRemove( - CxMap *map, - cxmutstr const &key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), nullptr); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxMapRemove( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), nullptr); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - CxHashKey key, - void *targetbuf -) { - return map->cl->remove(map, key, targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cxMapRemoveAndGet( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cxMapRemoveAndGet( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} - -#else // __cplusplus - -/** - * @copydoc cxMapPut() + * If this map is storing pointers, the @p value pointer is written + * to the map. Otherwise, the memory is copied from @p value with + * memcpy(). + * + * The @p key is always copied. + * + * @param map the map + * @param key the key + * @param value the value + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapPut() */ cx_attr_nonnull -static inline int cx_map_put( - CxMap *map, - CxHashKey key, - void *value -) { - return map->cl->put(map, key, value) == NULL; -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_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) == NULL; -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_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) == NULL; -} - -/** - * @copydoc cxMapPut() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_put_str( - CxMap *map, - const char *key, - void *value -) { - return map->cl->put(map, cx_hash_key_str(key), value) == NULL; -} +CX_EXPORT int cx_map_put(CxMap *map, CxHashKey key, void *value); /** * Puts a key/value-pair into the map. @@ -627,63 +334,35 @@ * The @p key is always copied. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @param value (@c void*) the value * @retval zero success * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() */ -#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, \ - const char*: cx_map_put_str) \ - (map, key, value) +#define cxMapPut(map, key, value) cx_map_put(map, CX_HASH_KEY(key), value) /** - * @copydoc cxMapEmplace() - */ -cx_attr_nonnull -static inline void *cx_map_emplace( - CxMap *map, - CxHashKey key -) { - return map->cl->put(map, key, NULL); -} - -/** - * @copydoc cxMapEmplace() + * Allocates memory for a value in the map associated with the specified key. + * + * A possible existing value will be overwritten. + * If destructor functions are specified, they are called for + * the overwritten element. + * + * If the map is storing pointers, this function returns a @c void** pointer, + * meaning a pointer to that pointer. + * + * The @p key is always copied. + * + * @param map the map + * @param key the key + * @return the pointer to the allocated memory or @c NULL if allocation fails + * @retval zero success + * @retval non-zero value on memory allocation failure + * @see cxMapEmplace() */ cx_attr_nonnull -static inline void *cx_map_emplace_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->put(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapEmplace() - */ -cx_attr_nonnull -static inline void *cx_map_emplace_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->put(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapEmplace() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline void *cx_map_emplace_str( - CxMap *map, - const char *key -) { - return map->cl->put(map, cx_hash_key_str(key), NULL); -} +CX_EXPORT void *cx_map_emplace(CxMap *map, CxHashKey key); /** * Allocates memory for a value in the map associated with the specified key. @@ -698,67 +377,28 @@ * The @p key is always copied. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @return the pointer to the allocated memory or @c NULL if allocation fails * @retval zero success * @retval non-zero value on memory allocation failure + * @see CX_HASH_KEY() */ -#define cxMapEmplace(map, key) _Generic((key), \ - CxHashKey: cx_map_emplace, \ - cxstring: cx_map_emplace_cxstr, \ - cxmutstr: cx_map_emplace_mustr, \ - char*: cx_map_emplace_str, \ - const char*: cx_map_emplace_str) \ - (map, key) - -/** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get( - const CxMap *map, - CxHashKey key -) { - return map->cl->get(map, key); -} +#define cxMapEmplace(map, key) cx_map_emplace(map, CX_HASH_KEY(key)) /** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_cxstr( - const CxMap *map, - cxstring key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -/** - * @copydoc cxMapGet() + * Retrieves a value by using a key. + * + * If this map is storing pointers, the stored pointer is returned. + * Otherwise, a pointer to the element within the map's memory + * is returned (which is valid as long as the element stays in the map). + * + * @param map the map + * @param key the key + * @return the value + * @see cxMapGet() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cx_map_get_mustr( - const CxMap *map, - cxmutstr key -) { - return map->cl->get(map, cx_hash_key_cxstr(key)); -} - -/** - * @copydoc cxMapGet() - */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(2) -static inline void *cx_map_get_str( - const CxMap *map, - const char *key -) { - return map->cl->get(map, cx_hash_key_str(key)); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cx_map_get(const CxMap *map, CxHashKey key); /** * Retrieves a value by using a key. @@ -768,134 +408,55 @@ * is returned (which is valid as long as the element stays in the map). * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key - * @return (@c void*) the value + * @param key (any supported key type) the key + * @return (@c void*) the value or @c NULL when no value with that @p key exists + * @see CX_HASH_KEY() */ -#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, \ - const char*: cx_map_get_str) \ - (map, key) - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove( - CxMap *map, - CxHashKey key -) { - return map->cl->remove(map, key, NULL); -} +#define cxMapGet(map, key) cx_map_get(map, CX_HASH_KEY(key)) /** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -static inline int cx_map_remove_cxstr( - CxMap *map, - cxstring key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapRemove() + * Checks if a map contains a specific key. + * + * @param map (@c CxMap*) the map + * @param key (any supported key type) the key + * @retval true if the key exists in the map + * @retval false if the key does not exist in the map + * @see CX_HASH_KEY() */ -cx_attr_nonnull -static inline int cx_map_remove_mustr( - CxMap *map, - cxmutstr key -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), NULL); -} - -/** - * @copydoc cxMapRemove() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_map_remove_str( - CxMap *map, - const char *key -) { - return map->cl->remove(map, cx_hash_key_str(key), NULL); -} +#define cxMapContains(map, key) (cxMapGet(map, key) != NULL) /** * Removes a key/value-pair from the map by using the key. * - * Always invokes the destructors functions, if any, on the removed element. + * Invokes the destructor functions, if any, on the removed element if and only if the + * @p targetbuf is @c NULL. + * + * @param map the map + * @param key the key + * @param targetbuf the optional buffer where the removed element shall be copied to + * @retval zero success + * @retval non-zero the key was not found + * + * @see cxMapRemove() + * @see cxMapRemoveAndGet() + */ +cx_attr_nonnull_arg(1) +CX_EXPORT int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf); + +/** + * Removes a key/value-pair from the map by using the key. + * + * Always invokes the destructor functions, if any, on the removed element. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @retval zero success * @retval non-zero the key was not found * * @see cxMapRemoveAndGet() - */ -#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, \ - const char*: cx_map_remove_str) \ - (map, key) - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get( - CxMap *map, - CxHashKey key, - void *targetbuf -) { - return map->cl->remove(map, key, targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() + * @see CX_HASH_KEY() */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_cxstr( - CxMap *map, - cxstring key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -static inline int cx_map_remove_and_get_mustr( - CxMap *map, - cxmutstr key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_cxstr(key), targetbuf); -} - -/** - * @copydoc cxMapRemoveAndGet() - */ -cx_attr_nonnull -cx_attr_access_w(3) -cx_attr_cstr_arg(2) -static inline int cx_map_remove_and_get_str( - CxMap *map, - const char *key, - void *targetbuf -) { - return map->cl->remove(map, cx_hash_key_str(key), targetbuf); -} +#define cxMapRemove(map, key) cx_map_remove(map, CX_HASH_KEY(key), NULL) /** * Removes a key/value-pair from the map by using the key. @@ -909,21 +470,141 @@ * and not the object it points to. * * @param map (@c CxMap*) the map - * @param key (@c CxHashKey, @c char*, @c cxstring, or @c cxmutstr) the key + * @param key (any supported key type) the key * @param targetbuf (@c void*) the buffer where the element shall be copied to * @retval zero success * @retval non-zero the key was not found * * @see cxMapRemove() + * @see CX_HASH_KEY() */ -#define cxMapRemoveAndGet(map, key, targetbuf) _Generic((key), \ - CxHashKey: cx_map_remove_and_get, \ - cxstring: cx_map_remove_and_get_cxstr, \ - cxmutstr: cx_map_remove_and_get_mustr, \ - char*: cx_map_remove_and_get_str, \ - const char*: cx_map_remove_and_get_str) \ - (map, key, targetbuf) +#define cxMapRemoveAndGet(map, key, targetbuf) cx_map_remove(map, CX_HASH_KEY(key), targetbuf) + + +/** + * Performs a deep clone of one map into another. + * + * If the destination map already contains entries, the cloned entries + * are added to that map, possibly overwriting existing elements when + * the keys already exist. + * + * When elements in the destination map need to be replaced, any destructor + * function is called on the replaced elements before replacing them. + * + * @attention If the cloned elements need to be destroyed by a destructor + * function, you must make sure that the destination map also uses this + * destructor function. + * + * @param dst the destination map + * @param src the source map + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when all elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapClone(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map if their key is not present in another map. + * + * @param dst the destination map + * @param minuend the map to subtract the entries from + * @param subtrahend the map containing the elements to be subtracted + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); -#endif // __cplusplus +/** + * Clones entries of a map if their key is not present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + + +/** + * Clones entries of a map only if their key is present in another map. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param other the map to check for existence of the keys + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries of a map only if their key is present in a list. + * + * Note that the list must contain keys of type @c CxKey + * (or pointers to such keys) and must use @c cx_hash_key_cmp + * as the compare function. + * Generic key types cannot be processed in this case. + * + * @param dst the destination map + * @param src the source map + * @param keys the list of @c CxKey items + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3, 4) +CX_EXPORT int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +/** + * Clones entries into a map if their key does not exist yet. + * + * If you want to calculate the union of two maps into a fresh new map, + * you can proceed as follows: + * 1. Clone the first map into a fresh, empty map. + * 2. Use this function to clone the second map into the result from step 1. + * + * @param dst the destination map + * @param src the map to clone the entries from + * @param clone_func the clone function for the values + * @param clone_allocator the allocator that is passed to the clone function + * @param data optional additional data that is passed to the clone function + * @retval zero when the elements were successfully cloned + * @retval non-zero when an allocation error occurred + */ +cx_attr_nonnull_arg(1, 2, 3) +CX_EXPORT int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data); + +#ifdef __cplusplus +} // extern "C" +#endif #endif // UCX_MAP_H
--- a/ucx/cx/mempool.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/mempool.h Sat Nov 08 23:06:11 2025 +0100 @@ -156,8 +156,7 @@ * * @param pool the memory pool to free */ -cx_attr_export -void cxMempoolFree(CxMempool *pool); +CX_EXPORT void cxMempoolFree(CxMempool *pool); /** * Creates an array-based memory pool. @@ -169,11 +168,8 @@ * @param type the type of memory pool * @return the created memory pool or @c NULL if allocation failed */ -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxMempoolFree, 1) -cx_attr_export -CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); +cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxMempoolFree, 1) +CX_EXPORT CxMempool *cxMempoolCreate(size_t capacity, enum cx_mempool_type type); /** * Creates a basic array-based memory pool. @@ -212,8 +208,7 @@ * @param fnc the destructor that shall be applied to all memory blocks */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); +CX_EXPORT void cxMempoolGlobalDestructor(CxMempool *pool, cx_destructor_func fnc); /** * Sets the global destructor for all memory blocks within the specified pool. @@ -223,8 +218,7 @@ * @param data additional data for the destructor function */ cx_attr_nonnull_arg(1) -cx_attr_export -void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); +CX_EXPORT void cxMempoolGlobalDestructor2(CxMempool *pool, cx_destructor_func2 fnc, void *data); /** * Sets the destructor function for a specific allocated memory object. @@ -237,11 +231,7 @@ * @param fnc the destructor function */ cx_attr_nonnull -cx_attr_export -void cxMempoolSetDestructor( - void *memory, - cx_destructor_func fnc -); +CX_EXPORT void cxMempoolSetDestructor(void *memory, cx_destructor_func fnc); /** * Sets the destructor function for a specific allocated memory object. @@ -255,12 +245,7 @@ * @param data additional data for the destructor function */ cx_attr_nonnull -cx_attr_export -void cxMempoolSetDestructor2( - void *memory, - cx_destructor_func2 fnc, - void *data -); +CX_EXPORT void cxMempoolSetDestructor2(void *memory, cx_destructor_func2 fnc, void *data); /** * Removes the destructor function for a specific allocated memory object. @@ -271,8 +256,7 @@ * @param memory the object allocated in the pool */ cx_attr_nonnull -cx_attr_export -void cxMempoolRemoveDestructor(void *memory); +CX_EXPORT void cxMempoolRemoveDestructor(void *memory); /** * Removes the destructor function for a specific allocated memory object. @@ -283,8 +267,7 @@ * @param memory the object allocated in the pool */ cx_attr_nonnull -cx_attr_export -void cxMempoolRemoveDestructor2(void *memory); +CX_EXPORT void cxMempoolRemoveDestructor2(void *memory); /** * Registers foreign memory with this pool. @@ -302,12 +285,7 @@ * @retval non-zero failure */ cx_attr_nonnull -cx_attr_export -int cxMempoolRegister( - CxMempool *pool, - void *memory, - cx_destructor_func destr -); +CX_EXPORT int cxMempoolRegister(CxMempool *pool, void *memory, cx_destructor_func destr); /** @@ -330,13 +308,7 @@ * @retval non-zero failure */ cx_attr_nonnull -cx_attr_export -int cxMempoolRegister2( - CxMempool *pool, - void *memory, - cx_destructor_func2 destr, - void *data -); +CX_EXPORT int cxMempoolRegister2(CxMempool *pool, void *memory, cx_destructor_func2 destr, void *data); /** * Transfers all the memory managed by one pool to another. @@ -354,11 +326,7 @@ * @retval non-zero allocation failure or incompatible pools */ cx_attr_nonnull -cx_attr_export -int cxMempoolTransfer( - CxMempool *source, - CxMempool *dest -); +CX_EXPORT int cxMempoolTransfer(CxMempool *source, CxMempool *dest); /** * Transfers an object from one pool to another. @@ -375,12 +343,7 @@ * @retval non-zero failure, or the object was not found in the source pool, or the pools are incompatible */ cx_attr_nonnull -cx_attr_export -int cxMempoolTransferObject( - CxMempool *source, - CxMempool *dest, - const void *obj -); +CX_EXPORT int cxMempoolTransferObject(CxMempool *source, CxMempool *dest, const void *obj); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/printf.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/printf.h Sat Nov 08 23:06:11 2025 +0100 @@ -27,7 +27,7 @@ */ /** * @file printf.h - * @brief Wrapper for write functions with a printf-like interface. + * @brief Wrapper for write-functions with a printf-like interface. * @author Mike Becker * @author Olaf Wintermann * @copyright 2-Clause BSD License @@ -56,8 +56,7 @@ /** * The maximum string length that fits into stack memory. */ -cx_attr_export -extern const unsigned cx_printf_sbo_size; +CX_EXPORT extern const unsigned cx_printf_sbo_size; /** * A @c fprintf like function which writes the output to a stream by @@ -69,16 +68,8 @@ * @param ... additional arguments * @return the total number of bytes written or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 3) -cx_attr_printf(3, 4) -cx_attr_cstr_arg(3) -cx_attr_export -int cx_fprintf( - void *stream, - cx_write_func wfc, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 3) cx_attr_printf(3, 4) cx_attr_cstr_arg(3) +CX_EXPORT int cx_fprintf(void *stream, cx_write_func wfc, const char *fmt, ...); /** * A @c vfprintf like function which writes the output to a stream by @@ -91,18 +82,11 @@ * @return the total number of bytes written or an error code from stdlib printf implementation * @see cx_fprintf() */ -cx_attr_nonnull -cx_attr_cstr_arg(3) -cx_attr_export -int cx_vfprintf( - void *stream, - cx_write_func wfc, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(3) +CX_EXPORT int cx_vfprintf(void *stream, cx_write_func wfc, const char *fmt, va_list ap); /** - * A @c asprintf like function which allocates space for a string + * An @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, @@ -115,18 +99,11 @@ * @return the formatted string * @see cx_strfree_a() */ -cx_attr_nonnull_arg(1, 2) -cx_attr_printf(2, 3) -cx_attr_cstr_arg(2) -cx_attr_export -cxmutstr cx_asprintf_a( - const CxAllocator *allocator, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2) cx_attr_printf(2, 3) cx_attr_cstr_arg(2) +CX_EXPORT cxmutstr cx_asprintf_a(const CxAllocator *allocator, const char *fmt, ...); /** - * A @c asprintf like function which allocates space for a string + * An @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, @@ -138,8 +115,7 @@ * @return (@c cxmutstr) the formatted string * @see cx_strfree() */ -#define cx_asprintf(fmt, ...) \ - cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) +#define cx_asprintf(fmt, ...) cx_asprintf_a(cxDefaultAllocator, fmt, __VA_ARGS__) /** * A @c vasprintf like function which allocates space for a string @@ -155,21 +131,15 @@ * @return the formatted string * @see cx_asprintf_a() */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -cx_attr_export -cxmutstr cx_vasprintf_a( - const CxAllocator *allocator, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(2) +CX_EXPORT cxmutstr cx_vasprintf_a(const CxAllocator *allocator, const char *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, - * unless there was in error, in which case the string's pointer + * unless there was an error, in which case the string's pointer * will be @c NULL. * * @param fmt (@c char*) format string @@ -189,8 +159,7 @@ * @see cx_fprintf() * @see cxBufferWrite() */ -#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, \ - cxBufferWriteFunc, fmt, __VA_ARGS__) +#define cx_bprintf(buffer, fmt, ...) cx_fprintf((void*)buffer, cxBufferWriteFunc, fmt, __VA_ARGS__) /** @@ -204,7 +173,7 @@ * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf(str, len, fmt, ...) cx_sprintf_a(cxDefaultAllocator, str, len, fmt, __VA_ARGS__) @@ -222,19 +191,10 @@ * @param len a pointer to the length of the buffer * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 3, 4) -cx_attr_printf(4, 5) -cx_attr_cstr_arg(4) -cx_attr_export -int cx_sprintf_a( - const CxAllocator *alloc, - char **str, - size_t *len, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 3, 4) cx_attr_printf(4, 5) cx_attr_cstr_arg(4) +CX_EXPORT int cx_sprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, ...); /** @@ -248,7 +208,7 @@ * @param len (@c size_t*) a pointer to the length of the buffer * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf(str, len, fmt, ap) cx_vsprintf_a(cxDefaultAllocator, str, len, fmt, ap) @@ -266,20 +226,10 @@ * @param len a pointer to the length of the buffer * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull -cx_attr_cstr_arg(4) -cx_attr_access_rw(2) -cx_attr_access_rw(3) -cx_attr_export -int cx_vsprintf_a( - const CxAllocator *alloc, - char **str, - size_t *len, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(4) cx_attr_access_rw(2) cx_attr_access_rw(3) +CX_EXPORT int cx_vsprintf_a(const CxAllocator *alloc, char **str, size_t *len, const char *fmt, va_list ap); /** @@ -300,7 +250,7 @@ * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ... additional arguments - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_sprintf_s(buf, len, str, fmt, ...) cx_sprintf_sa(cxDefaultAllocator, buf, len, str, fmt, __VA_ARGS__) @@ -323,23 +273,11 @@ * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ... additional arguments - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull_arg(1, 2, 4, 5) -cx_attr_printf(5, 6) -cx_attr_cstr_arg(5) -cx_attr_access_rw(2) -cx_attr_access_rw(3) -cx_attr_access_rw(4) -cx_attr_export -int cx_sprintf_sa( - const CxAllocator *alloc, - char *buf, - size_t *len, - char **str, - const char *fmt, - ... -); +cx_attr_nonnull_arg(1, 2, 4, 5) cx_attr_printf(5, 6) cx_attr_cstr_arg(5) +cx_attr_access_rw(2) cx_attr_access_rw(3) cx_attr_access_rw(4) +CX_EXPORT int cx_sprintf_sa(const CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, ...); /** * An @c sprintf like function which allocates a new string when the buffer is not large enough. @@ -359,7 +297,7 @@ * @param str (@c char**) a pointer where the location of the result shall be stored * @param fmt (@c char*) the format string * @param ap (@c va_list) argument list - * @return (@c int) the length of produced string or an error code from stdlib printf implementation + * @return (@c int) the length of the produced string or an error code from stdlib printf implementation */ #define cx_vsprintf_s(buf, len, str, fmt, ap) cx_vsprintf_sa(cxDefaultAllocator, buf, len, str, fmt, ap) @@ -382,19 +320,10 @@ * @param str a pointer where the location of the result shall be stored * @param fmt the format string * @param ap argument list - * @return the length of produced string or an error code from stdlib printf implementation + * @return the length of the produced string or an error code from stdlib printf implementation */ -cx_attr_nonnull -cx_attr_cstr_arg(5) -cx_attr_export -int cx_vsprintf_sa( - const CxAllocator *alloc, - char *buf, - size_t *len, - char **str, - const char *fmt, - va_list ap -); +cx_attr_nonnull cx_attr_cstr_arg(5) +CX_EXPORT int cx_vsprintf_sa(const CxAllocator *alloc, char *buf, size_t *len, char **str, const char *fmt, va_list ap); #ifdef __cplusplus
--- a/ucx/cx/properties.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/properties.h Sat Nov 08 23:06:11 2025 +0100 @@ -94,8 +94,7 @@ /** * Default properties configuration. */ -cx_attr_export -extern const CxPropertiesConfig cx_properties_config_default; +CX_EXPORT extern const CxPropertiesConfig cx_properties_config_default; /** * Status codes for the properties interface. @@ -122,7 +121,7 @@ * You can use this enumerator to check for all "good" status results * by checking if the status is less than @c CX_PROPERTIES_OK. * - * A "good" status means, that you can refill data and continue parsing. + * A "good" status means that you can refill data and continue parsing. */ CX_PROPERTIES_OK, /** @@ -130,11 +129,11 @@ */ CX_PROPERTIES_NULL_INPUT, /** - * The line contains a delimiter, but no key. + * The line contains a delimiter but no key. */ CX_PROPERTIES_INVALID_EMPTY_KEY, /** - * The line contains data, but no delimiter. + * The line contains data but no delimiter. */ CX_PROPERTIES_INVALID_MISSING_DELIMITER, /** @@ -200,7 +199,7 @@ /** * A function that consumes a k/v-pair in a sink. * - * The sink could be e.g. a map and the sink function would be calling + * The sink could be a map, and the sink function would be calling * a map function to store the k/v-pair. * * @param prop the properties interface that wants to sink a k/v-pair @@ -210,7 +209,6 @@ * @retval zero success * @retval non-zero sinking the k/v-pair failed */ -cx_attr_nonnull typedef int(*cx_properties_sink_func)( CxProperties *prop, CxPropertiesSink *sink, @@ -257,7 +255,6 @@ * @retval zero success * @retval non-zero reading the data failed */ -cx_attr_nonnull typedef int(*cx_properties_read_func)( CxProperties *prop, CxPropertiesSource *src, @@ -272,7 +269,6 @@ * @retval zero initialization was successful * @retval non-zero otherwise */ -cx_attr_nonnull typedef int(*cx_properties_read_init_func)( CxProperties *prop, CxPropertiesSource *src @@ -284,7 +280,6 @@ * @param prop the properties interface that wants to read from the source * @param src the source */ -cx_attr_nonnull typedef void(*cx_properties_read_clean_func)( CxProperties *prop, CxPropertiesSource *src @@ -297,7 +292,7 @@ /** * The source object. * - * For example a file stream or a string. + * For example, a file stream or a string. */ void *src; /** @@ -331,38 +326,32 @@ * @see cxPropertiesInitDefault() */ cx_attr_nonnull -cx_attr_export -void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); +CX_EXPORT void cxPropertiesInit(CxProperties *prop, CxPropertiesConfig config); /** * Destroys the properties interface. * * @note Even when you are certain that you did not use the interface in a * way that caused a memory allocation, you should call this function anyway. - * Future versions of the library might add features that need additional memory - * and you really don't want to search the entire code where you might need - * add call to this function. + * Future versions of the library might add features that need additional memory, + * and you really don't want to search the entire code where you might need to + * add a call to this function. * * @param prop the properties interface */ cx_attr_nonnull -cx_attr_export -void cxPropertiesDestroy(CxProperties *prop); +CX_EXPORT void cxPropertiesDestroy(CxProperties *prop); /** * Destroys and re-initializes the properties interface. * - * You might want to use this, to reset the parser after + * You might want to use this to reset the parser after * encountering a syntax error. * * @param prop the properties interface */ cx_attr_nonnull -static inline void cxPropertiesReset(CxProperties *prop) { - CxPropertiesConfig config = prop->config; - cxPropertiesDestroy(prop); - cxPropertiesInit(prop, config); -} +CX_EXPORT void cxPropertiesReset(CxProperties *prop); /** * Initialize a properties parser with the default configuration. @@ -371,7 +360,7 @@ * @see cxPropertiesInit() */ #define cxPropertiesInitDefault(prop) \ - cxPropertiesInit(prop, cx_properties_config_default) + cxPropertiesInit(prop, cx_properties_config_default) /** * Fills the input buffer with data. @@ -394,44 +383,22 @@ * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFill() */ +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT int cxPropertiesFilln(CxProperties *prop, const char *buf, size_t len); + +/** + * Internal function, do not use. + * + * @param prop the properties interface + * @param str the text to fill in + * @retval zero success + * @retval non-zero a memory allocation was necessary but failed + */ cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -int cxPropertiesFilln( - CxProperties *prop, - const char *buf, - size_t len -); - -#ifdef __cplusplus -} // extern "C" -cx_attr_nonnull -static inline int cxPropertiesFill( - CxProperties *prop, - cxstring str -) { +CX_INLINE int cx_properties_fill(CxProperties *prop, cxstring str) { return cxPropertiesFilln(prop, str.ptr, str.length); } -cx_attr_nonnull -static inline int cxPropertiesFill( - CxProperties *prop, - cxmutstr str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cxPropertiesFill( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} - -extern "C" { -#else // __cplusplus /** * Fills the input buffer with data. * @@ -452,62 +419,17 @@ * @retval non-zero a memory allocation was necessary but failed * @see cxPropertiesFilln() */ -#define cxPropertiesFill(prop, str) _Generic((str), \ - cxstring: cx_properties_fill_cxstr, \ - cxmutstr: cx_properties_fill_mutstr, \ - char*: cx_properties_fill_str, \ - const char*: cx_properties_fill_str) \ - (prop, str) - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_cxstr( - CxProperties *prop, - cxstring str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} +#define cxPropertiesFill(prop, str) cx_properties_fill(prop, cx_strcast(str)) /** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -static inline int cx_properties_fill_mutstr( - CxProperties *prop, - cxmutstr str -) { - return cxPropertiesFilln(prop, str.ptr, str.length); -} - -/** - * @copydoc cxPropertiesFill() - */ -cx_attr_nonnull -cx_attr_cstr_arg(2) -static inline int cx_properties_fill_str( - CxProperties *prop, - const char *str -) { - return cxPropertiesFilln(prop, str, strlen(str)); -} -#endif - -/** - * Specifies stack memory that shall be used as internal buffer. + * Specifies stack memory that shall be used as an internal buffer. * * @param prop the properties interface * @param buf a pointer to stack memory * @param capacity the capacity of the stack memory */ cx_attr_nonnull -cx_attr_export -void cxPropertiesUseStack( - CxProperties *prop, - char *buf, - size_t capacity -); +CX_EXPORT void cxPropertiesUseStack(CxProperties *prop, char *buf, size_t capacity); /** * Retrieves the next key/value-pair. @@ -539,14 +461,8 @@ * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxPropertiesStatus cxPropertiesNext( - CxProperties *prop, - cxstring *key, - cxstring *value -); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxPropertiesStatus cxPropertiesNext(CxProperties *prop, cxstring *key, cxstring *value); /** * Creates a properties sink for an UCX map. @@ -562,10 +478,8 @@ * @return the sink * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -CxPropertiesSink cxPropertiesMapSink(CxMap *map); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxPropertiesSink cxPropertiesMapSink(CxMap *map); /** * Creates a properties source based on an UCX string. @@ -575,8 +489,7 @@ * @see cxPropertiesLoad() */ cx_attr_nodiscard -cx_attr_export -CxPropertiesSource cxPropertiesStringSource(cxstring str); +CX_EXPORT CxPropertiesSource cxPropertiesStringSource(cxstring str); /** * Creates a properties source based on C string with the specified length. @@ -586,11 +499,8 @@ * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len); /** * Creates a properties source based on a C string. @@ -602,11 +512,8 @@ * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -CxPropertiesSource cxPropertiesCstrSource(const char *str); +cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT CxPropertiesSource cxPropertiesCstrSource(const char *str); /** * Creates a properties source based on an FILE. @@ -617,11 +524,8 @@ * @return the properties source * @see cxPropertiesLoad() */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_r(1) -cx_attr_export -CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_r(1) +CX_EXPORT CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size); /** @@ -653,12 +557,8 @@ * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed */ cx_attr_nonnull -cx_attr_export -CxPropertiesStatus cxPropertiesLoad( - CxProperties *prop, - CxPropertiesSink sink, - CxPropertiesSource source -); +CX_EXPORT CxPropertiesStatus cxPropertiesLoad(CxProperties *prop, + CxPropertiesSink sink, CxPropertiesSource source); #ifdef __cplusplus } // extern "C"
--- a/ucx/cx/streams.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/streams.h Sat Nov 08 23:06:11 2025 +0100 @@ -54,7 +54,7 @@ * @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 + * @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 @@ -62,19 +62,10 @@ * @return the total number of bytes copied */ cx_attr_nonnull_arg(1, 2, 3, 4) -cx_attr_access_r(1) -cx_attr_access_w(2) -cx_attr_access_w(5) -cx_attr_export -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 -); +cx_attr_access_r(1) cx_attr_access_w(2) cx_attr_access_w(5) +CX_EXPORT 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. @@ -86,7 +77,7 @@ * @param buf (@c char*) a pointer to the copy buffer or @c NULL if a buffer * shall be implicitly created on the heap * @param bufsize (@c size_t) the size of the copy buffer - if @p buf is - * @c NULL you can set this to zero to let the implementation decide + * @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) \ @@ -95,7 +86,7 @@ /** * Reads data from a stream and writes it to another stream. * - * The data is temporarily stored in a stack allocated buffer. + * The data is temporarily stored in a stack-allocated buffer. * * @param src the source stream * @param dest the destination stream @@ -104,22 +95,14 @@ * @param n the maximum number of bytes that shall be copied. * @return total number of bytes copied */ -cx_attr_nonnull -cx_attr_access_r(1) -cx_attr_access_w(2) -cx_attr_export -size_t cx_stream_ncopy( - void *src, - void *dest, - cx_read_func rfnc, - cx_write_func wfnc, - size_t n -); +cx_attr_nonnull cx_attr_access_r(1) cx_attr_access_w(2) +CX_EXPORT 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. + * The data is temporarily stored in a stack-allocated buffer. * * @param src (@c void*) the source stream * @param dest (@c void*) the destination stream
--- a/ucx/cx/string.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/string.h Sat Nov 08 23:06:11 2025 +0100 @@ -48,8 +48,7 @@ /** * The maximum length of the "needle" in cx_strstr() that can use SBO. */ -cx_attr_export -extern const unsigned cx_strstr_sbo_size; +CX_EXPORT extern const unsigned cx_strstr_sbo_size; /** * The UCX string structure. @@ -112,10 +111,10 @@ */ size_t pos; /** - * Position of next delimiter in the source string. + * Position of the 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 + * is undefined. If the tokenizer reaches the end of the string, this field * contains the length of the source string. */ size_t delim_pos; @@ -174,15 +173,13 @@ * * If you need to wrap a constant string, use cx_str(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_mutstrn() */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -cxmutstr cx_mutstr(char *cstring); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT cxmutstr cx_mutstr(char *cstring); /** * Wraps a string that does not need to be zero-terminated. @@ -200,13 +197,8 @@ * * @see cx_mutstr() */ -cx_attr_nodiscard -cx_attr_access_rw(1, 2) -cx_attr_export -cxmutstr cx_mutstrn( - char *cstring, - size_t length -); +cx_attr_nodiscard cx_attr_access_rw(1, 2) +CX_EXPORT cxmutstr cx_mutstrn(char *cstring, size_t length); /** * Wraps a string that must be zero-terminated. @@ -220,15 +212,13 @@ * * If you need to wrap a non-constant string, use cx_mutstr(). * - * @param cstring the string to wrap, must be zero-terminated + * @param cstring the string to wrap (must be zero-terminated) * @return the wrapped string * * @see cx_strn() */ -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_export -cxstring cx_str(const char *cstring); +cx_attr_nodiscard cx_attr_cstr_arg(1) +CX_EXPORT cxstring cx_str(const char *cstring); /** @@ -247,28 +237,27 @@ * * @see cx_str() */ -cx_attr_nodiscard -cx_attr_access_r(1, 2) -cx_attr_export -cxstring cx_strn( - const char *cstring, - size_t length -); +cx_attr_nodiscard cx_attr_access_r(1, 2) +CX_EXPORT cxstring cx_strn(const char *cstring, size_t length); #ifdef __cplusplus } // extern "C" cx_attr_nodiscard -static inline cxstring cx_strcast(cxmutstr str) { +CX_CPPDECL cxstring cx_strcast(cxmutstr str) { return cx_strn(str.ptr, str.length); } cx_attr_nodiscard -static inline cxstring cx_strcast(cxstring str) { +CX_CPPDECL cxstring cx_strcast(cxstring str) { return str; } cx_attr_nodiscard -static inline cxstring cx_strcast(const char *str) { +CX_CPPDECL cxstring cx_strcast(const char *str) { return cx_str(str); } +cx_attr_nodiscard +CX_CPPDECL cxstring cx_strcast(const unsigned char *str) { + return cx_str(static_cast<const char*>(str)); +} extern "C" { #else /** @@ -278,7 +267,7 @@ * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_m(cxmutstr str) { +CX_INLINE cxstring cx_strcast_m(cxmutstr str) { return (cxstring) {str.ptr, str.length}; } /** @@ -288,7 +277,7 @@ * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_c(cxstring str) { +CX_INLINE cxstring cx_strcast_c(cxstring str) { return str; } @@ -299,25 +288,32 @@ * @see cx_strcast() */ cx_attr_nodiscard -static inline cxstring cx_strcast_z(const char *str) { +CX_INLINE cxstring cx_strcast_u(const unsigned char *str) { + return cx_str((const char*)str); +} + +/** + * Internal function, do not use. + * @param str + * @return + * @see cx_strcast() + */ +cx_attr_nodiscard +CX_INLINE cxstring cx_strcast_z(const char *str) { return cx_str(str); } /** -* Casts a mutable string to an immutable string. -* -* Does nothing for already immutable strings. -* -* @note This is not seriously a cast. Instead, you get a copy -* of the struct with the desired pointer type. Both structs still -* point to the same location, though! -* -* @param str (@c cxstring or @c cxmutstr) the string to cast -* @return (@c cxstring) an immutable copy of the string pointer -*/ + * Wraps any string into an UCX string. + * + * @param str (any supported string type) the string to cast + * @return (@c cxstring) the string wrapped as UCX string + */ #define cx_strcast(str) _Generic((str), \ cxmutstr: cx_strcast_m, \ cxstring: cx_strcast_c, \ + const unsigned char*: cx_strcast_u, \ + unsigned char *: cx_strcast_u, \ const char*: cx_strcast_z, \ char *: cx_strcast_z) (str) #endif @@ -325,37 +321,32 @@ /** * Passes the pointer in this string to the cxDefaultAllocator's @c free() function. * - * The pointer in the struct is set to @c NULL and the length is set to zero + * The pointer in the struct is set to @c NULL, and the length is set to zero, * which means that this function protects you against double-free. * * @note There is no implementation for cxstring, because it is unlikely that * you ever have a <code>const char*</code> you are really supposed to free. - * If you encounter such situation, you should double-check your code. + * If you encounter such a situation, you should double-check your code. * * @param str the string to free */ -cx_attr_export -void cx_strfree(cxmutstr *str); +CX_EXPORT void cx_strfree(cxmutstr *str); /** - * Passes the pointer in this string to the allocators free function. + * Passes the pointer in this string to the allocator's free function. * - * The pointer in the struct is set to @c NULL and the length is set to zero + * The pointer in the struct is set to @c NULL, and the length is set to zero, * which means that this function protects you against double-free. * * @note There is no implementation for cxstring, because it is unlikely that * you ever have a <code>const char*</code> you are really supposed to free. - * If you encounter such situation, you should double-check your code. + * If you encounter such a situation, you should double-check your code. * * @param alloc the allocator * @param str the string to free */ cx_attr_nonnull_arg(1) -cx_attr_export -void cx_strfree_a( - const CxAllocator *alloc, - cxmutstr *str -); +CX_EXPORT void cx_strfree_a(const CxAllocator *alloc, cxmutstr *str); /** * Copies a string. @@ -373,12 +364,7 @@ * @retval non-zero if re-allocation failed */ cx_attr_nonnull_arg(1) -cx_attr_export -int cx_strcpy_a( - const CxAllocator *alloc, - cxmutstr *dest, - cxstring src -); +CX_EXPORT int cx_strcpy_a(const CxAllocator *alloc, cxmutstr *dest, cxstring src); /** @@ -410,11 +396,7 @@ * @return the accumulated length of all strings */ cx_attr_nodiscard -cx_attr_export -size_t cx_strlen( - size_t count, - ... -); +CX_EXPORT size_t cx_strlen(size_t count, ...); /** * Concatenates strings. @@ -438,15 +420,9 @@ * @param ... all other UCX strings * @return the concatenated string */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strcat_ma( - const CxAllocator *alloc, - cxmutstr str, - size_t count, - ... -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strcat_ma(const CxAllocator *alloc, + cxmutstr str, size_t count, ...); /** * Concatenates strings and returns a new string. @@ -467,7 +443,7 @@ * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_a(alloc, count, ...) \ -cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + cx_strcat_ma(alloc, cx_mutstrn(NULL, 0), count, __VA_ARGS__) /** * Concatenates strings and returns a new string. @@ -487,7 +463,7 @@ * @return (@c cxmutstr) the concatenated string */ #define cx_strcat(count, ...) \ -cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) + cx_strcat_ma(cxDefaultAllocator, cx_mutstrn(NULL, 0), count, __VA_ARGS__) /** * Concatenates strings. @@ -511,7 +487,7 @@ * @return (@c cxmutstr) the concatenated string */ #define cx_strcat_m(str, count, ...) \ -cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) + cx_strcat_ma(cxDefaultAllocator, str, count, __VA_ARGS__) /** * Returns a substring starting at the specified location. @@ -529,11 +505,7 @@ * @see cx_strsubsl_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strsubs( - cxstring string, - size_t start -); +CX_EXPORT cxstring cx_strsubs(cxstring string, size_t start); /** * Returns a substring starting at the specified location. @@ -555,12 +527,7 @@ * @see cx_strsubsl_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strsubsl( - cxstring string, - size_t start, - size_t length -); +CX_EXPORT cxstring cx_strsubsl(cxstring string, size_t start, size_t length); /** * Returns a substring starting at the specified location. @@ -578,11 +545,7 @@ * @see cx_strsubsl() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strsubs_m( - cxmutstr string, - size_t start -); +CX_EXPORT cxmutstr cx_strsubs_m(cxmutstr string, size_t start); /** * Returns a substring starting at the specified location. @@ -604,12 +567,7 @@ * @see cx_strsubsl() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strsubsl_m( - cxmutstr string, - size_t start, - size_t length -); +CX_EXPORT 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 @@ -624,11 +582,7 @@ * @see cx_strchr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strchr( - cxstring string, - int chr -); +CX_EXPORT cxstring cx_strchr(cxstring string, int chr); /** * Returns a substring starting at the location of the first occurrence of the @@ -643,11 +597,7 @@ * @see cx_strchr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strchr_m( - cxmutstr string, - int chr -); +CX_EXPORT cxmutstr cx_strchr_m(cxmutstr string, int chr); /** * Returns a substring starting at the location of the last occurrence of the @@ -662,11 +612,7 @@ * @see cx_strrchr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strrchr( - cxstring string, - int chr -); +CX_EXPORT cxstring cx_strrchr(cxstring string, int chr); /** * Returns a substring starting at the location of the last occurrence of the @@ -681,11 +627,7 @@ * @see cx_strrchr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strrchr_m( - cxmutstr string, - int chr -); +CX_EXPORT cxmutstr cx_strrchr_m(cxmutstr string, int chr); /** * Returns a substring starting at the location of the first occurrence of the @@ -704,11 +646,7 @@ * @see cx_strstr_m() */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strstr( - cxstring haystack, - cxstring needle -); +CX_EXPORT cxstring cx_strstr(cxstring haystack, cxstring needle); /** * Returns a substring starting at the location of the first occurrence of the @@ -727,11 +665,7 @@ * @see cx_strstr() */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strstr_m( - cxmutstr haystack, - cxstring needle -); +CX_EXPORT cxmutstr cx_strstr_m(cxmutstr haystack, cxstring needle); /** * Splits a given string using a delimiter string. @@ -745,16 +679,9 @@ * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(4, 3) -cx_attr_export -size_t cx_strsplit( - cxstring string, - cxstring delim, - size_t limit, - cxstring *output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +CX_EXPORT size_t cx_strsplit(cxstring string, cxstring delim, + size_t limit, cxstring *output); /** * Splits a given string using a delimiter string. @@ -775,17 +702,10 @@ * written to * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -size_t cx_strsplit_a( - const CxAllocator *allocator, - cxstring string, - cxstring delim, - size_t limit, - cxstring **output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT size_t cx_strsplit_a(const CxAllocator *allocator, + cxstring string, cxstring delim, + size_t limit, cxstring **output); /** @@ -800,16 +720,9 @@ * @param output a preallocated array of at least @p limit length * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(4, 3) -cx_attr_export -size_t cx_strsplit_m( - cxmutstr string, - cxstring delim, - size_t limit, - cxmutstr *output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(4, 3) +CX_EXPORT size_t cx_strsplit_m(cxmutstr string, cxstring delim, + size_t limit, cxmutstr *output); /** * Splits a given string using a delimiter string. @@ -830,17 +743,10 @@ * written to * @return the actual number of split items */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -size_t cx_strsplit_ma( - const CxAllocator *allocator, - cxmutstr string, - cxstring delim, - size_t limit, - cxmutstr **output -); +cx_attr_nodiscard cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT size_t cx_strsplit_ma(const CxAllocator *allocator, + cxmutstr string, cxstring delim, size_t limit, + cxmutstr **output); /** * Compares two strings. @@ -851,11 +757,17 @@ * than @p s2, zero if both strings equal */ cx_attr_nodiscard -cx_attr_export -int cx_strcmp( - cxstring s1, - cxstring s2 -); +CX_EXPORT int cx_strcmp_(cxstring s1, cxstring s2); + +/** + * 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 + */ +#define cx_strcmp(s1, s2) cx_strcmp_(cx_strcast(s1), cx_strcast(s2)) /** * Compares two strings ignoring case. @@ -866,29 +778,33 @@ * than @p s2, zero if both strings equal ignoring case */ cx_attr_nodiscard -cx_attr_export -int cx_strcasecmp( - cxstring s1, - cxstring s2 -); +CX_EXPORT int cx_strcasecmp_(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 + */ +#define cx_strcasecmp(s1, s2) cx_strcasecmp_(cx_strcast(s1), cx_strcast(s2)) /** * Compares two strings. * * This function has a compatible signature for the use as a cx_compare_func. * + * @attention This function can @em only compare UCX strings. It is unsafe to + * pass normal C-strings to this function. + * * @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 */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cx_strcmp_p( - const void *s1, - const void *s2 -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_strcmp_p(const void *s1, const void *s2); /** * Compares two strings ignoring case. @@ -900,13 +816,8 @@ * @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 */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -int cx_strcasecmp_p( - const void *s1, - const void *s2 -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT int cx_strcasecmp_p(const void *s1, const void *s2); /** @@ -921,13 +832,8 @@ * @return a duplicate of the string * @see cx_strdup() */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strdup_a_( - const CxAllocator *allocator, - cxstring string -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strdup_a_(const CxAllocator *allocator, cxstring string); /** * Creates a duplicate of the specified string. @@ -942,8 +848,7 @@ * @see cx_strdup() * @see cx_strfree_a() */ -#define cx_strdup_a(allocator, string) \ - cx_strdup_a_((allocator), cx_strcast(string)) +#define cx_strdup_a(allocator, string) cx_strdup_a_((allocator), cx_strcast(string)) /** * Creates a duplicate of the specified string. @@ -970,8 +875,7 @@ * @return the trimmed string */ cx_attr_nodiscard -cx_attr_export -cxstring cx_strtrim(cxstring string); +CX_EXPORT cxstring cx_strtrim(cxstring string); /** * Omits leading and trailing spaces. @@ -983,11 +887,10 @@ * @return the trimmed string */ cx_attr_nodiscard -cx_attr_export -cxmutstr cx_strtrim_m(cxmutstr string); +CX_EXPORT cxmutstr cx_strtrim_m(cxmutstr string); /** - * Checks, if a string has a specific prefix. + * Checks if a string has a specific prefix. * * @param string the string to check * @param prefix the prefix the string should have @@ -995,14 +898,20 @@ * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strprefix( - cxstring string, - cxstring prefix -); +CX_EXPORT bool cx_strprefix_(cxstring string, cxstring prefix); /** - * Checks, if a string has a specific suffix. + * 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 + */ +#define cx_strprefix(string, prefix) cx_strprefix_(cx_strcast(string), cx_strcast(prefix)) + +/** + * Checks if a string has a specific suffix. * * @param string the string to check * @param suffix the suffix the string should have @@ -1010,14 +919,20 @@ * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strsuffix( - cxstring string, - cxstring suffix -); +CX_EXPORT bool cx_strsuffix_(cxstring string, cxstring suffix); /** - * Checks, if a string has a specific prefix, ignoring the case. + * 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 + */ +#define cx_strsuffix(string, suffix) cx_strsuffix_(cx_strcast(string), cx_strcast(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 @@ -1025,11 +940,17 @@ * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strcaseprefix( - cxstring string, - cxstring prefix -); +CX_EXPORT bool cx_strcaseprefix_(cxstring string, cxstring prefix); + +/** + * 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 + */ +#define cx_strcaseprefix(string, prefix) cx_strcaseprefix_(cx_strcast(string), cx_strcast(prefix)) /** * Checks, if a string has a specific suffix, ignoring the case. @@ -1040,16 +961,22 @@ * @c false otherwise */ cx_attr_nodiscard -cx_attr_export -bool cx_strcasesuffix( - cxstring string, - cxstring suffix -); +CX_EXPORT bool cx_strcasesuffix_(cxstring string, cxstring suffix); + +/** + * 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 + */ +#define cx_strcasesuffix(string, suffix) cx_strcasesuffix_(cx_strcast(string), cx_strcast(suffix)) /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by @p allocator and is guaranteed * to be zero-terminated. @@ -1064,21 +991,14 @@ * @param replmax maximum number of replacements * @return the resulting string after applying the replacements */ -cx_attr_nodiscard -cx_attr_nonnull -cx_attr_export -cxmutstr cx_strreplacen_a( - const CxAllocator *allocator, - cxstring str, - cxstring search, - cxstring replacement, - size_t replmax -); +cx_attr_nodiscard cx_attr_nonnull +CX_EXPORT cxmutstr cx_strreplacen_a(const CxAllocator *allocator, + cxstring str, cxstring search, cxstring replacement, size_t replmax); /** * Replaces a string with another string. * - * Replaces at most @p replmax occurrences. + * The function replaces at most @p replmax occurrences. * * The returned string will be allocated by the cxDefaultAllocator and is guaranteed * to be zero-terminated. @@ -1093,7 +1013,7 @@ * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplacen(str, search, replacement, replmax) \ -cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) + cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, replmax) /** * Replaces a string with another string. @@ -1111,7 +1031,7 @@ * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace_a(allocator, str, search, replacement) \ -cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) + cx_strreplacen_a(allocator, str, search, replacement, SIZE_MAX) /** * Replaces a string with another string. @@ -1128,7 +1048,7 @@ * @return (@c cxmutstr) the resulting string after applying the replacements */ #define cx_strreplace(str, search, replacement) \ -cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) + cx_strreplacen_a(cxDefaultAllocator, str, search, replacement, SIZE_MAX) /** * Creates a string tokenization context. @@ -1139,12 +1059,7 @@ * @return a new string tokenization context */ cx_attr_nodiscard -cx_attr_export -CxStrtokCtx cx_strtok_( - cxstring str, - cxstring delim, - size_t limit -); +CX_EXPORT CxStrtokCtx cx_strtok_(cxstring str, cxstring delim, size_t limit); /** * Creates a string tokenization context. @@ -1155,7 +1070,7 @@ * @return (@c CxStrtokCtx) a new string tokenization context */ #define cx_strtok(str, delim, limit) \ - cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit)) + cx_strtok_(cx_strcast(str), cx_strcast(delim), (limit)) /** * Returns the next token. @@ -1167,14 +1082,8 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_w(2) -cx_attr_export -bool cx_strtok_next( - CxStrtokCtx *ctx, - cxstring *token -); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +CX_EXPORT bool cx_strtok_next(CxStrtokCtx *ctx, cxstring *token); /** * Returns the next token of a mutable string. @@ -1190,14 +1099,8 @@ * @return true if successful, false if the limit or the end of the string * has been reached */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_access_w(2) -cx_attr_export -bool cx_strtok_next_m( - CxStrtokCtx *ctx, - cxmutstr *token -); +cx_attr_nonnull cx_attr_nodiscard cx_attr_access_w(2) +CX_EXPORT bool cx_strtok_next_m(CxStrtokCtx *ctx, cxmutstr *token); /** * Defines an array of more delimiters for the specified tokenization context. @@ -1206,14 +1109,8 @@ * @param delim array of more delimiters * @param count number of elements in the array */ -cx_attr_nonnull -cx_attr_access_r(2, 3) -cx_attr_export -void cx_strtok_delim( - CxStrtokCtx *ctx, - const cxstring *delim, - size_t count -); +cx_attr_nonnull cx_attr_access_r(2, 3) +CX_EXPORT void cx_strtok_delim(CxStrtokCtx *ctx, const cxstring *delim, size_t count); /* ------------------------------------------------------------------------- * * string to number conversion functions * @@ -1229,12 +1126,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtos_lc_(cxstring str, short *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1246,12 +1143,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi_lc_(cxstring str, int *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1263,12 +1160,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtol_lc_(cxstring str, long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1280,12 +1177,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoll_lc_(cxstring str, long long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1297,12 +1194,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi8_lc_(cxstring str, int8_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1314,12 +1211,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi16_lc_(cxstring str, int16_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1331,12 +1228,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi32_lc_(cxstring str, int32_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1348,12 +1245,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoi64_lc_(cxstring str, int64_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1365,12 +1262,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtous_lc_(cxstring str, unsigned short *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1382,12 +1279,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou_lc_(cxstring str, unsigned int *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1399,12 +1296,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoul_lc_(cxstring str, unsigned long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1416,12 +1313,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoull_lc_(cxstring str, unsigned long long *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1433,12 +1330,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou8_lc_(cxstring str, uint8_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1450,12 +1347,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou16_lc_(cxstring str, uint16_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1467,12 +1364,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou32_lc_(cxstring str, uint32_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1484,12 +1381,12 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtou64_lc_(cxstring str, uint64_t *output, int base, const char *groupsep); /** * Converts a string to a number. @@ -1501,15 +1398,15 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtoz_lc_(cxstring str, size_t *output, int base, const char *groupsep); /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -1518,15 +1415,15 @@ * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtof_lc_(cxstring str, float *output, char decsep, const char *groupsep); /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -1535,12 +1432,12 @@ * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ -cx_attr_access_w(2) cx_attr_nonnull_arg(2) cx_attr_export -int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); +cx_attr_access_w(2) cx_attr_nonnull_arg(2) +CX_EXPORT int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep); /** * Converts a string to a number. @@ -1552,7 +1449,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1568,7 +1465,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1584,7 +1481,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1600,7 +1497,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1616,7 +1513,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1632,7 +1529,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1648,7 +1545,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1664,7 +1561,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1680,7 +1577,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1696,7 +1593,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1712,7 +1609,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1728,7 +1625,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1744,7 +1641,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1760,7 +1657,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1776,7 +1673,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1792,7 +1689,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1808,7 +1705,7 @@ * @param str the string to convert * @param output a pointer to the integer variable where the result shall be stored * @param base 2, 8, 10, or 16 - * @param groupsep (@c const @c char*) each character in this string is treated as group separator and ignored during conversion + * @param groupsep (@c const @c char*) each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ @@ -1821,7 +1718,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1839,7 +1736,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1857,7 +1754,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1875,7 +1772,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1893,7 +1790,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1911,7 +1808,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1929,7 +1826,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1947,7 +1844,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1965,7 +1862,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -1983,7 +1880,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2001,7 +1898,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2019,7 +1916,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2037,7 +1934,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2055,7 +1952,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2073,7 +1970,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2091,7 +1988,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2109,7 +2006,7 @@ * In that case the function sets errno to EINVAL when the reason is an invalid character or an unsupported base. * It sets errno to ERANGE when the target datatype is too small. * - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose the set of group separators, use the @c _lc variant of this function (e.g. cx_strtoz_lc()). * * @param str the string to convert @@ -2121,7 +2018,7 @@ #define cx_strtou64(str, output, base) cx_strtou64_lc_(cx_strcast(str), output, base, ",") /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -2130,14 +2027,14 @@ * @param str the string to convert * @param output a pointer to the float variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ #define cx_strtof_lc(str, output, decsep, groupsep) cx_strtof_lc_(cx_strcast(str), output, decsep, groupsep) /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. @@ -2145,21 +2042,21 @@ * @param str the string to convert * @param output a pointer to the double variable where the result shall be stored * @param decsep the decimal separator - * @param groupsep each character in this string is treated as group separator and ignored during conversion + * @param groupsep each character in this string is treated as a group separator and ignored during conversion * @retval zero success * @retval non-zero conversion was not possible */ #define cx_strtod_lc(str, output, decsep, groupsep) cx_strtod_lc_(cx_strcast(str), output, decsep, groupsep) /** - * Converts a string to a single precision floating point number. + * Converts a string to a single precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. * It sets errno to ERANGE when the necessary representation would exceed the limits defined in libc's float.h. * * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert @@ -2170,13 +2067,13 @@ #define cx_strtof(str, output) cx_strtof_lc_(cx_strcast(str), output, '.', ",") /** - * Converts a string to a double precision floating point number. + * Converts a string to a double precision floating-point number. * * The function returns non-zero when conversion is not possible. * In that case the function sets errno to EINVAL when the reason is an invalid character. * * The decimal separator is assumed to be a dot character. - * The comma character is treated as group separator and ignored during parsing. + * The comma character is treated as a group separator and ignored during parsing. * If you want to choose a different format, use cx_strtof_lc(). * * @param str the string to convert
--- a/ucx/cx/test.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/test.h Sat Nov 08 23:06:11 2025 +0100 @@ -56,7 +56,7 @@ * } * </code> * - * @attention Do not call own functions within a test, that use + * @attention Do not call own functions within a test that use * CX_TEST_ASSERT() macros and are not defined by using CX_TEST_SUBROUTINE(). * * @author Mike Becker @@ -136,10 +136,7 @@ * @param name optional name of the suite * @return a new test suite */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_cstr_arg(1) -cx_attr_malloc +cx_attr_nonnull cx_attr_nodiscard cx_attr_cstr_arg(1) cx_attr_malloc static inline CxTestSuite* cx_test_suite_new(const char *name) { CxTestSuite* suite = (CxTestSuite*) malloc(sizeof(CxTestSuite)); if (suite != NULL) { @@ -157,7 +154,7 @@ * * @param suite the test suite to free */ -static inline void cx_test_suite_free(CxTestSuite* suite) { +CX_INLINE void cx_test_suite_free(CxTestSuite* suite) { if (suite == NULL) return; CxTestSet *l = suite->tests; while (l != NULL) { @@ -171,13 +168,13 @@ /** * Registers a test function with the specified test suite. * - * @param suite the suite, the test function shall be added to + * @param suite the suite the test function shall be added to * @param test the test function to register * @retval zero success * @retval non-zero failure */ cx_attr_nonnull -static inline int cx_test_register(CxTestSuite* suite, CxTest test) { +CX_INLINE int cx_test_register(CxTestSuite* suite, CxTest test) { CxTestSet *t = (CxTestSet*) malloc(sizeof(CxTestSet)); if (t) { t->test = test; @@ -204,8 +201,7 @@ * @param out_writer the write function writing to @p out_target */ cx_attr_nonnull -static inline void cx_test_run(CxTestSuite *suite, - void *out_target, cx_write_func out_writer) { +CX_INLINE void cx_test_run(CxTestSuite *suite, void *out_target, cx_write_func out_writer) { if (suite->name == NULL) { out_writer("*** Test Suite ***\n", 1, 19, out_target); } else { @@ -263,7 +259,7 @@ * } * @endcode * - * @attention Any CX_TEST_ASSERT() calls must be performed in scope of + * @attention Any CX_TEST_ASSERT() calls must be performed in the scope of * #CX_TEST_DO. */ #define CX_TEST_DO _writefnc_("Running ", 1, 8, _output_);\
--- a/ucx/cx/tree.h Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/cx/tree.h Sat Nov 08 23:06:11 2025 +0100 @@ -49,12 +49,12 @@ * * This iterator is not position-aware in a strict sense, as it does not assume * a particular order of elements in the tree. However, the iterator keeps track - * of the number of nodes it has passed in a counter variable. + * of the number of nodes it has passed in a counter-variable. * Each node, regardless of the number of passes, is counted only once. * * @note Objects that are pointed to by an iterator are mutable through that * iterator. However, if the - * underlying data structure is mutated by other means than this iterator (e.g. + * underlying data structure is mutated by other means than this iterator (e.g., * elements added or removed), the iterator becomes invalid (regardless of what * cxIteratorValid() returns). * @@ -71,7 +71,7 @@ bool skip; /** * Set to true, when the iterator shall visit a node again - * when all it's children have been processed. + * when all its children have been processed. */ bool visit_on_exit; /** @@ -97,7 +97,7 @@ */ void *node; /** - * Stores a copy of the next pointer of the visited node. + * Stores a copy of the pointer to the successor of the visited node. * Allows freeing a node on exit without corrupting the iteration. */ void *node_next; @@ -155,12 +155,12 @@ * * This iterator is not position-aware in a strict sense, as it does not assume * a particular order of elements in the tree. However, the iterator keeps track - * of the number of nodes it has passed in a counter variable. + * of the number of nodes it has passed in a counter-variable. * Each node, regardless of the number of passes, is counted only once. * * @note Objects that are pointed to by an iterator are mutable through that * iterator. However, if the - * underlying data structure is mutated by other means than this iterator (e.g. + * underlying data structure is mutated by other means than this iterator (e.g., * elements added or removed), the iterator becomes invalid (regardless of what * cxIteratorValid() returns). * @@ -212,24 +212,14 @@ * @param iter the iterator */ cx_attr_nonnull -static inline void cxTreeIteratorDispose(CxTreeIterator *iter) { - cxFreeDefault(iter->stack); - iter->stack = NULL; -} +CX_EXPORT void cxTreeIteratorDispose(CxTreeIterator *iter); /** * Releases internal memory of the given tree visitor. * @param visitor the visitor */ cx_attr_nonnull -static inline void cxTreeVisitorDispose(CxTreeVisitor *visitor) { - struct cx_tree_visitor_queue_s *q = visitor->queue_next; - while (q != NULL) { - struct cx_tree_visitor_queue_s *next = q->next; - cxFreeDefault(q); - q = next; - } -} +CX_EXPORT void cxTreeVisitorDispose(CxTreeVisitor *visitor); /** * Advises the iterator to skip the subtree below the current node and @@ -250,7 +240,7 @@ /** * Links a node to a (new) parent. * - * If the node has already a parent, it is unlinked, first. + * If the node already has a parent, it is unlinked, first. * If the parent has children already, the node is @em appended to the list * of all currently existing children. * @@ -265,16 +255,9 @@ * @see cx_tree_unlink() */ cx_attr_nonnull -cx_attr_export -void cx_tree_link( - void *parent, - void *node, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_tree_link(void *parent, void *node, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Unlinks a node from its parent. @@ -291,15 +274,9 @@ * @see cx_tree_link() */ cx_attr_nonnull -cx_attr_export -void cx_tree_unlink( - void *node, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +CX_EXPORT void cx_tree_unlink(void *node, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Macro that can be used instead of the magic value for infinite search depth. @@ -318,8 +295,8 @@ * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file shall * return a positive number to indicate that a child node might contain the * searched item. On the other hand, if the node denotes a path that is not a * prefix of the searched filename, the function would return -1 to indicate @@ -330,9 +307,8 @@ * * @return 0 if the node contains the data, * positive if one of the children might contain the data, - * negative if neither the node, nor the children contains the data + * negative if neither the node nor the children contains the data */ -cx_attr_nonnull typedef int (*cx_tree_search_data_func)(const void *node, const void *data); @@ -348,8 +324,8 @@ * Zero means exact match and a positive number is an implementation defined * measure for the distance to an exact match. * - * For example if a tree stores file path information, a node that is - * describing a parent directory of a filename that is searched, shall + * For example, consider a tree that stores file path information. + * A node which is describing a parent directory of a searched file shall * return a positive number to indicate that a child node might contain the * searched item. On the other hand, if the node denotes a path that is not a * prefix of the searched filename, the function would return -1 to indicate @@ -360,19 +336,18 @@ * * @return 0 if @p node contains the same data as @p new_node, * positive if one of the children might contain the data, - * negative if neither the node, nor the children contains the data + * negative if neither the node nor the children contains the data */ -cx_attr_nonnull typedef int (*cx_tree_search_func)(const void *node, const void *new_node); /** * Searches for data in a tree. * - * When the data cannot be found exactly, the search function might return a - * closest result which might be a good starting point for adding a new node + * When the data cannot be found exactly, the search function might return the + * closest result, which might be a good starting point for adding a new node * to the tree (see also #cx_tree_add()). * - * Depending on the tree structure it is not necessarily guaranteed that the + * Depending on the tree structure, it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node * with the best match according to the @p sfunc (meaning: the return value of * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary @@ -389,27 +364,19 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -int cx_tree_search_data( - const void *root, - size_t depth, - const void *data, - cx_tree_search_data_func sfunc, - void **result, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT int cx_tree_search_data(const void *root, size_t depth, + const void *data, cx_tree_search_data_func sfunc, + void **result, ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Searches for a node in a tree. * * When no node with the same data can be found, the search function might - * return a closest result which might be a good starting point for adding the + * return the closest result, which might be a good starting point for adding the * new node to the tree (see also #cx_tree_add()). * - * Depending on the tree structure it is not necessarily guaranteed that the + * Depending on the tree structure, it is not necessarily guaranteed that the * "closest" match is uniquely defined. This function will search for a node * with the best match according to the @p sfunc (meaning: the return value of * @p sfunc which is closest to zero). If that is also ambiguous, an arbitrary @@ -426,18 +393,10 @@ * could contain the node (but doesn't right now), negative if the tree does not * contain any node that might be related to the searched data */ -cx_attr_nonnull -cx_attr_access_w(5) -cx_attr_export -int cx_tree_search( - const void *root, - size_t depth, - const void *node, - cx_tree_search_func sfunc, - void **result, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +cx_attr_nonnull cx_attr_access_w(5) +CX_EXPORT int cx_tree_search(const void *root, size_t depth, + const void *node, cx_tree_search_func sfunc, + void **result, ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Creates a depth-first iterator for a tree with the specified root node. @@ -460,13 +419,8 @@ * @see cxTreeIteratorDispose() */ cx_attr_nodiscard -cx_attr_export -CxTreeIterator cx_tree_iterator( - void *root, - bool visit_on_exit, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +CX_EXPORT CxTreeIterator cx_tree_iterator(void *root, bool visit_on_exit, + ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Creates a breadth-first iterator for a tree with the specified root node. @@ -487,24 +441,19 @@ * @see cxTreeVisitorDispose() */ cx_attr_nodiscard -cx_attr_export -CxTreeVisitor cx_tree_visitor( - void *root, - ptrdiff_t loc_children, - ptrdiff_t loc_next -); +CX_EXPORT CxTreeVisitor cx_tree_visitor(void *root, + ptrdiff_t loc_children, ptrdiff_t loc_next); /** * Describes a function that creates a tree node from the specified data. - * The first argument points to the data the node shall contain and - * the second argument may be used for additional data (e.g. an allocator). + * The first argument points to the data the node shall contain, and + * the second argument may be used for additional data (e.g., an allocator). * Functions of this type shall either return a new pointer to a newly * created node or @c NULL when allocation fails. * * @note the function may leave the node pointers in the struct uninitialized. * The caller is responsible to set them according to the intended use case. */ -cx_attr_nonnull_arg(1) typedef void *(*cx_tree_node_create_func)(const void *, void *); /** @@ -513,8 +462,7 @@ * This variable is used by #cx_tree_add_array() and #cx_tree_add_iter() to * implement optimized insertion of multiple elements into a tree. */ -cx_attr_export -extern unsigned int cx_tree_add_look_around_depth; +CX_EXPORT extern unsigned int cx_tree_add_look_around_depth; /** * Adds multiple elements efficiently to a tree. @@ -554,23 +502,12 @@ * @return the number of nodes created and added * @see cx_tree_add() */ -cx_attr_nonnull_arg(1, 3, 4, 6, 7) -cx_attr_access_w(6) -cx_attr_export -size_t cx_tree_add_iter( - struct cx_iterator_base_s *iter, - size_t num, - cx_tree_search_func sfunc, - cx_tree_node_create_func cfunc, - void *cdata, - void **failed, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +cx_attr_nonnull_arg(1, 3, 4, 6, 7) cx_attr_access_w(6) +CX_EXPORT size_t cx_tree_add_iter(struct cx_iterator_base_s *iter, size_t num, + cx_tree_search_func sfunc, cx_tree_node_create_func cfunc, + void *cdata, void **failed, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Adds multiple elements efficiently to a tree. @@ -609,24 +546,12 @@ * @return the number of array elements successfully processed * @see cx_tree_add() */ -cx_attr_nonnull_arg(1, 4, 5, 7, 8) -cx_attr_access_w(7) -cx_attr_export -size_t cx_tree_add_array( - const void *src, - size_t num, - size_t elem_size, - cx_tree_search_func sfunc, - cx_tree_node_create_func cfunc, - void *cdata, - void **failed, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +cx_attr_nonnull_arg(1, 4, 5, 7, 8) cx_attr_access_w(7) +CX_EXPORT size_t cx_tree_add_array(const void *src, size_t num, size_t elem_size, + cx_tree_search_func sfunc, cx_tree_node_create_func cfunc, + void *cdata, void **failed, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Adds data to a tree. @@ -637,17 +562,17 @@ * When a location is found, the @p cfunc will be invoked with @p cdata. * * The node returned by @p cfunc will be linked into the tree. - * When @p sfunc returned a positive integer, the new node will be linked as a + * When @p sfunc returns a positive integer, the new node will be linked as a * child. The other children (now siblings of the new node) are then checked * with @p sfunc, whether they could be children of the new node and re-linked * accordingly. * - * When @p sfunc returned zero and the found node has a parent, the new - * node will be added as sibling - otherwise, the new node will be added + * When @p sfunc returns zero and the found node has a parent, the new + * node will be added as a sibling - otherwise, the new node will be added * as a child. * - * When @p sfunc returned a negative value, the new node will not be added to - * the tree and this function returns a non-zero value. + * When @p sfunc returns a negative value, the new node will not be added to + * the tree, and this function returns a non-zero value. * The caller should check if @p cnode contains a node pointer and deal with the * node that could not be added. * @@ -673,22 +598,12 @@ * @return zero when a new node was created and added to the tree, * non-zero otherwise */ -cx_attr_nonnull_arg(1, 2, 3, 5, 6) -cx_attr_access_w(5) -cx_attr_export -int cx_tree_add( - const void *src, - cx_tree_search_func sfunc, - cx_tree_node_create_func cfunc, - void *cdata, - void **cnode, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +cx_attr_nonnull_arg(1, 2, 3, 5, 6) cx_attr_access_w(5) +CX_EXPORT int cx_tree_add(const void *src, + cx_tree_search_func sfunc, cx_tree_node_create_func cfunc, + void *cdata, void **cnode, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** @@ -747,9 +662,9 @@ * A function to create new nodes. * * Invocations to this function will receive a pointer to this tree - * structure as second argument. + * structure as the second argument. * - * Nodes MAY use #cx_tree_node_base_s as base layout, but do not need to. + * Nodes MAY use #cx_tree_node_base_s as the base layout, but do not need to. */ cx_tree_node_create_func node_create; @@ -814,7 +729,7 @@ * Macro to roll out the #cx_tree_node_base_s structure with a custom * node type. * - * Must be used as first member in your custom tree struct. + * Must be used as the first member in your custom tree struct. * * @param type the data type for the nodes */ @@ -850,32 +765,20 @@ * Implementations SHALL NOT simply invoke @p insert_many as this comes * with too much overhead. */ - int (*insert_element)( - struct cx_tree_s *tree, - const void *data - ); + int (*insert_element)(struct cx_tree_s *tree, const void *data); /** * Member function for inserting multiple elements. * - * Implementations SHALL avoid to perform a full search in the tree for + * Implementations SHALL avoid performing a full search in the tree for * every element even though the source data MAY be unsorted. */ - size_t (*insert_many)( - struct cx_tree_s *tree, - struct cx_iterator_base_s *iter, - size_t n - ); + size_t (*insert_many)(struct cx_tree_s *tree, struct cx_iterator_base_s *iter, size_t n); /** * Member function for finding a node. */ - void *(*find)( - struct cx_tree_s *tree, - const void *subtree, - const void *data, - size_t depth - ); + void *(*find)(struct cx_tree_s *tree, const void *subtree, const void *data, size_t depth); }; /** @@ -885,7 +788,7 @@ /** - * Destroys a node and it's subtree. + * Destroys a node and its subtree. * * It is guaranteed that the simple destructor is invoked before * the advanced destructor, starting with the leaf nodes of the subtree. @@ -906,8 +809,7 @@ * @see cxTreeFree() */ cx_attr_nonnull -cx_attr_export -void cxTreeDestroySubtree(CxTree *tree, void *node); +CX_EXPORT void cxTreeDestroySubtree(CxTree *tree, void *node); /** @@ -921,7 +823,7 @@ * * @attention Be careful when calling this function when no destructor function * is registered that actually frees the memory of nodes. In that case you will - * need a reference to the (former) root node of the tree somewhere or + * need a reference to the (former) root node of the tree somewhere, or * otherwise you will be leaking memory. * * @param tree the tree @@ -945,8 +847,7 @@ * * @param tree the tree to free */ -cx_attr_export -void cxTreeFree(CxTree *tree); +CX_EXPORT void cxTreeFree(CxTree *tree); /** * Creates a new tree structure based on the specified layout. @@ -972,29 +873,18 @@ * @see cxTreeCreateSimple() * @see cxTreeCreateWrapped() */ -cx_attr_nonnull_arg(2, 3, 4) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxTreeFree, 1) -cx_attr_export -CxTree *cxTreeCreate( - const CxAllocator *allocator, - cx_tree_node_create_func create_func, - cx_tree_search_func search_func, - cx_tree_search_data_func search_data_func, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +cx_attr_nonnull_arg(2, 3, 4) cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +CX_EXPORT CxTree *cxTreeCreate(const CxAllocator *allocator, cx_tree_node_create_func create_func, + cx_tree_search_func search_func, cx_tree_search_data_func search_data_func, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Creates a new tree structure based on a default layout. * - * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as first + * Nodes created by @p create_func MUST contain #cx_tree_node_base_s as the first * member (or at least respect the default offsets specified in the tree - * struct) and they MUST be allocated with the specified allocator. + * struct), and they MUST be allocated with the specified allocator. * * @note This function will also register an advanced destructor which * will free the nodes with the allocator's free() method. @@ -1006,10 +896,8 @@ * @return (@c CxTree*) the new tree * @see cxTreeCreate() */ -#define cxTreeCreateSimple(\ - allocator, create_func, search_func, search_data_func \ -) cxTreeCreate(allocator, create_func, search_func, search_data_func, \ -cx_tree_node_base_layout) +#define cxTreeCreateSimple(allocator, create_func, search_func, search_data_func) \ + cxTreeCreate(allocator, create_func, search_func, search_data_func, cx_tree_node_base_layout) /** * Creates a new tree structure based on an existing tree. @@ -1019,7 +907,7 @@ * @attention This function will create an incompletely defined tree structure * where neither the create function, the search function, nor a destructor * will be set. If you wish to use any of this functionality for the wrapped - * tree, you need to specify those functions afterwards. + * tree, you need to specify those functions afterward. * * @param allocator the allocator that was used for nodes of the wrapped tree * (if @c NULL, the cxDefaultAllocator is assumed) @@ -1033,20 +921,10 @@ * @return the new tree * @see cxTreeCreate() */ -cx_attr_nonnull_arg(2) -cx_attr_nodiscard -cx_attr_malloc -cx_attr_dealloc(cxTreeFree, 1) -cx_attr_export -CxTree *cxTreeCreateWrapped( - const CxAllocator *allocator, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -); +cx_attr_nonnull_arg(2) cx_attr_nodiscard cx_attr_malloc cx_attr_dealloc(cxTreeFree, 1) +CX_EXPORT CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next); /** * Inserts data into the tree. @@ -1061,12 +939,7 @@ * @retval non-zero failure */ cx_attr_nonnull -static inline int cxTreeInsert( - CxTree *tree, - const void *data -) { - return tree->cl->insert_element(tree, data); -} +CX_EXPORT int cxTreeInsert(CxTree *tree, const void *data); /** * Inserts elements provided by an iterator efficiently into the tree. @@ -1081,13 +954,7 @@ * @return the number of elements that could be successfully inserted */ cx_attr_nonnull -static inline size_t cxTreeInsertIter( - CxTree *tree, - CxIteratorBase *iter, - size_t n -) { - return tree->cl->insert_many(tree, iter, n); -} +CX_EXPORT size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n); /** * Inserts an array of data efficiently into the tree. @@ -1103,17 +970,7 @@ * @return the number of elements that could be successfully inserted */ cx_attr_nonnull -static inline size_t cxTreeInsertArray( - CxTree *tree, - const void *data, - size_t elem_size, - size_t n -) { - if (n == 0) return 0; - if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; - CxIterator iter = cxIterator(data, elem_size, n); - return cxTreeInsertIter(tree, cxIteratorRef(iter), n); -} +CX_EXPORT size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n); /** * Searches the data in the specified tree. @@ -1126,14 +983,8 @@ * @param data the data to search for * @return the first matching node, or @c NULL when the data cannot be found */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxTreeFind( - CxTree *tree, - const void *data -) { - return tree->cl->find(tree, tree->root, data, 0); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cxTreeFind(CxTree *tree, const void *data); /** * Searches the data in the specified subtree. @@ -1154,16 +1005,8 @@ * @param max_depth the maximum search depth * @return the first matching node, or @c NULL when the data cannot be found */ -cx_attr_nonnull -cx_attr_nodiscard -static inline void *cxTreeFindInSubtree( - CxTree *tree, - const void *data, - void *subtree_root, - size_t max_depth -) { - return tree->cl->find(tree, subtree_root, data, max_depth); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth); /** * Determines the size of the specified subtree. @@ -1172,10 +1015,8 @@ * @param subtree_root the root node of the subtree * @return the number of nodes in the specified subtree */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root); /** * Determines the depth of the specified subtree. @@ -1184,10 +1025,8 @@ * @param subtree_root the root node of the subtree * @return the tree depth including the @p subtree_root */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSubtreeDepth(CxTree *tree, void *subtree_root); /** * Determines the size of the entire tree. @@ -1195,11 +1034,8 @@ * @param tree the tree * @return the tree size, counting the root as one */ -cx_attr_nonnull -cx_attr_nodiscard -static inline size_t cxTreeSize(CxTree *tree) { - return tree->size; -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeSize(CxTree *tree); /** * Determines the depth of the entire tree. @@ -1207,10 +1043,8 @@ * @param tree the tree * @return the tree depth, counting the root as one */ -cx_attr_nonnull -cx_attr_nodiscard -cx_attr_export -size_t cxTreeDepth(CxTree *tree); +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT size_t cxTreeDepth(CxTree *tree); /** * Creates a depth-first iterator for the specified tree starting in @p node. @@ -1224,18 +1058,8 @@ * @return a tree iterator (depth-first) * @see cxTreeVisit() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeIterator cxTreeIterateSubtree( - CxTree *tree, - void *node, - bool visit_on_exit -) { - return cx_tree_iterator( - node, visit_on_exit, - tree->loc_children, tree->loc_next - ); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit); /** * Creates a breadth-first iterator for the specified tree starting in @p node. @@ -1247,13 +1071,8 @@ * @return a tree visitor (a.k.a. breadth-first iterator) * @see cxTreeIterate() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { - return cx_tree_visitor( - node, tree->loc_children, tree->loc_next - ); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node); /** * Creates a depth-first iterator for the specified tree. @@ -1264,14 +1083,8 @@ * @return a tree iterator (depth-first) * @see cxTreeVisit() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeIterator cxTreeIterate( - CxTree *tree, - bool visit_on_exit -) { - return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); -} +cx_attr_nonnull cx_attr_nodiscard +CX_EXPORT CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit); /** * Creates a breadth-first iterator for the specified tree. @@ -1280,16 +1093,13 @@ * @return a tree visitor (a.k.a. breadth-first iterator) * @see cxTreeIterate() */ -cx_attr_nonnull -cx_attr_nodiscard -static inline CxTreeVisitor cxTreeVisit(CxTree *tree) { - return cxTreeVisitSubtree(tree, tree->root); -} +cx_attr_nonnull cx_attr_nodiscard +CxTreeVisitor cxTreeVisit(CxTree *tree); /** * Sets the (new) parent of the specified child. * - * If the @p child is not already member of the tree, this function behaves + * If the @p child is not already a member of the tree, this function behaves * as #cxTreeAddChildNode(). * * @param tree the tree @@ -1298,21 +1108,16 @@ * @see cxTreeAddChildNode() */ cx_attr_nonnull -cx_attr_export -void cxTreeSetParent( - CxTree *tree, - void *parent, - void *child -); +CX_EXPORT void cxTreeSetParent(CxTree *tree, void *parent, void *child); /** * Adds a new node to the tree. * - * If the @p child is already member of the tree, the behavior is undefined. + * If the @p child is already a member of the tree, the behavior is undefined. * Use #cxTreeSetParent() if you want to move a subtree to another location. * * @attention The node may be externally created, but MUST obey the same rules - * as if it was created by the tree itself with #cxTreeAddChild() (e.g. use + * as if it was created by the tree itself with #cxTreeAddChild() (e.g., use * the same allocator). * * @param tree the tree @@ -1321,12 +1126,7 @@ * @see cxTreeSetParent() */ cx_attr_nonnull -cx_attr_export -void cxTreeAddChildNode( - CxTree *tree, - void *parent, - void *child -); +CX_EXPORT void cxTreeAddChildNode(CxTree *tree, void *parent, void *child); /** * Creates a new node and adds it to the tree. @@ -1336,7 +1136,7 @@ * leaving this task to the tree by using #cxTreeInsert(). * * Be aware that adding nodes at arbitrary locations in the tree might cause - * wrong or undesired results when subsequently invoking #cxTreeInsert() and + * wrong or undesired results when subsequently invoking #cxTreeInsert(), and * the invariant imposed by the search function does not hold any longer. * * @param tree the tree @@ -1346,12 +1146,7 @@ * @see cxTreeInsert() */ cx_attr_nonnull -cx_attr_export -int cxTreeAddChild( - CxTree *tree, - void *parent, - const void *data -); +CX_EXPORT int cxTreeAddChild(CxTree *tree, void *parent, const void *data); /** * A function that is invoked when a node needs to be re-linked to a new parent. @@ -1365,7 +1160,6 @@ * @param old_parent the old parent of the node * @param new_parent the new parent of the node */ -cx_attr_nonnull typedef void (*cx_tree_relink_func)( void *node, const void *old_parent, @@ -1387,15 +1181,10 @@ * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) -cx_attr_export -int cxTreeRemoveNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); +CX_EXPORT int cxTreeRemoveNode(CxTree *tree, void *node, cx_tree_relink_func relink_func); /** - * Removes a node and it's subtree from the tree. + * Removes a node and its subtree from the tree. * * If the node is not part of the tree, the behavior is undefined. * @@ -1406,8 +1195,7 @@ * @param node the node to remove */ cx_attr_nonnull -cx_attr_export -void cxTreeRemoveSubtree(CxTree *tree, void *node); +CX_EXPORT void cxTreeRemoveSubtree(CxTree *tree, void *node); /** * Destroys a node and re-links its children to its former parent. @@ -1428,12 +1216,7 @@ * @return zero on success, non-zero if @p node is the root node of the tree */ cx_attr_nonnull_arg(1, 2) -cx_attr_export -int cxTreeDestroyNode( - CxTree *tree, - void *node, - cx_tree_relink_func relink_func -); +CX_EXPORT int cxTreeDestroyNode(CxTree *tree, void *node, cx_tree_relink_func relink_func); #ifdef __cplusplus } // extern "C"
--- a/ucx/hash_key.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/hash_key.c Sat Nov 08 23:06:11 2025 +0100 @@ -27,6 +27,7 @@ */ #include "cx/hash_key.h" +#include "cx/compare.h" #include <string.h> void cx_hash_murmur(CxHashKey *key) { @@ -81,6 +82,21 @@ key->hash = h; } + +uint32_t cx_hash_u32(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = (x >> 16) ^ x; + return x; +} + +uint64_t cx_hash_u64(uint64_t x) { + x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9); + x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb); + x = x ^ (x >> 31); + return x; +} + CxHashKey cx_hash_key_str(const char *str) { CxHashKey key; key.data = str; @@ -89,6 +105,22 @@ return key; } +CxHashKey cx_hash_key_ustr(unsigned const char *str) { + CxHashKey key; + key.data = str; + key.len = str == NULL ? 0 : strlen((const char*)str); + cx_hash_murmur(&key); + return key; +} + +CxHashKey cx_hash_key_cxstr(cxstring str) { + return cx_hash_key(str.ptr, str.length); +} + +CxHashKey cx_hash_key_mutstr(cxmutstr str) { + return cx_hash_key(str.ptr, str.length); +} + CxHashKey cx_hash_key_bytes( const unsigned char *bytes, size_t len @@ -110,3 +142,31 @@ cx_hash_murmur(&key); return key; } + +CxHashKey cx_hash_key_u32(uint32_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u32(x); + return key; +} + +CxHashKey cx_hash_key_u64(uint64_t x) { + CxHashKey key; + key.data = NULL; + key.len = 0; + key.hash = cx_hash_u64(x); + return key; +} + +int cx_hash_key_cmp(const void *l, const void *r) { + const CxHashKey *left = l; + const CxHashKey *right = r; + int d; + d = cx_vcmp_uint64(left->hash, right->hash); + if (d != 0) return d; + d = cx_vcmp_size(left->len, right->len); + if (d != 0) return d; + if (left->len == 0) return 0; + return memcmp(left->data, right->data, left->len); +}
--- a/ucx/hash_map.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/hash_map.c Sat Nov 08 23:06:11 2025 +0100 @@ -86,7 +86,7 @@ struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; const CxAllocator *allocator = map->collection.allocator; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; @@ -101,8 +101,7 @@ elm = elm->next; } - if (elm != NULL && elm->key.hash == hash && elm->key.len == key.len && - memcmp(elm->key.data, key.data, key.len) == 0) { + if (elm != NULL && cx_hash_key_cmp(&elm->key, &key) == 0) { // overwrite existing element, but call destructors first cx_invoke_destructor(map, elm->data); if (value == NULL) { @@ -204,7 +203,7 @@ ) { struct cx_hash_map_s *hash_map = (struct cx_hash_map_s *) map; - unsigned hash = key.hash; + uint64_t hash = key.hash; if (hash == 0) { cx_hash_murmur(&key); hash = key.hash; @@ -214,27 +213,25 @@ 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) { - if (remove) { - if (targetbuf == NULL) { - cx_invoke_destructor(map, elm->data); - } else { - memcpy(targetbuf, elm->data, map->collection.elem_size); - } - cx_hash_map_unlink(hash_map, slot, prev, elm); + if (cx_hash_key_cmp(&elm->key, &key) == 0) { + if (remove) { + if (targetbuf == NULL) { + cx_invoke_destructor(map, elm->data); } else { - assert(targetbuf != NULL); - void *data = NULL; - if (map->collection.store_pointer) { - data = *(void **) elm->data; - } else { - data = elm->data; - } - memcpy(targetbuf, &data, sizeof(void *)); + memcpy(targetbuf, elm->data, map->collection.elem_size); } - return 0; + cx_hash_map_unlink(hash_map, slot, prev, elm); + } else { + assert(targetbuf != NULL); + void *data = NULL; + if (map->collection.store_pointer) { + data = *(void **) elm->data; + } else { + data = elm->data; + } + memcpy(targetbuf, &data, sizeof(void *)); } + return 0; } prev = elm; elm = prev->next; @@ -269,19 +266,12 @@ static void *cx_hash_map_iter_current_key(const void *it) { const CxMapIterator *iter = it; - struct cx_hash_map_element_s *elm = iter->elem; - return &elm->key; + return (void*) iter->entry.key; } static void *cx_hash_map_iter_current_value(const void *it) { const CxMapIterator *iter = it; - const CxMap *map = iter->map.c; - struct cx_hash_map_element_s *elm = iter->elem; - if (map->collection.store_pointer) { - return *(void **) elm->data; - } else { - return elm->data; - } + return iter->entry.value; } static bool cx_hash_map_iter_valid(const void *it) { @@ -291,7 +281,7 @@ static void cx_hash_map_iter_next(void *it) { CxMapIterator *iter = it; - CxMap *map = iter->map.m; + CxMap *map = iter->map; struct cx_hash_map_s *hmap = (struct cx_hash_map_s *) map; struct cx_hash_map_element_s *elm = iter->elem; @@ -318,6 +308,7 @@ // unlink cx_hash_map_unlink(hmap, iter->slot, prev, elm); + iter->elem_count--; // advance elm = next; @@ -338,7 +329,7 @@ // must not modify the iterator (the parameter is const) if (elm != NULL) { iter->entry.key = &elm->key; - if (iter->map.c->collection.store_pointer) { + if (map->collection.store_pointer) { iter->entry.value = *(void **) elm->data; } else { iter->entry.value = elm->data; @@ -352,7 +343,7 @@ ) { CxMapIterator iter; - iter.map.c = map; + iter.map = (CxMap*) map; iter.elem_count = map->collection.size; switch (type) { @@ -375,7 +366,7 @@ iter.base.valid = cx_hash_map_iter_valid; iter.base.next = cx_hash_map_iter_next; iter.base.remove = false; - iter.base.mutating = false; + iter.base.allow_remove = true; iter.slot = 0; iter.index = 0;
--- a/ucx/iterator.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/iterator.c Sat Nov 08 23:06:11 2025 +0100 @@ -53,7 +53,7 @@ // only move the last element when we are not currently aiming // at the last element already if (iter->index < iter->elem_count) { - void *last = ((char *) iter->src_handle.m) + void *last = ((char *) iter->src_handle) + iter->elem_count * iter->elem_size; memcpy(iter->elem_handle, last, iter->elem_size); } @@ -84,8 +84,8 @@ } } -CxIterator cxMutIterator( - void *array, +CxIterator cxIterator( + const void *array, size_t elem_size, size_t elem_count, bool remove_keeps_order @@ -93,44 +93,25 @@ CxIterator iter; iter.index = 0; - iter.src_handle.m = array; - iter.elem_handle = array; + iter.src_handle = (void*) array; + iter.elem_handle = (void*) array; iter.elem_size = elem_size; iter.elem_count = array == NULL ? 0 : elem_count; iter.base.valid = cx_iter_valid; iter.base.current = cx_iter_current; iter.base.next = remove_keeps_order ? cx_iter_next_slow : cx_iter_next_fast; iter.base.remove = false; - iter.base.mutating = true; - - return iter; -} + iter.base.allow_remove = true; -CxIterator cxIterator( - const void *array, - size_t elem_size, - size_t elem_count -) { - CxIterator iter = cxMutIterator((void*)array, elem_size, elem_count, false); - iter.base.mutating = false; - return iter; -} - -CxIterator cxMutIteratorPtr( - void *array, - size_t elem_count, - bool remove_keeps_order -) { - CxIterator iter = cxMutIterator(array, sizeof(void*), elem_count, remove_keeps_order); - iter.base.current = cx_iter_current_ptr; return iter; } CxIterator cxIteratorPtr( const void *array, - size_t elem_count + size_t elem_count, + bool remove_keeps_order ) { - CxIterator iter = cxMutIteratorPtr((void*) array, elem_count, false); - iter.base.mutating = false; + CxIterator iter = cxIterator(array, sizeof(void*), elem_count, remove_keeps_order); + iter.base.current = cx_iter_current_ptr; return iter; }
--- a/ucx/json.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/json.c Sat Nov 08 23:06:11 2025 +0100 @@ -32,6 +32,7 @@ #include <assert.h> #include <stdio.h> #include <inttypes.h> +#include <ctype.h> /* * RFC 8259 @@ -127,16 +128,6 @@ } } -static bool json_isdigit(char c) { - // TODO: remove once UCX has public API for this - return c >= '0' && c <= '9'; -} - -static bool json_isspace(char c) { - // TODO: remove once UCX has public API for this - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; -} - static int num_isexp(const char *content, size_t length, size_t pos) { if (pos >= length) { return 0; @@ -145,7 +136,7 @@ int ok = 0; for (size_t i = pos; i < length; i++) { char c = content[i]; - if (json_isdigit(c)) { + if (isdigit((unsigned char)c)) { ok = 1; } else if (i == pos) { if (!(c == '+' || c == '-')) { @@ -162,7 +153,7 @@ static CxJsonTokenType token_numbertype(const char *content, size_t length) { if (length == 0) return CX_JSON_TOKEN_ERROR; - if (content[0] != '-' && !json_isdigit(content[0])) { + if (content[0] != '-' && !isdigit((unsigned char)content[0])) { return CX_JSON_TOKEN_ERROR; } @@ -175,7 +166,7 @@ type = CX_JSON_TOKEN_NUMBER; } else if (content[i] == 'e' || content[i] == 'E') { return num_isexp(content, length, i + 1) ? CX_JSON_TOKEN_NUMBER : CX_JSON_TOKEN_ERROR; - } else if (!json_isdigit(content[i])) { + } else if (!isdigit((unsigned char)content[i])) { return CX_JSON_TOKEN_ERROR; // char is not a digit, decimal separator or exponent sep } } @@ -239,7 +230,7 @@ return CX_JSON_TOKEN_STRING; } default: { - if (json_isspace(c)) { + if (isspace((unsigned char)c)) { return CX_JSON_TOKEN_SPACE; } } @@ -639,6 +630,12 @@ } } +void cxJsonReset(CxJson *json) { + const CxAllocator *allocator = json->allocator; + cxJsonDestroy(json); + cxJsonInit(json, allocator); +} + int cxJsonFilln(CxJson *json, const char *buf, size_t size) { if (cxBufferEof(&json->buffer)) { // reinitialize the buffer @@ -1135,10 +1132,39 @@ return ret; } +char *cxJsonAsString(const CxJsonValue *value) { + return value->value.string.ptr; +} + +cxstring cxJsonAsCxString(const CxJsonValue *value) { + return cx_strcast(value->value.string); +} + +cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { + return value->value.string; +} + +double cxJsonAsDouble(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return (double) value->value.integer; + } else { + return value->value.number; + } +} + +int64_t cxJsonAsInteger(const CxJsonValue *value) { + if (value->type == CX_JSON_INTEGER) { + return value->value.integer; + } else { + return (int64_t) value->value.number; + } +} + CxIterator cxJsonArrIter(const CxJsonValue *value) { return cxIteratorPtr( value->value.array.array, - value->value.array.array_size + value->value.array.array_size, + true // arrays need to keep order ); } @@ -1146,11 +1172,12 @@ return cxIterator( value->value.object.values, sizeof(CxJsonObjValue), - value->value.object.values_size + value->value.object.values_size, + true // TODO: objects do not always need to keep order ); } -CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) { +CxJsonValue *cx_json_obj_get(const CxJsonValue *value, cxstring name) { size_t index = json_find_objvalue(value, name); if (index >= value->value.object.values_size) { return &cx_json_value_nothing; @@ -1159,14 +1186,14 @@ } } -CxJsonValue *cx_json_obj_remove_cxstr(CxJsonValue *value, cxstring name) { +CxJsonValue *cx_json_obj_remove(CxJsonValue *value, cxstring name) { size_t index = json_find_objvalue(value, name); if (index >= value->value.object.values_size) { return NULL; } else { CxJsonObjValue kv = value->value.object.values[index]; cx_strfree_a(value->allocator, &kv.name); - // TODO: replace with cx_array_remove() + // TODO: replace with cx_array_remove() / cx_array_remove_fast() value->value.object.values_size--; memmove(value->value.object.values + index, value->value.object.values + index + 1, (value->value.object.values_size - index) * sizeof(CxJsonObjValue)); return kv.value;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ucx/kv_list.c Sat Nov 08 23:06:11 2025 +0100 @@ -0,0 +1,705 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 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/kv_list.h" +#include "cx/hash_map.h" +#include "cx/linked_list.h" + +#include <string.h> +#include <assert.h> + +typedef struct cx_kv_list_s cx_kv_list; + +struct cx_kv_list_map_s { + struct cx_hash_map_s map_base; + /** Back-reference to the list. */ + cx_kv_list *list; +}; + +struct cx_kv_list_s { + struct cx_linked_list_s list; + /** The lookup map - stores pointers to the nodes. */ + struct cx_kv_list_map_s *map; + const cx_list_class *list_methods; + const cx_map_class *map_methods; + cx_destructor_func list_destr; + cx_destructor_func2 list_destr2; + void *list_destr_data; + cx_destructor_func map_destr; + cx_destructor_func2 map_destr2; + void *map_destr_data; +}; + +static void cx_kv_list_destructor_wrapper(void *list_ptr, void *elem) { + const cx_kv_list *kv_list = list_ptr; + // list destructor is already called with proper deref of the elem + if (kv_list->list_destr) { + kv_list->list_destr(elem); + } + if (kv_list->list_destr2) { + kv_list->list_destr2(kv_list->list_destr_data, elem); + } + if (kv_list->map_destr) { + kv_list->map_destr(elem); + } + if (kv_list->map_destr2) { + kv_list->map_destr2(kv_list->map_destr_data, elem); + } +} + +static void cx_kv_list_update_destructors(cx_kv_list *list) { + // we copy the destructors to our custom fields and register + // an own destructor function which invokes all these + if (list->list.base.collection.simple_destructor != NULL) { + list->list_destr = list->list.base.collection.simple_destructor; + list->list.base.collection.simple_destructor = NULL; + } + if (list->list.base.collection.advanced_destructor != cx_kv_list_destructor_wrapper) { + list->list_destr2 = list->list.base.collection.advanced_destructor; + list->list_destr_data = list->list.base.collection.destructor_data; + list->list.base.collection.advanced_destructor = cx_kv_list_destructor_wrapper; + list->list.base.collection.destructor_data = list; + } + if (list->map->map_base.base.collection.simple_destructor != NULL) { + list->map_destr = list->map->map_base.base.collection.simple_destructor; + list->map->map_base.base.collection.simple_destructor = NULL; + } + if (list->map->map_base.base.collection.advanced_destructor != NULL) { + list->map_destr2 = list->map->map_base.base.collection.advanced_destructor; + list->map_destr_data = list->map->map_base.base.collection.destructor_data; + list->map->map_base.base.collection.advanced_destructor = NULL; + list->map->map_base.base.collection.destructor_data = NULL; + } +} + +static CxHashKey *cx_kv_list_loc_key(cx_kv_list *list, void *node_data) { + return (CxHashKey*)((char*)node_data + list->list.base.collection.elem_size); +} + +static void cx_kvl_deallocate(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + kv_list->map_methods->deallocate(&kv_list->map->map_base.base); + // then free the list, now the destructors may be called + kv_list->list_methods->deallocate(list); +} + +static void *cx_kvl_insert_element( + struct cx_list_s *list, + size_t index, + const void *data +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_element(list, index, data); +} + +static size_t cx_kvl_insert_array( + struct cx_list_s *list, + size_t index, + const void *data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_array(list, index, data, n); +} + +static size_t cx_kvl_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_sorted(list, sorted_data, n); +} + +static size_t cx_kvl_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->insert_unique(list, sorted_data, n); +} + +static int cx_kvl_insert_iter( + struct cx_iterator_s *iter, + const void *elem, + int prepend +) { + cx_kv_list *kv_list = iter->src_handle; + return kv_list->list_methods->insert_iter(iter, elem, prepend); +} + +static size_t cx_kvl_remove( + struct cx_list_s *list, + size_t index, + size_t num, + void *targetbuf +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + // we also have to do that when targetbuf is NULL, + // because we do not want wrong destructors to be called when we remove keys from the map + cx_kv_list_update_destructors(kv_list); + // iterate through the elements first and remove their keys from the map + CxIterator iter = kv_list->list_methods->iterator(list, index, false); + for (size_t i = 0; i < num && cxIteratorValid(iter); i++) { + void *node_data = cxIteratorCurrent(iter); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + // when the hash is zero, there is no key assigned to that element + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + cxIteratorNext(iter); + } + return kv_list->list_methods->remove(list, index, num, targetbuf); +} + +static void cx_kvl_clear(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // patch the destructors + cx_kv_list_update_destructors(kv_list); + // clear the list + kv_list->list_methods->clear(list); + // then clear the map + kv_list->map_methods->clear(&kv_list->map->map_base.base); +} + +static int cx_kvl_swap( + struct cx_list_s *list, + size_t i, + size_t j +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + return kv_list->list_methods->swap(list, i, j); +} + +static void *cx_kvl_at( + const struct cx_list_s *list, + size_t index +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + return kv_list->list_methods->at(list, index); +} + +static size_t cx_kvl_find_remove( + struct cx_list_s *list, + const void *elem, + bool remove +) { + cx_kv_list *kv_list = (cx_kv_list*)list; + // we do not use the original list methods, + // because that would need two passes for removal + // (the first to find the index, the second to get a pointer) + if (list->collection.size == 0) return 0; + + size_t index; + cx_linked_list *ll = &kv_list->list; + char *node = cx_linked_list_find( + ll->begin, + ll->loc_next, ll->loc_data, + list->collection.cmpfunc, elem, + &index + ); + if (node == NULL) { + return list->collection.size; + } + if (remove) { + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + ll->loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + list->collection.size--; + cxFree(list->collection.allocator, node); + } + return index; +} + +static void cx_kvl_sort(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->sort(list); +} + +static void cx_kvl_reverse(struct cx_list_s *list) { + cx_kv_list *kv_list = (cx_kv_list*)list; + kv_list->list_methods->reverse(list); +} + +static void cx_kvl_list_iter_next(void *it) { + struct cx_iterator_s *iter = it; + if (iter->base.remove) { + // remove the assigned key from the map before calling the actual function + cx_kv_list *kv_list = iter->src_handle; + cx_kv_list_update_destructors(kv_list); + char *node = iter->elem_handle; + CxHashKey *key = cx_kv_list_loc_key(kv_list, node + kv_list->list.loc_data); + if (key->hash != 0) { + kv_list->map_methods->remove(&kv_list->map->map_base.base, *key, NULL); + } + } + // note that we do not clear the remove flag, because the next_impl will do that + iter->base.next_impl(it); +} + +static struct cx_iterator_s cx_kvl_iterator( + const struct cx_list_s *list, + size_t index, + bool backward +) { + const cx_kv_list *kv_list = (const cx_kv_list*)list; + struct cx_iterator_s iter = kv_list->list_methods->iterator(list, index, backward); + iter.base.next_impl = iter.base.next; + iter.base.next = cx_kvl_list_iter_next; + return iter; +} + +static void cx_kvl_map_deallocate(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + kv_list->map_methods->deallocate(map); + kv_list->list_methods->deallocate(&kv_list->list.base); +} + +static void cx_kvl_map_clear(struct cx_map_s *map) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + cx_kv_list_update_destructors(kv_list); + kv_list->list_methods->clear(&kv_list->list.base); + kv_list->map_methods->clear(map); +} + +static void *cx_kvl_map_put(CxMap *map, CxHashKey key, void *value) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // reserve memory in the map first + void **map_data = kv_list->map_methods->put(map, key, NULL); + if (map_data == NULL) return NULL; // LCOV_EXCL_LINE + + // insert the data into the list (which most likely destroys the sorted property) + kv_list->list.base.collection.sorted = false; + void *node_data = kv_list->list_methods->insert_element( + &kv_list->list.base, kv_list->list.base.collection.size, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return NULL; + } // LCOV_EXCL_STOP + + // write the node pointer to the map entry + *map_data = node_data; + + // copy the key to the node data + CxHashKey *key_ptr = cx_kv_list_loc_key(kv_list, node_data); + *key_ptr = key; + + // we must return node_data here and not map_data, + // because the node_data is the actual element of this collection + return node_data; +} + +void *cx_kvl_map_get(const CxMap *map, CxHashKey key) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + void *node_data = kv_list->map_methods->get(map, key); + if (node_data == NULL) return NULL; // LCOV_EXCL_LINE + // return the node data + return kv_list->list.base.collection.store_pointer ? *(void**)node_data : node_data; +} + +int cx_kvl_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + + void *node_data; + if (kv_list->map_methods->remove(map, key, &node_data)) { + return 1; + } + // we cannot just call a list method (because we don't have the index) + // and tbh. we also don't want to (because it's not performant when we + // can have the node ptr directly instead) + // therefore, we re-implement the logic ourselves + + // check if the outside caller want's us to return or to destroy the element + if (targetbuf == NULL) { + // patch the destructors and invoke them through the wrapper + cx_kv_list_update_destructors(kv_list); + cx_invoke_advanced_destructor(&kv_list->list.base, node_data); + } else { + // copy the element to the target buffer + memcpy(targetbuf, node_data, kv_list->list.base.collection.elem_size); + } + + // calculate the address of the node + void *node_ptr = (char*)node_data - kv_list->list.loc_data; + + // unlink the node + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + node_ptr + ); + + // decrement the list's size + kv_list->list.base.collection.size--; + + // deallocate the node + cxFree(kv_list->list.base.collection.allocator, node_ptr); + + return 0; +} + +static void *cx_kvl_iter_current_entry(const void *it) { + const CxMapIterator *iter = it; + return (void*)&iter->entry; +} + +static void *cx_kvl_iter_current_key(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return (void*)entry->key; +} + +static void *cx_kvl_iter_current_value(const void *it) { + const CxMapEntry *entry = cx_kvl_iter_current_entry(it); + return entry->value; +} + +static void cx_kvl_iter_next(void *it) { + CxMapIterator *iter = it; + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)iter->map)->list; + + // find the next list entry that has a key assigned + CxHashKey *key = NULL; + char *next = iter->elem; + while (true) { + next = *(char**)(next + kv_list->list.loc_next); + if (next == NULL) break; + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + } + + // remove previous element if requested + if (iter->base.remove) { + iter->base.remove = false; + cx_kv_list_update_destructors(kv_list); + char *elem = iter->elem; + char *elem_data = elem + kv_list->list.loc_data; + CxHashKey *elem_key = cx_kv_list_loc_key(kv_list, elem_data); + // key is guaranteed to exist because iterator only iterates over elems with a key + kv_list->map_methods->remove(&kv_list->map->map_base.base, *elem_key, NULL); + cx_invoke_advanced_destructor(&kv_list->list.base, elem_data); + cx_linked_list_remove( + &kv_list->list.begin, + &kv_list->list.end, + kv_list->list.loc_prev, + kv_list->list.loc_next, + elem + ); + cxFree(kv_list->list.base.collection.allocator, elem); + kv_list->list.base.collection.size--; + iter->index--; + iter->elem_count--; + } + + // advance to the next element, if any + if (next == NULL) { + iter->index = kv_list->list.base.collection.size; + iter->elem = NULL; + iter->entry = (CxMapEntry){NULL, NULL}; + return; + } + iter->index++; + iter->elem = next; + iter->entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter->entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter->entry.value = (void*)(next + kv_list->list.loc_data); + } +} + +static bool cx_kvl_iter_valid(const void *it) { + const CxMapIterator *iter = it; + return iter->elem != NULL; +} + +CxMapIterator cx_kvl_map_iterator(const CxMap *map, enum cx_map_iterator_type type) { + CxMapIterator iter = {0}; + + iter.type = type; + iter.map = (CxMap*)map; + // although we iterate over the list, we only report that many elements that have a key in the map + iter.elem_count = map->collection.size; + + switch (type) { + case CX_MAP_ITERATOR_PAIRS: + iter.elem_size = sizeof(CxMapEntry); + iter.base.current = cx_kvl_iter_current_entry; + break; + case CX_MAP_ITERATOR_KEYS: + iter.elem_size = sizeof(CxHashKey); + iter.base.current = cx_kvl_iter_current_key; + break; + case CX_MAP_ITERATOR_VALUES: + iter.elem_size = map->collection.elem_size; + iter.base.current = cx_kvl_iter_current_value; + break; + default: + assert(false); // LCOV_EXCL_LINE + } + + iter.base.allow_remove = true; + iter.base.next = cx_kvl_iter_next; + iter.base.valid = cx_kvl_iter_valid; + + // find the first list entry that has a key assigned + cx_kv_list *kv_list = ((struct cx_kv_list_map_s*)map)->list; + CxHashKey *key = NULL; + char *next = kv_list->list.begin; + while (next != NULL) { + key = cx_kv_list_loc_key(kv_list, next + kv_list->list.loc_data); + if (key->hash != 0) break; + next = *(char**)(next + kv_list->list.loc_next); + } + if (next == NULL) { + iter.elem = NULL; + iter.entry = (CxMapEntry){NULL, NULL}; + } else { + iter.elem = next; + iter.entry.key = key; + if (kv_list->list.base.collection.store_pointer) { + iter.entry.value = *(void**)(next + kv_list->list.loc_data); + } else { + iter.entry.value = (void*)(next + kv_list->list.loc_data); + } + } + + return iter; +} + +static cx_list_class cx_kv_list_class = { + cx_kvl_deallocate, + cx_kvl_insert_element, + cx_kvl_insert_array, + cx_kvl_insert_sorted, + cx_kvl_insert_unique, + cx_kvl_insert_iter, + cx_kvl_remove, + cx_kvl_clear, + cx_kvl_swap, + cx_kvl_at, + cx_kvl_find_remove, + cx_kvl_sort, + NULL, + cx_kvl_reverse, + cx_kvl_iterator, +}; + +static cx_map_class cx_kv_map_class = { + cx_kvl_map_deallocate, + cx_kvl_map_clear, + cx_kvl_map_put, + cx_kvl_map_get, + cx_kvl_map_remove, + cx_kvl_map_iterator, +}; + +CxList *cxKvListCreate( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + if (allocator == NULL) { + allocator = cxDefaultAllocator; + } + + // create a normal linked list and a normal hash map, first + CxList *list = cxLinkedListCreate(allocator, comparator, elem_size); + if (list == NULL) return NULL; // LCOV_EXCL_LINE + cx_linked_list *ll = (cx_linked_list*)list; + ll->extra_data_len = sizeof(CxHashKey); + CxMap *map = cxHashMapCreate(allocator, CX_STORE_POINTERS, 0); + if (map == NULL) { // LCOV_EXCL_START + cxListFree(list); + return NULL; + } // LCOV_EXCL_STOP + + // patch the kv-list class with the compare function of the linked list + // this allows cxListCompare() to optimize comparisons between linked lists and kv-list + cx_kv_list_class.compare = list->cl->compare; + + // reallocate the map to add memory for the list back-reference + struct cx_kv_list_map_s *kv_map = cxRealloc(allocator, map, sizeof(struct cx_kv_list_map_s)); + + // reallocate the list to add memory for storing the metadata + cx_kv_list *kv_list = cxRealloc(allocator, list, sizeof(struct cx_kv_list_s)); + + // if any of the reallocations failed, we bail out + if (kv_map != NULL && kv_list != NULL) { + map = (CxMap*) kv_map; + list = (CxList*) kv_list; + } else { // LCOV_EXCL_START + cxListFree(list); + cxMapFree(map); + return NULL; + } // LCOV_EXCL_STOP + + // zero the custom destructor information + memset((char*)kv_list + offsetof(cx_kv_list, list_destr), 0, sizeof(void*)*6); + + // combine the list and the map aspect + kv_list->map = kv_map; + kv_map->list = kv_list; + + // remember the base methods and override them + kv_list->map_methods = map->cl; + map->cl = &cx_kv_map_class; + if (list->climpl == NULL) { + kv_list->list_methods = list->cl; + list->cl = &cx_kv_list_class; + } else { + kv_list->list_methods = list->climpl; + list->climpl = &cx_kv_list_class; + } + + return list; +} + +CxMap *cxKvListCreateAsMap( + const CxAllocator *allocator, + cx_compare_func comparator, + size_t elem_size +) { + CxList *list = cxKvListCreate(allocator, comparator, elem_size); + return list == NULL ? NULL : cxKvListAsMap(list); +} + +CxList *cxKvListAsList(CxMap *map) { + return &((struct cx_kv_list_map_s*)map)->list->list.base; +} + +CxMap *cxKvListAsMap(CxList *list) { + return &((cx_kv_list*)list)->map->map_base.base; +} + +int cx_kv_list_set_key(CxList *list, size_t index, CxHashKey key) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + // if the hash has not yet been computed, do it now + if (key.hash == 0) { + cx_hash_murmur(&key); + } + + // check if the key is already assigned + void *existing = kv_list->map_methods->get(&kv_list->map->map_base.base, key); + if (existing == node_data) { + return 0; // nothing to do + } + if (existing != NULL) { + // the key is already assigned to another node, we disallow re-assignment + return 1; + } + + // add the key to the map; + if (NULL == kv_list->map_methods->put(&kv_list->map->map_base.base, key, node_data)) { + return 1; // LCOV_EXCL_LINE + } + + // write the key to the list's node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +} + +int cxKvListRemoveKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return 1; + } + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + if (loc_key->hash == 0) { + return 0; + } + kv_list->map_methods->remove(&kv_list->map->map_base.base, *loc_key, NULL); + // also zero the memory in the list node, + // but don't free the key data (that was done by the map remove) + memset(loc_key, 0, sizeof(CxHashKey)); + return 0; +} + +const CxHashKey *cxKvListGetKey(CxList *list, size_t index) { + cx_kv_list *kv_list = (cx_kv_list*)list; + void *node_data = kv_list->list_methods->at(list, index); + if (node_data == NULL) { + return NULL; + } + CxHashKey *key = cx_kv_list_loc_key(kv_list, node_data); + if (key->hash == 0) { + return NULL; + } + return key; +} + +int cx_kv_list_insert(CxList *list, size_t index, CxHashKey key, void *value) { + // assume we are losing the sorted property + list->collection.sorted = false; + + cx_kv_list *kv_list = (cx_kv_list*)list; + + // reserve memory in the map + void **map_data = kv_list->map_methods->put(&kv_list->map->map_base.base, key, NULL); + if (map_data == NULL) return 1; // LCOV_EXCL_LINE + + // insert the node + void *node_data = kv_list->list_methods->insert_element(&kv_list->list.base, index, + kv_list->list.base.collection.store_pointer ? &value : value); + if (node_data == NULL) { // LCOV_EXCL_START + // non-destructively remove the key again + kv_list->map_methods->remove(&kv_list->map->map_base.base, key, &map_data); + return 1; + } // LCOV_EXCL_STOP + *map_data = node_data; + + // write the key to the node + CxHashKey *loc_key = cx_kv_list_loc_key(kv_list, node_data); + *loc_key = key; + + return 0; +}
--- a/ucx/linked_list.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/linked_list.c Sat Nov 08 23:06:11 2025 +0100 @@ -244,6 +244,147 @@ begin, end, loc_prev, loc_next, new_node, cmp_func); } +static void *cx_linked_list_insert_sorted_chain_impl( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func, + bool allow_duplicates +) { + assert(begin != NULL); + assert(loc_next >= 0); + assert(insert_begin != NULL); + + // strategy: build completely new chains from scratch + void *source_original = *begin; + void *source_argument = insert_begin; + void *new_begin = NULL; + void *new_end = NULL; + void *dup_begin = NULL; + void *dup_end = NULL; + + // determine the new start + { + int d = source_original == NULL ? 1 : cmp_func(source_original, source_argument); + if (d <= 0) { + // the new chain starts with the original chain + new_begin = new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, start a duplicate chain with the argument + dup_begin = dup_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } else { + // input is smaller, or there is no original chain; + // start the new chain with the source argument + new_begin = new_end = source_argument; + source_argument = ll_next(source_argument); + } + } + + // now successively compare the elements and add them to the correct chains + while (source_original != NULL && source_argument != NULL) { + int d = cmp_func(source_original, source_argument); + if (d <= 0) { + // the original is not larger, add it to the chain + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = source_original; + source_original = ll_next(source_original); + if (d == 0) { + if (allow_duplicates) { + // duplicate allowed, append it to the chain + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } else { + // duplicate is not allowed, append it to the duplicate chain + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } + source_argument = ll_next(source_argument); + } + } else { + // the original is larger, append the source argument to the chain + // check if we must discard the source argument as duplicate + if (!allow_duplicates && cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + // no duplicate or duplicates allowed + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } + + if (source_original != NULL) { + // something is left from the original chain, append it + cx_linked_list_link(new_end, source_original, loc_prev, loc_next); + new_end = cx_linked_list_last(source_original, loc_next); + } else if (source_argument != NULL) { + // something is left from the input chain; + // when we allow duplicates, append it + if (allow_duplicates) { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = cx_linked_list_last(source_argument, loc_next); + } else { + // otherwise we must check one-by-one + while (source_argument != NULL) { + if (cmp_func(new_end, source_argument) == 0) { + if (dup_end == NULL) { + dup_begin = dup_end = source_argument; + } else { + cx_linked_list_link(dup_end, source_argument, loc_prev, loc_next); + dup_end = source_argument; + } + } else { + cx_linked_list_link(new_end, source_argument, loc_prev, loc_next); + new_end = source_argument; + } + source_argument = ll_next(source_argument); + } + } + } + + // null the next pointers at the end of the chain + ll_next(new_end) = NULL; + if (dup_end != NULL) { + ll_next(dup_end) = NULL; + } + + // null the optional prev pointers + if (loc_prev >= 0) { + ll_prev(new_begin) = NULL; + if (dup_begin != NULL) { + ll_prev(dup_begin) = NULL; + } + } + + // output + *begin = new_begin; + if (end != NULL) { + *end = new_end; + } + return dup_begin; +} + void cx_linked_list_insert_sorted_chain( void **begin, void **end, @@ -252,72 +393,35 @@ void *insert_begin, cx_compare_func cmp_func ) { - assert(begin != NULL); - assert(loc_next >= 0); - assert(insert_begin != NULL); - - // track currently observed nodes - void *dest_prev = NULL; - void *dest = *begin; - void *src = insert_begin; - - // special case: list is empty - if (dest == NULL) { - *begin = src; - if (end != NULL) { - *end = cx_linked_list_last(src, loc_next); - } - return; - } - - // search the list for insertion points - while (dest != NULL && src != NULL) { - // compare current list node with source node - // if less or equal, skip - if (cmp_func(dest, src) <= 0) { - dest_prev = dest; - dest = ll_next(dest); - continue; - } + cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, true); +} - // determine chain of elements that can be inserted - void *end_of_chain = src; - void *next_in_chain = ll_next(src); - while (next_in_chain != NULL) { - // once we become larger than the list elem, break - if (cmp_func(dest, next_in_chain) <= 0) { - break; - } - // otherwise, we can insert one more - end_of_chain = next_in_chain; - next_in_chain = ll_next(next_in_chain); - } +int cx_linked_list_insert_unique( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *new_node, + cx_compare_func cmp_func +) { + assert(ll_next(new_node) == NULL); + return NULL != cx_linked_list_insert_unique_chain( + begin, end, loc_prev, loc_next, new_node, cmp_func); +} - // insert the elements - if (dest_prev == NULL) { - // new begin - *begin = src; - } else { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); - } - cx_linked_list_link(end_of_chain, dest, loc_prev, loc_next); - - // continue with next - src = next_in_chain; - dest_prev = dest; - dest = ll_next(dest); - } - - // insert remaining items - if (src != NULL) { - cx_linked_list_link(dest_prev, src, loc_prev, loc_next); - } - - // determine new end of list, if requested - if (end != NULL) { - *end = cx_linked_list_last( - dest != NULL ? dest : dest_prev, loc_next); - } +void *cx_linked_list_insert_unique_chain( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *insert_begin, + cx_compare_func cmp_func +) { + return cx_linked_list_insert_sorted_chain_impl( + begin, end, loc_prev, loc_next, + insert_begin, cmp_func, false); } size_t cx_linked_list_remove_chain( @@ -370,6 +474,16 @@ return removed; } +void cx_linked_list_remove( + void **begin, + void **end, + ptrdiff_t loc_prev, + ptrdiff_t loc_next, + void *node +) { + cx_linked_list_remove_chain(begin, end, loc_prev, loc_next, node, 1); +} + size_t cx_linked_list_size( const void *node, ptrdiff_t loc_next @@ -564,65 +678,46 @@ // HIGH LEVEL LINKED LIST IMPLEMENTATION -typedef struct cx_linked_list_node cx_linked_list_node; -// IMPORTANT: the layout must stay exactly like this, because kv_list.c uses that! -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( +static void *cx_ll_node_at( const cx_linked_list *list, size_t index ) { if (index >= list->base.collection.size) { return NULL; } else if (index > list->base.collection.size / 2) { - return cx_linked_list_at(list->end, list->base.collection.size - 1, CX_LL_LOC_PREV, index); + return cx_linked_list_at(list->end, list->base.collection.size - 1, list->loc_prev, index); } else { - return cx_linked_list_at(list->begin, 0, CX_LL_LOC_NEXT, index); + return cx_linked_list_at(list->begin, 0, list->loc_next, index); } } -static cx_linked_list_node *cx_ll_malloc_node(const struct cx_list_s *list) { - return cxMalloc(list->collection.allocator, - sizeof(cx_linked_list_node) + list->collection.elem_size); +static void *cx_ll_malloc_node(const cx_linked_list *list) { + return cxZalloc(list->base.collection.allocator, + list->loc_data + list->base.collection.elem_size + list->extra_data_len); } static int cx_ll_insert_at( struct cx_list_s *list, - cx_linked_list_node *node, + void *node, const void *elem ) { + cx_linked_list *ll = (cx_linked_list *) list; // create the new new_node - cx_linked_list_node *new_node = cx_ll_malloc_node(list); + void *new_node = cx_ll_malloc_node(ll); // sortir if failed if (new_node == NULL) return 1; - // initialize new new_node - new_node->prev = new_node->next = NULL; + // copy the data if (elem != NULL) { - memcpy(new_node->payload, elem, list->collection.elem_size); + memcpy((char*)new_node + ll->loc_data, elem, list->collection.elem_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, + &ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node, new_node, new_node ); @@ -641,7 +736,7 @@ if (index > list->collection.size || n == 0) return 0; // find position efficiently - cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + void *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; @@ -650,14 +745,17 @@ if (n == 1) return 1; // we now know exactly where we are - node = node == NULL ? ((cx_linked_list *) list)->begin : node->next; + cx_linked_list *ll = (cx_linked_list *) list; + node = node == NULL ? ((cx_linked_list *) list)->begin : CX_LL_PTR(node, ll->loc_next); // we can add the remaining nodes and immediately advance to the inserted node const char *source = array; for (size_t i = 1; i < n; i++) { - source += list->collection.elem_size; + if (source != NULL) { + source += list->collection.elem_size; + } if (0 != cx_ll_insert_at(list, node, source)) return i; - node = node->next; + node = CX_LL_PTR(node, ll->loc_next); } return n; } @@ -671,25 +769,95 @@ if (index > list->collection.size) return NULL; // find position efficiently - cx_linked_list_node *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); + void *node = index == 0 ? NULL : cx_ll_node_at((cx_linked_list *) list, index - 1); // perform first insert if (cx_ll_insert_at(list, node, element)) return NULL; // return a pointer to the data of the inserted node + cx_linked_list *ll = (cx_linked_list *) list; if (node == NULL) { - return ((cx_linked_list *) list)->begin->payload; + return (char*)(ll->begin) + ll->loc_data; } else { - return node->next->payload; + char *next = CX_LL_PTR(node, ll->loc_next); + return next + ll->loc_data; } } static _Thread_local cx_compare_func cx_ll_insert_sorted_cmp_func; +static _Thread_local off_t cx_ll_insert_sorted_loc_data; static int cx_ll_insert_sorted_cmp_helper(const void *l, const void *r) { - const cx_linked_list_node *left = l; - const cx_linked_list_node *right = r; - return cx_ll_insert_sorted_cmp_func(left->payload, right->payload); + const char *left = (const char*)l + cx_ll_insert_sorted_loc_data; + const char *right = (const char*)r + cx_ll_insert_sorted_loc_data; + return cx_ll_insert_sorted_cmp_func(left, right); +} + +static size_t cx_ll_insert_sorted_impl( + struct cx_list_s *list, + const void *array, + size_t n, + bool allow_duplicates +) { + cx_linked_list *ll = (cx_linked_list *) list; + + // special case + if (n == 0) return 0; + + // create a new chain of nodes + void *chain = cx_ll_malloc_node(ll); + if (chain == NULL) return 0; + + memcpy((char*)chain + ll->loc_data, array, list->collection.elem_size); + + // add all elements from the array to that chain + void *prev = chain; + const char *src = array; + size_t inserted = 1; + for (; inserted < n; inserted++) { + void *next = cx_ll_malloc_node(ll); + if (next == NULL) break; + src += list->collection.elem_size; + memcpy((char*)next + ll->loc_data, src, list->collection.elem_size); + CX_LL_PTR(prev, ll->loc_next) = next; + CX_LL_PTR(next, ll->loc_prev) = prev; + prev = next; + } + CX_LL_PTR(prev, ll->loc_next) = NULL; + + // invoke the low level function + cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; + cx_ll_insert_sorted_loc_data = ll->loc_data; + if (allow_duplicates) { + cx_linked_list_insert_sorted_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + } else { + void *duplicates = cx_linked_list_insert_unique_chain( + &ll->begin, + &ll->end, + ll->loc_prev, + ll->loc_next, + chain, + cx_ll_insert_sorted_cmp_helper + ); + list->collection.size += inserted; + // free the nodes that did not make it into the list + while (duplicates != NULL) { + void *next = CX_LL_PTR(duplicates, ll->loc_next); + cxFree(list->collection.allocator, duplicates); + duplicates = next; + list->collection.size--; + } + } + + return inserted; } static size_t cx_ll_insert_sorted( @@ -697,48 +865,15 @@ const void *array, size_t n ) { - // special case - if (n == 0) return 0; - - // create a new chain of nodes - cx_linked_list_node *chain = cx_ll_malloc_node(list); - if (chain == NULL) return 0; - - memcpy(chain->payload, array, list->collection.elem_size); - chain->prev = NULL; - chain->next = NULL; + return cx_ll_insert_sorted_impl(list, array, n, true); +} - // add all elements from the array to that chain - cx_linked_list_node *prev = chain; - const char *src = array; - size_t inserted = 1; - for (; inserted < n; inserted++) { - cx_linked_list_node *next = cx_ll_malloc_node(list); - if (next == NULL) break; - src += list->collection.elem_size; - memcpy(next->payload, src, list->collection.elem_size); - prev->next = next; - next->prev = prev; - prev = next; - } - prev->next = NULL; - - // invoke the low level function - cx_linked_list *ll = (cx_linked_list *) list; - cx_ll_insert_sorted_cmp_func = list->collection.cmpfunc; - cx_linked_list_insert_sorted_chain( - (void **) &ll->begin, - (void **) &ll->end, - CX_LL_LOC_PREV, - CX_LL_LOC_NEXT, - chain, - cx_ll_insert_sorted_cmp_helper - ); - - // adjust the list metadata - list->collection.size += inserted; - - return inserted; +static size_t cx_ll_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + return cx_ll_insert_sorted_impl(list, array, n, false); } static size_t cx_ll_remove( @@ -748,7 +883,7 @@ void *targetbuf ) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = cx_ll_node_at(ll, index); + void *node = cx_ll_node_at(ll, index); // out-of-bounds check if (node == NULL) return 0; @@ -757,8 +892,8 @@ size_t removed = cx_linked_list_remove_chain( (void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, - CX_LL_LOC_NEXT, + ll->loc_prev, + ll->loc_next, node, num ); @@ -768,28 +903,28 @@ // copy or destroy the removed chain if (targetbuf == NULL) { - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // element destruction - cx_invoke_destructor(list, n->payload); + cx_invoke_destructor(list, n + ll->loc_data); // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } } else { char *dest = targetbuf; - cx_linked_list_node *n = node; + char *n = node; for (size_t i = 0; i < removed; i++) { // copy payload - memcpy(dest, n->payload, list->collection.elem_size); + memcpy(dest, n + ll->loc_data, list->collection.elem_size); // advance target buffer dest += list->collection.elem_size; // free the node and advance - void *next = n->next; + void *next = CX_LL_PTR(n, ll->loc_next); cxFree(list->collection.allocator, n); n = next; } @@ -802,10 +937,10 @@ if (list->collection.size == 0) return; cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = ll->begin; + char *node = ll->begin; while (node != NULL) { - cx_invoke_destructor(list, node->payload); - cx_linked_list_node *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -832,14 +967,14 @@ left = j; right = i; } - cx_linked_list_node *nleft = NULL, *nright = NULL; + void *nleft = NULL, *nright = NULL; if (left < mid && right < mid) { // case 1: both items left from mid nleft = cx_ll_node_at(ll, left); assert(nleft != NULL); nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else if (left >= mid && right >= mid) { // case 2: both items right from mid @@ -847,7 +982,7 @@ assert(nright != NULL); nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } else { // case 3: one item left, one item right @@ -873,12 +1008,12 @@ if (closest == left) { nright = nleft; for (size_t c = left; c < right; c++) { - nright = nright->next; + nright = CX_LL_PTR(nright, ll->loc_next); } } else { nleft = nright; for (size_t c = right; c > left; c--) { - nleft = nleft->prev; + nleft = CX_LL_PTR(nleft, ll->loc_prev); } } } else { @@ -891,33 +1026,33 @@ } } - 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; + void *prev = CX_LL_PTR(nleft, ll->loc_prev); + void *next = CX_LL_PTR(nright, ll->loc_next); + void *midstart = CX_LL_PTR(nleft, ll->loc_next); + void *midend = CX_LL_PTR(nright, ll->loc_prev); if (prev == NULL) { ll->begin = nright; } else { - prev->next = nright; + CX_LL_PTR(prev, ll->loc_next) = nright; } - nright->prev = prev; + CX_LL_PTR(nright, ll->loc_prev) = prev; if (midstart == nright) { // special case: both nodes are adjacent - nright->next = nleft; - nleft->prev = nright; + CX_LL_PTR(nright, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = nright; } else { // likely case: a chain is between the two nodes - nright->next = midstart; - midstart->prev = nright; - midend->next = nleft; - nleft->prev = midend; + CX_LL_PTR(nright, ll->loc_next) = midstart; + CX_LL_PTR(midstart, ll->loc_prev) = nright; + CX_LL_PTR(midend, ll->loc_next) = nleft; + CX_LL_PTR(nleft, ll->loc_prev) = midend; } - nleft->next = next; + CX_LL_PTR(nleft, ll->loc_next) = next; if (next == NULL) { ll->end = nleft; } else { - next->prev = nleft; + CX_LL_PTR(next, ll->loc_prev) = nleft; } return 0; @@ -928,8 +1063,8 @@ 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; + char *node = cx_ll_node_at(ll, index); + return node == NULL ? NULL : node + ll->loc_data; } static size_t cx_ll_find_remove( @@ -940,10 +1075,10 @@ if (list->collection.size == 0) return 0; size_t index; - cx_linked_list *ll = ((cx_linked_list *) list); - cx_linked_list_node *node = cx_linked_list_find( + cx_linked_list *ll = (cx_linked_list *) list; + char *node = cx_linked_list_find( ll->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + ll->loc_next, ll->loc_data, list->collection.cmpfunc, elem, &index ); @@ -951,9 +1086,9 @@ return list->collection.size; } if (remove) { - cx_invoke_destructor(list, node->payload); - cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; cxFree(list->collection.allocator, node); } @@ -962,14 +1097,14 @@ 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, + cx_linked_list_sort(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, ll->loc_data, list->collection.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); + cx_linked_list_reverse(&ll->begin, &ll->end, ll->loc_prev, ll->loc_next); } static int cx_ll_compare( @@ -978,8 +1113,10 @@ ) { cx_linked_list *left = (cx_linked_list *) list; cx_linked_list *right = (cx_linked_list *) other; + assert(left->loc_next == right->loc_next); + assert(left->loc_data == right->loc_data); return cx_linked_list_compare(left->begin, right->begin, - CX_LL_LOC_NEXT, CX_LL_LOC_DATA, + left->loc_next, left->loc_data, list->collection.cmpfunc); } @@ -990,49 +1127,52 @@ static void cx_ll_iter_next(void *it) { struct cx_iterator_s *iter = it; + cx_linked_list *ll = iter->src_handle; if (iter->base.remove) { iter->base.remove = false; - struct cx_list_s *list = iter->src_handle.m; - cx_linked_list *ll = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->next; - cx_invoke_destructor(list, node->payload); - cx_linked_list_remove((void **) &ll->begin, (void **) &ll->end, - CX_LL_LOC_PREV, CX_LL_LOC_NEXT, node); + struct cx_list_s *list = iter->src_handle; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index++; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->next; + void *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_next); } } static void cx_ll_iter_prev(void *it) { struct cx_iterator_s *iter = it; + cx_linked_list *ll = iter->src_handle; if (iter->base.remove) { iter->base.remove = false; - struct cx_list_s *list = iter->src_handle.m; - cx_linked_list *ll = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->prev; + struct cx_list_s *list = iter->src_handle; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_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); + cx_invoke_destructor(list, node + ll->loc_data); + cx_linked_list_remove(&ll->begin, &ll->end, + ll->loc_prev, ll->loc_next, node); list->collection.size--; + iter->elem_count--; cxFree(list->collection.allocator, node); } else { iter->index--; - cx_linked_list_node *node = iter->elem_handle; - iter->elem_handle = node->prev; + char *node = iter->elem_handle; + iter->elem_handle = CX_LL_PTR(node, ll->loc_prev); } } static void *cx_ll_iter_current(const void *it) { const struct cx_iterator_s *iter = it; - cx_linked_list_node *node = iter->elem_handle; - return node->payload; + const cx_linked_list *ll = iter->src_handle; + char *node = iter->elem_handle; + return node + ll->loc_data; } static CxIterator cx_ll_iterator( @@ -1042,14 +1182,14 @@ ) { CxIterator iter; iter.index = index; - iter.src_handle.c = list; + iter.src_handle = (void*)list; iter.elem_handle = cx_ll_node_at((const cx_linked_list *) list, index); iter.elem_size = list->collection.elem_size; iter.elem_count = list->collection.size; iter.base.valid = cx_ll_iter_valid; iter.base.current = cx_ll_iter_current; iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next; - iter.base.mutating = false; + iter.base.allow_remove = true; iter.base.remove = false; return iter; } @@ -1059,11 +1199,12 @@ const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle.m; - cx_linked_list_node *node = iter->elem_handle; + struct cx_list_s *list = iter->src_handle; + cx_linked_list *ll = iter->src_handle; + void *node = iter->elem_handle; if (node != NULL) { assert(prepend >= 0 && prepend <= 1); - cx_linked_list_node *choice[2] = {node, node->prev}; + void *choice[2] = {node, CX_LL_PTR(node, ll->loc_prev)}; int result = cx_ll_insert_at(list, choice[prepend], elem); if (result == 0) { iter->elem_count++; @@ -1085,10 +1226,10 @@ static void cx_ll_destructor(CxList *list) { cx_linked_list *ll = (cx_linked_list *) list; - cx_linked_list_node *node = ll->begin; + char *node = ll->begin; while (node) { - cx_invoke_destructor(list, node->payload); - void *next = node->next; + cx_invoke_destructor(list, node + ll->loc_data); + void *next = CX_LL_PTR(node, ll->loc_next); cxFree(list->collection.allocator, node); node = next; } @@ -1101,6 +1242,7 @@ cx_ll_insert_element, cx_ll_insert_array, cx_ll_insert_sorted, + cx_ll_insert_unique, cx_ll_insert_iter, cx_ll_remove, cx_ll_clear, @@ -1124,6 +1266,10 @@ cx_linked_list *list = cxCalloc(allocator, 1, sizeof(cx_linked_list)); if (list == NULL) return NULL; + list->extra_data_len = 0; + list->loc_prev = 0; + list->loc_next = sizeof(void*); + list->loc_data = sizeof(void*)*2; cx_list_init((CxList*)list, &cx_linked_list_class, allocator, comparator, elem_size);
--- a/ucx/list.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/list.c Sat Nov 08 23:06:11 2025 +0100 @@ -29,6 +29,7 @@ #include "cx/list.h" #include <string.h> +#include <assert.h> // <editor-fold desc="Store Pointers Functionality"> @@ -38,10 +39,18 @@ const void *l, const void *r ) { + // l and r are guaranteed to be non-NULL pointing to the list's memory void *const *lptr = l; void *const *rptr = r; - const void *left = lptr == NULL ? NULL : *lptr; - const void *right = rptr == NULL ? NULL : *rptr; + const void *left = *lptr; + const void *right = *rptr; + if (left == NULL) { + // NULL is smaller than any value except NULL + return right == NULL ? 0 : -1; + } else if (right == NULL) { + // any value is larger than NULL + return 1; + } return cx_pl_cmpfunc_impl(left, right); } @@ -90,12 +99,23 @@ return result; } +static size_t cx_pl_insert_unique( + struct cx_list_s *list, + const void *array, + size_t n +) { + cx_pl_hack_cmpfunc(list); + size_t result = list->climpl->insert_unique(list, array, n); + cx_pl_unhack_cmpfunc(list); + return result; +} + static int cx_pl_insert_iter( struct cx_iterator_s *iter, const void *elem, int prepend ) { - struct cx_list_s *list = iter->src_handle.m; + struct cx_list_s *list = iter->src_handle; return list->climpl->insert_iter(iter, &elem, prepend); } @@ -181,6 +201,7 @@ cx_pl_insert_element, cx_pl_insert_array, cx_pl_insert_sorted, + cx_pl_insert_unique, cx_pl_insert_iter, cx_pl_remove, cx_pl_clear, @@ -225,7 +246,7 @@ cx_attr_unused bool backwards ) { CxIterator iter = {0}; - iter.src_handle.c = list; + iter.src_handle = (void*) list; iter.index = index; iter.base.valid = cx_emptyl_iter_valid; return iter; @@ -238,6 +259,7 @@ NULL, NULL, NULL, + NULL, cx_emptyl_noop, NULL, cx_emptyl_at, @@ -278,21 +300,26 @@ const void *data, size_t n ) { - size_t elem_size = list->collection.elem_size; const char *src = data; size_t i = 0; for (; i < n; i++) { if (NULL == invoke_list_func( - insert_element, list, index + i, - src + (i * elem_size))) return i; + insert_element, list, index + i, src) + ) { + return i; // LCOV_EXCL_LINE + } + if (src != NULL) { + src += list->collection.elem_size; + } } return i; } -size_t cx_list_default_insert_sorted( +static size_t cx_list_default_insert_sorted_impl( struct cx_list_s *list, const void *sorted_data, - size_t n + size_t n, + bool allow_duplicates ) { // corner case if (n == 0) return 0; @@ -302,22 +329,54 @@ const char *src = sorted_data; // track indices and number of inserted items - size_t di = 0, si = 0, inserted = 0; + size_t di = 0, si = 0, processed = 0; // search the list for insertion points - for (; di < list->collection.size; di++) { + while (di < list->collection.size) { const void *list_elm = invoke_list_func(at, list, di); - // compare current list element with first source element - // if less or equal, skip - if (cmp(list_elm, src) <= 0) { - continue; + // compare the current list element with the first source element + // if less, skip the list elements + // if equal, skip the list elements and optionally the source elements + { + int d = cmp(list_elm, src); + if (d <= 0) { + if (!allow_duplicates && d == 0) { + src += elem_size; + si++; + processed++; // we also count duplicates for the return value + while (si < n && cmp(list_elm, src) == 0) { + src += elem_size; + si++; + processed++; + } + if (processed == n) { + return processed; + } + } + di++; + continue; + } } - // determine number of consecutive elements that can be inserted - size_t ins = 1; + // determine the number of consecutive elements that can be inserted + size_t ins = 1, skip = 0; const char *next = src; while (++si < n) { + if (!allow_duplicates) { + // skip duplicates within the source + if (cmp(next, next + elem_size) == 0) { + next += elem_size; + skip++; + continue; + } else { + if (skip > 0) { + // if we had to skip something, we must wait for the next run + next += elem_size; + break; + } + } + } next += elem_size; // once we become larger than the list elem, break if (cmp(list_elm, next) <= 0) { @@ -329,33 +388,70 @@ // insert the elements at location si if (ins == 1) { - if (NULL == invoke_list_func( - insert_element, list, di, src)) return inserted; + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } } else { size_t r = invoke_list_func(insert_array, list, di, src, ins); - if (r < ins) return inserted + r; + if (r < ins) { + return processed + r; // LCOV_EXCL_LINE + } } - inserted += ins; + processed += ins + skip; di += ins; // everything inserted? - if (inserted == n) return inserted; + if (processed == n) { + return processed; + } src = next; } // insert remaining items if (si < n) { - inserted += invoke_list_func(insert_array, list, di, src, n - si); + if (allow_duplicates) { + processed += invoke_list_func(insert_array, list, di, src, n - si); + } else { + const void *last = di == 0 ? NULL : invoke_list_func(at, list, di - 1); + for (; si < n; si++) { + // skip duplicates within the source + if (last == NULL || cmp(last, src) != 0) { + if (NULL == invoke_list_func(insert_element, list, di, src)) { + return processed; // LCOV_EXCL_LINE + } + last = src; + di++; + } + processed++; + src += elem_size; + } + } } - return inserted; + return processed; +} + +size_t cx_list_default_insert_sorted( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, true); +} + +size_t cx_list_default_insert_unique( + struct cx_list_s *list, + const void *sorted_data, + size_t n +) { + return cx_list_default_insert_sorted_impl(list, sorted_data, n, false); } void cx_list_default_sort(struct cx_list_s *list) { size_t elem_size = list->collection.elem_size; size_t list_size = list->collection.size; void *tmp = cxMallocDefault(elem_size * list_size); - if (tmp == NULL) abort(); + if (tmp == NULL) abort(); // LCOV_EXCL_LINE // copy elements from source array char *loc = tmp; @@ -388,7 +484,7 @@ size_t elem_size = list->collection.elem_size; void *tmp = cxMallocDefault(elem_size); - if (tmp == NULL) return 1; + if (tmp == NULL) return 1; // LCOV_EXCL_LINE void *ip = invoke_list_func(at, list, i); void *jp = invoke_list_func(at, list, j); @@ -472,36 +568,174 @@ } } -CxIterator cxListMutIteratorAt( - CxList *list, - size_t index -) { - if (list == NULL) list = cxEmptyList; - CxIterator it = list->cl->iterator(list, index, false); - it.base.mutating = true; - return it; +size_t cxListSize(const CxList *list) { + return list->collection.size; +} + +int cxListAdd(CxList *list, const void *elem) { + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, elem) == NULL; +} + +size_t cxListAddArray(CxList *list, const void *array, size_t n) { + list->collection.sorted = false; + return list->cl->insert_array(list, list->collection.size, array, n); +} + +int cxListInsert(CxList *list, size_t index, const void *elem) { + list->collection.sorted = false; + return list->cl->insert_element(list, index, elem) == NULL; +} + +void *cxListEmplaceAt(CxList *list, size_t index) { + list->collection.sorted = false; + return list->cl->insert_element(list, index, NULL); +} + +void *cxListEmplace(CxList *list) { + list->collection.sorted = false; + return list->cl->insert_element(list, list->collection.size, NULL); +} + +static bool cx_list_emplace_iterator_valid(const void *it) { + const CxIterator *iter = it; + return iter->index < iter->elem_count; +} + +CxIterator cxListEmplaceArrayAt(CxList *list, size_t index, size_t n) { + list->collection.sorted = false; + size_t c = list->cl->insert_array(list, index, NULL, n); + CxIterator iter = list->cl->iterator(list, index, false); + // tweak the fields of this iterator + iter.elem_count = c; + iter.index = 0; + // replace the valid function to abort iteration when c is reached + iter.base.valid = cx_list_emplace_iterator_valid; + // if we are storing pointers, we want to return the pure pointers. + // therefore, we must unwrap the "current" method + if (list->collection.store_pointer) { + iter.base.current = iter.base.current_impl; + } + return iter; +} + +CxIterator cxListEmplaceArray(CxList *list, size_t n) { + return cxListEmplaceArrayAt(list, list->collection.size, n); +} + +int cxListInsertSorted(CxList *list, const void *elem) { + assert(cxCollectionSorted(list)); + list->collection.sorted = true; + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_sorted(list, data, 1) == 0; +} + +int cxListInsertUnique(CxList *list, const void *elem) { + if (cxCollectionSorted(list)) { + list->collection.sorted = true; + const void *data = list->collection.store_pointer ? &elem : elem; + return list->cl->insert_unique(list, data, 1) == 0; + } else { + if (cxListContains(list, elem)) { + return 0; + } else { + return cxListAdd(list, elem); + } + } +} + +size_t cxListInsertArray(CxList *list, size_t index, const void *array, size_t n) { + list->collection.sorted = false; + return list->cl->insert_array(list, index, array, n); } -CxIterator cxListMutBackwardsIteratorAt( - CxList *list, - size_t index -) { - if (list == NULL) list = cxEmptyList; - CxIterator it = list->cl->iterator(list, index, true); - it.base.mutating = true; - return it; +size_t cxListInsertSortedArray(CxList *list, const void *array, size_t n) { + assert(cxCollectionSorted(list)); + list->collection.sorted = true; + return list->cl->insert_sorted(list, array, n); +} + +size_t cxListInsertUniqueArray(CxList *list, const void *array, size_t n) { + if (cxCollectionSorted(list)) { + list->collection.sorted = true; + return list->cl->insert_unique(list, array, n); + } else { + const char *source = array; + for (size_t i = 0 ; i < n; i++) { + // note: this also checks elements added in a previous iteration + const void *data = list->collection.store_pointer ? + *((const void**)source) : source; + if (!cxListContains(list, data)) { + if (cxListAdd(list, data)) { + return i; // LCOV_EXCL_LINE + } + } + source += list->collection.elem_size; + } + return n; + } +} + +int cxListInsertAfter(CxIterator *iter, const void *elem) { + CxList* list = (CxList*)iter->src_handle; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 0); +} + +int cxListInsertBefore(CxIterator *iter, const void *elem) { + CxList* list = (CxList*)iter->src_handle; + list->collection.sorted = false; + return list->cl->insert_iter(iter, elem, 1); +} + +int cxListRemove(CxList *list, size_t index) { + return list->cl->remove(list, index, 1, NULL) == 0; } -void cxListFree(CxList *list) { - if (list == NULL) return; - list->cl->deallocate(list); +int cxListRemoveAndGet(CxList *list, size_t index, void *targetbuf) { + return list->cl->remove(list, index, 1, targetbuf) == 0; +} + +int cxListRemoveAndGetFirst(CxList *list, void *targetbuf) { + return list->cl->remove(list, 0, 1, targetbuf) == 0; +} + +int cxListRemoveAndGetLast(CxList *list, void *targetbuf) { + // note: index may wrap - member function will catch that + return list->cl->remove(list, list->collection.size - 1, 1, targetbuf) == 0; +} + +size_t cxListRemoveArray(CxList *list, size_t index, size_t num) { + return list->cl->remove(list, index, num, NULL); +} + +size_t cxListRemoveArrayAndGet(CxList *list, size_t index, size_t num, void *targetbuf) { + return list->cl->remove(list, index, num, targetbuf); } -int cxListSet( - CxList *list, - size_t index, - const void *elem -) { +void cxListClear(CxList *list) { + list->cl->clear(list); + list->collection.sorted = true; // empty lists are always sorted +} + +int cxListSwap(CxList *list, size_t i, size_t j) { + list->collection.sorted = false; + return list->cl->swap(list, i, j); +} + +void *cxListAt(const CxList *list, size_t index) { + return list->cl->at(list, index); +} + +void *cxListFirst(const CxList *list) { + return list->cl->at(list, 0); +} + +void *cxListLast(const CxList *list) { + return list->cl->at(list, list->collection.size - 1); +} + +int cxListSet(CxList *list, size_t index, const void *elem) { if (index >= list->collection.size) { return 1; } @@ -517,3 +751,330 @@ return 0; } + +CxIterator cxListIteratorAt(const CxList *list, size_t index) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, index, false); +} + +CxIterator cxListBackwardsIteratorAt(const CxList *list, size_t index) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, index, true); +} + +CxIterator cxListIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, 0, false); +} + +CxIterator cxListBackwardsIterator(const CxList *list) { + if (list == NULL) list = cxEmptyList; + return list->cl->iterator(list, list->collection.size - 1, true); +} + +size_t cxListFind(const CxList *list, const void *elem) { + return list->cl->find_remove((CxList*)list, elem, false); +} + +bool cxListContains(const CxList* list, const void* elem) { + return list->cl->find_remove((CxList*)list, elem, false) < list->collection.size; +} + +bool cxListIndexValid(const CxList *list, size_t index) { + return index < list->collection.size; +} + +size_t cxListFindRemove(CxList *list, const void *elem) { + return list->cl->find_remove(list, elem, true); +} + +void cxListSort(CxList *list) { + if (list->collection.sorted) return; + list->cl->sort(list); + list->collection.sorted = true; +} + +void cxListReverse(CxList *list) { + // still sorted, but not according to the cmp_func + list->collection.sorted = false; + list->cl->reverse(list); +} + +void cxListFree(CxList *list) { + if (list == NULL) return; + list->cl->deallocate(list); +} + +static void cx_list_pop_uninitialized_elements(CxList *list, size_t n) { + cx_destructor_func destr_bak = list->collection.simple_destructor; + cx_destructor_func2 destr2_bak = list->collection.advanced_destructor; + list->collection.simple_destructor = NULL; + list->collection.advanced_destructor = NULL; + if (n == 1) { + cxListRemove(list, list->collection.size - 1); + } else { + cxListRemoveArray(list,list->collection.size - n, n); + } + list->collection.simple_destructor = destr_bak; + list->collection.advanced_destructor = destr2_bak; +} + +int cxListClone(CxList *dst, const CxList *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // remember the original size + size_t orig_size = dst->collection.size; + + // first, try to allocate the memory in the new list + CxIterator empl_iter = cxListEmplaceArray(dst, src->collection.size); + + // get an iterator over the source elements + CxIterator src_iter = cxListIterator(src); + + // now clone the elements + size_t cloned = empl_iter.elem_count; + for (size_t i = 0 ; i < empl_iter.elem_count; i++) { + void *src_elem = cxIteratorCurrent(src_iter); + void **dest_memory = cxIteratorCurrent(empl_iter); + void *target = cxCollectionStoresPointers(dst) ? NULL : dest_memory; + void *dest_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dest_ptr == NULL) { + cloned = i; + break; + } + if (cxCollectionStoresPointers(dst)) { + *dest_memory = dest_ptr; + } + cxIteratorNext(src_iter); + cxIteratorNext(empl_iter); + } + + // if we could not clone everything, free the allocated memory + // (disable the destructors!) + if (cloned < src->collection.size) { + cx_list_pop_uninitialized_elements(dst, + dst->collection.size - cloned - orig_size); + return 1; + } + + return 0; +} + +int cxListDifference(CxList *dst, + const CxList *minuend, const CxList *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(minuend) && cxCollectionSorted(subtrahend)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator min_iter = cxListIterator(minuend); + CxIterator sub_iter = cxListIterator(subtrahend); + while (cxIteratorValid(min_iter)) { + void *min_elem = cxIteratorCurrent(min_iter); + void *sub_elem; + int d; + if (cxIteratorValid(sub_iter)) { + sub_elem = cxIteratorCurrent(sub_iter); + cx_compare_func cmp = subtrahend->collection.cmpfunc; + d = cmp(sub_elem, min_elem); + } else { + // no more elements in the subtrahend, + // i.e., the min_elem is larger than any elem of the subtrahend + d = 1; + } + if (d == 0) { + // is contained, so skip it + cxIteratorNext(min_iter); + } else if (d < 0) { + // subtrahend is smaller than minuend, + // check the next element + cxIteratorNext(sub_iter); + } else { + // subtrahend is larger than the dst element, + // clone the minuend and advance + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, min_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(min_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator min_iter = cxListIterator(minuend); + cx_foreach(void *, elem, min_iter) { + if (cxListContains(subtrahend, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListIntersection(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) && cxIteratorValid(other_iter)) { + void *src_elem = cxIteratorCurrent(src_iter); + void *other_elem = cxIteratorCurrent(other_iter); + int d = src->collection.cmpfunc(src_elem, other_elem); + if (d == 0) { + // is contained, clone it + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } else if (d < 0) { + // the other element is larger, skip the source element + cxIteratorNext(src_iter); + } else { + // the source element is larger, try to find it in the other list + cxIteratorNext(other_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + CxIterator src_iter = cxListIterator(src); + cx_foreach(void *, elem, src_iter) { + if (!cxListContains(other, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +} + +int cxListUnion(CxList *dst, + const CxList *src, const CxList *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + // optimize for sorted collections + if (cxCollectionSorted(src) && cxCollectionSorted(other)) { + bool dst_was_empty = cxCollectionSize(dst) == 0; + + CxIterator src_iter = cxListIterator(src); + CxIterator other_iter = cxListIterator(other); + while (cxIteratorValid(src_iter) || cxIteratorValid(other_iter)) { + void *src_elem, *other_elem; + int d; + if (!cxIteratorValid(src_iter)) { + other_elem = cxIteratorCurrent(other_iter); + d = 1; + } else if (!cxIteratorValid(other_iter)) { + src_elem = cxIteratorCurrent(src_iter); + d = -1; + } else { + src_elem = cxIteratorCurrent(src_iter); + other_elem = cxIteratorCurrent(other_iter); + d = src->collection.cmpfunc(src_elem, other_elem); + } + if (d <= 0) { + // source element is smaller or equal, clone it + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, src_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + // if the other element was equal, skip it + if (d == 0) { + cxIteratorNext(other_iter); + } + } else { + // the other element is smaller, clone it + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, other_elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(other_iter); + } + } + + // if dst was empty, it is now guaranteed to be sorted + dst->collection.sorted = dst_was_empty; + } else { + if (cxListClone(dst, src, clone_func, clone_allocator, data)) { + return 1; + } + CxIterator other_iter = cxListIterator(other); + cx_foreach(void *, elem, other_iter) { + if (cxListContains(src, elem)) { + continue; + } + void **dst_mem = cxListEmplace(dst); + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, elem, clone_allocator, data); + if (dst_ptr == NULL) { + cx_list_pop_uninitialized_elements(dst, 1); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + } + + return 0; +}
--- a/ucx/map.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/map.c Sat Nov 08 23:06:11 2025 +0100 @@ -29,6 +29,8 @@ #include "cx/map.h" #include <string.h> +#include "cx/list.h" + // <editor-fold desc="empty map implementation"> static void cx_empty_map_noop(cx_attr_unused CxMap *map) { @@ -51,7 +53,7 @@ cx_attr_unused enum cx_map_iterator_type type ) { CxMapIterator iter = {0}; - iter.map.c = map; + iter.map = (CxMap*) map; iter.base.valid = cx_empty_map_iter_valid; return iter; } @@ -84,28 +86,210 @@ // </editor-fold> -CxMapIterator cxMapMutIteratorValues(CxMap *map) { +void cxMapClear(CxMap *map) { + map->cl->clear(map); +} + +size_t cxMapSize(const CxMap *map) { + return map->collection.size; +} + +CxMapIterator cxMapIteratorValues(const CxMap *map) { if (map == NULL) map = cxEmptyMap; - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); - it.base.mutating = true; - return it; + return map->cl->iterator(map, CX_MAP_ITERATOR_VALUES); +} + +CxMapIterator cxMapIteratorKeys(const CxMap *map) { + if (map == NULL) map = cxEmptyMap; + return map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); } -CxMapIterator cxMapMutIteratorKeys(CxMap *map) { +CxMapIterator cxMapIterator(const CxMap *map) { if (map == NULL) map = cxEmptyMap; - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_KEYS); - it.base.mutating = true; - return it; + return map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); +} + +int cx_map_put(CxMap *map, CxHashKey key, void *value) { + return map->cl->put(map, key, value) == NULL; } -CxMapIterator cxMapMutIterator(CxMap *map) { - if (map == NULL) map = cxEmptyMap; - CxMapIterator it = map->cl->iterator(map, CX_MAP_ITERATOR_PAIRS); - it.base.mutating = true; - return it; +void *cx_map_emplace(CxMap *map, CxHashKey key) { + return map->cl->put(map, key, NULL); +} + +void *cx_map_get(const CxMap *map, CxHashKey key) { + return map->cl->get(map, key); +} + +int cx_map_remove(CxMap *map, CxHashKey key, void *targetbuf) { + return map->cl->remove(map, key, targetbuf); } void cxMapFree(CxMap *map) { if (map == NULL) return; map->cl->deallocate(map); } + +static void cx_map_remove_uninitialized_entry(CxMap *map, CxHashKey key) { + cx_destructor_func destr_bak = map->collection.simple_destructor; + cx_destructor_func2 destr2_bak = map->collection.advanced_destructor; + map->collection.simple_destructor = NULL; + map->collection.advanced_destructor = NULL; + cxMapRemove(map, key); + map->collection.simple_destructor = destr_bak; + map->collection.advanced_destructor = destr2_bak; +} + +int cxMapClone(CxMap *dst, const CxMap *src, cx_clone_func clone_func, + const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + CxMapIterator src_iter = cxMapIterator(src); + for (size_t i = 0; i < cxMapSize(src); i++) { + const CxMapEntry *entry = cxIteratorCurrent(src_iter); + void **dst_mem = cxMapEmplace(dst, *(entry->key)); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void *dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + cxIteratorNext(src_iter); + } + return 0; +} + +int cxMapDifference(CxMap *dst, const CxMap *minuend, const CxMap *subtrahend, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(minuend); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(subtrahend, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListDifference(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapIntersection(CxMap *dst, const CxMap *src, const CxMap *other, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxMapContains(other, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapListIntersection(CxMap *dst, const CxMap *src, const CxList *keys, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (!cxListContains(keys, entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +} + +int cxMapUnion(CxMap *dst, const CxMap *src, + cx_clone_func clone_func, const CxAllocator *clone_allocator, void *data) { + if (clone_allocator == NULL) clone_allocator = cxDefaultAllocator; + + CxMapIterator src_iter = cxMapIterator(src); + cx_foreach(const CxMapEntry *, entry, src_iter) { + if (cxMapContains(dst, *entry->key)) { + continue; + } + void** dst_mem = cxMapEmplace(dst, *entry->key); + if (dst_mem == NULL) { + return 1; // LCOV_EXCL_LINE + } + void *target = cxCollectionStoresPointers(dst) ? NULL : dst_mem; + void* dst_ptr = clone_func(target, entry->value, clone_allocator, data); + if (dst_ptr == NULL) { + cx_map_remove_uninitialized_entry(dst, *(entry->key)); + return 1; + } + if (cxCollectionStoresPointers(dst)) { + *dst_mem = dst_ptr; + } + } + return 0; +}
--- a/ucx/mempool.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/mempool.c Sat Nov 08 23:06:11 2025 +0100 @@ -116,6 +116,9 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory_s)); @@ -124,11 +127,11 @@ if (mem->destructor) { mem->destructor(mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -179,18 +182,18 @@ } static void cx_mempool_free_all_simple(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -247,6 +250,9 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + struct cx_mempool_memory2_s *mem = (void*) ((char *) ptr - sizeof(struct cx_mempool_memory2_s)); @@ -255,11 +261,11 @@ if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (pool->destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); size_t last_index = pool->size - 1; @@ -310,18 +316,18 @@ } static void cx_mempool_free_all_advanced(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { struct cx_mempool_memory2_s *mem = pool->data[i]; if (mem->destructor) { mem->destructor(mem->data, mem->c); } - if (has_destr) { - pool->destr(mem->c); + if (destr != NULL) { + destr(mem->c); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem->c); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem->c); } cxFree(pool->base_allocator, mem); } @@ -376,13 +382,16 @@ if (!ptr) return; struct cx_mempool_s *pool = p; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; + for (size_t i = 0; i < pool->size; i++) { if (ptr == pool->data[i]) { - if (pool->destr) { - pool->destr(ptr); + if (destr != NULL) { + destr(ptr); } - if (pool->destr2) { - pool->destr2(pool->destr2_data, ptr); + if (destr2 != NULL) { + destr2(pool->destr2_data, ptr); } cxFree(pool->base_allocator, ptr); size_t last_index = pool->size - 1; @@ -427,15 +436,15 @@ } static void cx_mempool_free_all_pure(const struct cx_mempool_s *pool) { - const bool has_destr = pool->destr; - const bool has_destr2 = pool->destr2; + cx_destructor_func destr = pool->destr; + cx_destructor_func2 destr2 = pool->destr2; for (size_t i = 0; i < pool->size; i++) { void *mem = pool->data[i]; - if (has_destr) { - pool->destr(mem); + if (destr != NULL) { + destr(mem); } - if (has_destr2) { - pool->destr2(pool->destr2_data, mem); + if (destr2 != NULL) { + destr2(pool->destr2_data, mem); } cxFree(pool->base_allocator, mem); }
--- a/ucx/properties.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/properties.c Sat Nov 08 23:06:11 2025 +0100 @@ -51,6 +51,12 @@ cxBufferDestroy(&prop->buffer); } +void cxPropertiesReset(CxProperties *prop) { + CxPropertiesConfig config = prop->config; + cxPropertiesDestroy(prop); + cxPropertiesInit(prop, config); +} + int cxPropertiesFilln( CxProperties *prop, const char *buf, @@ -244,7 +250,7 @@ CxMap *map = sink->sink; CxAllocator *alloc = sink->data; cxmutstr v = cx_strdup_a(alloc, value); - int r = cx_map_put_cxstr(map, key, v.ptr); + int r = cxMapPut(map, key, v.ptr); if (r != 0) cx_strfree_a(alloc, &v); return r; }
--- a/ucx/string.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/string.c Sat Nov 08 23:06:11 2025 +0100 @@ -25,6 +25,10 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ +#ifdef MEMRCHR_NEED_GNU +#define _GNU_SOURCE +#endif + #include "cx/string.h" #include <string.h> @@ -33,6 +37,7 @@ #include <errno.h> #include <limits.h> #include <float.h> +#include <ctype.h> #ifdef _WIN32 #define cx_strcasecmp_impl _strnicmp @@ -231,19 +236,24 @@ } cxstring cx_strrchr( - cxstring string, - int chr + cxstring string, + int chr ) { +#ifdef WITH_MEMRCHR + char *ret = memrchr(string.ptr, 0xFF & chr, string.length); + if (ret == NULL) return (cxstring) {NULL, 0}; + return (cxstring) {ret, string.length - (ret - string.ptr)}; +#else 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}; +#endif } cxmutstr cx_strrchr_m( @@ -451,7 +461,7 @@ delim, limit, (cxstring **) output); } -int cx_strcmp( +int cx_strcmp_( cxstring s1, cxstring s2 ) { @@ -468,7 +478,7 @@ } } -int cx_strcasecmp( +int cx_strcasecmp_( cxstring s1, cxstring s2 ) { @@ -520,19 +530,13 @@ return result; } -static bool str_isspace(char c) { - // TODO: remove once UCX has public API for this - return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; -} - cxstring cx_strtrim(cxstring string) { cxstring result = string; - // TODO: optimize by comparing multiple bytes at once - while (result.length > 0 && str_isspace(*result.ptr)) { + while (result.length > 0 && isspace((unsigned char)(result.ptr[0]))) { result.ptr++; result.length--; } - while (result.length > 0 && str_isspace(result.ptr[result.length - 1])) { + while (result.length > 0 && isspace((unsigned char)result.ptr[result.length - 1])) { result.length--; } return result; @@ -543,7 +547,7 @@ return (cxmutstr) {(char *) result.ptr, result.length}; } -bool cx_strprefix( +bool cx_strprefix_( cxstring string, cxstring prefix ) { @@ -551,7 +555,7 @@ return memcmp(string.ptr, prefix.ptr, prefix.length) == 0; } -bool cx_strsuffix( +bool cx_strsuffix_( cxstring string, cxstring suffix ) { @@ -560,7 +564,7 @@ suffix.ptr, suffix.length) == 0; } -bool cx_strcaseprefix( +bool cx_strcaseprefix_( cxstring string, cxstring prefix ) { @@ -572,7 +576,7 @@ #endif } -bool cx_strcasesuffix( +bool cx_strcasesuffix_( cxstring string, cxstring suffix ) { @@ -957,11 +961,6 @@ return 0; } -static bool str_isdigit(char c) { - // TODO: remove once UCX has public API for this - return c >= '0' && c <= '9'; -} - int cx_strtod_lc_(cxstring str, double *output, char decsep, const char *groupsep) { // TODO: overflow check // TODO: increase precision @@ -994,7 +993,7 @@ // parse all digits until we find the decsep size_t pos = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { result = result * 10 + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { break; @@ -1023,7 +1022,7 @@ // parse everything until exponent or end double factor = 1.; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { factor *= 0.1; result = result + factor * (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { @@ -1064,7 +1063,7 @@ // parse the exponent unsigned int exp = 0; do { - if (str_isdigit(str.ptr[pos])) { + if (isdigit((unsigned char)str.ptr[pos])) { exp = 10 * exp + (str.ptr[pos] - '0'); } else if (strchr(groupsep, str.ptr[pos]) == NULL) { errno = EINVAL;
--- a/ucx/tree.c Tue Oct 14 21:02:26 2025 +0200 +++ b/ucx/tree.c Sat Nov 08 23:06:11 2025 +0100 @@ -375,7 +375,7 @@ iter.skip = false; // assign base iterator functions - iter.base.mutating = false; + iter.base.allow_remove = false; iter.base.remove = false; iter.base.current_impl = NULL; iter.base.valid = cx_tree_iter_valid; @@ -496,7 +496,7 @@ iter.queue_last = NULL; // assign base iterator functions - iter.base.mutating = false; + iter.base.allow_remove = false; iter.base.remove = false; iter.base.current_impl = NULL; iter.base.valid = cx_tree_visitor_valid; @@ -717,7 +717,7 @@ } // otherwise, create iterator and hand over to other function - CxIterator iter = cxIterator(src, elem_size, num); + CxIterator iter = cxIterator(src, elem_size, num, false); return cx_tree_add_iter(cxIteratorRef(iter), num, sfunc, cfunc, cdata, failed, root, loc_parent, loc_children, loc_last_child, @@ -804,16 +804,12 @@ cx_tree_default_find }; -CxTree *cxTreeCreate( - const CxAllocator *allocator, +CxTree *cxTreeCreate(const CxAllocator *allocator, cx_tree_node_create_func create_func, cx_tree_search_func search_func, cx_tree_search_data_func search_data_func, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next ) { if (allocator == NULL) { allocator = cxDefaultAllocator; @@ -852,15 +848,9 @@ cxFree(tree->allocator, tree); } -CxTree *cxTreeCreateWrapped( - const CxAllocator *allocator, - void *root, - ptrdiff_t loc_parent, - ptrdiff_t loc_children, - ptrdiff_t loc_last_child, - ptrdiff_t loc_prev, - ptrdiff_t loc_next -) { +CxTree *cxTreeCreateWrapped(const CxAllocator *allocator, void *root, + ptrdiff_t loc_parent, ptrdiff_t loc_children, ptrdiff_t loc_last_child, + ptrdiff_t loc_prev, ptrdiff_t loc_next) { if (allocator == NULL) { allocator = cxDefaultAllocator; } @@ -888,11 +878,7 @@ return tree; } -void cxTreeSetParent( - CxTree *tree, - void *parent, - void *child -) { +void cxTreeSetParent(CxTree *tree, void *parent, void *child) { size_t loc_parent = tree->loc_parent; if (tree_parent(child) == NULL) { tree->size++; @@ -900,19 +886,12 @@ cx_tree_link(parent, child, cx_tree_node_layout(tree)); } -void cxTreeAddChildNode( - CxTree *tree, - void *parent, - void *child -) { +void cxTreeAddChildNode(CxTree *tree, void *parent, void *child) { cx_tree_link(parent, child, cx_tree_node_layout(tree)); tree->size++; } -int cxTreeAddChild( - CxTree *tree, - void *parent, - const void *data) { +int cxTreeAddChild(CxTree *tree, void *parent, const void *data) { void *node = tree->node_create(data, tree); if (node == NULL) return 1; cx_tree_zero_pointers(node, cx_tree_node_layout(tree)); @@ -921,6 +900,29 @@ return 0; } +int cxTreeInsert(CxTree *tree, const void *data) { + return tree->cl->insert_element(tree, data); +} + +size_t cxTreeInsertIter(CxTree *tree, CxIteratorBase *iter, size_t n) { + return tree->cl->insert_many(tree, iter, n); +} + +size_t cxTreeInsertArray(CxTree *tree, const void *data, size_t elem_size, size_t n) { + if (n == 0) return 0; + if (n == 1) return 0 == cxTreeInsert(tree, data) ? 1 : 0; + CxIterator iter = cxIterator(data, elem_size, n, false); + return cxTreeInsertIter(tree, cxIteratorRef(iter), n); +} + +void *cxTreeFind( CxTree *tree, const void *data) { + return tree->cl->find(tree, tree->root, data, 0); +} + +void *cxTreeFindInSubtree(CxTree *tree, const void *data, void *subtree_root, size_t max_depth) { + return tree->cl->find(tree, subtree_root, data, max_depth); +} + size_t cxTreeSubtreeSize(CxTree *tree, void *subtree_root) { CxTreeVisitor visitor = cx_tree_visitor( subtree_root, @@ -945,6 +947,10 @@ return visitor.depth; } +size_t cxTreeSize(CxTree *tree) { + return tree->size; +} + size_t cxTreeDepth(CxTree *tree) { CxTreeVisitor visitor = cx_tree_visitor( tree->root, tree->loc_children, tree->loc_next @@ -1052,3 +1058,38 @@ tree->root = NULL; } } + +void cxTreeIteratorDispose(CxTreeIterator *iter) { + cxFreeDefault(iter->stack); + iter->stack = NULL; +} + +void cxTreeVisitorDispose(CxTreeVisitor *visitor) { + struct cx_tree_visitor_queue_s *q = visitor->queue_next; + while (q != NULL) { + struct cx_tree_visitor_queue_s *next = q->next; + cxFreeDefault(q); + q = next; + } +} + +CxTreeIterator cxTreeIterateSubtree(CxTree *tree, void *node, bool visit_on_exit) { + return cx_tree_iterator( + node, visit_on_exit, + tree->loc_children, tree->loc_next + ); +} + +CxTreeVisitor cxTreeVisitSubtree(CxTree *tree, void *node) { + return cx_tree_visitor( + node, tree->loc_children, tree->loc_next + ); +} + +CxTreeIterator cxTreeIterate(CxTree *tree, bool visit_on_exit) { + return cxTreeIterateSubtree(tree, tree->root, visit_on_exit); +} + +CxTreeVisitor cxTreeVisit(CxTree *tree) { + return cxTreeVisitSubtree(tree, tree->root); +}